aboutsummaryrefslogtreecommitdiff
path: root/drivers/staging/media
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/staging/media')
-rw-r--r--drivers/staging/media/Kconfig49
-rw-r--r--drivers/staging/media/Makefile15
-rw-r--r--drivers/staging/media/as102/Kconfig8
-rw-r--r--drivers/staging/media/as102/Makefile6
-rw-r--r--drivers/staging/media/as102/as102_drv.c277
-rw-r--r--drivers/staging/media/as102/as102_drv.h99
-rw-r--r--drivers/staging/media/as102/as102_fe.c571
-rw-r--r--drivers/staging/media/as102/as102_fw.c232
-rw-r--r--drivers/staging/media/as102/as102_fw.h38
-rw-r--r--drivers/staging/media/as102/as102_usb_drv.c472
-rw-r--r--drivers/staging/media/as102/as102_usb_drv.h61
-rw-r--r--drivers/staging/media/as102/as10x_cmd.c418
-rw-r--r--drivers/staging/media/as102/as10x_cmd.h529
-rw-r--r--drivers/staging/media/as102/as10x_cmd_cfg.c206
-rw-r--r--drivers/staging/media/as102/as10x_cmd_stream.c211
-rw-r--r--drivers/staging/media/as102/as10x_handle.h54
-rw-r--r--drivers/staging/media/as102/as10x_types.h194
-rw-r--r--drivers/staging/media/bcm2048/Kconfig13
-rw-r--r--drivers/staging/media/bcm2048/Makefile1
-rw-r--r--drivers/staging/media/bcm2048/TODO24
-rw-r--r--drivers/staging/media/bcm2048/radio-bcm2048.c2744
-rw-r--r--drivers/staging/media/bcm2048/radio-bcm2048.h30
-rw-r--r--drivers/staging/media/cxd2099/Kconfig12
-rw-r--r--drivers/staging/media/cxd2099/Makefile5
-rw-r--r--drivers/staging/media/cxd2099/TODO12
-rw-r--r--drivers/staging/media/cxd2099/cxd2099.c719
-rw-r--r--drivers/staging/media/cxd2099/cxd2099.h51
-rw-r--r--drivers/staging/media/davinci_vpfe/Kconfig9
-rw-r--r--drivers/staging/media/davinci_vpfe/Makefile3
-rw-r--r--drivers/staging/media/davinci_vpfe/TODO37
-rw-r--r--drivers/staging/media/davinci_vpfe/davinci-vpfe-mc.txt154
-rw-r--r--drivers/staging/media/davinci_vpfe/davinci_vpfe_user.h1290
-rw-r--r--drivers/staging/media/davinci_vpfe/dm365_ipipe.c1863
-rw-r--r--drivers/staging/media/davinci_vpfe/dm365_ipipe.h179
-rw-r--r--drivers/staging/media/davinci_vpfe/dm365_ipipe_hw.c1048
-rw-r--r--drivers/staging/media/davinci_vpfe/dm365_ipipe_hw.h559
-rw-r--r--drivers/staging/media/davinci_vpfe/dm365_ipipeif.c1070
-rw-r--r--drivers/staging/media/davinci_vpfe/dm365_ipipeif.h233
-rw-r--r--drivers/staging/media/davinci_vpfe/dm365_ipipeif_user.h93
-rw-r--r--drivers/staging/media/davinci_vpfe/dm365_isif.c2105
-rw-r--r--drivers/staging/media/davinci_vpfe/dm365_isif.h203
-rw-r--r--drivers/staging/media/davinci_vpfe/dm365_isif_regs.h294
-rw-r--r--drivers/staging/media/davinci_vpfe/dm365_resizer.c1999
-rw-r--r--drivers/staging/media/davinci_vpfe/dm365_resizer.h244
-rw-r--r--drivers/staging/media/davinci_vpfe/vpfe.h86
-rw-r--r--drivers/staging/media/davinci_vpfe/vpfe_mc_capture.c724
-rw-r--r--drivers/staging/media/davinci_vpfe/vpfe_mc_capture.h95
-rw-r--r--drivers/staging/media/davinci_vpfe/vpfe_video.c1637
-rw-r--r--drivers/staging/media/davinci_vpfe/vpfe_video.h153
-rw-r--r--drivers/staging/media/dt3155v4l/Kconfig28
-rw-r--r--drivers/staging/media/dt3155v4l/Makefile1
-rw-r--r--drivers/staging/media/dt3155v4l/dt3155v4l.c995
-rw-r--r--drivers/staging/media/dt3155v4l/dt3155v4l.h212
-rw-r--r--drivers/staging/media/go7007/Kconfig51
-rw-r--r--drivers/staging/media/go7007/Makefile15
-rw-r--r--drivers/staging/media/go7007/README137
-rw-r--r--drivers/staging/media/go7007/go7007-driver.c711
-rw-r--r--drivers/staging/media/go7007/go7007-fw.c1628
-rw-r--r--drivers/staging/media/go7007/go7007-i2c.c222
-rw-r--r--drivers/staging/media/go7007/go7007-loader.c145
-rw-r--r--drivers/staging/media/go7007/go7007-priv.h294
-rw-r--r--drivers/staging/media/go7007/go7007-usb.c1349
-rw-r--r--drivers/staging/media/go7007/go7007-v4l2.c1055
-rw-r--r--drivers/staging/media/go7007/go7007.h40
-rw-r--r--drivers/staging/media/go7007/go7007.txt478
-rw-r--r--drivers/staging/media/go7007/s2250-board.c631
-rw-r--r--drivers/staging/media/go7007/saa7134-go7007.c567
-rw-r--r--drivers/staging/media/go7007/snd-go7007.c302
-rw-r--r--drivers/staging/media/lirc/Kconfig72
-rw-r--r--drivers/staging/media/lirc/Makefile13
-rw-r--r--drivers/staging/media/lirc/TODO13
-rw-r--r--drivers/staging/media/lirc/TODO.lirc_zilog36
-rw-r--r--drivers/staging/media/lirc/lirc_bt829.c405
-rw-r--r--drivers/staging/media/lirc/lirc_igorplugusb.c514
-rw-r--r--drivers/staging/media/lirc/lirc_imon.c1042
-rw-r--r--drivers/staging/media/lirc/lirc_parallel.c746
-rw-r--r--drivers/staging/media/lirc/lirc_parallel.h26
-rw-r--r--drivers/staging/media/lirc/lirc_sasem.c934
-rw-r--r--drivers/staging/media/lirc/lirc_serial.c1240
-rw-r--r--drivers/staging/media/lirc/lirc_sir.c1317
-rw-r--r--drivers/staging/media/lirc/lirc_zilog.c1686
-rw-r--r--drivers/staging/media/msi3101/Kconfig10
-rw-r--r--drivers/staging/media/msi3101/Makefile2
-rw-r--r--drivers/staging/media/msi3101/msi001.c500
-rw-r--r--drivers/staging/media/msi3101/sdr-msi3101.c1518
-rw-r--r--drivers/staging/media/omap24xx/Kconfig35
-rw-r--r--drivers/staging/media/omap24xx/Makefile5
-rw-r--r--drivers/staging/media/omap24xx/omap24xxcam-dma.c601
-rw-r--r--drivers/staging/media/omap24xx/omap24xxcam.c1888
-rw-r--r--drivers/staging/media/omap24xx/omap24xxcam.h596
-rw-r--r--drivers/staging/media/omap24xx/tcm825x.c937
-rw-r--r--drivers/staging/media/omap24xx/tcm825x.h200
-rw-r--r--drivers/staging/media/omap24xx/v4l2-int-device.c164
-rw-r--r--drivers/staging/media/omap24xx/v4l2-int-device.h305
-rw-r--r--drivers/staging/media/omap4iss/Kconfig6
-rw-r--r--drivers/staging/media/omap4iss/Makefile6
-rw-r--r--drivers/staging/media/omap4iss/TODO4
-rw-r--r--drivers/staging/media/omap4iss/iss.c1557
-rw-r--r--drivers/staging/media/omap4iss/iss.h250
-rw-r--r--drivers/staging/media/omap4iss/iss_csi2.c1326
-rw-r--r--drivers/staging/media/omap4iss/iss_csi2.h158
-rw-r--r--drivers/staging/media/omap4iss/iss_csiphy.c279
-rw-r--r--drivers/staging/media/omap4iss/iss_csiphy.h51
-rw-r--r--drivers/staging/media/omap4iss/iss_ipipe.c570
-rw-r--r--drivers/staging/media/omap4iss/iss_ipipe.h67
-rw-r--r--drivers/staging/media/omap4iss/iss_ipipeif.c849
-rw-r--r--drivers/staging/media/omap4iss/iss_ipipeif.h92
-rw-r--r--drivers/staging/media/omap4iss/iss_regs.h901
-rw-r--r--drivers/staging/media/omap4iss/iss_resizer.c893
-rw-r--r--drivers/staging/media/omap4iss/iss_resizer.h75
-rw-r--r--drivers/staging/media/omap4iss/iss_video.c1226
-rw-r--r--drivers/staging/media/omap4iss/iss_video.h204
-rw-r--r--drivers/staging/media/rtl2832u_sdr/Kconfig7
-rw-r--r--drivers/staging/media/rtl2832u_sdr/Makefile6
-rw-r--r--drivers/staging/media/rtl2832u_sdr/rtl2832_sdr.c1497
-rw-r--r--drivers/staging/media/rtl2832u_sdr/rtl2832_sdr.h54
-rw-r--r--drivers/staging/media/sn9c102/Kconfig17
-rw-r--r--drivers/staging/media/sn9c102/Makefile15
-rw-r--r--drivers/staging/media/sn9c102/sn9c102.h214
-rw-r--r--drivers/staging/media/sn9c102/sn9c102.txt592
-rw-r--r--drivers/staging/media/sn9c102/sn9c102_config.h86
-rw-r--r--drivers/staging/media/sn9c102/sn9c102_core.c3465
-rw-r--r--drivers/staging/media/sn9c102/sn9c102_devtable.h145
-rw-r--r--drivers/staging/media/sn9c102/sn9c102_hv7131d.c269
-rw-r--r--drivers/staging/media/sn9c102/sn9c102_hv7131r.c369
-rw-r--r--drivers/staging/media/sn9c102/sn9c102_mi0343.c352
-rw-r--r--drivers/staging/media/sn9c102/sn9c102_mi0360.c453
-rw-r--r--drivers/staging/media/sn9c102/sn9c102_mt9v111.c260
-rw-r--r--drivers/staging/media/sn9c102/sn9c102_ov7630.c634
-rw-r--r--drivers/staging/media/sn9c102/sn9c102_ov7660.c546
-rw-r--r--drivers/staging/media/sn9c102/sn9c102_pas106b.c308
-rw-r--r--drivers/staging/media/sn9c102/sn9c102_pas202bcb.c340
-rw-r--r--drivers/staging/media/sn9c102/sn9c102_sensor.h307
-rw-r--r--drivers/staging/media/sn9c102/sn9c102_tas5110c1b.c154
-rw-r--r--drivers/staging/media/sn9c102/sn9c102_tas5110d.c119
-rw-r--r--drivers/staging/media/sn9c102/sn9c102_tas5130d1b.c165
-rw-r--r--drivers/staging/media/solo6x10/Kconfig18
-rw-r--r--drivers/staging/media/solo6x10/Makefile5
-rw-r--r--drivers/staging/media/solo6x10/TODO15
-rw-r--r--drivers/staging/media/solo6x10/solo6x10-core.c709
-rw-r--r--drivers/staging/media/solo6x10/solo6x10-disp.c322
-rw-r--r--drivers/staging/media/solo6x10/solo6x10-eeprom.c154
-rw-r--r--drivers/staging/media/solo6x10/solo6x10-enc.c344
-rw-r--r--drivers/staging/media/solo6x10/solo6x10-g723.c424
-rw-r--r--drivers/staging/media/solo6x10/solo6x10-gpio.c109
-rw-r--r--drivers/staging/media/solo6x10/solo6x10-i2c.c334
-rw-r--r--drivers/staging/media/solo6x10/solo6x10-jpeg.h193
-rw-r--r--drivers/staging/media/solo6x10/solo6x10-offsets.h87
-rw-r--r--drivers/staging/media/solo6x10/solo6x10-p2m.c333
-rw-r--r--drivers/staging/media/solo6x10/solo6x10-regs.h639
-rw-r--r--drivers/staging/media/solo6x10/solo6x10-tw28.c874
-rw-r--r--drivers/staging/media/solo6x10/solo6x10-tw28.h69
-rw-r--r--drivers/staging/media/solo6x10/solo6x10-v4l2-enc.c1414
-rw-r--r--drivers/staging/media/solo6x10/solo6x10-v4l2.c734
-rw-r--r--drivers/staging/media/solo6x10/solo6x10.h426
155 files changed, 70886 insertions, 0 deletions
diff --git a/drivers/staging/media/Kconfig b/drivers/staging/media/Kconfig
new file mode 100644
index 00000000000..a9f2e63a7c9
--- /dev/null
+++ b/drivers/staging/media/Kconfig
@@ -0,0 +1,49 @@
+menuconfig STAGING_MEDIA
+ bool "Media staging drivers"
+ default n
+ ---help---
+ This option allows you to select a number of media drivers that
+ don't have the "normal" Linux kernel quality level.
+ Most of them don't follow properly the V4L, DVB and/or RC API's,
+ so, they won't likely work fine with the existing applications.
+ That also means that, once fixed, their API's will change to match
+ the existing ones.
+
+ If you wish to work on these drivers, to help improve them, or
+ to report problems you have with them, please use the
+ linux-media@vger.kernel.org mailing list.
+
+ If in doubt, say N here.
+
+
+if STAGING_MEDIA
+
+# Please keep them in alphabetic order
+source "drivers/staging/media/as102/Kconfig"
+
+source "drivers/staging/media/bcm2048/Kconfig"
+
+source "drivers/staging/media/cxd2099/Kconfig"
+
+source "drivers/staging/media/davinci_vpfe/Kconfig"
+
+source "drivers/staging/media/dt3155v4l/Kconfig"
+
+source "drivers/staging/media/go7007/Kconfig"
+
+source "drivers/staging/media/msi3101/Kconfig"
+
+source "drivers/staging/media/omap24xx/Kconfig"
+
+source "drivers/staging/media/sn9c102/Kconfig"
+
+source "drivers/staging/media/solo6x10/Kconfig"
+
+source "drivers/staging/media/omap4iss/Kconfig"
+
+source "drivers/staging/media/rtl2832u_sdr/Kconfig"
+
+# Keep LIRC at the end, as it has sub-menus
+source "drivers/staging/media/lirc/Kconfig"
+
+endif
diff --git a/drivers/staging/media/Makefile b/drivers/staging/media/Makefile
new file mode 100644
index 00000000000..8e2c5d27216
--- /dev/null
+++ b/drivers/staging/media/Makefile
@@ -0,0 +1,15 @@
+obj-$(CONFIG_DVB_AS102) += as102/
+obj-$(CONFIG_I2C_BCM2048) += bcm2048/
+obj-$(CONFIG_DVB_CXD2099) += cxd2099/
+obj-$(CONFIG_LIRC_STAGING) += lirc/
+obj-$(CONFIG_SOLO6X10) += solo6x10/
+obj-$(CONFIG_VIDEO_DT3155) += dt3155v4l/
+obj-$(CONFIG_VIDEO_GO7007) += go7007/
+obj-$(CONFIG_USB_MSI3101) += msi3101/
+obj-$(CONFIG_VIDEO_DM365_VPFE) += davinci_vpfe/
+obj-$(CONFIG_VIDEO_OMAP4) += omap4iss/
+obj-$(CONFIG_USB_SN9C102) += sn9c102/
+obj-$(CONFIG_VIDEO_OMAP2) += omap24xx/
+obj-$(CONFIG_VIDEO_TCM825X) += omap24xx/
+obj-$(CONFIG_DVB_RTL2832_SDR) += rtl2832u_sdr/
+
diff --git a/drivers/staging/media/as102/Kconfig b/drivers/staging/media/as102/Kconfig
new file mode 100644
index 00000000000..28aba00dc62
--- /dev/null
+++ b/drivers/staging/media/as102/Kconfig
@@ -0,0 +1,8 @@
+config DVB_AS102
+ tristate "Abilis AS102 DVB receiver"
+ depends on DVB_CORE && USB && I2C && INPUT
+ select FW_LOADER
+ help
+ Choose Y or M here if you have a device containing an AS102
+
+ To compile this driver as a module, choose M here
diff --git a/drivers/staging/media/as102/Makefile b/drivers/staging/media/as102/Makefile
new file mode 100644
index 00000000000..8916d8a909b
--- /dev/null
+++ b/drivers/staging/media/as102/Makefile
@@ -0,0 +1,6 @@
+dvb-as102-objs := as102_drv.o as102_fw.o as10x_cmd.o as10x_cmd_stream.o \
+ as102_fe.o as102_usb_drv.o as10x_cmd_cfg.o
+
+obj-$(CONFIG_DVB_AS102) += dvb-as102.o
+
+ccflags-y += -Idrivers/media/dvb-core
diff --git a/drivers/staging/media/as102/as102_drv.c b/drivers/staging/media/as102/as102_drv.c
new file mode 100644
index 00000000000..09d64cd6750
--- /dev/null
+++ b/drivers/staging/media/as102/as102_drv.c
@@ -0,0 +1,277 @@
+/*
+ * Abilis Systems Single DVB-T Receiver
+ * Copyright (C) 2008 Pierrick Hascoet <pierrick.hascoet@abilis.com>
+ * Copyright (C) 2010 Devin Heitmueller <dheitmueller@kernellabs.com>
+ *
+ * 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, 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/mm.h>
+#include <linux/kref.h>
+#include <linux/uaccess.h>
+#include <linux/usb.h>
+
+/* header file for usb device driver*/
+#include "as102_drv.h"
+#include "as102_fw.h"
+#include "dvbdev.h"
+
+int as102_debug;
+module_param_named(debug, as102_debug, int, 0644);
+MODULE_PARM_DESC(debug, "Turn on/off debugging (default: off)");
+
+int dual_tuner;
+module_param_named(dual_tuner, dual_tuner, int, 0644);
+MODULE_PARM_DESC(dual_tuner, "Activate Dual-Tuner config (default: off)");
+
+static int fw_upload = 1;
+module_param_named(fw_upload, fw_upload, int, 0644);
+MODULE_PARM_DESC(fw_upload, "Turn on/off default FW upload (default: on)");
+
+static int pid_filtering;
+module_param_named(pid_filtering, pid_filtering, int, 0644);
+MODULE_PARM_DESC(pid_filtering, "Activate HW PID filtering (default: off)");
+
+static int ts_auto_disable;
+module_param_named(ts_auto_disable, ts_auto_disable, int, 0644);
+MODULE_PARM_DESC(ts_auto_disable, "Stream Auto Enable on FW (default: off)");
+
+int elna_enable = 1;
+module_param_named(elna_enable, elna_enable, int, 0644);
+MODULE_PARM_DESC(elna_enable, "Activate eLNA (default: on)");
+
+DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr);
+
+static void as102_stop_stream(struct as102_dev_t *dev)
+{
+ struct as10x_bus_adapter_t *bus_adap;
+
+ if (dev != NULL)
+ bus_adap = &dev->bus_adap;
+ else
+ return;
+
+ if (bus_adap->ops->stop_stream != NULL)
+ bus_adap->ops->stop_stream(dev);
+
+ if (ts_auto_disable) {
+ if (mutex_lock_interruptible(&dev->bus_adap.lock))
+ return;
+
+ if (as10x_cmd_stop_streaming(bus_adap) < 0)
+ dprintk(debug, "as10x_cmd_stop_streaming failed\n");
+
+ mutex_unlock(&dev->bus_adap.lock);
+ }
+}
+
+static int as102_start_stream(struct as102_dev_t *dev)
+{
+ struct as10x_bus_adapter_t *bus_adap;
+ int ret = -EFAULT;
+
+ if (dev != NULL)
+ bus_adap = &dev->bus_adap;
+ else
+ return ret;
+
+ if (bus_adap->ops->start_stream != NULL)
+ ret = bus_adap->ops->start_stream(dev);
+
+ if (ts_auto_disable) {
+ if (mutex_lock_interruptible(&dev->bus_adap.lock))
+ return -EFAULT;
+
+ ret = as10x_cmd_start_streaming(bus_adap);
+
+ mutex_unlock(&dev->bus_adap.lock);
+ }
+
+ return ret;
+}
+
+static int as10x_pid_filter(struct as102_dev_t *dev,
+ int index, u16 pid, int onoff) {
+
+ struct as10x_bus_adapter_t *bus_adap = &dev->bus_adap;
+ int ret = -EFAULT;
+
+ if (mutex_lock_interruptible(&dev->bus_adap.lock)) {
+ dprintk(debug, "mutex_lock_interruptible(lock) failed !\n");
+ return -EBUSY;
+ }
+
+ switch (onoff) {
+ case 0:
+ ret = as10x_cmd_del_PID_filter(bus_adap, (uint16_t) pid);
+ dprintk(debug, "DEL_PID_FILTER([%02d] 0x%04x) ret = %d\n",
+ index, pid, ret);
+ break;
+ case 1:
+ {
+ struct as10x_ts_filter filter;
+
+ filter.type = TS_PID_TYPE_TS;
+ filter.idx = 0xFF;
+ filter.pid = pid;
+
+ ret = as10x_cmd_add_PID_filter(bus_adap, &filter);
+ dprintk(debug,
+ "ADD_PID_FILTER([%02d -> %02d], 0x%04x) ret = %d\n",
+ index, filter.idx, filter.pid, ret);
+ break;
+ }
+ }
+
+ mutex_unlock(&dev->bus_adap.lock);
+ return ret;
+}
+
+static int as102_dvb_dmx_start_feed(struct dvb_demux_feed *dvbdmxfeed)
+{
+ int ret = 0;
+ struct dvb_demux *demux = dvbdmxfeed->demux;
+ struct as102_dev_t *as102_dev = demux->priv;
+
+ if (mutex_lock_interruptible(&as102_dev->sem))
+ return -ERESTARTSYS;
+
+ if (pid_filtering)
+ as10x_pid_filter(as102_dev, dvbdmxfeed->index,
+ dvbdmxfeed->pid, 1);
+
+ if (as102_dev->streaming++ == 0)
+ ret = as102_start_stream(as102_dev);
+
+ mutex_unlock(&as102_dev->sem);
+ return ret;
+}
+
+static int as102_dvb_dmx_stop_feed(struct dvb_demux_feed *dvbdmxfeed)
+{
+ struct dvb_demux *demux = dvbdmxfeed->demux;
+ struct as102_dev_t *as102_dev = demux->priv;
+
+ if (mutex_lock_interruptible(&as102_dev->sem))
+ return -ERESTARTSYS;
+
+ if (--as102_dev->streaming == 0)
+ as102_stop_stream(as102_dev);
+
+ if (pid_filtering)
+ as10x_pid_filter(as102_dev, dvbdmxfeed->index,
+ dvbdmxfeed->pid, 0);
+
+ mutex_unlock(&as102_dev->sem);
+ return 0;
+}
+
+int as102_dvb_register(struct as102_dev_t *as102_dev)
+{
+ struct device *dev = &as102_dev->bus_adap.usb_dev->dev;
+ int ret;
+
+ ret = dvb_register_adapter(&as102_dev->dvb_adap,
+ as102_dev->name, THIS_MODULE,
+ dev, adapter_nr);
+ if (ret < 0) {
+ dev_err(dev, "%s: dvb_register_adapter() failed: %d\n",
+ __func__, ret);
+ return ret;
+ }
+
+ as102_dev->dvb_dmx.priv = as102_dev;
+ as102_dev->dvb_dmx.filternum = pid_filtering ? 16 : 256;
+ as102_dev->dvb_dmx.feednum = 256;
+ as102_dev->dvb_dmx.start_feed = as102_dvb_dmx_start_feed;
+ as102_dev->dvb_dmx.stop_feed = as102_dvb_dmx_stop_feed;
+
+ as102_dev->dvb_dmx.dmx.capabilities = DMX_TS_FILTERING |
+ DMX_SECTION_FILTERING;
+
+ as102_dev->dvb_dmxdev.filternum = as102_dev->dvb_dmx.filternum;
+ as102_dev->dvb_dmxdev.demux = &as102_dev->dvb_dmx.dmx;
+ as102_dev->dvb_dmxdev.capabilities = 0;
+
+ ret = dvb_dmx_init(&as102_dev->dvb_dmx);
+ if (ret < 0) {
+ dev_err(dev, "%s: dvb_dmx_init() failed: %d\n", __func__, ret);
+ goto edmxinit;
+ }
+
+ ret = dvb_dmxdev_init(&as102_dev->dvb_dmxdev, &as102_dev->dvb_adap);
+ if (ret < 0) {
+ dev_err(dev, "%s: dvb_dmxdev_init() failed: %d\n",
+ __func__, ret);
+ goto edmxdinit;
+ }
+
+ ret = as102_dvb_register_fe(as102_dev, &as102_dev->dvb_fe);
+ if (ret < 0) {
+ dev_err(dev, "%s: as102_dvb_register_frontend() failed: %d",
+ __func__, ret);
+ goto efereg;
+ }
+
+ /* init bus mutex for token locking */
+ mutex_init(&as102_dev->bus_adap.lock);
+
+ /* init start / stop stream mutex */
+ mutex_init(&as102_dev->sem);
+
+ /*
+ * try to load as102 firmware. If firmware upload failed, we'll be
+ * able to upload it later.
+ */
+ if (fw_upload)
+ try_then_request_module(as102_fw_upload(&as102_dev->bus_adap),
+ "firmware_class");
+
+ pr_info("Registered device %s", as102_dev->name);
+ return 0;
+
+efereg:
+ dvb_dmxdev_release(&as102_dev->dvb_dmxdev);
+edmxdinit:
+ dvb_dmx_release(&as102_dev->dvb_dmx);
+edmxinit:
+ dvb_unregister_adapter(&as102_dev->dvb_adap);
+ return ret;
+}
+
+void as102_dvb_unregister(struct as102_dev_t *as102_dev)
+{
+ /* unregister as102 frontend */
+ as102_dvb_unregister_fe(&as102_dev->dvb_fe);
+
+ /* unregister demux device */
+ dvb_dmxdev_release(&as102_dev->dvb_dmxdev);
+ dvb_dmx_release(&as102_dev->dvb_dmx);
+
+ /* unregister dvb adapter */
+ dvb_unregister_adapter(&as102_dev->dvb_adap);
+
+ pr_info("Unregistered device %s", as102_dev->name);
+}
+
+module_usb_driver(as102_usb_driver);
+
+/* modinfo details */
+MODULE_DESCRIPTION(DRIVER_FULL_NAME);
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Pierrick Hascoet <pierrick.hascoet@abilis.com>");
diff --git a/drivers/staging/media/as102/as102_drv.h b/drivers/staging/media/as102/as102_drv.h
new file mode 100644
index 00000000000..a06837dcc05
--- /dev/null
+++ b/drivers/staging/media/as102/as102_drv.h
@@ -0,0 +1,99 @@
+/*
+ * Abilis Systems Single DVB-T Receiver
+ * Copyright (C) 2008 Pierrick Hascoet <pierrick.hascoet@abilis.com>
+ *
+ * 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, 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/usb.h>
+#include <dvb_demux.h>
+#include <dvb_frontend.h>
+#include <dmxdev.h>
+#include "as10x_cmd.h"
+#include "as102_usb_drv.h"
+
+#define DRIVER_FULL_NAME "Abilis Systems as10x usb driver"
+#define DRIVER_NAME "as10x_usb"
+
+extern int as102_debug;
+#define debug as102_debug
+extern struct usb_driver as102_usb_driver;
+extern int elna_enable;
+
+#define dprintk(debug, args...) \
+ do { if (debug) { \
+ pr_debug("%s: ", __func__); \
+ printk(args); \
+ } } while (0)
+
+#define AS102_DEVICE_MAJOR 192
+
+#define AS102_USB_BUF_SIZE 512
+#define MAX_STREAM_URB 32
+
+struct as10x_bus_adapter_t {
+ struct usb_device *usb_dev;
+ /* bus token lock */
+ struct mutex lock;
+ /* low level interface for bus adapter */
+ union as10x_bus_token_t {
+ /* usb token */
+ struct as10x_usb_token_cmd_t usb;
+ } token;
+
+ /* token cmd xfer id */
+ uint16_t cmd_xid;
+
+ /* as10x command and response for dvb interface*/
+ struct as10x_cmd_t *cmd, *rsp;
+
+ /* bus adapter private ops callback */
+ struct as102_priv_ops_t *ops;
+};
+
+struct as102_dev_t {
+ const char *name;
+ struct as10x_bus_adapter_t bus_adap;
+ struct list_head device_entry;
+ struct kref kref;
+ uint8_t elna_cfg;
+
+ struct dvb_adapter dvb_adap;
+ struct dvb_frontend dvb_fe;
+ struct dvb_demux dvb_dmx;
+ struct dmxdev dvb_dmxdev;
+
+ /* demodulator stats */
+ struct as10x_demod_stats demod_stats;
+ /* signal strength */
+ uint16_t signal_strength;
+ /* bit error rate */
+ uint32_t ber;
+
+ /* timer handle to trig ts stream download */
+ struct timer_list timer_handle;
+
+ struct mutex sem;
+ dma_addr_t dma_addr;
+ void *stream;
+ int streaming;
+ struct urb *stream_urb[MAX_STREAM_URB];
+};
+
+int as102_dvb_register(struct as102_dev_t *dev);
+void as102_dvb_unregister(struct as102_dev_t *dev);
+
+int as102_dvb_register_fe(struct as102_dev_t *dev, struct dvb_frontend *fe);
+int as102_dvb_unregister_fe(struct dvb_frontend *dev);
diff --git a/drivers/staging/media/as102/as102_fe.c b/drivers/staging/media/as102/as102_fe.c
new file mode 100644
index 00000000000..b686b7617cd
--- /dev/null
+++ b/drivers/staging/media/as102/as102_fe.c
@@ -0,0 +1,571 @@
+/*
+ * Abilis Systems Single DVB-T Receiver
+ * Copyright (C) 2008 Pierrick Hascoet <pierrick.hascoet@abilis.com>
+ * Copyright (C) 2010 Devin Heitmueller <dheitmueller@kernellabs.com>
+ *
+ * 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, 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+#include "as102_drv.h"
+#include "as10x_types.h"
+#include "as10x_cmd.h"
+
+static void as10x_fe_copy_tps_parameters(struct dtv_frontend_properties *dst,
+ struct as10x_tps *src);
+
+static void as102_fe_copy_tune_parameters(struct as10x_tune_args *dst,
+ struct dtv_frontend_properties *src);
+
+static int as102_fe_set_frontend(struct dvb_frontend *fe)
+{
+ struct dtv_frontend_properties *p = &fe->dtv_property_cache;
+ int ret = 0;
+ struct as102_dev_t *dev;
+ struct as10x_tune_args tune_args = { 0 };
+
+ dev = (struct as102_dev_t *) fe->tuner_priv;
+ if (dev == NULL)
+ return -ENODEV;
+
+ if (mutex_lock_interruptible(&dev->bus_adap.lock))
+ return -EBUSY;
+
+ as102_fe_copy_tune_parameters(&tune_args, p);
+
+ /* send abilis command: SET_TUNE */
+ ret = as10x_cmd_set_tune(&dev->bus_adap, &tune_args);
+ if (ret != 0)
+ dprintk(debug, "as10x_cmd_set_tune failed. (err = %d)\n", ret);
+
+ mutex_unlock(&dev->bus_adap.lock);
+
+ return (ret < 0) ? -EINVAL : 0;
+}
+
+static int as102_fe_get_frontend(struct dvb_frontend *fe)
+{
+ struct dtv_frontend_properties *p = &fe->dtv_property_cache;
+ int ret = 0;
+ struct as102_dev_t *dev;
+ struct as10x_tps tps = { 0 };
+
+ dev = (struct as102_dev_t *) fe->tuner_priv;
+ if (dev == NULL)
+ return -EINVAL;
+
+ if (mutex_lock_interruptible(&dev->bus_adap.lock))
+ return -EBUSY;
+
+ /* send abilis command: GET_TPS */
+ ret = as10x_cmd_get_tps(&dev->bus_adap, &tps);
+
+ if (ret == 0)
+ as10x_fe_copy_tps_parameters(p, &tps);
+
+ mutex_unlock(&dev->bus_adap.lock);
+
+ return (ret < 0) ? -EINVAL : 0;
+}
+
+static int as102_fe_get_tune_settings(struct dvb_frontend *fe,
+ struct dvb_frontend_tune_settings *settings) {
+
+#if 0
+ dprintk(debug, "step_size = %d\n", settings->step_size);
+ dprintk(debug, "max_drift = %d\n", settings->max_drift);
+ dprintk(debug, "min_delay_ms = %d -> %d\n", settings->min_delay_ms,
+ 1000);
+#endif
+
+ settings->min_delay_ms = 1000;
+
+ return 0;
+}
+
+
+static int as102_fe_read_status(struct dvb_frontend *fe, fe_status_t *status)
+{
+ int ret = 0;
+ struct as102_dev_t *dev;
+ struct as10x_tune_status tstate = { 0 };
+
+ dev = (struct as102_dev_t *) fe->tuner_priv;
+ if (dev == NULL)
+ return -ENODEV;
+
+ if (mutex_lock_interruptible(&dev->bus_adap.lock))
+ return -EBUSY;
+
+ /* send abilis command: GET_TUNE_STATUS */
+ ret = as10x_cmd_get_tune_status(&dev->bus_adap, &tstate);
+ if (ret < 0) {
+ dprintk(debug, "as10x_cmd_get_tune_status failed (err = %d)\n",
+ ret);
+ goto out;
+ }
+
+ dev->signal_strength = tstate.signal_strength;
+ dev->ber = tstate.BER;
+
+ switch (tstate.tune_state) {
+ case TUNE_STATUS_SIGNAL_DVB_OK:
+ *status = FE_HAS_SIGNAL | FE_HAS_CARRIER;
+ break;
+ case TUNE_STATUS_STREAM_DETECTED:
+ *status = FE_HAS_SIGNAL | FE_HAS_CARRIER | FE_HAS_SYNC;
+ break;
+ case TUNE_STATUS_STREAM_TUNED:
+ *status = FE_HAS_SIGNAL | FE_HAS_CARRIER | FE_HAS_SYNC |
+ FE_HAS_LOCK;
+ break;
+ default:
+ *status = TUNE_STATUS_NOT_TUNED;
+ }
+
+ dprintk(debug, "tuner status: 0x%02x, strength %d, per: %d, ber: %d\n",
+ tstate.tune_state, tstate.signal_strength,
+ tstate.PER, tstate.BER);
+
+ if (*status & FE_HAS_LOCK) {
+ if (as10x_cmd_get_demod_stats(&dev->bus_adap,
+ (struct as10x_demod_stats *) &dev->demod_stats) < 0) {
+ memset(&dev->demod_stats, 0, sizeof(dev->demod_stats));
+ dprintk(debug,
+ "as10x_cmd_get_demod_stats failed (probably not tuned)\n");
+ } else {
+ dprintk(debug,
+ "demod status: fc: 0x%08x, bad fc: 0x%08x, "
+ "bytes corrected: 0x%08x , MER: 0x%04x\n",
+ dev->demod_stats.frame_count,
+ dev->demod_stats.bad_frame_count,
+ dev->demod_stats.bytes_fixed_by_rs,
+ dev->demod_stats.mer);
+ }
+ } else {
+ memset(&dev->demod_stats, 0, sizeof(dev->demod_stats));
+ }
+
+out:
+ mutex_unlock(&dev->bus_adap.lock);
+ return ret;
+}
+
+/*
+ * Note:
+ * - in AS102 SNR=MER
+ * - the SNR will be returned in linear terms, i.e. not in dB
+ * - the accuracy equals ±2dB for a SNR range from 4dB to 30dB
+ * - the accuracy is >2dB for SNR values outside this range
+ */
+static int as102_fe_read_snr(struct dvb_frontend *fe, u16 *snr)
+{
+ struct as102_dev_t *dev;
+
+ dev = (struct as102_dev_t *) fe->tuner_priv;
+ if (dev == NULL)
+ return -ENODEV;
+
+ *snr = dev->demod_stats.mer;
+
+ return 0;
+}
+
+static int as102_fe_read_ber(struct dvb_frontend *fe, u32 *ber)
+{
+ struct as102_dev_t *dev;
+
+ dev = (struct as102_dev_t *) fe->tuner_priv;
+ if (dev == NULL)
+ return -ENODEV;
+
+ *ber = dev->ber;
+
+ return 0;
+}
+
+static int as102_fe_read_signal_strength(struct dvb_frontend *fe,
+ u16 *strength)
+{
+ struct as102_dev_t *dev;
+
+ dev = (struct as102_dev_t *) fe->tuner_priv;
+ if (dev == NULL)
+ return -ENODEV;
+
+ *strength = (((0xffff * 400) * dev->signal_strength + 41000) * 2);
+
+ return 0;
+}
+
+static int as102_fe_read_ucblocks(struct dvb_frontend *fe, u32 *ucblocks)
+{
+ struct as102_dev_t *dev;
+
+ dev = (struct as102_dev_t *) fe->tuner_priv;
+ if (dev == NULL)
+ return -ENODEV;
+
+ if (dev->demod_stats.has_started)
+ *ucblocks = dev->demod_stats.bad_frame_count;
+ else
+ *ucblocks = 0;
+
+ return 0;
+}
+
+static int as102_fe_ts_bus_ctrl(struct dvb_frontend *fe, int acquire)
+{
+ struct as102_dev_t *dev;
+ int ret;
+
+ dev = (struct as102_dev_t *) fe->tuner_priv;
+ if (dev == NULL)
+ return -ENODEV;
+
+ if (mutex_lock_interruptible(&dev->bus_adap.lock))
+ return -EBUSY;
+
+ if (acquire) {
+ if (elna_enable)
+ as10x_cmd_set_context(&dev->bus_adap,
+ CONTEXT_LNA, dev->elna_cfg);
+
+ ret = as10x_cmd_turn_on(&dev->bus_adap);
+ } else {
+ ret = as10x_cmd_turn_off(&dev->bus_adap);
+ }
+
+ mutex_unlock(&dev->bus_adap.lock);
+
+ return ret;
+}
+
+static struct dvb_frontend_ops as102_fe_ops = {
+ .delsys = { SYS_DVBT },
+ .info = {
+ .name = "Unknown AS102 device",
+ .frequency_min = 174000000,
+ .frequency_max = 862000000,
+ .frequency_stepsize = 166667,
+ .caps = FE_CAN_INVERSION_AUTO
+ | FE_CAN_FEC_1_2 | FE_CAN_FEC_2_3 | FE_CAN_FEC_3_4
+ | FE_CAN_FEC_5_6 | FE_CAN_FEC_7_8 | FE_CAN_FEC_AUTO
+ | FE_CAN_QAM_16 | FE_CAN_QAM_64 | FE_CAN_QPSK
+ | FE_CAN_QAM_AUTO
+ | FE_CAN_TRANSMISSION_MODE_AUTO
+ | FE_CAN_GUARD_INTERVAL_AUTO
+ | FE_CAN_HIERARCHY_AUTO
+ | FE_CAN_RECOVER
+ | FE_CAN_MUTE_TS
+ },
+
+ .set_frontend = as102_fe_set_frontend,
+ .get_frontend = as102_fe_get_frontend,
+ .get_tune_settings = as102_fe_get_tune_settings,
+
+ .read_status = as102_fe_read_status,
+ .read_snr = as102_fe_read_snr,
+ .read_ber = as102_fe_read_ber,
+ .read_signal_strength = as102_fe_read_signal_strength,
+ .read_ucblocks = as102_fe_read_ucblocks,
+ .ts_bus_ctrl = as102_fe_ts_bus_ctrl,
+};
+
+int as102_dvb_unregister_fe(struct dvb_frontend *fe)
+{
+ /* unregister frontend */
+ dvb_unregister_frontend(fe);
+
+ /* detach frontend */
+ dvb_frontend_detach(fe);
+
+ return 0;
+}
+
+int as102_dvb_register_fe(struct as102_dev_t *as102_dev,
+ struct dvb_frontend *dvb_fe)
+{
+ int errno;
+ struct dvb_adapter *dvb_adap;
+
+ if (as102_dev == NULL)
+ return -EINVAL;
+
+ /* extract dvb_adapter */
+ dvb_adap = &as102_dev->dvb_adap;
+
+ /* init frontend callback ops */
+ memcpy(&dvb_fe->ops, &as102_fe_ops, sizeof(struct dvb_frontend_ops));
+ strncpy(dvb_fe->ops.info.name, as102_dev->name,
+ sizeof(dvb_fe->ops.info.name));
+
+ /* register dvb frontend */
+ errno = dvb_register_frontend(dvb_adap, dvb_fe);
+ if (errno == 0)
+ dvb_fe->tuner_priv = as102_dev;
+
+ return errno;
+}
+
+static void as10x_fe_copy_tps_parameters(struct dtv_frontend_properties *fe_tps,
+ struct as10x_tps *as10x_tps)
+{
+
+ /* extract constellation */
+ switch (as10x_tps->modulation) {
+ case CONST_QPSK:
+ fe_tps->modulation = QPSK;
+ break;
+ case CONST_QAM16:
+ fe_tps->modulation = QAM_16;
+ break;
+ case CONST_QAM64:
+ fe_tps->modulation = QAM_64;
+ break;
+ }
+
+ /* extract hierarchy */
+ switch (as10x_tps->hierarchy) {
+ case HIER_NONE:
+ fe_tps->hierarchy = HIERARCHY_NONE;
+ break;
+ case HIER_ALPHA_1:
+ fe_tps->hierarchy = HIERARCHY_1;
+ break;
+ case HIER_ALPHA_2:
+ fe_tps->hierarchy = HIERARCHY_2;
+ break;
+ case HIER_ALPHA_4:
+ fe_tps->hierarchy = HIERARCHY_4;
+ break;
+ }
+
+ /* extract code rate HP */
+ switch (as10x_tps->code_rate_HP) {
+ case CODE_RATE_1_2:
+ fe_tps->code_rate_HP = FEC_1_2;
+ break;
+ case CODE_RATE_2_3:
+ fe_tps->code_rate_HP = FEC_2_3;
+ break;
+ case CODE_RATE_3_4:
+ fe_tps->code_rate_HP = FEC_3_4;
+ break;
+ case CODE_RATE_5_6:
+ fe_tps->code_rate_HP = FEC_5_6;
+ break;
+ case CODE_RATE_7_8:
+ fe_tps->code_rate_HP = FEC_7_8;
+ break;
+ }
+
+ /* extract code rate LP */
+ switch (as10x_tps->code_rate_LP) {
+ case CODE_RATE_1_2:
+ fe_tps->code_rate_LP = FEC_1_2;
+ break;
+ case CODE_RATE_2_3:
+ fe_tps->code_rate_LP = FEC_2_3;
+ break;
+ case CODE_RATE_3_4:
+ fe_tps->code_rate_LP = FEC_3_4;
+ break;
+ case CODE_RATE_5_6:
+ fe_tps->code_rate_LP = FEC_5_6;
+ break;
+ case CODE_RATE_7_8:
+ fe_tps->code_rate_LP = FEC_7_8;
+ break;
+ }
+
+ /* extract guard interval */
+ switch (as10x_tps->guard_interval) {
+ case GUARD_INT_1_32:
+ fe_tps->guard_interval = GUARD_INTERVAL_1_32;
+ break;
+ case GUARD_INT_1_16:
+ fe_tps->guard_interval = GUARD_INTERVAL_1_16;
+ break;
+ case GUARD_INT_1_8:
+ fe_tps->guard_interval = GUARD_INTERVAL_1_8;
+ break;
+ case GUARD_INT_1_4:
+ fe_tps->guard_interval = GUARD_INTERVAL_1_4;
+ break;
+ }
+
+ /* extract transmission mode */
+ switch (as10x_tps->transmission_mode) {
+ case TRANS_MODE_2K:
+ fe_tps->transmission_mode = TRANSMISSION_MODE_2K;
+ break;
+ case TRANS_MODE_8K:
+ fe_tps->transmission_mode = TRANSMISSION_MODE_8K;
+ break;
+ }
+}
+
+static uint8_t as102_fe_get_code_rate(fe_code_rate_t arg)
+{
+ uint8_t c;
+
+ switch (arg) {
+ case FEC_1_2:
+ c = CODE_RATE_1_2;
+ break;
+ case FEC_2_3:
+ c = CODE_RATE_2_3;
+ break;
+ case FEC_3_4:
+ c = CODE_RATE_3_4;
+ break;
+ case FEC_5_6:
+ c = CODE_RATE_5_6;
+ break;
+ case FEC_7_8:
+ c = CODE_RATE_7_8;
+ break;
+ default:
+ c = CODE_RATE_UNKNOWN;
+ break;
+ }
+
+ return c;
+}
+
+static void as102_fe_copy_tune_parameters(struct as10x_tune_args *tune_args,
+ struct dtv_frontend_properties *params)
+{
+
+ /* set frequency */
+ tune_args->freq = params->frequency / 1000;
+
+ /* fix interleaving_mode */
+ tune_args->interleaving_mode = INTLV_NATIVE;
+
+ switch (params->bandwidth_hz) {
+ case 8000000:
+ tune_args->bandwidth = BW_8_MHZ;
+ break;
+ case 7000000:
+ tune_args->bandwidth = BW_7_MHZ;
+ break;
+ case 6000000:
+ tune_args->bandwidth = BW_6_MHZ;
+ break;
+ default:
+ tune_args->bandwidth = BW_8_MHZ;
+ }
+
+ switch (params->guard_interval) {
+ case GUARD_INTERVAL_1_32:
+ tune_args->guard_interval = GUARD_INT_1_32;
+ break;
+ case GUARD_INTERVAL_1_16:
+ tune_args->guard_interval = GUARD_INT_1_16;
+ break;
+ case GUARD_INTERVAL_1_8:
+ tune_args->guard_interval = GUARD_INT_1_8;
+ break;
+ case GUARD_INTERVAL_1_4:
+ tune_args->guard_interval = GUARD_INT_1_4;
+ break;
+ case GUARD_INTERVAL_AUTO:
+ default:
+ tune_args->guard_interval = GUARD_UNKNOWN;
+ break;
+ }
+
+ switch (params->modulation) {
+ case QPSK:
+ tune_args->modulation = CONST_QPSK;
+ break;
+ case QAM_16:
+ tune_args->modulation = CONST_QAM16;
+ break;
+ case QAM_64:
+ tune_args->modulation = CONST_QAM64;
+ break;
+ default:
+ tune_args->modulation = CONST_UNKNOWN;
+ break;
+ }
+
+ switch (params->transmission_mode) {
+ case TRANSMISSION_MODE_2K:
+ tune_args->transmission_mode = TRANS_MODE_2K;
+ break;
+ case TRANSMISSION_MODE_8K:
+ tune_args->transmission_mode = TRANS_MODE_8K;
+ break;
+ default:
+ tune_args->transmission_mode = TRANS_MODE_UNKNOWN;
+ }
+
+ switch (params->hierarchy) {
+ case HIERARCHY_NONE:
+ tune_args->hierarchy = HIER_NONE;
+ break;
+ case HIERARCHY_1:
+ tune_args->hierarchy = HIER_ALPHA_1;
+ break;
+ case HIERARCHY_2:
+ tune_args->hierarchy = HIER_ALPHA_2;
+ break;
+ case HIERARCHY_4:
+ tune_args->hierarchy = HIER_ALPHA_4;
+ break;
+ case HIERARCHY_AUTO:
+ tune_args->hierarchy = HIER_UNKNOWN;
+ break;
+ }
+
+ dprintk(debug, "tuner parameters: freq: %d bw: 0x%02x gi: 0x%02x\n",
+ params->frequency,
+ tune_args->bandwidth,
+ tune_args->guard_interval);
+
+ /*
+ * Detect a hierarchy selection
+ * if HP/LP are both set to FEC_NONE, HP will be selected.
+ */
+ if ((tune_args->hierarchy != HIER_NONE) &&
+ ((params->code_rate_LP == FEC_NONE) ||
+ (params->code_rate_HP == FEC_NONE))) {
+
+ if (params->code_rate_LP == FEC_NONE) {
+ tune_args->hier_select = HIER_HIGH_PRIORITY;
+ tune_args->code_rate =
+ as102_fe_get_code_rate(params->code_rate_HP);
+ }
+
+ if (params->code_rate_HP == FEC_NONE) {
+ tune_args->hier_select = HIER_LOW_PRIORITY;
+ tune_args->code_rate =
+ as102_fe_get_code_rate(params->code_rate_LP);
+ }
+
+ dprintk(debug,
+ "\thierarchy: 0x%02x selected: %s code_rate_%s: 0x%02x\n",
+ tune_args->hierarchy,
+ tune_args->hier_select == HIER_HIGH_PRIORITY ?
+ "HP" : "LP",
+ tune_args->hier_select == HIER_HIGH_PRIORITY ?
+ "HP" : "LP",
+ tune_args->code_rate);
+ } else {
+ tune_args->code_rate =
+ as102_fe_get_code_rate(params->code_rate_HP);
+ }
+}
diff --git a/drivers/staging/media/as102/as102_fw.c b/drivers/staging/media/as102/as102_fw.c
new file mode 100644
index 00000000000..f33f752c0aa
--- /dev/null
+++ b/drivers/staging/media/as102/as102_fw.c
@@ -0,0 +1,232 @@
+/*
+ * Abilis Systems Single DVB-T Receiver
+ * Copyright (C) 2008 Pierrick Hascoet <pierrick.hascoet@abilis.com>
+ * Copyright (C) 2010 Devin Heitmueller <dheitmueller@kernellabs.com>
+ *
+ * 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, 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/ctype.h>
+#include <linux/delay.h>
+#include <linux/firmware.h>
+
+#include "as102_drv.h"
+#include "as102_fw.h"
+
+static const char as102_st_fw1[] = "as102_data1_st.hex";
+static const char as102_st_fw2[] = "as102_data2_st.hex";
+static const char as102_dt_fw1[] = "as102_data1_dt.hex";
+static const char as102_dt_fw2[] = "as102_data2_dt.hex";
+
+static unsigned char atohx(unsigned char *dst, char *src)
+{
+ unsigned char value = 0;
+
+ char msb = tolower(*src) - '0';
+ char lsb = tolower(*(src + 1)) - '0';
+
+ if (msb > 9)
+ msb -= 7;
+ if (lsb > 9)
+ lsb -= 7;
+
+ *dst = value = ((msb & 0xF) << 4) | (lsb & 0xF);
+ return value;
+}
+
+/*
+ * Parse INTEL HEX firmware file to extract address and data.
+ */
+static int parse_hex_line(unsigned char *fw_data, unsigned char *addr,
+ unsigned char *data, int *dataLength,
+ unsigned char *addr_has_changed) {
+
+ int count = 0;
+ unsigned char *src, dst;
+
+ if (*fw_data++ != ':') {
+ pr_err("invalid firmware file\n");
+ return -EFAULT;
+ }
+
+ /* locate end of line */
+ for (src = fw_data; *src != '\n'; src += 2) {
+ atohx(&dst, src);
+ /* parse line to split addr / data */
+ switch (count) {
+ case 0:
+ *dataLength = dst;
+ break;
+ case 1:
+ addr[2] = dst;
+ break;
+ case 2:
+ addr[3] = dst;
+ break;
+ case 3:
+ /* check if data is an address */
+ if (dst == 0x04)
+ *addr_has_changed = 1;
+ else
+ *addr_has_changed = 0;
+ break;
+ case 4:
+ case 5:
+ if (*addr_has_changed)
+ addr[(count - 4)] = dst;
+ else
+ data[(count - 4)] = dst;
+ break;
+ default:
+ data[(count - 4)] = dst;
+ break;
+ }
+ count++;
+ }
+
+ /* return read value + ':' + '\n' */
+ return (count * 2) + 2;
+}
+
+static int as102_firmware_upload(struct as10x_bus_adapter_t *bus_adap,
+ unsigned char *cmd,
+ const struct firmware *firmware) {
+
+ struct as10x_fw_pkt_t fw_pkt;
+ int total_read_bytes = 0, errno = 0;
+ unsigned char addr_has_changed = 0;
+
+ for (total_read_bytes = 0; total_read_bytes < firmware->size; ) {
+ int read_bytes = 0, data_len = 0;
+
+ /* parse intel hex line */
+ read_bytes = parse_hex_line(
+ (u8 *) (firmware->data + total_read_bytes),
+ fw_pkt.raw.address,
+ fw_pkt.raw.data,
+ &data_len,
+ &addr_has_changed);
+
+ if (read_bytes <= 0)
+ goto error;
+
+ /* detect the end of file */
+ total_read_bytes += read_bytes;
+ if (total_read_bytes == firmware->size) {
+ fw_pkt.u.request[0] = 0x00;
+ fw_pkt.u.request[1] = 0x03;
+
+ /* send EOF command */
+ errno = bus_adap->ops->upload_fw_pkt(bus_adap,
+ (uint8_t *)
+ &fw_pkt, 2, 0);
+ if (errno < 0)
+ goto error;
+ } else {
+ if (!addr_has_changed) {
+ /* prepare command to send */
+ fw_pkt.u.request[0] = 0x00;
+ fw_pkt.u.request[1] = 0x01;
+
+ data_len += sizeof(fw_pkt.u.request);
+ data_len += sizeof(fw_pkt.raw.address);
+
+ /* send cmd to device */
+ errno = bus_adap->ops->upload_fw_pkt(bus_adap,
+ (uint8_t *)
+ &fw_pkt,
+ data_len,
+ 0);
+ if (errno < 0)
+ goto error;
+ }
+ }
+ }
+error:
+ return (errno == 0) ? total_read_bytes : errno;
+}
+
+int as102_fw_upload(struct as10x_bus_adapter_t *bus_adap)
+{
+ int errno = -EFAULT;
+ const struct firmware *firmware = NULL;
+ unsigned char *cmd_buf = NULL;
+ const char *fw1, *fw2;
+ struct usb_device *dev = bus_adap->usb_dev;
+
+ /* select fw file to upload */
+ if (dual_tuner) {
+ fw1 = as102_dt_fw1;
+ fw2 = as102_dt_fw2;
+ } else {
+ fw1 = as102_st_fw1;
+ fw2 = as102_st_fw2;
+ }
+
+ /* allocate buffer to store firmware upload command and data */
+ cmd_buf = kzalloc(MAX_FW_PKT_SIZE, GFP_KERNEL);
+ if (cmd_buf == NULL) {
+ errno = -ENOMEM;
+ goto error;
+ }
+
+ /* request kernel to locate firmware file: part1 */
+ errno = request_firmware(&firmware, fw1, &dev->dev);
+ if (errno < 0) {
+ pr_err("%s: unable to locate firmware file: %s\n",
+ DRIVER_NAME, fw1);
+ goto error;
+ }
+
+ /* initiate firmware upload */
+ errno = as102_firmware_upload(bus_adap, cmd_buf, firmware);
+ if (errno < 0) {
+ pr_err("%s: error during firmware upload part1\n",
+ DRIVER_NAME);
+ goto error;
+ }
+
+ pr_info("%s: firmware: %s loaded with success\n",
+ DRIVER_NAME, fw1);
+ release_firmware(firmware);
+
+ /* wait for boot to complete */
+ mdelay(100);
+
+ /* request kernel to locate firmware file: part2 */
+ errno = request_firmware(&firmware, fw2, &dev->dev);
+ if (errno < 0) {
+ pr_err("%s: unable to locate firmware file: %s\n",
+ DRIVER_NAME, fw2);
+ goto error;
+ }
+
+ /* initiate firmware upload */
+ errno = as102_firmware_upload(bus_adap, cmd_buf, firmware);
+ if (errno < 0) {
+ pr_err("%s: error during firmware upload part2\n",
+ DRIVER_NAME);
+ goto error;
+ }
+
+ pr_info("%s: firmware: %s loaded with success\n",
+ DRIVER_NAME, fw2);
+error:
+ kfree(cmd_buf);
+ release_firmware(firmware);
+
+ return errno;
+}
diff --git a/drivers/staging/media/as102/as102_fw.h b/drivers/staging/media/as102/as102_fw.h
new file mode 100644
index 00000000000..4bfc6849d95
--- /dev/null
+++ b/drivers/staging/media/as102/as102_fw.h
@@ -0,0 +1,38 @@
+/*
+ * Abilis Systems Single DVB-T Receiver
+ * Copyright (C) 2008 Pierrick Hascoet <pierrick.hascoet@abilis.com>
+ *
+ * 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, 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+#define MAX_FW_PKT_SIZE 64
+
+extern int dual_tuner;
+
+struct as10x_raw_fw_pkt {
+ unsigned char address[4];
+ unsigned char data[MAX_FW_PKT_SIZE - 6];
+} __packed;
+
+struct as10x_fw_pkt_t {
+ union {
+ unsigned char request[2];
+ unsigned char length[2];
+ } __packed u;
+ struct as10x_raw_fw_pkt raw;
+} __packed;
+
+#ifdef __KERNEL__
+int as102_fw_upload(struct as10x_bus_adapter_t *bus_adap);
+#endif
diff --git a/drivers/staging/media/as102/as102_usb_drv.c b/drivers/staging/media/as102/as102_usb_drv.c
new file mode 100644
index 00000000000..e6f6278e97d
--- /dev/null
+++ b/drivers/staging/media/as102/as102_usb_drv.c
@@ -0,0 +1,472 @@
+/*
+ * Abilis Systems Single DVB-T Receiver
+ * Copyright (C) 2008 Pierrick Hascoet <pierrick.hascoet@abilis.com>
+ * Copyright (C) 2010 Devin Heitmueller <dheitmueller@kernellabs.com>
+ *
+ * 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, 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/mm.h>
+#include <linux/usb.h>
+
+#include "as102_drv.h"
+#include "as102_usb_drv.h"
+#include "as102_fw.h"
+
+static void as102_usb_disconnect(struct usb_interface *interface);
+static int as102_usb_probe(struct usb_interface *interface,
+ const struct usb_device_id *id);
+
+static int as102_usb_start_stream(struct as102_dev_t *dev);
+static void as102_usb_stop_stream(struct as102_dev_t *dev);
+
+static int as102_open(struct inode *inode, struct file *file);
+static int as102_release(struct inode *inode, struct file *file);
+
+static struct usb_device_id as102_usb_id_table[] = {
+ { USB_DEVICE(AS102_USB_DEVICE_VENDOR_ID, AS102_USB_DEVICE_PID_0001) },
+ { USB_DEVICE(PCTV_74E_USB_VID, PCTV_74E_USB_PID) },
+ { USB_DEVICE(ELGATO_EYETV_DTT_USB_VID, ELGATO_EYETV_DTT_USB_PID) },
+ { USB_DEVICE(NBOX_DVBT_DONGLE_USB_VID, NBOX_DVBT_DONGLE_USB_PID) },
+ { USB_DEVICE(SKY_IT_DIGITAL_KEY_USB_VID, SKY_IT_DIGITAL_KEY_USB_PID) },
+ { } /* Terminating entry */
+};
+
+/* Note that this table must always have the same number of entries as the
+ as102_usb_id_table struct */
+static const char * const as102_device_names[] = {
+ AS102_REFERENCE_DESIGN,
+ AS102_PCTV_74E,
+ AS102_ELGATO_EYETV_DTT_NAME,
+ AS102_NBOX_DVBT_DONGLE_NAME,
+ AS102_SKY_IT_DIGITAL_KEY_NAME,
+ NULL /* Terminating entry */
+};
+
+/* eLNA configuration: devices built on the reference design work best
+ with 0xA0, while custom designs seem to require 0xC0 */
+static uint8_t const as102_elna_cfg[] = {
+ 0xA0,
+ 0xC0,
+ 0xC0,
+ 0xA0,
+ 0xA0,
+ 0x00 /* Terminating entry */
+};
+
+struct usb_driver as102_usb_driver = {
+ .name = DRIVER_FULL_NAME,
+ .probe = as102_usb_probe,
+ .disconnect = as102_usb_disconnect,
+ .id_table = as102_usb_id_table
+};
+
+static const struct file_operations as102_dev_fops = {
+ .owner = THIS_MODULE,
+ .open = as102_open,
+ .release = as102_release,
+};
+
+static struct usb_class_driver as102_usb_class_driver = {
+ .name = "aton2-%d",
+ .fops = &as102_dev_fops,
+ .minor_base = AS102_DEVICE_MAJOR,
+};
+
+static int as102_usb_xfer_cmd(struct as10x_bus_adapter_t *bus_adap,
+ unsigned char *send_buf, int send_buf_len,
+ unsigned char *recv_buf, int recv_buf_len)
+{
+ int ret = 0;
+
+ if (send_buf != NULL) {
+ ret = usb_control_msg(bus_adap->usb_dev,
+ usb_sndctrlpipe(bus_adap->usb_dev, 0),
+ AS102_USB_DEVICE_TX_CTRL_CMD,
+ USB_DIR_OUT | USB_TYPE_VENDOR |
+ USB_RECIP_DEVICE,
+ bus_adap->cmd_xid, /* value */
+ 0, /* index */
+ send_buf, send_buf_len,
+ USB_CTRL_SET_TIMEOUT /* 200 */);
+ if (ret < 0) {
+ dprintk(debug, "usb_control_msg(send) failed, err %i\n",
+ ret);
+ return ret;
+ }
+
+ if (ret != send_buf_len) {
+ dprintk(debug, "only wrote %d of %d bytes\n",
+ ret, send_buf_len);
+ return -1;
+ }
+ }
+
+ if (recv_buf != NULL) {
+#ifdef TRACE
+ dprintk(debug, "want to read: %d bytes\n", recv_buf_len);
+#endif
+ ret = usb_control_msg(bus_adap->usb_dev,
+ usb_rcvctrlpipe(bus_adap->usb_dev, 0),
+ AS102_USB_DEVICE_RX_CTRL_CMD,
+ USB_DIR_IN | USB_TYPE_VENDOR |
+ USB_RECIP_DEVICE,
+ bus_adap->cmd_xid, /* value */
+ 0, /* index */
+ recv_buf, recv_buf_len,
+ USB_CTRL_GET_TIMEOUT /* 200 */);
+ if (ret < 0) {
+ dprintk(debug, "usb_control_msg(recv) failed, err %i\n",
+ ret);
+ return ret;
+ }
+#ifdef TRACE
+ dprintk(debug, "read %d bytes\n", recv_buf_len);
+#endif
+ }
+
+ return ret;
+}
+
+static int as102_send_ep1(struct as10x_bus_adapter_t *bus_adap,
+ unsigned char *send_buf,
+ int send_buf_len,
+ int swap32)
+{
+ int ret = 0, actual_len;
+
+ ret = usb_bulk_msg(bus_adap->usb_dev,
+ usb_sndbulkpipe(bus_adap->usb_dev, 1),
+ send_buf, send_buf_len, &actual_len, 200);
+ if (ret) {
+ dprintk(debug, "usb_bulk_msg(send) failed, err %i\n", ret);
+ return ret;
+ }
+
+ if (actual_len != send_buf_len) {
+ dprintk(debug, "only wrote %d of %d bytes\n",
+ actual_len, send_buf_len);
+ return -1;
+ }
+ return ret ? ret : actual_len;
+}
+
+static int as102_read_ep2(struct as10x_bus_adapter_t *bus_adap,
+ unsigned char *recv_buf, int recv_buf_len)
+{
+ int ret = 0, actual_len;
+
+ if (recv_buf == NULL)
+ return -EINVAL;
+
+ ret = usb_bulk_msg(bus_adap->usb_dev,
+ usb_rcvbulkpipe(bus_adap->usb_dev, 2),
+ recv_buf, recv_buf_len, &actual_len, 200);
+ if (ret) {
+ dprintk(debug, "usb_bulk_msg(recv) failed, err %i\n", ret);
+ return ret;
+ }
+
+ if (actual_len != recv_buf_len) {
+ dprintk(debug, "only read %d of %d bytes\n",
+ actual_len, recv_buf_len);
+ return -1;
+ }
+ return ret ? ret : actual_len;
+}
+
+static struct as102_priv_ops_t as102_priv_ops = {
+ .upload_fw_pkt = as102_send_ep1,
+ .xfer_cmd = as102_usb_xfer_cmd,
+ .as102_read_ep2 = as102_read_ep2,
+ .start_stream = as102_usb_start_stream,
+ .stop_stream = as102_usb_stop_stream,
+};
+
+static int as102_submit_urb_stream(struct as102_dev_t *dev, struct urb *urb)
+{
+ int err;
+
+ usb_fill_bulk_urb(urb,
+ dev->bus_adap.usb_dev,
+ usb_rcvbulkpipe(dev->bus_adap.usb_dev, 0x2),
+ urb->transfer_buffer,
+ AS102_USB_BUF_SIZE,
+ as102_urb_stream_irq,
+ dev);
+
+ err = usb_submit_urb(urb, GFP_ATOMIC);
+ if (err)
+ dprintk(debug, "%s: usb_submit_urb failed\n", __func__);
+
+ return err;
+}
+
+void as102_urb_stream_irq(struct urb *urb)
+{
+ struct as102_dev_t *as102_dev = urb->context;
+
+ if (urb->actual_length > 0) {
+ dvb_dmx_swfilter(&as102_dev->dvb_dmx,
+ urb->transfer_buffer,
+ urb->actual_length);
+ } else {
+ if (urb->actual_length == 0)
+ memset(urb->transfer_buffer, 0, AS102_USB_BUF_SIZE);
+ }
+
+ /* is not stopped, re-submit urb */
+ if (as102_dev->streaming)
+ as102_submit_urb_stream(as102_dev, urb);
+}
+
+static void as102_free_usb_stream_buffer(struct as102_dev_t *dev)
+{
+ int i;
+
+ for (i = 0; i < MAX_STREAM_URB; i++)
+ usb_free_urb(dev->stream_urb[i]);
+
+ usb_free_coherent(dev->bus_adap.usb_dev,
+ MAX_STREAM_URB * AS102_USB_BUF_SIZE,
+ dev->stream,
+ dev->dma_addr);
+}
+
+static int as102_alloc_usb_stream_buffer(struct as102_dev_t *dev)
+{
+ int i;
+
+ dev->stream = usb_alloc_coherent(dev->bus_adap.usb_dev,
+ MAX_STREAM_URB * AS102_USB_BUF_SIZE,
+ GFP_KERNEL,
+ &dev->dma_addr);
+ if (!dev->stream) {
+ dprintk(debug, "%s: usb_buffer_alloc failed\n", __func__);
+ return -ENOMEM;
+ }
+
+ memset(dev->stream, 0, MAX_STREAM_URB * AS102_USB_BUF_SIZE);
+
+ /* init urb buffers */
+ for (i = 0; i < MAX_STREAM_URB; i++) {
+ struct urb *urb;
+
+ urb = usb_alloc_urb(0, GFP_ATOMIC);
+ if (urb == NULL) {
+ dprintk(debug, "%s: usb_alloc_urb failed\n", __func__);
+ as102_free_usb_stream_buffer(dev);
+ return -ENOMEM;
+ }
+
+ urb->transfer_buffer = dev->stream + (i * AS102_USB_BUF_SIZE);
+ urb->transfer_dma = dev->dma_addr + (i * AS102_USB_BUF_SIZE);
+ urb->transfer_flags = URB_NO_TRANSFER_DMA_MAP;
+ urb->transfer_buffer_length = AS102_USB_BUF_SIZE;
+
+ dev->stream_urb[i] = urb;
+ }
+ return 0;
+}
+
+static void as102_usb_stop_stream(struct as102_dev_t *dev)
+{
+ int i;
+
+ for (i = 0; i < MAX_STREAM_URB; i++)
+ usb_kill_urb(dev->stream_urb[i]);
+}
+
+static int as102_usb_start_stream(struct as102_dev_t *dev)
+{
+ int i, ret = 0;
+
+ for (i = 0; i < MAX_STREAM_URB; i++) {
+ ret = as102_submit_urb_stream(dev, dev->stream_urb[i]);
+ if (ret) {
+ as102_usb_stop_stream(dev);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static void as102_usb_release(struct kref *kref)
+{
+ struct as102_dev_t *as102_dev;
+
+ as102_dev = container_of(kref, struct as102_dev_t, kref);
+ if (as102_dev != NULL) {
+ usb_put_dev(as102_dev->bus_adap.usb_dev);
+ kfree(as102_dev);
+ }
+}
+
+static void as102_usb_disconnect(struct usb_interface *intf)
+{
+ struct as102_dev_t *as102_dev;
+
+ /* extract as102_dev_t from usb_device private data */
+ as102_dev = usb_get_intfdata(intf);
+
+ /* unregister dvb layer */
+ as102_dvb_unregister(as102_dev);
+
+ /* free usb buffers */
+ as102_free_usb_stream_buffer(as102_dev);
+
+ usb_set_intfdata(intf, NULL);
+
+ /* usb unregister device */
+ usb_deregister_dev(intf, &as102_usb_class_driver);
+
+ /* decrement usage counter */
+ kref_put(&as102_dev->kref, as102_usb_release);
+
+ pr_info("%s: device has been disconnected\n", DRIVER_NAME);
+}
+
+static int as102_usb_probe(struct usb_interface *intf,
+ const struct usb_device_id *id)
+{
+ int ret;
+ struct as102_dev_t *as102_dev;
+ int i;
+
+ /* This should never actually happen */
+ if (ARRAY_SIZE(as102_usb_id_table) !=
+ (sizeof(as102_device_names) / sizeof(const char *))) {
+ pr_err("Device names table invalid size");
+ return -EINVAL;
+ }
+
+ as102_dev = kzalloc(sizeof(struct as102_dev_t), GFP_KERNEL);
+ if (as102_dev == NULL)
+ return -ENOMEM;
+
+ /* Assign the user-friendly device name */
+ for (i = 0; i < ARRAY_SIZE(as102_usb_id_table); i++) {
+ if (id == &as102_usb_id_table[i]) {
+ as102_dev->name = as102_device_names[i];
+ as102_dev->elna_cfg = as102_elna_cfg[i];
+ }
+ }
+
+ if (as102_dev->name == NULL)
+ as102_dev->name = "Unknown AS102 device";
+
+ /* set private callback functions */
+ as102_dev->bus_adap.ops = &as102_priv_ops;
+
+ /* init cmd token for usb bus */
+ as102_dev->bus_adap.cmd = &as102_dev->bus_adap.token.usb.c;
+ as102_dev->bus_adap.rsp = &as102_dev->bus_adap.token.usb.r;
+
+ /* init kernel device reference */
+ kref_init(&as102_dev->kref);
+
+ /* store as102 device to usb_device private data */
+ usb_set_intfdata(intf, (void *) as102_dev);
+
+ /* store in as102 device the usb_device pointer */
+ as102_dev->bus_adap.usb_dev = usb_get_dev(interface_to_usbdev(intf));
+
+ /* we can register the device now, as it is ready */
+ ret = usb_register_dev(intf, &as102_usb_class_driver);
+ if (ret < 0) {
+ /* something prevented us from registering this driver */
+ dev_err(&intf->dev,
+ "%s: usb_register_dev() failed (errno = %d)\n",
+ __func__, ret);
+ goto failed;
+ }
+
+ pr_info("%s: device has been detected\n", DRIVER_NAME);
+
+ /* request buffer allocation for streaming */
+ ret = as102_alloc_usb_stream_buffer(as102_dev);
+ if (ret != 0)
+ goto failed_stream;
+
+ /* register dvb layer */
+ ret = as102_dvb_register(as102_dev);
+ if (ret != 0)
+ goto failed_dvb;
+
+ return ret;
+
+failed_dvb:
+ as102_free_usb_stream_buffer(as102_dev);
+failed_stream:
+ usb_deregister_dev(intf, &as102_usb_class_driver);
+failed:
+ usb_put_dev(as102_dev->bus_adap.usb_dev);
+ usb_set_intfdata(intf, NULL);
+ kfree(as102_dev);
+ return ret;
+}
+
+static int as102_open(struct inode *inode, struct file *file)
+{
+ int ret = 0, minor = 0;
+ struct usb_interface *intf = NULL;
+ struct as102_dev_t *dev = NULL;
+
+ /* read minor from inode */
+ minor = iminor(inode);
+
+ /* fetch device from usb interface */
+ intf = usb_find_interface(&as102_usb_driver, minor);
+ if (intf == NULL) {
+ pr_err("%s: can't find device for minor %d\n",
+ __func__, minor);
+ ret = -ENODEV;
+ goto exit;
+ }
+
+ /* get our device */
+ dev = usb_get_intfdata(intf);
+ if (dev == NULL) {
+ ret = -EFAULT;
+ goto exit;
+ }
+
+ /* save our device object in the file's private structure */
+ file->private_data = dev;
+
+ /* increment our usage count for the device */
+ kref_get(&dev->kref);
+
+exit:
+ return ret;
+}
+
+static int as102_release(struct inode *inode, struct file *file)
+{
+ struct as102_dev_t *dev = NULL;
+
+ dev = file->private_data;
+ if (dev != NULL) {
+ /* decrement the count on our device */
+ kref_put(&dev->kref, as102_usb_release);
+ }
+
+ return 0;
+}
+
+MODULE_DEVICE_TABLE(usb, as102_usb_id_table);
diff --git a/drivers/staging/media/as102/as102_usb_drv.h b/drivers/staging/media/as102/as102_usb_drv.h
new file mode 100644
index 00000000000..1ad1ec52b11
--- /dev/null
+++ b/drivers/staging/media/as102/as102_usb_drv.h
@@ -0,0 +1,61 @@
+/*
+ * Abilis Systems Single DVB-T Receiver
+ * Copyright (C) 2008 Pierrick Hascoet <pierrick.hascoet@abilis.com>
+ * Copyright (C) 2010 Devin Heitmueller <dheitmueller@kernellabs.com>
+ *
+ * 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, 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+#ifndef _AS102_USB_DRV_H_
+#define _AS102_USB_DRV_H_
+
+#define AS102_USB_DEVICE_TX_CTRL_CMD 0xF1
+#define AS102_USB_DEVICE_RX_CTRL_CMD 0xF2
+
+/* define these values to match the supported devices */
+
+/* Abilis system: "TITAN" */
+#define AS102_REFERENCE_DESIGN "Abilis Systems DVB-Titan"
+#define AS102_USB_DEVICE_VENDOR_ID 0x1BA6
+#define AS102_USB_DEVICE_PID_0001 0x0001
+
+/* PCTV Systems: PCTV picoStick (74e) */
+#define AS102_PCTV_74E "PCTV Systems picoStick (74e)"
+#define PCTV_74E_USB_VID 0x2013
+#define PCTV_74E_USB_PID 0x0246
+
+/* Elgato: EyeTV DTT Deluxe */
+#define AS102_ELGATO_EYETV_DTT_NAME "Elgato EyeTV DTT Deluxe"
+#define ELGATO_EYETV_DTT_USB_VID 0x0fd9
+#define ELGATO_EYETV_DTT_USB_PID 0x002c
+
+/* nBox: nBox DVB-T Dongle */
+#define AS102_NBOX_DVBT_DONGLE_NAME "nBox DVB-T Dongle"
+#define NBOX_DVBT_DONGLE_USB_VID 0x0b89
+#define NBOX_DVBT_DONGLE_USB_PID 0x0007
+
+/* Sky Italia: Digital Key (green led) */
+#define AS102_SKY_IT_DIGITAL_KEY_NAME "Sky IT Digital Key (green led)"
+#define SKY_IT_DIGITAL_KEY_USB_VID 0x2137
+#define SKY_IT_DIGITAL_KEY_USB_PID 0x0001
+
+void as102_urb_stream_irq(struct urb *urb);
+
+struct as10x_usb_token_cmd_t {
+ /* token cmd */
+ struct as10x_cmd_t c;
+ /* token response */
+ struct as10x_cmd_t r;
+};
+#endif
diff --git a/drivers/staging/media/as102/as10x_cmd.c b/drivers/staging/media/as102/as10x_cmd.c
new file mode 100644
index 00000000000..9e49f15a7c9
--- /dev/null
+++ b/drivers/staging/media/as102/as10x_cmd.c
@@ -0,0 +1,418 @@
+/*
+ * Abilis Systems Single DVB-T Receiver
+ * Copyright (C) 2008 Pierrick Hascoet <pierrick.hascoet@abilis.com>
+ * Copyright (C) 2010 Devin Heitmueller <dheitmueller@kernellabs.com>
+ *
+ * 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, 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/kernel.h>
+#include "as102_drv.h"
+#include "as10x_types.h"
+#include "as10x_cmd.h"
+
+/**
+ * as10x_cmd_turn_on - send turn on command to AS10x
+ * @adap: pointer to AS10x bus adapter
+ *
+ * Return 0 when no error, < 0 in case of error.
+ */
+int as10x_cmd_turn_on(struct as10x_bus_adapter_t *adap)
+{
+ int error = AS10X_CMD_ERROR;
+ struct as10x_cmd_t *pcmd, *prsp;
+
+ pcmd = adap->cmd;
+ prsp = adap->rsp;
+
+ /* prepare command */
+ as10x_cmd_build(pcmd, (++adap->cmd_xid),
+ sizeof(pcmd->body.turn_on.req));
+
+ /* fill command */
+ pcmd->body.turn_on.req.proc_id = cpu_to_le16(CONTROL_PROC_TURNON);
+
+ /* send command */
+ if (adap->ops->xfer_cmd) {
+ error = adap->ops->xfer_cmd(adap, (uint8_t *) pcmd,
+ sizeof(pcmd->body.turn_on.req) +
+ HEADER_SIZE,
+ (uint8_t *) prsp,
+ sizeof(prsp->body.turn_on.rsp) +
+ HEADER_SIZE);
+ }
+
+ if (error < 0)
+ goto out;
+
+ /* parse response */
+ error = as10x_rsp_parse(prsp, CONTROL_PROC_TURNON_RSP);
+
+out:
+ return error;
+}
+
+/**
+ * as10x_cmd_turn_off - send turn off command to AS10x
+ * @adap: pointer to AS10x bus adapter
+ *
+ * Return 0 on success or negative value in case of error.
+ */
+int as10x_cmd_turn_off(struct as10x_bus_adapter_t *adap)
+{
+ int error = AS10X_CMD_ERROR;
+ struct as10x_cmd_t *pcmd, *prsp;
+
+ pcmd = adap->cmd;
+ prsp = adap->rsp;
+
+ /* prepare command */
+ as10x_cmd_build(pcmd, (++adap->cmd_xid),
+ sizeof(pcmd->body.turn_off.req));
+
+ /* fill command */
+ pcmd->body.turn_off.req.proc_id = cpu_to_le16(CONTROL_PROC_TURNOFF);
+
+ /* send command */
+ if (adap->ops->xfer_cmd) {
+ error = adap->ops->xfer_cmd(
+ adap, (uint8_t *) pcmd,
+ sizeof(pcmd->body.turn_off.req) + HEADER_SIZE,
+ (uint8_t *) prsp,
+ sizeof(prsp->body.turn_off.rsp) + HEADER_SIZE);
+ }
+
+ if (error < 0)
+ goto out;
+
+ /* parse response */
+ error = as10x_rsp_parse(prsp, CONTROL_PROC_TURNOFF_RSP);
+
+out:
+ return error;
+}
+
+/**
+ * as10x_cmd_set_tune - send set tune command to AS10x
+ * @adap: pointer to AS10x bus adapter
+ * @ptune: tune parameters
+ *
+ * Return 0 on success or negative value in case of error.
+ */
+int as10x_cmd_set_tune(struct as10x_bus_adapter_t *adap,
+ struct as10x_tune_args *ptune)
+{
+ int error = AS10X_CMD_ERROR;
+ struct as10x_cmd_t *preq, *prsp;
+
+ preq = adap->cmd;
+ prsp = adap->rsp;
+
+ /* prepare command */
+ as10x_cmd_build(preq, (++adap->cmd_xid),
+ sizeof(preq->body.set_tune.req));
+
+ /* fill command */
+ preq->body.set_tune.req.proc_id = cpu_to_le16(CONTROL_PROC_SETTUNE);
+ preq->body.set_tune.req.args.freq = cpu_to_le32(ptune->freq);
+ preq->body.set_tune.req.args.bandwidth = ptune->bandwidth;
+ preq->body.set_tune.req.args.hier_select = ptune->hier_select;
+ preq->body.set_tune.req.args.modulation = ptune->modulation;
+ preq->body.set_tune.req.args.hierarchy = ptune->hierarchy;
+ preq->body.set_tune.req.args.interleaving_mode =
+ ptune->interleaving_mode;
+ preq->body.set_tune.req.args.code_rate = ptune->code_rate;
+ preq->body.set_tune.req.args.guard_interval = ptune->guard_interval;
+ preq->body.set_tune.req.args.transmission_mode =
+ ptune->transmission_mode;
+
+ /* send command */
+ if (adap->ops->xfer_cmd) {
+ error = adap->ops->xfer_cmd(adap,
+ (uint8_t *) preq,
+ sizeof(preq->body.set_tune.req)
+ + HEADER_SIZE,
+ (uint8_t *) prsp,
+ sizeof(prsp->body.set_tune.rsp)
+ + HEADER_SIZE);
+ }
+
+ if (error < 0)
+ goto out;
+
+ /* parse response */
+ error = as10x_rsp_parse(prsp, CONTROL_PROC_SETTUNE_RSP);
+
+out:
+ return error;
+}
+
+/**
+ * as10x_cmd_get_tune_status - send get tune status command to AS10x
+ * @adap: pointer to AS10x bus adapter
+ * @pstatus: pointer to updated status structure of the current tune
+ *
+ * Return 0 on success or negative value in case of error.
+ */
+int as10x_cmd_get_tune_status(struct as10x_bus_adapter_t *adap,
+ struct as10x_tune_status *pstatus)
+{
+ int error = AS10X_CMD_ERROR;
+ struct as10x_cmd_t *preq, *prsp;
+
+ preq = adap->cmd;
+ prsp = adap->rsp;
+
+ /* prepare command */
+ as10x_cmd_build(preq, (++adap->cmd_xid),
+ sizeof(preq->body.get_tune_status.req));
+
+ /* fill command */
+ preq->body.get_tune_status.req.proc_id =
+ cpu_to_le16(CONTROL_PROC_GETTUNESTAT);
+
+ /* send command */
+ if (adap->ops->xfer_cmd) {
+ error = adap->ops->xfer_cmd(
+ adap,
+ (uint8_t *) preq,
+ sizeof(preq->body.get_tune_status.req) + HEADER_SIZE,
+ (uint8_t *) prsp,
+ sizeof(prsp->body.get_tune_status.rsp) + HEADER_SIZE);
+ }
+
+ if (error < 0)
+ goto out;
+
+ /* parse response */
+ error = as10x_rsp_parse(prsp, CONTROL_PROC_GETTUNESTAT_RSP);
+ if (error < 0)
+ goto out;
+
+ /* Response OK -> get response data */
+ pstatus->tune_state = prsp->body.get_tune_status.rsp.sts.tune_state;
+ pstatus->signal_strength =
+ le16_to_cpu(prsp->body.get_tune_status.rsp.sts.signal_strength);
+ pstatus->PER = le16_to_cpu(prsp->body.get_tune_status.rsp.sts.PER);
+ pstatus->BER = le16_to_cpu(prsp->body.get_tune_status.rsp.sts.BER);
+
+out:
+ return error;
+}
+
+/**
+ * as10x_cmd_get_tps - send get TPS command to AS10x
+ * @adap: pointer to AS10x handle
+ * @ptps: pointer to TPS parameters structure
+ *
+ * Return 0 on success or negative value in case of error.
+ */
+int as10x_cmd_get_tps(struct as10x_bus_adapter_t *adap, struct as10x_tps *ptps)
+{
+ int error = AS10X_CMD_ERROR;
+ struct as10x_cmd_t *pcmd, *prsp;
+
+ pcmd = adap->cmd;
+ prsp = adap->rsp;
+
+ /* prepare command */
+ as10x_cmd_build(pcmd, (++adap->cmd_xid),
+ sizeof(pcmd->body.get_tps.req));
+
+ /* fill command */
+ pcmd->body.get_tune_status.req.proc_id =
+ cpu_to_le16(CONTROL_PROC_GETTPS);
+
+ /* send command */
+ if (adap->ops->xfer_cmd) {
+ error = adap->ops->xfer_cmd(adap,
+ (uint8_t *) pcmd,
+ sizeof(pcmd->body.get_tps.req) +
+ HEADER_SIZE,
+ (uint8_t *) prsp,
+ sizeof(prsp->body.get_tps.rsp) +
+ HEADER_SIZE);
+ }
+
+ if (error < 0)
+ goto out;
+
+ /* parse response */
+ error = as10x_rsp_parse(prsp, CONTROL_PROC_GETTPS_RSP);
+ if (error < 0)
+ goto out;
+
+ /* Response OK -> get response data */
+ ptps->modulation = prsp->body.get_tps.rsp.tps.modulation;
+ ptps->hierarchy = prsp->body.get_tps.rsp.tps.hierarchy;
+ ptps->interleaving_mode = prsp->body.get_tps.rsp.tps.interleaving_mode;
+ ptps->code_rate_HP = prsp->body.get_tps.rsp.tps.code_rate_HP;
+ ptps->code_rate_LP = prsp->body.get_tps.rsp.tps.code_rate_LP;
+ ptps->guard_interval = prsp->body.get_tps.rsp.tps.guard_interval;
+ ptps->transmission_mode = prsp->body.get_tps.rsp.tps.transmission_mode;
+ ptps->DVBH_mask_HP = prsp->body.get_tps.rsp.tps.DVBH_mask_HP;
+ ptps->DVBH_mask_LP = prsp->body.get_tps.rsp.tps.DVBH_mask_LP;
+ ptps->cell_ID = le16_to_cpu(prsp->body.get_tps.rsp.tps.cell_ID);
+
+out:
+ return error;
+}
+
+/**
+ * as10x_cmd_get_demod_stats - send get demod stats command to AS10x
+ * @adap: pointer to AS10x bus adapter
+ * @pdemod_stats: pointer to demod stats parameters structure
+ *
+ * Return 0 on success or negative value in case of error.
+ */
+int as10x_cmd_get_demod_stats(struct as10x_bus_adapter_t *adap,
+ struct as10x_demod_stats *pdemod_stats)
+{
+ int error = AS10X_CMD_ERROR;
+ struct as10x_cmd_t *pcmd, *prsp;
+
+ pcmd = adap->cmd;
+ prsp = adap->rsp;
+
+ /* prepare command */
+ as10x_cmd_build(pcmd, (++adap->cmd_xid),
+ sizeof(pcmd->body.get_demod_stats.req));
+
+ /* fill command */
+ pcmd->body.get_demod_stats.req.proc_id =
+ cpu_to_le16(CONTROL_PROC_GET_DEMOD_STATS);
+
+ /* send command */
+ if (adap->ops->xfer_cmd) {
+ error = adap->ops->xfer_cmd(adap,
+ (uint8_t *) pcmd,
+ sizeof(pcmd->body.get_demod_stats.req)
+ + HEADER_SIZE,
+ (uint8_t *) prsp,
+ sizeof(prsp->body.get_demod_stats.rsp)
+ + HEADER_SIZE);
+ }
+
+ if (error < 0)
+ goto out;
+
+ /* parse response */
+ error = as10x_rsp_parse(prsp, CONTROL_PROC_GET_DEMOD_STATS_RSP);
+ if (error < 0)
+ goto out;
+
+ /* Response OK -> get response data */
+ pdemod_stats->frame_count =
+ le32_to_cpu(prsp->body.get_demod_stats.rsp.stats.frame_count);
+ pdemod_stats->bad_frame_count =
+ le32_to_cpu(prsp->body.get_demod_stats.rsp.stats.bad_frame_count);
+ pdemod_stats->bytes_fixed_by_rs =
+ le32_to_cpu(prsp->body.get_demod_stats.rsp.stats.bytes_fixed_by_rs);
+ pdemod_stats->mer =
+ le16_to_cpu(prsp->body.get_demod_stats.rsp.stats.mer);
+ pdemod_stats->has_started =
+ prsp->body.get_demod_stats.rsp.stats.has_started;
+
+out:
+ return error;
+}
+
+/**
+ * as10x_cmd_get_impulse_resp - send get impulse response command to AS10x
+ * @adap: pointer to AS10x bus adapter
+ * @is_ready: pointer to value indicating when impulse
+ * response data is ready
+ *
+ * Return 0 on success or negative value in case of error.
+ */
+int as10x_cmd_get_impulse_resp(struct as10x_bus_adapter_t *adap,
+ uint8_t *is_ready)
+{
+ int error = AS10X_CMD_ERROR;
+ struct as10x_cmd_t *pcmd, *prsp;
+
+ pcmd = adap->cmd;
+ prsp = adap->rsp;
+
+ /* prepare command */
+ as10x_cmd_build(pcmd, (++adap->cmd_xid),
+ sizeof(pcmd->body.get_impulse_rsp.req));
+
+ /* fill command */
+ pcmd->body.get_impulse_rsp.req.proc_id =
+ cpu_to_le16(CONTROL_PROC_GET_IMPULSE_RESP);
+
+ /* send command */
+ if (adap->ops->xfer_cmd) {
+ error = adap->ops->xfer_cmd(adap,
+ (uint8_t *) pcmd,
+ sizeof(pcmd->body.get_impulse_rsp.req)
+ + HEADER_SIZE,
+ (uint8_t *) prsp,
+ sizeof(prsp->body.get_impulse_rsp.rsp)
+ + HEADER_SIZE);
+ }
+
+ if (error < 0)
+ goto out;
+
+ /* parse response */
+ error = as10x_rsp_parse(prsp, CONTROL_PROC_GET_IMPULSE_RESP_RSP);
+ if (error < 0)
+ goto out;
+
+ /* Response OK -> get response data */
+ *is_ready = prsp->body.get_impulse_rsp.rsp.is_ready;
+
+out:
+ return error;
+}
+
+/**
+ * as10x_cmd_build - build AS10x command header
+ * @pcmd: pointer to AS10x command buffer
+ * @xid: sequence id of the command
+ * @cmd_len: length of the command
+ */
+void as10x_cmd_build(struct as10x_cmd_t *pcmd,
+ uint16_t xid, uint16_t cmd_len)
+{
+ pcmd->header.req_id = cpu_to_le16(xid);
+ pcmd->header.prog = cpu_to_le16(SERVICE_PROG_ID);
+ pcmd->header.version = cpu_to_le16(SERVICE_PROG_VERSION);
+ pcmd->header.data_len = cpu_to_le16(cmd_len);
+}
+
+/**
+ * as10x_rsp_parse - Parse command response
+ * @prsp: pointer to AS10x command buffer
+ * @proc_id: id of the command
+ *
+ * Return 0 on success or negative value in case of error.
+ */
+int as10x_rsp_parse(struct as10x_cmd_t *prsp, uint16_t proc_id)
+{
+ int error;
+
+ /* extract command error code */
+ error = prsp->body.common.rsp.error;
+
+ if ((error == 0) &&
+ (le16_to_cpu(prsp->body.common.rsp.proc_id) == proc_id)) {
+ return 0;
+ }
+
+ return AS10X_CMD_ERROR;
+}
diff --git a/drivers/staging/media/as102/as10x_cmd.h b/drivers/staging/media/as102/as10x_cmd.h
new file mode 100644
index 00000000000..e21ec6c702a
--- /dev/null
+++ b/drivers/staging/media/as102/as10x_cmd.h
@@ -0,0 +1,529 @@
+/*
+ * Abilis Systems Single DVB-T Receiver
+ * Copyright (C) 2008 Pierrick Hascoet <pierrick.hascoet@abilis.com>
+ *
+ * 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, 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+#ifndef _AS10X_CMD_H_
+#define _AS10X_CMD_H_
+
+#ifdef __KERNEL__
+#include <linux/kernel.h>
+#endif
+
+#include "as10x_types.h"
+
+/*********************************/
+/* MACRO DEFINITIONS */
+/*********************************/
+#define AS10X_CMD_ERROR -1
+
+#define SERVICE_PROG_ID 0x0002
+#define SERVICE_PROG_VERSION 0x0001
+
+#define HIER_NONE 0x00
+#define HIER_LOW_PRIORITY 0x01
+
+#define HEADER_SIZE (sizeof(struct as10x_cmd_header_t))
+
+/* context request types */
+#define GET_CONTEXT_DATA 1
+#define SET_CONTEXT_DATA 2
+
+/* ODSP suspend modes */
+#define CFG_MODE_ODSP_RESUME 0
+#define CFG_MODE_ODSP_SUSPEND 1
+
+/* Dump memory size */
+#define DUMP_BLOCK_SIZE_MAX 0x20
+
+/*********************************/
+/* TYPE DEFINITION */
+/*********************************/
+enum control_proc {
+ CONTROL_PROC_TURNON = 0x0001,
+ CONTROL_PROC_TURNON_RSP = 0x0100,
+ CONTROL_PROC_SET_REGISTER = 0x0002,
+ CONTROL_PROC_SET_REGISTER_RSP = 0x0200,
+ CONTROL_PROC_GET_REGISTER = 0x0003,
+ CONTROL_PROC_GET_REGISTER_RSP = 0x0300,
+ CONTROL_PROC_SETTUNE = 0x000A,
+ CONTROL_PROC_SETTUNE_RSP = 0x0A00,
+ CONTROL_PROC_GETTUNESTAT = 0x000B,
+ CONTROL_PROC_GETTUNESTAT_RSP = 0x0B00,
+ CONTROL_PROC_GETTPS = 0x000D,
+ CONTROL_PROC_GETTPS_RSP = 0x0D00,
+ CONTROL_PROC_SETFILTER = 0x000E,
+ CONTROL_PROC_SETFILTER_RSP = 0x0E00,
+ CONTROL_PROC_REMOVEFILTER = 0x000F,
+ CONTROL_PROC_REMOVEFILTER_RSP = 0x0F00,
+ CONTROL_PROC_GET_IMPULSE_RESP = 0x0012,
+ CONTROL_PROC_GET_IMPULSE_RESP_RSP = 0x1200,
+ CONTROL_PROC_START_STREAMING = 0x0013,
+ CONTROL_PROC_START_STREAMING_RSP = 0x1300,
+ CONTROL_PROC_STOP_STREAMING = 0x0014,
+ CONTROL_PROC_STOP_STREAMING_RSP = 0x1400,
+ CONTROL_PROC_GET_DEMOD_STATS = 0x0015,
+ CONTROL_PROC_GET_DEMOD_STATS_RSP = 0x1500,
+ CONTROL_PROC_ELNA_CHANGE_MODE = 0x0016,
+ CONTROL_PROC_ELNA_CHANGE_MODE_RSP = 0x1600,
+ CONTROL_PROC_ODSP_CHANGE_MODE = 0x0017,
+ CONTROL_PROC_ODSP_CHANGE_MODE_RSP = 0x1700,
+ CONTROL_PROC_AGC_CHANGE_MODE = 0x0018,
+ CONTROL_PROC_AGC_CHANGE_MODE_RSP = 0x1800,
+
+ CONTROL_PROC_CONTEXT = 0x00FC,
+ CONTROL_PROC_CONTEXT_RSP = 0xFC00,
+ CONTROL_PROC_DUMP_MEMORY = 0x00FD,
+ CONTROL_PROC_DUMP_MEMORY_RSP = 0xFD00,
+ CONTROL_PROC_DUMPLOG_MEMORY = 0x00FE,
+ CONTROL_PROC_DUMPLOG_MEMORY_RSP = 0xFE00,
+ CONTROL_PROC_TURNOFF = 0x00FF,
+ CONTROL_PROC_TURNOFF_RSP = 0xFF00
+};
+
+union as10x_turn_on {
+ /* request */
+ struct {
+ /* request identifier */
+ uint16_t proc_id;
+ } __packed req;
+ /* response */
+ struct {
+ /* response identifier */
+ uint16_t proc_id;
+ /* error */
+ uint8_t error;
+ } __packed rsp;
+} __packed;
+
+union as10x_turn_off {
+ /* request */
+ struct {
+ /* request identifier */
+ uint16_t proc_id;
+ } __packed req;
+ /* response */
+ struct {
+ /* response identifier */
+ uint16_t proc_id;
+ /* error */
+ uint8_t err;
+ } __packed rsp;
+} __packed;
+
+union as10x_set_tune {
+ /* request */
+ struct {
+ /* request identifier */
+ uint16_t proc_id;
+ /* tune params */
+ struct as10x_tune_args args;
+ } __packed req;
+ /* response */
+ struct {
+ /* response identifier */
+ uint16_t proc_id;
+ /* response error */
+ uint8_t error;
+ } __packed rsp;
+} __packed;
+
+union as10x_get_tune_status {
+ /* request */
+ struct {
+ /* request identifier */
+ uint16_t proc_id;
+ } __packed req;
+ /* response */
+ struct {
+ /* response identifier */
+ uint16_t proc_id;
+ /* response error */
+ uint8_t error;
+ /* tune status */
+ struct as10x_tune_status sts;
+ } __packed rsp;
+} __packed;
+
+union as10x_get_tps {
+ /* request */
+ struct {
+ /* request identifier */
+ uint16_t proc_id;
+ } __packed req;
+ /* response */
+ struct {
+ /* response identifier */
+ uint16_t proc_id;
+ /* response error */
+ uint8_t error;
+ /* tps details */
+ struct as10x_tps tps;
+ } __packed rsp;
+} __packed;
+
+union as10x_common {
+ /* request */
+ struct {
+ /* request identifier */
+ uint16_t proc_id;
+ } __packed req;
+ /* response */
+ struct {
+ /* response identifier */
+ uint16_t proc_id;
+ /* response error */
+ uint8_t error;
+ } __packed rsp;
+} __packed;
+
+union as10x_add_pid_filter {
+ /* request */
+ struct {
+ /* request identifier */
+ uint16_t proc_id;
+ /* PID to filter */
+ uint16_t pid;
+ /* stream type (MPE, PSI/SI or PES )*/
+ uint8_t stream_type;
+ /* PID index in filter table */
+ uint8_t idx;
+ } __packed req;
+ /* response */
+ struct {
+ /* response identifier */
+ uint16_t proc_id;
+ /* response error */
+ uint8_t error;
+ /* Filter id */
+ uint8_t filter_id;
+ } __packed rsp;
+} __packed;
+
+union as10x_del_pid_filter {
+ /* request */
+ struct {
+ /* request identifier */
+ uint16_t proc_id;
+ /* PID to remove */
+ uint16_t pid;
+ } __packed req;
+ /* response */
+ struct {
+ /* response identifier */
+ uint16_t proc_id;
+ /* response error */
+ uint8_t error;
+ } __packed rsp;
+} __packed;
+
+union as10x_start_streaming {
+ /* request */
+ struct {
+ /* request identifier */
+ uint16_t proc_id;
+ } __packed req;
+ /* response */
+ struct {
+ /* response identifier */
+ uint16_t proc_id;
+ /* error */
+ uint8_t error;
+ } __packed rsp;
+} __packed;
+
+union as10x_stop_streaming {
+ /* request */
+ struct {
+ /* request identifier */
+ uint16_t proc_id;
+ } __packed req;
+ /* response */
+ struct {
+ /* response identifier */
+ uint16_t proc_id;
+ /* error */
+ uint8_t error;
+ } __packed rsp;
+} __packed;
+
+union as10x_get_demod_stats {
+ /* request */
+ struct {
+ /* request identifier */
+ uint16_t proc_id;
+ } __packed req;
+ /* response */
+ struct {
+ /* response identifier */
+ uint16_t proc_id;
+ /* error */
+ uint8_t error;
+ /* demod stats */
+ struct as10x_demod_stats stats;
+ } __packed rsp;
+} __packed;
+
+union as10x_get_impulse_resp {
+ /* request */
+ struct {
+ /* request identifier */
+ uint16_t proc_id;
+ } __packed req;
+ /* response */
+ struct {
+ /* response identifier */
+ uint16_t proc_id;
+ /* error */
+ uint8_t error;
+ /* impulse response ready */
+ uint8_t is_ready;
+ } __packed rsp;
+} __packed;
+
+union as10x_fw_context {
+ /* request */
+ struct {
+ /* request identifier */
+ uint16_t proc_id;
+ /* value to write (for set context)*/
+ struct as10x_register_value reg_val;
+ /* context tag */
+ uint16_t tag;
+ /* context request type */
+ uint16_t type;
+ } __packed req;
+ /* response */
+ struct {
+ /* response identifier */
+ uint16_t proc_id;
+ /* value read (for get context) */
+ struct as10x_register_value reg_val;
+ /* context request type */
+ uint16_t type;
+ /* error */
+ uint8_t error;
+ } __packed rsp;
+} __packed;
+
+union as10x_set_register {
+ /* request */
+ struct {
+ /* response identifier */
+ uint16_t proc_id;
+ /* register description */
+ struct as10x_register_addr reg_addr;
+ /* register content */
+ struct as10x_register_value reg_val;
+ } __packed req;
+ /* response */
+ struct {
+ /* response identifier */
+ uint16_t proc_id;
+ /* error */
+ uint8_t error;
+ } __packed rsp;
+} __packed;
+
+union as10x_get_register {
+ /* request */
+ struct {
+ /* response identifier */
+ uint16_t proc_id;
+ /* register description */
+ struct as10x_register_addr reg_addr;
+ } __packed req;
+ /* response */
+ struct {
+ /* response identifier */
+ uint16_t proc_id;
+ /* error */
+ uint8_t error;
+ /* register content */
+ struct as10x_register_value reg_val;
+ } __packed rsp;
+} __packed;
+
+union as10x_cfg_change_mode {
+ /* request */
+ struct {
+ /* request identifier */
+ uint16_t proc_id;
+ /* mode */
+ uint8_t mode;
+ } __packed req;
+ /* response */
+ struct {
+ /* response identifier */
+ uint16_t proc_id;
+ /* error */
+ uint8_t error;
+ } __packed rsp;
+} __packed;
+
+struct as10x_cmd_header_t {
+ uint16_t req_id;
+ uint16_t prog;
+ uint16_t version;
+ uint16_t data_len;
+} __packed;
+
+#define DUMP_BLOCK_SIZE 16
+
+union as10x_dump_memory {
+ /* request */
+ struct {
+ /* request identifier */
+ uint16_t proc_id;
+ /* dump memory type request */
+ uint8_t dump_req;
+ /* register description */
+ struct as10x_register_addr reg_addr;
+ /* nb blocks to read */
+ uint16_t num_blocks;
+ } __packed req;
+ /* response */
+ struct {
+ /* response identifier */
+ uint16_t proc_id;
+ /* error */
+ uint8_t error;
+ /* dump response */
+ uint8_t dump_rsp;
+ /* data */
+ union {
+ uint8_t data8[DUMP_BLOCK_SIZE];
+ uint16_t data16[DUMP_BLOCK_SIZE / sizeof(uint16_t)];
+ uint32_t data32[DUMP_BLOCK_SIZE / sizeof(uint32_t)];
+ } __packed u;
+ } __packed rsp;
+} __packed;
+
+union as10x_dumplog_memory {
+ struct {
+ /* request identifier */
+ uint16_t proc_id;
+ /* dump memory type request */
+ uint8_t dump_req;
+ } __packed req;
+ struct {
+ /* request identifier */
+ uint16_t proc_id;
+ /* error */
+ uint8_t error;
+ /* dump response */
+ uint8_t dump_rsp;
+ /* dump data */
+ uint8_t data[DUMP_BLOCK_SIZE];
+ } __packed rsp;
+} __packed;
+
+union as10x_raw_data {
+ /* request */
+ struct {
+ uint16_t proc_id;
+ uint8_t data[64 - sizeof(struct as10x_cmd_header_t)
+ - 2 /* proc_id */];
+ } __packed req;
+ /* response */
+ struct {
+ uint16_t proc_id;
+ uint8_t error;
+ uint8_t data[64 - sizeof(struct as10x_cmd_header_t)
+ - 2 /* proc_id */ - 1 /* rc */];
+ } __packed rsp;
+} __packed;
+
+struct as10x_cmd_t {
+ struct as10x_cmd_header_t header;
+ union {
+ union as10x_turn_on turn_on;
+ union as10x_turn_off turn_off;
+ union as10x_set_tune set_tune;
+ union as10x_get_tune_status get_tune_status;
+ union as10x_get_tps get_tps;
+ union as10x_common common;
+ union as10x_add_pid_filter add_pid_filter;
+ union as10x_del_pid_filter del_pid_filter;
+ union as10x_start_streaming start_streaming;
+ union as10x_stop_streaming stop_streaming;
+ union as10x_get_demod_stats get_demod_stats;
+ union as10x_get_impulse_resp get_impulse_rsp;
+ union as10x_fw_context context;
+ union as10x_set_register set_register;
+ union as10x_get_register get_register;
+ union as10x_cfg_change_mode cfg_change_mode;
+ union as10x_dump_memory dump_memory;
+ union as10x_dumplog_memory dumplog_memory;
+ union as10x_raw_data raw_data;
+ } __packed body;
+} __packed;
+
+struct as10x_token_cmd_t {
+ /* token cmd */
+ struct as10x_cmd_t c;
+ /* token response */
+ struct as10x_cmd_t r;
+} __packed;
+
+
+/**************************/
+/* FUNCTION DECLARATION */
+/**************************/
+
+void as10x_cmd_build(struct as10x_cmd_t *pcmd, uint16_t proc_id,
+ uint16_t cmd_len);
+int as10x_rsp_parse(struct as10x_cmd_t *r, uint16_t proc_id);
+
+/* as10x cmd */
+int as10x_cmd_turn_on(struct as10x_bus_adapter_t *adap);
+int as10x_cmd_turn_off(struct as10x_bus_adapter_t *adap);
+
+int as10x_cmd_set_tune(struct as10x_bus_adapter_t *adap,
+ struct as10x_tune_args *ptune);
+
+int as10x_cmd_get_tune_status(struct as10x_bus_adapter_t *adap,
+ struct as10x_tune_status *pstatus);
+
+int as10x_cmd_get_tps(struct as10x_bus_adapter_t *adap,
+ struct as10x_tps *ptps);
+
+int as10x_cmd_get_demod_stats(struct as10x_bus_adapter_t *adap,
+ struct as10x_demod_stats *pdemod_stats);
+
+int as10x_cmd_get_impulse_resp(struct as10x_bus_adapter_t *adap,
+ uint8_t *is_ready);
+
+/* as10x cmd stream */
+int as10x_cmd_add_PID_filter(struct as10x_bus_adapter_t *adap,
+ struct as10x_ts_filter *filter);
+int as10x_cmd_del_PID_filter(struct as10x_bus_adapter_t *adap,
+ uint16_t pid_value);
+
+int as10x_cmd_start_streaming(struct as10x_bus_adapter_t *adap);
+int as10x_cmd_stop_streaming(struct as10x_bus_adapter_t *adap);
+
+/* as10x cmd cfg */
+int as10x_cmd_set_context(struct as10x_bus_adapter_t *adap,
+ uint16_t tag,
+ uint32_t value);
+int as10x_cmd_get_context(struct as10x_bus_adapter_t *adap,
+ uint16_t tag,
+ uint32_t *pvalue);
+
+int as10x_cmd_eLNA_change_mode(struct as10x_bus_adapter_t *adap, uint8_t mode);
+int as10x_context_rsp_parse(struct as10x_cmd_t *prsp, uint16_t proc_id);
+#endif
diff --git a/drivers/staging/media/as102/as10x_cmd_cfg.c b/drivers/staging/media/as102/as10x_cmd_cfg.c
new file mode 100644
index 00000000000..b1e300d8875
--- /dev/null
+++ b/drivers/staging/media/as102/as10x_cmd_cfg.c
@@ -0,0 +1,206 @@
+/*
+ * Abilis Systems Single DVB-T Receiver
+ * Copyright (C) 2008 Pierrick Hascoet <pierrick.hascoet@abilis.com>
+ *
+ * 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, 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/kernel.h>
+#include "as102_drv.h"
+#include "as10x_types.h"
+#include "as10x_cmd.h"
+
+/***************************/
+/* FUNCTION DEFINITION */
+/***************************/
+
+/**
+ * as10x_cmd_get_context - Send get context command to AS10x
+ * @adap: pointer to AS10x bus adapter
+ * @tag: context tag
+ * @pvalue: pointer where to store context value read
+ *
+ * Return 0 on success or negative value in case of error.
+ */
+int as10x_cmd_get_context(struct as10x_bus_adapter_t *adap, uint16_t tag,
+ uint32_t *pvalue)
+{
+ int error;
+ struct as10x_cmd_t *pcmd, *prsp;
+
+ pcmd = adap->cmd;
+ prsp = adap->rsp;
+
+ /* prepare command */
+ as10x_cmd_build(pcmd, (++adap->cmd_xid),
+ sizeof(pcmd->body.context.req));
+
+ /* fill command */
+ pcmd->body.context.req.proc_id = cpu_to_le16(CONTROL_PROC_CONTEXT);
+ pcmd->body.context.req.tag = cpu_to_le16(tag);
+ pcmd->body.context.req.type = cpu_to_le16(GET_CONTEXT_DATA);
+
+ /* send command */
+ if (adap->ops->xfer_cmd) {
+ error = adap->ops->xfer_cmd(adap,
+ (uint8_t *) pcmd,
+ sizeof(pcmd->body.context.req)
+ + HEADER_SIZE,
+ (uint8_t *) prsp,
+ sizeof(prsp->body.context.rsp)
+ + HEADER_SIZE);
+ } else {
+ error = AS10X_CMD_ERROR;
+ }
+
+ if (error < 0)
+ goto out;
+
+ /* parse response: context command do not follow the common response */
+ /* structure -> specific handling response parse required */
+ error = as10x_context_rsp_parse(prsp, CONTROL_PROC_CONTEXT_RSP);
+
+ if (error == 0) {
+ /* Response OK -> get response data */
+ *pvalue = le32_to_cpu(prsp->body.context.rsp.reg_val.u.value32);
+ /* value returned is always a 32-bit value */
+ }
+
+out:
+ return error;
+}
+
+/**
+ * as10x_cmd_set_context - send set context command to AS10x
+ * @adap: pointer to AS10x bus adapter
+ * @tag: context tag
+ * @value: value to set in context
+ *
+ * Return 0 on success or negative value in case of error.
+ */
+int as10x_cmd_set_context(struct as10x_bus_adapter_t *adap, uint16_t tag,
+ uint32_t value)
+{
+ int error;
+ struct as10x_cmd_t *pcmd, *prsp;
+
+ pcmd = adap->cmd;
+ prsp = adap->rsp;
+
+ /* prepare command */
+ as10x_cmd_build(pcmd, (++adap->cmd_xid),
+ sizeof(pcmd->body.context.req));
+
+ /* fill command */
+ pcmd->body.context.req.proc_id = cpu_to_le16(CONTROL_PROC_CONTEXT);
+ /* pcmd->body.context.req.reg_val.mode initialization is not required */
+ pcmd->body.context.req.reg_val.u.value32 = cpu_to_le32(value);
+ pcmd->body.context.req.tag = cpu_to_le16(tag);
+ pcmd->body.context.req.type = cpu_to_le16(SET_CONTEXT_DATA);
+
+ /* send command */
+ if (adap->ops->xfer_cmd) {
+ error = adap->ops->xfer_cmd(adap,
+ (uint8_t *) pcmd,
+ sizeof(pcmd->body.context.req)
+ + HEADER_SIZE,
+ (uint8_t *) prsp,
+ sizeof(prsp->body.context.rsp)
+ + HEADER_SIZE);
+ } else {
+ error = AS10X_CMD_ERROR;
+ }
+
+ if (error < 0)
+ goto out;
+
+ /* parse response: context command do not follow the common response */
+ /* structure -> specific handling response parse required */
+ error = as10x_context_rsp_parse(prsp, CONTROL_PROC_CONTEXT_RSP);
+
+out:
+ return error;
+}
+
+/**
+ * as10x_cmd_eLNA_change_mode - send eLNA change mode command to AS10x
+ * @adap: pointer to AS10x bus adapter
+ * @mode: mode selected:
+ * - ON : 0x0 => eLNA always ON
+ * - OFF : 0x1 => eLNA always OFF
+ * - AUTO : 0x2 => eLNA follow hysteresis parameters
+ * to be ON or OFF
+ *
+ * Return 0 on success or negative value in case of error.
+ */
+int as10x_cmd_eLNA_change_mode(struct as10x_bus_adapter_t *adap, uint8_t mode)
+{
+ int error;
+ struct as10x_cmd_t *pcmd, *prsp;
+
+ pcmd = adap->cmd;
+ prsp = adap->rsp;
+
+ /* prepare command */
+ as10x_cmd_build(pcmd, (++adap->cmd_xid),
+ sizeof(pcmd->body.cfg_change_mode.req));
+
+ /* fill command */
+ pcmd->body.cfg_change_mode.req.proc_id =
+ cpu_to_le16(CONTROL_PROC_ELNA_CHANGE_MODE);
+ pcmd->body.cfg_change_mode.req.mode = mode;
+
+ /* send command */
+ if (adap->ops->xfer_cmd) {
+ error = adap->ops->xfer_cmd(adap, (uint8_t *) pcmd,
+ sizeof(pcmd->body.cfg_change_mode.req)
+ + HEADER_SIZE, (uint8_t *) prsp,
+ sizeof(prsp->body.cfg_change_mode.rsp)
+ + HEADER_SIZE);
+ } else {
+ error = AS10X_CMD_ERROR;
+ }
+
+ if (error < 0)
+ goto out;
+
+ /* parse response */
+ error = as10x_rsp_parse(prsp, CONTROL_PROC_ELNA_CHANGE_MODE_RSP);
+
+out:
+ return error;
+}
+
+/**
+ * as10x_context_rsp_parse - Parse context command response
+ * @prsp: pointer to AS10x command response buffer
+ * @proc_id: id of the command
+ *
+ * Since the contex command response does not follow the common
+ * response, a specific parse function is required.
+ * Return 0 on success or negative value in case of error.
+ */
+int as10x_context_rsp_parse(struct as10x_cmd_t *prsp, uint16_t proc_id)
+{
+ int err;
+
+ err = prsp->body.context.rsp.error;
+
+ if ((err == 0) &&
+ (le16_to_cpu(prsp->body.context.rsp.proc_id) == proc_id)) {
+ return 0;
+ }
+ return AS10X_CMD_ERROR;
+}
diff --git a/drivers/staging/media/as102/as10x_cmd_stream.c b/drivers/staging/media/as102/as10x_cmd_stream.c
new file mode 100644
index 00000000000..1088ca1fe92
--- /dev/null
+++ b/drivers/staging/media/as102/as10x_cmd_stream.c
@@ -0,0 +1,211 @@
+/*
+ * Abilis Systems Single DVB-T Receiver
+ * Copyright (C) 2008 Pierrick Hascoet <pierrick.hascoet@abilis.com>
+ *
+ * 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, 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/kernel.h>
+#include "as102_drv.h"
+#include "as10x_cmd.h"
+
+/**
+ * as10x_cmd_add_PID_filter - send add filter command to AS10x
+ * @adap: pointer to AS10x bus adapter
+ * @filter: TSFilter filter for DVB-T
+ *
+ * Return 0 on success or negative value in case of error.
+ */
+int as10x_cmd_add_PID_filter(struct as10x_bus_adapter_t *adap,
+ struct as10x_ts_filter *filter)
+{
+ int error;
+ struct as10x_cmd_t *pcmd, *prsp;
+
+ pcmd = adap->cmd;
+ prsp = adap->rsp;
+
+ /* prepare command */
+ as10x_cmd_build(pcmd, (++adap->cmd_xid),
+ sizeof(pcmd->body.add_pid_filter.req));
+
+ /* fill command */
+ pcmd->body.add_pid_filter.req.proc_id =
+ cpu_to_le16(CONTROL_PROC_SETFILTER);
+ pcmd->body.add_pid_filter.req.pid = cpu_to_le16(filter->pid);
+ pcmd->body.add_pid_filter.req.stream_type = filter->type;
+
+ if (filter->idx < 16)
+ pcmd->body.add_pid_filter.req.idx = filter->idx;
+ else
+ pcmd->body.add_pid_filter.req.idx = 0xFF;
+
+ /* send command */
+ if (adap->ops->xfer_cmd) {
+ error = adap->ops->xfer_cmd(adap, (uint8_t *) pcmd,
+ sizeof(pcmd->body.add_pid_filter.req)
+ + HEADER_SIZE, (uint8_t *) prsp,
+ sizeof(prsp->body.add_pid_filter.rsp)
+ + HEADER_SIZE);
+ } else {
+ error = AS10X_CMD_ERROR;
+ }
+
+ if (error < 0)
+ goto out;
+
+ /* parse response */
+ error = as10x_rsp_parse(prsp, CONTROL_PROC_SETFILTER_RSP);
+
+ if (error == 0) {
+ /* Response OK -> get response data */
+ filter->idx = prsp->body.add_pid_filter.rsp.filter_id;
+ }
+
+out:
+ return error;
+}
+
+/**
+ * as10x_cmd_del_PID_filter - Send delete filter command to AS10x
+ * @adap: pointer to AS10x bus adapte
+ * @pid_value: PID to delete
+ *
+ * Return 0 on success or negative value in case of error.
+ */
+int as10x_cmd_del_PID_filter(struct as10x_bus_adapter_t *adap,
+ uint16_t pid_value)
+{
+ int error;
+ struct as10x_cmd_t *pcmd, *prsp;
+
+ pcmd = adap->cmd;
+ prsp = adap->rsp;
+
+ /* prepare command */
+ as10x_cmd_build(pcmd, (++adap->cmd_xid),
+ sizeof(pcmd->body.del_pid_filter.req));
+
+ /* fill command */
+ pcmd->body.del_pid_filter.req.proc_id =
+ cpu_to_le16(CONTROL_PROC_REMOVEFILTER);
+ pcmd->body.del_pid_filter.req.pid = cpu_to_le16(pid_value);
+
+ /* send command */
+ if (adap->ops->xfer_cmd) {
+ error = adap->ops->xfer_cmd(adap, (uint8_t *) pcmd,
+ sizeof(pcmd->body.del_pid_filter.req)
+ + HEADER_SIZE, (uint8_t *) prsp,
+ sizeof(prsp->body.del_pid_filter.rsp)
+ + HEADER_SIZE);
+ } else {
+ error = AS10X_CMD_ERROR;
+ }
+
+ if (error < 0)
+ goto out;
+
+ /* parse response */
+ error = as10x_rsp_parse(prsp, CONTROL_PROC_REMOVEFILTER_RSP);
+
+out:
+ return error;
+}
+
+/**
+ * as10x_cmd_start_streaming - Send start streaming command to AS10x
+ * @adap: pointer to AS10x bus adapter
+ *
+ * Return 0 on success or negative value in case of error.
+ */
+int as10x_cmd_start_streaming(struct as10x_bus_adapter_t *adap)
+{
+ int error;
+ struct as10x_cmd_t *pcmd, *prsp;
+
+ pcmd = adap->cmd;
+ prsp = adap->rsp;
+
+ /* prepare command */
+ as10x_cmd_build(pcmd, (++adap->cmd_xid),
+ sizeof(pcmd->body.start_streaming.req));
+
+ /* fill command */
+ pcmd->body.start_streaming.req.proc_id =
+ cpu_to_le16(CONTROL_PROC_START_STREAMING);
+
+ /* send command */
+ if (adap->ops->xfer_cmd) {
+ error = adap->ops->xfer_cmd(adap, (uint8_t *) pcmd,
+ sizeof(pcmd->body.start_streaming.req)
+ + HEADER_SIZE, (uint8_t *) prsp,
+ sizeof(prsp->body.start_streaming.rsp)
+ + HEADER_SIZE);
+ } else {
+ error = AS10X_CMD_ERROR;
+ }
+
+ if (error < 0)
+ goto out;
+
+ /* parse response */
+ error = as10x_rsp_parse(prsp, CONTROL_PROC_START_STREAMING_RSP);
+
+out:
+ return error;
+}
+
+/**
+ * as10x_cmd_stop_streaming - Send stop streaming command to AS10x
+ * @adap: pointer to AS10x bus adapter
+ *
+ * Return 0 on success or negative value in case of error.
+ */
+int as10x_cmd_stop_streaming(struct as10x_bus_adapter_t *adap)
+{
+ int8_t error;
+ struct as10x_cmd_t *pcmd, *prsp;
+
+ pcmd = adap->cmd;
+ prsp = adap->rsp;
+
+ /* prepare command */
+ as10x_cmd_build(pcmd, (++adap->cmd_xid),
+ sizeof(pcmd->body.stop_streaming.req));
+
+ /* fill command */
+ pcmd->body.stop_streaming.req.proc_id =
+ cpu_to_le16(CONTROL_PROC_STOP_STREAMING);
+
+ /* send command */
+ if (adap->ops->xfer_cmd) {
+ error = adap->ops->xfer_cmd(adap, (uint8_t *) pcmd,
+ sizeof(pcmd->body.stop_streaming.req)
+ + HEADER_SIZE, (uint8_t *) prsp,
+ sizeof(prsp->body.stop_streaming.rsp)
+ + HEADER_SIZE);
+ } else {
+ error = AS10X_CMD_ERROR;
+ }
+
+ if (error < 0)
+ goto out;
+
+ /* parse response */
+ error = as10x_rsp_parse(prsp, CONTROL_PROC_STOP_STREAMING_RSP);
+
+out:
+ return error;
+}
diff --git a/drivers/staging/media/as102/as10x_handle.h b/drivers/staging/media/as102/as10x_handle.h
new file mode 100644
index 00000000000..5638b191b78
--- /dev/null
+++ b/drivers/staging/media/as102/as10x_handle.h
@@ -0,0 +1,54 @@
+/*
+ * Abilis Systems Single DVB-T Receiver
+ * Copyright (C) 2008 Pierrick Hascoet <pierrick.hascoet@abilis.com>
+ *
+ * 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, 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+#ifdef __KERNEL__
+struct as10x_bus_adapter_t;
+struct as102_dev_t;
+
+#include "as10x_cmd.h"
+
+/* values for "mode" field */
+#define REGMODE8 8
+#define REGMODE16 16
+#define REGMODE32 32
+
+struct as102_priv_ops_t {
+ int (*upload_fw_pkt)(struct as10x_bus_adapter_t *bus_adap,
+ unsigned char *buf, int buflen, int swap32);
+
+ int (*send_cmd)(struct as10x_bus_adapter_t *bus_adap,
+ unsigned char *buf, int buflen);
+
+ int (*xfer_cmd)(struct as10x_bus_adapter_t *bus_adap,
+ unsigned char *send_buf, int send_buf_len,
+ unsigned char *recv_buf, int recv_buf_len);
+
+ int (*start_stream)(struct as102_dev_t *dev);
+ void (*stop_stream)(struct as102_dev_t *dev);
+
+ int (*reset_target)(struct as10x_bus_adapter_t *bus_adap);
+
+ int (*read_write)(struct as10x_bus_adapter_t *bus_adap, uint8_t mode,
+ uint32_t rd_addr, uint16_t rd_len,
+ uint32_t wr_addr, uint16_t wr_len);
+
+ int (*as102_read_ep2)(struct as10x_bus_adapter_t *bus_adap,
+ unsigned char *recv_buf,
+ int recv_buf_len);
+};
+#endif
diff --git a/drivers/staging/media/as102/as10x_types.h b/drivers/staging/media/as102/as10x_types.h
new file mode 100644
index 00000000000..af26e057d9a
--- /dev/null
+++ b/drivers/staging/media/as102/as10x_types.h
@@ -0,0 +1,194 @@
+/*
+ * Abilis Systems Single DVB-T Receiver
+ * Copyright (C) 2008 Pierrick Hascoet <pierrick.hascoet@abilis.com>
+ *
+ * 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, 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+#ifndef _AS10X_TYPES_H_
+#define _AS10X_TYPES_H_
+
+#include "as10x_handle.h"
+
+/*********************************/
+/* MACRO DEFINITIONS */
+/*********************************/
+
+/* bandwidth constant values */
+#define BW_5_MHZ 0x00
+#define BW_6_MHZ 0x01
+#define BW_7_MHZ 0x02
+#define BW_8_MHZ 0x03
+
+/* hierarchy priority selection values */
+#define HIER_NO_PRIORITY 0x00
+#define HIER_LOW_PRIORITY 0x01
+#define HIER_HIGH_PRIORITY 0x02
+
+/* constellation available values */
+#define CONST_QPSK 0x00
+#define CONST_QAM16 0x01
+#define CONST_QAM64 0x02
+#define CONST_UNKNOWN 0xFF
+
+/* hierarchy available values */
+#define HIER_NONE 0x00
+#define HIER_ALPHA_1 0x01
+#define HIER_ALPHA_2 0x02
+#define HIER_ALPHA_4 0x03
+#define HIER_UNKNOWN 0xFF
+
+/* interleaving available values */
+#define INTLV_NATIVE 0x00
+#define INTLV_IN_DEPTH 0x01
+#define INTLV_UNKNOWN 0xFF
+
+/* code rate available values */
+#define CODE_RATE_1_2 0x00
+#define CODE_RATE_2_3 0x01
+#define CODE_RATE_3_4 0x02
+#define CODE_RATE_5_6 0x03
+#define CODE_RATE_7_8 0x04
+#define CODE_RATE_UNKNOWN 0xFF
+
+/* guard interval available values */
+#define GUARD_INT_1_32 0x00
+#define GUARD_INT_1_16 0x01
+#define GUARD_INT_1_8 0x02
+#define GUARD_INT_1_4 0x03
+#define GUARD_UNKNOWN 0xFF
+
+/* transmission mode available values */
+#define TRANS_MODE_2K 0x00
+#define TRANS_MODE_8K 0x01
+#define TRANS_MODE_4K 0x02
+#define TRANS_MODE_UNKNOWN 0xFF
+
+/* DVBH signalling available values */
+#define TIMESLICING_PRESENT 0x01
+#define MPE_FEC_PRESENT 0x02
+
+/* tune state available */
+#define TUNE_STATUS_NOT_TUNED 0x00
+#define TUNE_STATUS_IDLE 0x01
+#define TUNE_STATUS_LOCKING 0x02
+#define TUNE_STATUS_SIGNAL_DVB_OK 0x03
+#define TUNE_STATUS_STREAM_DETECTED 0x04
+#define TUNE_STATUS_STREAM_TUNED 0x05
+#define TUNE_STATUS_ERROR 0xFF
+
+/* available TS FID filter types */
+#define TS_PID_TYPE_TS 0
+#define TS_PID_TYPE_PSI_SI 1
+#define TS_PID_TYPE_MPE 2
+
+/* number of echos available */
+#define MAX_ECHOS 15
+
+/* Context types */
+#define CONTEXT_LNA 1010
+#define CONTEXT_ELNA_HYSTERESIS 4003
+#define CONTEXT_ELNA_GAIN 4004
+#define CONTEXT_MER_THRESHOLD 5005
+#define CONTEXT_MER_OFFSET 5006
+#define CONTEXT_IR_STATE 7000
+#define CONTEXT_TSOUT_MSB_FIRST 7004
+#define CONTEXT_TSOUT_FALLING_EDGE 7005
+
+/* Configuration modes */
+#define CFG_MODE_ON 0
+#define CFG_MODE_OFF 1
+#define CFG_MODE_AUTO 2
+
+struct as10x_tps {
+ uint8_t modulation;
+ uint8_t hierarchy;
+ uint8_t interleaving_mode;
+ uint8_t code_rate_HP;
+ uint8_t code_rate_LP;
+ uint8_t guard_interval;
+ uint8_t transmission_mode;
+ uint8_t DVBH_mask_HP;
+ uint8_t DVBH_mask_LP;
+ uint16_t cell_ID;
+} __packed;
+
+struct as10x_tune_args {
+ /* frequency */
+ uint32_t freq;
+ /* bandwidth */
+ uint8_t bandwidth;
+ /* hierarchy selection */
+ uint8_t hier_select;
+ /* constellation */
+ uint8_t modulation;
+ /* hierarchy */
+ uint8_t hierarchy;
+ /* interleaving mode */
+ uint8_t interleaving_mode;
+ /* code rate */
+ uint8_t code_rate;
+ /* guard interval */
+ uint8_t guard_interval;
+ /* transmission mode */
+ uint8_t transmission_mode;
+} __packed;
+
+struct as10x_tune_status {
+ /* tune status */
+ uint8_t tune_state;
+ /* signal strength */
+ int16_t signal_strength;
+ /* packet error rate 10^-4 */
+ uint16_t PER;
+ /* bit error rate 10^-4 */
+ uint16_t BER;
+} __packed;
+
+struct as10x_demod_stats {
+ /* frame counter */
+ uint32_t frame_count;
+ /* Bad frame counter */
+ uint32_t bad_frame_count;
+ /* Number of wrong bytes fixed by Reed-Solomon */
+ uint32_t bytes_fixed_by_rs;
+ /* Averaged MER */
+ uint16_t mer;
+ /* statistics calculation state indicator (started or not) */
+ uint8_t has_started;
+} __packed;
+
+struct as10x_ts_filter {
+ uint16_t pid; /* valid PID value 0x00 : 0x2000 */
+ uint8_t type; /* Red TS_PID_TYPE_<N> values */
+ uint8_t idx; /* index in filtering table */
+} __packed;
+
+struct as10x_register_value {
+ uint8_t mode;
+ union {
+ uint8_t value8; /* 8 bit value */
+ uint16_t value16; /* 16 bit value */
+ uint32_t value32; /* 32 bit value */
+ } __packed u;
+} __packed;
+
+struct as10x_register_addr {
+ /* register addr */
+ uint32_t addr;
+ /* register mode access */
+ uint8_t mode;
+};
+
+#endif
diff --git a/drivers/staging/media/bcm2048/Kconfig b/drivers/staging/media/bcm2048/Kconfig
new file mode 100644
index 00000000000..a9fc6e18649
--- /dev/null
+++ b/drivers/staging/media/bcm2048/Kconfig
@@ -0,0 +1,13 @@
+#
+# Multimedia Video device configuration
+#
+
+config I2C_BCM2048
+ tristate "Broadcom BCM2048 FM Radio Receiver support"
+ depends on I2C && VIDEO_V4L2 && RADIO_ADAPTERS
+ ---help---
+ Say Y here if you want support to BCM2048 FM Radio Receiver.
+ This device driver supports only i2c bus.
+
+ To compile this driver as a module, choose M here: the
+ module will be called radio-bcm2048.
diff --git a/drivers/staging/media/bcm2048/Makefile b/drivers/staging/media/bcm2048/Makefile
new file mode 100644
index 00000000000..b4f5663d140
--- /dev/null
+++ b/drivers/staging/media/bcm2048/Makefile
@@ -0,0 +1 @@
+obj-$(CONFIG_I2C_BCM2048) += radio-bcm2048.o
diff --git a/drivers/staging/media/bcm2048/TODO b/drivers/staging/media/bcm2048/TODO
new file mode 100644
index 00000000000..051f85dbe89
--- /dev/null
+++ b/drivers/staging/media/bcm2048/TODO
@@ -0,0 +1,24 @@
+TODO:
+
+From the initial code review:
+
+The main thing you need to do is to implement all the controls using the
+control framework (see Documentation/video4linux/v4l2-controls.txt).
+Most drivers are by now converted to the control framework, so you will
+find many examples of how to do this in drivers/media/radio.
+
+The sysfs stuff should be replaced by controls as well. A lot of the RDS
+support is now available as controls (although there may well be some
+missing features, but that is easy enough to add). Since the RDS data is
+actually read() from the device I am not sure whether the RDS
+properties/controls should be there at all.
+
+Correct Coding Style, as this driver also violates several Style
+rules, and do evil tricks, like returning from a function inside a
+macro.
+
+Finally this driver should probably be split up into two parts: one
+v4l2_subdev-based core driver and one platform driver. See e.g.
+radio-si4713/si4713-i2c.c as a good example. But I would wait with that
+until the rest of the driver is cleaned up. Then I have a better idea of
+whether this is necessary or not.
diff --git a/drivers/staging/media/bcm2048/radio-bcm2048.c b/drivers/staging/media/bcm2048/radio-bcm2048.c
new file mode 100644
index 00000000000..bbf236e842a
--- /dev/null
+++ b/drivers/staging/media/bcm2048/radio-bcm2048.c
@@ -0,0 +1,2744 @@
+/*
+ * drivers/staging/media/radio-bcm2048.c
+ *
+ * Driver for I2C Broadcom BCM2048 FM Radio Receiver:
+ *
+ * Copyright (C) Nokia Corporation
+ * Contact: Eero Nurkkala <ext-eero.nurkkala@nokia.com>
+ *
+ * Copyright (C) Nils Faerber <nils.faerber@kernelconcepts.de>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ */
+
+/*
+ * History:
+ * Eero Nurkkala <ext-eero.nurkkala@nokia.com>
+ * Version 0.0.1
+ * - Initial implementation
+ * 2010-02-21 Nils Faerber <nils.faerber@kernelconcepts.de>
+ * Version 0.0.2
+ * - Add support for interrupt driven rds data reading
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/version.h>
+#include <linux/interrupt.h>
+#include <linux/sysfs.h>
+#include <linux/completion.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/videodev2.h>
+#include <linux/mutex.h>
+#include <linux/slab.h>
+#include <media/v4l2-common.h>
+#include <media/v4l2-ioctl.h>
+#include "radio-bcm2048.h"
+
+/* driver definitions */
+#define BCM2048_DRIVER_AUTHOR "Eero Nurkkala <ext-eero.nurkkala@nokia.com>"
+#define BCM2048_DRIVER_NAME BCM2048_NAME
+#define BCM2048_DRIVER_VERSION KERNEL_VERSION(0, 0, 1)
+#define BCM2048_DRIVER_CARD "Broadcom bcm2048 FM Radio Receiver"
+#define BCM2048_DRIVER_DESC "I2C driver for BCM2048 FM Radio Receiver"
+
+/* I2C Control Registers */
+#define BCM2048_I2C_FM_RDS_SYSTEM 0x00
+#define BCM2048_I2C_FM_CTRL 0x01
+#define BCM2048_I2C_RDS_CTRL0 0x02
+#define BCM2048_I2C_RDS_CTRL1 0x03
+#define BCM2048_I2C_FM_AUDIO_PAUSE 0x04
+#define BCM2048_I2C_FM_AUDIO_CTRL0 0x05
+#define BCM2048_I2C_FM_AUDIO_CTRL1 0x06
+#define BCM2048_I2C_FM_SEARCH_CTRL0 0x07
+#define BCM2048_I2C_FM_SEARCH_CTRL1 0x08
+#define BCM2048_I2C_FM_SEARCH_TUNE_MODE 0x09
+#define BCM2048_I2C_FM_FREQ0 0x0a
+#define BCM2048_I2C_FM_FREQ1 0x0b
+#define BCM2048_I2C_FM_AF_FREQ0 0x0c
+#define BCM2048_I2C_FM_AF_FREQ1 0x0d
+#define BCM2048_I2C_FM_CARRIER 0x0e
+#define BCM2048_I2C_FM_RSSI 0x0f
+#define BCM2048_I2C_FM_RDS_MASK0 0x10
+#define BCM2048_I2C_FM_RDS_MASK1 0x11
+#define BCM2048_I2C_FM_RDS_FLAG0 0x12
+#define BCM2048_I2C_FM_RDS_FLAG1 0x13
+#define BCM2048_I2C_RDS_WLINE 0x14
+#define BCM2048_I2C_RDS_BLKB_MATCH0 0x16
+#define BCM2048_I2C_RDS_BLKB_MATCH1 0x17
+#define BCM2048_I2C_RDS_BLKB_MASK0 0x18
+#define BCM2048_I2C_RDS_BLKB_MASK1 0x19
+#define BCM2048_I2C_RDS_PI_MATCH0 0x1a
+#define BCM2048_I2C_RDS_PI_MATCH1 0x1b
+#define BCM2048_I2C_RDS_PI_MASK0 0x1c
+#define BCM2048_I2C_RDS_PI_MASK1 0x1d
+#define BCM2048_I2C_SPARE1 0x20
+#define BCM2048_I2C_SPARE2 0x21
+#define BCM2048_I2C_FM_RDS_REV 0x28
+#define BCM2048_I2C_SLAVE_CONFIGURATION 0x29
+#define BCM2048_I2C_RDS_DATA 0x80
+#define BCM2048_I2C_FM_BEST_TUNE_MODE 0x90
+
+/* BCM2048_I2C_FM_RDS_SYSTEM */
+#define BCM2048_FM_ON 0x01
+#define BCM2048_RDS_ON 0x02
+
+/* BCM2048_I2C_FM_CTRL */
+#define BCM2048_BAND_SELECT 0x01
+#define BCM2048_STEREO_MONO_AUTO_SELECT 0x02
+#define BCM2048_STEREO_MONO_MANUAL_SELECT 0x04
+#define BCM2048_STEREO_MONO_BLEND_SWITCH 0x08
+#define BCM2048_HI_LO_INJECTION 0x10
+
+/* BCM2048_I2C_RDS_CTRL0 */
+#define BCM2048_RBDS_RDS_SELECT 0x01
+#define BCM2048_FLUSH_FIFO 0x02
+
+/* BCM2048_I2C_FM_AUDIO_PAUSE */
+#define BCM2048_AUDIO_PAUSE_RSSI_TRESH 0x0f
+#define BCM2048_AUDIO_PAUSE_DURATION 0xf0
+
+/* BCM2048_I2C_FM_AUDIO_CTRL0 */
+#define BCM2048_RF_MUTE 0x01
+#define BCM2048_MANUAL_MUTE 0x02
+#define BCM2048_DAC_OUTPUT_LEFT 0x04
+#define BCM2048_DAC_OUTPUT_RIGHT 0x08
+#define BCM2048_AUDIO_ROUTE_DAC 0x10
+#define BCM2048_AUDIO_ROUTE_I2S 0x20
+#define BCM2048_DE_EMPHASIS_SELECT 0x40
+#define BCM2048_AUDIO_BANDWIDTH_SELECT 0x80
+
+/* BCM2048_I2C_FM_SEARCH_CTRL0 */
+#define BCM2048_SEARCH_RSSI_THRESHOLD 0x7f
+#define BCM2048_SEARCH_DIRECTION 0x80
+
+/* BCM2048_I2C_FM_SEARCH_TUNE_MODE */
+#define BCM2048_FM_AUTO_SEARCH 0x03
+
+/* BCM2048_I2C_FM_RSSI */
+#define BCM2048_RSSI_VALUE 0xff
+
+/* BCM2048_I2C_FM_RDS_MASK0 */
+/* BCM2048_I2C_FM_RDS_MASK1 */
+#define BCM2048_FM_FLAG_SEARCH_TUNE_FINISHED 0x01
+#define BCM2048_FM_FLAG_SEARCH_TUNE_FAIL 0x02
+#define BCM2048_FM_FLAG_RSSI_LOW 0x04
+#define BCM2048_FM_FLAG_CARRIER_ERROR_HIGH 0x08
+#define BCM2048_FM_FLAG_AUDIO_PAUSE_INDICATION 0x10
+#define BCM2048_FLAG_STEREO_DETECTED 0x20
+#define BCM2048_FLAG_STEREO_ACTIVE 0x40
+
+/* BCM2048_I2C_RDS_DATA */
+#define BCM2048_SLAVE_ADDRESS 0x3f
+#define BCM2048_SLAVE_ENABLE 0x80
+
+/* BCM2048_I2C_FM_BEST_TUNE_MODE */
+#define BCM2048_BEST_TUNE_MODE 0x80
+
+#define BCM2048_FM_FLAG_SEARCH_TUNE_FINISHED 0x01
+#define BCM2048_FM_FLAG_SEARCH_TUNE_FAIL 0x02
+#define BCM2048_FM_FLAG_RSSI_LOW 0x04
+#define BCM2048_FM_FLAG_CARRIER_ERROR_HIGH 0x08
+#define BCM2048_FM_FLAG_AUDIO_PAUSE_INDICATION 0x10
+#define BCM2048_FLAG_STEREO_DETECTED 0x20
+#define BCM2048_FLAG_STEREO_ACTIVE 0x40
+
+#define BCM2048_RDS_FLAG_FIFO_WLINE 0x02
+#define BCM2048_RDS_FLAG_B_BLOCK_MATCH 0x08
+#define BCM2048_RDS_FLAG_SYNC_LOST 0x10
+#define BCM2048_RDS_FLAG_PI_MATCH 0x20
+
+#define BCM2048_RDS_MARK_END_BYTE0 0x7C
+#define BCM2048_RDS_MARK_END_BYTEN 0xFF
+
+#define BCM2048_FM_FLAGS_ALL (FM_FLAG_SEARCH_TUNE_FINISHED | \
+ FM_FLAG_SEARCH_TUNE_FAIL | \
+ FM_FLAG_RSSI_LOW | \
+ FM_FLAG_CARRIER_ERROR_HIGH | \
+ FM_FLAG_AUDIO_PAUSE_INDICATION | \
+ FLAG_STEREO_DETECTED | FLAG_STEREO_ACTIVE)
+
+#define BCM2048_RDS_FLAGS_ALL (RDS_FLAG_FIFO_WLINE | \
+ RDS_FLAG_B_BLOCK_MATCH | \
+ RDS_FLAG_SYNC_LOST | RDS_FLAG_PI_MATCH)
+
+#define BCM2048_DEFAULT_TIMEOUT 1500
+#define BCM2048_AUTO_SEARCH_TIMEOUT 3000
+
+
+#define BCM2048_FREQDEV_UNIT 10000
+#define BCM2048_FREQV4L2_MULTI 625
+#define dev_to_v4l2(f) ((f * BCM2048_FREQDEV_UNIT) / BCM2048_FREQV4L2_MULTI)
+#define v4l2_to_dev(f) ((f * BCM2048_FREQV4L2_MULTI) / BCM2048_FREQDEV_UNIT)
+
+#define msb(x) ((u8)((u16) x >> 8))
+#define lsb(x) ((u8)((u16) x & 0x00FF))
+#define compose_u16(msb, lsb) (((u16)msb << 8) | lsb)
+
+#define BCM2048_DEFAULT_POWERING_DELAY 20
+#define BCM2048_DEFAULT_REGION 0x02
+#define BCM2048_DEFAULT_MUTE 0x01
+#define BCM2048_DEFAULT_RSSI_THRESHOLD 0x64
+#define BCM2048_DEFAULT_RDS_WLINE 0x7E
+
+#define BCM2048_FM_SEARCH_INACTIVE 0x00
+#define BCM2048_FM_PRE_SET_MODE 0x01
+#define BCM2048_FM_AUTO_SEARCH_MODE 0x02
+#define BCM2048_FM_AF_JUMP_MODE 0x03
+
+#define BCM2048_FREQUENCY_BASE 64000
+
+#define BCM2048_POWER_ON 0x01
+#define BCM2048_POWER_OFF 0x00
+
+#define BCM2048_ITEM_ENABLED 0x01
+#define BCM2048_SEARCH_DIRECTION_UP 0x01
+
+#define BCM2048_DE_EMPHASIS_75us 75
+#define BCM2048_DE_EMPHASIS_50us 50
+
+#define BCM2048_SCAN_FAIL 0x00
+#define BCM2048_SCAN_OK 0x01
+
+#define BCM2048_FREQ_ERROR_FLOOR -20
+#define BCM2048_FREQ_ERROR_ROOF 20
+
+/* -60 dB is reported as full signal strenght */
+#define BCM2048_RSSI_LEVEL_BASE -60
+#define BCM2048_RSSI_LEVEL_ROOF -100
+#define BCM2048_RSSI_LEVEL_ROOF_NEG 100
+#define BCM2048_SIGNAL_MULTIPLIER (0xFFFF / \
+ (BCM2048_RSSI_LEVEL_ROOF_NEG + \
+ BCM2048_RSSI_LEVEL_BASE))
+
+#define BCM2048_RDS_FIFO_DUPLE_SIZE 0x03
+#define BCM2048_RDS_CRC_MASK 0x0F
+#define BCM2048_RDS_CRC_NONE 0x00
+#define BCM2048_RDS_CRC_MAX_2BITS 0x04
+#define BCM2048_RDS_CRC_LEAST_2BITS 0x08
+#define BCM2048_RDS_CRC_UNRECOVARABLE 0x0C
+
+#define BCM2048_RDS_BLOCK_MASK 0xF0
+#define BCM2048_RDS_BLOCK_A 0x00
+#define BCM2048_RDS_BLOCK_B 0x10
+#define BCM2048_RDS_BLOCK_C 0x20
+#define BCM2048_RDS_BLOCK_D 0x30
+#define BCM2048_RDS_BLOCK_C_SCORED 0x40
+#define BCM2048_RDS_BLOCK_E 0x60
+
+#define BCM2048_RDS_RT 0x20
+#define BCM2048_RDS_PS 0x00
+
+#define BCM2048_RDS_GROUP_AB_MASK 0x08
+#define BCM2048_RDS_GROUP_A 0x00
+#define BCM2048_RDS_GROUP_B 0x08
+
+#define BCM2048_RDS_RT_AB_MASK 0x10
+#define BCM2048_RDS_RT_A 0x00
+#define BCM2048_RDS_RT_B 0x10
+#define BCM2048_RDS_RT_INDEX 0x0F
+
+#define BCM2048_RDS_PS_INDEX 0x03
+
+struct rds_info {
+ u16 rds_pi;
+#define BCM2048_MAX_RDS_RT (64 + 1)
+ u8 rds_rt[BCM2048_MAX_RDS_RT];
+ u8 rds_rt_group_b;
+ u8 rds_rt_ab;
+#define BCM2048_MAX_RDS_PS (8 + 1)
+ u8 rds_ps[BCM2048_MAX_RDS_PS];
+ u8 rds_ps_group;
+ u8 rds_ps_group_cnt;
+#define BCM2048_MAX_RDS_RADIO_TEXT 255
+ u8 radio_text[BCM2048_MAX_RDS_RADIO_TEXT + 3];
+ u8 text_len;
+};
+
+struct region_info {
+ u32 bottom_frequency;
+ u32 top_frequency;
+ u8 deemphasis;
+ u8 channel_spacing;
+ u8 region;
+};
+
+struct bcm2048_device {
+ struct i2c_client *client;
+ struct video_device *videodev;
+ struct work_struct work;
+ struct completion compl;
+ struct mutex mutex;
+ struct bcm2048_platform_data *platform_data;
+ struct rds_info rds_info;
+ struct region_info region_info;
+ u16 frequency;
+ u8 cache_fm_rds_system;
+ u8 cache_fm_ctrl;
+ u8 cache_fm_audio_ctrl0;
+ u8 cache_fm_search_ctrl0;
+ u8 power_state;
+ u8 rds_state;
+ u8 fifo_size;
+ u8 scan_state;
+ u8 mute_state;
+
+ /* for rds data device read */
+ wait_queue_head_t read_queue;
+ unsigned int users;
+ unsigned char rds_data_available;
+ unsigned int rd_index;
+};
+
+static int radio_nr = -1; /* radio device minor (-1 ==> auto assign) */
+module_param(radio_nr, int, 0);
+MODULE_PARM_DESC(radio_nr,
+ "Minor number for radio device (-1 ==> auto assign)");
+
+static struct region_info region_configs[] = {
+ /* USA */
+ {
+ .channel_spacing = 20,
+ .bottom_frequency = 87500,
+ .top_frequency = 108000,
+ .deemphasis = 75,
+ .region = 0,
+ },
+ /* Australia */
+ {
+ .channel_spacing = 20,
+ .bottom_frequency = 87500,
+ .top_frequency = 108000,
+ .deemphasis = 50,
+ .region = 1,
+ },
+ /* Europe */
+ {
+ .channel_spacing = 10,
+ .bottom_frequency = 87500,
+ .top_frequency = 108000,
+ .deemphasis = 50,
+ .region = 2,
+ },
+ /* Japan */
+ {
+ .channel_spacing = 10,
+ .bottom_frequency = 76000,
+ .top_frequency = 90000,
+ .deemphasis = 50,
+ .region = 3,
+ },
+ /* Japan wide band */
+ {
+ .channel_spacing = 10,
+ .bottom_frequency = 76000,
+ .top_frequency = 108000,
+ .deemphasis = 50,
+ .region = 4,
+ },
+};
+
+/*
+ * I2C Interface read / write
+ */
+static int bcm2048_send_command(struct bcm2048_device *bdev, unsigned int reg,
+ unsigned int value)
+{
+ struct i2c_client *client = bdev->client;
+ u8 data[2];
+
+ if (!bdev->power_state) {
+ dev_err(&bdev->client->dev, "bcm2048: chip not powered!\n");
+ return -EIO;
+ }
+
+ data[0] = reg & 0xff;
+ data[1] = value & 0xff;
+
+ if (i2c_master_send(client, data, 2) == 2) {
+ return 0;
+ } else {
+ dev_err(&bdev->client->dev, "BCM I2C error!\n");
+ dev_err(&bdev->client->dev, "Is Bluetooth up and running?\n");
+ return -EIO;
+ }
+}
+
+static int bcm2048_recv_command(struct bcm2048_device *bdev, unsigned int reg,
+ u8 *value)
+{
+ struct i2c_client *client = bdev->client;
+
+ if (!bdev->power_state) {
+ dev_err(&bdev->client->dev, "bcm2048: chip not powered!\n");
+ return -EIO;
+ }
+
+ value[0] = i2c_smbus_read_byte_data(client, reg & 0xff);
+
+ return 0;
+}
+
+static int bcm2048_recv_duples(struct bcm2048_device *bdev, unsigned int reg,
+ u8 *value, u8 duples)
+{
+ struct i2c_client *client = bdev->client;
+ struct i2c_adapter *adap = client->adapter;
+ struct i2c_msg msg[2];
+ u8 buf;
+
+ if (!bdev->power_state) {
+ dev_err(&bdev->client->dev, "bcm2048: chip not powered!\n");
+ return -EIO;
+ }
+
+ buf = reg & 0xff;
+
+ msg[0].addr = client->addr;
+ msg[0].flags = client->flags & I2C_M_TEN;
+ msg[0].len = 1;
+ msg[0].buf = &buf;
+
+ msg[1].addr = client->addr;
+ msg[1].flags = client->flags & I2C_M_TEN;
+ msg[1].flags |= I2C_M_RD;
+ msg[1].len = duples;
+ msg[1].buf = value;
+
+ return i2c_transfer(adap, msg, 2);
+}
+
+/*
+ * BCM2048 - I2C register programming helpers
+ */
+static int bcm2048_set_power_state(struct bcm2048_device *bdev, u8 power)
+{
+ int err = 0;
+
+ mutex_lock(&bdev->mutex);
+
+ if (power) {
+ bdev->power_state = BCM2048_POWER_ON;
+ bdev->cache_fm_rds_system |= BCM2048_FM_ON;
+ } else {
+ bdev->cache_fm_rds_system &= ~BCM2048_FM_ON;
+ }
+
+ /*
+ * Warning! FM cannot be turned off because then
+ * the I2C communications get ruined!
+ * Comment off the "if (power)" when the chip works!
+ */
+ if (power)
+ err = bcm2048_send_command(bdev, BCM2048_I2C_FM_RDS_SYSTEM,
+ bdev->cache_fm_rds_system);
+ msleep(BCM2048_DEFAULT_POWERING_DELAY);
+
+ if (!power)
+ bdev->power_state = BCM2048_POWER_OFF;
+
+ mutex_unlock(&bdev->mutex);
+ return err;
+}
+
+static int bcm2048_get_power_state(struct bcm2048_device *bdev)
+{
+ int err;
+ u8 value;
+
+ mutex_lock(&bdev->mutex);
+
+ err = bcm2048_recv_command(bdev, BCM2048_I2C_FM_RDS_SYSTEM, &value);
+
+ mutex_unlock(&bdev->mutex);
+
+ if (!err && (value & BCM2048_FM_ON))
+ return BCM2048_POWER_ON;
+
+ return err;
+}
+
+static int bcm2048_set_rds_no_lock(struct bcm2048_device *bdev, u8 rds_on)
+{
+ int err;
+ u8 flags;
+
+ bdev->cache_fm_rds_system &= ~BCM2048_RDS_ON;
+
+ if (rds_on) {
+ bdev->cache_fm_rds_system |= BCM2048_RDS_ON;
+ bdev->rds_state = BCM2048_RDS_ON;
+ flags = BCM2048_RDS_FLAG_FIFO_WLINE;
+ err = bcm2048_send_command(bdev, BCM2048_I2C_FM_RDS_MASK1,
+ flags);
+ } else {
+ flags = 0;
+ bdev->rds_state = 0;
+ err = bcm2048_send_command(bdev, BCM2048_I2C_FM_RDS_MASK1,
+ flags);
+ memset(&bdev->rds_info, 0, sizeof(bdev->rds_info));
+ }
+
+ err = bcm2048_send_command(bdev, BCM2048_I2C_FM_RDS_SYSTEM,
+ bdev->cache_fm_rds_system);
+
+ return err;
+}
+
+static int bcm2048_get_rds_no_lock(struct bcm2048_device *bdev)
+{
+ int err;
+ u8 value;
+
+ err = bcm2048_recv_command(bdev, BCM2048_I2C_FM_RDS_SYSTEM, &value);
+
+ if (!err && (value & BCM2048_RDS_ON))
+ return BCM2048_ITEM_ENABLED;
+
+ return err;
+}
+
+static int bcm2048_set_rds(struct bcm2048_device *bdev, u8 rds_on)
+{
+ int err;
+
+ mutex_lock(&bdev->mutex);
+
+ err = bcm2048_set_rds_no_lock(bdev, rds_on);
+
+ mutex_unlock(&bdev->mutex);
+ return err;
+}
+
+static int bcm2048_get_rds(struct bcm2048_device *bdev)
+{
+ int err;
+
+ mutex_lock(&bdev->mutex);
+
+ err = bcm2048_get_rds_no_lock(bdev);
+
+ mutex_unlock(&bdev->mutex);
+ return err;
+}
+
+static int bcm2048_get_rds_pi(struct bcm2048_device *bdev)
+{
+ return bdev->rds_info.rds_pi;
+}
+
+static int bcm2048_set_fm_automatic_stereo_mono(struct bcm2048_device *bdev,
+ u8 enabled)
+{
+ int err;
+
+ mutex_lock(&bdev->mutex);
+
+ bdev->cache_fm_ctrl &= ~BCM2048_STEREO_MONO_AUTO_SELECT;
+
+ if (enabled)
+ bdev->cache_fm_ctrl |= BCM2048_STEREO_MONO_AUTO_SELECT;
+
+ err = bcm2048_send_command(bdev, BCM2048_I2C_FM_CTRL,
+ bdev->cache_fm_ctrl);
+
+ mutex_unlock(&bdev->mutex);
+ return err;
+}
+
+static int bcm2048_set_fm_hi_lo_injection(struct bcm2048_device *bdev,
+ u8 hi_lo)
+{
+ int err;
+
+ mutex_lock(&bdev->mutex);
+
+ bdev->cache_fm_ctrl &= ~BCM2048_HI_LO_INJECTION;
+
+ if (hi_lo)
+ bdev->cache_fm_ctrl |= BCM2048_HI_LO_INJECTION;
+
+ err = bcm2048_send_command(bdev, BCM2048_I2C_FM_CTRL,
+ bdev->cache_fm_ctrl);
+
+ mutex_unlock(&bdev->mutex);
+ return err;
+}
+
+static int bcm2048_get_fm_hi_lo_injection(struct bcm2048_device *bdev)
+{
+ int err;
+ u8 value;
+
+ mutex_lock(&bdev->mutex);
+
+ err = bcm2048_recv_command(bdev, BCM2048_I2C_FM_CTRL, &value);
+
+ mutex_unlock(&bdev->mutex);
+
+ if (!err && (value & BCM2048_HI_LO_INJECTION))
+ return BCM2048_ITEM_ENABLED;
+
+ return err;
+}
+
+static int bcm2048_set_fm_frequency(struct bcm2048_device *bdev, u32 frequency)
+{
+ int err;
+
+ if (frequency < bdev->region_info.bottom_frequency ||
+ frequency > bdev->region_info.top_frequency)
+ return -EDOM;
+
+ frequency -= BCM2048_FREQUENCY_BASE;
+
+ mutex_lock(&bdev->mutex);
+
+ err = bcm2048_send_command(bdev, BCM2048_I2C_FM_FREQ0, lsb(frequency));
+ err |= bcm2048_send_command(bdev, BCM2048_I2C_FM_FREQ1,
+ msb(frequency));
+
+ if (!err)
+ bdev->frequency = frequency;
+
+ mutex_unlock(&bdev->mutex);
+ return err;
+}
+
+static int bcm2048_get_fm_frequency(struct bcm2048_device *bdev)
+{
+ int err;
+ u8 lsb, msb;
+
+ mutex_lock(&bdev->mutex);
+
+ err = bcm2048_recv_command(bdev, BCM2048_I2C_FM_FREQ0, &lsb);
+ err |= bcm2048_recv_command(bdev, BCM2048_I2C_FM_FREQ1, &msb);
+
+ mutex_unlock(&bdev->mutex);
+
+ if (err)
+ return err;
+
+ err = compose_u16(msb, lsb);
+ err += BCM2048_FREQUENCY_BASE;
+
+ return err;
+}
+
+static int bcm2048_set_fm_af_frequency(struct bcm2048_device *bdev,
+ u32 frequency)
+{
+ int err;
+
+ if (frequency < bdev->region_info.bottom_frequency ||
+ frequency > bdev->region_info.top_frequency)
+ return -EDOM;
+
+ frequency -= BCM2048_FREQUENCY_BASE;
+
+ mutex_lock(&bdev->mutex);
+
+ err = bcm2048_send_command(bdev, BCM2048_I2C_FM_AF_FREQ0,
+ lsb(frequency));
+ err |= bcm2048_send_command(bdev, BCM2048_I2C_FM_AF_FREQ1,
+ msb(frequency));
+ if (!err)
+ bdev->frequency = frequency;
+
+ mutex_unlock(&bdev->mutex);
+ return err;
+}
+
+static int bcm2048_get_fm_af_frequency(struct bcm2048_device *bdev)
+{
+ int err;
+ u8 lsb, msb;
+
+ mutex_lock(&bdev->mutex);
+
+ err = bcm2048_recv_command(bdev, BCM2048_I2C_FM_AF_FREQ0, &lsb);
+ err |= bcm2048_recv_command(bdev, BCM2048_I2C_FM_AF_FREQ1, &msb);
+
+ mutex_unlock(&bdev->mutex);
+
+ if (err)
+ return err;
+
+ err = compose_u16(msb, lsb);
+ err += BCM2048_FREQUENCY_BASE;
+
+ return err;
+}
+
+static int bcm2048_set_fm_deemphasis(struct bcm2048_device *bdev, int d)
+{
+ int err;
+ u8 deemphasis;
+
+ if (d == BCM2048_DE_EMPHASIS_75us)
+ deemphasis = BCM2048_DE_EMPHASIS_SELECT;
+ else
+ deemphasis = 0;
+
+ mutex_lock(&bdev->mutex);
+
+ bdev->cache_fm_audio_ctrl0 &= ~BCM2048_DE_EMPHASIS_SELECT;
+ bdev->cache_fm_audio_ctrl0 |= deemphasis;
+
+ err = bcm2048_send_command(bdev, BCM2048_I2C_FM_AUDIO_CTRL0,
+ bdev->cache_fm_audio_ctrl0);
+
+ if (!err)
+ bdev->region_info.deemphasis = d;
+
+ mutex_unlock(&bdev->mutex);
+
+ return err;
+}
+
+static int bcm2048_get_fm_deemphasis(struct bcm2048_device *bdev)
+{
+ int err;
+ u8 value;
+
+ mutex_lock(&bdev->mutex);
+
+ err = bcm2048_recv_command(bdev, BCM2048_I2C_FM_AUDIO_CTRL0, &value);
+
+ mutex_unlock(&bdev->mutex);
+
+ if (!err) {
+ if (value & BCM2048_DE_EMPHASIS_SELECT)
+ return BCM2048_DE_EMPHASIS_75us;
+ else
+ return BCM2048_DE_EMPHASIS_50us;
+ }
+
+ return err;
+}
+
+static int bcm2048_set_region(struct bcm2048_device *bdev, u8 region)
+{
+ int err;
+ u32 new_frequency = 0;
+
+ if (region >= ARRAY_SIZE(region_configs))
+ return -EINVAL;
+
+ mutex_lock(&bdev->mutex);
+ bdev->region_info = region_configs[region];
+ mutex_unlock(&bdev->mutex);
+
+ if (bdev->frequency < region_configs[region].bottom_frequency ||
+ bdev->frequency > region_configs[region].top_frequency)
+ new_frequency = region_configs[region].bottom_frequency;
+
+ if (new_frequency > 0) {
+ err = bcm2048_set_fm_frequency(bdev, new_frequency);
+
+ if (err)
+ goto done;
+ }
+
+ err = bcm2048_set_fm_deemphasis(bdev,
+ region_configs[region].deemphasis);
+
+done:
+ return err;
+}
+
+static int bcm2048_get_region(struct bcm2048_device *bdev)
+{
+ int err;
+
+ mutex_lock(&bdev->mutex);
+ err = bdev->region_info.region;
+ mutex_unlock(&bdev->mutex);
+
+ return err;
+}
+
+static int bcm2048_set_mute(struct bcm2048_device *bdev, u16 mute)
+{
+ int err;
+
+ mutex_lock(&bdev->mutex);
+
+ bdev->cache_fm_audio_ctrl0 &= ~(BCM2048_RF_MUTE | BCM2048_MANUAL_MUTE);
+
+ if (mute)
+ bdev->cache_fm_audio_ctrl0 |= (BCM2048_RF_MUTE |
+ BCM2048_MANUAL_MUTE);
+
+ err = bcm2048_send_command(bdev, BCM2048_I2C_FM_AUDIO_CTRL0,
+ bdev->cache_fm_audio_ctrl0);
+
+ if (!err)
+ bdev->mute_state = mute;
+
+ mutex_unlock(&bdev->mutex);
+ return err;
+}
+
+static int bcm2048_get_mute(struct bcm2048_device *bdev)
+{
+ int err;
+ u8 value;
+
+ mutex_lock(&bdev->mutex);
+
+ if (bdev->power_state) {
+ err = bcm2048_recv_command(bdev, BCM2048_I2C_FM_AUDIO_CTRL0,
+ &value);
+ if (!err)
+ err = value & (BCM2048_RF_MUTE | BCM2048_MANUAL_MUTE);
+ } else {
+ err = bdev->mute_state;
+ }
+
+ mutex_unlock(&bdev->mutex);
+ return err;
+}
+
+static int bcm2048_set_audio_route(struct bcm2048_device *bdev, u8 route)
+{
+ int err;
+
+ mutex_lock(&bdev->mutex);
+
+ route &= (BCM2048_AUDIO_ROUTE_DAC | BCM2048_AUDIO_ROUTE_I2S);
+ bdev->cache_fm_audio_ctrl0 &= ~(BCM2048_AUDIO_ROUTE_DAC |
+ BCM2048_AUDIO_ROUTE_I2S);
+ bdev->cache_fm_audio_ctrl0 |= route;
+
+ err = bcm2048_send_command(bdev, BCM2048_I2C_FM_AUDIO_CTRL0,
+ bdev->cache_fm_audio_ctrl0);
+
+ mutex_unlock(&bdev->mutex);
+ return err;
+}
+
+static int bcm2048_get_audio_route(struct bcm2048_device *bdev)
+{
+ int err;
+ u8 value;
+
+ mutex_lock(&bdev->mutex);
+
+ err = bcm2048_recv_command(bdev, BCM2048_I2C_FM_AUDIO_CTRL0, &value);
+
+ mutex_unlock(&bdev->mutex);
+
+ if (!err)
+ return value & (BCM2048_AUDIO_ROUTE_DAC |
+ BCM2048_AUDIO_ROUTE_I2S);
+
+ return err;
+}
+
+static int bcm2048_set_dac_output(struct bcm2048_device *bdev, u8 channels)
+{
+ int err;
+
+ mutex_lock(&bdev->mutex);
+
+ bdev->cache_fm_audio_ctrl0 &= ~(BCM2048_DAC_OUTPUT_LEFT |
+ BCM2048_DAC_OUTPUT_RIGHT);
+ bdev->cache_fm_audio_ctrl0 |= channels;
+
+ err = bcm2048_send_command(bdev, BCM2048_I2C_FM_AUDIO_CTRL0,
+ bdev->cache_fm_audio_ctrl0);
+
+ mutex_unlock(&bdev->mutex);
+ return err;
+}
+
+static int bcm2048_get_dac_output(struct bcm2048_device *bdev)
+{
+ int err;
+ u8 value;
+
+ mutex_lock(&bdev->mutex);
+
+ err = bcm2048_recv_command(bdev, BCM2048_I2C_FM_AUDIO_CTRL0, &value);
+
+ mutex_unlock(&bdev->mutex);
+
+ if (!err)
+ return value & (BCM2048_DAC_OUTPUT_LEFT |
+ BCM2048_DAC_OUTPUT_RIGHT);
+
+ return err;
+}
+
+static int bcm2048_set_fm_search_rssi_threshold(struct bcm2048_device *bdev,
+ u8 threshold)
+{
+ int err;
+
+ mutex_lock(&bdev->mutex);
+
+ threshold &= BCM2048_SEARCH_RSSI_THRESHOLD;
+ bdev->cache_fm_search_ctrl0 &= ~BCM2048_SEARCH_RSSI_THRESHOLD;
+ bdev->cache_fm_search_ctrl0 |= threshold;
+
+ err = bcm2048_send_command(bdev, BCM2048_I2C_FM_SEARCH_CTRL0,
+ bdev->cache_fm_search_ctrl0);
+
+ mutex_unlock(&bdev->mutex);
+ return err;
+}
+
+static int bcm2048_get_fm_search_rssi_threshold(struct bcm2048_device *bdev)
+{
+ int err;
+ u8 value;
+
+ mutex_lock(&bdev->mutex);
+
+ err = bcm2048_recv_command(bdev, BCM2048_I2C_FM_SEARCH_CTRL0, &value);
+
+ mutex_unlock(&bdev->mutex);
+
+ if (!err)
+ return value & BCM2048_SEARCH_RSSI_THRESHOLD;
+
+ return err;
+}
+
+static int bcm2048_set_fm_search_mode_direction(struct bcm2048_device *bdev,
+ u8 direction)
+{
+ int err;
+
+ mutex_lock(&bdev->mutex);
+
+ bdev->cache_fm_search_ctrl0 &= ~BCM2048_SEARCH_DIRECTION;
+
+ if (direction)
+ bdev->cache_fm_search_ctrl0 |= BCM2048_SEARCH_DIRECTION;
+
+ err = bcm2048_send_command(bdev, BCM2048_I2C_FM_SEARCH_CTRL0,
+ bdev->cache_fm_search_ctrl0);
+
+ mutex_unlock(&bdev->mutex);
+ return err;
+}
+
+static int bcm2048_get_fm_search_mode_direction(struct bcm2048_device *bdev)
+{
+ int err;
+ u8 value;
+
+ mutex_lock(&bdev->mutex);
+
+ err = bcm2048_recv_command(bdev, BCM2048_I2C_FM_SEARCH_CTRL0, &value);
+
+ mutex_unlock(&bdev->mutex);
+
+ if (!err && (value & BCM2048_SEARCH_DIRECTION))
+ return BCM2048_SEARCH_DIRECTION_UP;
+
+ return err;
+}
+
+static int bcm2048_set_fm_search_tune_mode(struct bcm2048_device *bdev,
+ u8 mode)
+{
+ int err, timeout, restart_rds = 0;
+ u8 value, flags;
+
+ value = mode & BCM2048_FM_AUTO_SEARCH;
+
+ flags = BCM2048_FM_FLAG_SEARCH_TUNE_FINISHED |
+ BCM2048_FM_FLAG_SEARCH_TUNE_FAIL;
+
+ mutex_lock(&bdev->mutex);
+
+ /*
+ * If RDS is enabled, and frequency is changed, RDS quits working.
+ * Thus, always restart RDS if it's enabled. Moreover, RDS must
+ * not be enabled while changing the frequency because it can
+ * provide a race to the mutex from the workqueue handler if RDS
+ * IRQ occurs while waiting for frequency changed IRQ.
+ */
+ if (bcm2048_get_rds_no_lock(bdev)) {
+ err = bcm2048_set_rds_no_lock(bdev, 0);
+ if (err)
+ goto unlock;
+ restart_rds = 1;
+ }
+
+ err = bcm2048_send_command(bdev, BCM2048_I2C_FM_RDS_MASK0, flags);
+
+ if (err)
+ goto unlock;
+
+ bcm2048_send_command(bdev, BCM2048_I2C_FM_SEARCH_TUNE_MODE, value);
+
+ if (mode != BCM2048_FM_AUTO_SEARCH_MODE)
+ timeout = BCM2048_DEFAULT_TIMEOUT;
+ else
+ timeout = BCM2048_AUTO_SEARCH_TIMEOUT;
+
+ if (!wait_for_completion_timeout(&bdev->compl,
+ msecs_to_jiffies(timeout)))
+ dev_err(&bdev->client->dev, "IRQ timeout.\n");
+
+ if (value)
+ if (!bdev->scan_state)
+ err = -EIO;
+
+unlock:
+ if (restart_rds)
+ err |= bcm2048_set_rds_no_lock(bdev, 1);
+
+ mutex_unlock(&bdev->mutex);
+
+ return err;
+}
+
+static int bcm2048_get_fm_search_tune_mode(struct bcm2048_device *bdev)
+{
+ int err;
+ u8 value;
+
+ mutex_lock(&bdev->mutex);
+
+ err = bcm2048_recv_command(bdev, BCM2048_I2C_FM_SEARCH_TUNE_MODE,
+ &value);
+
+ mutex_unlock(&bdev->mutex);
+
+ if (!err)
+ return value & BCM2048_FM_AUTO_SEARCH;
+
+ return err;
+}
+
+static int bcm2048_set_rds_b_block_mask(struct bcm2048_device *bdev, u16 mask)
+{
+ int err;
+
+ mutex_lock(&bdev->mutex);
+
+ err = bcm2048_send_command(bdev,
+ BCM2048_I2C_RDS_BLKB_MASK0, lsb(mask));
+ err |= bcm2048_send_command(bdev,
+ BCM2048_I2C_RDS_BLKB_MASK1, msb(mask));
+
+ mutex_unlock(&bdev->mutex);
+ return err;
+}
+
+static int bcm2048_get_rds_b_block_mask(struct bcm2048_device *bdev)
+{
+ int err;
+ u8 lsb, msb;
+
+ mutex_lock(&bdev->mutex);
+
+ err = bcm2048_recv_command(bdev,
+ BCM2048_I2C_RDS_BLKB_MASK0, &lsb);
+ err |= bcm2048_recv_command(bdev,
+ BCM2048_I2C_RDS_BLKB_MASK1, &msb);
+
+ mutex_unlock(&bdev->mutex);
+
+ if (!err)
+ return compose_u16(msb, lsb);
+
+ return err;
+}
+
+static int bcm2048_set_rds_b_block_match(struct bcm2048_device *bdev,
+ u16 match)
+{
+ int err;
+
+ mutex_lock(&bdev->mutex);
+
+ err = bcm2048_send_command(bdev,
+ BCM2048_I2C_RDS_BLKB_MATCH0, lsb(match));
+ err |= bcm2048_send_command(bdev,
+ BCM2048_I2C_RDS_BLKB_MATCH1, msb(match));
+
+ mutex_unlock(&bdev->mutex);
+ return err;
+}
+
+static int bcm2048_get_rds_b_block_match(struct bcm2048_device *bdev)
+{
+ int err;
+ u8 lsb, msb;
+
+ mutex_lock(&bdev->mutex);
+
+ err = bcm2048_recv_command(bdev,
+ BCM2048_I2C_RDS_BLKB_MATCH0, &lsb);
+ err |= bcm2048_recv_command(bdev,
+ BCM2048_I2C_RDS_BLKB_MATCH1, &msb);
+
+ mutex_unlock(&bdev->mutex);
+
+ if (!err)
+ return compose_u16(msb, lsb);
+
+ return err;
+}
+
+static int bcm2048_set_rds_pi_mask(struct bcm2048_device *bdev, u16 mask)
+{
+ int err;
+
+ mutex_lock(&bdev->mutex);
+
+ err = bcm2048_send_command(bdev,
+ BCM2048_I2C_RDS_PI_MASK0, lsb(mask));
+ err |= bcm2048_send_command(bdev,
+ BCM2048_I2C_RDS_PI_MASK1, msb(mask));
+
+ mutex_unlock(&bdev->mutex);
+ return err;
+}
+
+static int bcm2048_get_rds_pi_mask(struct bcm2048_device *bdev)
+{
+ int err;
+ u8 lsb, msb;
+
+ mutex_lock(&bdev->mutex);
+
+ err = bcm2048_recv_command(bdev,
+ BCM2048_I2C_RDS_PI_MASK0, &lsb);
+ err |= bcm2048_recv_command(bdev,
+ BCM2048_I2C_RDS_PI_MASK1, &msb);
+
+ mutex_unlock(&bdev->mutex);
+
+ if (!err)
+ return compose_u16(msb, lsb);
+
+ return err;
+}
+
+static int bcm2048_set_rds_pi_match(struct bcm2048_device *bdev, u16 match)
+{
+ int err;
+
+ mutex_lock(&bdev->mutex);
+
+ err = bcm2048_send_command(bdev,
+ BCM2048_I2C_RDS_PI_MATCH0, lsb(match));
+ err |= bcm2048_send_command(bdev,
+ BCM2048_I2C_RDS_PI_MATCH1, msb(match));
+
+ mutex_unlock(&bdev->mutex);
+ return err;
+}
+
+static int bcm2048_get_rds_pi_match(struct bcm2048_device *bdev)
+{
+ int err;
+ u8 lsb, msb;
+
+ mutex_lock(&bdev->mutex);
+
+ err = bcm2048_recv_command(bdev,
+ BCM2048_I2C_RDS_PI_MATCH0, &lsb);
+ err |= bcm2048_recv_command(bdev,
+ BCM2048_I2C_RDS_PI_MATCH1, &msb);
+
+ mutex_unlock(&bdev->mutex);
+
+ if (!err)
+ return compose_u16(msb, lsb);
+
+ return err;
+}
+
+static int bcm2048_set_fm_rds_mask(struct bcm2048_device *bdev, u16 mask)
+{
+ int err;
+
+ mutex_lock(&bdev->mutex);
+
+ err = bcm2048_send_command(bdev,
+ BCM2048_I2C_FM_RDS_MASK0, lsb(mask));
+ err |= bcm2048_send_command(bdev,
+ BCM2048_I2C_FM_RDS_MASK1, msb(mask));
+
+ mutex_unlock(&bdev->mutex);
+ return err;
+}
+
+static int bcm2048_get_fm_rds_mask(struct bcm2048_device *bdev)
+{
+ int err;
+ u8 value0, value1;
+
+ mutex_lock(&bdev->mutex);
+
+ err = bcm2048_recv_command(bdev, BCM2048_I2C_FM_RDS_MASK0, &value0);
+ err |= bcm2048_recv_command(bdev, BCM2048_I2C_FM_RDS_MASK1, &value1);
+
+ mutex_unlock(&bdev->mutex);
+
+ if (!err)
+ return compose_u16(value1, value0);
+
+ return err;
+}
+
+static int bcm2048_get_fm_rds_flags(struct bcm2048_device *bdev)
+{
+ int err;
+ u8 value0, value1;
+
+ mutex_lock(&bdev->mutex);
+
+ err = bcm2048_recv_command(bdev, BCM2048_I2C_FM_RDS_FLAG0, &value0);
+ err |= bcm2048_recv_command(bdev, BCM2048_I2C_FM_RDS_FLAG1, &value1);
+
+ mutex_unlock(&bdev->mutex);
+
+ if (!err)
+ return compose_u16(value1, value0);
+
+ return err;
+}
+
+static int bcm2048_get_region_bottom_frequency(struct bcm2048_device *bdev)
+{
+ return bdev->region_info.bottom_frequency;
+}
+
+static int bcm2048_get_region_top_frequency(struct bcm2048_device *bdev)
+{
+ return bdev->region_info.top_frequency;
+}
+
+static int bcm2048_set_fm_best_tune_mode(struct bcm2048_device *bdev, u8 mode)
+{
+ int err;
+ u8 value;
+
+ mutex_lock(&bdev->mutex);
+
+ /* Perform read as the manual indicates */
+ err = bcm2048_recv_command(bdev, BCM2048_I2C_FM_BEST_TUNE_MODE,
+ &value);
+ value &= ~BCM2048_BEST_TUNE_MODE;
+
+ if (mode)
+ value |= BCM2048_BEST_TUNE_MODE;
+ err |= bcm2048_send_command(bdev, BCM2048_I2C_FM_BEST_TUNE_MODE,
+ value);
+
+ mutex_unlock(&bdev->mutex);
+ return err;
+}
+
+static int bcm2048_get_fm_best_tune_mode(struct bcm2048_device *bdev)
+{
+ int err;
+ u8 value;
+
+ mutex_lock(&bdev->mutex);
+
+ err = bcm2048_recv_command(bdev, BCM2048_I2C_FM_BEST_TUNE_MODE,
+ &value);
+
+ mutex_unlock(&bdev->mutex);
+
+ if (!err && (value & BCM2048_BEST_TUNE_MODE))
+ return BCM2048_ITEM_ENABLED;
+
+ return err;
+}
+
+static int bcm2048_get_fm_carrier_error(struct bcm2048_device *bdev)
+{
+ int err = 0;
+ s8 value;
+
+ mutex_lock(&bdev->mutex);
+ err = bcm2048_recv_command(bdev, BCM2048_I2C_FM_CARRIER, &value);
+ mutex_unlock(&bdev->mutex);
+
+ if (!err)
+ return value;
+
+ return err;
+}
+
+static int bcm2048_get_fm_rssi(struct bcm2048_device *bdev)
+{
+ int err;
+ s8 value;
+
+ mutex_lock(&bdev->mutex);
+ err = bcm2048_recv_command(bdev, BCM2048_I2C_FM_RSSI, &value);
+ mutex_unlock(&bdev->mutex);
+
+ if (!err)
+ return value;
+
+ return err;
+}
+
+static int bcm2048_set_rds_wline(struct bcm2048_device *bdev, u8 wline)
+{
+ int err;
+
+ mutex_lock(&bdev->mutex);
+
+ err = bcm2048_send_command(bdev, BCM2048_I2C_RDS_WLINE, wline);
+
+ if (!err)
+ bdev->fifo_size = wline;
+
+ mutex_unlock(&bdev->mutex);
+ return err;
+}
+
+static int bcm2048_get_rds_wline(struct bcm2048_device *bdev)
+{
+ int err;
+ u8 value;
+
+ mutex_lock(&bdev->mutex);
+
+ err = bcm2048_recv_command(bdev, BCM2048_I2C_RDS_WLINE, &value);
+
+ mutex_unlock(&bdev->mutex);
+
+ if (!err) {
+ bdev->fifo_size = value;
+ return value;
+ }
+
+ return err;
+}
+
+static int bcm2048_checkrev(struct bcm2048_device *bdev)
+{
+ int err;
+ u8 version;
+
+ mutex_lock(&bdev->mutex);
+
+ err = bcm2048_recv_command(bdev, BCM2048_I2C_FM_RDS_REV, &version);
+
+ mutex_unlock(&bdev->mutex);
+
+ if (!err) {
+ dev_info(&bdev->client->dev, "BCM2048 Version 0x%x\n",
+ version);
+ return version;
+ }
+
+ return err;
+}
+
+static int bcm2048_get_rds_rt(struct bcm2048_device *bdev, char *data)
+{
+ int err = 0, i, j = 0, ce = 0, cr = 0;
+ char data_buffer[BCM2048_MAX_RDS_RT+1];
+
+ mutex_lock(&bdev->mutex);
+
+ if (!bdev->rds_info.text_len) {
+ err = -EINVAL;
+ goto unlock;
+ }
+
+ for (i = 0; i < BCM2048_MAX_RDS_RT; i++) {
+ if (bdev->rds_info.rds_rt[i]) {
+ ce = i;
+ /* Skip the carriage return */
+ if (bdev->rds_info.rds_rt[i] != 0x0d) {
+ data_buffer[j++] = bdev->rds_info.rds_rt[i];
+ } else {
+ cr = i;
+ break;
+ }
+ }
+ }
+
+ if (j <= BCM2048_MAX_RDS_RT)
+ data_buffer[j] = 0;
+
+ for (i = 0; i < BCM2048_MAX_RDS_RT; i++) {
+ if (!bdev->rds_info.rds_rt[i]) {
+ if (cr && (i < cr)) {
+ err = -EBUSY;
+ goto unlock;
+ }
+ if (i < ce) {
+ if (cr && (i >= cr))
+ break;
+ err = -EBUSY;
+ goto unlock;
+ }
+ }
+ }
+
+ memcpy(data, data_buffer, sizeof(data_buffer));
+
+unlock:
+ mutex_unlock(&bdev->mutex);
+ return err;
+}
+
+static int bcm2048_get_rds_ps(struct bcm2048_device *bdev, char *data)
+{
+ int err = 0, i, j = 0;
+ char data_buffer[BCM2048_MAX_RDS_PS+1];
+
+ mutex_lock(&bdev->mutex);
+
+ if (!bdev->rds_info.text_len) {
+ err = -EINVAL;
+ goto unlock;
+ }
+
+ for (i = 0; i < BCM2048_MAX_RDS_PS; i++) {
+ if (bdev->rds_info.rds_ps[i]) {
+ data_buffer[j++] = bdev->rds_info.rds_ps[i];
+ } else {
+ if (i < (BCM2048_MAX_RDS_PS - 1)) {
+ err = -EBUSY;
+ goto unlock;
+ }
+ }
+ }
+
+ if (j <= BCM2048_MAX_RDS_PS)
+ data_buffer[j] = 0;
+
+ memcpy(data, data_buffer, sizeof(data_buffer));
+
+unlock:
+ mutex_unlock(&bdev->mutex);
+ return err;
+}
+
+static void bcm2048_parse_rds_pi(struct bcm2048_device *bdev)
+{
+ int i, cnt = 0;
+ u16 pi;
+
+ for (i = 0; i < bdev->fifo_size; i += BCM2048_RDS_FIFO_DUPLE_SIZE) {
+
+ /* Block A match, only data without crc errors taken */
+ if (bdev->rds_info.radio_text[i] == BCM2048_RDS_BLOCK_A) {
+
+ pi = ((bdev->rds_info.radio_text[i+1] << 8) +
+ bdev->rds_info.radio_text[i+2]);
+
+ if (!bdev->rds_info.rds_pi) {
+ bdev->rds_info.rds_pi = pi;
+ return;
+ }
+ if (pi != bdev->rds_info.rds_pi) {
+ cnt++;
+ if (cnt > 3) {
+ bdev->rds_info.rds_pi = pi;
+ cnt = 0;
+ }
+ } else {
+ cnt = 0;
+ }
+ }
+ }
+}
+
+static int bcm2048_rds_block_crc(struct bcm2048_device *bdev, int i)
+{
+ return bdev->rds_info.radio_text[i] & BCM2048_RDS_CRC_MASK;
+}
+
+static void bcm2048_parse_rds_rt_block(struct bcm2048_device *bdev, int i,
+ int index, int crc)
+{
+ /* Good data will overwrite poor data */
+ if (crc) {
+ if (!bdev->rds_info.rds_rt[index])
+ bdev->rds_info.rds_rt[index] =
+ bdev->rds_info.radio_text[i+1];
+ if (!bdev->rds_info.rds_rt[index+1])
+ bdev->rds_info.rds_rt[index+1] =
+ bdev->rds_info.radio_text[i+2];
+ } else {
+ bdev->rds_info.rds_rt[index] = bdev->rds_info.radio_text[i+1];
+ bdev->rds_info.rds_rt[index+1] =
+ bdev->rds_info.radio_text[i+2];
+ }
+}
+
+static int bcm2048_parse_rt_match_b(struct bcm2048_device *bdev, int i)
+{
+ int crc, rt_id, rt_group_b, rt_ab, index = 0;
+
+ crc = bcm2048_rds_block_crc(bdev, i);
+
+ if (crc == BCM2048_RDS_CRC_UNRECOVARABLE)
+ return -EIO;
+
+ if ((bdev->rds_info.radio_text[i] & BCM2048_RDS_BLOCK_MASK) ==
+ BCM2048_RDS_BLOCK_B) {
+
+ rt_id = (bdev->rds_info.radio_text[i+1] &
+ BCM2048_RDS_BLOCK_MASK);
+ rt_group_b = bdev->rds_info.radio_text[i+1] &
+ BCM2048_RDS_GROUP_AB_MASK;
+ rt_ab = bdev->rds_info.radio_text[i+2] &
+ BCM2048_RDS_RT_AB_MASK;
+
+ if (rt_group_b != bdev->rds_info.rds_rt_group_b) {
+ memset(bdev->rds_info.rds_rt, 0,
+ sizeof(bdev->rds_info.rds_rt));
+ bdev->rds_info.rds_rt_group_b = rt_group_b;
+ }
+
+ if (rt_id == BCM2048_RDS_RT) {
+ /* A to B or (vice versa), means: clear screen */
+ if (rt_ab != bdev->rds_info.rds_rt_ab) {
+ memset(bdev->rds_info.rds_rt, 0,
+ sizeof(bdev->rds_info.rds_rt));
+ bdev->rds_info.rds_rt_ab = rt_ab;
+ }
+
+ index = bdev->rds_info.radio_text[i+2] &
+ BCM2048_RDS_RT_INDEX;
+
+ if (bdev->rds_info.rds_rt_group_b)
+ index <<= 1;
+ else
+ index <<= 2;
+
+ return index;
+ }
+ }
+
+ return -EIO;
+}
+
+static int bcm2048_parse_rt_match_c(struct bcm2048_device *bdev, int i,
+ int index)
+{
+ int crc;
+
+ crc = bcm2048_rds_block_crc(bdev, i);
+
+ if (crc == BCM2048_RDS_CRC_UNRECOVARABLE)
+ return 0;
+
+ BUG_ON((index+2) >= BCM2048_MAX_RDS_RT);
+
+ if ((bdev->rds_info.radio_text[i] & BCM2048_RDS_BLOCK_MASK) ==
+ BCM2048_RDS_BLOCK_C) {
+ if (bdev->rds_info.rds_rt_group_b)
+ return 1;
+ bcm2048_parse_rds_rt_block(bdev, i, index, crc);
+ return 1;
+ }
+
+ return 0;
+}
+
+static void bcm2048_parse_rt_match_d(struct bcm2048_device *bdev, int i,
+ int index)
+{
+ int crc;
+
+ crc = bcm2048_rds_block_crc(bdev, i);
+
+ if (crc == BCM2048_RDS_CRC_UNRECOVARABLE)
+ return;
+
+ BUG_ON((index+4) >= BCM2048_MAX_RDS_RT);
+
+ if ((bdev->rds_info.radio_text[i] & BCM2048_RDS_BLOCK_MASK) ==
+ BCM2048_RDS_BLOCK_D)
+ bcm2048_parse_rds_rt_block(bdev, i, index+2, crc);
+}
+
+static int bcm2048_parse_rds_rt(struct bcm2048_device *bdev)
+{
+ int i, index = 0, crc, match_b = 0, match_c = 0, match_d = 0;
+
+ for (i = 0; i < bdev->fifo_size; i += BCM2048_RDS_FIFO_DUPLE_SIZE) {
+
+ if (match_b) {
+ match_b = 0;
+ index = bcm2048_parse_rt_match_b(bdev, i);
+ if (index >= 0 && index <= (BCM2048_MAX_RDS_RT - 5))
+ match_c = 1;
+ continue;
+ } else if (match_c) {
+ match_c = 0;
+ if (bcm2048_parse_rt_match_c(bdev, i, index))
+ match_d = 1;
+ continue;
+ } else if (match_d) {
+ match_d = 0;
+ bcm2048_parse_rt_match_d(bdev, i, index);
+ continue;
+ }
+
+ /* Skip erroneous blocks due to messed up A block altogether */
+ if ((bdev->rds_info.radio_text[i] & BCM2048_RDS_BLOCK_MASK)
+ == BCM2048_RDS_BLOCK_A) {
+ crc = bcm2048_rds_block_crc(bdev, i);
+ if (crc == BCM2048_RDS_CRC_UNRECOVARABLE)
+ continue;
+ /* Syncronize to a good RDS PI */
+ if (((bdev->rds_info.radio_text[i+1] << 8) +
+ bdev->rds_info.radio_text[i+2]) ==
+ bdev->rds_info.rds_pi)
+ match_b = 1;
+ }
+ }
+
+ return 0;
+}
+
+static void bcm2048_parse_rds_ps_block(struct bcm2048_device *bdev, int i,
+ int index, int crc)
+{
+ /* Good data will overwrite poor data */
+ if (crc) {
+ if (!bdev->rds_info.rds_ps[index])
+ bdev->rds_info.rds_ps[index] =
+ bdev->rds_info.radio_text[i+1];
+ if (!bdev->rds_info.rds_ps[index+1])
+ bdev->rds_info.rds_ps[index+1] =
+ bdev->rds_info.radio_text[i+2];
+ } else {
+ bdev->rds_info.rds_ps[index] = bdev->rds_info.radio_text[i+1];
+ bdev->rds_info.rds_ps[index+1] =
+ bdev->rds_info.radio_text[i+2];
+ }
+}
+
+static int bcm2048_parse_ps_match_c(struct bcm2048_device *bdev, int i,
+ int index)
+{
+ int crc;
+
+ crc = bcm2048_rds_block_crc(bdev, i);
+
+ if (crc == BCM2048_RDS_CRC_UNRECOVARABLE)
+ return 0;
+
+ if ((bdev->rds_info.radio_text[i] & BCM2048_RDS_BLOCK_MASK) ==
+ BCM2048_RDS_BLOCK_C)
+ return 1;
+
+ return 0;
+}
+
+static void bcm2048_parse_ps_match_d(struct bcm2048_device *bdev, int i,
+ int index)
+{
+ int crc;
+
+ crc = bcm2048_rds_block_crc(bdev, i);
+
+ if (crc == BCM2048_RDS_CRC_UNRECOVARABLE)
+ return;
+
+ if ((bdev->rds_info.radio_text[i] & BCM2048_RDS_BLOCK_MASK) ==
+ BCM2048_RDS_BLOCK_D)
+ bcm2048_parse_rds_ps_block(bdev, i, index, crc);
+}
+
+static int bcm2048_parse_ps_match_b(struct bcm2048_device *bdev, int i)
+{
+ int crc, index, ps_id, ps_group;
+
+ crc = bcm2048_rds_block_crc(bdev, i);
+
+ if (crc == BCM2048_RDS_CRC_UNRECOVARABLE)
+ return -EIO;
+
+ /* Block B Radio PS match */
+ if ((bdev->rds_info.radio_text[i] & BCM2048_RDS_BLOCK_MASK) ==
+ BCM2048_RDS_BLOCK_B) {
+ ps_id = bdev->rds_info.radio_text[i+1] &
+ BCM2048_RDS_BLOCK_MASK;
+ ps_group = bdev->rds_info.radio_text[i+1] &
+ BCM2048_RDS_GROUP_AB_MASK;
+
+ /*
+ * Poor RSSI will lead to RDS data corruption
+ * So using 3 (same) sequential values to justify major changes
+ */
+ if (ps_group != bdev->rds_info.rds_ps_group) {
+ if (crc == BCM2048_RDS_CRC_NONE) {
+ bdev->rds_info.rds_ps_group_cnt++;
+ if (bdev->rds_info.rds_ps_group_cnt > 2) {
+ bdev->rds_info.rds_ps_group = ps_group;
+ bdev->rds_info.rds_ps_group_cnt = 0;
+ dev_err(&bdev->client->dev,
+ "RDS PS Group change!\n");
+ } else {
+ return -EIO;
+ }
+ } else {
+ bdev->rds_info.rds_ps_group_cnt = 0;
+ }
+ }
+
+ if (ps_id == BCM2048_RDS_PS) {
+ index = bdev->rds_info.radio_text[i+2] &
+ BCM2048_RDS_PS_INDEX;
+ index <<= 1;
+ return index;
+ }
+ }
+
+ return -EIO;
+}
+
+static void bcm2048_parse_rds_ps(struct bcm2048_device *bdev)
+{
+ int i, index = 0, crc, match_b = 0, match_c = 0, match_d = 0;
+
+ for (i = 0; i < bdev->fifo_size; i += BCM2048_RDS_FIFO_DUPLE_SIZE) {
+
+ if (match_b) {
+ match_b = 0;
+ index = bcm2048_parse_ps_match_b(bdev, i);
+ if (index >= 0 && index < (BCM2048_MAX_RDS_PS - 1))
+ match_c = 1;
+ continue;
+ } else if (match_c) {
+ match_c = 0;
+ if (bcm2048_parse_ps_match_c(bdev, i, index))
+ match_d = 1;
+ continue;
+ } else if (match_d) {
+ match_d = 0;
+ bcm2048_parse_ps_match_d(bdev, i, index);
+ continue;
+ }
+
+ /* Skip erroneous blocks due to messed up A block altogether */
+ if ((bdev->rds_info.radio_text[i] & BCM2048_RDS_BLOCK_MASK)
+ == BCM2048_RDS_BLOCK_A) {
+ crc = bcm2048_rds_block_crc(bdev, i);
+ if (crc == BCM2048_RDS_CRC_UNRECOVARABLE)
+ continue;
+ /* Syncronize to a good RDS PI */
+ if (((bdev->rds_info.radio_text[i+1] << 8) +
+ bdev->rds_info.radio_text[i+2]) ==
+ bdev->rds_info.rds_pi)
+ match_b = 1;
+ }
+ }
+}
+
+static void bcm2048_rds_fifo_receive(struct bcm2048_device *bdev)
+{
+ int err;
+
+ mutex_lock(&bdev->mutex);
+
+ err = bcm2048_recv_duples(bdev, BCM2048_I2C_RDS_DATA,
+ bdev->rds_info.radio_text, bdev->fifo_size);
+ if (err != 2) {
+ dev_err(&bdev->client->dev, "RDS Read problem\n");
+ mutex_unlock(&bdev->mutex);
+ return;
+ }
+
+ bdev->rds_info.text_len = bdev->fifo_size;
+
+ bcm2048_parse_rds_pi(bdev);
+ bcm2048_parse_rds_rt(bdev);
+ bcm2048_parse_rds_ps(bdev);
+
+ mutex_unlock(&bdev->mutex);
+
+ wake_up_interruptible(&bdev->read_queue);
+}
+
+static int bcm2048_get_rds_data(struct bcm2048_device *bdev, char *data)
+{
+ int err = 0, i, p = 0;
+ char *data_buffer;
+
+ mutex_lock(&bdev->mutex);
+
+ if (!bdev->rds_info.text_len) {
+ err = -EINVAL;
+ goto unlock;
+ }
+
+ data_buffer = kzalloc(BCM2048_MAX_RDS_RADIO_TEXT*5, GFP_KERNEL);
+ if (!data_buffer) {
+ err = -ENOMEM;
+ goto unlock;
+ }
+
+ for (i = 0; i < bdev->rds_info.text_len; i++) {
+ p += sprintf(data_buffer+p, "%x ",
+ bdev->rds_info.radio_text[i]);
+ }
+
+ memcpy(data, data_buffer, p);
+ kfree(data_buffer);
+
+unlock:
+ mutex_unlock(&bdev->mutex);
+ return err;
+}
+
+/*
+ * BCM2048 default initialization sequence
+ */
+static int bcm2048_init(struct bcm2048_device *bdev)
+{
+ int err;
+
+ err = bcm2048_set_power_state(bdev, BCM2048_POWER_ON);
+ if (err < 0)
+ goto exit;
+
+ err = bcm2048_set_audio_route(bdev, BCM2048_AUDIO_ROUTE_DAC);
+ if (err < 0)
+ goto exit;
+
+ err = bcm2048_set_dac_output(bdev, BCM2048_DAC_OUTPUT_LEFT |
+ BCM2048_DAC_OUTPUT_RIGHT);
+
+exit:
+ return err;
+}
+
+/*
+ * BCM2048 default deinitialization sequence
+ */
+static int bcm2048_deinit(struct bcm2048_device *bdev)
+{
+ int err;
+
+ err = bcm2048_set_audio_route(bdev, 0);
+ if (err < 0)
+ goto exit;
+
+ err = bcm2048_set_dac_output(bdev, 0);
+ if (err < 0)
+ goto exit;
+
+ err = bcm2048_set_power_state(bdev, BCM2048_POWER_OFF);
+ if (err < 0)
+ goto exit;
+
+exit:
+ return err;
+}
+
+/*
+ * BCM2048 probe sequence
+ */
+static int bcm2048_probe(struct bcm2048_device *bdev)
+{
+ int err;
+
+ err = bcm2048_set_power_state(bdev, BCM2048_POWER_ON);
+ if (err < 0)
+ goto unlock;
+
+ err = bcm2048_checkrev(bdev);
+ if (err < 0)
+ goto unlock;
+
+ err = bcm2048_set_mute(bdev, BCM2048_DEFAULT_MUTE);
+ if (err < 0)
+ goto unlock;
+
+ err = bcm2048_set_region(bdev, BCM2048_DEFAULT_REGION);
+ if (err < 0)
+ goto unlock;
+
+ err = bcm2048_set_fm_search_rssi_threshold(bdev,
+ BCM2048_DEFAULT_RSSI_THRESHOLD);
+ if (err < 0)
+ goto unlock;
+
+ err = bcm2048_set_fm_automatic_stereo_mono(bdev, BCM2048_ITEM_ENABLED);
+ if (err < 0)
+ goto unlock;
+
+ err = bcm2048_get_rds_wline(bdev);
+ if (err < BCM2048_DEFAULT_RDS_WLINE)
+ err = bcm2048_set_rds_wline(bdev, BCM2048_DEFAULT_RDS_WLINE);
+ if (err < 0)
+ goto unlock;
+
+ err = bcm2048_set_power_state(bdev, BCM2048_POWER_OFF);
+
+ init_waitqueue_head(&bdev->read_queue);
+ bdev->rds_data_available = 0;
+ bdev->rd_index = 0;
+ bdev->users = 0;
+
+unlock:
+ return err;
+}
+
+/*
+ * BCM2048 workqueue handler
+ */
+static void bcm2048_work(struct work_struct *work)
+{
+ struct bcm2048_device *bdev;
+ u8 flag_lsb, flag_msb, flags;
+
+ bdev = container_of(work, struct bcm2048_device, work);
+ bcm2048_recv_command(bdev, BCM2048_I2C_FM_RDS_FLAG0, &flag_lsb);
+ bcm2048_recv_command(bdev, BCM2048_I2C_FM_RDS_FLAG1, &flag_msb);
+
+ if (flag_lsb & (BCM2048_FM_FLAG_SEARCH_TUNE_FINISHED |
+ BCM2048_FM_FLAG_SEARCH_TUNE_FAIL)) {
+
+ if (flag_lsb & BCM2048_FM_FLAG_SEARCH_TUNE_FAIL)
+ bdev->scan_state = BCM2048_SCAN_FAIL;
+ else
+ bdev->scan_state = BCM2048_SCAN_OK;
+
+ complete(&bdev->compl);
+ }
+
+ if (flag_msb & BCM2048_RDS_FLAG_FIFO_WLINE) {
+ bcm2048_rds_fifo_receive(bdev);
+ if (bdev->rds_state) {
+ flags = BCM2048_RDS_FLAG_FIFO_WLINE;
+ bcm2048_send_command(bdev, BCM2048_I2C_FM_RDS_MASK1,
+ flags);
+ }
+ bdev->rds_data_available = 1;
+ bdev->rd_index = 0; /* new data, new start */
+ }
+}
+
+/*
+ * BCM2048 interrupt handler
+ */
+static irqreturn_t bcm2048_handler(int irq, void *dev)
+{
+ struct bcm2048_device *bdev = dev;
+
+ dev_dbg(&bdev->client->dev, "IRQ called, queuing work\n");
+ if (bdev->power_state)
+ schedule_work(&bdev->work);
+
+ return IRQ_HANDLED;
+}
+
+/*
+ * BCM2048 sysfs interface definitions
+ */
+#define property_write(prop, type, mask, check) \
+static ssize_t bcm2048_##prop##_write(struct device *dev, \
+ struct device_attribute *attr, \
+ const char *buf, \
+ size_t count) \
+{ \
+ struct bcm2048_device *bdev = dev_get_drvdata(dev); \
+ type value; \
+ int err; \
+ \
+ if (!bdev) \
+ return -ENODEV; \
+ \
+ sscanf(buf, mask, &value); \
+ \
+ if (check) \
+ return -EDOM; \
+ \
+ err = bcm2048_set_##prop(bdev, value); \
+ \
+ return err < 0 ? err : count; \
+}
+
+#define property_read(prop, size, mask) \
+static ssize_t bcm2048_##prop##_read(struct device *dev, \
+ struct device_attribute *attr, \
+ char *buf) \
+{ \
+ struct bcm2048_device *bdev = dev_get_drvdata(dev); \
+ int value; \
+ \
+ if (!bdev) \
+ return -ENODEV; \
+ \
+ value = bcm2048_get_##prop(bdev); \
+ \
+ if (value >= 0) \
+ value = sprintf(buf, mask "\n", value); \
+ \
+ return value; \
+}
+
+#define property_signed_read(prop, size, mask) \
+static ssize_t bcm2048_##prop##_read(struct device *dev, \
+ struct device_attribute *attr, \
+ char *buf) \
+{ \
+ struct bcm2048_device *bdev = dev_get_drvdata(dev); \
+ size value; \
+ \
+ if (!bdev) \
+ return -ENODEV; \
+ \
+ value = bcm2048_get_##prop(bdev); \
+ \
+ value = sprintf(buf, mask "\n", value); \
+ \
+ return value; \
+}
+
+#define DEFINE_SYSFS_PROPERTY(prop, signal, size, mask, check) \
+property_write(prop, signal size, mask, check) \
+property_read(prop, size, mask)
+
+#define property_str_read(prop, size) \
+static ssize_t bcm2048_##prop##_read(struct device *dev, \
+ struct device_attribute *attr, \
+ char *buf) \
+{ \
+ struct bcm2048_device *bdev = dev_get_drvdata(dev); \
+ int count; \
+ u8 *out; \
+ \
+ if (!bdev) \
+ return -ENODEV; \
+ \
+ out = kzalloc(size + 1, GFP_KERNEL); \
+ if (!out) \
+ return -ENOMEM; \
+ \
+ bcm2048_get_##prop(bdev, out); \
+ count = sprintf(buf, "%s\n", out); \
+ \
+ kfree(out); \
+ \
+ return count; \
+}
+
+DEFINE_SYSFS_PROPERTY(power_state, unsigned, int, "%u", 0)
+DEFINE_SYSFS_PROPERTY(mute, unsigned, int, "%u", 0)
+DEFINE_SYSFS_PROPERTY(audio_route, unsigned, int, "%u", 0)
+DEFINE_SYSFS_PROPERTY(dac_output, unsigned, int, "%u", 0)
+
+DEFINE_SYSFS_PROPERTY(fm_hi_lo_injection, unsigned, int, "%u", 0)
+DEFINE_SYSFS_PROPERTY(fm_frequency, unsigned, int, "%u", 0)
+DEFINE_SYSFS_PROPERTY(fm_af_frequency, unsigned, int, "%u", 0)
+DEFINE_SYSFS_PROPERTY(fm_deemphasis, unsigned, int, "%u", 0)
+DEFINE_SYSFS_PROPERTY(fm_rds_mask, unsigned, int, "%u", 0)
+DEFINE_SYSFS_PROPERTY(fm_best_tune_mode, unsigned, int, "%u", 0)
+DEFINE_SYSFS_PROPERTY(fm_search_rssi_threshold, unsigned, int, "%u", 0)
+DEFINE_SYSFS_PROPERTY(fm_search_mode_direction, unsigned, int, "%u", 0)
+DEFINE_SYSFS_PROPERTY(fm_search_tune_mode, unsigned, int, "%u", value > 3)
+
+DEFINE_SYSFS_PROPERTY(rds, unsigned, int, "%u", 0)
+DEFINE_SYSFS_PROPERTY(rds_b_block_mask, unsigned, int, "%u", 0)
+DEFINE_SYSFS_PROPERTY(rds_b_block_match, unsigned, int, "%u", 0)
+DEFINE_SYSFS_PROPERTY(rds_pi_mask, unsigned, int, "%u", 0)
+DEFINE_SYSFS_PROPERTY(rds_pi_match, unsigned, int, "%u", 0)
+DEFINE_SYSFS_PROPERTY(rds_wline, unsigned, int, "%u", 0)
+property_read(rds_pi, unsigned int, "%x")
+property_str_read(rds_rt, (BCM2048_MAX_RDS_RT + 1))
+property_str_read(rds_ps, (BCM2048_MAX_RDS_PS + 1))
+
+property_read(fm_rds_flags, unsigned int, "%u")
+property_str_read(rds_data, BCM2048_MAX_RDS_RADIO_TEXT*5)
+
+property_read(region_bottom_frequency, unsigned int, "%u")
+property_read(region_top_frequency, unsigned int, "%u")
+property_signed_read(fm_carrier_error, int, "%d")
+property_signed_read(fm_rssi, int, "%d")
+DEFINE_SYSFS_PROPERTY(region, unsigned, int, "%u", 0)
+
+static struct device_attribute attrs[] = {
+ __ATTR(power_state, S_IRUGO | S_IWUSR, bcm2048_power_state_read,
+ bcm2048_power_state_write),
+ __ATTR(mute, S_IRUGO | S_IWUSR, bcm2048_mute_read,
+ bcm2048_mute_write),
+ __ATTR(audio_route, S_IRUGO | S_IWUSR, bcm2048_audio_route_read,
+ bcm2048_audio_route_write),
+ __ATTR(dac_output, S_IRUGO | S_IWUSR, bcm2048_dac_output_read,
+ bcm2048_dac_output_write),
+ __ATTR(fm_hi_lo_injection, S_IRUGO | S_IWUSR,
+ bcm2048_fm_hi_lo_injection_read,
+ bcm2048_fm_hi_lo_injection_write),
+ __ATTR(fm_frequency, S_IRUGO | S_IWUSR, bcm2048_fm_frequency_read,
+ bcm2048_fm_frequency_write),
+ __ATTR(fm_af_frequency, S_IRUGO | S_IWUSR,
+ bcm2048_fm_af_frequency_read,
+ bcm2048_fm_af_frequency_write),
+ __ATTR(fm_deemphasis, S_IRUGO | S_IWUSR, bcm2048_fm_deemphasis_read,
+ bcm2048_fm_deemphasis_write),
+ __ATTR(fm_rds_mask, S_IRUGO | S_IWUSR, bcm2048_fm_rds_mask_read,
+ bcm2048_fm_rds_mask_write),
+ __ATTR(fm_best_tune_mode, S_IRUGO | S_IWUSR,
+ bcm2048_fm_best_tune_mode_read,
+ bcm2048_fm_best_tune_mode_write),
+ __ATTR(fm_search_rssi_threshold, S_IRUGO | S_IWUSR,
+ bcm2048_fm_search_rssi_threshold_read,
+ bcm2048_fm_search_rssi_threshold_write),
+ __ATTR(fm_search_mode_direction, S_IRUGO | S_IWUSR,
+ bcm2048_fm_search_mode_direction_read,
+ bcm2048_fm_search_mode_direction_write),
+ __ATTR(fm_search_tune_mode, S_IRUGO | S_IWUSR,
+ bcm2048_fm_search_tune_mode_read,
+ bcm2048_fm_search_tune_mode_write),
+ __ATTR(rds, S_IRUGO | S_IWUSR, bcm2048_rds_read,
+ bcm2048_rds_write),
+ __ATTR(rds_b_block_mask, S_IRUGO | S_IWUSR,
+ bcm2048_rds_b_block_mask_read,
+ bcm2048_rds_b_block_mask_write),
+ __ATTR(rds_b_block_match, S_IRUGO | S_IWUSR,
+ bcm2048_rds_b_block_match_read,
+ bcm2048_rds_b_block_match_write),
+ __ATTR(rds_pi_mask, S_IRUGO | S_IWUSR, bcm2048_rds_pi_mask_read,
+ bcm2048_rds_pi_mask_write),
+ __ATTR(rds_pi_match, S_IRUGO | S_IWUSR, bcm2048_rds_pi_match_read,
+ bcm2048_rds_pi_match_write),
+ __ATTR(rds_wline, S_IRUGO | S_IWUSR, bcm2048_rds_wline_read,
+ bcm2048_rds_wline_write),
+ __ATTR(rds_pi, S_IRUGO, bcm2048_rds_pi_read, NULL),
+ __ATTR(rds_rt, S_IRUGO, bcm2048_rds_rt_read, NULL),
+ __ATTR(rds_ps, S_IRUGO, bcm2048_rds_ps_read, NULL),
+ __ATTR(fm_rds_flags, S_IRUGO, bcm2048_fm_rds_flags_read, NULL),
+ __ATTR(region_bottom_frequency, S_IRUGO,
+ bcm2048_region_bottom_frequency_read, NULL),
+ __ATTR(region_top_frequency, S_IRUGO,
+ bcm2048_region_top_frequency_read, NULL),
+ __ATTR(fm_carrier_error, S_IRUGO,
+ bcm2048_fm_carrier_error_read, NULL),
+ __ATTR(fm_rssi, S_IRUGO,
+ bcm2048_fm_rssi_read, NULL),
+ __ATTR(region, S_IRUGO | S_IWUSR, bcm2048_region_read,
+ bcm2048_region_write),
+ __ATTR(rds_data, S_IRUGO, bcm2048_rds_data_read, NULL),
+};
+
+static int bcm2048_sysfs_unregister_properties(struct bcm2048_device *bdev,
+ int size)
+{
+ int i;
+
+ for (i = 0; i < size; i++)
+ device_remove_file(&bdev->client->dev, &attrs[i]);
+
+ return 0;
+}
+
+static int bcm2048_sysfs_register_properties(struct bcm2048_device *bdev)
+{
+ int err = 0;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(attrs); i++) {
+ if (device_create_file(&bdev->client->dev, &attrs[i]) != 0) {
+ dev_err(&bdev->client->dev,
+ "could not register sysfs entry\n");
+ err = -EBUSY;
+ bcm2048_sysfs_unregister_properties(bdev, i);
+ break;
+ }
+ }
+
+ return err;
+}
+
+
+static int bcm2048_fops_open(struct file *file)
+{
+ struct bcm2048_device *bdev = video_drvdata(file);
+
+ bdev->users++;
+ bdev->rd_index = 0;
+ bdev->rds_data_available = 0;
+
+ return 0;
+}
+
+static int bcm2048_fops_release(struct file *file)
+{
+ struct bcm2048_device *bdev = video_drvdata(file);
+
+ bdev->users--;
+
+ return 0;
+}
+
+static unsigned int bcm2048_fops_poll(struct file *file,
+ struct poll_table_struct *pts)
+{
+ struct bcm2048_device *bdev = video_drvdata(file);
+ int retval = 0;
+
+ poll_wait(file, &bdev->read_queue, pts);
+
+ if (bdev->rds_data_available)
+ retval = POLLIN | POLLRDNORM;
+
+ return retval;
+}
+
+static ssize_t bcm2048_fops_read(struct file *file, char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ struct bcm2048_device *bdev = video_drvdata(file);
+ int i;
+ int retval = 0;
+
+ /* we return at least 3 bytes, one block */
+ count = (count / 3) * 3; /* only multiples of 3 */
+ if (count < 3)
+ return -ENOBUFS;
+
+ while (!bdev->rds_data_available) {
+ if (file->f_flags & O_NONBLOCK) {
+ retval = -EWOULDBLOCK;
+ goto done;
+ }
+ /* interruptible_sleep_on(&bdev->read_queue); */
+ if (wait_event_interruptible(bdev->read_queue,
+ bdev->rds_data_available) < 0) {
+ retval = -EINTR;
+ goto done;
+ }
+ }
+
+ mutex_lock(&bdev->mutex);
+ /* copy data to userspace */
+ i = bdev->fifo_size - bdev->rd_index;
+ if (count > i)
+ count = (i / 3) * 3;
+
+ i = 0;
+ while (i < count) {
+ unsigned char tmpbuf[3];
+ tmpbuf[i] = bdev->rds_info.radio_text[bdev->rd_index+i+2];
+ tmpbuf[i+1] = bdev->rds_info.radio_text[bdev->rd_index+i+1];
+ tmpbuf[i+2] = ((bdev->rds_info.radio_text[bdev->rd_index+i]
+ & 0xf0) >> 4);
+ if ((bdev->rds_info.radio_text[bdev->rd_index+i] &
+ BCM2048_RDS_CRC_MASK) == BCM2048_RDS_CRC_UNRECOVARABLE)
+ tmpbuf[i+2] |= 0x80;
+ if (copy_to_user(buf+i, tmpbuf, 3)) {
+ retval = -EFAULT;
+ break;
+ }
+ i += 3;
+ }
+
+ bdev->rd_index += i;
+ if (bdev->rd_index >= bdev->fifo_size)
+ bdev->rds_data_available = 0;
+
+ mutex_unlock(&bdev->mutex);
+ if (retval == 0)
+ retval = i;
+
+done:
+ return retval;
+}
+
+/*
+ * bcm2048_fops - file operations interface
+ */
+static const struct v4l2_file_operations bcm2048_fops = {
+ .owner = THIS_MODULE,
+ .ioctl = video_ioctl2,
+ /* for RDS read support */
+ .open = bcm2048_fops_open,
+ .release = bcm2048_fops_release,
+ .read = bcm2048_fops_read,
+ .poll = bcm2048_fops_poll
+};
+
+/*
+ * Video4Linux Interface
+ */
+static struct v4l2_queryctrl bcm2048_v4l2_queryctrl[] = {
+ {
+ .id = V4L2_CID_AUDIO_VOLUME,
+ .flags = V4L2_CTRL_FLAG_DISABLED,
+ },
+ {
+ .id = V4L2_CID_AUDIO_BALANCE,
+ .flags = V4L2_CTRL_FLAG_DISABLED,
+ },
+ {
+ .id = V4L2_CID_AUDIO_BASS,
+ .flags = V4L2_CTRL_FLAG_DISABLED,
+ },
+ {
+ .id = V4L2_CID_AUDIO_TREBLE,
+ .flags = V4L2_CTRL_FLAG_DISABLED,
+ },
+ {
+ .id = V4L2_CID_AUDIO_MUTE,
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ .name = "Mute",
+ .minimum = 0,
+ .maximum = 1,
+ .step = 1,
+ .default_value = 1,
+ },
+ {
+ .id = V4L2_CID_AUDIO_LOUDNESS,
+ .flags = V4L2_CTRL_FLAG_DISABLED,
+ },
+};
+
+static int bcm2048_vidioc_querycap(struct file *file, void *priv,
+ struct v4l2_capability *capability)
+{
+ struct bcm2048_device *bdev = video_get_drvdata(video_devdata(file));
+
+ strlcpy(capability->driver, BCM2048_DRIVER_NAME,
+ sizeof(capability->driver));
+ strlcpy(capability->card, BCM2048_DRIVER_CARD,
+ sizeof(capability->card));
+ snprintf(capability->bus_info, 32, "I2C: 0x%X", bdev->client->addr);
+ capability->version = BCM2048_DRIVER_VERSION;
+ capability->capabilities = V4L2_CAP_TUNER | V4L2_CAP_RADIO |
+ V4L2_CAP_HW_FREQ_SEEK;
+
+ return 0;
+}
+
+static int bcm2048_vidioc_g_input(struct file *filp, void *priv,
+ unsigned int *i)
+{
+ *i = 0;
+
+ return 0;
+}
+
+static int bcm2048_vidioc_s_input(struct file *filp, void *priv,
+ unsigned int i)
+{
+ if (i)
+ return -EINVAL;
+
+ return 0;
+}
+
+static int bcm2048_vidioc_queryctrl(struct file *file, void *priv,
+ struct v4l2_queryctrl *qc)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(bcm2048_v4l2_queryctrl); i++) {
+ if (qc->id && qc->id == bcm2048_v4l2_queryctrl[i].id) {
+ *qc = bcm2048_v4l2_queryctrl[i];
+ return 0;
+ }
+ }
+
+ return -EINVAL;
+}
+
+static int bcm2048_vidioc_g_ctrl(struct file *file, void *priv,
+ struct v4l2_control *ctrl)
+{
+ struct bcm2048_device *bdev = video_get_drvdata(video_devdata(file));
+ int err = 0;
+
+ if (!bdev)
+ return -ENODEV;
+
+ switch (ctrl->id) {
+ case V4L2_CID_AUDIO_MUTE:
+ err = bcm2048_get_mute(bdev);
+ if (err >= 0)
+ ctrl->value = err;
+ break;
+ }
+
+ return err;
+}
+
+static int bcm2048_vidioc_s_ctrl(struct file *file, void *priv,
+ struct v4l2_control *ctrl)
+{
+ struct bcm2048_device *bdev = video_get_drvdata(video_devdata(file));
+ int err = 0;
+
+ if (!bdev)
+ return -ENODEV;
+
+ switch (ctrl->id) {
+ case V4L2_CID_AUDIO_MUTE:
+ if (ctrl->value) {
+ if (bdev->power_state) {
+ err = bcm2048_set_mute(bdev, ctrl->value);
+ err |= bcm2048_deinit(bdev);
+ }
+ } else {
+ if (!bdev->power_state) {
+ err = bcm2048_init(bdev);
+ err |= bcm2048_set_mute(bdev, ctrl->value);
+ }
+ }
+ break;
+ }
+
+ return err;
+}
+
+static int bcm2048_vidioc_g_audio(struct file *file, void *priv,
+ struct v4l2_audio *audio)
+{
+ if (audio->index > 1)
+ return -EINVAL;
+
+ strncpy(audio->name, "Radio", 32);
+ audio->capability = V4L2_AUDCAP_STEREO;
+
+ return 0;
+}
+
+static int bcm2048_vidioc_s_audio(struct file *file, void *priv,
+ const struct v4l2_audio *audio)
+{
+ if (audio->index != 0)
+ return -EINVAL;
+
+ return 0;
+}
+
+static int bcm2048_vidioc_g_tuner(struct file *file, void *priv,
+ struct v4l2_tuner *tuner)
+{
+ struct bcm2048_device *bdev = video_get_drvdata(video_devdata(file));
+ s8 f_error;
+ s8 rssi;
+
+ if (!bdev)
+ return -ENODEV;
+
+ if (tuner->index > 0)
+ return -EINVAL;
+
+ strncpy(tuner->name, "FM Receiver", 32);
+ tuner->type = V4L2_TUNER_RADIO;
+ tuner->rangelow =
+ dev_to_v4l2(bcm2048_get_region_bottom_frequency(bdev));
+ tuner->rangehigh =
+ dev_to_v4l2(bcm2048_get_region_top_frequency(bdev));
+ tuner->rxsubchans = V4L2_TUNER_SUB_STEREO;
+ tuner->capability = V4L2_TUNER_CAP_STEREO | V4L2_TUNER_CAP_LOW;
+ tuner->audmode = V4L2_TUNER_MODE_STEREO;
+ tuner->afc = 0;
+ if (bdev->power_state) {
+ /*
+ * Report frequencies with high carrier errors to have zero
+ * signal level
+ */
+ f_error = bcm2048_get_fm_carrier_error(bdev);
+ if (f_error < BCM2048_FREQ_ERROR_FLOOR ||
+ f_error > BCM2048_FREQ_ERROR_ROOF) {
+ tuner->signal = 0;
+ } else {
+ /*
+ * RSSI level -60 dB is defined to report full
+ * signal strenght
+ */
+ rssi = bcm2048_get_fm_rssi(bdev);
+ if (rssi >= BCM2048_RSSI_LEVEL_BASE) {
+ tuner->signal = 0xFFFF;
+ } else if (rssi > BCM2048_RSSI_LEVEL_ROOF) {
+ tuner->signal = (rssi +
+ BCM2048_RSSI_LEVEL_ROOF_NEG)
+ * BCM2048_SIGNAL_MULTIPLIER;
+ } else {
+ tuner->signal = 0;
+ }
+ }
+ } else {
+ tuner->signal = 0;
+ }
+
+ return 0;
+}
+
+static int bcm2048_vidioc_s_tuner(struct file *file, void *priv,
+ const struct v4l2_tuner *tuner)
+{
+ struct bcm2048_device *bdev = video_get_drvdata(video_devdata(file));
+
+ if (!bdev)
+ return -ENODEV;
+
+ if (tuner->index > 0)
+ return -EINVAL;
+
+ return 0;
+}
+
+static int bcm2048_vidioc_g_frequency(struct file *file, void *priv,
+ struct v4l2_frequency *freq)
+{
+ struct bcm2048_device *bdev = video_get_drvdata(video_devdata(file));
+ int err = 0;
+ int f;
+
+ if (!bdev->power_state)
+ return -ENODEV;
+
+ freq->type = V4L2_TUNER_RADIO;
+ f = bcm2048_get_fm_frequency(bdev);
+
+ if (f < 0)
+ err = f;
+ else
+ freq->frequency = dev_to_v4l2(f);
+
+ return err;
+}
+
+static int bcm2048_vidioc_s_frequency(struct file *file, void *priv,
+ const struct v4l2_frequency *freq)
+{
+ struct bcm2048_device *bdev = video_get_drvdata(video_devdata(file));
+ int err;
+
+ if (freq->type != V4L2_TUNER_RADIO)
+ return -EINVAL;
+
+ if (!bdev->power_state)
+ return -ENODEV;
+
+ err = bcm2048_set_fm_frequency(bdev, v4l2_to_dev(freq->frequency));
+ err |= bcm2048_set_fm_search_tune_mode(bdev, BCM2048_FM_PRE_SET_MODE);
+
+ return err;
+}
+
+static int bcm2048_vidioc_s_hw_freq_seek(struct file *file, void *priv,
+ const struct v4l2_hw_freq_seek *seek)
+{
+ struct bcm2048_device *bdev = video_get_drvdata(video_devdata(file));
+ int err;
+
+ if (!bdev->power_state)
+ return -ENODEV;
+
+ if ((seek->tuner != 0) || (seek->type != V4L2_TUNER_RADIO))
+ return -EINVAL;
+
+ err = bcm2048_set_fm_search_mode_direction(bdev, seek->seek_upward);
+ err |= bcm2048_set_fm_search_tune_mode(bdev,
+ BCM2048_FM_AUTO_SEARCH_MODE);
+
+ return err;
+}
+
+static struct v4l2_ioctl_ops bcm2048_ioctl_ops = {
+ .vidioc_querycap = bcm2048_vidioc_querycap,
+ .vidioc_g_input = bcm2048_vidioc_g_input,
+ .vidioc_s_input = bcm2048_vidioc_s_input,
+ .vidioc_queryctrl = bcm2048_vidioc_queryctrl,
+ .vidioc_g_ctrl = bcm2048_vidioc_g_ctrl,
+ .vidioc_s_ctrl = bcm2048_vidioc_s_ctrl,
+ .vidioc_g_audio = bcm2048_vidioc_g_audio,
+ .vidioc_s_audio = bcm2048_vidioc_s_audio,
+ .vidioc_g_tuner = bcm2048_vidioc_g_tuner,
+ .vidioc_s_tuner = bcm2048_vidioc_s_tuner,
+ .vidioc_g_frequency = bcm2048_vidioc_g_frequency,
+ .vidioc_s_frequency = bcm2048_vidioc_s_frequency,
+ .vidioc_s_hw_freq_seek = bcm2048_vidioc_s_hw_freq_seek,
+};
+
+/*
+ * bcm2048_viddev_template - video device interface
+ */
+static struct video_device bcm2048_viddev_template = {
+ .fops = &bcm2048_fops,
+ .name = BCM2048_DRIVER_NAME,
+ .release = video_device_release,
+ .ioctl_ops = &bcm2048_ioctl_ops,
+};
+
+/*
+ * I2C driver interface
+ */
+static int bcm2048_i2c_driver_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct bcm2048_device *bdev;
+ int err, skip_release = 0;
+
+ bdev = kzalloc(sizeof(*bdev), GFP_KERNEL);
+ if (!bdev) {
+ dev_dbg(&client->dev, "Failed to alloc video device.\n");
+ err = -ENOMEM;
+ goto exit;
+ }
+
+ bdev->videodev = video_device_alloc();
+ if (!bdev->videodev) {
+ dev_dbg(&client->dev, "Failed to alloc video device.\n");
+ err = -ENOMEM;
+ goto free_bdev;
+ }
+
+ bdev->client = client;
+ i2c_set_clientdata(client, bdev);
+ mutex_init(&bdev->mutex);
+ init_completion(&bdev->compl);
+ INIT_WORK(&bdev->work, bcm2048_work);
+
+ if (client->irq) {
+ err = request_irq(client->irq,
+ bcm2048_handler, IRQF_TRIGGER_FALLING | IRQF_DISABLED,
+ client->name, bdev);
+ if (err < 0) {
+ dev_err(&client->dev, "Could not request IRQ\n");
+ goto free_vdev;
+ }
+ dev_dbg(&client->dev, "IRQ requested.\n");
+ } else {
+ dev_dbg(&client->dev, "IRQ not configured. Using timeouts.\n");
+ }
+
+ *bdev->videodev = bcm2048_viddev_template;
+ video_set_drvdata(bdev->videodev, bdev);
+ if (video_register_device(bdev->videodev, VFL_TYPE_RADIO, radio_nr)) {
+ dev_dbg(&client->dev, "Could not register video device.\n");
+ err = -EIO;
+ goto free_irq;
+ }
+
+ err = bcm2048_sysfs_register_properties(bdev);
+ if (err < 0) {
+ dev_dbg(&client->dev, "Could not register sysfs interface.\n");
+ goto free_registration;
+ }
+
+ err = bcm2048_probe(bdev);
+ if (err < 0) {
+ dev_dbg(&client->dev, "Failed to probe device information.\n");
+ goto free_sysfs;
+ }
+
+ return 0;
+
+free_sysfs:
+ bcm2048_sysfs_unregister_properties(bdev, ARRAY_SIZE(attrs));
+free_registration:
+ video_unregister_device(bdev->videodev);
+ /* video_unregister_device frees bdev->videodev */
+ bdev->videodev = NULL;
+ skip_release = 1;
+free_irq:
+ if (client->irq)
+ free_irq(client->irq, bdev);
+free_vdev:
+ if (!skip_release)
+ video_device_release(bdev->videodev);
+ i2c_set_clientdata(client, NULL);
+free_bdev:
+ kfree(bdev);
+exit:
+ return err;
+}
+
+static int __exit bcm2048_i2c_driver_remove(struct i2c_client *client)
+{
+ struct bcm2048_device *bdev = i2c_get_clientdata(client);
+ struct video_device *vd;
+
+ if (!client->adapter)
+ return -ENODEV;
+
+ if (bdev) {
+ vd = bdev->videodev;
+
+ bcm2048_sysfs_unregister_properties(bdev, ARRAY_SIZE(attrs));
+
+ if (vd)
+ video_unregister_device(vd);
+
+ if (bdev->power_state)
+ bcm2048_set_power_state(bdev, BCM2048_POWER_OFF);
+
+ if (client->irq > 0)
+ free_irq(client->irq, bdev);
+
+ cancel_work_sync(&bdev->work);
+
+ kfree(bdev);
+ }
+
+ i2c_set_clientdata(client, NULL);
+
+ return 0;
+}
+
+/*
+ * bcm2048_i2c_driver - i2c driver interface
+ */
+static const struct i2c_device_id bcm2048_id[] = {
+ { "bcm2048" , 0 },
+ { },
+};
+MODULE_DEVICE_TABLE(i2c, bcm2048_id);
+
+static struct i2c_driver bcm2048_i2c_driver = {
+ .driver = {
+ .name = BCM2048_DRIVER_NAME,
+ },
+ .probe = bcm2048_i2c_driver_probe,
+ .remove = __exit_p(bcm2048_i2c_driver_remove),
+ .id_table = bcm2048_id,
+};
+
+/*
+ * Module Interface
+ */
+static int __init bcm2048_module_init(void)
+{
+ pr_info(BCM2048_DRIVER_DESC "\n");
+
+ return i2c_add_driver(&bcm2048_i2c_driver);
+}
+module_init(bcm2048_module_init);
+
+static void __exit bcm2048_module_exit(void)
+{
+ i2c_del_driver(&bcm2048_i2c_driver);
+}
+module_exit(bcm2048_module_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR(BCM2048_DRIVER_AUTHOR);
+MODULE_DESCRIPTION(BCM2048_DRIVER_DESC);
+MODULE_VERSION("0.0.2");
diff --git a/drivers/staging/media/bcm2048/radio-bcm2048.h b/drivers/staging/media/bcm2048/radio-bcm2048.h
new file mode 100644
index 00000000000..4c90a32db79
--- /dev/null
+++ b/drivers/staging/media/bcm2048/radio-bcm2048.h
@@ -0,0 +1,30 @@
+/*
+ * drivers/staging/media/radio-bcm2048.h
+ *
+ * Property and command definitions for bcm2048 radio receiver chip.
+ *
+ * Copyright (C) Nokia Corporation
+ * Contact: Eero Nurkkala <ext-eero.nurkkala@nokia.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ */
+
+#ifndef BCM2048_H
+#define BCM2048_H
+
+#define BCM2048_NAME "bcm2048"
+#define BCM2048_I2C_ADDR 0x22
+
+#endif /* ifndef BCM2048_H */
diff --git a/drivers/staging/media/cxd2099/Kconfig b/drivers/staging/media/cxd2099/Kconfig
new file mode 100644
index 00000000000..b48aefddc84
--- /dev/null
+++ b/drivers/staging/media/cxd2099/Kconfig
@@ -0,0 +1,12 @@
+config DVB_CXD2099
+ tristate "CXD2099AR Common Interface driver"
+ depends on DVB_CORE && PCI && I2C
+ ---help---
+ Support for the CI module found on cards based on
+ - Micronas ngene PCIe bridge: cineS2 etc.
+ - Digital Devices PCIe bridge: Octopus series
+
+ For now, data is passed through '/dev/dvb/adapterX/sec0':
+ - Encrypted data must be written to 'sec0'.
+ - Decrypted data can be read from 'sec0'.
+ - Setup the CAM using device 'ca0'.
diff --git a/drivers/staging/media/cxd2099/Makefile b/drivers/staging/media/cxd2099/Makefile
new file mode 100644
index 00000000000..b2905e65057
--- /dev/null
+++ b/drivers/staging/media/cxd2099/Makefile
@@ -0,0 +1,5 @@
+obj-$(CONFIG_DVB_CXD2099) += cxd2099.o
+
+ccflags-y += -Idrivers/media/dvb-core/
+ccflags-y += -Idrivers/media/dvb-frontends/
+ccflags-y += -Idrivers/media/tuners/
diff --git a/drivers/staging/media/cxd2099/TODO b/drivers/staging/media/cxd2099/TODO
new file mode 100644
index 00000000000..375bb6f8ee2
--- /dev/null
+++ b/drivers/staging/media/cxd2099/TODO
@@ -0,0 +1,12 @@
+For now, data is passed through '/dev/dvb/adapterX/sec0':
+ - Encrypted data must be written to 'sec0'.
+ - Decrypted data can be read from 'sec0'.
+ - Setup the CAM using device 'ca0'.
+
+But this is wrong. There are some discussions about the proper way for
+doing it, as seen at:
+ http://www.mail-archive.com/linux-media@vger.kernel.org/msg22196.html
+
+While there's no proper fix for it, the driver should be kept in staging.
+
+Patches should be submitted to: linux-media@vger.kernel.org.
diff --git a/drivers/staging/media/cxd2099/cxd2099.c b/drivers/staging/media/cxd2099/cxd2099.c
new file mode 100644
index 00000000000..a2a5182570c
--- /dev/null
+++ b/drivers/staging/media/cxd2099/cxd2099.c
@@ -0,0 +1,719 @@
+/*
+ * cxd2099.c: Driver for the CXD2099AR Common Interface Controller
+ *
+ * Copyright (C) 2010-2011 Digital Devices GmbH
+ *
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 only, as published by the Free Software Foundation.
+ *
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA
+ * Or, point your browser to http://www.gnu.org/copyleft/gpl.html
+ */
+
+#include <linux/slab.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/i2c.h>
+#include <linux/wait.h>
+#include <linux/delay.h>
+#include <linux/mutex.h>
+#include <linux/io.h>
+
+#include "cxd2099.h"
+
+#define MAX_BUFFER_SIZE 248
+
+struct cxd {
+ struct dvb_ca_en50221 en;
+
+ struct i2c_adapter *i2c;
+ struct cxd2099_cfg cfg;
+
+ u8 regs[0x23];
+ u8 lastaddress;
+ u8 clk_reg_f;
+ u8 clk_reg_b;
+ int mode;
+ int ready;
+ int dr;
+ int slot_stat;
+
+ u8 amem[1024];
+ int amem_read;
+
+ int cammode;
+ struct mutex lock;
+};
+
+static int i2c_write_reg(struct i2c_adapter *adapter, u8 adr,
+ u8 reg, u8 data)
+{
+ u8 m[2] = {reg, data};
+ struct i2c_msg msg = {.addr = adr, .flags = 0, .buf = m, .len = 2};
+
+ if (i2c_transfer(adapter, &msg, 1) != 1) {
+ dev_err(&adapter->dev,
+ "Failed to write to I2C register %02x@%02x!\n",
+ reg, adr);
+ return -1;
+ }
+ return 0;
+}
+
+static int i2c_write(struct i2c_adapter *adapter, u8 adr,
+ u8 *data, u8 len)
+{
+ struct i2c_msg msg = {.addr = adr, .flags = 0, .buf = data, .len = len};
+
+ if (i2c_transfer(adapter, &msg, 1) != 1) {
+ dev_err(&adapter->dev, "Failed to write to I2C!\n");
+ return -1;
+ }
+ return 0;
+}
+
+static int i2c_read_reg(struct i2c_adapter *adapter, u8 adr,
+ u8 reg, u8 *val)
+{
+ struct i2c_msg msgs[2] = {{.addr = adr, .flags = 0,
+ .buf = &reg, .len = 1},
+ {.addr = adr, .flags = I2C_M_RD,
+ .buf = val, .len = 1} };
+
+ if (i2c_transfer(adapter, msgs, 2) != 2) {
+ dev_err(&adapter->dev, "error in i2c_read_reg\n");
+ return -1;
+ }
+ return 0;
+}
+
+static int i2c_read(struct i2c_adapter *adapter, u8 adr,
+ u8 reg, u8 *data, u8 n)
+{
+ struct i2c_msg msgs[2] = {{.addr = adr, .flags = 0,
+ .buf = &reg, .len = 1},
+ {.addr = adr, .flags = I2C_M_RD,
+ .buf = data, .len = n} };
+
+ if (i2c_transfer(adapter, msgs, 2) != 2) {
+ dev_err(&adapter->dev, "error in i2c_read\n");
+ return -1;
+ }
+ return 0;
+}
+
+static int read_block(struct cxd *ci, u8 adr, u8 *data, u8 n)
+{
+ int status;
+
+ status = i2c_write_reg(ci->i2c, ci->cfg.adr, 0, adr);
+ if (!status) {
+ ci->lastaddress = adr;
+ status = i2c_read(ci->i2c, ci->cfg.adr, 1, data, n);
+ }
+ return status;
+}
+
+static int read_reg(struct cxd *ci, u8 reg, u8 *val)
+{
+ return read_block(ci, reg, val, 1);
+}
+
+
+static int read_pccard(struct cxd *ci, u16 address, u8 *data, u8 n)
+{
+ int status;
+ u8 addr[3] = {2, address & 0xff, address >> 8};
+
+ status = i2c_write(ci->i2c, ci->cfg.adr, addr, 3);
+ if (!status)
+ status = i2c_read(ci->i2c, ci->cfg.adr, 3, data, n);
+ return status;
+}
+
+static int write_pccard(struct cxd *ci, u16 address, u8 *data, u8 n)
+{
+ int status;
+ u8 addr[3] = {2, address & 0xff, address >> 8};
+
+ status = i2c_write(ci->i2c, ci->cfg.adr, addr, 3);
+ if (!status) {
+ u8 buf[256] = {3};
+ memcpy(buf+1, data, n);
+ status = i2c_write(ci->i2c, ci->cfg.adr, buf, n+1);
+ }
+ return status;
+}
+
+static int read_io(struct cxd *ci, u16 address, u8 *val)
+{
+ int status;
+ u8 addr[3] = {2, address & 0xff, address >> 8};
+
+ status = i2c_write(ci->i2c, ci->cfg.adr, addr, 3);
+ if (!status)
+ status = i2c_read(ci->i2c, ci->cfg.adr, 3, val, 1);
+ return status;
+}
+
+static int write_io(struct cxd *ci, u16 address, u8 val)
+{
+ int status;
+ u8 addr[3] = {2, address & 0xff, address >> 8};
+ u8 buf[2] = {3, val};
+
+ status = i2c_write(ci->i2c, ci->cfg.adr, addr, 3);
+ if (!status)
+ status = i2c_write(ci->i2c, ci->cfg.adr, buf, 2);
+ return status;
+}
+
+#if 0
+static int read_io_data(struct cxd *ci, u8 *data, u8 n)
+{
+ int status;
+ u8 addr[3] = { 2, 0, 0 };
+
+ status = i2c_write(ci->i2c, ci->cfg.adr, addr, 3);
+ if (!status)
+ status = i2c_read(ci->i2c, ci->cfg.adr, 3, data, n);
+ return 0;
+}
+
+static int write_io_data(struct cxd *ci, u8 *data, u8 n)
+{
+ int status;
+ u8 addr[3] = {2, 0, 0};
+
+ status = i2c_write(ci->i2c, ci->cfg.adr, addr, 3);
+ if (!status) {
+ u8 buf[256] = {3};
+ memcpy(buf+1, data, n);
+ status = i2c_write(ci->i2c, ci->cfg.adr, buf, n + 1);
+ }
+ return 0;
+}
+#endif
+
+static int write_regm(struct cxd *ci, u8 reg, u8 val, u8 mask)
+{
+ int status;
+
+ status = i2c_write_reg(ci->i2c, ci->cfg.adr, 0, reg);
+ if (!status && reg >= 6 && reg <= 8 && mask != 0xff)
+ status = i2c_read_reg(ci->i2c, ci->cfg.adr, 1, &ci->regs[reg]);
+ ci->regs[reg] = (ci->regs[reg] & (~mask)) | val;
+ if (!status) {
+ ci->lastaddress = reg;
+ status = i2c_write_reg(ci->i2c, ci->cfg.adr, 1, ci->regs[reg]);
+ }
+ if (reg == 0x20)
+ ci->regs[reg] &= 0x7f;
+ return status;
+}
+
+static int write_reg(struct cxd *ci, u8 reg, u8 val)
+{
+ return write_regm(ci, reg, val, 0xff);
+}
+
+#ifdef BUFFER_MODE
+static int write_block(struct cxd *ci, u8 adr, u8 *data, int n)
+{
+ int status;
+ u8 buf[256] = {1};
+
+ status = i2c_write_reg(ci->i2c, ci->cfg.adr, 0, adr);
+ if (!status) {
+ ci->lastaddress = adr;
+ memcpy(buf + 1, data, n);
+ status = i2c_write(ci->i2c, ci->cfg.adr, buf, n + 1);
+ }
+ return status;
+}
+#endif
+
+static void set_mode(struct cxd *ci, int mode)
+{
+ if (mode == ci->mode)
+ return;
+
+ switch (mode) {
+ case 0x00: /* IO mem */
+ write_regm(ci, 0x06, 0x00, 0x07);
+ break;
+ case 0x01: /* ATT mem */
+ write_regm(ci, 0x06, 0x02, 0x07);
+ break;
+ default:
+ break;
+ }
+ ci->mode = mode;
+}
+
+static void cam_mode(struct cxd *ci, int mode)
+{
+ if (mode == ci->cammode)
+ return;
+
+ switch (mode) {
+ case 0x00:
+ write_regm(ci, 0x20, 0x80, 0x80);
+ break;
+ case 0x01:
+#ifdef BUFFER_MODE
+ if (!ci->en.read_data)
+ return;
+ dev_info(&ci->i2c->dev, "enable cam buffer mode\n");
+ /* write_reg(ci, 0x0d, 0x00); */
+ /* write_reg(ci, 0x0e, 0x01); */
+ write_regm(ci, 0x08, 0x40, 0x40);
+ /* read_reg(ci, 0x12, &dummy); */
+ write_regm(ci, 0x08, 0x80, 0x80);
+#endif
+ break;
+ default:
+ break;
+ }
+ ci->cammode = mode;
+}
+
+
+
+static int init(struct cxd *ci)
+{
+ int status;
+
+ mutex_lock(&ci->lock);
+ ci->mode = -1;
+ do {
+ status = write_reg(ci, 0x00, 0x00);
+ if (status < 0)
+ break;
+ status = write_reg(ci, 0x01, 0x00);
+ if (status < 0)
+ break;
+ status = write_reg(ci, 0x02, 0x10);
+ if (status < 0)
+ break;
+ status = write_reg(ci, 0x03, 0x00);
+ if (status < 0)
+ break;
+ status = write_reg(ci, 0x05, 0xFF);
+ if (status < 0)
+ break;
+ status = write_reg(ci, 0x06, 0x1F);
+ if (status < 0)
+ break;
+ status = write_reg(ci, 0x07, 0x1F);
+ if (status < 0)
+ break;
+ status = write_reg(ci, 0x08, 0x28);
+ if (status < 0)
+ break;
+ status = write_reg(ci, 0x14, 0x20);
+ if (status < 0)
+ break;
+
+#if 0
+ /* Input Mode C, BYPass Serial, TIVAL = low, MSB */
+ status = write_reg(ci, 0x09, 0x4D);
+ if (status < 0)
+ break;
+#endif
+ /* TOSTRT = 8, Mode B (gated clock), falling Edge,
+ * Serial, POL=HIGH, MSB */
+ status = write_reg(ci, 0x0A, 0xA7);
+ if (status < 0)
+ break;
+
+ status = write_reg(ci, 0x0B, 0x33);
+ if (status < 0)
+ break;
+ status = write_reg(ci, 0x0C, 0x33);
+ if (status < 0)
+ break;
+
+ status = write_regm(ci, 0x14, 0x00, 0x0F);
+ if (status < 0)
+ break;
+ status = write_reg(ci, 0x15, ci->clk_reg_b);
+ if (status < 0)
+ break;
+ status = write_regm(ci, 0x16, 0x00, 0x0F);
+ if (status < 0)
+ break;
+ status = write_reg(ci, 0x17, ci->clk_reg_f);
+ if (status < 0)
+ break;
+
+ if (ci->cfg.clock_mode) {
+ if (ci->cfg.polarity) {
+ status = write_reg(ci, 0x09, 0x6f);
+ if (status < 0)
+ break;
+ } else {
+ status = write_reg(ci, 0x09, 0x6d);
+ if (status < 0)
+ break;
+ }
+ status = write_reg(ci, 0x20, 0x68);
+ if (status < 0)
+ break;
+ status = write_reg(ci, 0x21, 0x00);
+ if (status < 0)
+ break;
+ status = write_reg(ci, 0x22, 0x02);
+ if (status < 0)
+ break;
+ } else {
+ if (ci->cfg.polarity) {
+ status = write_reg(ci, 0x09, 0x4f);
+ if (status < 0)
+ break;
+ } else {
+ status = write_reg(ci, 0x09, 0x4d);
+ if (status < 0)
+ break;
+ }
+
+ status = write_reg(ci, 0x20, 0x28);
+ if (status < 0)
+ break;
+ status = write_reg(ci, 0x21, 0x00);
+ if (status < 0)
+ break;
+ status = write_reg(ci, 0x22, 0x07);
+ if (status < 0)
+ break;
+ }
+
+ status = write_regm(ci, 0x20, 0x80, 0x80);
+ if (status < 0)
+ break;
+ status = write_regm(ci, 0x03, 0x02, 0x02);
+ if (status < 0)
+ break;
+ status = write_reg(ci, 0x01, 0x04);
+ if (status < 0)
+ break;
+ status = write_reg(ci, 0x00, 0x31);
+ if (status < 0)
+ break;
+
+ /* Put TS in bypass */
+ status = write_regm(ci, 0x09, 0x08, 0x08);
+ if (status < 0)
+ break;
+ ci->cammode = -1;
+ cam_mode(ci, 0);
+ } while (0);
+ mutex_unlock(&ci->lock);
+
+ return 0;
+}
+
+static int read_attribute_mem(struct dvb_ca_en50221 *ca,
+ int slot, int address)
+{
+ struct cxd *ci = ca->data;
+#if 0
+ if (ci->amem_read) {
+ if (address <= 0 || address > 1024)
+ return -EIO;
+ return ci->amem[address];
+ }
+
+ mutex_lock(&ci->lock);
+ write_regm(ci, 0x06, 0x00, 0x05);
+ read_pccard(ci, 0, &ci->amem[0], 128);
+ read_pccard(ci, 128, &ci->amem[0], 128);
+ read_pccard(ci, 256, &ci->amem[0], 128);
+ read_pccard(ci, 384, &ci->amem[0], 128);
+ write_regm(ci, 0x06, 0x05, 0x05);
+ mutex_unlock(&ci->lock);
+ return ci->amem[address];
+#else
+ u8 val;
+ mutex_lock(&ci->lock);
+ set_mode(ci, 1);
+ read_pccard(ci, address, &val, 1);
+ mutex_unlock(&ci->lock);
+ /* printk(KERN_INFO "%02x:%02x\n", address,val); */
+ return val;
+#endif
+}
+
+static int write_attribute_mem(struct dvb_ca_en50221 *ca, int slot,
+ int address, u8 value)
+{
+ struct cxd *ci = ca->data;
+
+ mutex_lock(&ci->lock);
+ set_mode(ci, 1);
+ write_pccard(ci, address, &value, 1);
+ mutex_unlock(&ci->lock);
+ return 0;
+}
+
+static int read_cam_control(struct dvb_ca_en50221 *ca,
+ int slot, u8 address)
+{
+ struct cxd *ci = ca->data;
+ u8 val;
+
+ mutex_lock(&ci->lock);
+ set_mode(ci, 0);
+ read_io(ci, address, &val);
+ mutex_unlock(&ci->lock);
+ return val;
+}
+
+static int write_cam_control(struct dvb_ca_en50221 *ca, int slot,
+ u8 address, u8 value)
+{
+ struct cxd *ci = ca->data;
+
+ mutex_lock(&ci->lock);
+ set_mode(ci, 0);
+ write_io(ci, address, value);
+ mutex_unlock(&ci->lock);
+ return 0;
+}
+
+static int slot_reset(struct dvb_ca_en50221 *ca, int slot)
+{
+ struct cxd *ci = ca->data;
+
+ mutex_lock(&ci->lock);
+#if 0
+ write_reg(ci, 0x00, 0x21);
+ write_reg(ci, 0x06, 0x1F);
+ write_reg(ci, 0x00, 0x31);
+#else
+#if 0
+ write_reg(ci, 0x06, 0x1F);
+ write_reg(ci, 0x06, 0x2F);
+#else
+ cam_mode(ci, 0);
+ write_reg(ci, 0x00, 0x21);
+ write_reg(ci, 0x06, 0x1F);
+ write_reg(ci, 0x00, 0x31);
+ write_regm(ci, 0x20, 0x80, 0x80);
+ write_reg(ci, 0x03, 0x02);
+ ci->ready = 0;
+#endif
+#endif
+ ci->mode = -1;
+ {
+ int i;
+#if 0
+ u8 val;
+#endif
+ for (i = 0; i < 100; i++) {
+ msleep(10);
+#if 0
+ read_reg(ci, 0x06, &val);
+ dev_info(&ci->i2c->dev, "%d:%02x\n", i, val);
+ if (!(val&0x10))
+ break;
+#else
+ if (ci->ready)
+ break;
+#endif
+ }
+ }
+ mutex_unlock(&ci->lock);
+ /* msleep(500); */
+ return 0;
+}
+
+static int slot_shutdown(struct dvb_ca_en50221 *ca, int slot)
+{
+ struct cxd *ci = ca->data;
+
+ dev_info(&ci->i2c->dev, "slot_shutdown\n");
+ mutex_lock(&ci->lock);
+ write_regm(ci, 0x09, 0x08, 0x08);
+ write_regm(ci, 0x20, 0x80, 0x80); /* Reset CAM Mode */
+ write_regm(ci, 0x06, 0x07, 0x07); /* Clear IO Mode */
+ ci->mode = -1;
+ mutex_unlock(&ci->lock);
+ return 0;
+}
+
+static int slot_ts_enable(struct dvb_ca_en50221 *ca, int slot)
+{
+ struct cxd *ci = ca->data;
+
+ mutex_lock(&ci->lock);
+ write_regm(ci, 0x09, 0x00, 0x08);
+ set_mode(ci, 0);
+#ifdef BUFFER_MODE
+ cam_mode(ci, 1);
+#endif
+ mutex_unlock(&ci->lock);
+ return 0;
+}
+
+
+static int campoll(struct cxd *ci)
+{
+ u8 istat;
+
+ read_reg(ci, 0x04, &istat);
+ if (!istat)
+ return 0;
+ write_reg(ci, 0x05, istat);
+
+ if (istat&0x40) {
+ ci->dr = 1;
+ dev_info(&ci->i2c->dev, "DR\n");
+ }
+ if (istat&0x20)
+ dev_info(&ci->i2c->dev, "WC\n");
+
+ if (istat&2) {
+ u8 slotstat;
+
+ read_reg(ci, 0x01, &slotstat);
+ if (!(2&slotstat)) {
+ if (!ci->slot_stat) {
+ ci->slot_stat = DVB_CA_EN50221_POLL_CAM_PRESENT;
+ write_regm(ci, 0x03, 0x08, 0x08);
+ }
+
+ } else {
+ if (ci->slot_stat) {
+ ci->slot_stat = 0;
+ write_regm(ci, 0x03, 0x00, 0x08);
+ dev_info(&ci->i2c->dev, "NO CAM\n");
+ ci->ready = 0;
+ }
+ }
+ if (istat&8 &&
+ ci->slot_stat == DVB_CA_EN50221_POLL_CAM_PRESENT) {
+ ci->ready = 1;
+ ci->slot_stat |= DVB_CA_EN50221_POLL_CAM_READY;
+ }
+ }
+ return 0;
+}
+
+
+static int poll_slot_status(struct dvb_ca_en50221 *ca, int slot, int open)
+{
+ struct cxd *ci = ca->data;
+ u8 slotstat;
+
+ mutex_lock(&ci->lock);
+ campoll(ci);
+ read_reg(ci, 0x01, &slotstat);
+ mutex_unlock(&ci->lock);
+
+ return ci->slot_stat;
+}
+
+#ifdef BUFFER_MODE
+static int read_data(struct dvb_ca_en50221 *ca, int slot, u8 *ebuf, int ecount)
+{
+ struct cxd *ci = ca->data;
+ u8 msb, lsb;
+ u16 len;
+
+ mutex_lock(&ci->lock);
+ campoll(ci);
+ mutex_unlock(&ci->lock);
+
+ dev_info(&ci->i2c->dev, "read_data\n");
+ if (!ci->dr)
+ return 0;
+
+ mutex_lock(&ci->lock);
+ read_reg(ci, 0x0f, &msb);
+ read_reg(ci, 0x10, &lsb);
+ len = (msb<<8)|lsb;
+ read_block(ci, 0x12, ebuf, len);
+ ci->dr = 0;
+ mutex_unlock(&ci->lock);
+
+ return len;
+}
+
+static int write_data(struct dvb_ca_en50221 *ca, int slot, u8 *ebuf, int ecount)
+{
+ struct cxd *ci = ca->data;
+
+ mutex_lock(&ci->lock);
+ printk(kern_INFO "write_data %d\n", ecount);
+ write_reg(ci, 0x0d, ecount>>8);
+ write_reg(ci, 0x0e, ecount&0xff);
+ write_block(ci, 0x11, ebuf, ecount);
+ mutex_unlock(&ci->lock);
+ return ecount;
+}
+#endif
+
+static struct dvb_ca_en50221 en_templ = {
+ .read_attribute_mem = read_attribute_mem,
+ .write_attribute_mem = write_attribute_mem,
+ .read_cam_control = read_cam_control,
+ .write_cam_control = write_cam_control,
+ .slot_reset = slot_reset,
+ .slot_shutdown = slot_shutdown,
+ .slot_ts_enable = slot_ts_enable,
+ .poll_slot_status = poll_slot_status,
+#ifdef BUFFER_MODE
+ .read_data = read_data,
+ .write_data = write_data,
+#endif
+
+};
+
+struct dvb_ca_en50221 *cxd2099_attach(struct cxd2099_cfg *cfg,
+ void *priv,
+ struct i2c_adapter *i2c)
+{
+ struct cxd *ci;
+ u8 val;
+
+ if (i2c_read_reg(i2c, cfg->adr, 0, &val) < 0) {
+ dev_info(&i2c->dev, "No CXD2099 detected at %02x\n", cfg->adr);
+ return NULL;
+ }
+
+ ci = kzalloc(sizeof(struct cxd), GFP_KERNEL);
+ if (!ci)
+ return NULL;
+
+ mutex_init(&ci->lock);
+ ci->cfg = *cfg;
+ ci->i2c = i2c;
+ ci->lastaddress = 0xff;
+ ci->clk_reg_b = 0x4a;
+ ci->clk_reg_f = 0x1b;
+
+ ci->en = en_templ;
+ ci->en.data = ci;
+ init(ci);
+ dev_info(&i2c->dev, "Attached CXD2099AR at %02x\n", ci->cfg.adr);
+ return &ci->en;
+}
+EXPORT_SYMBOL(cxd2099_attach);
+
+MODULE_DESCRIPTION("cxd2099");
+MODULE_AUTHOR("Ralph Metzler");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/media/cxd2099/cxd2099.h b/drivers/staging/media/cxd2099/cxd2099.h
new file mode 100644
index 00000000000..0eb607c5b42
--- /dev/null
+++ b/drivers/staging/media/cxd2099/cxd2099.h
@@ -0,0 +1,51 @@
+/*
+ * cxd2099.h: Driver for the CXD2099AR Common Interface Controller
+ *
+ * Copyright (C) 2010-2011 Digital Devices GmbH
+ *
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 only, as published by the Free Software Foundation.
+ *
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA
+ * Or, point your browser to http://www.gnu.org/copyleft/gpl.html
+ */
+
+#ifndef _CXD2099_H_
+#define _CXD2099_H_
+
+#include <dvb_ca_en50221.h>
+
+struct cxd2099_cfg {
+ u32 bitrate;
+ u8 adr;
+ u8 polarity:1;
+ u8 clock_mode:1;
+};
+
+#if defined(CONFIG_DVB_CXD2099) || \
+ (defined(CONFIG_DVB_CXD2099_MODULE) && defined(MODULE))
+struct dvb_ca_en50221 *cxd2099_attach(struct cxd2099_cfg *cfg,
+ void *priv, struct i2c_adapter *i2c);
+#else
+
+static inline struct dvb_ca_en50221 *cxd2099_attach(struct cxd2099_cfg *cfg,
+ void *priv, struct i2c_adapter *i2c)
+{
+ dev_warn(&i2c->dev, "%s: driver disabled by Kconfig\n", __func__);
+ return NULL;
+}
+#endif
+
+#endif
diff --git a/drivers/staging/media/davinci_vpfe/Kconfig b/drivers/staging/media/davinci_vpfe/Kconfig
new file mode 100644
index 00000000000..12f321dd239
--- /dev/null
+++ b/drivers/staging/media/davinci_vpfe/Kconfig
@@ -0,0 +1,9 @@
+config VIDEO_DM365_VPFE
+ tristate "DM365 VPFE Media Controller Capture Driver"
+ depends on VIDEO_V4L2 && ARCH_DAVINCI_DM365 && !VIDEO_DM365_ISIF
+ select VIDEOBUF2_DMA_CONTIG
+ help
+ Support for DM365 VPFE based Media Controller Capture driver.
+
+ To compile this driver as a module, choose M here: the
+ module will be called vpfe-mc-capture.
diff --git a/drivers/staging/media/davinci_vpfe/Makefile b/drivers/staging/media/davinci_vpfe/Makefile
new file mode 100644
index 00000000000..c64515c644c
--- /dev/null
+++ b/drivers/staging/media/davinci_vpfe/Makefile
@@ -0,0 +1,3 @@
+obj-$(CONFIG_VIDEO_DM365_VPFE) += \
+ dm365_isif.o dm365_ipipe_hw.o dm365_ipipe.o \
+ dm365_resizer.o dm365_ipipeif.o vpfe_mc_capture.o vpfe_video.o
diff --git a/drivers/staging/media/davinci_vpfe/TODO b/drivers/staging/media/davinci_vpfe/TODO
new file mode 100644
index 00000000000..7015ab35ded
--- /dev/null
+++ b/drivers/staging/media/davinci_vpfe/TODO
@@ -0,0 +1,37 @@
+TODO (general):
+==================================
+
+- User space interface refinement
+ - Controls should be used when possible rather than private ioctl
+ - No enums should be used
+ - Use of MC and V4L2 subdev APIs when applicable
+ - Single interface header might suffice
+ - Current interface forces to configure everything at once
+- Get rid of the dm365_ipipe_hw.[ch] layer
+- Active external sub-devices defined by link configuration; no strcmp
+ needed
+- More generic platform data (i2c adapters)
+- The driver should have no knowledge of possible external subdevs; see
+ struct vpfe_subdev_id
+- Some of the hardware control should be refactorede
+- Check proper serialisation (through mutexes and spinlocks)
+- Names that are visible in kernel global namespace should have a common
+ prefix (or a few)
+- While replacing the older driver in media folder, provide a compatibility
+ layer and compatibility tests that warrants (using the libv4l's LD_PRELOAD
+ approach) there is no regression for the users using the older driver.
+
+Building of uImage and Applications:
+==================================
+
+As of now since the interface will undergo few changes all the include
+files are present in staging itself, to build for dm365 follow below steps,
+
+- copy vpfe.h from drivers/staging/media/davinci_vpfe/ to
+ include/media/davinci/ folder for building the uImage.
+- copy davinci_vpfe_user.h from drivers/staging/media/davinci_vpfe/ to
+ include/uapi/linux/davinci_vpfe.h, and add a entry in Kbuild (required
+ for building application).
+- copy dm365_ipipeif_user.h from drivers/staging/media/davinci_vpfe/ to
+ include/uapi/linux/dm365_ipipeif.h and a entry in Kbuild (required
+ for building application).
diff --git a/drivers/staging/media/davinci_vpfe/davinci-vpfe-mc.txt b/drivers/staging/media/davinci_vpfe/davinci-vpfe-mc.txt
new file mode 100644
index 00000000000..a1e91778aa9
--- /dev/null
+++ b/drivers/staging/media/davinci_vpfe/davinci-vpfe-mc.txt
@@ -0,0 +1,154 @@
+Davinci Video processing Front End (VPFE) driver
+
+Copyright (C) 2012 Texas Instruments Inc
+
+Contacts: Manjunath Hadli <manjunath.hadli@ti.com>
+ Prabhakar Lad <prabhakar.lad@ti.com>
+
+
+Introduction
+============
+
+This file documents the Texas Instruments Davinci Video processing Front End
+(VPFE) driver located under drivers/media/platform/davinci. The original driver
+exists for Davinci VPFE, which is now being changed to Media Controller
+Framework.
+
+Currently the driver has been successfully used on the following
+version of Davinci:
+
+ DM365/DM368
+
+The driver implements V4L2, Media controller and v4l2_subdev interfaces. Sensor,
+lens and flash drivers using the v4l2_subdev interface in the kernel are
+supported.
+
+
+Split to subdevs
+================
+
+The Davinci VPFE is split into V4L2 subdevs, each of the blocks inside the VPFE
+having one subdev to represent it. Each of the subdevs provide a V4L2 subdev
+interface to userspace.
+
+ DAVINCI ISIF
+ DAVINCI IPIPEIF
+ DAVINCI IPIPE
+ DAVINCI CROP RESIZER
+ DAVINCI RESIZER A
+ DAVINCI RESIZER B
+
+Each possible link in the VPFE is modelled by a link in the Media controller
+interface. For an example program see [1].
+
+
+ISIF, IPIPE, and RESIZER block IOCTLs
+======================================
+
+The Davinci Video processing Front End (VPFE) driver supports standard V4L2
+IOCTLs and controls where possible and practical. Much of the functions provided
+by the VPFE, however, does not fall under the standard IOCTL's.
+
+In general, there is a private ioctl for configuring each of the blocks
+containing hardware-dependent functions.
+
+The following private IOCTLs are supported:
+
+ VIDIOC_VPFE_ISIF_[S/G]_RAW_PARAMS
+ VIDIOC_VPFE_IPIPE_[S/G]_CONFIG
+ VIDIOC_VPFE_RSZ_[S/G]_CONFIG
+
+The parameter structures used by these ioctl's are described in
+include/uapi/linux/davinci_vpfe.h.
+
+The VIDIOC_VPFE_ISIF_S_RAW_PARAMS, VIDIOC_VPFE_IPIPE_S_CONFIG and
+VIDIOC_VPFE_RSZ_S_CONFIG are used to configure, enable and disable functions in
+the isif, ipipe and resizer blocks respectively. These IOCTL's control several
+functions in the blocks they control. VIDIOC_VPFE_ISIF_S_RAW_PARAMS IOCTL
+accepts a pointer to struct vpfe_isif_raw_config as its argument. Similarly
+VIDIOC_VPFE_IPIPE_S_CONFIG accepts a pointer to struct vpfe_ipipe_config. And
+VIDIOC_VPFE_RSZ_S_CONFIG accepts a pointer to struct vpfe_rsz_config as its
+argument. Similarly VIDIOC_VPFE_ISIF_G_RAW_PARAMS, VIDIOC_VPFE_IPIPE_G_CONFIG
+and VIDIOC_VPFE_RSZ_G_CONFIG are used to get the current configuration set in
+the isif, ipipe and resizer blocks respectively.
+
+The detailed functions of the VPFE itself related to a given VPFE block is
+described in the Technical Reference Manuals (TRMs) --- see the end of the
+document for those.
+
+
+IPIPEIF block IOCTLs
+======================================
+
+The following private IOCTLs are supported:
+
+ VIDIOC_VPFE_IPIPEIF_[S/G]_CONFIG
+
+The parameter structures used by these ioctl's are described in
+include/uapi/linux/dm365_ipipeif.h
+
+The VIDIOC_VPFE_IPIPEIF_S_CONFIG is used to configure the ipipeif
+hardware block. The VIDIOC_VPFE_IPIPEIF_S_CONFIG and
+VIDIOC_VPFE_IPIPEIF_G_CONFIG accepts a pointer to struct ipipeif_params
+as its argument.
+
+
+VPFE Operating Modes
+==========================================
+
+a: Continuous Modes
+------------------------
+
+1: tvp514x/tvp7002/mt9p031---> DAVINCI ISIF---> SDRAM
+
+2: tvp514x/tvp7002/mt9p031---> DAVINCI ISIF---> DAVINCI IPIPEIF--->|
+ |
+ <--------------------<----------------<---------------------<---|
+ |
+ V
+ DAVINCI CROP RESIZER--->DAVINCI RESIZER [A/B]---> SDRAM
+
+3: tvp514x/tvp7002/mt9p031---> DAVINCI ISIF---> DAVINCI IPIPEIF--->|
+ |
+ <--------------------<----------------<---------------------<---|
+ |
+ V
+ DAVINCI IPIPE---> DAVINCI CROP RESIZER--->DAVINCI RESIZER [A/B]---> SDRAM
+
+a: Single Shot Modes
+------------------------
+
+1: SDRAM---> DAVINCI IPIPEIF---> DAVINCI IPIPE---> DAVINCI CROP RESIZER--->|
+ |
+ <----------------<----------------<------------------<---------------<--|
+ |
+ V
+DAVINCI RESIZER [A/B]---> SDRAM
+
+2: SDRAM---> DAVINCI IPIPEIF---> DAVINCI CROP RESIZER--->|
+ |
+ <----------------<----------------<---------------<---|
+ |
+ V
+DAVINCI RESIZER [A/B]---> SDRAM
+
+
+Technical reference manuals (TRMs) and other documentation
+==========================================================
+
+Davinci DM365 TRM:
+<URL:http://www.ti.com/lit/ds/sprs457e/sprs457e.pdf>
+Referenced MARCH 2009-REVISED JUNE 2011
+
+Davinci DM368 TRM:
+<URL:http://www.ti.com/lit/ds/sprs668c/sprs668c.pdf>
+Referenced APRIL 2010-REVISED JUNE 2011
+
+Davinci Video Processing Front End (VPFE) DM36x
+<URL:http://www.ti.com/lit/ug/sprufg8c/sprufg8c.pdf>
+
+
+References
+==========
+
+[1] http://git.ideasonboard.org/?p=media-ctl.git;a=summary
diff --git a/drivers/staging/media/davinci_vpfe/davinci_vpfe_user.h b/drivers/staging/media/davinci_vpfe/davinci_vpfe_user.h
new file mode 100644
index 00000000000..7b7e7b26c1e
--- /dev/null
+++ b/drivers/staging/media/davinci_vpfe/davinci_vpfe_user.h
@@ -0,0 +1,1290 @@
+/*
+ * Copyright (C) 2012 Texas Instruments Inc
+ *
+ * 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 version 2.
+ *
+ * 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
+ *
+ * Contributors:
+ * Manjunath Hadli <manjunath.hadli@ti.com>
+ * Prabhakar Lad <prabhakar.lad@ti.com>
+ */
+
+#ifndef _DAVINCI_VPFE_USER_H
+#define _DAVINCI_VPFE_USER_H
+
+#include <linux/types.h>
+#include <linux/videodev2.h>
+
+/*
+ * Private IOCTL
+ *
+ * VIDIOC_VPFE_ISIF_S_RAW_PARAMS: Set raw params in isif
+ * VIDIOC_VPFE_ISIF_G_RAW_PARAMS: Get raw params from isif
+ * VIDIOC_VPFE_PRV_S_CONFIG: Set ipipe engine configuration
+ * VIDIOC_VPFE_PRV_G_CONFIG: Get ipipe engine configuration
+ * VIDIOC_VPFE_RSZ_S_CONFIG: Set resizer engine configuration
+ * VIDIOC_VPFE_RSZ_G_CONFIG: Get resizer engine configuration
+ */
+
+#define VIDIOC_VPFE_ISIF_S_RAW_PARAMS \
+ _IOW('V', BASE_VIDIOC_PRIVATE + 1, struct vpfe_isif_raw_config)
+#define VIDIOC_VPFE_ISIF_G_RAW_PARAMS \
+ _IOR('V', BASE_VIDIOC_PRIVATE + 2, struct vpfe_isif_raw_config)
+#define VIDIOC_VPFE_IPIPE_S_CONFIG \
+ _IOWR('P', BASE_VIDIOC_PRIVATE + 3, struct vpfe_ipipe_config)
+#define VIDIOC_VPFE_IPIPE_G_CONFIG \
+ _IOWR('P', BASE_VIDIOC_PRIVATE + 4, struct vpfe_ipipe_config)
+#define VIDIOC_VPFE_RSZ_S_CONFIG \
+ _IOWR('R', BASE_VIDIOC_PRIVATE + 5, struct vpfe_rsz_config)
+#define VIDIOC_VPFE_RSZ_G_CONFIG \
+ _IOWR('R', BASE_VIDIOC_PRIVATE + 6, struct vpfe_rsz_config)
+
+/*
+ * Private Control's for ISIF
+ */
+#define VPFE_ISIF_CID_CRGAIN (V4L2_CID_USER_BASE | 0xa001)
+#define VPFE_ISIF_CID_CGRGAIN (V4L2_CID_USER_BASE | 0xa002)
+#define VPFE_ISIF_CID_CGBGAIN (V4L2_CID_USER_BASE | 0xa003)
+#define VPFE_ISIF_CID_CBGAIN (V4L2_CID_USER_BASE | 0xa004)
+#define VPFE_ISIF_CID_GAIN_OFFSET (V4L2_CID_USER_BASE | 0xa005)
+
+/*
+ * Private Control's for ISIF and IPIPEIF
+ */
+#define VPFE_CID_DPCM_PREDICTOR (V4L2_CID_USER_BASE | 0xa006)
+
+/************************************************************************
+ * Vertical Defect Correction parameters
+ ***********************************************************************/
+
+/**
+ * vertical defect correction methods
+ */
+enum vpfe_isif_vdfc_corr_mode {
+ /* Defect level subtraction. Just fed through if saturating */
+ VPFE_ISIF_VDFC_NORMAL,
+ /**
+ * Defect level subtraction. Horizontal interpolation ((i-2)+(i+2))/2
+ * if data saturating
+ */
+ VPFE_ISIF_VDFC_HORZ_INTERPOL_IF_SAT,
+ /* Horizontal interpolation (((i-2)+(i+2))/2) */
+ VPFE_ISIF_VDFC_HORZ_INTERPOL
+};
+
+/**
+ * Max Size of the Vertical Defect Correction table
+ */
+#define VPFE_ISIF_VDFC_TABLE_SIZE 8
+
+/**
+ * Values used for shifting up the vdfc defect level
+ */
+enum vpfe_isif_vdfc_shift {
+ /* No Shift */
+ VPFE_ISIF_VDFC_NO_SHIFT,
+ /* Shift by 1 bit */
+ VPFE_ISIF_VDFC_SHIFT_1,
+ /* Shift by 2 bit */
+ VPFE_ISIF_VDFC_SHIFT_2,
+ /* Shift by 3 bit */
+ VPFE_ISIF_VDFC_SHIFT_3,
+ /* Shift by 4 bit */
+ VPFE_ISIF_VDFC_SHIFT_4
+};
+
+/**
+ * Defect Correction (DFC) table entry
+ */
+struct vpfe_isif_vdfc_entry {
+ /* vertical position of defect */
+ unsigned short pos_vert;
+ /* horizontal position of defect */
+ unsigned short pos_horz;
+ /**
+ * Defect level of Vertical line defect position. This is subtracted
+ * from the data at the defect position
+ */
+ unsigned char level_at_pos;
+ /**
+ * Defect level of the pixels upper than the vertical line defect.
+ * This is subtracted from the data
+ */
+ unsigned char level_up_pixels;
+ /**
+ * Defect level of the pixels lower than the vertical line defect.
+ * This is subtracted from the data
+ */
+ unsigned char level_low_pixels;
+};
+
+/**
+ * Structure for Defect Correction (DFC) parameter
+ */
+struct vpfe_isif_dfc {
+ /* enable vertical defect correction */
+ unsigned char en;
+ /* Correction methods */
+ enum vpfe_isif_vdfc_corr_mode corr_mode;
+ /**
+ * 0 - whole line corrected, 1 - not
+ * pixels upper than the defect
+ */
+ unsigned char corr_whole_line;
+ /**
+ * defect level shift value. level_at_pos, level_upper_pos,
+ * and level_lower_pos can be shifted up by this value
+ */
+ enum vpfe_isif_vdfc_shift def_level_shift;
+ /* defect saturation level */
+ unsigned short def_sat_level;
+ /* number of vertical defects. Max is VPFE_ISIF_VDFC_TABLE_SIZE */
+ short num_vdefects;
+ /* VDFC table ptr */
+ struct vpfe_isif_vdfc_entry table[VPFE_ISIF_VDFC_TABLE_SIZE];
+};
+
+/************************************************************************
+* Digital/Black clamp or DC Subtract parameters
+************************************************************************/
+/**
+ * Horizontal Black Clamp modes
+ */
+enum vpfe_isif_horz_bc_mode {
+ /**
+ * Horizontal clamp disabled. Only vertical clamp
+ * value is subtracted
+ */
+ VPFE_ISIF_HORZ_BC_DISABLE,
+ /**
+ * Horizontal clamp value is calculated and subtracted
+ * from image data along with vertical clamp value
+ */
+ VPFE_ISIF_HORZ_BC_CLAMP_CALC_ENABLED,
+ /**
+ * Horizontal clamp value calculated from previous image
+ * is subtracted from image data along with vertical clamp
+ * value. How the horizontal clamp value for the first image
+ * is calculated in this case ???
+ */
+ VPFE_ISIF_HORZ_BC_CLAMP_NOT_UPDATED
+};
+
+/**
+ * Base window selection for Horizontal Black Clamp calculations
+ */
+enum vpfe_isif_horz_bc_base_win_sel {
+ /* Select Most left window for bc calculation */
+ VPFE_ISIF_SEL_MOST_LEFT_WIN,
+
+ /* Select Most right window for bc calculation */
+ VPFE_ISIF_SEL_MOST_RIGHT_WIN,
+};
+
+/* Size of window in horizontal direction for horizontal bc */
+enum vpfe_isif_horz_bc_sz_h {
+ VPFE_ISIF_HORZ_BC_SZ_H_2PIXELS,
+ VPFE_ISIF_HORZ_BC_SZ_H_4PIXELS,
+ VPFE_ISIF_HORZ_BC_SZ_H_8PIXELS,
+ VPFE_ISIF_HORZ_BC_SZ_H_16PIXELS
+};
+
+/* Size of window in vertcal direction for vertical bc */
+enum vpfe_isif_horz_bc_sz_v {
+ VPFE_ISIF_HORZ_BC_SZ_H_32PIXELS,
+ VPFE_ISIF_HORZ_BC_SZ_H_64PIXELS,
+ VPFE_ISIF_HORZ_BC_SZ_H_128PIXELS,
+ VPFE_ISIF_HORZ_BC_SZ_H_256PIXELS
+};
+
+/**
+ * Structure for Horizontal Black Clamp config params
+ */
+struct vpfe_isif_horz_bclamp {
+ /* horizontal clamp mode */
+ enum vpfe_isif_horz_bc_mode mode;
+ /**
+ * pixel value limit enable.
+ * 0 - limit disabled
+ * 1 - pixel value limited to 1023
+ */
+ unsigned char clamp_pix_limit;
+ /**
+ * Select most left or right window for clamp val
+ * calculation
+ */
+ enum vpfe_isif_horz_bc_base_win_sel base_win_sel_calc;
+ /* Window count per color for calculation. range 1-32 */
+ unsigned char win_count_calc;
+ /* Window start position - horizontal for calculation. 0 - 8191 */
+ unsigned short win_start_h_calc;
+ /* Window start position - vertical for calculation 0 - 8191 */
+ unsigned short win_start_v_calc;
+ /* Width of the sample window in pixels for calculation */
+ enum vpfe_isif_horz_bc_sz_h win_h_sz_calc;
+ /* Height of the sample window in pixels for calculation */
+ enum vpfe_isif_horz_bc_sz_v win_v_sz_calc;
+};
+
+/**
+ * Black Clamp vertical reset values
+ */
+enum vpfe_isif_vert_bc_reset_val_sel {
+ /* Reset value used is the clamp value calculated */
+ VPFE_ISIF_VERT_BC_USE_HORZ_CLAMP_VAL,
+ /* Reset value used is reset_clamp_val configured */
+ VPFE_ISIF_VERT_BC_USE_CONFIG_CLAMP_VAL,
+ /* No update, previous image value is used */
+ VPFE_ISIF_VERT_BC_NO_UPDATE
+};
+
+enum vpfe_isif_vert_bc_sz_h {
+ VPFE_ISIF_VERT_BC_SZ_H_2PIXELS,
+ VPFE_ISIF_VERT_BC_SZ_H_4PIXELS,
+ VPFE_ISIF_VERT_BC_SZ_H_8PIXELS,
+ VPFE_ISIF_VERT_BC_SZ_H_16PIXELS,
+ VPFE_ISIF_VERT_BC_SZ_H_32PIXELS,
+ VPFE_ISIF_VERT_BC_SZ_H_64PIXELS
+};
+
+/**
+ * Structure for Vertical Black Clamp configuration params
+ */
+struct vpfe_isif_vert_bclamp {
+ /* Reset value selection for vertical clamp calculation */
+ enum vpfe_isif_vert_bc_reset_val_sel reset_val_sel;
+ /* U12 value if reset_sel = ISIF_BC_VERT_USE_CONFIG_CLAMP_VAL */
+ unsigned short reset_clamp_val;
+ /**
+ * U8Q8. Line average coefficient used in vertical clamp
+ * calculation
+ */
+ unsigned char line_ave_coef;
+ /* Width in pixels of the optical black region used for calculation. */
+ enum vpfe_isif_vert_bc_sz_h ob_h_sz_calc;
+ /* Height of the optical black region for calculation */
+ unsigned short ob_v_sz_calc;
+ /* Optical black region start position - horizontal. 0 - 8191 */
+ unsigned short ob_start_h;
+ /* Optical black region start position - vertical 0 - 8191 */
+ unsigned short ob_start_v;
+};
+
+/**
+ * Structure for Black Clamp configuration params
+ */
+struct vpfe_isif_black_clamp {
+ /**
+ * this offset value is added irrespective of the clamp
+ * enable status. S13
+ */
+ unsigned short dc_offset;
+ /**
+ * Enable black/digital clamp value to be subtracted
+ * from the image data
+ */
+ unsigned char en;
+ /**
+ * black clamp mode. same/separate clamp for 4 colors
+ * 0 - disable - same clamp value for all colors
+ * 1 - clamp value calculated separately for all colors
+ */
+ unsigned char bc_mode_color;
+ /* Vertical start position for bc subtraction */
+ unsigned short vert_start_sub;
+ /* Black clamp for horizontal direction */
+ struct vpfe_isif_horz_bclamp horz;
+ /* Black clamp for vertical direction */
+ struct vpfe_isif_vert_bclamp vert;
+};
+
+/*************************************************************************
+** Color Space Conversion (CSC)
+*************************************************************************/
+/**
+ * Number of Coefficient values used for CSC
+ */
+#define VPFE_ISIF_CSC_NUM_COEFF 16
+
+struct float_8_bit {
+ /* 8 bit integer part */
+ __u8 integer;
+ /* 8 bit decimal part */
+ __u8 decimal;
+};
+
+struct float_16_bit {
+ /* 16 bit integer part */
+ __u16 integer;
+ /* 16 bit decimal part */
+ __u16 decimal;
+};
+
+/*************************************************************************
+** Color Space Conversion parameters
+*************************************************************************/
+/**
+ * Structure used for CSC config params
+ */
+struct vpfe_isif_color_space_conv {
+ /* Enable color space conversion */
+ unsigned char en;
+ /**
+ * csc coefficient table. S8Q5, M00 at index 0, M01 at index 1, and
+ * so forth
+ */
+ struct float_8_bit coeff[VPFE_ISIF_CSC_NUM_COEFF];
+};
+
+enum vpfe_isif_datasft {
+ /* No Shift */
+ VPFE_ISIF_NO_SHIFT,
+ /* 1 bit Shift */
+ VPFE_ISIF_1BIT_SHIFT,
+ /* 2 bit Shift */
+ VPFE_ISIF_2BIT_SHIFT,
+ /* 3 bit Shift */
+ VPFE_ISIF_3BIT_SHIFT,
+ /* 4 bit Shift */
+ VPFE_ISIF_4BIT_SHIFT,
+ /* 5 bit Shift */
+ VPFE_ISIF_5BIT_SHIFT,
+ /* 6 bit Shift */
+ VPFE_ISIF_6BIT_SHIFT
+};
+
+#define VPFE_ISIF_LINEAR_TAB_SIZE 192
+/*************************************************************************
+** Linearization parameters
+*************************************************************************/
+/**
+ * Structure for Sensor data linearization
+ */
+struct vpfe_isif_linearize {
+ /* Enable or Disable linearization of data */
+ unsigned char en;
+ /* Shift value applied */
+ enum vpfe_isif_datasft corr_shft;
+ /* scale factor applied U11Q10 */
+ struct float_16_bit scale_fact;
+ /* Size of the linear table */
+ unsigned short table[VPFE_ISIF_LINEAR_TAB_SIZE];
+};
+
+/*************************************************************************
+** ISIF Raw configuration parameters
+*************************************************************************/
+enum vpfe_isif_fmt_mode {
+ VPFE_ISIF_SPLIT,
+ VPFE_ISIF_COMBINE
+};
+
+enum vpfe_isif_lnum {
+ VPFE_ISIF_1LINE,
+ VPFE_ISIF_2LINES,
+ VPFE_ISIF_3LINES,
+ VPFE_ISIF_4LINES
+};
+
+enum vpfe_isif_line {
+ VPFE_ISIF_1STLINE,
+ VPFE_ISIF_2NDLINE,
+ VPFE_ISIF_3RDLINE,
+ VPFE_ISIF_4THLINE
+};
+
+struct vpfe_isif_fmtplen {
+ /**
+ * number of program entries for SET0, range 1 - 16
+ * when fmtmode is ISIF_SPLIT, 1 - 8 when fmtmode is
+ * ISIF_COMBINE
+ */
+ unsigned short plen0;
+ /**
+ * number of program entries for SET1, range 1 - 16
+ * when fmtmode is ISIF_SPLIT, 1 - 8 when fmtmode is
+ * ISIF_COMBINE
+ */
+ unsigned short plen1;
+ /**
+ * number of program entries for SET2, range 1 - 16
+ * when fmtmode is ISIF_SPLIT, 1 - 8 when fmtmode is
+ * ISIF_COMBINE
+ */
+ unsigned short plen2;
+ /**
+ * number of program entries for SET3, range 1 - 16
+ * when fmtmode is ISIF_SPLIT, 1 - 8 when fmtmode is
+ * ISIF_COMBINE
+ */
+ unsigned short plen3;
+};
+
+struct vpfe_isif_fmt_cfg {
+ /* Split or combine or line alternate */
+ enum vpfe_isif_fmt_mode fmtmode;
+ /* enable or disable line alternating mode */
+ unsigned char ln_alter_en;
+ /* Split/combine line number */
+ enum vpfe_isif_lnum lnum;
+ /* Address increment Range 1 - 16 */
+ unsigned int addrinc;
+};
+
+struct vpfe_isif_fmt_addr_ptr {
+ /* Initial address */
+ unsigned int init_addr;
+ /* output line number */
+ enum vpfe_isif_line out_line;
+};
+
+struct vpfe_isif_fmtpgm_ap {
+ /* program address pointer */
+ unsigned char pgm_aptr;
+ /* program address increment or decrement */
+ unsigned char pgmupdt;
+};
+
+struct vpfe_isif_data_formatter {
+ /* Enable/Disable data formatter */
+ unsigned char en;
+ /* data formatter configuration */
+ struct vpfe_isif_fmt_cfg cfg;
+ /* Formatter program entries length */
+ struct vpfe_isif_fmtplen plen;
+ /* first pixel in a line fed to formatter */
+ unsigned short fmtrlen;
+ /* HD interval for output line. Only valid when split line */
+ unsigned short fmthcnt;
+ /* formatter address pointers */
+ struct vpfe_isif_fmt_addr_ptr fmtaddr_ptr[16];
+ /* program enable/disable */
+ unsigned char pgm_en[32];
+ /* program address pointers */
+ struct vpfe_isif_fmtpgm_ap fmtpgm_ap[32];
+};
+
+struct vpfe_isif_df_csc {
+ /* Color Space Conversion configuration, 0 - csc, 1 - df */
+ unsigned int df_or_csc;
+ /* csc configuration valid if df_or_csc is 0 */
+ struct vpfe_isif_color_space_conv csc;
+ /* data formatter configuration valid if df_or_csc is 1 */
+ struct vpfe_isif_data_formatter df;
+ /* start pixel in a line at the input */
+ unsigned int start_pix;
+ /* number of pixels in input line */
+ unsigned int num_pixels;
+ /* start line at the input */
+ unsigned int start_line;
+ /* number of lines at the input */
+ unsigned int num_lines;
+};
+
+struct vpfe_isif_gain_offsets_adj {
+ /* Enable or Disable Gain adjustment for SDRAM data */
+ unsigned char gain_sdram_en;
+ /* Enable or Disable Gain adjustment for IPIPE data */
+ unsigned char gain_ipipe_en;
+ /* Enable or Disable Gain adjustment for H3A data */
+ unsigned char gain_h3a_en;
+ /* Enable or Disable Gain adjustment for SDRAM data */
+ unsigned char offset_sdram_en;
+ /* Enable or Disable Gain adjustment for IPIPE data */
+ unsigned char offset_ipipe_en;
+ /* Enable or Disable Gain adjustment for H3A data */
+ unsigned char offset_h3a_en;
+};
+
+struct vpfe_isif_cul {
+ /* Horizontal Cull pattern for odd lines */
+ unsigned char hcpat_odd;
+ /* Horizontal Cull pattern for even lines */
+ unsigned char hcpat_even;
+ /* Vertical Cull pattern */
+ unsigned char vcpat;
+ /* Enable or disable lpf. Apply when cull is enabled */
+ unsigned char en_lpf;
+};
+
+/* all the stuff in this struct will be provided by userland */
+struct vpfe_isif_raw_config {
+ /* Linearization parameters for image sensor data input */
+ struct vpfe_isif_linearize linearize;
+ /* Data formatter or CSC */
+ struct vpfe_isif_df_csc df_csc;
+ /* Defect Pixel Correction (DFC) confguration */
+ struct vpfe_isif_dfc dfc;
+ /* Black/Digital Clamp configuration */
+ struct vpfe_isif_black_clamp bclamp;
+ /* Gain, offset adjustments */
+ struct vpfe_isif_gain_offsets_adj gain_offset;
+ /* Culling */
+ struct vpfe_isif_cul culling;
+ /* horizontal offset for Gain/LSC/DFC */
+ unsigned short horz_offset;
+ /* vertical offset for Gain/LSC/DFC */
+ unsigned short vert_offset;
+};
+
+/**********************************************************************
+ IPIPE API Structures
+**********************************************************************/
+
+/* IPIPE module configurations */
+
+/* IPIPE input configuration */
+#define VPFE_IPIPE_INPUT_CONFIG (1 << 0)
+/* LUT based Defect Pixel Correction */
+#define VPFE_IPIPE_LUTDPC (1 << 1)
+/* On the fly (OTF) Defect Pixel Correction */
+#define VPFE_IPIPE_OTFDPC (1 << 2)
+/* Noise Filter - 1 */
+#define VPFE_IPIPE_NF1 (1 << 3)
+/* Noise Filter - 2 */
+#define VPFE_IPIPE_NF2 (1 << 4)
+/* White Balance. Also a control ID */
+#define VPFE_IPIPE_WB (1 << 5)
+/* 1st RGB to RBG Blend module */
+#define VPFE_IPIPE_RGB2RGB_1 (1 << 6)
+/* 2nd RGB to RBG Blend module */
+#define VPFE_IPIPE_RGB2RGB_2 (1 << 7)
+/* Gamma Correction */
+#define VPFE_IPIPE_GAMMA (1 << 8)
+/* 3D LUT color conversion */
+#define VPFE_IPIPE_3D_LUT (1 << 9)
+/* RGB to YCbCr module */
+#define VPFE_IPIPE_RGB2YUV (1 << 10)
+/* YUV 422 conversion module */
+#define VPFE_IPIPE_YUV422_CONV (1 << 11)
+/* Edge Enhancement */
+#define VPFE_IPIPE_YEE (1 << 12)
+/* Green Imbalance Correction */
+#define VPFE_IPIPE_GIC (1 << 13)
+/* CFA Interpolation */
+#define VPFE_IPIPE_CFA (1 << 14)
+/* Chroma Artifact Reduction */
+#define VPFE_IPIPE_CAR (1 << 15)
+/* Chroma Gain Suppression */
+#define VPFE_IPIPE_CGS (1 << 16)
+/* Global brightness and contrast control */
+#define VPFE_IPIPE_GBCE (1 << 17)
+
+#define VPFE_IPIPE_MAX_MODULES 18
+
+struct ipipe_float_u16 {
+ unsigned short integer;
+ unsigned short decimal;
+};
+
+struct ipipe_float_s16 {
+ short integer;
+ unsigned short decimal;
+};
+
+struct ipipe_float_u8 {
+ unsigned char integer;
+ unsigned char decimal;
+};
+
+/* Copy method selection for vertical correction
+ * Used when ipipe_dfc_corr_meth is IPIPE_DPC_CTORB_AFTER_HINT
+ */
+enum vpfe_ipipe_dpc_corr_meth {
+ /* replace by black or white dot specified by repl_white */
+ VPFE_IPIPE_DPC_REPL_BY_DOT = 0,
+ /* Copy from left */
+ VPFE_IPIPE_DPC_CL = 1,
+ /* Copy from right */
+ VPFE_IPIPE_DPC_CR = 2,
+ /* Horizontal interpolation */
+ VPFE_IPIPE_DPC_H_INTP = 3,
+ /* Vertical interpolation */
+ VPFE_IPIPE_DPC_V_INTP = 4,
+ /* Copy from top */
+ VPFE_IPIPE_DPC_CT = 5,
+ /* Copy from bottom */
+ VPFE_IPIPE_DPC_CB = 6,
+ /* 2D interpolation */
+ VPFE_IPIPE_DPC_2D_INTP = 7,
+};
+
+struct vpfe_ipipe_lutdpc_entry {
+ /* Horizontal position */
+ unsigned short horz_pos;
+ /* vertical position */
+ unsigned short vert_pos;
+ enum vpfe_ipipe_dpc_corr_meth method;
+};
+
+#define VPFE_IPIPE_MAX_SIZE_DPC 256
+
+/* Structure for configuring DPC module */
+struct vpfe_ipipe_lutdpc {
+ /* 0 - disable, 1 - enable */
+ unsigned char en;
+ /* 0 - replace with black dot, 1 - white dot when correction
+ * method is IPIPE_DFC_REPL_BY_DOT=0,
+ */
+ unsigned char repl_white;
+ /* number of entries in the correction table. Currently only
+ * support up-to 256 entries. infinite mode is not supported
+ */
+ unsigned short dpc_size;
+ struct vpfe_ipipe_lutdpc_entry table[VPFE_IPIPE_MAX_SIZE_DPC];
+};
+
+enum vpfe_ipipe_otfdpc_det_meth {
+ VPFE_IPIPE_DPC_OTF_MIN_MAX,
+ VPFE_IPIPE_DPC_OTF_MIN_MAX2
+};
+
+struct vpfe_ipipe_otfdpc_thr {
+ unsigned short r;
+ unsigned short gr;
+ unsigned short gb;
+ unsigned short b;
+};
+
+enum vpfe_ipipe_otfdpc_alg {
+ VPFE_IPIPE_OTFDPC_2_0,
+ VPFE_IPIPE_OTFDPC_3_0
+};
+
+struct vpfe_ipipe_otfdpc_2_0_cfg {
+ /* defect detection threshold for MIN_MAX2 method (DPC 2.0 alg) */
+ struct vpfe_ipipe_otfdpc_thr det_thr;
+ /* defect correction threshold for MIN_MAX2 method (DPC 2.0 alg) or
+ * maximum value for MIN_MAX method
+ */
+ struct vpfe_ipipe_otfdpc_thr corr_thr;
+};
+
+struct vpfe_ipipe_otfdpc_3_0_cfg {
+ /* DPC3.0 activity adj shf. activity = (max2-min2) >> (6 -shf)
+ */
+ unsigned char act_adj_shf;
+ /* DPC3.0 detection threshold, THR */
+ unsigned short det_thr;
+ /* DPC3.0 detection threshold slope, SLP */
+ unsigned short det_slp;
+ /* DPC3.0 detection threshold min, MIN */
+ unsigned short det_thr_min;
+ /* DPC3.0 detection threshold max, MAX */
+ unsigned short det_thr_max;
+ /* DPC3.0 correction threshold, THR */
+ unsigned short corr_thr;
+ /* DPC3.0 correction threshold slope, SLP */
+ unsigned short corr_slp;
+ /* DPC3.0 correction threshold min, MIN */
+ unsigned short corr_thr_min;
+ /* DPC3.0 correction threshold max, MAX */
+ unsigned short corr_thr_max;
+};
+
+struct vpfe_ipipe_otfdpc {
+ /* 0 - disable, 1 - enable */
+ unsigned char en;
+ /* defect detection method */
+ enum vpfe_ipipe_otfdpc_det_meth det_method;
+ /* Algorithm used. Applicable only when IPIPE_DPC_OTF_MIN_MAX2 is
+ * used
+ */
+ enum vpfe_ipipe_otfdpc_alg alg;
+ union {
+ /* if alg is IPIPE_OTFDPC_2_0 */
+ struct vpfe_ipipe_otfdpc_2_0_cfg dpc_2_0;
+ /* if alg is IPIPE_OTFDPC_3_0 */
+ struct vpfe_ipipe_otfdpc_3_0_cfg dpc_3_0;
+ } alg_cfg;
+};
+
+/* Threshold values table size */
+#define VPFE_IPIPE_NF_THR_TABLE_SIZE 8
+/* Intensity values table size */
+#define VPFE_IPIPE_NF_STR_TABLE_SIZE 8
+
+/* NF, sampling method for green pixels */
+enum vpfe_ipipe_nf_sampl_meth {
+ /* Same as R or B */
+ VPFE_IPIPE_NF_BOX,
+ /* Diamond mode */
+ VPFE_IPIPE_NF_DIAMOND
+};
+
+/* Structure for configuring NF module */
+struct vpfe_ipipe_nf {
+ /* 0 - disable, 1 - enable */
+ unsigned char en;
+ /* Sampling method for green pixels */
+ enum vpfe_ipipe_nf_sampl_meth gr_sample_meth;
+ /* Down shift value in LUT reference address
+ */
+ unsigned char shft_val;
+ /* Spread value in NF algorithm
+ */
+ unsigned char spread_val;
+ /* Apply LSC gain to threshold. Enable this only if
+ * LSC is enabled in ISIF
+ */
+ unsigned char apply_lsc_gain;
+ /* Threshold values table */
+ unsigned short thr[VPFE_IPIPE_NF_THR_TABLE_SIZE];
+ /* intensity values table */
+ unsigned char str[VPFE_IPIPE_NF_STR_TABLE_SIZE];
+ /* Edge detection minimum threshold */
+ unsigned short edge_det_min_thr;
+ /* Edge detection maximum threshold */
+ unsigned short edge_det_max_thr;
+};
+
+enum vpfe_ipipe_gic_alg {
+ VPFE_IPIPE_GIC_ALG_CONST_GAIN,
+ VPFE_IPIPE_GIC_ALG_ADAPT_GAIN
+};
+
+enum vpfe_ipipe_gic_thr_sel {
+ VPFE_IPIPE_GIC_THR_REG,
+ VPFE_IPIPE_GIC_THR_NF
+};
+
+enum vpfe_ipipe_gic_wt_fn_type {
+ /* Use difference as index */
+ VPFE_IPIPE_GIC_WT_FN_TYP_DIF,
+ /* Use weight function as index */
+ VPFE_IPIPE_GIC_WT_FN_TYP_HP_VAL
+};
+
+/* structure for Green Imbalance Correction */
+struct vpfe_ipipe_gic {
+ /* 0 - disable, 1 - enable */
+ unsigned char en;
+ /* 0 - Constant gain , 1 - Adaptive gain algorithm */
+ enum vpfe_ipipe_gic_alg gic_alg;
+ /* GIC gain or weight. Used for Constant gain and Adaptive algorithms
+ */
+ unsigned short gain;
+ /* Threshold selection. GIC register values or NF2 thr table */
+ enum vpfe_ipipe_gic_thr_sel thr_sel;
+ /* thr1. Used when thr_sel is IPIPE_GIC_THR_REG */
+ unsigned short thr;
+ /* this value is used for thr2-thr1, thr3-thr2 or
+ * thr4-thr3 when wt_fn_type is index. Otherwise it
+ * is the
+ */
+ unsigned short slope;
+ /* Apply LSC gain to threshold. Enable this only if
+ * LSC is enabled in ISIF & thr_sel is IPIPE_GIC_THR_REG
+ */
+ unsigned char apply_lsc_gain;
+ /* Multiply Nf2 threshold by this gain. Use this when thr_sel
+ * is IPIPE_GIC_THR_NF
+ */
+ struct ipipe_float_u8 nf2_thr_gain;
+ /* Weight function uses difference as index or high pass value.
+ * Used for adaptive gain algorithm
+ */
+ enum vpfe_ipipe_gic_wt_fn_type wt_fn_type;
+};
+
+/* Structure for configuring WB module */
+struct vpfe_ipipe_wb {
+ /* Offset (S12) for R */
+ short ofst_r;
+ /* Offset (S12) for Gr */
+ short ofst_gr;
+ /* Offset (S12) for Gb */
+ short ofst_gb;
+ /* Offset (S12) for B */
+ short ofst_b;
+ /* Gain (U13Q9) for Red */
+ struct ipipe_float_u16 gain_r;
+ /* Gain (U13Q9) for Gr */
+ struct ipipe_float_u16 gain_gr;
+ /* Gain (U13Q9) for Gb */
+ struct ipipe_float_u16 gain_gb;
+ /* Gain (U13Q9) for Blue */
+ struct ipipe_float_u16 gain_b;
+};
+
+enum vpfe_ipipe_cfa_alg {
+ /* Algorithm is 2DirAC */
+ VPFE_IPIPE_CFA_ALG_2DIRAC,
+ /* Algorithm is 2DirAC + Digital Antialiasing (DAA) */
+ VPFE_IPIPE_CFA_ALG_2DIRAC_DAA,
+ /* Algorithm is DAA */
+ VPFE_IPIPE_CFA_ALG_DAA
+};
+
+/* Structure for CFA Interpolation */
+struct vpfe_ipipe_cfa {
+ /* 2DirAC or 2DirAC + DAA */
+ enum vpfe_ipipe_cfa_alg alg;
+ /* 2Dir CFA HP value Low Threshold */
+ unsigned short hpf_thr_2dir;
+ /* 2Dir CFA HP value slope */
+ unsigned short hpf_slp_2dir;
+ /* 2Dir CFA HP mix threshold */
+ unsigned short hp_mix_thr_2dir;
+ /* 2Dir CFA HP mix slope */
+ unsigned short hp_mix_slope_2dir;
+ /* 2Dir Direction threshold */
+ unsigned short dir_thr_2dir;
+ /* 2Dir Direction slope */
+ unsigned short dir_slope_2dir;
+ /* 2Dir Non Directional Weight */
+ unsigned short nd_wt_2dir;
+ /* DAA Mono Hue Fraction */
+ unsigned short hue_fract_daa;
+ /* DAA Mono Edge threshold */
+ unsigned short edge_thr_daa;
+ /* DAA Mono threshold minimum */
+ unsigned short thr_min_daa;
+ /* DAA Mono threshold slope */
+ unsigned short thr_slope_daa;
+ /* DAA Mono slope minimum */
+ unsigned short slope_min_daa;
+ /* DAA Mono slope slope */
+ unsigned short slope_slope_daa;
+ /* DAA Mono LP wight */
+ unsigned short lp_wt_daa;
+};
+
+/* Struct for configuring RGB2RGB blending module */
+struct vpfe_ipipe_rgb2rgb {
+ /* Matrix coefficient for RR S12Q8 for ID = 1 and S11Q8 for ID = 2 */
+ struct ipipe_float_s16 coef_rr;
+ /* Matrix coefficient for GR S12Q8/S11Q8 */
+ struct ipipe_float_s16 coef_gr;
+ /* Matrix coefficient for BR S12Q8/S11Q8 */
+ struct ipipe_float_s16 coef_br;
+ /* Matrix coefficient for RG S12Q8/S11Q8 */
+ struct ipipe_float_s16 coef_rg;
+ /* Matrix coefficient for GG S12Q8/S11Q8 */
+ struct ipipe_float_s16 coef_gg;
+ /* Matrix coefficient for BG S12Q8/S11Q8 */
+ struct ipipe_float_s16 coef_bg;
+ /* Matrix coefficient for RB S12Q8/S11Q8 */
+ struct ipipe_float_s16 coef_rb;
+ /* Matrix coefficient for GB S12Q8/S11Q8 */
+ struct ipipe_float_s16 coef_gb;
+ /* Matrix coefficient for BB S12Q8/S11Q8 */
+ struct ipipe_float_s16 coef_bb;
+ /* Output offset for R S13/S11 */
+ int out_ofst_r;
+ /* Output offset for G S13/S11 */
+ int out_ofst_g;
+ /* Output offset for B S13/S11 */
+ int out_ofst_b;
+};
+
+#define VPFE_IPIPE_MAX_SIZE_GAMMA 512
+
+enum vpfe_ipipe_gamma_tbl_size {
+ VPFE_IPIPE_GAMMA_TBL_SZ_64 = 64,
+ VPFE_IPIPE_GAMMA_TBL_SZ_128 = 128,
+ VPFE_IPIPE_GAMMA_TBL_SZ_256 = 256,
+ VPFE_IPIPE_GAMMA_TBL_SZ_512 = 512,
+};
+
+enum vpfe_ipipe_gamma_tbl_sel {
+ VPFE_IPIPE_GAMMA_TBL_RAM = 0,
+ VPFE_IPIPE_GAMMA_TBL_ROM = 1,
+};
+
+struct vpfe_ipipe_gamma_entry {
+ /* 10 bit slope */
+ short slope;
+ /* 10 bit offset */
+ unsigned short offset;
+};
+
+/* Structure for configuring Gamma correction module */
+struct vpfe_ipipe_gamma {
+ /* 0 - Enable Gamma correction for Red
+ * 1 - bypass Gamma correction. Data is divided by 16
+ */
+ unsigned char bypass_r;
+ /* 0 - Enable Gamma correction for Blue
+ * 1 - bypass Gamma correction. Data is divided by 16
+ */
+ unsigned char bypass_b;
+ /* 0 - Enable Gamma correction for Green
+ * 1 - bypass Gamma correction. Data is divided by 16
+ */
+ unsigned char bypass_g;
+ /* IPIPE_GAMMA_TBL_RAM or IPIPE_GAMMA_TBL_ROM */
+ enum vpfe_ipipe_gamma_tbl_sel tbl_sel;
+ /* Table size for RAM gamma table.
+ */
+ enum vpfe_ipipe_gamma_tbl_size tbl_size;
+ /* R table */
+ struct vpfe_ipipe_gamma_entry table_r[VPFE_IPIPE_MAX_SIZE_GAMMA];
+ /* Blue table */
+ struct vpfe_ipipe_gamma_entry table_b[VPFE_IPIPE_MAX_SIZE_GAMMA];
+ /* Green table */
+ struct vpfe_ipipe_gamma_entry table_g[VPFE_IPIPE_MAX_SIZE_GAMMA];
+};
+
+#define VPFE_IPIPE_MAX_SIZE_3D_LUT 729
+
+struct vpfe_ipipe_3d_lut_entry {
+ /* 10 bit entry for red */
+ unsigned short r;
+ /* 10 bit entry for green */
+ unsigned short g;
+ /* 10 bit entry for blue */
+ unsigned short b;
+};
+
+/* structure for 3D-LUT */
+struct vpfe_ipipe_3d_lut {
+ /* enable/disable 3D lut */
+ unsigned char en;
+ /* 3D - LUT table entry */
+ struct vpfe_ipipe_3d_lut_entry table[VPFE_IPIPE_MAX_SIZE_3D_LUT];
+};
+
+/* Struct for configuring rgb2ycbcr module */
+struct vpfe_ipipe_rgb2yuv {
+ /* Matrix coefficient for RY S12Q8 */
+ struct ipipe_float_s16 coef_ry;
+ /* Matrix coefficient for GY S12Q8 */
+ struct ipipe_float_s16 coef_gy;
+ /* Matrix coefficient for BY S12Q8 */
+ struct ipipe_float_s16 coef_by;
+ /* Matrix coefficient for RCb S12Q8 */
+ struct ipipe_float_s16 coef_rcb;
+ /* Matrix coefficient for GCb S12Q8 */
+ struct ipipe_float_s16 coef_gcb;
+ /* Matrix coefficient for BCb S12Q8 */
+ struct ipipe_float_s16 coef_bcb;
+ /* Matrix coefficient for RCr S12Q8 */
+ struct ipipe_float_s16 coef_rcr;
+ /* Matrix coefficient for GCr S12Q8 */
+ struct ipipe_float_s16 coef_gcr;
+ /* Matrix coefficient for BCr S12Q8 */
+ struct ipipe_float_s16 coef_bcr;
+ /* Output offset for R S11 */
+ int out_ofst_y;
+ /* Output offset for Cb S11 */
+ int out_ofst_cb;
+ /* Output offset for Cr S11 */
+ int out_ofst_cr;
+};
+
+enum vpfe_ipipe_gbce_type {
+ VPFE_IPIPE_GBCE_Y_VAL_TBL = 0,
+ VPFE_IPIPE_GBCE_GAIN_TBL = 1,
+};
+
+#define VPFE_IPIPE_MAX_SIZE_GBCE_LUT 1024
+
+/* structure for Global brightness and Contrast */
+struct vpfe_ipipe_gbce {
+ /* enable/disable GBCE */
+ unsigned char en;
+ /* Y - value table or Gain table */
+ enum vpfe_ipipe_gbce_type type;
+ /* ptr to LUT for GBCE with 1024 entries */
+ unsigned short table[VPFE_IPIPE_MAX_SIZE_GBCE_LUT];
+};
+
+/* Chrominance position. Applicable only for YCbCr input
+ * Applied after edge enhancement
+ */
+enum vpfe_chr_pos {
+ /* Co-siting, same position with luminance */
+ VPFE_IPIPE_YUV422_CHR_POS_COSITE = 0,
+ /* Centering, In the middle of luminance */
+ VPFE_IPIPE_YUV422_CHR_POS_CENTRE = 1,
+};
+
+/* Structure for configuring yuv422 conversion module */
+struct vpfe_ipipe_yuv422_conv {
+ /* Max Chrominance value */
+ unsigned char en_chrom_lpf;
+ /* 1 - enable LPF for chrminance, 0 - disable */
+ enum vpfe_chr_pos chrom_pos;
+};
+
+#define VPFE_IPIPE_MAX_SIZE_YEE_LUT 1024
+
+enum vpfe_ipipe_yee_merge_meth {
+ VPFE_IPIPE_YEE_ABS_MAX = 0,
+ VPFE_IPIPE_YEE_EE_ES = 1,
+};
+
+/* Structure for configuring YUV Edge Enhancement module */
+struct vpfe_ipipe_yee {
+ /* 1 - enable enhancement, 0 - disable */
+ unsigned char en;
+ /* enable/disable halo reduction in edge sharpner */
+ unsigned char en_halo_red;
+ /* Merge method between Edge Enhancer and Edge sharpner */
+ enum vpfe_ipipe_yee_merge_meth merge_meth;
+ /* HPF Shift length */
+ unsigned char hpf_shft;
+ /* HPF Coefficient 00, S10 */
+ short hpf_coef_00;
+ /* HPF Coefficient 01, S10 */
+ short hpf_coef_01;
+ /* HPF Coefficient 02, S10 */
+ short hpf_coef_02;
+ /* HPF Coefficient 10, S10 */
+ short hpf_coef_10;
+ /* HPF Coefficient 11, S10 */
+ short hpf_coef_11;
+ /* HPF Coefficient 12, S10 */
+ short hpf_coef_12;
+ /* HPF Coefficient 20, S10 */
+ short hpf_coef_20;
+ /* HPF Coefficient 21, S10 */
+ short hpf_coef_21;
+ /* HPF Coefficient 22, S10 */
+ short hpf_coef_22;
+ /* Lower threshold before referring to LUT */
+ unsigned short yee_thr;
+ /* Edge sharpener Gain */
+ unsigned short es_gain;
+ /* Edge sharpener lower threshold */
+ unsigned short es_thr1;
+ /* Edge sharpener upper threshold */
+ unsigned short es_thr2;
+ /* Edge sharpener gain on gradient */
+ unsigned short es_gain_grad;
+ /* Edge sharpener offset on gradient */
+ unsigned short es_ofst_grad;
+ /* Ptr to EE table. Must have 1024 entries */
+ short table[VPFE_IPIPE_MAX_SIZE_YEE_LUT];
+};
+
+enum vpfe_ipipe_car_meth {
+ /* Chromatic Gain Control */
+ VPFE_IPIPE_CAR_CHR_GAIN_CTRL = 0,
+ /* Dynamic switching between CHR_GAIN_CTRL
+ * and MED_FLTR
+ */
+ VPFE_IPIPE_CAR_DYN_SWITCH = 1,
+ /* Median Filter */
+ VPFE_IPIPE_CAR_MED_FLTR = 2,
+};
+
+enum vpfe_ipipe_car_hpf_type {
+ VPFE_IPIPE_CAR_HPF_Y = 0,
+ VPFE_IPIPE_CAR_HPF_H = 1,
+ VPFE_IPIPE_CAR_HPF_V = 2,
+ VPFE_IPIPE_CAR_HPF_2D = 3,
+ /* 2D HPF from YUV Edge Enhancement */
+ VPFE_IPIPE_CAR_HPF_2D_YEE = 4,
+};
+
+struct vpfe_ipipe_car_gain {
+ /* csup_gain */
+ unsigned char gain;
+ /* csup_shf. */
+ unsigned char shft;
+ /* gain minimum */
+ unsigned short gain_min;
+};
+
+/* Structure for Chromatic Artifact Reduction */
+struct vpfe_ipipe_car {
+ /* enable/disable */
+ unsigned char en;
+ /* Gain control or Dynamic switching */
+ enum vpfe_ipipe_car_meth meth;
+ /* Gain1 function configuration for Gain control */
+ struct vpfe_ipipe_car_gain gain1;
+ /* Gain2 function configuration for Gain control */
+ struct vpfe_ipipe_car_gain gain2;
+ /* HPF type used for CAR */
+ enum vpfe_ipipe_car_hpf_type hpf;
+ /* csup_thr: HPF threshold for Gain control */
+ unsigned char hpf_thr;
+ /* Down shift value for hpf. 2 bits */
+ unsigned char hpf_shft;
+ /* switch limit for median filter */
+ unsigned char sw0;
+ /* switch coefficient for Gain control */
+ unsigned char sw1;
+};
+
+/* structure for Chromatic Gain Suppression */
+struct vpfe_ipipe_cgs {
+ /* enable/disable */
+ unsigned char en;
+ /* gain1 bright side threshold */
+ unsigned char h_thr;
+ /* gain1 bright side slope */
+ unsigned char h_slope;
+ /* gain1 down shift value for bright side */
+ unsigned char h_shft;
+ /* gain1 bright side minimum gain */
+ unsigned char h_min;
+};
+
+/* Max pixels allowed in the input. If above this either decimation
+ * or frame division mode to be enabled
+ */
+#define VPFE_IPIPE_MAX_INPUT_WIDTH 2600
+
+struct vpfe_ipipe_input_config {
+ unsigned int vst;
+ unsigned int hst;
+};
+
+/**
+ * struct vpfe_ipipe_config - IPIPE engine configuration (user)
+ * @input_config: Pointer to structure for ipipe configuration.
+ * @flag: Specifies which ISP IPIPE functions should be enabled.
+ * @lutdpc: Pointer to luma enhancement structure.
+ * @otfdpc: Pointer to structure for defect correction.
+ * @nf1: Pointer to structure for Noise Filter.
+ * @nf2: Pointer to structure for Noise Filter.
+ * @gic: Pointer to structure for Green Imbalance.
+ * @wbal: Pointer to structure for White Balance.
+ * @cfa: Pointer to structure containing the CFA interpolation.
+ * @rgb2rgb1: Pointer to structure for RGB to RGB Blending.
+ * @rgb2rgb2: Pointer to structure for RGB to RGB Blending.
+ * @gamma: Pointer to gamma structure.
+ * @lut: Pointer to structure for 3D LUT.
+ * @rgb2yuv: Pointer to structure for RGB-YCbCr conversion.
+ * @gbce: Pointer to structure for Global Brightness,Contrast Control.
+ * @yuv422_conv: Pointer to structure for YUV 422 conversion.
+ * @yee: Pointer to structure for Edge Enhancer.
+ * @car: Pointer to structure for Chromatic Artifact Reduction.
+ * @cgs: Pointer to structure for Chromatic Gain Suppression.
+ */
+struct vpfe_ipipe_config {
+ __u32 flag;
+ struct vpfe_ipipe_input_config __user *input_config;
+ struct vpfe_ipipe_lutdpc __user *lutdpc;
+ struct vpfe_ipipe_otfdpc __user *otfdpc;
+ struct vpfe_ipipe_nf __user *nf1;
+ struct vpfe_ipipe_nf __user *nf2;
+ struct vpfe_ipipe_gic __user *gic;
+ struct vpfe_ipipe_wb __user *wbal;
+ struct vpfe_ipipe_cfa __user *cfa;
+ struct vpfe_ipipe_rgb2rgb __user *rgb2rgb1;
+ struct vpfe_ipipe_rgb2rgb __user *rgb2rgb2;
+ struct vpfe_ipipe_gamma __user *gamma;
+ struct vpfe_ipipe_3d_lut __user *lut;
+ struct vpfe_ipipe_rgb2yuv __user *rgb2yuv;
+ struct vpfe_ipipe_gbce __user *gbce;
+ struct vpfe_ipipe_yuv422_conv __user *yuv422_conv;
+ struct vpfe_ipipe_yee __user *yee;
+ struct vpfe_ipipe_car __user *car;
+ struct vpfe_ipipe_cgs __user *cgs;
+};
+
+/*******************************************************************
+** Resizer API structures
+*******************************************************************/
+/* Interpolation types used for horizontal rescale */
+enum vpfe_rsz_intp_t {
+ VPFE_RSZ_INTP_CUBIC,
+ VPFE_RSZ_INTP_LINEAR
+};
+
+/* Horizontal LPF intensity selection */
+enum vpfe_rsz_h_lpf_lse_t {
+ VPFE_RSZ_H_LPF_LSE_INTERN,
+ VPFE_RSZ_H_LPF_LSE_USER_VAL
+};
+
+enum vpfe_rsz_down_scale_ave_sz {
+ VPFE_IPIPE_DWN_SCALE_1_OVER_2,
+ VPFE_IPIPE_DWN_SCALE_1_OVER_4,
+ VPFE_IPIPE_DWN_SCALE_1_OVER_8,
+ VPFE_IPIPE_DWN_SCALE_1_OVER_16,
+ VPFE_IPIPE_DWN_SCALE_1_OVER_32,
+ VPFE_IPIPE_DWN_SCALE_1_OVER_64,
+ VPFE_IPIPE_DWN_SCALE_1_OVER_128,
+ VPFE_IPIPE_DWN_SCALE_1_OVER_256
+};
+
+struct vpfe_rsz_output_spec {
+ /* enable horizontal flip */
+ unsigned char h_flip;
+ /* enable vertical flip */
+ unsigned char v_flip;
+ /* line start offset for y. */
+ unsigned int vst_y;
+ /* line start offset for c. Only for 420 */
+ unsigned int vst_c;
+ /* vertical rescale interpolation type, YCbCr or Luminance */
+ enum vpfe_rsz_intp_t v_typ_y;
+ /* vertical rescale interpolation type for Chrominance */
+ enum vpfe_rsz_intp_t v_typ_c;
+ /* vertical lpf intensity - Luminance */
+ unsigned char v_lpf_int_y;
+ /* vertical lpf intensity - Chrominance */
+ unsigned char v_lpf_int_c;
+ /* horizontal rescale interpolation types, YCbCr or Luminance */
+ enum vpfe_rsz_intp_t h_typ_y;
+ /* horizontal rescale interpolation types, Chrominance */
+ enum vpfe_rsz_intp_t h_typ_c;
+ /* horizontal lpf intensity - Luminance */
+ unsigned char h_lpf_int_y;
+ /* horizontal lpf intensity - Chrominance */
+ unsigned char h_lpf_int_c;
+ /* Use down scale mode for scale down */
+ unsigned char en_down_scale;
+ /* if downscale, set the downscale more average size for horizontal
+ * direction. Used only if output width and height is less than
+ * input sizes
+ */
+ enum vpfe_rsz_down_scale_ave_sz h_dscale_ave_sz;
+ /* if downscale, set the downscale more average size for vertical
+ * direction. Used only if output width and height is less than
+ * input sizes
+ */
+ enum vpfe_rsz_down_scale_ave_sz v_dscale_ave_sz;
+ /* Y offset. If set, the offset would be added to the base address
+ */
+ unsigned int user_y_ofst;
+ /* C offset. If set, the offset would be added to the base address
+ */
+ unsigned int user_c_ofst;
+};
+
+struct vpfe_rsz_config_params {
+ unsigned int vst;
+ /* horizontal start position of the image
+ * data to IPIPE
+ */
+ unsigned int hst;
+ /* output spec of the image data coming out of resizer - 0(UYVY).
+ */
+ struct vpfe_rsz_output_spec output1;
+ /* output spec of the image data coming out of resizer - 1(UYVY).
+ */
+ struct vpfe_rsz_output_spec output2;
+ /* 0 , chroma sample at odd pixel, 1 - even pixel */
+ unsigned char chroma_sample_even;
+ unsigned char frame_div_mode_en;
+ unsigned char yuv_y_min;
+ unsigned char yuv_y_max;
+ unsigned char yuv_c_min;
+ unsigned char yuv_c_max;
+ enum vpfe_chr_pos out_chr_pos;
+ unsigned char bypass;
+};
+
+/* Structure for VIDIOC_VPFE_RSZ_[S/G]_CONFIG IOCTLs */
+struct vpfe_rsz_config {
+ struct vpfe_rsz_config_params *config;
+};
+
+#endif /* _DAVINCI_VPFE_USER_H */
diff --git a/drivers/staging/media/davinci_vpfe/dm365_ipipe.c b/drivers/staging/media/davinci_vpfe/dm365_ipipe.c
new file mode 100644
index 00000000000..b7044a380fe
--- /dev/null
+++ b/drivers/staging/media/davinci_vpfe/dm365_ipipe.c
@@ -0,0 +1,1863 @@
+/*
+ * Copyright (C) 2012 Texas Instruments Inc
+ *
+ * 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 version 2.
+ *
+ * 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
+ *
+ * Contributors:
+ * Manjunath Hadli <manjunath.hadli@ti.com>
+ * Prabhakar Lad <prabhakar.lad@ti.com>
+ *
+ *
+ * IPIPE allows fine tuning of the input image using different
+ * tuning modules in IPIPE. Some examples :- Noise filter, Defect
+ * pixel correction etc. It essentially operate on Bayer Raw data
+ * or YUV raw data. To do image tuning, application call,
+ *
+ */
+
+#include <linux/slab.h>
+
+#include "dm365_ipipe.h"
+#include "dm365_ipipe_hw.h"
+#include "vpfe_mc_capture.h"
+
+#define MIN_OUT_WIDTH 32
+#define MIN_OUT_HEIGHT 32
+
+/* ipipe input format's */
+static const unsigned int ipipe_input_fmts[] = {
+ V4L2_MBUS_FMT_UYVY8_2X8,
+ V4L2_MBUS_FMT_SGRBG12_1X12,
+ V4L2_MBUS_FMT_SGRBG10_DPCM8_1X8,
+ V4L2_MBUS_FMT_SGRBG10_ALAW8_1X8,
+};
+
+/* ipipe output format's */
+static const unsigned int ipipe_output_fmts[] = {
+ V4L2_MBUS_FMT_UYVY8_2X8,
+};
+
+static int ipipe_validate_lutdpc_params(struct vpfe_ipipe_lutdpc *lutdpc)
+{
+ int i;
+
+ if (lutdpc->en > 1 || lutdpc->repl_white > 1 ||
+ lutdpc->dpc_size > LUT_DPC_MAX_SIZE)
+ return -EINVAL;
+
+ if (lutdpc->en && !lutdpc->table)
+ return -EINVAL;
+
+ for (i = 0; i < lutdpc->dpc_size; i++)
+ if (lutdpc->table[i].horz_pos > LUT_DPC_H_POS_MASK ||
+ lutdpc->table[i].vert_pos > LUT_DPC_V_POS_MASK)
+ return -EINVAL;
+
+ return 0;
+}
+
+static int ipipe_set_lutdpc_params(struct vpfe_ipipe_device *ipipe, void *param)
+{
+ struct vpfe_ipipe_lutdpc *lutdpc = &ipipe->config.lutdpc;
+ struct vpfe_ipipe_lutdpc *dpc_param;
+ struct device *dev;
+
+ if (!param) {
+ memset((void *)lutdpc, 0, sizeof(struct vpfe_ipipe_lutdpc));
+ goto success;
+ }
+
+ dev = ipipe->subdev.v4l2_dev->dev;
+ dpc_param = (struct vpfe_ipipe_lutdpc *)param;
+ lutdpc->en = dpc_param->en;
+ lutdpc->repl_white = dpc_param->repl_white;
+ lutdpc->dpc_size = dpc_param->dpc_size;
+ memcpy(&lutdpc->table, &dpc_param->table,
+ (dpc_param->dpc_size * sizeof(struct vpfe_ipipe_lutdpc_entry)));
+ if (ipipe_validate_lutdpc_params(lutdpc) < 0)
+ return -EINVAL;
+
+success:
+ ipipe_set_lutdpc_regs(ipipe->base_addr, ipipe->isp5_base_addr, lutdpc);
+
+ return 0;
+}
+
+static int ipipe_get_lutdpc_params(struct vpfe_ipipe_device *ipipe, void *param)
+{
+ struct vpfe_ipipe_lutdpc *lut_param = (struct vpfe_ipipe_lutdpc *)param;
+ struct vpfe_ipipe_lutdpc *lutdpc = &ipipe->config.lutdpc;
+
+ lut_param->en = lutdpc->en;
+ lut_param->repl_white = lutdpc->repl_white;
+ lut_param->dpc_size = lutdpc->dpc_size;
+ memcpy(&lut_param->table, &lutdpc->table,
+ (lutdpc->dpc_size * sizeof(struct vpfe_ipipe_lutdpc_entry)));
+
+ return 0;
+}
+
+static int ipipe_set_input_config(struct vpfe_ipipe_device *ipipe, void *param)
+{
+ struct vpfe_ipipe_input_config *config = &ipipe->config.input_config;
+
+ if (!param)
+ memset(config, 0, sizeof(struct vpfe_ipipe_input_config));
+ else
+ memcpy(config, param, sizeof(struct vpfe_ipipe_input_config));
+ return 0;
+}
+
+static int ipipe_get_input_config(struct vpfe_ipipe_device *ipipe, void *param)
+{
+ struct vpfe_ipipe_input_config *config = &ipipe->config.input_config;
+
+ if (!param)
+ return -EINVAL;
+
+ memcpy(param, config, sizeof(struct vpfe_ipipe_input_config));
+
+ return 0;
+}
+
+static int ipipe_validate_otfdpc_params(struct vpfe_ipipe_otfdpc *dpc_param)
+{
+ struct vpfe_ipipe_otfdpc_2_0_cfg *dpc_2_0;
+ struct vpfe_ipipe_otfdpc_3_0_cfg *dpc_3_0;
+
+ if (dpc_param->en > 1)
+ return -EINVAL;
+
+ if (dpc_param->alg == VPFE_IPIPE_OTFDPC_2_0) {
+ dpc_2_0 = &dpc_param->alg_cfg.dpc_2_0;
+ if (dpc_2_0->det_thr.r > OTFDPC_DPC2_THR_MASK ||
+ dpc_2_0->det_thr.gr > OTFDPC_DPC2_THR_MASK ||
+ dpc_2_0->det_thr.gb > OTFDPC_DPC2_THR_MASK ||
+ dpc_2_0->det_thr.b > OTFDPC_DPC2_THR_MASK ||
+ dpc_2_0->corr_thr.r > OTFDPC_DPC2_THR_MASK ||
+ dpc_2_0->corr_thr.gr > OTFDPC_DPC2_THR_MASK ||
+ dpc_2_0->corr_thr.gb > OTFDPC_DPC2_THR_MASK ||
+ dpc_2_0->corr_thr.b > OTFDPC_DPC2_THR_MASK)
+ return -EINVAL;
+ return 0;
+ }
+
+ dpc_3_0 = &dpc_param->alg_cfg.dpc_3_0;
+
+ if (dpc_3_0->act_adj_shf > OTF_DPC3_0_SHF_MASK ||
+ dpc_3_0->det_thr > OTF_DPC3_0_DET_MASK ||
+ dpc_3_0->det_slp > OTF_DPC3_0_SLP_MASK ||
+ dpc_3_0->det_thr_min > OTF_DPC3_0_DET_MASK ||
+ dpc_3_0->det_thr_max > OTF_DPC3_0_DET_MASK ||
+ dpc_3_0->corr_thr > OTF_DPC3_0_CORR_MASK ||
+ dpc_3_0->corr_slp > OTF_DPC3_0_SLP_MASK ||
+ dpc_3_0->corr_thr_min > OTF_DPC3_0_CORR_MASK ||
+ dpc_3_0->corr_thr_max > OTF_DPC3_0_CORR_MASK)
+ return -EINVAL;
+
+ return 0;
+}
+
+static int ipipe_set_otfdpc_params(struct vpfe_ipipe_device *ipipe, void *param)
+{
+ struct vpfe_ipipe_otfdpc *dpc_param = (struct vpfe_ipipe_otfdpc *)param;
+ struct vpfe_ipipe_otfdpc *otfdpc = &ipipe->config.otfdpc;
+ struct device *dev;
+
+ if (!param) {
+ memset((void *)otfdpc, 0, sizeof(struct ipipe_otfdpc_2_0));
+ goto success;
+ }
+ dev = ipipe->subdev.v4l2_dev->dev;
+ memcpy(otfdpc, dpc_param, sizeof(struct vpfe_ipipe_otfdpc));
+ if (ipipe_validate_otfdpc_params(otfdpc) < 0) {
+ dev_err(dev, "Invalid otfdpc params\n");
+ return -EINVAL;
+ }
+
+success:
+ ipipe_set_otfdpc_regs(ipipe->base_addr, otfdpc);
+
+ return 0;
+}
+
+static int ipipe_get_otfdpc_params(struct vpfe_ipipe_device *ipipe, void *param)
+{
+ struct vpfe_ipipe_otfdpc *dpc_param = (struct vpfe_ipipe_otfdpc *)param;
+ struct vpfe_ipipe_otfdpc *otfdpc = &ipipe->config.otfdpc;
+
+ memcpy(dpc_param, otfdpc, sizeof(struct vpfe_ipipe_otfdpc));
+ return 0;
+}
+
+static int ipipe_validate_nf_params(struct vpfe_ipipe_nf *nf_param)
+{
+ int i;
+
+ if (nf_param->en > 1 || nf_param->shft_val > D2F_SHFT_VAL_MASK ||
+ nf_param->spread_val > D2F_SPR_VAL_MASK ||
+ nf_param->apply_lsc_gain > 1 ||
+ nf_param->edge_det_min_thr > D2F_EDGE_DET_THR_MASK ||
+ nf_param->edge_det_max_thr > D2F_EDGE_DET_THR_MASK)
+ return -EINVAL;
+
+ for (i = 0; i < VPFE_IPIPE_NF_THR_TABLE_SIZE; i++)
+ if (nf_param->thr[i] > D2F_THR_VAL_MASK)
+ return -EINVAL;
+
+ for (i = 0; i < VPFE_IPIPE_NF_STR_TABLE_SIZE; i++)
+ if (nf_param->str[i] > D2F_STR_VAL_MASK)
+ return -EINVAL;
+
+ return 0;
+}
+
+static int ipipe_set_nf_params(struct vpfe_ipipe_device *ipipe,
+ unsigned int id, void *param)
+{
+ struct vpfe_ipipe_nf *nf_param = (struct vpfe_ipipe_nf *)param;
+ struct vpfe_ipipe_nf *nf = &ipipe->config.nf1;
+ struct device *dev;
+
+ if (id == IPIPE_D2F_2ND)
+ nf = &ipipe->config.nf2;
+
+ if (!nf_param) {
+ memset((void *)nf, 0, sizeof(struct vpfe_ipipe_nf));
+ goto success;
+ }
+
+ dev = ipipe->subdev.v4l2_dev->dev;
+ memcpy(nf, nf_param, sizeof(struct vpfe_ipipe_nf));
+ if (ipipe_validate_nf_params(nf) < 0) {
+ dev_err(dev, "Invalid nf params\n");
+ return -EINVAL;
+ }
+
+success:
+ ipipe_set_d2f_regs(ipipe->base_addr, id, nf);
+
+ return 0;
+}
+
+static int ipipe_set_nf1_params(struct vpfe_ipipe_device *ipipe, void *param)
+{
+ return ipipe_set_nf_params(ipipe, IPIPE_D2F_1ST, param);
+}
+
+static int ipipe_set_nf2_params(struct vpfe_ipipe_device *ipipe, void *param)
+{
+ return ipipe_set_nf_params(ipipe, IPIPE_D2F_2ND, param);
+}
+
+static int ipipe_get_nf_params(struct vpfe_ipipe_device *ipipe,
+ unsigned int id, void *param)
+{
+ struct vpfe_ipipe_nf *nf_param = (struct vpfe_ipipe_nf *)param;
+ struct vpfe_ipipe_nf *nf = &ipipe->config.nf1;
+
+ if (id == IPIPE_D2F_2ND)
+ nf = &ipipe->config.nf2;
+
+ memcpy(nf_param, nf, sizeof(struct vpfe_ipipe_nf));
+
+ return 0;
+}
+
+static int ipipe_get_nf1_params(struct vpfe_ipipe_device *ipipe, void *param)
+{
+ return ipipe_get_nf_params(ipipe, IPIPE_D2F_1ST, param);
+}
+
+static int ipipe_get_nf2_params(struct vpfe_ipipe_device *ipipe, void *param)
+{
+ return ipipe_get_nf_params(ipipe, IPIPE_D2F_2ND, param);
+}
+
+static int ipipe_validate_gic_params(struct vpfe_ipipe_gic *gic)
+{
+ if (gic->en > 1 || gic->gain > GIC_GAIN_MASK ||
+ gic->thr > GIC_THR_MASK || gic->slope > GIC_SLOPE_MASK ||
+ gic->apply_lsc_gain > 1 ||
+ gic->nf2_thr_gain.integer > GIC_NFGAN_INT_MASK ||
+ gic->nf2_thr_gain.decimal > GIC_NFGAN_DECI_MASK)
+ return -EINVAL;
+
+ return 0;
+}
+
+static int ipipe_set_gic_params(struct vpfe_ipipe_device *ipipe, void *param)
+{
+ struct vpfe_ipipe_gic *gic_param = (struct vpfe_ipipe_gic *)param;
+ struct device *dev = ipipe->subdev.v4l2_dev->dev;
+ struct vpfe_ipipe_gic *gic = &ipipe->config.gic;
+
+ if (!gic_param) {
+ memset((void *)gic, 0, sizeof(struct vpfe_ipipe_gic));
+ goto success;
+ }
+
+ memcpy(gic, gic_param, sizeof(struct vpfe_ipipe_gic));
+ if (ipipe_validate_gic_params(gic) < 0) {
+ dev_err(dev, "Invalid gic params\n");
+ return -EINVAL;
+ }
+
+success:
+ ipipe_set_gic_regs(ipipe->base_addr, gic);
+
+ return 0;
+}
+
+static int ipipe_get_gic_params(struct vpfe_ipipe_device *ipipe, void *param)
+{
+ struct vpfe_ipipe_gic *gic_param = (struct vpfe_ipipe_gic *)param;
+ struct vpfe_ipipe_gic *gic = &ipipe->config.gic;
+
+ memcpy(gic_param, gic, sizeof(struct vpfe_ipipe_gic));
+
+ return 0;
+}
+
+static int ipipe_validate_wb_params(struct vpfe_ipipe_wb *wbal)
+{
+ if (wbal->ofst_r > WB_OFFSET_MASK ||
+ wbal->ofst_gr > WB_OFFSET_MASK ||
+ wbal->ofst_gb > WB_OFFSET_MASK ||
+ wbal->ofst_b > WB_OFFSET_MASK ||
+ wbal->gain_r.integer > WB_GAIN_INT_MASK ||
+ wbal->gain_r.decimal > WB_GAIN_DECI_MASK ||
+ wbal->gain_gr.integer > WB_GAIN_INT_MASK ||
+ wbal->gain_gr.decimal > WB_GAIN_DECI_MASK ||
+ wbal->gain_gb.integer > WB_GAIN_INT_MASK ||
+ wbal->gain_gb.decimal > WB_GAIN_DECI_MASK ||
+ wbal->gain_b.integer > WB_GAIN_INT_MASK ||
+ wbal->gain_b.decimal > WB_GAIN_DECI_MASK)
+ return -EINVAL;
+
+ return 0;
+}
+
+static int ipipe_set_wb_params(struct vpfe_ipipe_device *ipipe, void *param)
+{
+ struct vpfe_ipipe_wb *wb_param = (struct vpfe_ipipe_wb *)param;
+ struct vpfe_ipipe_wb *wbal = &ipipe->config.wbal;
+
+ if (!wb_param) {
+ const struct vpfe_ipipe_wb wb_defaults = {
+ .gain_r = {2, 0x0},
+ .gain_gr = {2, 0x0},
+ .gain_gb = {2, 0x0},
+ .gain_b = {2, 0x0}
+ };
+ memcpy(wbal, &wb_defaults, sizeof(struct vpfe_ipipe_wb));
+ goto success;
+ }
+
+ memcpy(wbal, wb_param, sizeof(struct vpfe_ipipe_wb));
+ if (ipipe_validate_wb_params(wbal) < 0)
+ return -EINVAL;
+
+success:
+ ipipe_set_wb_regs(ipipe->base_addr, wbal);
+
+ return 0;
+}
+
+static int ipipe_get_wb_params(struct vpfe_ipipe_device *ipipe, void *param)
+{
+ struct vpfe_ipipe_wb *wb_param = (struct vpfe_ipipe_wb *)param;
+ struct vpfe_ipipe_wb *wbal = &ipipe->config.wbal;
+
+ memcpy(wb_param, wbal, sizeof(struct vpfe_ipipe_wb));
+ return 0;
+}
+
+static int ipipe_validate_cfa_params(struct vpfe_ipipe_cfa *cfa)
+{
+ if (cfa->hpf_thr_2dir > CFA_HPF_THR_2DIR_MASK ||
+ cfa->hpf_slp_2dir > CFA_HPF_SLOPE_2DIR_MASK ||
+ cfa->hp_mix_thr_2dir > CFA_HPF_MIX_THR_2DIR_MASK ||
+ cfa->hp_mix_slope_2dir > CFA_HPF_MIX_SLP_2DIR_MASK ||
+ cfa->dir_thr_2dir > CFA_DIR_THR_2DIR_MASK ||
+ cfa->dir_slope_2dir > CFA_DIR_SLP_2DIR_MASK ||
+ cfa->nd_wt_2dir > CFA_ND_WT_2DIR_MASK ||
+ cfa->hue_fract_daa > CFA_DAA_HUE_FRA_MASK ||
+ cfa->edge_thr_daa > CFA_DAA_EDG_THR_MASK ||
+ cfa->thr_min_daa > CFA_DAA_THR_MIN_MASK ||
+ cfa->thr_slope_daa > CFA_DAA_THR_SLP_MASK ||
+ cfa->slope_min_daa > CFA_DAA_SLP_MIN_MASK ||
+ cfa->slope_slope_daa > CFA_DAA_SLP_SLP_MASK ||
+ cfa->lp_wt_daa > CFA_DAA_LP_WT_MASK)
+ return -EINVAL;
+
+ return 0;
+}
+
+static int ipipe_set_cfa_params(struct vpfe_ipipe_device *ipipe, void *param)
+{
+ struct vpfe_ipipe_cfa *cfa_param = (struct vpfe_ipipe_cfa *)param;
+ struct vpfe_ipipe_cfa *cfa = &ipipe->config.cfa;
+
+ if (!cfa_param) {
+ memset(cfa, 0, sizeof(struct vpfe_ipipe_cfa));
+ cfa->alg = VPFE_IPIPE_CFA_ALG_2DIRAC;
+ goto success;
+ }
+
+ memcpy(cfa, cfa_param, sizeof(struct vpfe_ipipe_cfa));
+ if (ipipe_validate_cfa_params(cfa) < 0)
+ return -EINVAL;
+
+success:
+ ipipe_set_cfa_regs(ipipe->base_addr, cfa);
+
+ return 0;
+}
+
+static int ipipe_get_cfa_params(struct vpfe_ipipe_device *ipipe, void *param)
+{
+ struct vpfe_ipipe_cfa *cfa_param = (struct vpfe_ipipe_cfa *)param;
+ struct vpfe_ipipe_cfa *cfa = &ipipe->config.cfa;
+
+ memcpy(cfa_param, cfa, sizeof(struct vpfe_ipipe_cfa));
+ return 0;
+}
+
+static int
+ipipe_validate_rgb2rgb_params(struct vpfe_ipipe_rgb2rgb *rgb2rgb,
+ unsigned int id)
+{
+ u32 gain_int_upper = RGB2RGB_1_GAIN_INT_MASK;
+ u32 offset_upper = RGB2RGB_1_OFST_MASK;
+
+ if (id == IPIPE_RGB2RGB_2) {
+ offset_upper = RGB2RGB_2_OFST_MASK;
+ gain_int_upper = RGB2RGB_2_GAIN_INT_MASK;
+ }
+
+ if (rgb2rgb->coef_rr.decimal > RGB2RGB_GAIN_DECI_MASK ||
+ rgb2rgb->coef_rr.integer > gain_int_upper)
+ return -EINVAL;
+
+ if (rgb2rgb->coef_gr.decimal > RGB2RGB_GAIN_DECI_MASK ||
+ rgb2rgb->coef_gr.integer > gain_int_upper)
+ return -EINVAL;
+
+ if (rgb2rgb->coef_br.decimal > RGB2RGB_GAIN_DECI_MASK ||
+ rgb2rgb->coef_br.integer > gain_int_upper)
+ return -EINVAL;
+
+ if (rgb2rgb->coef_rg.decimal > RGB2RGB_GAIN_DECI_MASK ||
+ rgb2rgb->coef_rg.integer > gain_int_upper)
+ return -EINVAL;
+
+ if (rgb2rgb->coef_gg.decimal > RGB2RGB_GAIN_DECI_MASK ||
+ rgb2rgb->coef_gg.integer > gain_int_upper)
+ return -EINVAL;
+
+ if (rgb2rgb->coef_bg.decimal > RGB2RGB_GAIN_DECI_MASK ||
+ rgb2rgb->coef_bg.integer > gain_int_upper)
+ return -EINVAL;
+
+ if (rgb2rgb->coef_rb.decimal > RGB2RGB_GAIN_DECI_MASK ||
+ rgb2rgb->coef_rb.integer > gain_int_upper)
+ return -EINVAL;
+
+ if (rgb2rgb->coef_gb.decimal > RGB2RGB_GAIN_DECI_MASK ||
+ rgb2rgb->coef_gb.integer > gain_int_upper)
+ return -EINVAL;
+
+ if (rgb2rgb->coef_bb.decimal > RGB2RGB_GAIN_DECI_MASK ||
+ rgb2rgb->coef_bb.integer > gain_int_upper)
+ return -EINVAL;
+
+ if (rgb2rgb->out_ofst_r > offset_upper ||
+ rgb2rgb->out_ofst_g > offset_upper ||
+ rgb2rgb->out_ofst_b > offset_upper)
+ return -EINVAL;
+
+ return 0;
+}
+
+static int ipipe_set_rgb2rgb_params(struct vpfe_ipipe_device *ipipe,
+ unsigned int id, void *param)
+{
+ struct vpfe_ipipe_rgb2rgb *rgb2rgb = &ipipe->config.rgb2rgb1;
+ struct device *dev = ipipe->subdev.v4l2_dev->dev;
+ struct vpfe_ipipe_rgb2rgb *rgb2rgb_param;
+
+ rgb2rgb_param = (struct vpfe_ipipe_rgb2rgb *)param;
+
+ if (id == IPIPE_RGB2RGB_2)
+ rgb2rgb = &ipipe->config.rgb2rgb2;
+
+ if (!rgb2rgb_param) {
+ const struct vpfe_ipipe_rgb2rgb rgb2rgb_defaults = {
+ .coef_rr = {1, 0}, /* 256 */
+ .coef_gr = {0, 0},
+ .coef_br = {0, 0},
+ .coef_rg = {0, 0},
+ .coef_gg = {1, 0}, /* 256 */
+ .coef_bg = {0, 0},
+ .coef_rb = {0, 0},
+ .coef_gb = {0, 0},
+ .coef_bb = {1, 0}, /* 256 */
+ };
+ /* Copy defaults for rgb2rgb conversion */
+ memcpy(rgb2rgb, &rgb2rgb_defaults,
+ sizeof(struct vpfe_ipipe_rgb2rgb));
+ goto success;
+ }
+
+ memcpy(rgb2rgb, rgb2rgb_param, sizeof(struct vpfe_ipipe_rgb2rgb));
+ if (ipipe_validate_rgb2rgb_params(rgb2rgb, id) < 0) {
+ dev_err(dev, "Invalid rgb2rgb params\n");
+ return -EINVAL;
+ }
+
+success:
+ ipipe_set_rgb2rgb_regs(ipipe->base_addr, id, rgb2rgb);
+
+ return 0;
+}
+
+static int
+ipipe_set_rgb2rgb_1_params(struct vpfe_ipipe_device *ipipe, void *param)
+{
+ return ipipe_set_rgb2rgb_params(ipipe, IPIPE_RGB2RGB_1, param);
+}
+
+static int
+ipipe_set_rgb2rgb_2_params(struct vpfe_ipipe_device *ipipe, void *param)
+{
+ return ipipe_set_rgb2rgb_params(ipipe, IPIPE_RGB2RGB_2, param);
+}
+
+static int ipipe_get_rgb2rgb_params(struct vpfe_ipipe_device *ipipe,
+ unsigned int id, void *param)
+{
+ struct vpfe_ipipe_rgb2rgb *rgb2rgb = &ipipe->config.rgb2rgb1;
+ struct vpfe_ipipe_rgb2rgb *rgb2rgb_param;
+
+ rgb2rgb_param = (struct vpfe_ipipe_rgb2rgb *)param;
+
+ if (id == IPIPE_RGB2RGB_2)
+ rgb2rgb = &ipipe->config.rgb2rgb2;
+
+ memcpy(rgb2rgb_param, rgb2rgb, sizeof(struct vpfe_ipipe_rgb2rgb));
+
+ return 0;
+}
+
+static int
+ipipe_get_rgb2rgb_1_params(struct vpfe_ipipe_device *ipipe, void *param)
+{
+ return ipipe_get_rgb2rgb_params(ipipe, IPIPE_RGB2RGB_1, param);
+}
+
+static int
+ipipe_get_rgb2rgb_2_params(struct vpfe_ipipe_device *ipipe, void *param)
+{
+ return ipipe_get_rgb2rgb_params(ipipe, IPIPE_RGB2RGB_2, param);
+}
+
+static int
+ipipe_validate_gamma_entry(struct vpfe_ipipe_gamma_entry *table, int size)
+{
+ int i;
+
+ if (!table)
+ return -EINVAL;
+
+ for (i = 0; i < size; i++)
+ if (table[i].slope > GAMMA_MASK ||
+ table[i].offset > GAMMA_MASK)
+ return -EINVAL;
+
+ return 0;
+}
+
+static int
+ipipe_validate_gamma_params(struct vpfe_ipipe_gamma *gamma, struct device *dev)
+{
+ int table_size;
+ int err;
+
+ if (gamma->bypass_r > 1 ||
+ gamma->bypass_b > 1 ||
+ gamma->bypass_g > 1)
+ return -EINVAL;
+
+ if (gamma->tbl_sel != VPFE_IPIPE_GAMMA_TBL_RAM)
+ return 0;
+
+ table_size = gamma->tbl_size;
+ if (!gamma->bypass_r) {
+ err = ipipe_validate_gamma_entry(gamma->table_r, table_size);
+ if (err) {
+ dev_err(dev, "GAMMA R - table entry invalid\n");
+ return err;
+ }
+ }
+
+ if (!gamma->bypass_b) {
+ err = ipipe_validate_gamma_entry(gamma->table_b, table_size);
+ if (err) {
+ dev_err(dev, "GAMMA B - table entry invalid\n");
+ return err;
+ }
+ }
+
+ if (!gamma->bypass_g) {
+ err = ipipe_validate_gamma_entry(gamma->table_g, table_size);
+ if (err) {
+ dev_err(dev, "GAMMA G - table entry invalid\n");
+ return err;
+ }
+ }
+
+ return 0;
+}
+
+static int
+ipipe_set_gamma_params(struct vpfe_ipipe_device *ipipe, void *param)
+{
+ struct vpfe_ipipe_gamma *gamma_param = (struct vpfe_ipipe_gamma *)param;
+ struct vpfe_ipipe_gamma *gamma = &ipipe->config.gamma;
+ struct device *dev = ipipe->subdev.v4l2_dev->dev;
+ int table_size;
+
+ if (!gamma_param) {
+ memset(gamma, 0, sizeof(struct vpfe_ipipe_gamma));
+ gamma->tbl_sel = VPFE_IPIPE_GAMMA_TBL_ROM;
+ goto success;
+ }
+
+ gamma->bypass_r = gamma_param->bypass_r;
+ gamma->bypass_b = gamma_param->bypass_b;
+ gamma->bypass_g = gamma_param->bypass_g;
+ gamma->tbl_sel = gamma_param->tbl_sel;
+ gamma->tbl_size = gamma_param->tbl_size;
+
+ if (ipipe_validate_gamma_params(gamma, dev) < 0)
+ return -EINVAL;
+
+ if (gamma_param->tbl_sel != VPFE_IPIPE_GAMMA_TBL_RAM)
+ goto success;
+
+ table_size = gamma->tbl_size;
+ if (!gamma_param->bypass_r)
+ memcpy(&gamma->table_r, &gamma_param->table_r,
+ (table_size * sizeof(struct vpfe_ipipe_gamma_entry)));
+
+ if (!gamma_param->bypass_b)
+ memcpy(&gamma->table_b, &gamma_param->table_b,
+ (table_size * sizeof(struct vpfe_ipipe_gamma_entry)));
+
+ if (!gamma_param->bypass_g)
+ memcpy(&gamma->table_g, &gamma_param->table_g,
+ (table_size * sizeof(struct vpfe_ipipe_gamma_entry)));
+
+success:
+ ipipe_set_gamma_regs(ipipe->base_addr, ipipe->isp5_base_addr, gamma);
+
+ return 0;
+}
+
+static int ipipe_get_gamma_params(struct vpfe_ipipe_device *ipipe, void *param)
+{
+ struct vpfe_ipipe_gamma *gamma_param = (struct vpfe_ipipe_gamma *)param;
+ struct vpfe_ipipe_gamma *gamma = &ipipe->config.gamma;
+ struct device *dev = ipipe->subdev.v4l2_dev->dev;
+ int table_size;
+
+ gamma_param->bypass_r = gamma->bypass_r;
+ gamma_param->bypass_g = gamma->bypass_g;
+ gamma_param->bypass_b = gamma->bypass_b;
+ gamma_param->tbl_sel = gamma->tbl_sel;
+ gamma_param->tbl_size = gamma->tbl_size;
+
+ if (gamma->tbl_sel != VPFE_IPIPE_GAMMA_TBL_RAM)
+ return 0;
+
+ table_size = gamma->tbl_size;
+
+ if (!gamma->bypass_r && !gamma_param->table_r) {
+ dev_err(dev,
+ "ipipe_get_gamma_params: table ptr empty for R\n");
+ return -EINVAL;
+ }
+ memcpy(gamma_param->table_r, gamma->table_r,
+ (table_size * sizeof(struct vpfe_ipipe_gamma_entry)));
+
+ if (!gamma->bypass_g && !gamma_param->table_g) {
+ dev_err(dev, "ipipe_get_gamma_params: table ptr empty for G\n");
+ return -EINVAL;
+ }
+ memcpy(gamma_param->table_g, gamma->table_g,
+ (table_size * sizeof(struct vpfe_ipipe_gamma_entry)));
+
+ if (!gamma->bypass_b && !gamma_param->table_b) {
+ dev_err(dev, "ipipe_get_gamma_params: table ptr empty for B\n");
+ return -EINVAL;
+ }
+ memcpy(gamma_param->table_b, gamma->table_b,
+ (table_size * sizeof(struct vpfe_ipipe_gamma_entry)));
+
+ return 0;
+}
+
+static int ipipe_validate_3d_lut_params(struct vpfe_ipipe_3d_lut *lut)
+{
+ int i;
+
+ if (!lut->en)
+ return 0;
+
+ for (i = 0; i < VPFE_IPIPE_MAX_SIZE_3D_LUT; i++)
+ if (lut->table[i].r > D3_LUT_ENTRY_MASK ||
+ lut->table[i].g > D3_LUT_ENTRY_MASK ||
+ lut->table[i].b > D3_LUT_ENTRY_MASK)
+ return -EINVAL;
+
+ return 0;
+}
+
+static int ipipe_get_3d_lut_params(struct vpfe_ipipe_device *ipipe, void *param)
+{
+ struct vpfe_ipipe_3d_lut *lut_param = (struct vpfe_ipipe_3d_lut *)param;
+ struct vpfe_ipipe_3d_lut *lut = &ipipe->config.lut;
+ struct device *dev = ipipe->subdev.v4l2_dev->dev;
+
+ lut_param->en = lut->en;
+ if (!lut_param->table) {
+ dev_err(dev, "ipipe_get_3d_lut_params: Invalid table ptr\n");
+ return -EINVAL;
+ }
+
+ memcpy(lut_param->table, &lut->table,
+ (VPFE_IPIPE_MAX_SIZE_3D_LUT *
+ sizeof(struct vpfe_ipipe_3d_lut_entry)));
+
+ return 0;
+}
+
+static int
+ipipe_set_3d_lut_params(struct vpfe_ipipe_device *ipipe, void *param)
+{
+ struct vpfe_ipipe_3d_lut *lut_param = (struct vpfe_ipipe_3d_lut *)param;
+ struct vpfe_ipipe_3d_lut *lut = &ipipe->config.lut;
+ struct device *dev = ipipe->subdev.v4l2_dev->dev;
+
+ if (!lut_param) {
+ memset(lut, 0, sizeof(struct vpfe_ipipe_3d_lut));
+ goto success;
+ }
+
+ memcpy(lut, lut_param, sizeof(struct vpfe_ipipe_3d_lut));
+ if (ipipe_validate_3d_lut_params(lut) < 0) {
+ dev_err(dev, "Invalid 3D-LUT Params\n");
+ return -EINVAL;
+ }
+
+success:
+ ipipe_set_3d_lut_regs(ipipe->base_addr, ipipe->isp5_base_addr, lut);
+
+ return 0;
+}
+
+static int ipipe_validate_rgb2yuv_params(struct vpfe_ipipe_rgb2yuv *rgb2yuv)
+{
+ if (rgb2yuv->coef_ry.decimal > RGB2YCBCR_COEF_DECI_MASK ||
+ rgb2yuv->coef_ry.integer > RGB2YCBCR_COEF_INT_MASK)
+ return -EINVAL;
+
+ if (rgb2yuv->coef_gy.decimal > RGB2YCBCR_COEF_DECI_MASK ||
+ rgb2yuv->coef_gy.integer > RGB2YCBCR_COEF_INT_MASK)
+ return -EINVAL;
+
+ if (rgb2yuv->coef_by.decimal > RGB2YCBCR_COEF_DECI_MASK ||
+ rgb2yuv->coef_by.integer > RGB2YCBCR_COEF_INT_MASK)
+ return -EINVAL;
+
+ if (rgb2yuv->coef_rcb.decimal > RGB2YCBCR_COEF_DECI_MASK ||
+ rgb2yuv->coef_rcb.integer > RGB2YCBCR_COEF_INT_MASK)
+ return -EINVAL;
+
+ if (rgb2yuv->coef_gcb.decimal > RGB2YCBCR_COEF_DECI_MASK ||
+ rgb2yuv->coef_gcb.integer > RGB2YCBCR_COEF_INT_MASK)
+ return -EINVAL;
+
+ if (rgb2yuv->coef_bcb.decimal > RGB2YCBCR_COEF_DECI_MASK ||
+ rgb2yuv->coef_bcb.integer > RGB2YCBCR_COEF_INT_MASK)
+ return -EINVAL;
+
+ if (rgb2yuv->coef_rcr.decimal > RGB2YCBCR_COEF_DECI_MASK ||
+ rgb2yuv->coef_rcr.integer > RGB2YCBCR_COEF_INT_MASK)
+ return -EINVAL;
+
+ if (rgb2yuv->coef_gcr.decimal > RGB2YCBCR_COEF_DECI_MASK ||
+ rgb2yuv->coef_gcr.integer > RGB2YCBCR_COEF_INT_MASK)
+ return -EINVAL;
+
+ if (rgb2yuv->coef_bcr.decimal > RGB2YCBCR_COEF_DECI_MASK ||
+ rgb2yuv->coef_bcr.integer > RGB2YCBCR_COEF_INT_MASK)
+ return -EINVAL;
+
+ if (rgb2yuv->out_ofst_y > RGB2YCBCR_OFST_MASK ||
+ rgb2yuv->out_ofst_cb > RGB2YCBCR_OFST_MASK ||
+ rgb2yuv->out_ofst_cr > RGB2YCBCR_OFST_MASK)
+ return -EINVAL;
+
+ return 0;
+}
+
+static int
+ipipe_set_rgb2yuv_params(struct vpfe_ipipe_device *ipipe, void *param)
+{
+ struct vpfe_ipipe_rgb2yuv *rgb2yuv = &ipipe->config.rgb2yuv;
+ struct device *dev = ipipe->subdev.v4l2_dev->dev;
+ struct vpfe_ipipe_rgb2yuv *rgb2yuv_param;
+
+ rgb2yuv_param = (struct vpfe_ipipe_rgb2yuv *)param;
+ if (!rgb2yuv_param) {
+ /* Defaults for rgb2yuv conversion */
+ const struct vpfe_ipipe_rgb2yuv rgb2yuv_defaults = {
+ .coef_ry = {0, 0x4d},
+ .coef_gy = {0, 0x96},
+ .coef_by = {0, 0x1d},
+ .coef_rcb = {0xf, 0xd5},
+ .coef_gcb = {0xf, 0xab},
+ .coef_bcb = {0, 0x80},
+ .coef_rcr = {0, 0x80},
+ .coef_gcr = {0xf, 0x95},
+ .coef_bcr = {0xf, 0xeb},
+ .out_ofst_cb = 0x80,
+ .out_ofst_cr = 0x80,
+ };
+ /* Copy defaults for rgb2yuv conversion */
+ memcpy(rgb2yuv, &rgb2yuv_defaults,
+ sizeof(struct vpfe_ipipe_rgb2yuv));
+ goto success;
+ }
+
+ memcpy(rgb2yuv, rgb2yuv_param, sizeof(struct vpfe_ipipe_rgb2yuv));
+ if (ipipe_validate_rgb2yuv_params(rgb2yuv) < 0) {
+ dev_err(dev, "Invalid rgb2yuv params\n");
+ return -EINVAL;
+ }
+
+success:
+ ipipe_set_rgb2ycbcr_regs(ipipe->base_addr, rgb2yuv);
+
+ return 0;
+}
+
+static int
+ipipe_get_rgb2yuv_params(struct vpfe_ipipe_device *ipipe, void *param)
+{
+ struct vpfe_ipipe_rgb2yuv *rgb2yuv = &ipipe->config.rgb2yuv;
+ struct vpfe_ipipe_rgb2yuv *rgb2yuv_param;
+
+ rgb2yuv_param = (struct vpfe_ipipe_rgb2yuv *)param;
+ memcpy(rgb2yuv_param, rgb2yuv, sizeof(struct vpfe_ipipe_rgb2yuv));
+ return 0;
+}
+
+static int ipipe_validate_gbce_params(struct vpfe_ipipe_gbce *gbce)
+{
+ u32 max = GBCE_Y_VAL_MASK;
+ int i;
+
+ if (!gbce->en)
+ return 0;
+
+ if (gbce->type == VPFE_IPIPE_GBCE_GAIN_TBL)
+ max = GBCE_GAIN_VAL_MASK;
+
+ for (i = 0; i < VPFE_IPIPE_MAX_SIZE_GBCE_LUT; i++)
+ if (gbce->table[i] > max)
+ return -EINVAL;
+
+ return 0;
+}
+
+static int ipipe_set_gbce_params(struct vpfe_ipipe_device *ipipe, void *param)
+{
+ struct vpfe_ipipe_gbce *gbce_param = (struct vpfe_ipipe_gbce *)param;
+ struct vpfe_ipipe_gbce *gbce = &ipipe->config.gbce;
+ struct device *dev = ipipe->subdev.v4l2_dev->dev;
+
+ if (!gbce_param) {
+ memset(gbce, 0 , sizeof(struct vpfe_ipipe_gbce));
+ } else {
+ memcpy(gbce, gbce_param, sizeof(struct vpfe_ipipe_gbce));
+ if (ipipe_validate_gbce_params(gbce) < 0) {
+ dev_err(dev, "Invalid gbce params\n");
+ return -EINVAL;
+ }
+ }
+
+ ipipe_set_gbce_regs(ipipe->base_addr, ipipe->isp5_base_addr, gbce);
+
+ return 0;
+}
+
+static int ipipe_get_gbce_params(struct vpfe_ipipe_device *ipipe, void *param)
+{
+ struct vpfe_ipipe_gbce *gbce_param = (struct vpfe_ipipe_gbce *)param;
+ struct vpfe_ipipe_gbce *gbce = &ipipe->config.gbce;
+ struct device *dev = ipipe->subdev.v4l2_dev->dev;
+
+ gbce_param->en = gbce->en;
+ gbce_param->type = gbce->type;
+ if (!gbce_param->table) {
+ dev_err(dev, "ipipe_get_gbce_params: Invalid table ptr\n");
+ return -EINVAL;
+ }
+
+ memcpy(gbce_param->table, gbce->table,
+ (VPFE_IPIPE_MAX_SIZE_GBCE_LUT * sizeof(unsigned short)));
+
+ return 0;
+}
+
+static int
+ipipe_validate_yuv422_conv_params(struct vpfe_ipipe_yuv422_conv *yuv422_conv)
+{
+ if (yuv422_conv->en_chrom_lpf > 1)
+ return -EINVAL;
+
+ return 0;
+}
+
+static int
+ipipe_set_yuv422_conv_params(struct vpfe_ipipe_device *ipipe, void *param)
+{
+ struct vpfe_ipipe_yuv422_conv *yuv422_conv = &ipipe->config.yuv422_conv;
+ struct vpfe_ipipe_yuv422_conv *yuv422_conv_param;
+ struct device *dev = ipipe->subdev.v4l2_dev->dev;
+
+ yuv422_conv_param = (struct vpfe_ipipe_yuv422_conv *)param;
+ if (!yuv422_conv_param) {
+ memset(yuv422_conv, 0, sizeof(struct vpfe_ipipe_yuv422_conv));
+ yuv422_conv->chrom_pos = VPFE_IPIPE_YUV422_CHR_POS_COSITE;
+ } else {
+ memcpy(yuv422_conv, yuv422_conv_param,
+ sizeof(struct vpfe_ipipe_yuv422_conv));
+ if (ipipe_validate_yuv422_conv_params(yuv422_conv) < 0) {
+ dev_err(dev, "Invalid yuv422 params\n");
+ return -EINVAL;
+ }
+ }
+
+ ipipe_set_yuv422_conv_regs(ipipe->base_addr, yuv422_conv);
+
+ return 0;
+}
+
+static int
+ipipe_get_yuv422_conv_params(struct vpfe_ipipe_device *ipipe, void *param)
+{
+ struct vpfe_ipipe_yuv422_conv *yuv422_conv = &ipipe->config.yuv422_conv;
+ struct vpfe_ipipe_yuv422_conv *yuv422_conv_param;
+
+ yuv422_conv_param = (struct vpfe_ipipe_yuv422_conv *)param;
+ memcpy(yuv422_conv_param, yuv422_conv,
+ sizeof(struct vpfe_ipipe_yuv422_conv));
+
+ return 0;
+}
+
+static int ipipe_validate_yee_params(struct vpfe_ipipe_yee *yee)
+{
+ int i;
+
+ if (yee->en > 1 ||
+ yee->en_halo_red > 1 ||
+ yee->hpf_shft > YEE_HPF_SHIFT_MASK)
+ return -EINVAL;
+
+ if (yee->hpf_coef_00 > YEE_COEF_MASK ||
+ yee->hpf_coef_01 > YEE_COEF_MASK ||
+ yee->hpf_coef_02 > YEE_COEF_MASK ||
+ yee->hpf_coef_10 > YEE_COEF_MASK ||
+ yee->hpf_coef_11 > YEE_COEF_MASK ||
+ yee->hpf_coef_12 > YEE_COEF_MASK ||
+ yee->hpf_coef_20 > YEE_COEF_MASK ||
+ yee->hpf_coef_21 > YEE_COEF_MASK ||
+ yee->hpf_coef_22 > YEE_COEF_MASK)
+ return -EINVAL;
+
+ if (yee->yee_thr > YEE_THR_MASK ||
+ yee->es_gain > YEE_ES_GAIN_MASK ||
+ yee->es_thr1 > YEE_ES_THR1_MASK ||
+ yee->es_thr2 > YEE_THR_MASK ||
+ yee->es_gain_grad > YEE_THR_MASK ||
+ yee->es_ofst_grad > YEE_THR_MASK)
+ return -EINVAL;
+
+ for (i = 0; i < VPFE_IPIPE_MAX_SIZE_YEE_LUT; i++)
+ if (yee->table[i] > YEE_ENTRY_MASK)
+ return -EINVAL;
+
+ return 0;
+}
+
+static int ipipe_set_yee_params(struct vpfe_ipipe_device *ipipe, void *param)
+{
+ struct vpfe_ipipe_yee *yee_param = (struct vpfe_ipipe_yee *)param;
+ struct device *dev = ipipe->subdev.v4l2_dev->dev;
+ struct vpfe_ipipe_yee *yee = &ipipe->config.yee;
+
+ if (!yee_param) {
+ memset(yee, 0, sizeof(struct vpfe_ipipe_yee));
+ } else {
+ memcpy(yee, yee_param, sizeof(struct vpfe_ipipe_yee));
+ if (ipipe_validate_yee_params(yee) < 0) {
+ dev_err(dev, "Invalid yee params\n");
+ return -EINVAL;
+ }
+ }
+
+ ipipe_set_ee_regs(ipipe->base_addr, ipipe->isp5_base_addr, yee);
+
+ return 0;
+}
+
+static int ipipe_get_yee_params(struct vpfe_ipipe_device *ipipe, void *param)
+{
+ struct vpfe_ipipe_yee *yee_param = (struct vpfe_ipipe_yee *)param;
+ struct vpfe_ipipe_yee *yee = &ipipe->config.yee;
+
+ yee_param->en = yee->en;
+ yee_param->en_halo_red = yee->en_halo_red;
+ yee_param->merge_meth = yee->merge_meth;
+ yee_param->hpf_shft = yee->hpf_shft;
+ yee_param->hpf_coef_00 = yee->hpf_coef_00;
+ yee_param->hpf_coef_01 = yee->hpf_coef_01;
+ yee_param->hpf_coef_02 = yee->hpf_coef_02;
+ yee_param->hpf_coef_10 = yee->hpf_coef_10;
+ yee_param->hpf_coef_11 = yee->hpf_coef_11;
+ yee_param->hpf_coef_12 = yee->hpf_coef_12;
+ yee_param->hpf_coef_20 = yee->hpf_coef_20;
+ yee_param->hpf_coef_21 = yee->hpf_coef_21;
+ yee_param->hpf_coef_22 = yee->hpf_coef_22;
+ yee_param->yee_thr = yee->yee_thr;
+ yee_param->es_gain = yee->es_gain;
+ yee_param->es_thr1 = yee->es_thr1;
+ yee_param->es_thr2 = yee->es_thr2;
+ yee_param->es_gain_grad = yee->es_gain_grad;
+ yee_param->es_ofst_grad = yee->es_ofst_grad;
+ memcpy(yee_param->table, &yee->table,
+ (VPFE_IPIPE_MAX_SIZE_YEE_LUT * sizeof(short)));
+
+ return 0;
+}
+
+static int ipipe_validate_car_params(struct vpfe_ipipe_car *car)
+{
+ if (car->en > 1 || car->hpf_shft > CAR_HPF_SHIFT_MASK ||
+ car->gain1.shft > CAR_GAIN1_SHFT_MASK ||
+ car->gain1.gain_min > CAR_GAIN_MIN_MASK ||
+ car->gain2.shft > CAR_GAIN2_SHFT_MASK ||
+ car->gain2.gain_min > CAR_GAIN_MIN_MASK)
+ return -EINVAL;
+
+ return 0;
+}
+
+static int ipipe_set_car_params(struct vpfe_ipipe_device *ipipe, void *param)
+{
+ struct vpfe_ipipe_car *car_param = (struct vpfe_ipipe_car *)param;
+ struct device *dev = ipipe->subdev.v4l2_dev->dev;
+ struct vpfe_ipipe_car *car = &ipipe->config.car;
+
+ if (!car_param) {
+ memset(car , 0, sizeof(struct vpfe_ipipe_car));
+ } else {
+ memcpy(car, car_param, sizeof(struct vpfe_ipipe_car));
+ if (ipipe_validate_car_params(car) < 0) {
+ dev_err(dev, "Invalid car params\n");
+ return -EINVAL;
+ }
+ }
+
+ ipipe_set_car_regs(ipipe->base_addr, car);
+
+ return 0;
+}
+
+static int ipipe_get_car_params(struct vpfe_ipipe_device *ipipe, void *param)
+{
+ struct vpfe_ipipe_car *car_param = (struct vpfe_ipipe_car *)param;
+ struct vpfe_ipipe_car *car = &ipipe->config.car;
+
+ memcpy(car_param, car, sizeof(struct vpfe_ipipe_car));
+ return 0;
+}
+
+static int ipipe_validate_cgs_params(struct vpfe_ipipe_cgs *cgs)
+{
+ if (cgs->en > 1 || cgs->h_shft > CAR_SHIFT_MASK)
+ return -EINVAL;
+
+ return 0;
+}
+
+static int ipipe_set_cgs_params(struct vpfe_ipipe_device *ipipe, void *param)
+{
+ struct vpfe_ipipe_cgs *cgs_param = (struct vpfe_ipipe_cgs *)param;
+ struct device *dev = ipipe->subdev.v4l2_dev->dev;
+ struct vpfe_ipipe_cgs *cgs = &ipipe->config.cgs;
+
+ if (!cgs_param) {
+ memset(cgs, 0, sizeof(struct vpfe_ipipe_cgs));
+ } else {
+ memcpy(cgs, cgs_param, sizeof(struct vpfe_ipipe_cgs));
+ if (ipipe_validate_cgs_params(cgs) < 0) {
+ dev_err(dev, "Invalid cgs params\n");
+ return -EINVAL;
+ }
+ }
+
+ ipipe_set_cgs_regs(ipipe->base_addr, cgs);
+
+ return 0;
+}
+
+static int ipipe_get_cgs_params(struct vpfe_ipipe_device *ipipe, void *param)
+{
+ struct vpfe_ipipe_cgs *cgs_param = (struct vpfe_ipipe_cgs *)param;
+ struct vpfe_ipipe_cgs *cgs = &ipipe->config.cgs;
+
+ memcpy(cgs_param, cgs, sizeof(struct vpfe_ipipe_cgs));
+
+ return 0;
+}
+
+static const struct ipipe_module_if ipipe_modules[VPFE_IPIPE_MAX_MODULES] = {
+ /* VPFE_IPIPE_INPUT_CONFIG */ {
+ offsetof(struct ipipe_module_params, input_config),
+ FIELD_SIZEOF(struct ipipe_module_params, input_config),
+ offsetof(struct vpfe_ipipe_config, input_config),
+ ipipe_set_input_config,
+ ipipe_get_input_config,
+ }, /* VPFE_IPIPE_LUTDPC */ {
+ offsetof(struct ipipe_module_params, lutdpc),
+ FIELD_SIZEOF(struct ipipe_module_params, lutdpc),
+ offsetof(struct vpfe_ipipe_config, lutdpc),
+ ipipe_set_lutdpc_params,
+ ipipe_get_lutdpc_params,
+ }, /* VPFE_IPIPE_OTFDPC */ {
+ offsetof(struct ipipe_module_params, otfdpc),
+ FIELD_SIZEOF(struct ipipe_module_params, otfdpc),
+ offsetof(struct vpfe_ipipe_config, otfdpc),
+ ipipe_set_otfdpc_params,
+ ipipe_get_otfdpc_params,
+ }, /* VPFE_IPIPE_NF1 */ {
+ offsetof(struct ipipe_module_params, nf1),
+ FIELD_SIZEOF(struct ipipe_module_params, nf1),
+ offsetof(struct vpfe_ipipe_config, nf1),
+ ipipe_set_nf1_params,
+ ipipe_get_nf1_params,
+ }, /* VPFE_IPIPE_NF2 */ {
+ offsetof(struct ipipe_module_params, nf2),
+ FIELD_SIZEOF(struct ipipe_module_params, nf2),
+ offsetof(struct vpfe_ipipe_config, nf2),
+ ipipe_set_nf2_params,
+ ipipe_get_nf2_params,
+ }, /* VPFE_IPIPE_WB */ {
+ offsetof(struct ipipe_module_params, wbal),
+ FIELD_SIZEOF(struct ipipe_module_params, wbal),
+ offsetof(struct vpfe_ipipe_config, wbal),
+ ipipe_set_wb_params,
+ ipipe_get_wb_params,
+ }, /* VPFE_IPIPE_RGB2RGB_1 */ {
+ offsetof(struct ipipe_module_params, rgb2rgb1),
+ FIELD_SIZEOF(struct ipipe_module_params, rgb2rgb1),
+ offsetof(struct vpfe_ipipe_config, rgb2rgb1),
+ ipipe_set_rgb2rgb_1_params,
+ ipipe_get_rgb2rgb_1_params,
+ }, /* VPFE_IPIPE_RGB2RGB_2 */ {
+ offsetof(struct ipipe_module_params, rgb2rgb2),
+ FIELD_SIZEOF(struct ipipe_module_params, rgb2rgb2),
+ offsetof(struct vpfe_ipipe_config, rgb2rgb2),
+ ipipe_set_rgb2rgb_2_params,
+ ipipe_get_rgb2rgb_2_params,
+ }, /* VPFE_IPIPE_GAMMA */ {
+ offsetof(struct ipipe_module_params, gamma),
+ FIELD_SIZEOF(struct ipipe_module_params, gamma),
+ offsetof(struct vpfe_ipipe_config, gamma),
+ ipipe_set_gamma_params,
+ ipipe_get_gamma_params,
+ }, /* VPFE_IPIPE_3D_LUT */ {
+ offsetof(struct ipipe_module_params, lut),
+ FIELD_SIZEOF(struct ipipe_module_params, lut),
+ offsetof(struct vpfe_ipipe_config, lut),
+ ipipe_set_3d_lut_params,
+ ipipe_get_3d_lut_params,
+ }, /* VPFE_IPIPE_RGB2YUV */ {
+ offsetof(struct ipipe_module_params, rgb2yuv),
+ FIELD_SIZEOF(struct ipipe_module_params, rgb2yuv),
+ offsetof(struct vpfe_ipipe_config, rgb2yuv),
+ ipipe_set_rgb2yuv_params,
+ ipipe_get_rgb2yuv_params,
+ }, /* VPFE_IPIPE_YUV422_CONV */ {
+ offsetof(struct ipipe_module_params, yuv422_conv),
+ FIELD_SIZEOF(struct ipipe_module_params, yuv422_conv),
+ offsetof(struct vpfe_ipipe_config, yuv422_conv),
+ ipipe_set_yuv422_conv_params,
+ ipipe_get_yuv422_conv_params,
+ }, /* VPFE_IPIPE_YEE */ {
+ offsetof(struct ipipe_module_params, yee),
+ FIELD_SIZEOF(struct ipipe_module_params, yee),
+ offsetof(struct vpfe_ipipe_config, yee),
+ ipipe_set_yee_params,
+ ipipe_get_yee_params,
+ }, /* VPFE_IPIPE_GIC */ {
+ offsetof(struct ipipe_module_params, gic),
+ FIELD_SIZEOF(struct ipipe_module_params, gic),
+ offsetof(struct vpfe_ipipe_config, gic),
+ ipipe_set_gic_params,
+ ipipe_get_gic_params,
+ }, /* VPFE_IPIPE_CFA */ {
+ offsetof(struct ipipe_module_params, cfa),
+ FIELD_SIZEOF(struct ipipe_module_params, cfa),
+ offsetof(struct vpfe_ipipe_config, cfa),
+ ipipe_set_cfa_params,
+ ipipe_get_cfa_params,
+ }, /* VPFE_IPIPE_CAR */ {
+ offsetof(struct ipipe_module_params, car),
+ FIELD_SIZEOF(struct ipipe_module_params, car),
+ offsetof(struct vpfe_ipipe_config, car),
+ ipipe_set_car_params,
+ ipipe_get_car_params,
+ }, /* VPFE_IPIPE_CGS */ {
+ offsetof(struct ipipe_module_params, cgs),
+ FIELD_SIZEOF(struct ipipe_module_params, cgs),
+ offsetof(struct vpfe_ipipe_config, cgs),
+ ipipe_set_cgs_params,
+ ipipe_get_cgs_params,
+ }, /* VPFE_IPIPE_GBCE */ {
+ offsetof(struct ipipe_module_params, gbce),
+ FIELD_SIZEOF(struct ipipe_module_params, gbce),
+ offsetof(struct vpfe_ipipe_config, gbce),
+ ipipe_set_gbce_params,
+ ipipe_get_gbce_params,
+ },
+};
+
+static int ipipe_s_config(struct v4l2_subdev *sd, struct vpfe_ipipe_config *cfg)
+{
+ struct vpfe_ipipe_device *ipipe = v4l2_get_subdevdata(sd);
+ unsigned int i;
+ int rval = 0;
+
+ for (i = 0; i < ARRAY_SIZE(ipipe_modules); i++) {
+ unsigned int bit = 1 << i;
+ if (cfg->flag & bit) {
+ const struct ipipe_module_if *module_if =
+ &ipipe_modules[i];
+ struct ipipe_module_params *params;
+ void __user *from = *(void * __user *)
+ ((void *)cfg + module_if->config_offset);
+ size_t size;
+ void *to;
+
+ params = kmalloc(sizeof(struct ipipe_module_params),
+ GFP_KERNEL);
+ to = (void *)params + module_if->param_offset;
+ size = module_if->param_size;
+
+ if (to && from && size) {
+ if (copy_from_user(to, from, size)) {
+ rval = -EFAULT;
+ break;
+ }
+ rval = module_if->set(ipipe, to);
+ if (rval)
+ goto error;
+ } else if (to && !from && size) {
+ rval = module_if->set(ipipe, NULL);
+ if (rval)
+ goto error;
+ }
+ kfree(params);
+ }
+ }
+error:
+ return rval;
+}
+
+static int ipipe_g_config(struct v4l2_subdev *sd, struct vpfe_ipipe_config *cfg)
+{
+ struct vpfe_ipipe_device *ipipe = v4l2_get_subdevdata(sd);
+ unsigned int i;
+ int rval = 0;
+
+ for (i = 1; i < ARRAY_SIZE(ipipe_modules); i++) {
+ unsigned int bit = 1 << i;
+ if (cfg->flag & bit) {
+ const struct ipipe_module_if *module_if =
+ &ipipe_modules[i];
+ struct ipipe_module_params *params;
+ void __user *to = *(void * __user *)
+ ((void *)cfg + module_if->config_offset);
+ size_t size;
+ void *from;
+
+ params = kmalloc(sizeof(struct ipipe_module_params),
+ GFP_KERNEL);
+ from = (void *)params + module_if->param_offset;
+ size = module_if->param_size;
+
+ if (to && from && size) {
+ rval = module_if->get(ipipe, from);
+ if (rval)
+ goto error;
+ if (copy_to_user(to, from, size)) {
+ rval = -EFAULT;
+ break;
+ }
+ }
+ kfree(params);
+ }
+ }
+error:
+ return rval;
+}
+
+/*
+ * ipipe_ioctl() - Handle ipipe module private ioctl's
+ * @sd: pointer to v4l2 subdev structure
+ * @cmd: configuration command
+ * @arg: configuration argument
+ */
+static long ipipe_ioctl(struct v4l2_subdev *sd, unsigned int cmd, void *arg)
+{
+ int ret = 0;
+
+ switch (cmd) {
+ case VIDIOC_VPFE_IPIPE_S_CONFIG:
+ ret = ipipe_s_config(sd, arg);
+ break;
+
+ case VIDIOC_VPFE_IPIPE_G_CONFIG:
+ ret = ipipe_g_config(sd, arg);
+ break;
+
+ default:
+ ret = -ENOIOCTLCMD;
+ }
+ return ret;
+}
+
+void vpfe_ipipe_enable(struct vpfe_device *vpfe_dev, int en)
+{
+ struct vpfe_ipipeif_device *ipipeif = &vpfe_dev->vpfe_ipipeif;
+ struct vpfe_ipipe_device *ipipe = &vpfe_dev->vpfe_ipipe;
+ unsigned char val;
+
+ if (ipipe->input == IPIPE_INPUT_NONE)
+ return;
+
+ /* ipipe is set to single shot */
+ if (ipipeif->input == IPIPEIF_INPUT_MEMORY && en) {
+ /* for single-shot mode, need to wait for h/w to
+ * reset many register bits
+ */
+ do {
+ val = regr_ip(vpfe_dev->vpfe_ipipe.base_addr,
+ IPIPE_SRC_EN);
+ } while (val);
+ }
+ regw_ip(vpfe_dev->vpfe_ipipe.base_addr, en, IPIPE_SRC_EN);
+}
+
+/*
+ * ipipe_set_stream() - Enable/Disable streaming on the ipipe subdevice
+ * @sd: pointer to v4l2 subdev structure
+ * @enable: 1 == Enable, 0 == Disable
+ */
+static int ipipe_set_stream(struct v4l2_subdev *sd, int enable)
+{
+ struct vpfe_ipipe_device *ipipe = v4l2_get_subdevdata(sd);
+ struct vpfe_device *vpfe_dev = to_vpfe_device(ipipe);
+
+ if (enable && ipipe->input != IPIPE_INPUT_NONE &&
+ ipipe->output != IPIPE_OUTPUT_NONE) {
+ if (config_ipipe_hw(ipipe) < 0)
+ return -EINVAL;
+ }
+
+ vpfe_ipipe_enable(vpfe_dev, enable);
+
+ return 0;
+}
+
+/*
+ * __ipipe_get_format() - helper function for getting ipipe format
+ * @ipipe: pointer to ipipe private structure.
+ * @pad: pad number.
+ * @fh: V4L2 subdev file handle.
+ * @which: wanted subdev format.
+ *
+ */
+static struct v4l2_mbus_framefmt *
+__ipipe_get_format(struct vpfe_ipipe_device *ipipe,
+ struct v4l2_subdev_fh *fh, unsigned int pad,
+ enum v4l2_subdev_format_whence which)
+{
+ if (which == V4L2_SUBDEV_FORMAT_TRY)
+ return v4l2_subdev_get_try_format(fh, pad);
+
+ return &ipipe->formats[pad];
+}
+
+/*
+ * ipipe_try_format() - Handle try format by pad subdev method
+ * @ipipe: VPFE ipipe device.
+ * @fh: V4L2 subdev file handle.
+ * @pad: pad num.
+ * @fmt: pointer to v4l2 format structure.
+ * @which : wanted subdev format
+ */
+static void
+ipipe_try_format(struct vpfe_ipipe_device *ipipe,
+ struct v4l2_subdev_fh *fh, unsigned int pad,
+ struct v4l2_mbus_framefmt *fmt,
+ enum v4l2_subdev_format_whence which)
+{
+ unsigned int max_out_height;
+ unsigned int max_out_width;
+ unsigned int i;
+
+ max_out_width = IPIPE_MAX_OUTPUT_WIDTH_A;
+ max_out_height = IPIPE_MAX_OUTPUT_HEIGHT_A;
+
+ if (pad == IPIPE_PAD_SINK) {
+ for (i = 0; i < ARRAY_SIZE(ipipe_input_fmts); i++)
+ if (fmt->code == ipipe_input_fmts[i])
+ break;
+
+ /* If not found, use SBGGR10 as default */
+ if (i >= ARRAY_SIZE(ipipe_input_fmts))
+ fmt->code = V4L2_MBUS_FMT_SGRBG12_1X12;
+ } else if (pad == IPIPE_PAD_SOURCE) {
+ for (i = 0; i < ARRAY_SIZE(ipipe_output_fmts); i++)
+ if (fmt->code == ipipe_output_fmts[i])
+ break;
+
+ /* If not found, use UYVY as default */
+ if (i >= ARRAY_SIZE(ipipe_output_fmts))
+ fmt->code = V4L2_MBUS_FMT_UYVY8_2X8;
+ }
+
+ fmt->width = clamp_t(u32, fmt->width, MIN_OUT_HEIGHT, max_out_width);
+ fmt->height = clamp_t(u32, fmt->height, MIN_OUT_WIDTH, max_out_height);
+}
+
+/*
+ * ipipe_set_format() - Handle set format by pads subdev method
+ * @sd: pointer to v4l2 subdev structure
+ * @fh: V4L2 subdev file handle
+ * @fmt: pointer to v4l2 subdev format structure
+ * return -EINVAL or zero on success
+ */
+static int
+ipipe_set_format(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh,
+ struct v4l2_subdev_format *fmt)
+{
+ struct vpfe_ipipe_device *ipipe = v4l2_get_subdevdata(sd);
+ struct v4l2_mbus_framefmt *format;
+
+ format = __ipipe_get_format(ipipe, fh, fmt->pad, fmt->which);
+ if (format == NULL)
+ return -EINVAL;
+
+ ipipe_try_format(ipipe, fh, fmt->pad, &fmt->format, fmt->which);
+ *format = fmt->format;
+
+ if (fmt->which == V4L2_SUBDEV_FORMAT_TRY)
+ return 0;
+
+ if (fmt->pad == IPIPE_PAD_SINK &&
+ (ipipe->input == IPIPE_INPUT_CCDC ||
+ ipipe->input == IPIPE_INPUT_MEMORY))
+ ipipe->formats[fmt->pad] = fmt->format;
+ else if (fmt->pad == IPIPE_PAD_SOURCE &&
+ ipipe->output == IPIPE_OUTPUT_RESIZER)
+ ipipe->formats[fmt->pad] = fmt->format;
+ else
+ return -EINVAL;
+
+ return 0;
+}
+
+/*
+ * ipipe_get_format() - Handle get format by pads subdev method.
+ * @sd: pointer to v4l2 subdev structure.
+ * @fh: V4L2 subdev file handle.
+ * @fmt: pointer to v4l2 subdev format structure.
+ */
+static int
+ipipe_get_format(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh,
+ struct v4l2_subdev_format *fmt)
+{
+ struct vpfe_ipipe_device *ipipe = v4l2_get_subdevdata(sd);
+
+ if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE)
+ fmt->format = ipipe->formats[fmt->pad];
+ else
+ fmt->format = *(v4l2_subdev_get_try_format(fh, fmt->pad));
+
+ return 0;
+}
+
+/*
+ * ipipe_enum_frame_size() - enum frame sizes on pads
+ * @sd: pointer to v4l2 subdev structure.
+ * @fh: V4L2 subdev file handle.
+ * @fse: pointer to v4l2_subdev_frame_size_enum structure.
+ */
+static int
+ipipe_enum_frame_size(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh,
+ struct v4l2_subdev_frame_size_enum *fse)
+{
+ struct vpfe_ipipe_device *ipipe = v4l2_get_subdevdata(sd);
+ struct v4l2_mbus_framefmt format;
+
+ if (fse->index != 0)
+ return -EINVAL;
+
+ format.code = fse->code;
+ format.width = 1;
+ format.height = 1;
+ ipipe_try_format(ipipe, fh, fse->pad, &format,
+ V4L2_SUBDEV_FORMAT_TRY);
+ fse->min_width = format.width;
+ fse->min_height = format.height;
+
+ if (format.code != fse->code)
+ return -EINVAL;
+
+ format.code = fse->code;
+ format.width = -1;
+ format.height = -1;
+ ipipe_try_format(ipipe, fh, fse->pad, &format,
+ V4L2_SUBDEV_FORMAT_TRY);
+ fse->max_width = format.width;
+ fse->max_height = format.height;
+
+ return 0;
+}
+
+/*
+ * ipipe_enum_mbus_code() - enum mbus codes for pads
+ * @sd: pointer to v4l2 subdev structure.
+ * @fh: V4L2 subdev file handle
+ * @code: pointer to v4l2_subdev_mbus_code_enum structure
+ */
+static int
+ipipe_enum_mbus_code(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh,
+ struct v4l2_subdev_mbus_code_enum *code)
+{
+ switch (code->pad) {
+ case IPIPE_PAD_SINK:
+ if (code->index >= ARRAY_SIZE(ipipe_input_fmts))
+ return -EINVAL;
+ code->code = ipipe_input_fmts[code->index];
+ break;
+
+ case IPIPE_PAD_SOURCE:
+ if (code->index >= ARRAY_SIZE(ipipe_output_fmts))
+ return -EINVAL;
+ code->code = ipipe_output_fmts[code->index];
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/*
+ * ipipe_s_ctrl() - Handle set control subdev method
+ * @ctrl: pointer to v4l2 control structure
+ */
+static int ipipe_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+ struct vpfe_ipipe_device *ipipe =
+ container_of(ctrl->handler, struct vpfe_ipipe_device, ctrls);
+ struct ipipe_lum_adj *lum_adj = &ipipe->config.lum_adj;
+
+ switch (ctrl->id) {
+ case V4L2_CID_BRIGHTNESS:
+ lum_adj->brightness = ctrl->val;
+ ipipe_set_lum_adj_regs(ipipe->base_addr, lum_adj);
+ break;
+
+ case V4L2_CID_CONTRAST:
+ lum_adj->contrast = ctrl->val;
+ ipipe_set_lum_adj_regs(ipipe->base_addr, lum_adj);
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/*
+ * ipipe_init_formats() - Initialize formats on all pads
+ * @sd: pointer to v4l2 subdev structure.
+ * @fh: V4L2 subdev file handle
+ *
+ * Initialize all pad formats with default values. If fh is not NULL, try
+ * formats are initialized on the file handle. Otherwise active formats are
+ * initialized on the device.
+ */
+static int
+ipipe_init_formats(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh)
+{
+ struct v4l2_subdev_format format;
+
+ memset(&format, 0, sizeof(format));
+ format.pad = IPIPE_PAD_SINK;
+ format.which = fh ? V4L2_SUBDEV_FORMAT_TRY : V4L2_SUBDEV_FORMAT_ACTIVE;
+ format.format.code = V4L2_MBUS_FMT_SGRBG12_1X12;
+ format.format.width = IPIPE_MAX_OUTPUT_WIDTH_A;
+ format.format.height = IPIPE_MAX_OUTPUT_HEIGHT_A;
+ ipipe_set_format(sd, fh, &format);
+
+ memset(&format, 0, sizeof(format));
+ format.pad = IPIPE_PAD_SOURCE;
+ format.which = fh ? V4L2_SUBDEV_FORMAT_TRY : V4L2_SUBDEV_FORMAT_ACTIVE;
+ format.format.code = V4L2_MBUS_FMT_UYVY8_2X8;
+ format.format.width = IPIPE_MAX_OUTPUT_WIDTH_A;
+ format.format.height = IPIPE_MAX_OUTPUT_HEIGHT_A;
+ ipipe_set_format(sd, fh, &format);
+
+ return 0;
+}
+
+/* subdev core operations */
+static const struct v4l2_subdev_core_ops ipipe_v4l2_core_ops = {
+ .ioctl = ipipe_ioctl,
+};
+
+static const struct v4l2_ctrl_ops ipipe_ctrl_ops = {
+ .s_ctrl = ipipe_s_ctrl,
+};
+
+/* subdev file operations */
+static const struct v4l2_subdev_internal_ops ipipe_v4l2_internal_ops = {
+ .open = ipipe_init_formats,
+};
+
+/* subdev video operations */
+static const struct v4l2_subdev_video_ops ipipe_v4l2_video_ops = {
+ .s_stream = ipipe_set_stream,
+};
+
+/* subdev pad operations */
+static const struct v4l2_subdev_pad_ops ipipe_v4l2_pad_ops = {
+ .enum_mbus_code = ipipe_enum_mbus_code,
+ .enum_frame_size = ipipe_enum_frame_size,
+ .get_fmt = ipipe_get_format,
+ .set_fmt = ipipe_set_format,
+};
+
+/* v4l2 subdev operation */
+static const struct v4l2_subdev_ops ipipe_v4l2_ops = {
+ .core = &ipipe_v4l2_core_ops,
+ .video = &ipipe_v4l2_video_ops,
+ .pad = &ipipe_v4l2_pad_ops,
+};
+
+/*
+ * Media entity operations
+ */
+
+/*
+ * ipipe_link_setup() - Setup ipipe connections
+ * @entity: ipipe media entity
+ * @local: Pad at the local end of the link
+ * @remote: Pad at the remote end of the link
+ * @flags: Link flags
+ *
+ * return -EINVAL or zero on success
+ */
+static int
+ipipe_link_setup(struct media_entity *entity, const struct media_pad *local,
+ const struct media_pad *remote, u32 flags)
+{
+ struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(entity);
+ struct vpfe_ipipe_device *ipipe = v4l2_get_subdevdata(sd);
+ struct vpfe_device *vpfe_dev = to_vpfe_device(ipipe);
+ u16 ipipeif_sink = vpfe_dev->vpfe_ipipeif.input;
+
+ switch (local->index | media_entity_type(remote->entity)) {
+ case IPIPE_PAD_SINK | MEDIA_ENT_T_V4L2_SUBDEV:
+ if (!(flags & MEDIA_LNK_FL_ENABLED)) {
+ ipipe->input = IPIPE_INPUT_NONE;
+ break;
+ }
+ if (ipipe->input != IPIPE_INPUT_NONE)
+ return -EBUSY;
+ if (ipipeif_sink == IPIPEIF_INPUT_MEMORY)
+ ipipe->input = IPIPE_INPUT_MEMORY;
+ else
+ ipipe->input = IPIPE_INPUT_CCDC;
+ break;
+
+ case IPIPE_PAD_SOURCE | MEDIA_ENT_T_V4L2_SUBDEV:
+ /* out to RESIZER */
+ if (flags & MEDIA_LNK_FL_ENABLED)
+ ipipe->output = IPIPE_OUTPUT_RESIZER;
+ else
+ ipipe->output = IPIPE_OUTPUT_NONE;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static const struct media_entity_operations ipipe_media_ops = {
+ .link_setup = ipipe_link_setup,
+};
+
+/*
+ * vpfe_ipipe_unregister_entities() - ipipe unregister entity
+ * @vpfe_ipipe: pointer to ipipe subdevice structure.
+ */
+void vpfe_ipipe_unregister_entities(struct vpfe_ipipe_device *vpfe_ipipe)
+{
+ /* unregister subdev */
+ v4l2_device_unregister_subdev(&vpfe_ipipe->subdev);
+ /* cleanup entity */
+ media_entity_cleanup(&vpfe_ipipe->subdev.entity);
+}
+
+/*
+ * vpfe_ipipe_register_entities() - ipipe register entity
+ * @ipipe: pointer to ipipe subdevice structure.
+ * @vdev: pointer to v4l2 device structure.
+ */
+int
+vpfe_ipipe_register_entities(struct vpfe_ipipe_device *ipipe,
+ struct v4l2_device *vdev)
+{
+ int ret;
+
+ /* Register the subdev */
+ ret = v4l2_device_register_subdev(vdev, &ipipe->subdev);
+ if (ret) {
+ pr_err("Failed to register ipipe as v4l2 subdevice\n");
+ return ret;
+ }
+
+ return ret;
+}
+
+#define IPIPE_CONTRAST_HIGH 0xff
+#define IPIPE_BRIGHT_HIGH 0xff
+
+/*
+ * vpfe_ipipe_init() - ipipe module initialization.
+ * @ipipe: pointer to ipipe subdevice structure.
+ * @pdev: platform device pointer.
+ */
+int
+vpfe_ipipe_init(struct vpfe_ipipe_device *ipipe, struct platform_device *pdev)
+{
+ struct media_pad *pads = &ipipe->pads[0];
+ struct v4l2_subdev *sd = &ipipe->subdev;
+ struct media_entity *me = &sd->entity;
+ static resource_size_t res_len;
+ struct resource *res;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 4);
+ if (!res)
+ return -ENOENT;
+
+ res_len = resource_size(res);
+ res = request_mem_region(res->start, res_len, res->name);
+ if (!res)
+ return -EBUSY;
+ ipipe->base_addr = ioremap_nocache(res->start, res_len);
+ if (!ipipe->base_addr)
+ return -EBUSY;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 6);
+ if (!res)
+ return -ENOENT;
+ ipipe->isp5_base_addr = ioremap_nocache(res->start, res_len);
+ if (!ipipe->isp5_base_addr)
+ return -EBUSY;
+
+ v4l2_subdev_init(sd, &ipipe_v4l2_ops);
+ sd->internal_ops = &ipipe_v4l2_internal_ops;
+ strlcpy(sd->name, "DAVINCI IPIPE", sizeof(sd->name));
+ sd->grp_id = 1 << 16; /* group ID for davinci subdevs */
+ v4l2_set_subdevdata(sd, ipipe);
+ sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
+
+ pads[IPIPE_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
+ pads[IPIPE_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
+
+ ipipe->input = IPIPE_INPUT_NONE;
+ ipipe->output = IPIPE_OUTPUT_NONE;
+
+ me->ops = &ipipe_media_ops;
+ v4l2_ctrl_handler_init(&ipipe->ctrls, 2);
+ v4l2_ctrl_new_std(&ipipe->ctrls, &ipipe_ctrl_ops,
+ V4L2_CID_BRIGHTNESS, 0,
+ IPIPE_BRIGHT_HIGH, 1, 16);
+ v4l2_ctrl_new_std(&ipipe->ctrls, &ipipe_ctrl_ops,
+ V4L2_CID_CONTRAST, 0,
+ IPIPE_CONTRAST_HIGH, 1, 16);
+
+
+ v4l2_ctrl_handler_setup(&ipipe->ctrls);
+ sd->ctrl_handler = &ipipe->ctrls;
+
+ return media_entity_init(me, IPIPE_PADS_NUM, pads, 0);
+}
+
+/*
+ * vpfe_ipipe_cleanup() - ipipe subdevice cleanup.
+ * @ipipe: pointer to ipipe subdevice
+ * @dev: pointer to platform device
+ */
+void vpfe_ipipe_cleanup(struct vpfe_ipipe_device *ipipe,
+ struct platform_device *pdev)
+{
+ struct resource *res;
+
+ v4l2_ctrl_handler_free(&ipipe->ctrls);
+
+ iounmap(ipipe->base_addr);
+ iounmap(ipipe->isp5_base_addr);
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 4);
+ if (res)
+ release_mem_region(res->start, resource_size(res));
+}
diff --git a/drivers/staging/media/davinci_vpfe/dm365_ipipe.h b/drivers/staging/media/davinci_vpfe/dm365_ipipe.h
new file mode 100644
index 00000000000..cf4204603eb
--- /dev/null
+++ b/drivers/staging/media/davinci_vpfe/dm365_ipipe.h
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2012 Texas Instruments Inc
+ *
+ * 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 version 2.
+ *
+ * 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
+ *
+ * Contributors:
+ * Manjunath Hadli <manjunath.hadli@ti.com>
+ * Prabhakar Lad <prabhakar.lad@ti.com>
+ */
+
+#ifndef _DAVINCI_VPFE_DM365_IPIPE_H
+#define _DAVINCI_VPFE_DM365_IPIPE_H
+
+#include <linux/platform_device.h>
+
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-subdev.h>
+
+#include "davinci_vpfe_user.h"
+#include "vpfe_video.h"
+
+#define CEIL(a, b) (((a) + (b-1)) / (b))
+
+enum ipipe_noise_filter {
+ IPIPE_D2F_1ST = 0,
+ IPIPE_D2F_2ND = 1,
+};
+
+/* Used for driver storage */
+struct ipipe_otfdpc_2_0 {
+ /* 0 - disable, 1 - enable */
+ unsigned char en;
+ /* defect detection method */
+ enum vpfe_ipipe_otfdpc_det_meth det_method;
+ /* Algorithm used. Applicable only when IPIPE_DPC_OTF_MIN_MAX2 is
+ * used
+ */
+ enum vpfe_ipipe_otfdpc_alg alg;
+ struct vpfe_ipipe_otfdpc_2_0_cfg otfdpc_2_0;
+};
+
+struct ipipe_otfdpc_3_0 {
+ /* 0 - disable, 1 - enable */
+ unsigned char en;
+ /* defect detection method */
+ enum vpfe_ipipe_otfdpc_det_meth det_method;
+ /* Algorithm used. Applicable only when IPIPE_DPC_OTF_MIN_MAX2 is
+ * used
+ */
+ enum vpfe_ipipe_otfdpc_alg alg;
+ struct vpfe_ipipe_otfdpc_3_0_cfg otfdpc_3_0;
+};
+
+/* Structure for configuring Luminance Adjustment module */
+struct ipipe_lum_adj {
+ /* Brightness adjustments */
+ unsigned char brightness;
+ /* contrast adjustments */
+ unsigned char contrast;
+};
+
+enum ipipe_rgb2rgb {
+ IPIPE_RGB2RGB_1 = 0,
+ IPIPE_RGB2RGB_2 = 1,
+};
+
+struct ipipe_module_params {
+ __u32 flag;
+ struct vpfe_ipipe_input_config input_config;
+ struct vpfe_ipipe_lutdpc lutdpc;
+ struct vpfe_ipipe_otfdpc otfdpc;
+ struct vpfe_ipipe_nf nf1;
+ struct vpfe_ipipe_nf nf2;
+ struct vpfe_ipipe_gic gic;
+ struct vpfe_ipipe_wb wbal;
+ struct vpfe_ipipe_cfa cfa;
+ struct vpfe_ipipe_rgb2rgb rgb2rgb1;
+ struct vpfe_ipipe_rgb2rgb rgb2rgb2;
+ struct vpfe_ipipe_gamma gamma;
+ struct vpfe_ipipe_3d_lut lut;
+ struct vpfe_ipipe_rgb2yuv rgb2yuv;
+ struct vpfe_ipipe_gbce gbce;
+ struct vpfe_ipipe_yuv422_conv yuv422_conv;
+ struct vpfe_ipipe_yee yee;
+ struct vpfe_ipipe_car car;
+ struct vpfe_ipipe_cgs cgs;
+ struct ipipe_lum_adj lum_adj;
+};
+
+#define IPIPE_PAD_SINK 0
+#define IPIPE_PAD_SOURCE 1
+
+#define IPIPE_PADS_NUM 2
+
+#define IPIPE_OUTPUT_NONE 0
+#define IPIPE_OUTPUT_RESIZER (1 << 0)
+
+enum ipipe_input_entity {
+ IPIPE_INPUT_NONE = 0,
+ IPIPE_INPUT_MEMORY = 1,
+ IPIPE_INPUT_CCDC = 2,
+};
+
+
+struct vpfe_ipipe_device {
+ struct v4l2_subdev subdev;
+ struct media_pad pads[IPIPE_PADS_NUM];
+ struct v4l2_mbus_framefmt formats[IPIPE_PADS_NUM];
+ enum ipipe_input_entity input;
+ unsigned int output;
+ struct v4l2_ctrl_handler ctrls;
+ void *__iomem base_addr;
+ void *__iomem isp5_base_addr;
+ struct ipipe_module_params config;
+};
+
+struct ipipe_module_if {
+ unsigned int param_offset;
+ unsigned int param_size;
+ unsigned int config_offset;
+ int (*set)(struct vpfe_ipipe_device *ipipe, void *param);
+ int (*get)(struct vpfe_ipipe_device *ipipe, void *param);
+};
+
+/* data paths */
+enum ipipe_data_paths {
+ IPIPE_RAW2YUV,
+ /* Bayer RAW input to YCbCr output */
+ IPIPE_RAW2RAW,
+ /* Bayer Raw to Bayer output */
+ IPIPE_RAW2BOX,
+ /* Bayer Raw to Boxcar output */
+ IPIPE_YUV2YUV
+ /* YUV Raw to YUV Raw output */
+};
+
+#define IPIPE_COLPTN_R_Ye 0x0
+#define IPIPE_COLPTN_Gr_Cy 0x1
+#define IPIPE_COLPTN_Gb_G 0x2
+#define IPIPE_COLPTN_B_Mg 0x3
+
+#define COLPAT_EE_SHIFT 0
+#define COLPAT_EO_SHIFT 2
+#define COLPAT_OE_SHIFT 4
+#define COLPAT_OO_SHIFT 6
+
+#define ipipe_sgrbg_pattern \
+ (IPIPE_COLPTN_Gr_Cy << COLPAT_EE_SHIFT | \
+ IPIPE_COLPTN_R_Ye << COLPAT_EO_SHIFT | \
+ IPIPE_COLPTN_B_Mg << COLPAT_OE_SHIFT | \
+ IPIPE_COLPTN_Gb_G << COLPAT_OO_SHIFT)
+
+#define ipipe_srggb_pattern \
+ (IPIPE_COLPTN_R_Ye << COLPAT_EE_SHIFT | \
+ IPIPE_COLPTN_Gr_Cy << COLPAT_EO_SHIFT | \
+ IPIPE_COLPTN_Gb_G << COLPAT_OE_SHIFT | \
+ IPIPE_COLPTN_B_Mg << COLPAT_OO_SHIFT)
+
+int vpfe_ipipe_register_entities(struct vpfe_ipipe_device *ipipe,
+ struct v4l2_device *v4l2_dev);
+int vpfe_ipipe_init(struct vpfe_ipipe_device *ipipe,
+ struct platform_device *pdev);
+void vpfe_ipipe_unregister_entities(struct vpfe_ipipe_device *ipipe);
+void vpfe_ipipe_cleanup(struct vpfe_ipipe_device *ipipe,
+ struct platform_device *pdev);
+void vpfe_ipipe_enable(struct vpfe_device *vpfe_dev, int en);
+
+#endif /* _DAVINCI_VPFE_DM365_IPIPE_H */
diff --git a/drivers/staging/media/davinci_vpfe/dm365_ipipe_hw.c b/drivers/staging/media/davinci_vpfe/dm365_ipipe_hw.c
new file mode 100644
index 00000000000..b2daf5e63f8
--- /dev/null
+++ b/drivers/staging/media/davinci_vpfe/dm365_ipipe_hw.c
@@ -0,0 +1,1048 @@
+/*
+ * Copyright (C) 2012 Texas Instruments Inc
+ *
+ * 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 version 2.
+ *
+ * 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
+ *
+ * Contributors:
+ * Manjunath Hadli <manjunath.hadli@ti.com>
+ * Prabhakar Lad <prabhakar.lad@ti.com>
+ */
+
+#include "dm365_ipipe_hw.h"
+
+#define IPIPE_MODE_CONTINUOUS 0
+#define IPIPE_MODE_SINGLE_SHOT 1
+
+static void ipipe_clock_enable(void *__iomem base_addr)
+{
+ /* enable IPIPE MMR for register write access */
+ regw_ip(base_addr, IPIPE_GCK_MMR_DEFAULT, IPIPE_GCK_MMR);
+
+ /* enable the clock wb,cfa,dfc,d2f,pre modules */
+ regw_ip(base_addr, IPIPE_GCK_PIX_DEFAULT, IPIPE_GCK_PIX);
+}
+
+static void
+rsz_set_common_params(void *__iomem rsz_base, struct resizer_params *params)
+{
+ struct rsz_common_params *rsz_common = &params->rsz_common;
+ u32 val;
+
+ /* Set mode */
+ regw_rsz(rsz_base, params->oper_mode, RSZ_SRC_MODE);
+
+ /* data source selection and bypass */
+ val = (rsz_common->passthrough << RSZ_BYPASS_SHIFT) |
+ rsz_common->source;
+ regw_rsz(rsz_base, val, RSZ_SRC_FMT0);
+
+ /* src image selection */
+ val = (rsz_common->raw_flip & 1) |
+ (rsz_common->src_img_fmt << RSZ_SRC_IMG_FMT_SHIFT) |
+ ((rsz_common->y_c & 1) << RSZ_SRC_Y_C_SEL_SHIFT);
+ regw_rsz(rsz_base, val, RSZ_SRC_FMT1);
+
+ regw_rsz(rsz_base, rsz_common->vps & IPIPE_RSZ_VPS_MASK, RSZ_SRC_VPS);
+ regw_rsz(rsz_base, rsz_common->hps & IPIPE_RSZ_HPS_MASK, RSZ_SRC_HPS);
+ regw_rsz(rsz_base, rsz_common->vsz & IPIPE_RSZ_VSZ_MASK, RSZ_SRC_VSZ);
+ regw_rsz(rsz_base, rsz_common->hsz & IPIPE_RSZ_HSZ_MASK, RSZ_SRC_HSZ);
+ regw_rsz(rsz_base, rsz_common->yuv_y_min, RSZ_YUV_Y_MIN);
+ regw_rsz(rsz_base, rsz_common->yuv_y_max, RSZ_YUV_Y_MAX);
+ regw_rsz(rsz_base, rsz_common->yuv_c_min, RSZ_YUV_C_MIN);
+ regw_rsz(rsz_base, rsz_common->yuv_c_max, RSZ_YUV_C_MAX);
+ /* chromatic position */
+ regw_rsz(rsz_base, rsz_common->out_chr_pos, RSZ_YUV_PHS);
+}
+
+static void
+rsz_set_rsz_regs(void *__iomem rsz_base, unsigned int rsz_id,
+ struct resizer_params *params)
+{
+ struct resizer_scale_param *rsc_params;
+ struct rsz_ext_mem_param *ext_mem;
+ struct resizer_rgb *rgb;
+ u32 reg_base;
+ u32 val;
+
+ rsc_params = &params->rsz_rsc_param[rsz_id];
+ rgb = &params->rsz2rgb[rsz_id];
+ ext_mem = &params->ext_mem_param[rsz_id];
+
+ if (rsz_id == RSZ_A) {
+ val = rsc_params->h_flip << RSZA_H_FLIP_SHIFT;
+ val |= rsc_params->v_flip << RSZA_V_FLIP_SHIFT;
+ reg_base = RSZ_EN_A;
+ } else {
+ val = rsc_params->h_flip << RSZB_H_FLIP_SHIFT;
+ val |= rsc_params->v_flip << RSZB_V_FLIP_SHIFT;
+ reg_base = RSZ_EN_B;
+ }
+ /* update flip settings */
+ regw_rsz(rsz_base, val, RSZ_SEQ);
+
+ regw_rsz(rsz_base, params->oper_mode, reg_base + RSZ_MODE);
+
+ val = (rsc_params->cen << RSZ_CEN_SHIFT) | rsc_params->yen;
+ regw_rsz(rsz_base, val, reg_base + RSZ_420);
+
+ regw_rsz(rsz_base, rsc_params->i_vps & RSZ_VPS_MASK,
+ reg_base + RSZ_I_VPS);
+ regw_rsz(rsz_base, rsc_params->i_hps & RSZ_HPS_MASK,
+ reg_base + RSZ_I_HPS);
+ regw_rsz(rsz_base, rsc_params->o_vsz & RSZ_O_VSZ_MASK,
+ reg_base + RSZ_O_VSZ);
+ regw_rsz(rsz_base, rsc_params->o_hsz & RSZ_O_HSZ_MASK,
+ reg_base + RSZ_O_HSZ);
+ regw_rsz(rsz_base, rsc_params->v_phs_y & RSZ_V_PHS_MASK,
+ reg_base + RSZ_V_PHS_Y);
+ regw_rsz(rsz_base, rsc_params->v_phs_c & RSZ_V_PHS_MASK,
+ reg_base + RSZ_V_PHS_C);
+
+ /* keep this additional adjustment to zero for now */
+ regw_rsz(rsz_base, rsc_params->v_dif & RSZ_V_DIF_MASK,
+ reg_base + RSZ_V_DIF);
+
+ val = (rsc_params->v_typ_y & 1) |
+ ((rsc_params->v_typ_c & 1) << RSZ_TYP_C_SHIFT);
+ regw_rsz(rsz_base, val, reg_base + RSZ_V_TYP);
+
+ val = (rsc_params->v_lpf_int_y & RSZ_LPF_INT_MASK) |
+ ((rsc_params->v_lpf_int_c & RSZ_LPF_INT_MASK) <<
+ RSZ_LPF_INT_C_SHIFT);
+ regw_rsz(rsz_base, val, reg_base + RSZ_V_LPF);
+
+ regw_rsz(rsz_base, rsc_params->h_phs &
+ RSZ_H_PHS_MASK, reg_base + RSZ_H_PHS);
+
+ regw_rsz(rsz_base, 0, reg_base + RSZ_H_PHS_ADJ);
+ regw_rsz(rsz_base, rsc_params->h_dif &
+ RSZ_H_DIF_MASK, reg_base + RSZ_H_DIF);
+
+ val = (rsc_params->h_typ_y & 1) |
+ ((rsc_params->h_typ_c & 1) << RSZ_TYP_C_SHIFT);
+ regw_rsz(rsz_base, val, reg_base + RSZ_H_TYP);
+
+ val = (rsc_params->h_lpf_int_y & RSZ_LPF_INT_MASK) |
+ ((rsc_params->h_lpf_int_c & RSZ_LPF_INT_MASK) <<
+ RSZ_LPF_INT_C_SHIFT);
+ regw_rsz(rsz_base, val, reg_base + RSZ_H_LPF);
+
+ regw_rsz(rsz_base, rsc_params->dscale_en & 1, reg_base + RSZ_DWN_EN);
+
+ val = (rsc_params->h_dscale_ave_sz & RSZ_DWN_SCALE_AV_SZ_MASK) |
+ ((rsc_params->v_dscale_ave_sz & RSZ_DWN_SCALE_AV_SZ_MASK) <<
+ RSZ_DWN_SCALE_AV_SZ_V_SHIFT);
+ regw_rsz(rsz_base, val, reg_base + RSZ_DWN_AV);
+
+ /* setting rgb conversion parameters */
+ regw_rsz(rsz_base, rgb->rgb_en, reg_base + RSZ_RGB_EN);
+
+ val = (rgb->rgb_typ << RSZ_RGB_TYP_SHIFT) |
+ (rgb->rgb_msk0 << RSZ_RGB_MSK0_SHIFT) |
+ (rgb->rgb_msk1 << RSZ_RGB_MSK1_SHIFT);
+ regw_rsz(rsz_base, val, reg_base + RSZ_RGB_TYP);
+
+ regw_rsz(rsz_base, rgb->rgb_alpha_val & RSZ_RGB_ALPHA_MASK,
+ reg_base + RSZ_RGB_BLD);
+
+ /* setting external memory parameters */
+ regw_rsz(rsz_base, ext_mem->rsz_sdr_oft_y, reg_base + RSZ_SDR_Y_OFT);
+ regw_rsz(rsz_base, ext_mem->rsz_sdr_ptr_s_y,
+ reg_base + RSZ_SDR_Y_PTR_S);
+ regw_rsz(rsz_base, ext_mem->rsz_sdr_ptr_e_y,
+ reg_base + RSZ_SDR_Y_PTR_E);
+ regw_rsz(rsz_base, ext_mem->rsz_sdr_oft_c, reg_base + RSZ_SDR_C_OFT);
+ regw_rsz(rsz_base, ext_mem->rsz_sdr_ptr_s_c,
+ reg_base + RSZ_SDR_C_PTR_S);
+ regw_rsz(rsz_base, (ext_mem->rsz_sdr_ptr_e_c >> 1),
+ reg_base + RSZ_SDR_C_PTR_E);
+}
+
+/*set the registers of either RSZ0 or RSZ1 */
+static void
+ipipe_setup_resizer(void *__iomem rsz_base, struct resizer_params *params)
+{
+ /* enable MMR gate to write to Resizer */
+ regw_rsz(rsz_base, 1, RSZ_GCK_MMR);
+
+ /* Enable resizer if it is not in bypass mode */
+ if (params->rsz_common.passthrough)
+ regw_rsz(rsz_base, 0, RSZ_GCK_SDR);
+ else
+ regw_rsz(rsz_base, 1, RSZ_GCK_SDR);
+
+ rsz_set_common_params(rsz_base, params);
+
+ regw_rsz(rsz_base, params->rsz_en[RSZ_A], RSZ_EN_A);
+
+ if (params->rsz_en[RSZ_A])
+ /*setting rescale parameters */
+ rsz_set_rsz_regs(rsz_base, RSZ_A, params);
+
+ regw_rsz(rsz_base, params->rsz_en[RSZ_B], RSZ_EN_B);
+
+ if (params->rsz_en[RSZ_B])
+ rsz_set_rsz_regs(rsz_base, RSZ_B, params);
+}
+
+static u32 ipipe_get_color_pat(enum v4l2_mbus_pixelcode pix)
+{
+ switch (pix) {
+ case V4L2_MBUS_FMT_SGRBG10_ALAW8_1X8:
+ case V4L2_MBUS_FMT_SGRBG10_DPCM8_1X8:
+ case V4L2_MBUS_FMT_SGRBG12_1X12:
+ return ipipe_sgrbg_pattern;
+
+ default:
+ return ipipe_srggb_pattern;
+ }
+}
+
+static int ipipe_get_data_path(struct vpfe_ipipe_device *ipipe)
+{
+ enum v4l2_mbus_pixelcode temp_pix_fmt;
+
+ switch (ipipe->formats[IPIPE_PAD_SINK].code) {
+ case V4L2_MBUS_FMT_SBGGR8_1X8:
+ case V4L2_MBUS_FMT_SGRBG10_ALAW8_1X8:
+ case V4L2_MBUS_FMT_SGRBG10_DPCM8_1X8:
+ case V4L2_MBUS_FMT_SGRBG12_1X12:
+ temp_pix_fmt = V4L2_MBUS_FMT_SGRBG12_1X12;
+ break;
+
+ default:
+ temp_pix_fmt = V4L2_MBUS_FMT_UYVY8_2X8;
+ }
+
+ if (temp_pix_fmt == V4L2_MBUS_FMT_SGRBG12_1X12) {
+ if (ipipe->formats[IPIPE_PAD_SOURCE].code ==
+ V4L2_MBUS_FMT_SGRBG12_1X12)
+ return IPIPE_RAW2RAW;
+ return IPIPE_RAW2YUV;
+ }
+
+ return IPIPE_YUV2YUV;
+}
+
+static int get_ipipe_mode(struct vpfe_ipipe_device *ipipe)
+{
+ struct vpfe_device *vpfe_dev = to_vpfe_device(ipipe);
+ u16 ipipeif_sink = vpfe_dev->vpfe_ipipeif.input;
+
+ if (ipipeif_sink == IPIPEIF_INPUT_MEMORY)
+ return IPIPE_MODE_SINGLE_SHOT;
+ else if (ipipeif_sink == IPIPEIF_INPUT_ISIF)
+ return IPIPE_MODE_CONTINUOUS;
+
+ return -EINVAL;
+}
+
+int config_ipipe_hw(struct vpfe_ipipe_device *ipipe)
+{
+ struct vpfe_ipipe_input_config *config = &ipipe->config.input_config;
+ void __iomem *ipipe_base = ipipe->base_addr;
+ struct v4l2_mbus_framefmt *outformat;
+ u32 color_pat;
+ u32 ipipe_mode;
+ u32 data_path;
+
+ /* enable clock to IPIPE */
+ vpss_enable_clock(VPSS_IPIPE_CLOCK, 1);
+ ipipe_clock_enable(ipipe_base);
+
+ if (ipipe->input == IPIPE_INPUT_NONE) {
+ regw_ip(ipipe_base, 0, IPIPE_SRC_EN);
+ return 0;
+ }
+
+ ipipe_mode = get_ipipe_mode(ipipe);
+ if (ipipe_mode < 0) {
+ pr_err("Failed to get ipipe mode");
+ return -EINVAL;
+ }
+ regw_ip(ipipe_base, ipipe_mode, IPIPE_SRC_MODE);
+
+ data_path = ipipe_get_data_path(ipipe);
+ regw_ip(ipipe_base, data_path, IPIPE_SRC_FMT);
+
+ regw_ip(ipipe_base, config->vst & IPIPE_RSZ_VPS_MASK, IPIPE_SRC_VPS);
+ regw_ip(ipipe_base, config->hst & IPIPE_RSZ_HPS_MASK, IPIPE_SRC_HPS);
+
+ outformat = &ipipe->formats[IPIPE_PAD_SOURCE];
+ regw_ip(ipipe_base, (outformat->height + 1) & IPIPE_RSZ_VSZ_MASK,
+ IPIPE_SRC_VSZ);
+ regw_ip(ipipe_base, (outformat->width + 1) & IPIPE_RSZ_HSZ_MASK,
+ IPIPE_SRC_HSZ);
+
+ if (data_path == IPIPE_RAW2YUV ||
+ data_path == IPIPE_RAW2RAW) {
+ color_pat =
+ ipipe_get_color_pat(ipipe->formats[IPIPE_PAD_SINK].code);
+ regw_ip(ipipe_base, color_pat, IPIPE_SRC_COL);
+ }
+
+ return 0;
+}
+
+/*
+ * config_rsz_hw() - Performs hardware setup of resizer.
+ */
+int config_rsz_hw(struct vpfe_resizer_device *resizer,
+ struct resizer_params *config)
+{
+ struct vpfe_device *vpfe_dev = to_vpfe_device(resizer);
+ void *__iomem ipipe_base = vpfe_dev->vpfe_ipipe.base_addr;
+ void *__iomem rsz_base = vpfe_dev->vpfe_resizer.base_addr;
+
+ /* enable VPSS clock */
+ vpss_enable_clock(VPSS_IPIPE_CLOCK, 1);
+ ipipe_clock_enable(ipipe_base);
+
+ ipipe_setup_resizer(rsz_base, config);
+
+ return 0;
+}
+
+static void
+rsz_set_y_address(void *__iomem rsz_base, unsigned int address,
+ unsigned int offset)
+{
+ u32 val;
+
+ val = address & SET_LOW_ADDR;
+ regw_rsz(rsz_base, val, offset + RSZ_SDR_Y_BAD_L);
+ regw_rsz(rsz_base, val, offset + RSZ_SDR_Y_SAD_L);
+
+ val = (address & SET_HIGH_ADDR) >> 16;
+ regw_rsz(rsz_base, val, offset + RSZ_SDR_Y_BAD_H);
+ regw_rsz(rsz_base, val, offset + RSZ_SDR_Y_SAD_H);
+}
+
+static void
+rsz_set_c_address(void *__iomem rsz_base, unsigned int address,
+ unsigned int offset)
+{
+ u32 val;
+
+ val = address & SET_LOW_ADDR;
+ regw_rsz(rsz_base, val, offset + RSZ_SDR_C_BAD_L);
+ regw_rsz(rsz_base, val, offset + RSZ_SDR_C_SAD_L);
+
+ val = (address & SET_HIGH_ADDR) >> 16;
+ regw_rsz(rsz_base, val, offset + RSZ_SDR_C_BAD_H);
+ regw_rsz(rsz_base, val, offset + RSZ_SDR_C_SAD_H);
+}
+
+/*
+ * resizer_set_outaddr() - set the address for given resize_no
+ * @rsz_base: resizer base address
+ * @params: pointer to ipipe_params structure
+ * @resize_no: 0 - Resizer-A, 1 - Resizer B
+ * @address: the address to set
+ */
+int
+resizer_set_outaddr(void *__iomem rsz_base, struct resizer_params *params,
+ int resize_no, unsigned int address)
+{
+ struct resizer_scale_param *rsc_param;
+ struct rsz_ext_mem_param *mem_param;
+ struct rsz_common_params *rsz_common;
+ unsigned int rsz_start_add;
+ unsigned int val;
+
+ if (resize_no != RSZ_A && resize_no != RSZ_B)
+ return -EINVAL;
+
+ mem_param = &params->ext_mem_param[resize_no];
+ rsc_param = &params->rsz_rsc_param[resize_no];
+ rsz_common = &params->rsz_common;
+
+ if (resize_no == RSZ_A)
+ rsz_start_add = RSZ_EN_A;
+ else
+ rsz_start_add = RSZ_EN_B;
+
+ /* y_c = 0 for y, = 1 for c */
+ if (rsz_common->src_img_fmt == RSZ_IMG_420) {
+ if (rsz_common->y_c) {
+ /* C channel */
+ val = address + mem_param->flip_ofst_c;
+ rsz_set_c_address(rsz_base, val, rsz_start_add);
+ } else {
+ val = address + mem_param->flip_ofst_y;
+ rsz_set_y_address(rsz_base, val, rsz_start_add);
+ }
+ } else {
+ if (rsc_param->cen && rsc_param->yen) {
+ /* 420 */
+ val = address + mem_param->c_offset +
+ mem_param->flip_ofst_c +
+ mem_param->user_y_ofst +
+ mem_param->user_c_ofst;
+ if (resize_no == RSZ_B)
+ val +=
+ params->ext_mem_param[RSZ_A].user_y_ofst +
+ params->ext_mem_param[RSZ_A].user_c_ofst;
+ /* set C address */
+ rsz_set_c_address(rsz_base, val, rsz_start_add);
+ }
+ val = address + mem_param->flip_ofst_y + mem_param->user_y_ofst;
+ if (resize_no == RSZ_B)
+ val += params->ext_mem_param[RSZ_A].user_y_ofst +
+ params->ext_mem_param[RSZ_A].user_c_ofst;
+ /* set Y address */
+ rsz_set_y_address(rsz_base, val, rsz_start_add);
+ }
+ /* resizer must be enabled */
+ regw_rsz(rsz_base, params->rsz_en[resize_no], rsz_start_add);
+
+ return 0;
+}
+
+void
+ipipe_set_lutdpc_regs(void *__iomem base_addr, void *__iomem isp5_base_addr,
+ struct vpfe_ipipe_lutdpc *dpc)
+{
+ u32 max_tbl_size = LUT_DPC_MAX_SIZE >> 1;
+ u32 lut_start_addr = DPC_TB0_START_ADDR;
+ u32 val;
+ u32 count;
+
+ ipipe_clock_enable(base_addr);
+ regw_ip(base_addr, dpc->en, DPC_LUT_EN);
+
+ if (dpc->en != 1)
+ return;
+
+ val = LUTDPC_TBL_256_EN | (dpc->repl_white & 1);
+ regw_ip(base_addr, val, DPC_LUT_SEL);
+ regw_ip(base_addr, LUT_DPC_START_ADDR, DPC_LUT_ADR);
+ regw_ip(base_addr, dpc->dpc_size, DPC_LUT_SIZ & LUT_DPC_SIZE_MASK);
+
+ if (dpc->table == NULL)
+ return;
+
+ for (count = 0; count < dpc->dpc_size; count++) {
+ if (count >= max_tbl_size)
+ lut_start_addr = DPC_TB1_START_ADDR;
+ val = (dpc->table[count].horz_pos & LUT_DPC_H_POS_MASK) |
+ ((dpc->table[count].vert_pos & LUT_DPC_V_POS_MASK) <<
+ LUT_DPC_V_POS_SHIFT) | (dpc->table[count].method <<
+ LUT_DPC_CORR_METH_SHIFT);
+ w_ip_table(isp5_base_addr, val, (lut_start_addr +
+ ((count % max_tbl_size) << 2)));
+ }
+}
+
+static void
+set_dpc_thresholds(void *__iomem base_addr,
+ struct vpfe_ipipe_otfdpc_2_0_cfg *dpc_thr)
+{
+ regw_ip(base_addr, dpc_thr->corr_thr.r & OTFDPC_DPC2_THR_MASK,
+ DPC_OTF_2C_THR_R);
+ regw_ip(base_addr, dpc_thr->corr_thr.gr & OTFDPC_DPC2_THR_MASK,
+ DPC_OTF_2C_THR_GR);
+ regw_ip(base_addr, dpc_thr->corr_thr.gb & OTFDPC_DPC2_THR_MASK,
+ DPC_OTF_2C_THR_GB);
+ regw_ip(base_addr, dpc_thr->corr_thr.b & OTFDPC_DPC2_THR_MASK,
+ DPC_OTF_2C_THR_B);
+ regw_ip(base_addr, dpc_thr->det_thr.r & OTFDPC_DPC2_THR_MASK,
+ DPC_OTF_2D_THR_R);
+ regw_ip(base_addr, dpc_thr->det_thr.gr & OTFDPC_DPC2_THR_MASK,
+ DPC_OTF_2D_THR_GR);
+ regw_ip(base_addr, dpc_thr->det_thr.gb & OTFDPC_DPC2_THR_MASK,
+ DPC_OTF_2D_THR_GB);
+ regw_ip(base_addr, dpc_thr->det_thr.b & OTFDPC_DPC2_THR_MASK,
+ DPC_OTF_2D_THR_B);
+}
+
+void ipipe_set_otfdpc_regs(void *__iomem base_addr,
+ struct vpfe_ipipe_otfdpc *otfdpc)
+{
+ struct vpfe_ipipe_otfdpc_2_0_cfg *dpc_2_0 = &otfdpc->alg_cfg.dpc_2_0;
+ struct vpfe_ipipe_otfdpc_3_0_cfg *dpc_3_0 = &otfdpc->alg_cfg.dpc_3_0;
+ u32 val;
+
+ ipipe_clock_enable(base_addr);
+
+ regw_ip(base_addr, (otfdpc->en & 1), DPC_OTF_EN);
+ if (!otfdpc->en)
+ return;
+
+ /* dpc enabled */
+ val = (otfdpc->det_method << OTF_DET_METHOD_SHIFT) | otfdpc->alg;
+ regw_ip(base_addr, val, DPC_OTF_TYP);
+
+ if (otfdpc->det_method == VPFE_IPIPE_DPC_OTF_MIN_MAX) {
+ /* ALG= 0, TYP = 0, DPC_OTF_2D_THR_[x]=0
+ * DPC_OTF_2C_THR_[x] = Maximum thresohld
+ * MinMax method
+ */
+ dpc_2_0->det_thr.r = dpc_2_0->det_thr.gb =
+ dpc_2_0->det_thr.gr = dpc_2_0->det_thr.b = 0;
+ set_dpc_thresholds(base_addr, dpc_2_0);
+ return;
+ }
+ /* MinMax2 */
+ if (otfdpc->alg == VPFE_IPIPE_OTFDPC_2_0) {
+ set_dpc_thresholds(base_addr, dpc_2_0);
+ return;
+ }
+ regw_ip(base_addr, dpc_3_0->act_adj_shf &
+ OTF_DPC3_0_SHF_MASK, DPC_OTF_3_SHF);
+ /* Detection thresholds */
+ regw_ip(base_addr, ((dpc_3_0->det_thr & OTF_DPC3_0_THR_MASK) <<
+ OTF_DPC3_0_THR_SHIFT), DPC_OTF_3D_THR);
+ regw_ip(base_addr, dpc_3_0->det_slp &
+ OTF_DPC3_0_SLP_MASK, DPC_OTF_3D_SLP);
+ regw_ip(base_addr, dpc_3_0->det_thr_min &
+ OTF_DPC3_0_DET_MASK, DPC_OTF_3D_MIN);
+ regw_ip(base_addr, dpc_3_0->det_thr_max &
+ OTF_DPC3_0_DET_MASK, DPC_OTF_3D_MAX);
+ /* Correction thresholds */
+ regw_ip(base_addr, ((dpc_3_0->corr_thr & OTF_DPC3_0_THR_MASK) <<
+ OTF_DPC3_0_THR_SHIFT), DPC_OTF_3C_THR);
+ regw_ip(base_addr, dpc_3_0->corr_slp &
+ OTF_DPC3_0_SLP_MASK, DPC_OTF_3C_SLP);
+ regw_ip(base_addr, dpc_3_0->corr_thr_min &
+ OTF_DPC3_0_CORR_MASK, DPC_OTF_3C_MIN);
+ regw_ip(base_addr, dpc_3_0->corr_thr_max &
+ OTF_DPC3_0_CORR_MASK, DPC_OTF_3C_MAX);
+}
+
+/* 2D Noise filter */
+void
+ipipe_set_d2f_regs(void *__iomem base_addr, unsigned int id,
+ struct vpfe_ipipe_nf *noise_filter)
+{
+
+ u32 offset = D2F_1ST;
+ int count;
+ u32 val;
+
+ if (id == IPIPE_D2F_2ND)
+ offset = D2F_2ND;
+
+ ipipe_clock_enable(base_addr);
+ regw_ip(base_addr, noise_filter->en & 1, offset + D2F_EN);
+ if (!noise_filter->en)
+ return;
+
+ /*noise filter enabled */
+ /* Combine all the fields to make D2F_CFG register of IPIPE */
+ val = ((noise_filter->spread_val & D2F_SPR_VAL_MASK) <<
+ D2F_SPR_VAL_SHIFT) | ((noise_filter->shft_val &
+ D2F_SHFT_VAL_MASK) << D2F_SHFT_VAL_SHIFT) |
+ (noise_filter->gr_sample_meth << D2F_SAMPLE_METH_SHIFT) |
+ ((noise_filter->apply_lsc_gain & 1) <<
+ D2F_APPLY_LSC_GAIN_SHIFT) | D2F_USE_SPR_REG_VAL;
+ regw_ip(base_addr, val, offset + D2F_TYP);
+
+ /* edge detection minimum */
+ regw_ip(base_addr, noise_filter->edge_det_min_thr &
+ D2F_EDGE_DET_THR_MASK, offset + D2F_EDG_MIN);
+
+ /* edge detection maximum */
+ regw_ip(base_addr, noise_filter->edge_det_max_thr &
+ D2F_EDGE_DET_THR_MASK, offset + D2F_EDG_MAX);
+
+ for (count = 0; count < VPFE_IPIPE_NF_STR_TABLE_SIZE; count++)
+ regw_ip(base_addr,
+ (noise_filter->str[count] & D2F_STR_VAL_MASK),
+ offset + D2F_STR + count * 4);
+
+ for (count = 0; count < VPFE_IPIPE_NF_THR_TABLE_SIZE; count++)
+ regw_ip(base_addr, noise_filter->thr[count] & D2F_THR_VAL_MASK,
+ offset + D2F_THR + count * 4);
+}
+
+#define IPIPE_U8Q5(decimal, integer) \
+ (((decimal & 0x1f) | ((integer & 0x7) << 5)))
+
+/* Green Imbalance Correction */
+void ipipe_set_gic_regs(void *__iomem base_addr, struct vpfe_ipipe_gic *gic)
+{
+ u32 val;
+
+ ipipe_clock_enable(base_addr);
+ regw_ip(base_addr, gic->en & 1, GIC_EN);
+
+ if (!gic->en)
+ return;
+
+ /*gic enabled */
+ val = (gic->wt_fn_type << GIC_TYP_SHIFT) |
+ (gic->thr_sel << GIC_THR_SEL_SHIFT) |
+ ((gic->apply_lsc_gain & 1) << GIC_APPLY_LSC_GAIN_SHIFT);
+ regw_ip(base_addr, val, GIC_TYP);
+
+ regw_ip(base_addr, gic->gain & GIC_GAIN_MASK, GIC_GAN);
+
+ if (gic->gic_alg != VPFE_IPIPE_GIC_ALG_ADAPT_GAIN) {
+ /* Constant Gain. Set threshold to maximum */
+ regw_ip(base_addr, GIC_THR_MASK, GIC_THR);
+ return;
+ }
+
+ if (gic->thr_sel == VPFE_IPIPE_GIC_THR_REG) {
+ regw_ip(base_addr, gic->thr & GIC_THR_MASK, GIC_THR);
+ regw_ip(base_addr, gic->slope & GIC_SLOPE_MASK, GIC_SLP);
+ } else {
+ /* Use NF thresholds */
+ val = IPIPE_U8Q5(gic->nf2_thr_gain.decimal,
+ gic->nf2_thr_gain.integer);
+ regw_ip(base_addr, val, GIC_NFGAN);
+ }
+}
+
+#define IPIPE_U13Q9(decimal, integer) \
+ (((decimal & 0x1ff) | ((integer & 0xf) << 9)))
+/* White balance */
+void ipipe_set_wb_regs(void *__iomem base_addr, struct vpfe_ipipe_wb *wb)
+{
+ u32 val;
+
+ ipipe_clock_enable(base_addr);
+ /* Ofsets. S12 */
+ regw_ip(base_addr, wb->ofst_r & WB_OFFSET_MASK, WB2_OFT_R);
+ regw_ip(base_addr, wb->ofst_gr & WB_OFFSET_MASK, WB2_OFT_GR);
+ regw_ip(base_addr, wb->ofst_gb & WB_OFFSET_MASK, WB2_OFT_GB);
+ regw_ip(base_addr, wb->ofst_b & WB_OFFSET_MASK, WB2_OFT_B);
+
+ /* Gains. U13Q9 */
+ val = IPIPE_U13Q9(wb->gain_r.decimal, wb->gain_r.integer);
+ regw_ip(base_addr, val, WB2_WGN_R);
+
+ val = IPIPE_U13Q9(wb->gain_gr.decimal, wb->gain_gr.integer);
+ regw_ip(base_addr, val, WB2_WGN_GR);
+
+ val = IPIPE_U13Q9(wb->gain_gb.decimal, wb->gain_gb.integer);
+ regw_ip(base_addr, val, WB2_WGN_GB);
+
+ val = IPIPE_U13Q9(wb->gain_b.decimal, wb->gain_b.integer);
+ regw_ip(base_addr, val, WB2_WGN_B);
+}
+
+/* CFA */
+void ipipe_set_cfa_regs(void *__iomem base_addr, struct vpfe_ipipe_cfa *cfa)
+{
+ ipipe_clock_enable(base_addr);
+
+ regw_ip(base_addr, cfa->alg, CFA_MODE);
+ regw_ip(base_addr, cfa->hpf_thr_2dir & CFA_HPF_THR_2DIR_MASK,
+ CFA_2DIR_HPF_THR);
+ regw_ip(base_addr, cfa->hpf_slp_2dir & CFA_HPF_SLOPE_2DIR_MASK,
+ CFA_2DIR_HPF_SLP);
+ regw_ip(base_addr, cfa->hp_mix_thr_2dir & CFA_HPF_MIX_THR_2DIR_MASK,
+ CFA_2DIR_MIX_THR);
+ regw_ip(base_addr, cfa->hp_mix_slope_2dir & CFA_HPF_MIX_SLP_2DIR_MASK,
+ CFA_2DIR_MIX_SLP);
+ regw_ip(base_addr, cfa->dir_thr_2dir & CFA_DIR_THR_2DIR_MASK,
+ CFA_2DIR_DIR_THR);
+ regw_ip(base_addr, cfa->dir_slope_2dir & CFA_DIR_SLP_2DIR_MASK,
+ CFA_2DIR_DIR_SLP);
+ regw_ip(base_addr, cfa->nd_wt_2dir & CFA_ND_WT_2DIR_MASK,
+ CFA_2DIR_NDWT);
+ regw_ip(base_addr, cfa->hue_fract_daa & CFA_DAA_HUE_FRA_MASK,
+ CFA_MONO_HUE_FRA);
+ regw_ip(base_addr, cfa->edge_thr_daa & CFA_DAA_EDG_THR_MASK,
+ CFA_MONO_EDG_THR);
+ regw_ip(base_addr, cfa->thr_min_daa & CFA_DAA_THR_MIN_MASK,
+ CFA_MONO_THR_MIN);
+ regw_ip(base_addr, cfa->thr_slope_daa & CFA_DAA_THR_SLP_MASK,
+ CFA_MONO_THR_SLP);
+ regw_ip(base_addr, cfa->slope_min_daa & CFA_DAA_SLP_MIN_MASK,
+ CFA_MONO_SLP_MIN);
+ regw_ip(base_addr, cfa->slope_slope_daa & CFA_DAA_SLP_SLP_MASK,
+ CFA_MONO_SLP_SLP);
+ regw_ip(base_addr, cfa->lp_wt_daa & CFA_DAA_LP_WT_MASK,
+ CFA_MONO_LPWT);
+}
+
+void
+ipipe_set_rgb2rgb_regs(void *__iomem base_addr, unsigned int id,
+ struct vpfe_ipipe_rgb2rgb *rgb)
+{
+ u32 offset_mask = RGB2RGB_1_OFST_MASK;
+ u32 offset = RGB1_MUL_BASE;
+ u32 integ_mask = 0xf;
+ u32 val;
+
+ ipipe_clock_enable(base_addr);
+
+ if (id == IPIPE_RGB2RGB_2) {
+ /* For second RGB module, gain integer is 3 bits instead
+ of 4, offset has 11 bits insread of 13 */
+ offset = RGB2_MUL_BASE;
+ integ_mask = 0x7;
+ offset_mask = RGB2RGB_2_OFST_MASK;
+ }
+ /* Gains */
+ val = (rgb->coef_rr.decimal & 0xff) |
+ ((rgb->coef_rr.integer & integ_mask) << 8);
+ regw_ip(base_addr, val, offset + RGB_MUL_RR);
+ val = (rgb->coef_gr.decimal & 0xff) |
+ ((rgb->coef_gr.integer & integ_mask) << 8);
+ regw_ip(base_addr, val, offset + RGB_MUL_GR);
+ val = (rgb->coef_br.decimal & 0xff) |
+ ((rgb->coef_br.integer & integ_mask) << 8);
+ regw_ip(base_addr, val, offset + RGB_MUL_BR);
+ val = (rgb->coef_rg.decimal & 0xff) |
+ ((rgb->coef_rg.integer & integ_mask) << 8);
+ regw_ip(base_addr, val, offset + RGB_MUL_RG);
+ val = (rgb->coef_gg.decimal & 0xff) |
+ ((rgb->coef_gg.integer & integ_mask) << 8);
+ regw_ip(base_addr, val, offset + RGB_MUL_GG);
+ val = (rgb->coef_bg.decimal & 0xff) |
+ ((rgb->coef_bg.integer & integ_mask) << 8);
+ regw_ip(base_addr, val, offset + RGB_MUL_BG);
+ val = (rgb->coef_rb.decimal & 0xff) |
+ ((rgb->coef_rb.integer & integ_mask) << 8);
+ regw_ip(base_addr, val, offset + RGB_MUL_RB);
+ val = (rgb->coef_gb.decimal & 0xff) |
+ ((rgb->coef_gb.integer & integ_mask) << 8);
+ regw_ip(base_addr, val, offset + RGB_MUL_GB);
+ val = (rgb->coef_bb.decimal & 0xff) |
+ ((rgb->coef_bb.integer & integ_mask) << 8);
+ regw_ip(base_addr, val, offset + RGB_MUL_BB);
+
+ /* Offsets */
+ regw_ip(base_addr, rgb->out_ofst_r & offset_mask, offset + RGB_OFT_OR);
+ regw_ip(base_addr, rgb->out_ofst_g & offset_mask, offset + RGB_OFT_OG);
+ regw_ip(base_addr, rgb->out_ofst_b & offset_mask, offset + RGB_OFT_OB);
+}
+
+static void
+ipipe_update_gamma_tbl(void *__iomem isp5_base_addr,
+ struct vpfe_ipipe_gamma_entry *table, int size, u32 addr)
+{
+ int count;
+ u32 val;
+
+ for (count = 0; count < size; count++) {
+ val = table[count].slope & GAMMA_MASK;
+ val |= (table[count].offset & GAMMA_MASK) << GAMMA_SHIFT;
+ w_ip_table(isp5_base_addr, val, (addr + (count * 4)));
+ }
+}
+
+void
+ipipe_set_gamma_regs(void *__iomem base_addr, void *__iomem isp5_base_addr,
+ struct vpfe_ipipe_gamma *gamma)
+{
+ int table_size;
+ u32 val;
+
+ ipipe_clock_enable(base_addr);
+ val = (gamma->bypass_r << GAMMA_BYPR_SHIFT) |
+ (gamma->bypass_b << GAMMA_BYPG_SHIFT) |
+ (gamma->bypass_g << GAMMA_BYPB_SHIFT) |
+ (gamma->tbl_sel << GAMMA_TBL_SEL_SHIFT) |
+ (gamma->tbl_size << GAMMA_TBL_SIZE_SHIFT);
+
+ regw_ip(base_addr, val, GMM_CFG);
+
+ if (gamma->tbl_sel != VPFE_IPIPE_GAMMA_TBL_RAM)
+ return;
+
+ table_size = gamma->tbl_size;
+
+ if (!gamma->bypass_r && gamma->table_r != NULL)
+ ipipe_update_gamma_tbl(isp5_base_addr, gamma->table_r,
+ table_size, GAMMA_R_START_ADDR);
+ if (!gamma->bypass_b && gamma->table_b != NULL)
+ ipipe_update_gamma_tbl(isp5_base_addr, gamma->table_b,
+ table_size, GAMMA_B_START_ADDR);
+ if (!gamma->bypass_g && gamma->table_g != NULL)
+ ipipe_update_gamma_tbl(isp5_base_addr, gamma->table_g,
+ table_size, GAMMA_G_START_ADDR);
+}
+
+void
+ipipe_set_3d_lut_regs(void *__iomem base_addr, void *__iomem isp5_base_addr,
+ struct vpfe_ipipe_3d_lut *lut_3d)
+{
+ struct vpfe_ipipe_3d_lut_entry *tbl;
+ u32 bnk_index;
+ u32 tbl_index;
+ u32 val;
+ u32 i;
+
+ ipipe_clock_enable(base_addr);
+ regw_ip(base_addr, lut_3d->en, D3LUT_EN);
+
+ if (!lut_3d->en)
+ return;
+
+ /* lut_3d enabled */
+ if (!lut_3d->table)
+ return;
+
+ /* valied table */
+ tbl = lut_3d->table;
+ for (i = 0; i < VPFE_IPIPE_MAX_SIZE_3D_LUT; i++) {
+ /* Each entry has 0-9 (B), 10-19 (G) and
+ 20-29 R values */
+ val = tbl[i].b & D3_LUT_ENTRY_MASK;
+ val |= (tbl[i].g & D3_LUT_ENTRY_MASK) <<
+ D3_LUT_ENTRY_G_SHIFT;
+ val |= (tbl[i].r & D3_LUT_ENTRY_MASK) <<
+ D3_LUT_ENTRY_R_SHIFT;
+ bnk_index = i % 4;
+ tbl_index = i >> 2;
+ tbl_index <<= 2;
+ if (bnk_index == 0)
+ w_ip_table(isp5_base_addr, val,
+ tbl_index + D3L_TB0_START_ADDR);
+ else if (bnk_index == 1)
+ w_ip_table(isp5_base_addr, val,
+ tbl_index + D3L_TB1_START_ADDR);
+ else if (bnk_index == 2)
+ w_ip_table(isp5_base_addr, val,
+ tbl_index + D3L_TB2_START_ADDR);
+ else
+ w_ip_table(isp5_base_addr, val,
+ tbl_index + D3L_TB3_START_ADDR);
+ }
+}
+
+/* Lumina adjustments */
+void
+ipipe_set_lum_adj_regs(void *__iomem base_addr, struct ipipe_lum_adj *lum_adj)
+{
+ u32 val;
+
+ ipipe_clock_enable(base_addr);
+
+ /* combine fields of YUV_ADJ to set brightness and contrast */
+ val = lum_adj->contrast << LUM_ADJ_CONTR_SHIFT |
+ lum_adj->brightness << LUM_ADJ_BRIGHT_SHIFT;
+ regw_ip(base_addr, val, YUV_ADJ);
+}
+
+#define IPIPE_S12Q8(decimal, integer) \
+ (((decimal & 0xff) | ((integer & 0xf) << 8)))
+
+void ipipe_set_rgb2ycbcr_regs(void *__iomem base_addr,
+ struct vpfe_ipipe_rgb2yuv *yuv)
+{
+ u32 val;
+
+ /* S10Q8 */
+ ipipe_clock_enable(base_addr);
+ val = IPIPE_S12Q8(yuv->coef_ry.decimal, yuv->coef_ry.integer);
+ regw_ip(base_addr, val, YUV_MUL_RY);
+ val = IPIPE_S12Q8(yuv->coef_gy.decimal, yuv->coef_gy.integer);
+ regw_ip(base_addr, val, YUV_MUL_GY);
+ val = IPIPE_S12Q8(yuv->coef_by.decimal, yuv->coef_by.integer);
+ regw_ip(base_addr, val, YUV_MUL_BY);
+ val = IPIPE_S12Q8(yuv->coef_rcb.decimal, yuv->coef_rcb.integer);
+ regw_ip(base_addr, val, YUV_MUL_RCB);
+ val = IPIPE_S12Q8(yuv->coef_gcb.decimal, yuv->coef_gcb.integer);
+ regw_ip(base_addr, val, YUV_MUL_GCB);
+ val = IPIPE_S12Q8(yuv->coef_bcb.decimal, yuv->coef_bcb.integer);
+ regw_ip(base_addr, val, YUV_MUL_BCB);
+ val = IPIPE_S12Q8(yuv->coef_rcr.decimal, yuv->coef_rcr.integer);
+ regw_ip(base_addr, val, YUV_MUL_RCR);
+ val = IPIPE_S12Q8(yuv->coef_gcr.decimal, yuv->coef_gcr.integer);
+ regw_ip(base_addr, val, YUV_MUL_GCR);
+ val = IPIPE_S12Q8(yuv->coef_bcr.decimal, yuv->coef_bcr.integer);
+ regw_ip(base_addr, val, YUV_MUL_BCR);
+ regw_ip(base_addr, yuv->out_ofst_y & RGB2YCBCR_OFST_MASK, YUV_OFT_Y);
+ regw_ip(base_addr, yuv->out_ofst_cb & RGB2YCBCR_OFST_MASK, YUV_OFT_CB);
+ regw_ip(base_addr, yuv->out_ofst_cr & RGB2YCBCR_OFST_MASK, YUV_OFT_CR);
+}
+
+/* YUV 422 conversion */
+void
+ipipe_set_yuv422_conv_regs(void *__iomem base_addr,
+ struct vpfe_ipipe_yuv422_conv *conv)
+{
+ u32 val;
+
+ ipipe_clock_enable(base_addr);
+
+ /* Combine all the fields to make YUV_PHS register of IPIPE */
+ val = (conv->chrom_pos << 0) | (conv->en_chrom_lpf << 1);
+ regw_ip(base_addr, val, YUV_PHS);
+}
+
+void
+ipipe_set_gbce_regs(void *__iomem base_addr, void *__iomem isp5_base_addr,
+ struct vpfe_ipipe_gbce *gbce)
+{
+ unsigned int count;
+ u32 mask = GBCE_Y_VAL_MASK;
+
+ if (gbce->type == VPFE_IPIPE_GBCE_GAIN_TBL)
+ mask = GBCE_GAIN_VAL_MASK;
+
+ ipipe_clock_enable(base_addr);
+ regw_ip(base_addr, gbce->en & 1, GBCE_EN);
+
+ if (!gbce->en)
+ return;
+
+ regw_ip(base_addr, gbce->type, GBCE_TYP);
+
+ if (!gbce->table)
+ return;
+
+ for (count = 0; count < VPFE_IPIPE_MAX_SIZE_GBCE_LUT; count += 2)
+ w_ip_table(isp5_base_addr, ((gbce->table[count + 1] & mask) <<
+ GBCE_ENTRY_SHIFT) | (gbce->table[count] & mask),
+ ((count/2) << 2) + GBCE_TB_START_ADDR);
+}
+
+void
+ipipe_set_ee_regs(void *__iomem base_addr, void *__iomem isp5_base_addr,
+ struct vpfe_ipipe_yee *ee)
+{
+ unsigned int count;
+ u32 val;
+
+ ipipe_clock_enable(base_addr);
+ regw_ip(base_addr, ee->en, YEE_EN);
+
+ if (!ee->en)
+ return;
+
+ val = ee->en_halo_red & 1;
+ val |= ee->merge_meth << YEE_HALO_RED_EN_SHIFT;
+ regw_ip(base_addr, val, YEE_TYP);
+
+ regw_ip(base_addr, ee->hpf_shft, YEE_SHF);
+ regw_ip(base_addr, ee->hpf_coef_00 & YEE_COEF_MASK, YEE_MUL_00);
+ regw_ip(base_addr, ee->hpf_coef_01 & YEE_COEF_MASK, YEE_MUL_01);
+ regw_ip(base_addr, ee->hpf_coef_02 & YEE_COEF_MASK, YEE_MUL_02);
+ regw_ip(base_addr, ee->hpf_coef_10 & YEE_COEF_MASK, YEE_MUL_10);
+ regw_ip(base_addr, ee->hpf_coef_11 & YEE_COEF_MASK, YEE_MUL_11);
+ regw_ip(base_addr, ee->hpf_coef_12 & YEE_COEF_MASK, YEE_MUL_12);
+ regw_ip(base_addr, ee->hpf_coef_20 & YEE_COEF_MASK, YEE_MUL_20);
+ regw_ip(base_addr, ee->hpf_coef_21 & YEE_COEF_MASK, YEE_MUL_21);
+ regw_ip(base_addr, ee->hpf_coef_22 & YEE_COEF_MASK, YEE_MUL_22);
+ regw_ip(base_addr, ee->yee_thr & YEE_THR_MASK, YEE_THR);
+ regw_ip(base_addr, ee->es_gain & YEE_ES_GAIN_MASK, YEE_E_GAN);
+ regw_ip(base_addr, ee->es_thr1 & YEE_ES_THR1_MASK, YEE_E_THR1);
+ regw_ip(base_addr, ee->es_thr2 & YEE_THR_MASK, YEE_E_THR2);
+ regw_ip(base_addr, ee->es_gain_grad & YEE_THR_MASK, YEE_G_GAN);
+ regw_ip(base_addr, ee->es_ofst_grad & YEE_THR_MASK, YEE_G_OFT);
+
+ if (ee->table == NULL)
+ return;
+
+ for (count = 0; count < VPFE_IPIPE_MAX_SIZE_YEE_LUT; count += 2)
+ w_ip_table(isp5_base_addr, ((ee->table[count + 1] &
+ YEE_ENTRY_MASK) << YEE_ENTRY_SHIFT) |
+ (ee->table[count] & YEE_ENTRY_MASK),
+ ((count/2) << 2) + YEE_TB_START_ADDR);
+}
+
+/* Chromatic Artifact Correction. CAR */
+static void ipipe_set_mf(void *__iomem base_addr)
+{
+ /* typ to dynamic switch */
+ regw_ip(base_addr, VPFE_IPIPE_CAR_DYN_SWITCH, CAR_TYP);
+ /* Set SW0 to maximum */
+ regw_ip(base_addr, CAR_MF_THR, CAR_SW);
+}
+
+static void
+ipipe_set_gain_ctrl(void *__iomem base_addr, struct vpfe_ipipe_car *car)
+{
+ regw_ip(base_addr, VPFE_IPIPE_CAR_CHR_GAIN_CTRL, CAR_TYP);
+ regw_ip(base_addr, car->hpf, CAR_HPF_TYP);
+ regw_ip(base_addr, car->hpf_shft & CAR_HPF_SHIFT_MASK, CAR_HPF_SHF);
+ regw_ip(base_addr, car->hpf_thr, CAR_HPF_THR);
+ regw_ip(base_addr, car->gain1.gain, CAR_GN1_GAN);
+ regw_ip(base_addr, car->gain1.shft & CAR_GAIN1_SHFT_MASK, CAR_GN1_SHF);
+ regw_ip(base_addr, car->gain1.gain_min & CAR_GAIN_MIN_MASK,
+ CAR_GN1_MIN);
+ regw_ip(base_addr, car->gain2.gain, CAR_GN2_GAN);
+ regw_ip(base_addr, car->gain2.shft & CAR_GAIN2_SHFT_MASK, CAR_GN2_SHF);
+ regw_ip(base_addr, car->gain2.gain_min & CAR_GAIN_MIN_MASK,
+ CAR_GN2_MIN);
+}
+
+void ipipe_set_car_regs(void *__iomem base_addr, struct vpfe_ipipe_car *car)
+{
+ u32 val;
+
+ ipipe_clock_enable(base_addr);
+ regw_ip(base_addr, car->en, CAR_EN);
+
+ if (!car->en)
+ return;
+
+ switch (car->meth) {
+ case VPFE_IPIPE_CAR_MED_FLTR:
+ ipipe_set_mf(base_addr);
+ break;
+
+ case VPFE_IPIPE_CAR_CHR_GAIN_CTRL:
+ ipipe_set_gain_ctrl(base_addr, car);
+ break;
+
+ default:
+ /* Dynamic switch between MF and Gain Ctrl. */
+ ipipe_set_mf(base_addr);
+ ipipe_set_gain_ctrl(base_addr, car);
+ /* Set the threshold for switching between
+ * the two Here we overwrite the MF SW0 value
+ */
+ regw_ip(base_addr, VPFE_IPIPE_CAR_DYN_SWITCH, CAR_TYP);
+ val = car->sw1;
+ val <<= CAR_SW1_SHIFT;
+ val |= car->sw0;
+ regw_ip(base_addr, val, CAR_SW);
+ }
+}
+
+/* Chromatic Gain Suppression */
+void ipipe_set_cgs_regs(void *__iomem base_addr, struct vpfe_ipipe_cgs *cgs)
+{
+ ipipe_clock_enable(base_addr);
+ regw_ip(base_addr, cgs->en, CGS_EN);
+
+ if (!cgs->en)
+ return;
+
+ /* Set the bright side parameters */
+ regw_ip(base_addr, cgs->h_thr, CGS_GN1_H_THR);
+ regw_ip(base_addr, cgs->h_slope, CGS_GN1_H_GAN);
+ regw_ip(base_addr, cgs->h_shft & CAR_SHIFT_MASK, CGS_GN1_H_SHF);
+ regw_ip(base_addr, cgs->h_min, CGS_GN1_H_MIN);
+}
+
+void rsz_src_enable(void *__iomem rsz_base, int enable)
+{
+ regw_rsz(rsz_base, enable, RSZ_SRC_EN);
+}
+
+int rsz_enable(void *__iomem rsz_base, int rsz_id, int enable)
+{
+ if (rsz_id == RSZ_A) {
+ regw_rsz(rsz_base, enable, RSZ_EN_A);
+ /* We always enable RSZ_A. RSZ_B is enable upon request from
+ * application. So enable RSZ_SRC_EN along with RSZ_A
+ */
+ regw_rsz(rsz_base, enable, RSZ_SRC_EN);
+ } else if (rsz_id == RSZ_B) {
+ regw_rsz(rsz_base, enable, RSZ_EN_B);
+ } else {
+ BUG();
+ }
+
+ return 0;
+}
diff --git a/drivers/staging/media/davinci_vpfe/dm365_ipipe_hw.h b/drivers/staging/media/davinci_vpfe/dm365_ipipe_hw.h
new file mode 100644
index 00000000000..010fdb247fa
--- /dev/null
+++ b/drivers/staging/media/davinci_vpfe/dm365_ipipe_hw.h
@@ -0,0 +1,559 @@
+/*
+ * Copyright (C) 2012 Texas Instruments Inc
+ *
+ * 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 version 2.
+ *
+ * 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
+ *
+ * Contributors:
+ * Manjunath Hadli <manjunath.hadli@ti.com>
+ * Prabhakar Lad <prabhakar.lad@ti.com>
+ */
+
+#ifndef _DAVINCI_VPFE_DM365_IPIPE_HW_H
+#define _DAVINCI_VPFE_DM365_IPIPE_HW_H
+
+#include "vpfe_mc_capture.h"
+
+#define SET_LOW_ADDR 0x0000ffff
+#define SET_HIGH_ADDR 0xffff0000
+
+/* Below are the internal tables */
+#define DPC_TB0_START_ADDR 0x8000
+#define DPC_TB1_START_ADDR 0x8400
+
+#define GAMMA_R_START_ADDR 0xa800
+#define GAMMA_G_START_ADDR 0xb000
+#define GAMMA_B_START_ADDR 0xb800
+
+/* RAM table addresses for edge enhancement correction*/
+#define YEE_TB_START_ADDR 0x8800
+
+/* RAM table address for GBC LUT */
+#define GBCE_TB_START_ADDR 0x9000
+
+/* RAM table for 3D NF LUT */
+#define D3L_TB0_START_ADDR 0x9800
+#define D3L_TB1_START_ADDR 0x9c00
+#define D3L_TB2_START_ADDR 0xa000
+#define D3L_TB3_START_ADDR 0xa400
+
+/* IPIPE Register Offsets from the base address */
+#define IPIPE_SRC_EN 0x0000
+#define IPIPE_SRC_MODE 0x0004
+#define IPIPE_SRC_FMT 0x0008
+#define IPIPE_SRC_COL 0x000c
+#define IPIPE_SRC_VPS 0x0010
+#define IPIPE_SRC_VSZ 0x0014
+#define IPIPE_SRC_HPS 0x0018
+#define IPIPE_SRC_HSZ 0x001c
+
+#define IPIPE_SEL_SBU 0x0020
+
+#define IPIPE_DMA_STA 0x0024
+#define IPIPE_GCK_MMR 0x0028
+#define IPIPE_GCK_PIX 0x002c
+#define IPIPE_RESERVED0 0x0030
+
+/* Defect Correction */
+#define DPC_LUT_EN 0x0034
+#define DPC_LUT_SEL 0x0038
+#define DPC_LUT_ADR 0x003c
+#define DPC_LUT_SIZ 0x0040
+#define DPC_OTF_EN 0x0044
+#define DPC_OTF_TYP 0x0048
+#define DPC_OTF_2D_THR_R 0x004c
+#define DPC_OTF_2D_THR_GR 0x0050
+#define DPC_OTF_2D_THR_GB 0x0054
+#define DPC_OTF_2D_THR_B 0x0058
+#define DPC_OTF_2C_THR_R 0x005c
+#define DPC_OTF_2C_THR_GR 0x0060
+#define DPC_OTF_2C_THR_GB 0x0064
+#define DPC_OTF_2C_THR_B 0x0068
+#define DPC_OTF_3_SHF 0x006c
+#define DPC_OTF_3D_THR 0x0070
+#define DPC_OTF_3D_SLP 0x0074
+#define DPC_OTF_3D_MIN 0x0078
+#define DPC_OTF_3D_MAX 0x007c
+#define DPC_OTF_3C_THR 0x0080
+#define DPC_OTF_3C_SLP 0x0084
+#define DPC_OTF_3C_MIN 0x0088
+#define DPC_OTF_3C_MAX 0x008c
+
+/* Lense Shading Correction */
+#define LSC_VOFT 0x90
+#define LSC_VA2 0x94
+#define LSC_VA1 0x98
+#define LSC_VS 0x9c
+#define LSC_HOFT 0xa0
+#define LSC_HA2 0xa4
+#define LSC_HA1 0xa8
+#define LSC_HS 0xac
+#define LSC_GAIN_R 0xb0
+#define LSC_GAIN_GR 0xb4
+#define LSC_GAIN_GB 0xb8
+#define LSC_GAIN_B 0xbc
+#define LSC_OFT_R 0xc0
+#define LSC_OFT_GR 0xc4
+#define LSC_OFT_GB 0xc8
+#define LSC_OFT_B 0xcc
+#define LSC_SHF 0xd0
+#define LSC_MAX 0xd4
+
+/* Noise Filter 1. Ofsets from start address given */
+#define D2F_1ST 0xd8
+#define D2F_EN 0x0
+#define D2F_TYP 0x4
+#define D2F_THR 0x8
+#define D2F_STR 0x28
+#define D2F_SPR 0x48
+#define D2F_EDG_MIN 0x68
+#define D2F_EDG_MAX 0x6c
+
+/* Noise Filter 2 */
+#define D2F_2ND 0x148
+
+/* GIC */
+#define GIC_EN 0x1b8
+#define GIC_TYP 0x1bc
+#define GIC_GAN 0x1c0
+#define GIC_NFGAN 0x1c4
+#define GIC_THR 0x1c8
+#define GIC_SLP 0x1cc
+
+/* White Balance */
+#define WB2_OFT_R 0x1d0
+#define WB2_OFT_GR 0x1d4
+#define WB2_OFT_GB 0x1d8
+#define WB2_OFT_B 0x1dc
+#define WB2_WGN_R 0x1e0
+#define WB2_WGN_GR 0x1e4
+#define WB2_WGN_GB 0x1e8
+#define WB2_WGN_B 0x1ec
+
+/* CFA interpolation */
+#define CFA_MODE 0x1f0
+#define CFA_2DIR_HPF_THR 0x1f4
+#define CFA_2DIR_HPF_SLP 0x1f8
+#define CFA_2DIR_MIX_THR 0x1fc
+#define CFA_2DIR_MIX_SLP 0x200
+#define CFA_2DIR_DIR_THR 0x204
+#define CFA_2DIR_DIR_SLP 0x208
+#define CFA_2DIR_NDWT 0x20c
+#define CFA_MONO_HUE_FRA 0x210
+#define CFA_MONO_EDG_THR 0x214
+#define CFA_MONO_THR_MIN 0x218
+#define CFA_MONO_THR_SLP 0x21c
+#define CFA_MONO_SLP_MIN 0x220
+#define CFA_MONO_SLP_SLP 0x224
+#define CFA_MONO_LPWT 0x228
+
+/* RGB to RGB conversiona - 1st */
+#define RGB1_MUL_BASE 0x22c
+/* Offsets from base */
+#define RGB_MUL_RR 0x0
+#define RGB_MUL_GR 0x4
+#define RGB_MUL_BR 0x8
+#define RGB_MUL_RG 0xc
+#define RGB_MUL_GG 0x10
+#define RGB_MUL_BG 0x14
+#define RGB_MUL_RB 0x18
+#define RGB_MUL_GB 0x1c
+#define RGB_MUL_BB 0x20
+#define RGB_OFT_OR 0x24
+#define RGB_OFT_OG 0x28
+#define RGB_OFT_OB 0x2c
+
+/* Gamma */
+#define GMM_CFG 0x25c
+
+/* RGB to RGB conversiona - 2nd */
+#define RGB2_MUL_BASE 0x260
+
+/* 3D LUT */
+#define D3LUT_EN 0x290
+
+/* RGB to YUV(YCbCr) conversion */
+#define YUV_ADJ 0x294
+#define YUV_MUL_RY 0x298
+#define YUV_MUL_GY 0x29c
+#define YUV_MUL_BY 0x2a0
+#define YUV_MUL_RCB 0x2a4
+#define YUV_MUL_GCB 0x2a8
+#define YUV_MUL_BCB 0x2ac
+#define YUV_MUL_RCR 0x2b0
+#define YUV_MUL_GCR 0x2b4
+#define YUV_MUL_BCR 0x2b8
+#define YUV_OFT_Y 0x2bc
+#define YUV_OFT_CB 0x2c0
+#define YUV_OFT_CR 0x2c4
+#define YUV_PHS 0x2c8
+
+/* Global Brightness and Contrast */
+#define GBCE_EN 0x2cc
+#define GBCE_TYP 0x2d0
+
+/* Edge Enhancer */
+#define YEE_EN 0x2d4
+#define YEE_TYP 0x2d8
+#define YEE_SHF 0x2dc
+#define YEE_MUL_00 0x2e0
+#define YEE_MUL_01 0x2e4
+#define YEE_MUL_02 0x2e8
+#define YEE_MUL_10 0x2ec
+#define YEE_MUL_11 0x2f0
+#define YEE_MUL_12 0x2f4
+#define YEE_MUL_20 0x2f8
+#define YEE_MUL_21 0x2fc
+#define YEE_MUL_22 0x300
+#define YEE_THR 0x304
+#define YEE_E_GAN 0x308
+#define YEE_E_THR1 0x30c
+#define YEE_E_THR2 0x310
+#define YEE_G_GAN 0x314
+#define YEE_G_OFT 0x318
+
+/* Chroma Artifact Reduction */
+#define CAR_EN 0x31c
+#define CAR_TYP 0x320
+#define CAR_SW 0x324
+#define CAR_HPF_TYP 0x328
+#define CAR_HPF_SHF 0x32c
+#define CAR_HPF_THR 0x330
+#define CAR_GN1_GAN 0x334
+#define CAR_GN1_SHF 0x338
+#define CAR_GN1_MIN 0x33c
+#define CAR_GN2_GAN 0x340
+#define CAR_GN2_SHF 0x344
+#define CAR_GN2_MIN 0x348
+
+/* Chroma Gain Suppression */
+#define CGS_EN 0x34c
+#define CGS_GN1_L_THR 0x350
+#define CGS_GN1_L_GAN 0x354
+#define CGS_GN1_L_SHF 0x358
+#define CGS_GN1_L_MIN 0x35c
+#define CGS_GN1_H_THR 0x360
+#define CGS_GN1_H_GAN 0x364
+#define CGS_GN1_H_SHF 0x368
+#define CGS_GN1_H_MIN 0x36c
+#define CGS_GN2_L_THR 0x370
+#define CGS_GN2_L_GAN 0x374
+#define CGS_GN2_L_SHF 0x378
+#define CGS_GN2_L_MIN 0x37c
+
+/* Resizer */
+#define RSZ_SRC_EN 0x0
+#define RSZ_SRC_MODE 0x4
+#define RSZ_SRC_FMT0 0x8
+#define RSZ_SRC_FMT1 0xc
+#define RSZ_SRC_VPS 0x10
+#define RSZ_SRC_VSZ 0x14
+#define RSZ_SRC_HPS 0x18
+#define RSZ_SRC_HSZ 0x1c
+#define RSZ_DMA_RZA 0x20
+#define RSZ_DMA_RZB 0x24
+#define RSZ_DMA_STA 0x28
+#define RSZ_GCK_MMR 0x2c
+#define RSZ_RESERVED0 0x30
+#define RSZ_GCK_SDR 0x34
+#define RSZ_IRQ_RZA 0x38
+#define RSZ_IRQ_RZB 0x3c
+#define RSZ_YUV_Y_MIN 0x40
+#define RSZ_YUV_Y_MAX 0x44
+#define RSZ_YUV_C_MIN 0x48
+#define RSZ_YUV_C_MAX 0x4c
+#define RSZ_YUV_PHS 0x50
+#define RSZ_SEQ 0x54
+
+/* Resizer Rescale Parameters */
+#define RSZ_EN_A 0x58
+#define RSZ_EN_B 0xe8
+/* offset of the registers to be added with base register of
+ either RSZ0 or RSZ1
+*/
+#define RSZ_MODE 0x4
+#define RSZ_420 0x8
+#define RSZ_I_VPS 0xc
+#define RSZ_I_HPS 0x10
+#define RSZ_O_VSZ 0x14
+#define RSZ_O_HSZ 0x18
+#define RSZ_V_PHS_Y 0x1c
+#define RSZ_V_PHS_C 0x20
+#define RSZ_V_DIF 0x24
+#define RSZ_V_TYP 0x28
+#define RSZ_V_LPF 0x2c
+#define RSZ_H_PHS 0x30
+#define RSZ_H_PHS_ADJ 0x34
+#define RSZ_H_DIF 0x38
+#define RSZ_H_TYP 0x3c
+#define RSZ_H_LPF 0x40
+#define RSZ_DWN_EN 0x44
+#define RSZ_DWN_AV 0x48
+
+/* Resizer RGB Conversion Parameters */
+#define RSZ_RGB_EN 0x4c
+#define RSZ_RGB_TYP 0x50
+#define RSZ_RGB_BLD 0x54
+
+/* Resizer External Memory Parameters */
+#define RSZ_SDR_Y_BAD_H 0x58
+#define RSZ_SDR_Y_BAD_L 0x5c
+#define RSZ_SDR_Y_SAD_H 0x60
+#define RSZ_SDR_Y_SAD_L 0x64
+#define RSZ_SDR_Y_OFT 0x68
+#define RSZ_SDR_Y_PTR_S 0x6c
+#define RSZ_SDR_Y_PTR_E 0x70
+#define RSZ_SDR_C_BAD_H 0x74
+#define RSZ_SDR_C_BAD_L 0x78
+#define RSZ_SDR_C_SAD_H 0x7c
+#define RSZ_SDR_C_SAD_L 0x80
+#define RSZ_SDR_C_OFT 0x84
+#define RSZ_SDR_C_PTR_S 0x88
+#define RSZ_SDR_C_PTR_E 0x8c
+
+/* Macro for resizer */
+#define RSZ_YUV_Y_MIN 0x40
+#define RSZ_YUV_Y_MAX 0x44
+#define RSZ_YUV_C_MIN 0x48
+#define RSZ_YUV_C_MAX 0x4c
+
+#define IPIPE_GCK_MMR_DEFAULT 1
+#define IPIPE_GCK_PIX_DEFAULT 0xe
+#define RSZ_GCK_MMR_DEFAULT 1
+#define RSZ_GCK_SDR_DEFAULT 1
+
+/* LUTDPC */
+#define LUTDPC_TBL_256_EN 0
+#define LUTDPC_INF_TBL_EN 1
+#define LUT_DPC_START_ADDR 0
+#define LUT_DPC_H_POS_MASK 0x1fff
+#define LUT_DPC_V_POS_MASK 0x1fff
+#define LUT_DPC_V_POS_SHIFT 13
+#define LUT_DPC_CORR_METH_SHIFT 26
+#define LUT_DPC_MAX_SIZE 256
+#define LUT_DPC_SIZE_MASK 0x3ff
+
+/* OTFDPC */
+#define OTFDPC_DPC2_THR_MASK 0xfff
+#define OTF_DET_METHOD_SHIFT 1
+#define OTF_DPC3_0_SHF_MASK 3
+#define OTF_DPC3_0_THR_SHIFT 6
+#define OTF_DPC3_0_THR_MASK 0x3f
+#define OTF_DPC3_0_SLP_MASK 0x3f
+#define OTF_DPC3_0_DET_MASK 0xfff
+#define OTF_DPC3_0_CORR_MASK 0xfff
+
+/* NF (D2F) */
+#define D2F_SPR_VAL_MASK 0x1f
+#define D2F_SPR_VAL_SHIFT 0
+#define D2F_SHFT_VAL_MASK 3
+#define D2F_SHFT_VAL_SHIFT 5
+#define D2F_SAMPLE_METH_SHIFT 7
+#define D2F_APPLY_LSC_GAIN_SHIFT 8
+#define D2F_USE_SPR_REG_VAL 0
+#define D2F_STR_VAL_MASK 0x1f
+#define D2F_THR_VAL_MASK 0x3ff
+#define D2F_EDGE_DET_THR_MASK 0x7ff
+
+/* Green Imbalance Correction */
+#define GIC_TYP_SHIFT 0
+#define GIC_THR_SEL_SHIFT 1
+#define GIC_APPLY_LSC_GAIN_SHIFT 2
+#define GIC_GAIN_MASK 0xff
+#define GIC_THR_MASK 0xfff
+#define GIC_SLOPE_MASK 0xfff
+#define GIC_NFGAN_INT_MASK 7
+#define GIC_NFGAN_DECI_MASK 0x1f
+
+/* WB */
+#define WB_OFFSET_MASK 0xfff
+#define WB_GAIN_INT_MASK 0xf
+#define WB_GAIN_DECI_MASK 0x1ff
+
+/* CFA */
+#define CFA_HPF_THR_2DIR_MASK 0x1fff
+#define CFA_HPF_SLOPE_2DIR_MASK 0x3ff
+#define CFA_HPF_MIX_THR_2DIR_MASK 0x1fff
+#define CFA_HPF_MIX_SLP_2DIR_MASK 0x3ff
+#define CFA_DIR_THR_2DIR_MASK 0x3ff
+#define CFA_DIR_SLP_2DIR_MASK 0x7f
+#define CFA_ND_WT_2DIR_MASK 0x3f
+#define CFA_DAA_HUE_FRA_MASK 0x3f
+#define CFA_DAA_EDG_THR_MASK 0xff
+#define CFA_DAA_THR_MIN_MASK 0x3ff
+#define CFA_DAA_THR_SLP_MASK 0x3ff
+#define CFA_DAA_SLP_MIN_MASK 0x3ff
+#define CFA_DAA_SLP_SLP_MASK 0x3ff
+#define CFA_DAA_LP_WT_MASK 0x3f
+
+/* RGB2RGB */
+#define RGB2RGB_1_OFST_MASK 0x1fff
+#define RGB2RGB_1_GAIN_INT_MASK 0xf
+#define RGB2RGB_GAIN_DECI_MASK 0xff
+#define RGB2RGB_2_OFST_MASK 0x7ff
+#define RGB2RGB_2_GAIN_INT_MASK 0x7
+
+/* Gamma */
+#define GAMMA_BYPR_SHIFT 0
+#define GAMMA_BYPG_SHIFT 1
+#define GAMMA_BYPB_SHIFT 2
+#define GAMMA_TBL_SEL_SHIFT 4
+#define GAMMA_TBL_SIZE_SHIFT 5
+#define GAMMA_MASK 0x3ff
+#define GAMMA_SHIFT 10
+
+/* 3D LUT */
+#define D3_LUT_ENTRY_MASK 0x3ff
+#define D3_LUT_ENTRY_R_SHIFT 20
+#define D3_LUT_ENTRY_G_SHIFT 10
+#define D3_LUT_ENTRY_B_SHIFT 0
+
+/* Lumina adj */
+#define LUM_ADJ_CONTR_SHIFT 0
+#define LUM_ADJ_BRIGHT_SHIFT 8
+
+/* RGB2YCbCr */
+#define RGB2YCBCR_OFST_MASK 0x7ff
+#define RGB2YCBCR_COEF_INT_MASK 0xf
+#define RGB2YCBCR_COEF_DECI_MASK 0xff
+
+/* GBCE */
+#define GBCE_Y_VAL_MASK 0xff
+#define GBCE_GAIN_VAL_MASK 0x3ff
+#define GBCE_ENTRY_SHIFT 10
+
+/* Edge Enhancements */
+#define YEE_HALO_RED_EN_SHIFT 1
+#define YEE_HPF_SHIFT_MASK 0xf
+#define YEE_COEF_MASK 0x3ff
+#define YEE_THR_MASK 0x3f
+#define YEE_ES_GAIN_MASK 0xfff
+#define YEE_ES_THR1_MASK 0xfff
+#define YEE_ENTRY_SHIFT 9
+#define YEE_ENTRY_MASK 0x1ff
+
+/* CAR */
+#define CAR_MF_THR 0xff
+#define CAR_SW1_SHIFT 8
+#define CAR_GAIN1_SHFT_MASK 7
+#define CAR_GAIN_MIN_MASK 0x1ff
+#define CAR_GAIN2_SHFT_MASK 0xf
+#define CAR_HPF_SHIFT_MASK 3
+
+/* CGS */
+#define CAR_SHIFT_MASK 3
+
+/* Resizer */
+#define RSZ_BYPASS_SHIFT 1
+#define RSZ_SRC_IMG_FMT_SHIFT 1
+#define RSZ_SRC_Y_C_SEL_SHIFT 2
+#define IPIPE_RSZ_VPS_MASK 0xffff
+#define IPIPE_RSZ_HPS_MASK 0xffff
+#define IPIPE_RSZ_VSZ_MASK 0x1fff
+#define IPIPE_RSZ_HSZ_MASK 0x1fff
+#define RSZ_HPS_MASK 0x1fff
+#define RSZ_VPS_MASK 0x1fff
+#define RSZ_O_HSZ_MASK 0x1fff
+#define RSZ_O_VSZ_MASK 0x1fff
+#define RSZ_V_PHS_MASK 0x3fff
+#define RSZ_V_DIF_MASK 0x3fff
+
+#define RSZA_H_FLIP_SHIFT 0
+#define RSZA_V_FLIP_SHIFT 1
+#define RSZB_H_FLIP_SHIFT 2
+#define RSZB_V_FLIP_SHIFT 3
+#define RSZ_A 0
+#define RSZ_B 1
+#define RSZ_CEN_SHIFT 1
+#define RSZ_YEN_SHIFT 0
+#define RSZ_TYP_Y_SHIFT 0
+#define RSZ_TYP_C_SHIFT 1
+#define RSZ_LPF_INT_MASK 0x3f
+#define RSZ_LPF_INT_MASK 0x3f
+#define RSZ_LPF_INT_C_SHIFT 6
+#define RSZ_H_PHS_MASK 0x3fff
+#define RSZ_H_DIF_MASK 0x3fff
+#define RSZ_DIFF_DOWN_THR 256
+#define RSZ_DWN_SCALE_AV_SZ_V_SHIFT 3
+#define RSZ_DWN_SCALE_AV_SZ_MASK 7
+#define RSZ_RGB_MSK1_SHIFT 2
+#define RSZ_RGB_MSK0_SHIFT 1
+#define RSZ_RGB_TYP_SHIFT 0
+#define RSZ_RGB_ALPHA_MASK 0xff
+
+static inline u32 regr_ip(void *__iomem addr, u32 offset)
+{
+ return readl(addr + offset);
+}
+
+static inline void regw_ip(void *__iomem addr, u32 val, u32 offset)
+{
+ writel(val, addr + offset);
+}
+
+static inline u32 w_ip_table(void *__iomem addr, u32 val, u32 offset)
+{
+ writel(val, addr + offset);
+
+ return val;
+}
+
+static inline u32 regr_rsz(void *__iomem addr, u32 offset)
+{
+ return readl(addr + offset);
+}
+
+static inline u32 regw_rsz(void *__iomem addr, u32 val, u32 offset)
+{
+ writel(val, addr + offset);
+
+ return val;
+}
+
+int config_ipipe_hw(struct vpfe_ipipe_device *ipipe);
+int resizer_set_outaddr(void *__iomem rsz_base, struct resizer_params *params,
+ int resize_no, unsigned int address);
+int rsz_enable(void *__iomem rsz_base, int rsz_id, int enable);
+void rsz_src_enable(void *__iomem rsz_base, int enable);
+void rsz_set_in_pix_format(unsigned char y_c);
+int config_rsz_hw(struct vpfe_resizer_device *resizer,
+ struct resizer_params *config);
+void ipipe_set_d2f_regs(void *__iomem base_addr, unsigned int id,
+ struct vpfe_ipipe_nf *noise_filter);
+void ipipe_set_rgb2rgb_regs(void *__iomem base_addr, unsigned int id,
+ struct vpfe_ipipe_rgb2rgb *rgb);
+void ipipe_set_yuv422_conv_regs(void *__iomem base_addr,
+ struct vpfe_ipipe_yuv422_conv *conv);
+void ipipe_set_lum_adj_regs(void *__iomem base_addr,
+ struct ipipe_lum_adj *lum_adj);
+void ipipe_set_rgb2ycbcr_regs(void *__iomem base_addr,
+ struct vpfe_ipipe_rgb2yuv *yuv);
+void ipipe_set_lutdpc_regs(void *__iomem base_addr,
+ void *__iomem isp5_base_addr, struct vpfe_ipipe_lutdpc *lutdpc);
+void ipipe_set_otfdpc_regs(void *__iomem base_addr,
+ struct vpfe_ipipe_otfdpc *otfdpc);
+void ipipe_set_3d_lut_regs(void *__iomem base_addr,
+ void *__iomem isp5_base_addr, struct vpfe_ipipe_3d_lut *lut_3d);
+void ipipe_set_gamma_regs(void *__iomem base_addr,
+ void *__iomem isp5_base_addr, struct vpfe_ipipe_gamma *gamma);
+void ipipe_set_ee_regs(void *__iomem base_addr,
+ void *__iomem isp5_base_addr, struct vpfe_ipipe_yee *ee);
+void ipipe_set_gbce_regs(void *__iomem base_addr,
+ void *__iomem isp5_base_addr, struct vpfe_ipipe_gbce *gbce);
+void ipipe_set_gic_regs(void *__iomem base_addr, struct vpfe_ipipe_gic *gic);
+void ipipe_set_cfa_regs(void *__iomem base_addr, struct vpfe_ipipe_cfa *cfa);
+void ipipe_set_car_regs(void *__iomem base_addr, struct vpfe_ipipe_car *car);
+void ipipe_set_cgs_regs(void *__iomem base_addr, struct vpfe_ipipe_cgs *cgs);
+void ipipe_set_wb_regs(void *__iomem base_addr, struct vpfe_ipipe_wb *wb);
+
+#endif /* _DAVINCI_VPFE_DM365_IPIPE_HW_H */
diff --git a/drivers/staging/media/davinci_vpfe/dm365_ipipeif.c b/drivers/staging/media/davinci_vpfe/dm365_ipipeif.c
new file mode 100644
index 00000000000..59540cd4bb9
--- /dev/null
+++ b/drivers/staging/media/davinci_vpfe/dm365_ipipeif.c
@@ -0,0 +1,1070 @@
+/*
+ * Copyright (C) 2012 Texas Instruments Inc
+ *
+ * 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 version 2.
+ *
+ * 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
+ *
+ * Contributors:
+ * Manjunath Hadli <manjunath.hadli@ti.com>
+ * Prabhakar Lad <prabhakar.lad@ti.com>
+ */
+
+#include "dm365_ipipeif.h"
+#include "vpfe_mc_capture.h"
+
+static const unsigned int ipipeif_input_fmts[] = {
+ V4L2_MBUS_FMT_UYVY8_2X8,
+ V4L2_MBUS_FMT_SGRBG12_1X12,
+ V4L2_MBUS_FMT_Y8_1X8,
+ V4L2_MBUS_FMT_UV8_1X8,
+ V4L2_MBUS_FMT_YDYUYDYV8_1X16,
+ V4L2_MBUS_FMT_SBGGR8_1X8,
+};
+
+static const unsigned int ipipeif_output_fmts[] = {
+ V4L2_MBUS_FMT_UYVY8_2X8,
+ V4L2_MBUS_FMT_SGRBG12_1X12,
+ V4L2_MBUS_FMT_Y8_1X8,
+ V4L2_MBUS_FMT_UV8_1X8,
+ V4L2_MBUS_FMT_YDYUYDYV8_1X16,
+ V4L2_MBUS_FMT_SBGGR8_1X8,
+ V4L2_MBUS_FMT_SGRBG10_DPCM8_1X8,
+ V4L2_MBUS_FMT_SGRBG10_ALAW8_1X8,
+};
+
+static int
+ipipeif_get_pack_mode(enum v4l2_mbus_pixelcode in_pix_fmt)
+{
+ switch (in_pix_fmt) {
+ case V4L2_MBUS_FMT_SBGGR8_1X8:
+ case V4L2_MBUS_FMT_Y8_1X8:
+ case V4L2_MBUS_FMT_SGRBG10_DPCM8_1X8:
+ case V4L2_MBUS_FMT_UV8_1X8:
+ return IPIPEIF_5_1_PACK_8_BIT;
+
+ case V4L2_MBUS_FMT_SGRBG10_ALAW8_1X8:
+ return IPIPEIF_5_1_PACK_8_BIT_A_LAW;
+
+ case V4L2_MBUS_FMT_SGRBG12_1X12:
+ return IPIPEIF_5_1_PACK_16_BIT;
+
+ case V4L2_MBUS_FMT_SBGGR12_1X12:
+ return IPIPEIF_5_1_PACK_12_BIT;
+
+ default:
+ return IPIPEIF_5_1_PACK_16_BIT;
+ }
+}
+
+static inline u32 ipipeif_read(void *addr, u32 offset)
+{
+ return readl(addr + offset);
+}
+
+static inline void ipipeif_write(u32 val, void *addr, u32 offset)
+{
+ writel(val, addr + offset);
+}
+
+static void ipipeif_config_dpc(void *addr, struct ipipeif_dpc *dpc)
+{
+ u32 val = 0;
+
+ if (dpc->en) {
+ val = (dpc->en & 1) << IPIPEIF_DPC2_EN_SHIFT;
+ val |= dpc->thr & IPIPEIF_DPC2_THR_MASK;
+ }
+ ipipeif_write(val, addr, IPIPEIF_DPC2);
+}
+
+#define IPIPEIF_MODE_CONTINUOUS 0
+#define IPIPEIF_MODE_ONE_SHOT 1
+
+static int get_oneshot_mode(enum ipipeif_input_entity input)
+{
+ if (input == IPIPEIF_INPUT_MEMORY)
+ return IPIPEIF_MODE_ONE_SHOT;
+ else if (input == IPIPEIF_INPUT_ISIF)
+ return IPIPEIF_MODE_CONTINUOUS;
+
+ return -EINVAL;
+}
+
+static int
+ipipeif_get_cfg_src1(struct vpfe_ipipeif_device *ipipeif)
+{
+ struct v4l2_mbus_framefmt *informat;
+
+ informat = &ipipeif->formats[IPIPEIF_PAD_SINK];
+ if (ipipeif->input == IPIPEIF_INPUT_MEMORY &&
+ (informat->code == V4L2_MBUS_FMT_Y8_1X8 ||
+ informat->code == V4L2_MBUS_FMT_UV8_1X8))
+ return IPIPEIF_CCDC;
+
+ return IPIPEIF_SRC1_PARALLEL_PORT;
+}
+
+static int
+ipipeif_get_data_shift(struct vpfe_ipipeif_device *ipipeif)
+{
+ struct v4l2_mbus_framefmt *informat;
+
+ informat = &ipipeif->formats[IPIPEIF_PAD_SINK];
+
+ switch (informat->code) {
+ case V4L2_MBUS_FMT_SGRBG12_1X12:
+ return IPIPEIF_5_1_BITS11_0;
+
+ case V4L2_MBUS_FMT_Y8_1X8:
+ case V4L2_MBUS_FMT_UV8_1X8:
+ return IPIPEIF_5_1_BITS11_0;
+
+ default:
+ return IPIPEIF_5_1_BITS7_0;
+ }
+}
+
+static enum ipipeif_input_source
+ipipeif_get_source(struct vpfe_ipipeif_device *ipipeif)
+{
+ struct v4l2_mbus_framefmt *informat;
+
+ informat = &ipipeif->formats[IPIPEIF_PAD_SINK];
+ if (ipipeif->input == IPIPEIF_INPUT_ISIF)
+ return IPIPEIF_CCDC;
+
+ if (informat->code == V4L2_MBUS_FMT_UYVY8_2X8)
+ return IPIPEIF_SDRAM_YUV;
+
+ return IPIPEIF_SDRAM_RAW;
+}
+
+void vpfe_ipipeif_ss_buffer_isr(struct vpfe_ipipeif_device *ipipeif)
+{
+ struct vpfe_video_device *video_in = &ipipeif->video_in;
+
+ if (ipipeif->input != IPIPEIF_INPUT_MEMORY)
+ return;
+
+ spin_lock(&video_in->dma_queue_lock);
+ vpfe_video_process_buffer_complete(video_in);
+ video_in->state = VPFE_VIDEO_BUFFER_NOT_QUEUED;
+ vpfe_video_schedule_next_buffer(video_in);
+ spin_unlock(&video_in->dma_queue_lock);
+}
+
+int vpfe_ipipeif_decimation_enabled(struct vpfe_device *vpfe_dev)
+{
+ struct vpfe_ipipeif_device *ipipeif = &vpfe_dev->vpfe_ipipeif;
+
+ return ipipeif->config.decimation;
+}
+
+int vpfe_ipipeif_get_rsz(struct vpfe_device *vpfe_dev)
+{
+ struct vpfe_ipipeif_device *ipipeif = &vpfe_dev->vpfe_ipipeif;
+
+ return ipipeif->config.rsz;
+}
+
+#define RD_DATA_15_2 0x7
+
+/*
+ * ipipeif_hw_setup() - This function sets up IPIPEIF
+ * @sd: pointer to v4l2 subdev structure
+ * return -EINVAL or zero on success
+ */
+static int ipipeif_hw_setup(struct v4l2_subdev *sd)
+{
+ struct vpfe_ipipeif_device *ipipeif = v4l2_get_subdevdata(sd);
+ struct v4l2_mbus_framefmt *informat, *outformat;
+ struct ipipeif_params params = ipipeif->config;
+ enum ipipeif_input_source ipipeif_source;
+ enum v4l2_mbus_pixelcode isif_port_if;
+ void *ipipeif_base_addr;
+ unsigned int val;
+ int data_shift;
+ int pack_mode;
+ int source1;
+
+ ipipeif_base_addr = ipipeif->ipipeif_base_addr;
+
+ /* Enable clock to IPIPEIF and IPIPE */
+ vpss_enable_clock(VPSS_IPIPEIF_CLOCK, 1);
+
+ informat = &ipipeif->formats[IPIPEIF_PAD_SINK];
+ outformat = &ipipeif->formats[IPIPEIF_PAD_SOURCE];
+
+ /* Combine all the fields to make CFG1 register of IPIPEIF */
+ val = get_oneshot_mode(ipipeif->input);
+ if (val < 0) {
+ pr_err("ipipeif: links setup required");
+ return -EINVAL;
+ }
+ val = val << ONESHOT_SHIFT;
+
+ ipipeif_source = ipipeif_get_source(ipipeif);
+ val |= ipipeif_source << INPSRC_SHIFT;
+
+ val |= params.clock_select << CLKSEL_SHIFT;
+ val |= params.avg_filter << AVGFILT_SHIFT;
+ val |= params.decimation << DECIM_SHIFT;
+
+ pack_mode = ipipeif_get_pack_mode(informat->code);
+ val |= pack_mode << PACK8IN_SHIFT;
+
+ source1 = ipipeif_get_cfg_src1(ipipeif);
+ val |= source1 << INPSRC1_SHIFT;
+
+ data_shift = ipipeif_get_data_shift(ipipeif);
+ if (ipipeif_source != IPIPEIF_SDRAM_YUV)
+ val |= data_shift << DATASFT_SHIFT;
+ else
+ val &= ~(RD_DATA_15_2 << DATASFT_SHIFT);
+
+ ipipeif_write(val, ipipeif_base_addr, IPIPEIF_CFG1);
+
+ switch (ipipeif_source) {
+ case IPIPEIF_CCDC:
+ ipipeif_write(ipipeif->gain, ipipeif_base_addr, IPIPEIF_GAIN);
+ break;
+
+ case IPIPEIF_SDRAM_RAW:
+ case IPIPEIF_CCDC_DARKFM:
+ ipipeif_write(ipipeif->gain, ipipeif_base_addr, IPIPEIF_GAIN);
+ /* fall through */
+ case IPIPEIF_SDRAM_YUV:
+ val |= data_shift << DATASFT_SHIFT;
+ ipipeif_write(params.ppln, ipipeif_base_addr, IPIPEIF_PPLN);
+ ipipeif_write(params.lpfr, ipipeif_base_addr, IPIPEIF_LPFR);
+ ipipeif_write(informat->width, ipipeif_base_addr, IPIPEIF_HNUM);
+ ipipeif_write(informat->height,
+ ipipeif_base_addr, IPIPEIF_VNUM);
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ /*check if decimation is enable or not */
+ if (params.decimation)
+ ipipeif_write(params.rsz, ipipeif_base_addr, IPIPEIF_RSZ);
+
+ /* Setup sync alignment and initial rsz position */
+ val = params.if_5_1.align_sync & 1;
+ val <<= IPIPEIF_INIRSZ_ALNSYNC_SHIFT;
+ val |= params.if_5_1.rsz_start & IPIPEIF_INIRSZ_MASK;
+ ipipeif_write(val, ipipeif_base_addr, IPIPEIF_INIRSZ);
+ isif_port_if = informat->code;
+
+ if (isif_port_if == V4L2_MBUS_FMT_Y8_1X8)
+ isif_port_if = V4L2_MBUS_FMT_YUYV8_1X16;
+ else if (isif_port_if == V4L2_MBUS_FMT_UV8_1X8)
+ isif_port_if = V4L2_MBUS_FMT_SGRBG12_1X12;
+
+ /* Enable DPCM decompression */
+ switch (ipipeif_source) {
+ case IPIPEIF_SDRAM_RAW:
+ val = 0;
+ if (outformat->code == V4L2_MBUS_FMT_SGRBG10_DPCM8_1X8) {
+ val = 1;
+ val |= (IPIPEIF_DPCM_8BIT_10BIT & 1) <<
+ IPIPEIF_DPCM_BITS_SHIFT;
+ val |= (ipipeif->dpcm_predictor & 1) <<
+ IPIPEIF_DPCM_PRED_SHIFT;
+ }
+ ipipeif_write(val, ipipeif_base_addr, IPIPEIF_DPCM);
+
+ /* set DPC */
+ ipipeif_config_dpc(ipipeif_base_addr, &params.if_5_1.dpc);
+
+ ipipeif_write(params.if_5_1.clip,
+ ipipeif_base_addr, IPIPEIF_OCLIP);
+
+ /* fall through for SDRAM YUV mode */
+ /* configure CFG2 */
+ val = ipipeif_read(ipipeif_base_addr, IPIPEIF_CFG2);
+ switch (isif_port_if) {
+ case V4L2_MBUS_FMT_YUYV8_1X16:
+ case V4L2_MBUS_FMT_UYVY8_2X8:
+ case V4L2_MBUS_FMT_Y8_1X8:
+ RESETBIT(val, IPIPEIF_CFG2_YUV8_SHIFT);
+ SETBIT(val, IPIPEIF_CFG2_YUV16_SHIFT);
+ ipipeif_write(val, ipipeif_base_addr, IPIPEIF_CFG2);
+ break;
+
+ default:
+ RESETBIT(val, IPIPEIF_CFG2_YUV8_SHIFT);
+ RESETBIT(val, IPIPEIF_CFG2_YUV16_SHIFT);
+ ipipeif_write(val, ipipeif_base_addr, IPIPEIF_CFG2);
+ break;
+ }
+
+ case IPIPEIF_SDRAM_YUV:
+ /* Set clock divider */
+ if (params.clock_select == IPIPEIF_SDRAM_CLK) {
+ val = ipipeif_read(ipipeif_base_addr, IPIPEIF_CLKDIV);
+ val |= (params.if_5_1.clk_div.m - 1) <<
+ IPIPEIF_CLKDIV_M_SHIFT;
+ val |= (params.if_5_1.clk_div.n - 1);
+ ipipeif_write(val, ipipeif_base_addr, IPIPEIF_CLKDIV);
+ }
+ break;
+
+ case IPIPEIF_CCDC:
+ case IPIPEIF_CCDC_DARKFM:
+ /* set DPC */
+ ipipeif_config_dpc(ipipeif_base_addr, &params.if_5_1.dpc);
+
+ /* Set DF gain & threshold control */
+ val = 0;
+ if (params.if_5_1.df_gain_en) {
+ val = params.if_5_1.df_gain_thr &
+ IPIPEIF_DF_GAIN_THR_MASK;
+ ipipeif_write(val, ipipeif_base_addr, IPIPEIF_DFSGTH);
+ val = (params.if_5_1.df_gain_en & 1) <<
+ IPIPEIF_DF_GAIN_EN_SHIFT;
+ val |= params.if_5_1.df_gain &
+ IPIPEIF_DF_GAIN_MASK;
+ }
+ ipipeif_write(val, ipipeif_base_addr, IPIPEIF_DFSGVL);
+ /* configure CFG2 */
+ val = VPFE_PINPOL_POSITIVE << IPIPEIF_CFG2_HDPOL_SHIFT;
+ val |= VPFE_PINPOL_POSITIVE << IPIPEIF_CFG2_VDPOL_SHIFT;
+
+ switch (isif_port_if) {
+ case V4L2_MBUS_FMT_YUYV8_1X16:
+ case V4L2_MBUS_FMT_YUYV10_1X20:
+ RESETBIT(val, IPIPEIF_CFG2_YUV8_SHIFT);
+ SETBIT(val, IPIPEIF_CFG2_YUV16_SHIFT);
+ break;
+
+ case V4L2_MBUS_FMT_YUYV8_2X8:
+ case V4L2_MBUS_FMT_UYVY8_2X8:
+ case V4L2_MBUS_FMT_Y8_1X8:
+ case V4L2_MBUS_FMT_YUYV10_2X10:
+ SETBIT(val, IPIPEIF_CFG2_YUV8_SHIFT);
+ SETBIT(val, IPIPEIF_CFG2_YUV16_SHIFT);
+ val |= IPIPEIF_CBCR_Y << IPIPEIF_CFG2_YUV8P_SHIFT;
+ break;
+
+ default:
+ /* Bayer */
+ ipipeif_write(params.if_5_1.clip, ipipeif_base_addr,
+ IPIPEIF_OCLIP);
+ }
+ ipipeif_write(val, ipipeif_base_addr, IPIPEIF_CFG2);
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int
+ipipeif_set_config(struct v4l2_subdev *sd, struct ipipeif_params *config)
+{
+ struct vpfe_ipipeif_device *ipipeif = v4l2_get_subdevdata(sd);
+ struct device *dev = ipipeif->subdev.v4l2_dev->dev;
+
+ if (!config) {
+ dev_err(dev, "Invalid configuration pointer\n");
+ return -EINVAL;
+ }
+
+ ipipeif->config.clock_select = config->clock_select;
+ ipipeif->config.ppln = config->ppln;
+ ipipeif->config.lpfr = config->lpfr;
+ ipipeif->config.rsz = config->rsz;
+ ipipeif->config.decimation = config->decimation;
+ if (ipipeif->config.decimation &&
+ (ipipeif->config.rsz < IPIPEIF_RSZ_MIN ||
+ ipipeif->config.rsz > IPIPEIF_RSZ_MAX)) {
+ dev_err(dev, "rsz range is %d to %d\n",
+ IPIPEIF_RSZ_MIN, IPIPEIF_RSZ_MAX);
+ return -EINVAL;
+ }
+
+ ipipeif->config.avg_filter = config->avg_filter;
+
+ ipipeif->config.if_5_1.df_gain_thr = config->if_5_1.df_gain_thr;
+ ipipeif->config.if_5_1.df_gain = config->if_5_1.df_gain;
+ ipipeif->config.if_5_1.df_gain_en = config->if_5_1.df_gain_en;
+
+ ipipeif->config.if_5_1.rsz_start = config->if_5_1.rsz_start;
+ ipipeif->config.if_5_1.align_sync = config->if_5_1.align_sync;
+ ipipeif->config.if_5_1.clip = config->if_5_1.clip;
+
+ ipipeif->config.if_5_1.dpc.en = config->if_5_1.dpc.en;
+ ipipeif->config.if_5_1.dpc.thr = config->if_5_1.dpc.thr;
+
+ ipipeif->config.if_5_1.clk_div.m = config->if_5_1.clk_div.m;
+ ipipeif->config.if_5_1.clk_div.n = config->if_5_1.clk_div.n;
+
+ return 0;
+}
+
+static int
+ipipeif_get_config(struct v4l2_subdev *sd, void __user *arg)
+{
+ struct vpfe_ipipeif_device *ipipeif = v4l2_get_subdevdata(sd);
+ struct ipipeif_params *config = (struct ipipeif_params *)arg;
+ struct device *dev = ipipeif->subdev.v4l2_dev->dev;
+
+ if (!arg) {
+ dev_err(dev, "Invalid configuration pointer\n");
+ return -EINVAL;
+ }
+
+ config->clock_select = ipipeif->config.clock_select;
+ config->ppln = ipipeif->config.ppln;
+ config->lpfr = ipipeif->config.lpfr;
+ config->rsz = ipipeif->config.rsz;
+ config->decimation = ipipeif->config.decimation;
+ config->avg_filter = ipipeif->config.avg_filter;
+
+ config->if_5_1.df_gain_thr = ipipeif->config.if_5_1.df_gain_thr;
+ config->if_5_1.df_gain = ipipeif->config.if_5_1.df_gain;
+ config->if_5_1.df_gain_en = ipipeif->config.if_5_1.df_gain_en;
+
+ config->if_5_1.rsz_start = ipipeif->config.if_5_1.rsz_start;
+ config->if_5_1.align_sync = ipipeif->config.if_5_1.align_sync;
+ config->if_5_1.clip = ipipeif->config.if_5_1.clip;
+
+ config->if_5_1.dpc.en = ipipeif->config.if_5_1.dpc.en;
+ config->if_5_1.dpc.thr = ipipeif->config.if_5_1.dpc.thr;
+
+ config->if_5_1.clk_div.m = ipipeif->config.if_5_1.clk_div.m;
+ config->if_5_1.clk_div.n = ipipeif->config.if_5_1.clk_div.n;
+
+ return 0;
+}
+
+/*
+ * ipipeif_ioctl() - Handle ipipeif module private ioctl's
+ * @sd: pointer to v4l2 subdev structure
+ * @cmd: configuration command
+ * @arg: configuration argument
+ */
+static long ipipeif_ioctl(struct v4l2_subdev *sd,
+ unsigned int cmd, void *arg)
+{
+ struct ipipeif_params *config = (struct ipipeif_params *)arg;
+ int ret = -ENOIOCTLCMD;
+
+ switch (cmd) {
+ case VIDIOC_VPFE_IPIPEIF_S_CONFIG:
+ ret = ipipeif_set_config(sd, config);
+ break;
+
+ case VIDIOC_VPFE_IPIPEIF_G_CONFIG:
+ ret = ipipeif_get_config(sd, arg);
+ break;
+ }
+ return ret;
+}
+
+/*
+ * ipipeif_s_ctrl() - Handle set control subdev method
+ * @ctrl: pointer to v4l2 control structure
+ */
+static int ipipeif_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+ struct vpfe_ipipeif_device *ipipeif =
+ container_of(ctrl->handler, struct vpfe_ipipeif_device, ctrls);
+
+ switch (ctrl->id) {
+ case VPFE_CID_DPCM_PREDICTOR:
+ ipipeif->dpcm_predictor = ctrl->val;
+ break;
+
+ case V4L2_CID_GAIN:
+ ipipeif->gain = ctrl->val;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+#define ENABLE_IPIPEIF 0x1
+
+void vpfe_ipipeif_enable(struct vpfe_device *vpfe_dev)
+{
+ struct vpfe_ipipeif_device *ipipeif = &vpfe_dev->vpfe_ipipeif;
+ void *ipipeif_base_addr = ipipeif->ipipeif_base_addr;
+ unsigned char val;
+
+ if (ipipeif->input != IPIPEIF_INPUT_MEMORY)
+ return;
+
+ do {
+ val = ipipeif_read(ipipeif_base_addr, IPIPEIF_ENABLE);
+ } while (val & 0x1);
+
+ ipipeif_write(ENABLE_IPIPEIF, ipipeif_base_addr, IPIPEIF_ENABLE);
+}
+
+/*
+ * ipipeif_set_stream() - Enable/Disable streaming on ipipeif subdev
+ * @sd: pointer to v4l2 subdev structure
+ * @enable: 1 == Enable, 0 == Disable
+ */
+static int ipipeif_set_stream(struct v4l2_subdev *sd, int enable)
+{
+ struct vpfe_ipipeif_device *ipipeif = v4l2_get_subdevdata(sd);
+ struct vpfe_device *vpfe_dev = to_vpfe_device(ipipeif);
+ int ret = 0;
+
+ if (!enable)
+ return ret;
+
+ ret = ipipeif_hw_setup(sd);
+ if (!ret)
+ vpfe_ipipeif_enable(vpfe_dev);
+
+ return ret;
+}
+
+/*
+ * ipipeif_enum_mbus_code() - Handle pixel format enumeration
+ * @sd: pointer to v4l2 subdev structure
+ * @fh: V4L2 subdev file handle
+ * @code: pointer to v4l2_subdev_mbus_code_enum structure
+ * return -EINVAL or zero on success
+ */
+static int ipipeif_enum_mbus_code(struct v4l2_subdev *sd,
+ struct v4l2_subdev_fh *fh,
+ struct v4l2_subdev_mbus_code_enum *code)
+{
+ switch (code->pad) {
+ case IPIPEIF_PAD_SINK:
+ if (code->index >= ARRAY_SIZE(ipipeif_input_fmts))
+ return -EINVAL;
+
+ code->code = ipipeif_input_fmts[code->index];
+ break;
+
+ case IPIPEIF_PAD_SOURCE:
+ if (code->index >= ARRAY_SIZE(ipipeif_output_fmts))
+ return -EINVAL;
+
+ code->code = ipipeif_output_fmts[code->index];
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/*
+ * ipipeif_get_format() - Handle get format by pads subdev method
+ * @sd: pointer to v4l2 subdev structure
+ * @fh: V4L2 subdev file handle
+ * @fmt: pointer to v4l2 subdev format structure
+ */
+static int
+ipipeif_get_format(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh,
+ struct v4l2_subdev_format *fmt)
+{
+ struct vpfe_ipipeif_device *ipipeif = v4l2_get_subdevdata(sd);
+
+ if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE)
+ fmt->format = ipipeif->formats[fmt->pad];
+ else
+ fmt->format = *(v4l2_subdev_get_try_format(fh, fmt->pad));
+
+ return 0;
+}
+
+#define MIN_OUT_WIDTH 32
+#define MIN_OUT_HEIGHT 32
+
+/*
+ * ipipeif_try_format() - Handle try format by pad subdev method
+ * @ipipeif: VPFE ipipeif device.
+ * @fh: V4L2 subdev file handle.
+ * @pad: pad num.
+ * @fmt: pointer to v4l2 format structure.
+ * @which : wanted subdev format
+ */
+static void
+ipipeif_try_format(struct vpfe_ipipeif_device *ipipeif,
+ struct v4l2_subdev_fh *fh, unsigned int pad,
+ struct v4l2_mbus_framefmt *fmt,
+ enum v4l2_subdev_format_whence which)
+{
+ unsigned int max_out_height;
+ unsigned int max_out_width;
+ unsigned int i;
+
+ max_out_width = IPIPE_MAX_OUTPUT_WIDTH_A;
+ max_out_height = IPIPE_MAX_OUTPUT_HEIGHT_A;
+
+ if (pad == IPIPEIF_PAD_SINK) {
+ for (i = 0; i < ARRAY_SIZE(ipipeif_input_fmts); i++)
+ if (fmt->code == ipipeif_input_fmts[i])
+ break;
+
+ /* If not found, use SBGGR10 as default */
+ if (i >= ARRAY_SIZE(ipipeif_input_fmts))
+ fmt->code = V4L2_MBUS_FMT_SGRBG12_1X12;
+ } else if (pad == IPIPEIF_PAD_SOURCE) {
+ for (i = 0; i < ARRAY_SIZE(ipipeif_output_fmts); i++)
+ if (fmt->code == ipipeif_output_fmts[i])
+ break;
+
+ /* If not found, use UYVY as default */
+ if (i >= ARRAY_SIZE(ipipeif_output_fmts))
+ fmt->code = V4L2_MBUS_FMT_UYVY8_2X8;
+ }
+
+ fmt->width = clamp_t(u32, fmt->width, MIN_OUT_HEIGHT, max_out_width);
+ fmt->height = clamp_t(u32, fmt->height, MIN_OUT_WIDTH, max_out_height);
+}
+
+static int
+ipipeif_enum_frame_size(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh,
+ struct v4l2_subdev_frame_size_enum *fse)
+{
+ struct vpfe_ipipeif_device *ipipeif = v4l2_get_subdevdata(sd);
+ struct v4l2_mbus_framefmt format;
+
+ if (fse->index != 0)
+ return -EINVAL;
+
+ format.code = fse->code;
+ format.width = 1;
+ format.height = 1;
+ ipipeif_try_format(ipipeif, fh, fse->pad, &format,
+ V4L2_SUBDEV_FORMAT_TRY);
+ fse->min_width = format.width;
+ fse->min_height = format.height;
+
+ if (format.code != fse->code)
+ return -EINVAL;
+
+ format.code = fse->code;
+ format.width = -1;
+ format.height = -1;
+ ipipeif_try_format(ipipeif, fh, fse->pad, &format,
+ V4L2_SUBDEV_FORMAT_TRY);
+ fse->max_width = format.width;
+ fse->max_height = format.height;
+
+ return 0;
+}
+
+/*
+ * __ipipeif_get_format() - helper function for getting ipipeif format
+ * @ipipeif: pointer to ipipeif private structure.
+ * @pad: pad number.
+ * @fh: V4L2 subdev file handle.
+ * @which: wanted subdev format.
+ *
+ */
+static struct v4l2_mbus_framefmt *
+__ipipeif_get_format(struct vpfe_ipipeif_device *ipipeif,
+ struct v4l2_subdev_fh *fh, unsigned int pad,
+ enum v4l2_subdev_format_whence which)
+{
+ if (which == V4L2_SUBDEV_FORMAT_TRY)
+ return v4l2_subdev_get_try_format(fh, pad);
+
+ return &ipipeif->formats[pad];
+}
+
+/*
+ * ipipeif_set_format() - Handle set format by pads subdev method
+ * @sd: pointer to v4l2 subdev structure
+ * @fh: V4L2 subdev file handle
+ * @fmt: pointer to v4l2 subdev format structure
+ * return -EINVAL or zero on success
+ */
+static int
+ipipeif_set_format(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh,
+ struct v4l2_subdev_format *fmt)
+{
+ struct vpfe_ipipeif_device *ipipeif = v4l2_get_subdevdata(sd);
+ struct v4l2_mbus_framefmt *format;
+
+ format = __ipipeif_get_format(ipipeif, fh, fmt->pad, fmt->which);
+ if (format == NULL)
+ return -EINVAL;
+
+ ipipeif_try_format(ipipeif, fh, fmt->pad, &fmt->format, fmt->which);
+ *format = fmt->format;
+
+ if (fmt->which == V4L2_SUBDEV_FORMAT_TRY)
+ return 0;
+
+ if (fmt->pad == IPIPEIF_PAD_SINK &&
+ ipipeif->input != IPIPEIF_INPUT_NONE)
+ ipipeif->formats[fmt->pad] = fmt->format;
+ else if (fmt->pad == IPIPEIF_PAD_SOURCE &&
+ ipipeif->output != IPIPEIF_OUTPUT_NONE)
+ ipipeif->formats[fmt->pad] = fmt->format;
+ else
+ return -EINVAL;
+
+ return 0;
+}
+
+static void ipipeif_set_default_config(struct vpfe_ipipeif_device *ipipeif)
+{
+#define WIDTH_I 640
+#define HEIGHT_I 480
+
+ const struct ipipeif_params ipipeif_defaults = {
+ .clock_select = IPIPEIF_SDRAM_CLK,
+ .ppln = WIDTH_I + 8,
+ .lpfr = HEIGHT_I + 10,
+ .rsz = 16, /* resize ratio 16/rsz */
+ .decimation = IPIPEIF_DECIMATION_OFF,
+ .avg_filter = IPIPEIF_AVG_OFF,
+ .if_5_1 = {
+ .clk_div = {
+ .m = 1, /* clock = sdram clock * (m/n) */
+ .n = 6
+ },
+ .clip = 4095,
+ },
+ };
+ memset(&ipipeif->config, 0, sizeof(struct ipipeif_params));
+ memcpy(&ipipeif->config, &ipipeif_defaults,
+ sizeof(struct ipipeif_params));
+}
+
+/*
+ * ipipeif_init_formats() - Initialize formats on all pads
+ * @sd: VPFE ipipeif V4L2 subdevice
+ * @fh: V4L2 subdev file handle
+ *
+ * Initialize all pad formats with default values. If fh is not NULL, try
+ * formats are initialized on the file handle. Otherwise active formats are
+ * initialized on the device.
+ */
+static int
+ipipeif_init_formats(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh)
+{
+ struct vpfe_ipipeif_device *ipipeif = v4l2_get_subdevdata(sd);
+ struct v4l2_subdev_format format;
+
+ memset(&format, 0, sizeof(format));
+ format.pad = IPIPEIF_PAD_SINK;
+ format.which = fh ? V4L2_SUBDEV_FORMAT_TRY : V4L2_SUBDEV_FORMAT_ACTIVE;
+ format.format.code = V4L2_MBUS_FMT_SGRBG12_1X12;
+ format.format.width = IPIPE_MAX_OUTPUT_WIDTH_A;
+ format.format.height = IPIPE_MAX_OUTPUT_HEIGHT_A;
+ ipipeif_set_format(sd, fh, &format);
+
+ memset(&format, 0, sizeof(format));
+ format.pad = IPIPEIF_PAD_SOURCE;
+ format.which = fh ? V4L2_SUBDEV_FORMAT_TRY : V4L2_SUBDEV_FORMAT_ACTIVE;
+ format.format.code = V4L2_MBUS_FMT_UYVY8_2X8;
+ format.format.width = IPIPE_MAX_OUTPUT_WIDTH_A;
+ format.format.height = IPIPE_MAX_OUTPUT_HEIGHT_A;
+ ipipeif_set_format(sd, fh, &format);
+
+ ipipeif_set_default_config(ipipeif);
+
+ return 0;
+}
+
+/*
+ * ipipeif_video_in_queue() - ipipeif video in queue
+ * @vpfe_dev: vpfe device pointer
+ * @addr: buffer address
+ */
+static int
+ipipeif_video_in_queue(struct vpfe_device *vpfe_dev, unsigned long addr)
+{
+ struct vpfe_ipipeif_device *ipipeif = &vpfe_dev->vpfe_ipipeif;
+ void *ipipeif_base_addr = ipipeif->ipipeif_base_addr;
+ unsigned int adofs;
+ u32 val;
+
+ if (ipipeif->input != IPIPEIF_INPUT_MEMORY)
+ return -EINVAL;
+
+ switch (ipipeif->formats[IPIPEIF_PAD_SINK].code) {
+ case V4L2_MBUS_FMT_Y8_1X8:
+ case V4L2_MBUS_FMT_UV8_1X8:
+ case V4L2_MBUS_FMT_YDYUYDYV8_1X16:
+ adofs = ipipeif->formats[IPIPEIF_PAD_SINK].width;
+ break;
+
+ default:
+ adofs = ipipeif->formats[IPIPEIF_PAD_SINK].width << 1;
+ break;
+ }
+
+ /* adjust the line len to be a multiple of 32 */
+ adofs += 31;
+ adofs &= ~0x1f;
+ val = (adofs >> 5) & IPIPEIF_ADOFS_LSB_MASK;
+ ipipeif_write(val, ipipeif_base_addr, IPIPEIF_ADOFS);
+
+ /* lower sixteen bit */
+ val = (addr >> IPIPEIF_ADDRL_SHIFT) & IPIPEIF_ADDRL_MASK;
+ ipipeif_write(val, ipipeif_base_addr, IPIPEIF_ADDRL);
+
+ /* upper next seven bit */
+ val = (addr >> IPIPEIF_ADDRU_SHIFT) & IPIPEIF_ADDRU_MASK;
+ ipipeif_write(val, ipipeif_base_addr, IPIPEIF_ADDRU);
+
+ return 0;
+}
+
+/* subdev core operations */
+static const struct v4l2_subdev_core_ops ipipeif_v4l2_core_ops = {
+ .ioctl = ipipeif_ioctl,
+};
+
+static const struct v4l2_ctrl_ops ipipeif_ctrl_ops = {
+ .s_ctrl = ipipeif_s_ctrl,
+};
+
+static const struct v4l2_ctrl_config vpfe_ipipeif_dpcm_pred = {
+ .ops = &ipipeif_ctrl_ops,
+ .id = VPFE_CID_DPCM_PREDICTOR,
+ .name = "DPCM Predictor",
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .min = 0,
+ .max = 1,
+ .step = 1,
+ .def = 0,
+};
+
+/* subdev file operations */
+static const struct v4l2_subdev_internal_ops ipipeif_v4l2_internal_ops = {
+ .open = ipipeif_init_formats,
+};
+
+/* subdev video operations */
+static const struct v4l2_subdev_video_ops ipipeif_v4l2_video_ops = {
+ .s_stream = ipipeif_set_stream,
+};
+
+/* subdev pad operations */
+static const struct v4l2_subdev_pad_ops ipipeif_v4l2_pad_ops = {
+ .enum_mbus_code = ipipeif_enum_mbus_code,
+ .enum_frame_size = ipipeif_enum_frame_size,
+ .get_fmt = ipipeif_get_format,
+ .set_fmt = ipipeif_set_format,
+};
+
+/* subdev operations */
+static const struct v4l2_subdev_ops ipipeif_v4l2_ops = {
+ .core = &ipipeif_v4l2_core_ops,
+ .video = &ipipeif_v4l2_video_ops,
+ .pad = &ipipeif_v4l2_pad_ops,
+};
+
+static const struct vpfe_video_operations video_in_ops = {
+ .queue = ipipeif_video_in_queue,
+};
+
+static int
+ipipeif_link_setup(struct media_entity *entity, const struct media_pad *local,
+ const struct media_pad *remote, u32 flags)
+{
+ struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(entity);
+ struct vpfe_ipipeif_device *ipipeif = v4l2_get_subdevdata(sd);
+ struct vpfe_device *vpfe = to_vpfe_device(ipipeif);
+
+ switch (local->index | media_entity_type(remote->entity)) {
+ case IPIPEIF_PAD_SINK | MEDIA_ENT_T_DEVNODE:
+ /* Single shot mode */
+ if (!(flags & MEDIA_LNK_FL_ENABLED)) {
+ ipipeif->input = IPIPEIF_INPUT_NONE;
+ break;
+ }
+ ipipeif->input = IPIPEIF_INPUT_MEMORY;
+ break;
+
+ case IPIPEIF_PAD_SINK | MEDIA_ENT_T_V4L2_SUBDEV:
+ /* read from isif */
+ if (!(flags & MEDIA_LNK_FL_ENABLED)) {
+ ipipeif->input = IPIPEIF_INPUT_NONE;
+ break;
+ }
+ if (ipipeif->input != IPIPEIF_INPUT_NONE)
+ return -EBUSY;
+
+ ipipeif->input = IPIPEIF_INPUT_ISIF;
+ break;
+
+ case IPIPEIF_PAD_SOURCE | MEDIA_ENT_T_V4L2_SUBDEV:
+ if (!(flags & MEDIA_LNK_FL_ENABLED)) {
+ ipipeif->output = IPIPEIF_OUTPUT_NONE;
+ break;
+ }
+ if (remote->entity == &vpfe->vpfe_ipipe.subdev.entity)
+ /* connencted to ipipe */
+ ipipeif->output = IPIPEIF_OUTPUT_IPIPE;
+ else if (remote->entity == &vpfe->vpfe_resizer.
+ crop_resizer.subdev.entity)
+ /* connected to resizer */
+ ipipeif->output = IPIPEIF_OUTPUT_RESIZER;
+ else
+ return -EINVAL;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static const struct media_entity_operations ipipeif_media_ops = {
+ .link_setup = ipipeif_link_setup,
+};
+
+/*
+ * vpfe_ipipeif_unregister_entities() - Unregister entity
+ * @ipipeif - pointer to ipipeif subdevice structure.
+ */
+void vpfe_ipipeif_unregister_entities(struct vpfe_ipipeif_device *ipipeif)
+{
+ /* unregister video device */
+ vpfe_video_unregister(&ipipeif->video_in);
+
+ /* unregister subdev */
+ v4l2_device_unregister_subdev(&ipipeif->subdev);
+ /* cleanup entity */
+ media_entity_cleanup(&ipipeif->subdev.entity);
+}
+
+int
+vpfe_ipipeif_register_entities(struct vpfe_ipipeif_device *ipipeif,
+ struct v4l2_device *vdev)
+{
+ struct vpfe_device *vpfe_dev = to_vpfe_device(ipipeif);
+ unsigned int flags;
+ int ret;
+
+ /* Register the subdev */
+ ret = v4l2_device_register_subdev(vdev, &ipipeif->subdev);
+ if (ret < 0)
+ return ret;
+
+ ret = vpfe_video_register(&ipipeif->video_in, vdev);
+ if (ret) {
+ pr_err("Failed to register ipipeif video-in device\n");
+ goto fail;
+ }
+ ipipeif->video_in.vpfe_dev = vpfe_dev;
+
+ flags = 0;
+ ret = media_entity_create_link(&ipipeif->video_in.video_dev.entity, 0,
+ &ipipeif->subdev.entity, 0, flags);
+ if (ret < 0)
+ goto fail;
+
+ return 0;
+fail:
+ v4l2_device_unregister_subdev(&ipipeif->subdev);
+
+ return ret;
+}
+
+#define IPIPEIF_GAIN_HIGH 0x3ff
+#define IPIPEIF_DEFAULT_GAIN 0x200
+
+int vpfe_ipipeif_init(struct vpfe_ipipeif_device *ipipeif,
+ struct platform_device *pdev)
+{
+ struct v4l2_subdev *sd = &ipipeif->subdev;
+ struct media_pad *pads = &ipipeif->pads[0];
+ struct media_entity *me = &sd->entity;
+ static resource_size_t res_len;
+ struct resource *res;
+ int ret;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 3);
+ if (!res)
+ return -ENOENT;
+
+ res_len = resource_size(res);
+ res = request_mem_region(res->start, res_len, res->name);
+ if (!res)
+ return -EBUSY;
+
+ ipipeif->ipipeif_base_addr = ioremap_nocache(res->start, res_len);
+ if (!ipipeif->ipipeif_base_addr) {
+ ret = -EBUSY;
+ goto fail;
+ }
+
+ v4l2_subdev_init(sd, &ipipeif_v4l2_ops);
+
+ sd->internal_ops = &ipipeif_v4l2_internal_ops;
+ strlcpy(sd->name, "DAVINCI IPIPEIF", sizeof(sd->name));
+ sd->grp_id = 1 << 16; /* group ID for davinci subdevs */
+
+ v4l2_set_subdevdata(sd, ipipeif);
+
+ sd->flags |= V4L2_SUBDEV_FL_HAS_EVENTS | V4L2_SUBDEV_FL_HAS_DEVNODE;
+ pads[IPIPEIF_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
+ pads[IPIPEIF_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
+ ipipeif->input = IPIPEIF_INPUT_NONE;
+ ipipeif->output = IPIPEIF_OUTPUT_NONE;
+ me->ops = &ipipeif_media_ops;
+
+ ret = media_entity_init(me, IPIPEIF_NUM_PADS, pads, 0);
+ if (ret)
+ goto fail;
+
+ v4l2_ctrl_handler_init(&ipipeif->ctrls, 2);
+ v4l2_ctrl_new_std(&ipipeif->ctrls, &ipipeif_ctrl_ops,
+ V4L2_CID_GAIN, 0,
+ IPIPEIF_GAIN_HIGH, 1, IPIPEIF_DEFAULT_GAIN);
+ v4l2_ctrl_new_custom(&ipipeif->ctrls, &vpfe_ipipeif_dpcm_pred, NULL);
+ v4l2_ctrl_handler_setup(&ipipeif->ctrls);
+ sd->ctrl_handler = &ipipeif->ctrls;
+
+ ipipeif->video_in.ops = &video_in_ops;
+ ipipeif->video_in.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
+ ret = vpfe_video_init(&ipipeif->video_in, "IPIPEIF");
+ if (ret) {
+ pr_err("Failed to init IPIPEIF video-in device\n");
+ goto fail;
+ }
+ ipipeif_set_default_config(ipipeif);
+ return 0;
+fail:
+ release_mem_region(res->start, res_len);
+ return ret;
+}
+
+void
+vpfe_ipipeif_cleanup(struct vpfe_ipipeif_device *ipipeif,
+ struct platform_device *pdev)
+{
+ struct resource *res;
+
+ v4l2_ctrl_handler_free(&ipipeif->ctrls);
+ iounmap(ipipeif->ipipeif_base_addr);
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 3);
+ if (res)
+ release_mem_region(res->start, resource_size(res));
+
+}
diff --git a/drivers/staging/media/davinci_vpfe/dm365_ipipeif.h b/drivers/staging/media/davinci_vpfe/dm365_ipipeif.h
new file mode 100644
index 00000000000..608701fc5fe
--- /dev/null
+++ b/drivers/staging/media/davinci_vpfe/dm365_ipipeif.h
@@ -0,0 +1,233 @@
+/*
+ * Copyright (C) 2012 Texas Instruments Inc
+ *
+ * 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 version 2.
+ *
+ * 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
+ *
+ * Contributors:
+ * Manjunath Hadli <manjunath.hadli@ti.com>
+ * Prabhakar Lad <prabhakar.lad@ti.com>
+ */
+
+#ifndef _DAVINCI_VPFE_DM365_IPIPEIF_H
+#define _DAVINCI_VPFE_DM365_IPIPEIF_H
+
+#include <linux/platform_device.h>
+
+#include <media/davinci/vpss.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-subdev.h>
+
+#include "dm365_ipipeif_user.h"
+#include "vpfe_video.h"
+
+/* IPIPE base specific types */
+enum ipipeif_data_shift {
+ IPIPEIF_BITS15_2 = 0,
+ IPIPEIF_BITS14_1 = 1,
+ IPIPEIF_BITS13_0 = 2,
+ IPIPEIF_BITS12_0 = 3,
+ IPIPEIF_BITS11_0 = 4,
+ IPIPEIF_BITS10_0 = 5,
+ IPIPEIF_BITS9_0 = 6,
+};
+
+enum ipipeif_clkdiv {
+ IPIPEIF_DIVIDE_HALF = 0,
+ IPIPEIF_DIVIDE_THIRD = 1,
+ IPIPEIF_DIVIDE_FOURTH = 2,
+ IPIPEIF_DIVIDE_FIFTH = 3,
+ IPIPEIF_DIVIDE_SIXTH = 4,
+ IPIPEIF_DIVIDE_EIGHTH = 5,
+ IPIPEIF_DIVIDE_SIXTEENTH = 6,
+ IPIPEIF_DIVIDE_THIRTY = 7,
+};
+
+enum ipipeif_pack_mode {
+ IPIPEIF_PACK_16_BIT = 0,
+ IPIPEIF_PACK_8_BIT = 1,
+};
+
+enum ipipeif_5_1_pack_mode {
+ IPIPEIF_5_1_PACK_16_BIT = 0,
+ IPIPEIF_5_1_PACK_8_BIT = 1,
+ IPIPEIF_5_1_PACK_8_BIT_A_LAW = 2,
+ IPIPEIF_5_1_PACK_12_BIT = 3
+};
+
+enum ipipeif_input_source {
+ IPIPEIF_CCDC = 0,
+ IPIPEIF_SDRAM_RAW = 1,
+ IPIPEIF_CCDC_DARKFM = 2,
+ IPIPEIF_SDRAM_YUV = 3,
+};
+
+enum ipipeif_ialaw {
+ IPIPEIF_ALAW_OFF = 0,
+ IPIPEIF_ALAW_ON = 1,
+};
+
+enum ipipeif_input_src1 {
+ IPIPEIF_SRC1_PARALLEL_PORT = 0,
+ IPIPEIF_SRC1_SDRAM_RAW = 1,
+ IPIPEIF_SRC1_ISIF_DARKFM = 2,
+ IPIPEIF_SRC1_SDRAM_YUV = 3,
+};
+
+enum ipipeif_dfs_dir {
+ IPIPEIF_PORT_MINUS_SDRAM = 0,
+ IPIPEIF_SDRAM_MINUS_PORT = 1,
+};
+
+enum ipipeif_chroma_phase {
+ IPIPEIF_CBCR_Y = 0,
+ IPIPEIF_Y_CBCR = 1,
+};
+
+enum ipipeif_dpcm_type {
+ IPIPEIF_DPCM_8BIT_10BIT = 0,
+ IPIPEIF_DPCM_8BIT_12BIT = 1,
+};
+
+/* data shift for IPIPE 5.1 */
+enum ipipeif_5_1_data_shift {
+ IPIPEIF_5_1_BITS11_0 = 0,
+ IPIPEIF_5_1_BITS10_0 = 1,
+ IPIPEIF_5_1_BITS9_0 = 2,
+ IPIPEIF_5_1_BITS8_0 = 3,
+ IPIPEIF_5_1_BITS7_0 = 4,
+ IPIPEIF_5_1_BITS15_4 = 5,
+};
+
+#define IPIPEIF_PAD_SINK 0
+#define IPIPEIF_PAD_SOURCE 1
+
+#define IPIPEIF_NUM_PADS 2
+
+enum ipipeif_input_entity {
+ IPIPEIF_INPUT_NONE = 0,
+ IPIPEIF_INPUT_ISIF = 1,
+ IPIPEIF_INPUT_MEMORY = 2,
+};
+
+enum ipipeif_output_entity {
+ IPIPEIF_OUTPUT_NONE = 0,
+ IPIPEIF_OUTPUT_IPIPE = 1,
+ IPIPEIF_OUTPUT_RESIZER = 2,
+};
+
+struct vpfe_ipipeif_device {
+ struct v4l2_subdev subdev;
+ struct media_pad pads[IPIPEIF_NUM_PADS];
+ struct v4l2_mbus_framefmt formats[IPIPEIF_NUM_PADS];
+ enum ipipeif_input_entity input;
+ unsigned int output;
+ struct vpfe_video_device video_in;
+ struct v4l2_ctrl_handler ctrls;
+ void *__iomem ipipeif_base_addr;
+ struct ipipeif_params config;
+ int dpcm_predictor;
+ int gain;
+};
+
+/* IPIPEIF Register Offsets from the base address */
+#define IPIPEIF_ENABLE 0x00
+#define IPIPEIF_CFG1 0x04
+#define IPIPEIF_PPLN 0x08
+#define IPIPEIF_LPFR 0x0c
+#define IPIPEIF_HNUM 0x10
+#define IPIPEIF_VNUM 0x14
+#define IPIPEIF_ADDRU 0x18
+#define IPIPEIF_ADDRL 0x1c
+#define IPIPEIF_ADOFS 0x20
+#define IPIPEIF_RSZ 0x24
+#define IPIPEIF_GAIN 0x28
+
+/* Below registers are available only on IPIPE 5.1 */
+#define IPIPEIF_DPCM 0x2c
+#define IPIPEIF_CFG2 0x30
+#define IPIPEIF_INIRSZ 0x34
+#define IPIPEIF_OCLIP 0x38
+#define IPIPEIF_DTUDF 0x3c
+#define IPIPEIF_CLKDIV 0x40
+#define IPIPEIF_DPC1 0x44
+#define IPIPEIF_DPC2 0x48
+#define IPIPEIF_DFSGVL 0x4c
+#define IPIPEIF_DFSGTH 0x50
+#define IPIPEIF_RSZ3A 0x54
+#define IPIPEIF_INIRSZ3A 0x58
+#define IPIPEIF_RSZ_MIN 16
+#define IPIPEIF_RSZ_MAX 112
+#define IPIPEIF_RSZ_CONST 16
+#define SETBIT(reg, bit) (reg = ((reg) | ((0x00000001)<<(bit))))
+#define RESETBIT(reg, bit) (reg = ((reg) & (~(0x00000001<<(bit)))))
+
+#define IPIPEIF_ADOFS_LSB_MASK 0x1ff
+#define IPIPEIF_ADOFS_LSB_SHIFT 5
+#define IPIPEIF_ADOFS_MSB_MASK 0x200
+#define IPIPEIF_ADDRU_MASK 0x7ff
+#define IPIPEIF_ADDRL_SHIFT 5
+#define IPIPEIF_ADDRL_MASK 0xffff
+#define IPIPEIF_ADDRU_SHIFT 21
+#define IPIPEIF_ADDRMSB_SHIFT 31
+#define IPIPEIF_ADDRMSB_LEFT_SHIFT 10
+
+/* CFG1 Masks and shifts */
+#define ONESHOT_SHIFT 0
+#define DECIM_SHIFT 1
+#define INPSRC_SHIFT 2
+#define CLKDIV_SHIFT 4
+#define AVGFILT_SHIFT 7
+#define PACK8IN_SHIFT 8
+#define IALAW_SHIFT 9
+#define CLKSEL_SHIFT 10
+#define DATASFT_SHIFT 11
+#define INPSRC1_SHIFT 14
+
+/* DPC2 */
+#define IPIPEIF_DPC2_EN_SHIFT 12
+#define IPIPEIF_DPC2_THR_MASK 0xfff
+/* Applicable for IPIPE 5.1 */
+#define IPIPEIF_DF_GAIN_EN_SHIFT 10
+#define IPIPEIF_DF_GAIN_MASK 0x3ff
+#define IPIPEIF_DF_GAIN_THR_MASK 0xfff
+/* DPCM */
+#define IPIPEIF_DPCM_BITS_SHIFT 2
+#define IPIPEIF_DPCM_PRED_SHIFT 1
+/* CFG2 */
+#define IPIPEIF_CFG2_HDPOL_SHIFT 1
+#define IPIPEIF_CFG2_VDPOL_SHIFT 2
+#define IPIPEIF_CFG2_YUV8_SHIFT 6
+#define IPIPEIF_CFG2_YUV16_SHIFT 3
+#define IPIPEIF_CFG2_YUV8P_SHIFT 7
+
+/* INIRSZ */
+#define IPIPEIF_INIRSZ_ALNSYNC_SHIFT 13
+#define IPIPEIF_INIRSZ_MASK 0x1fff
+
+/* CLKDIV */
+#define IPIPEIF_CLKDIV_M_SHIFT 8
+
+void vpfe_ipipeif_enable(struct vpfe_device *vpfe_dev);
+void vpfe_ipipeif_ss_buffer_isr(struct vpfe_ipipeif_device *ipipeif);
+int vpfe_ipipeif_decimation_enabled(struct vpfe_device *vpfe_dev);
+int vpfe_ipipeif_get_rsz(struct vpfe_device *vpfe_dev);
+void vpfe_ipipeif_cleanup(struct vpfe_ipipeif_device *ipipeif,
+ struct platform_device *pdev);
+int vpfe_ipipeif_init(struct vpfe_ipipeif_device *ipipeif,
+ struct platform_device *pdev);
+int vpfe_ipipeif_register_entities(struct vpfe_ipipeif_device *ipipeif,
+ struct v4l2_device *vdev);
+void vpfe_ipipeif_unregister_entities(struct vpfe_ipipeif_device *ipipeif);
+
+#endif /* _DAVINCI_VPFE_DM365_IPIPEIF_H */
diff --git a/drivers/staging/media/davinci_vpfe/dm365_ipipeif_user.h b/drivers/staging/media/davinci_vpfe/dm365_ipipeif_user.h
new file mode 100644
index 00000000000..e2a69b59554
--- /dev/null
+++ b/drivers/staging/media/davinci_vpfe/dm365_ipipeif_user.h
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2012 Texas Instruments Inc
+ *
+ * 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 version 2.
+ *
+ * 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
+ *
+ * Contributors:
+ * Manjunath Hadli <manjunath.hadli@ti.com>
+ * Prabhakar Lad <prabhakar.lad@ti.com>
+ */
+
+#ifndef _DAVINCI_VPFE_DM365_IPIPEIF_USER_H
+#define _DAVINCI_VPFE_DM365_IPIPEIF_USER_H
+
+/* clockdiv for IPIPE 5.1 */
+struct ipipeif_5_1_clkdiv {
+ unsigned char m;
+ unsigned char n;
+};
+
+enum ipipeif_decimation {
+ IPIPEIF_DECIMATION_OFF,
+ IPIPEIF_DECIMATION_ON
+};
+
+/* DPC at the if for IPIPE 5.1 */
+struct ipipeif_dpc {
+ /* 0 - disable, 1 - enable */
+ unsigned char en;
+ /* threshold */
+ unsigned short thr;
+};
+
+enum ipipeif_clock {
+ IPIPEIF_PIXCEL_CLK,
+ IPIPEIF_SDRAM_CLK
+};
+
+enum ipipeif_avg_filter {
+ IPIPEIF_AVG_OFF,
+ IPIPEIF_AVG_ON
+};
+
+struct ipipeif_5_1 {
+ struct ipipeif_5_1_clkdiv clk_div;
+ /* Defect pixel correction */
+ struct ipipeif_dpc dpc;
+ /* clipped to this value */
+ unsigned short clip;
+ /* Align HSync and VSync to rsz_start */
+ unsigned char align_sync;
+ /* resizer start position */
+ unsigned int rsz_start;
+ /* DF gain enable */
+ unsigned char df_gain_en;
+ /* DF gain value */
+ unsigned short df_gain;
+ /* DF gain threshold value */
+ unsigned short df_gain_thr;
+};
+
+struct ipipeif_params {
+ enum ipipeif_clock clock_select;
+ unsigned int ppln;
+ unsigned int lpfr;
+ unsigned char rsz;
+ enum ipipeif_decimation decimation;
+ enum ipipeif_avg_filter avg_filter;
+ /* IPIPE 5.1 */
+ struct ipipeif_5_1 if_5_1;
+};
+
+/*
+ * Private IOCTL
+ * VIDIOC_VPFE_IPIPEIF_S_CONFIG: Set IPIEIF configuration
+ * VIDIOC_VPFE_IPIPEIF_G_CONFIG: Get IPIEIF configuration
+ */
+#define VIDIOC_VPFE_IPIPEIF_S_CONFIG \
+ _IOWR('I', BASE_VIDIOC_PRIVATE + 1, struct ipipeif_params)
+#define VIDIOC_VPFE_IPIPEIF_G_CONFIG \
+ _IOWR('I', BASE_VIDIOC_PRIVATE + 2, struct ipipeif_params)
+
+#endif /* _DAVINCI_VPFE_DM365_IPIPEIF_USER_H */
diff --git a/drivers/staging/media/davinci_vpfe/dm365_isif.c b/drivers/staging/media/davinci_vpfe/dm365_isif.c
new file mode 100644
index 00000000000..b942bf73c43
--- /dev/null
+++ b/drivers/staging/media/davinci_vpfe/dm365_isif.c
@@ -0,0 +1,2105 @@
+/*
+ * Copyright (C) 2012 Texas Instruments Inc
+ *
+ * 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 version 2.
+ *
+ * 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
+ *
+ * Contributors:
+ * Manjunath Hadli <manjunath.hadli@ti.com>
+ * Prabhakar Lad <prabhakar.lad@ti.com>
+ */
+
+#include <linux/delay.h>
+#include "dm365_isif.h"
+#include "vpfe_mc_capture.h"
+
+#define MAX_WIDTH 4096
+#define MAX_HEIGHT 4096
+
+static const unsigned int isif_fmts[] = {
+ V4L2_MBUS_FMT_YUYV8_2X8,
+ V4L2_MBUS_FMT_UYVY8_2X8,
+ V4L2_MBUS_FMT_YUYV8_1X16,
+ V4L2_MBUS_FMT_YUYV10_1X20,
+ V4L2_MBUS_FMT_SGRBG12_1X12,
+ V4L2_MBUS_FMT_SGRBG10_ALAW8_1X8,
+ V4L2_MBUS_FMT_SGRBG10_DPCM8_1X8,
+};
+
+#define ISIF_COLPTN_R_Ye 0x0
+#define ISIF_COLPTN_Gr_Cy 0x1
+#define ISIF_COLPTN_Gb_G 0x2
+#define ISIF_COLPTN_B_Mg 0x3
+
+#define ISIF_CCOLP_CP01_0 0
+#define ISIF_CCOLP_CP03_2 2
+#define ISIF_CCOLP_CP05_4 4
+#define ISIF_CCOLP_CP07_6 6
+#define ISIF_CCOLP_CP11_0 8
+#define ISIF_CCOLP_CP13_2 10
+#define ISIF_CCOLP_CP15_4 12
+#define ISIF_CCOLP_CP17_6 14
+
+static const u32 isif_sgrbg_pattern =
+ ISIF_COLPTN_Gr_Cy << ISIF_CCOLP_CP01_0 |
+ ISIF_COLPTN_R_Ye << ISIF_CCOLP_CP03_2 |
+ ISIF_COLPTN_B_Mg << ISIF_CCOLP_CP05_4 |
+ ISIF_COLPTN_Gb_G << ISIF_CCOLP_CP07_6 |
+ ISIF_COLPTN_Gr_Cy << ISIF_CCOLP_CP11_0 |
+ ISIF_COLPTN_R_Ye << ISIF_CCOLP_CP13_2 |
+ ISIF_COLPTN_B_Mg << ISIF_CCOLP_CP15_4 |
+ ISIF_COLPTN_Gb_G << ISIF_CCOLP_CP17_6;
+
+static const u32 isif_srggb_pattern =
+ ISIF_COLPTN_R_Ye << ISIF_CCOLP_CP01_0 |
+ ISIF_COLPTN_Gr_Cy << ISIF_CCOLP_CP03_2 |
+ ISIF_COLPTN_Gb_G << ISIF_CCOLP_CP05_4 |
+ ISIF_COLPTN_B_Mg << ISIF_CCOLP_CP07_6 |
+ ISIF_COLPTN_R_Ye << ISIF_CCOLP_CP11_0 |
+ ISIF_COLPTN_Gr_Cy << ISIF_CCOLP_CP13_2 |
+ ISIF_COLPTN_Gb_G << ISIF_CCOLP_CP15_4 |
+ ISIF_COLPTN_B_Mg << ISIF_CCOLP_CP17_6;
+
+static inline u32 isif_read(void *__iomem base_addr, u32 offset)
+{
+ return readl(base_addr + offset);
+}
+
+static inline void isif_write(void *__iomem base_addr, u32 val, u32 offset)
+{
+ writel(val, base_addr + offset);
+}
+
+static inline u32 isif_merge(void *__iomem base_addr, u32 mask, u32 val,
+ u32 offset)
+{
+ u32 new_val = (isif_read(base_addr, offset) & ~mask) | (val & mask);
+
+ isif_write(base_addr, new_val, offset);
+
+ return new_val;
+}
+
+static void isif_enable_output_to_sdram(struct vpfe_isif_device *isif, int en)
+{
+ isif_merge(isif->isif_cfg.base_addr, ISIF_SYNCEN_WEN_MASK,
+ en << ISIF_SYNCEN_WEN_SHIFT, SYNCEN);
+}
+
+static inline void
+isif_regw_lin_tbl(struct vpfe_isif_device *isif, u32 val, u32 offset, int i)
+{
+ if (!i)
+ writel(val, isif->isif_cfg.linear_tbl0_addr + offset);
+ else
+ writel(val, isif->isif_cfg.linear_tbl1_addr + offset);
+}
+
+static void isif_disable_all_modules(struct vpfe_isif_device *isif)
+{
+ /* disable BC */
+ isif_write(isif->isif_cfg.base_addr, 0, CLAMPCFG);
+ /* disable vdfc */
+ isif_write(isif->isif_cfg.base_addr, 0, DFCCTL);
+ /* disable CSC */
+ isif_write(isif->isif_cfg.base_addr, 0, CSCCTL);
+ /* disable linearization */
+ isif_write(isif->isif_cfg.base_addr, 0, LINCFG0);
+}
+
+static void isif_enable(struct vpfe_isif_device *isif, int en)
+{
+ if (!en)
+ /* Before disable isif, disable all ISIF modules */
+ isif_disable_all_modules(isif);
+
+ /*
+ * wait for next VD. Assume lowest scan rate is 12 Hz. So
+ * 100 msec delay is good enough
+ */
+ msleep(100);
+ isif_merge(isif->isif_cfg.base_addr, ISIF_SYNCEN_VDHDEN_MASK,
+ en, SYNCEN);
+}
+
+/*
+ * ISIF helper functions
+ */
+
+#define DM365_ISIF_MDFS_OFFSET 15
+#define DM365_ISIF_MDFS_MASK 0x1
+
+/* get field id in isif hardware */
+enum v4l2_field vpfe_isif_get_fid(struct vpfe_device *vpfe_dev)
+{
+ struct vpfe_isif_device *isif = &vpfe_dev->vpfe_isif;
+ u32 field_status;
+
+ field_status = isif_read(isif->isif_cfg.base_addr, MODESET);
+ field_status = (field_status >> DM365_ISIF_MDFS_OFFSET) &
+ DM365_ISIF_MDFS_MASK;
+ return field_status;
+}
+
+static int
+isif_set_pixel_format(struct vpfe_isif_device *isif, unsigned int pixfmt)
+{
+ if (isif->formats[ISIF_PAD_SINK].code == V4L2_MBUS_FMT_SGRBG12_1X12) {
+ if (pixfmt == V4L2_PIX_FMT_SBGGR16)
+ isif->isif_cfg.data_pack = ISIF_PACK_16BIT;
+ else if ((pixfmt == V4L2_PIX_FMT_SGRBG10DPCM8) ||
+ (pixfmt == V4L2_PIX_FMT_SGRBG10ALAW8))
+ isif->isif_cfg.data_pack = ISIF_PACK_8BIT;
+ else
+ return -EINVAL;
+
+ isif->isif_cfg.bayer.pix_fmt = ISIF_PIXFMT_RAW;
+ isif->isif_cfg.bayer.v4l2_pix_fmt = pixfmt;
+ } else {
+ if (pixfmt == V4L2_PIX_FMT_YUYV)
+ isif->isif_cfg.ycbcr.pix_order = ISIF_PIXORDER_YCBYCR;
+ else if (pixfmt == V4L2_PIX_FMT_UYVY)
+ isif->isif_cfg.ycbcr.pix_order = ISIF_PIXORDER_CBYCRY;
+ else
+ return -EINVAL;
+
+ isif->isif_cfg.data_pack = ISIF_PACK_8BIT;
+ isif->isif_cfg.ycbcr.v4l2_pix_fmt = pixfmt;
+ }
+
+ return 0;
+}
+
+static int
+isif_set_frame_format(struct vpfe_isif_device *isif,
+ enum isif_frmfmt frm_fmt)
+{
+ if (isif->formats[ISIF_PAD_SINK].code == V4L2_MBUS_FMT_SGRBG12_1X12)
+ isif->isif_cfg.bayer.frm_fmt = frm_fmt;
+ else
+ isif->isif_cfg.ycbcr.frm_fmt = frm_fmt;
+
+ return 0;
+}
+
+static int isif_set_image_window(struct vpfe_isif_device *isif)
+{
+ struct v4l2_rect *win = &isif->crop;
+
+ if (isif->formats[ISIF_PAD_SINK].code == V4L2_MBUS_FMT_SGRBG12_1X12) {
+ isif->isif_cfg.bayer.win.top = win->top;
+ isif->isif_cfg.bayer.win.left = win->left;
+ isif->isif_cfg.bayer.win.width = win->width;
+ isif->isif_cfg.bayer.win.height = win->height;
+ return 0;
+ }
+ isif->isif_cfg.ycbcr.win.top = win->top;
+ isif->isif_cfg.ycbcr.win.left = win->left;
+ isif->isif_cfg.ycbcr.win.width = win->width;
+ isif->isif_cfg.ycbcr.win.height = win->height;
+
+ return 0;
+}
+
+static int
+isif_set_buftype(struct vpfe_isif_device *isif, enum isif_buftype buf_type)
+{
+ if (isif->formats[ISIF_PAD_SINK].code == V4L2_MBUS_FMT_SGRBG12_1X12)
+ isif->isif_cfg.bayer.buf_type = buf_type;
+ else
+ isif->isif_cfg.ycbcr.buf_type = buf_type;
+
+ return 0;
+}
+
+/* configure format in isif hardware */
+static int
+isif_config_format(struct vpfe_device *vpfe_dev, unsigned int pad)
+{
+ struct vpfe_isif_device *vpfe_isif = &vpfe_dev->vpfe_isif;
+ enum isif_frmfmt frm_fmt = ISIF_FRMFMT_INTERLACED;
+ struct v4l2_pix_format format;
+ int ret = 0;
+
+ v4l2_fill_pix_format(&format, &vpfe_dev->vpfe_isif.formats[pad]);
+ mbus_to_pix(&vpfe_dev->vpfe_isif.formats[pad], &format);
+
+ if (isif_set_pixel_format(vpfe_isif, format.pixelformat) < 0) {
+ v4l2_err(&vpfe_dev->v4l2_dev,
+ "Failed to set pixel format in isif\n");
+ return -EINVAL;
+ }
+
+ /* call for s_crop will override these values */
+ vpfe_isif->crop.left = 0;
+ vpfe_isif->crop.top = 0;
+ vpfe_isif->crop.width = format.width;
+ vpfe_isif->crop.height = format.height;
+
+ /* configure the image window */
+ isif_set_image_window(vpfe_isif);
+
+ switch (vpfe_dev->vpfe_isif.formats[pad].field) {
+ case V4L2_FIELD_INTERLACED:
+ /* do nothing, since it is default */
+ ret = isif_set_buftype(vpfe_isif, ISIF_BUFTYPE_FLD_INTERLEAVED);
+ break;
+
+ case V4L2_FIELD_NONE:
+ frm_fmt = ISIF_FRMFMT_PROGRESSIVE;
+ /* buffer type only applicable for interlaced scan */
+ break;
+
+ case V4L2_FIELD_SEQ_TB:
+ ret = isif_set_buftype(vpfe_isif, ISIF_BUFTYPE_FLD_SEPARATED);
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ /* set the frame format */
+ if (!ret)
+ ret = isif_set_frame_format(vpfe_isif, frm_fmt);
+
+ return ret;
+}
+
+/*
+ * isif_try_format() - Try video format on a pad
+ * @isif: VPFE isif device
+ * @fh: V4L2 subdev file handle
+ * @fmt: pointer to v4l2 subdev format structure
+ */
+static void
+isif_try_format(struct vpfe_isif_device *isif, struct v4l2_subdev_fh *fh,
+ struct v4l2_subdev_format *fmt)
+{
+ unsigned int width = fmt->format.width;
+ unsigned int height = fmt->format.height;
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(isif_fmts); i++) {
+ if (fmt->format.code == isif_fmts[i])
+ break;
+ }
+
+ /* If not found, use YUYV8_2x8 as default */
+ if (i >= ARRAY_SIZE(isif_fmts))
+ fmt->format.code = V4L2_MBUS_FMT_YUYV8_2X8;
+
+ /* Clamp the size. */
+ fmt->format.width = clamp_t(u32, width, 32, MAX_WIDTH);
+ fmt->format.height = clamp_t(u32, height, 32, MAX_HEIGHT);
+
+ /* The data formatter truncates the number of horizontal output
+ * pixels to a multiple of 16. To avoid clipping data, allow
+ * callers to request an output size bigger than the input size
+ * up to the nearest multiple of 16.
+ */
+ if (fmt->pad == ISIF_PAD_SOURCE)
+ fmt->format.width &= ~15;
+}
+
+/*
+ * vpfe_isif_buffer_isr() - isif module non-progressive buffer scheduling isr
+ * @isif: Pointer to isif subdevice.
+ */
+void vpfe_isif_buffer_isr(struct vpfe_isif_device *isif)
+{
+ struct vpfe_device *vpfe_dev = to_vpfe_device(isif);
+ struct vpfe_video_device *video = &isif->video_out;
+ enum v4l2_field field;
+ int fid;
+
+ if (!video->started)
+ return;
+
+ field = video->fmt.fmt.pix.field;
+
+ if (field == V4L2_FIELD_NONE) {
+ /* handle progressive frame capture */
+ if (video->cur_frm != video->next_frm)
+ vpfe_video_process_buffer_complete(video);
+ return;
+ }
+
+ /* interlaced or TB capture check which field we
+ * are in hardware
+ */
+ fid = vpfe_isif_get_fid(vpfe_dev);
+
+ /* switch the software maintained field id */
+ video->field_id ^= 1;
+ if (fid == video->field_id) {
+ /* we are in-sync here,continue */
+ if (fid == 0) {
+ /*
+ * One frame is just being captured. If the
+ * next frame is available, release the current
+ * frame and move on
+ */
+ if (video->cur_frm != video->next_frm)
+ vpfe_video_process_buffer_complete(video);
+ /*
+ * based on whether the two fields are stored
+ * interleavely or separately in memory,
+ * reconfigure the ISIF memory address
+ */
+ if (field == V4L2_FIELD_SEQ_TB)
+ vpfe_video_schedule_bottom_field(video);
+ return;
+ }
+ /*
+ * if one field is just being captured configure
+ * the next frame get the next frame from the
+ * empty queue if no frame is available hold on
+ * to the current buffer
+ */
+ spin_lock(&video->dma_queue_lock);
+ if (!list_empty(&video->dma_queue) &&
+ video->cur_frm == video->next_frm)
+ vpfe_video_schedule_next_buffer(video);
+ spin_unlock(&video->dma_queue_lock);
+ } else if (fid == 0) {
+ /*
+ * out of sync. Recover from any hardware out-of-sync.
+ * May loose one frame
+ */
+ video->field_id = fid;
+ }
+}
+
+/*
+ * vpfe_isif_vidint1_isr() - ISIF module progressive buffer scheduling isr
+ * @isif: Pointer to isif subdevice.
+ */
+void vpfe_isif_vidint1_isr(struct vpfe_isif_device *isif)
+{
+ struct vpfe_video_device *video = &isif->video_out;
+
+ if (!video->started)
+ return;
+
+ spin_lock(&video->dma_queue_lock);
+ if (video->fmt.fmt.pix.field == V4L2_FIELD_NONE &&
+ !list_empty(&video->dma_queue) && video->cur_frm == video->next_frm)
+ vpfe_video_schedule_next_buffer(video);
+
+ spin_unlock(&video->dma_queue_lock);
+}
+
+/*
+ * VPFE video operations
+ */
+
+static int isif_video_queue(struct vpfe_device *vpfe_dev, unsigned long addr)
+{
+ struct vpfe_isif_device *isif = &vpfe_dev->vpfe_isif;
+
+ isif_write(isif->isif_cfg.base_addr, (addr >> 21) &
+ ISIF_CADU_BITS, CADU);
+ isif_write(isif->isif_cfg.base_addr, (addr >> 5) &
+ ISIF_CADL_BITS, CADL);
+
+ return 0;
+}
+
+static const struct vpfe_video_operations isif_video_ops = {
+ .queue = isif_video_queue,
+};
+
+/*
+ * V4L2 subdev operations
+ */
+
+/* Parameter operations */
+static int isif_get_params(struct v4l2_subdev *sd, void *params)
+{
+ struct vpfe_isif_device *isif = v4l2_get_subdevdata(sd);
+
+ /* only raw module parameters can be set through the IOCTL */
+ if (isif->formats[ISIF_PAD_SINK].code != V4L2_MBUS_FMT_SGRBG12_1X12)
+ return -EINVAL;
+ memcpy(params, &isif->isif_cfg.bayer.config_params,
+ sizeof(isif->isif_cfg.bayer.config_params));
+ return 0;
+}
+
+static int isif_validate_df_csc_params(struct vpfe_isif_df_csc *df_csc)
+{
+ struct vpfe_isif_color_space_conv *csc;
+ int err = -EINVAL;
+ int csc_df_en;
+ int i;
+
+ if (!df_csc->df_or_csc) {
+ /* csc configuration */
+ csc = &df_csc->csc;
+ if (csc->en) {
+ csc_df_en = 1;
+ for (i = 0; i < VPFE_ISIF_CSC_NUM_COEFF; i++)
+ if (csc->coeff[i].integer >
+ ISIF_CSC_COEF_INTEG_MASK ||
+ csc->coeff[i].decimal >
+ ISIF_CSC_COEF_DECIMAL_MASK) {
+ pr_err("Invalid CSC coefficients\n");
+ return err;
+ }
+ }
+ }
+ if (df_csc->start_pix > ISIF_DF_CSC_SPH_MASK) {
+ pr_err("Invalid df_csc start pix value\n");
+ return err;
+ }
+
+ if (df_csc->num_pixels > ISIF_DF_NUMPIX) {
+ pr_err("Invalid df_csc num pixels value\n");
+ return err;
+ }
+
+ if (df_csc->start_line > ISIF_DF_CSC_LNH_MASK) {
+ pr_err("Invalid df_csc start_line value\n");
+ return err;
+ }
+
+ if (df_csc->num_lines > ISIF_DF_NUMLINES) {
+ pr_err("Invalid df_csc num_lines value\n");
+ return err;
+ }
+
+ return 0;
+}
+
+#define DM365_ISIF_MAX_VDFLSFT 4
+#define DM365_ISIF_MAX_VDFSLV 4095
+#define DM365_ISIF_MAX_DFCMEM0 0x1fff
+#define DM365_ISIF_MAX_DFCMEM1 0x1fff
+
+static int isif_validate_dfc_params(struct vpfe_isif_dfc *dfc)
+{
+ int err = -EINVAL;
+ int i;
+
+ if (!dfc->en)
+ return 0;
+
+ if (dfc->corr_whole_line > 1) {
+ pr_err("Invalid corr_whole_line value\n");
+ return err;
+ }
+
+ if (dfc->def_level_shift > DM365_ISIF_MAX_VDFLSFT) {
+ pr_err("Invalid def_level_shift value\n");
+ return err;
+ }
+
+ if (dfc->def_sat_level > DM365_ISIF_MAX_VDFSLV) {
+ pr_err("Invalid def_sat_level value\n");
+ return err;
+ }
+
+ if (!dfc->num_vdefects ||
+ dfc->num_vdefects > VPFE_ISIF_VDFC_TABLE_SIZE) {
+ pr_err("Invalid num_vdefects value\n");
+ return err;
+ }
+
+ for (i = 0; i < VPFE_ISIF_VDFC_TABLE_SIZE; i++) {
+ if (dfc->table[i].pos_vert > DM365_ISIF_MAX_DFCMEM0) {
+ pr_err("Invalid pos_vert value\n");
+ return err;
+ }
+ if (dfc->table[i].pos_horz > DM365_ISIF_MAX_DFCMEM1) {
+ pr_err("Invalid pos_horz value\n");
+ return err;
+ }
+ }
+
+ return 0;
+}
+
+#define DM365_ISIF_MAX_CLVRV 0xfff
+#define DM365_ISIF_MAX_CLDC 0x1fff
+#define DM365_ISIF_MAX_CLHSH 0x1fff
+#define DM365_ISIF_MAX_CLHSV 0x1fff
+#define DM365_ISIF_MAX_CLVSH 0x1fff
+#define DM365_ISIF_MAX_CLVSV 0x1fff
+#define DM365_ISIF_MAX_HEIGHT_BLACK_REGION 0x1fff
+
+static int isif_validate_bclamp_params(struct vpfe_isif_black_clamp *bclamp)
+{
+ int err = -EINVAL;
+
+ if (bclamp->dc_offset > DM365_ISIF_MAX_CLDC) {
+ pr_err("Invalid bclamp dc_offset value\n");
+ return err;
+ }
+ if (!bclamp->en)
+ return 0;
+ if (bclamp->horz.clamp_pix_limit > 1) {
+ pr_err("Invalid bclamp horz clamp_pix_limit value\n");
+ return err;
+ }
+ if (bclamp->horz.win_count_calc < 1 ||
+ bclamp->horz.win_count_calc > 32) {
+ pr_err("Invalid bclamp horz win_count_calc value\n");
+ return err;
+ }
+ if (bclamp->horz.win_start_h_calc > DM365_ISIF_MAX_CLHSH) {
+ pr_err("Invalid bclamp win_start_v_calc value\n");
+ return err;
+ }
+
+ if (bclamp->horz.win_start_v_calc > DM365_ISIF_MAX_CLHSV) {
+ pr_err("Invalid bclamp win_start_v_calc value\n");
+ return err;
+ }
+ if (bclamp->vert.reset_clamp_val > DM365_ISIF_MAX_CLVRV) {
+ pr_err("Invalid bclamp reset_clamp_val value\n");
+ return err;
+ }
+ if (bclamp->vert.ob_v_sz_calc > DM365_ISIF_MAX_HEIGHT_BLACK_REGION) {
+ pr_err("Invalid bclamp ob_v_sz_calc value\n");
+ return err;
+ }
+ if (bclamp->vert.ob_start_h > DM365_ISIF_MAX_CLVSH) {
+ pr_err("Invalid bclamp ob_start_h value\n");
+ return err;
+ }
+ if (bclamp->vert.ob_start_v > DM365_ISIF_MAX_CLVSV) {
+ pr_err("Invalid bclamp ob_start_h value\n");
+ return err;
+ }
+ return 0;
+}
+
+static int
+isif_validate_raw_params(struct vpfe_isif_raw_config *params)
+{
+ int ret;
+
+ ret = isif_validate_df_csc_params(&params->df_csc);
+ if (ret)
+ return ret;
+ ret = isif_validate_dfc_params(&params->dfc);
+ if (ret)
+ return ret;
+ ret = isif_validate_bclamp_params(&params->bclamp);
+ return ret;
+}
+
+static int isif_set_params(struct v4l2_subdev *sd, void *params)
+{
+ struct vpfe_isif_device *isif = v4l2_get_subdevdata(sd);
+ struct vpfe_isif_raw_config isif_raw_params;
+ int ret = -EINVAL;
+
+ /* only raw module parameters can be set through the IOCTL */
+ if (isif->formats[ISIF_PAD_SINK].code != V4L2_MBUS_FMT_SGRBG12_1X12)
+ return ret;
+
+ memcpy(&isif_raw_params, params, sizeof(isif_raw_params));
+ if (!isif_validate_raw_params(&isif_raw_params)) {
+ memcpy(&isif->isif_cfg.bayer.config_params, &isif_raw_params,
+ sizeof(isif_raw_params));
+ ret = 0;
+ }
+ return ret;
+}
+/*
+ * isif_ioctl() - isif module private ioctl's
+ * @sd: VPFE isif V4L2 subdevice
+ * @cmd: ioctl command
+ * @arg: ioctl argument
+ *
+ * Return 0 on success or a negative error code otherwise.
+ */
+static long isif_ioctl(struct v4l2_subdev *sd, unsigned int cmd, void *arg)
+{
+ int ret;
+
+ switch (cmd) {
+ case VIDIOC_VPFE_ISIF_S_RAW_PARAMS:
+ ret = isif_set_params(sd, arg);
+ break;
+
+ case VIDIOC_VPFE_ISIF_G_RAW_PARAMS:
+ ret = isif_get_params(sd, arg);
+ break;
+
+ default:
+ ret = -ENOIOCTLCMD;
+ }
+ return ret;
+}
+
+static void isif_config_gain_offset(struct vpfe_isif_device *isif)
+{
+ struct vpfe_isif_gain_offsets_adj *gain_off_ptr =
+ &isif->isif_cfg.bayer.config_params.gain_offset;
+ void *__iomem base = isif->isif_cfg.base_addr;
+ u32 val;
+
+ val = ((gain_off_ptr->gain_sdram_en & 1) << GAIN_SDRAM_EN_SHIFT) |
+ ((gain_off_ptr->gain_ipipe_en & 1) << GAIN_IPIPE_EN_SHIFT) |
+ ((gain_off_ptr->gain_h3a_en & 1) << GAIN_H3A_EN_SHIFT) |
+ ((gain_off_ptr->offset_sdram_en & 1) << OFST_SDRAM_EN_SHIFT) |
+ ((gain_off_ptr->offset_ipipe_en & 1) << OFST_IPIPE_EN_SHIFT) |
+ ((gain_off_ptr->offset_h3a_en & 1) << OFST_H3A_EN_SHIFT);
+ isif_merge(base, GAIN_OFFSET_EN_MASK, val, CGAMMAWD);
+
+ isif_write(base, isif->isif_cfg.isif_gain_params.cr_gain, CRGAIN);
+ isif_write(base, isif->isif_cfg.isif_gain_params.cgr_gain, CGRGAIN);
+ isif_write(base, isif->isif_cfg.isif_gain_params.cgb_gain, CGBGAIN);
+ isif_write(base, isif->isif_cfg.isif_gain_params.cb_gain, CBGAIN);
+ isif_write(base, isif->isif_cfg.isif_gain_params.offset & OFFSET_MASK,
+ COFSTA);
+
+}
+
+static void isif_config_bclamp(struct vpfe_isif_device *isif,
+ struct vpfe_isif_black_clamp *bc)
+{
+ u32 val;
+
+ /**
+ * DC Offset is always added to image data irrespective of bc enable
+ * status
+ */
+ val = bc->dc_offset & ISIF_BC_DCOFFSET_MASK;
+ isif_write(isif->isif_cfg.base_addr, val, CLDCOFST);
+
+ if (!bc->en)
+ return;
+
+ val = (bc->bc_mode_color & ISIF_BC_MODE_COLOR_MASK) <<
+ ISIF_BC_MODE_COLOR_SHIFT;
+
+ /* Enable BC and horizontal clamp calculation paramaters */
+ val = val | 1 | ((bc->horz.mode & ISIF_HORZ_BC_MODE_MASK) <<
+ ISIF_HORZ_BC_MODE_SHIFT);
+
+ isif_write(isif->isif_cfg.base_addr, val, CLAMPCFG);
+
+ if (bc->horz.mode != VPFE_ISIF_HORZ_BC_DISABLE) {
+ /*
+ * Window count for calculation
+ * Base window selection
+ * pixel limit
+ * Horizontal size of window
+ * vertical size of the window
+ * Horizontal start position of the window
+ * Vertical start position of the window
+ */
+ val = (bc->horz.win_count_calc & ISIF_HORZ_BC_WIN_COUNT_MASK) |
+ ((bc->horz.base_win_sel_calc & 1) <<
+ ISIF_HORZ_BC_WIN_SEL_SHIFT) |
+ ((bc->horz.clamp_pix_limit & 1) <<
+ ISIF_HORZ_BC_PIX_LIMIT_SHIFT) |
+ ((bc->horz.win_h_sz_calc &
+ ISIF_HORZ_BC_WIN_H_SIZE_MASK) <<
+ ISIF_HORZ_BC_WIN_H_SIZE_SHIFT) |
+ ((bc->horz.win_v_sz_calc &
+ ISIF_HORZ_BC_WIN_V_SIZE_MASK) <<
+ ISIF_HORZ_BC_WIN_V_SIZE_SHIFT);
+
+ isif_write(isif->isif_cfg.base_addr, val, CLHWIN0);
+
+ val = bc->horz.win_start_h_calc & ISIF_HORZ_BC_WIN_START_H_MASK;
+ isif_write(isif->isif_cfg.base_addr, val, CLHWIN1);
+
+ val = bc->horz.win_start_v_calc & ISIF_HORZ_BC_WIN_START_V_MASK;
+ isif_write(isif->isif_cfg.base_addr, val, CLHWIN2);
+ }
+
+ /* vertical clamp calculation paramaters */
+ /* OB H Valid */
+ val = bc->vert.ob_h_sz_calc & ISIF_VERT_BC_OB_H_SZ_MASK;
+
+ /* Reset clamp value sel for previous line */
+ val |= (bc->vert.reset_val_sel & ISIF_VERT_BC_RST_VAL_SEL_MASK) <<
+ ISIF_VERT_BC_RST_VAL_SEL_SHIFT;
+
+ /* Line average coefficient */
+ val |= bc->vert.line_ave_coef << ISIF_VERT_BC_LINE_AVE_COEF_SHIFT;
+ isif_write(isif->isif_cfg.base_addr, val, CLVWIN0);
+
+ /* Configured reset value */
+ if (bc->vert.reset_val_sel == VPFE_ISIF_VERT_BC_USE_CONFIG_CLAMP_VAL) {
+ val = bc->vert.reset_clamp_val & ISIF_VERT_BC_RST_VAL_MASK;
+ isif_write(isif->isif_cfg.base_addr, val, CLVRV);
+ }
+
+ /* Optical Black horizontal start position */
+ val = bc->vert.ob_start_h & ISIF_VERT_BC_OB_START_HORZ_MASK;
+ isif_write(isif->isif_cfg.base_addr, val, CLVWIN1);
+
+ /* Optical Black vertical start position */
+ val = bc->vert.ob_start_v & ISIF_VERT_BC_OB_START_VERT_MASK;
+ isif_write(isif->isif_cfg.base_addr, val, CLVWIN2);
+
+ val = bc->vert.ob_v_sz_calc & ISIF_VERT_BC_OB_VERT_SZ_MASK;
+ isif_write(isif->isif_cfg.base_addr, val, CLVWIN3);
+
+ /* Vertical start position for BC subtraction */
+ val = bc->vert_start_sub & ISIF_BC_VERT_START_SUB_V_MASK;
+ isif_write(isif->isif_cfg.base_addr, val, CLSV);
+}
+
+/* This function will configure the window size to be capture in ISIF reg */
+static void
+isif_setwin(struct vpfe_isif_device *isif, struct v4l2_rect *image_win,
+ enum isif_frmfmt frm_fmt, int ppc, int mode)
+{
+ int horz_nr_pixels;
+ int vert_nr_lines;
+ int horz_start;
+ int vert_start;
+ int mid_img;
+
+ /*
+ * ppc - per pixel count. indicates how many pixels per cell
+ * output to SDRAM. example, for ycbcr, it is one y and one c, so 2.
+ * raw capture this is 1
+ */
+ horz_start = image_win->left << (ppc - 1);
+ horz_nr_pixels = (image_win->width << (ppc - 1)) - 1;
+
+ /* Writing the horizontal info into the registers */
+ isif_write(isif->isif_cfg.base_addr,
+ horz_start & START_PX_HOR_MASK, SPH);
+ isif_write(isif->isif_cfg.base_addr,
+ horz_nr_pixels & NUM_PX_HOR_MASK, LNH);
+ vert_start = image_win->top;
+
+ if (frm_fmt == ISIF_FRMFMT_INTERLACED) {
+ vert_nr_lines = (image_win->height >> 1) - 1;
+ vert_start >>= 1;
+ /* To account for VD since line 0 doesn't have any data */
+ vert_start += 1;
+ } else {
+ /* To account for VD since line 0 doesn't have any data */
+ vert_start += 1;
+ vert_nr_lines = image_win->height - 1;
+ /* configure VDINT0 and VDINT1 */
+ mid_img = vert_start + (image_win->height / 2);
+ isif_write(isif->isif_cfg.base_addr, mid_img, VDINT1);
+ }
+
+ if (!mode)
+ isif_write(isif->isif_cfg.base_addr, 0, VDINT0);
+ else
+ isif_write(isif->isif_cfg.base_addr, vert_nr_lines, VDINT0);
+ isif_write(isif->isif_cfg.base_addr,
+ vert_start & START_VER_ONE_MASK, SLV0);
+ isif_write(isif->isif_cfg.base_addr,
+ vert_start & START_VER_TWO_MASK, SLV1);
+ isif_write(isif->isif_cfg.base_addr,
+ vert_nr_lines & NUM_LINES_VER, LNV);
+}
+
+#define DM365_ISIF_DFCMWR_MEMORY_WRITE 1
+#define DM365_ISIF_DFCMRD_MEMORY_READ 0x2
+
+static void
+isif_config_dfc(struct vpfe_isif_device *isif, struct vpfe_isif_dfc *vdfc)
+{
+#define DFC_WRITE_WAIT_COUNT 1000
+ u32 count = DFC_WRITE_WAIT_COUNT;
+ u32 val;
+ int i;
+
+ if (!vdfc->en)
+ return;
+
+ /* Correction mode */
+ val = (vdfc->corr_mode & ISIF_VDFC_CORR_MOD_MASK) <<
+ ISIF_VDFC_CORR_MOD_SHIFT;
+
+ /* Correct whole line or partial */
+ if (vdfc->corr_whole_line)
+ val |= 1 << ISIF_VDFC_CORR_WHOLE_LN_SHIFT;
+
+ /* level shift value */
+ val |= (vdfc->def_level_shift & ISIF_VDFC_LEVEL_SHFT_MASK) <<
+ ISIF_VDFC_LEVEL_SHFT_SHIFT;
+
+ isif_write(isif->isif_cfg.base_addr, val, DFCCTL);
+
+ /* Defect saturation level */
+ val = vdfc->def_sat_level & ISIF_VDFC_SAT_LEVEL_MASK;
+ isif_write(isif->isif_cfg.base_addr, val, VDFSATLV);
+
+ isif_write(isif->isif_cfg.base_addr, vdfc->table[0].pos_vert &
+ ISIF_VDFC_POS_MASK, DFCMEM0);
+ isif_write(isif->isif_cfg.base_addr, vdfc->table[0].pos_horz &
+ ISIF_VDFC_POS_MASK, DFCMEM1);
+ if (vdfc->corr_mode == VPFE_ISIF_VDFC_NORMAL ||
+ vdfc->corr_mode == VPFE_ISIF_VDFC_HORZ_INTERPOL_IF_SAT) {
+ isif_write(isif->isif_cfg.base_addr,
+ vdfc->table[0].level_at_pos, DFCMEM2);
+ isif_write(isif->isif_cfg.base_addr,
+ vdfc->table[0].level_up_pixels, DFCMEM3);
+ isif_write(isif->isif_cfg.base_addr,
+ vdfc->table[0].level_low_pixels, DFCMEM4);
+ }
+
+ val = isif_read(isif->isif_cfg.base_addr, DFCMEMCTL);
+ /* set DFCMARST and set DFCMWR */
+ val |= 1 << ISIF_DFCMEMCTL_DFCMARST_SHIFT;
+ val |= 1;
+ isif_write(isif->isif_cfg.base_addr, val, DFCMEMCTL);
+
+ while (count && (isif_read(isif->isif_cfg.base_addr, DFCMEMCTL) & 0x01))
+ count--;
+
+ val = isif_read(isif->isif_cfg.base_addr, DFCMEMCTL);
+ if (!count) {
+ pr_debug("defect table write timeout !!\n");
+ return;
+ }
+
+ for (i = 1; i < vdfc->num_vdefects; i++) {
+ isif_write(isif->isif_cfg.base_addr, vdfc->table[i].pos_vert &
+ ISIF_VDFC_POS_MASK, DFCMEM0);
+
+ isif_write(isif->isif_cfg.base_addr, vdfc->table[i].pos_horz &
+ ISIF_VDFC_POS_MASK, DFCMEM1);
+
+ if (vdfc->corr_mode == VPFE_ISIF_VDFC_NORMAL ||
+ vdfc->corr_mode == VPFE_ISIF_VDFC_HORZ_INTERPOL_IF_SAT) {
+ isif_write(isif->isif_cfg.base_addr,
+ vdfc->table[i].level_at_pos, DFCMEM2);
+ isif_write(isif->isif_cfg.base_addr,
+ vdfc->table[i].level_up_pixels, DFCMEM3);
+ isif_write(isif->isif_cfg.base_addr,
+ vdfc->table[i].level_low_pixels, DFCMEM4);
+ }
+ val = isif_read(isif->isif_cfg.base_addr, DFCMEMCTL);
+ /* clear DFCMARST and set DFCMWR */
+ val &= ~(1 << ISIF_DFCMEMCTL_DFCMARST_SHIFT);
+ val |= 1;
+ isif_write(isif->isif_cfg.base_addr, val, DFCMEMCTL);
+
+ count = DFC_WRITE_WAIT_COUNT;
+ while (count && (isif_read(isif->isif_cfg.base_addr,
+ DFCMEMCTL) & 0x01))
+ count--;
+
+ val = isif_read(isif->isif_cfg.base_addr, DFCMEMCTL);
+ if (!count) {
+ pr_debug("defect table write timeout !!\n");
+ return;
+ }
+ }
+ if (vdfc->num_vdefects < VPFE_ISIF_VDFC_TABLE_SIZE) {
+ /* Extra cycle needed */
+ isif_write(isif->isif_cfg.base_addr, 0, DFCMEM0);
+ isif_write(isif->isif_cfg.base_addr,
+ DM365_ISIF_MAX_DFCMEM1, DFCMEM1);
+ isif_write(isif->isif_cfg.base_addr,
+ DM365_ISIF_DFCMWR_MEMORY_WRITE, DFCMEMCTL);
+ }
+ /* enable VDFC */
+ isif_merge(isif->isif_cfg.base_addr, (1 << ISIF_VDFC_EN_SHIFT),
+ (1 << ISIF_VDFC_EN_SHIFT), DFCCTL);
+
+ isif_merge(isif->isif_cfg.base_addr, (1 << ISIF_VDFC_EN_SHIFT),
+ (0 << ISIF_VDFC_EN_SHIFT), DFCCTL);
+
+ isif_write(isif->isif_cfg.base_addr, 0x6, DFCMEMCTL);
+ for (i = 0; i < vdfc->num_vdefects; i++) {
+ count = DFC_WRITE_WAIT_COUNT;
+ while (count &&
+ (isif_read(isif->isif_cfg.base_addr, DFCMEMCTL) & 0x2))
+ count--;
+ val = isif_read(isif->isif_cfg.base_addr, DFCMEMCTL);
+ if (!count) {
+ pr_debug("defect table write timeout !!\n");
+ return;
+ }
+ isif_write(isif->isif_cfg.base_addr,
+ DM365_ISIF_DFCMRD_MEMORY_READ, DFCMEMCTL);
+ }
+}
+
+static void
+isif_config_csc(struct vpfe_isif_device *isif, struct vpfe_isif_df_csc *df_csc)
+{
+ u32 val1;
+ u32 val2;
+ u32 i;
+
+ if (!df_csc->csc.en) {
+ isif_write(isif->isif_cfg.base_addr, 0, CSCCTL);
+ return;
+ }
+ /* initialize all bits to 0 */
+ val1 = 0;
+ for (i = 0; i < VPFE_ISIF_CSC_NUM_COEFF; i++) {
+ if ((i % 2) == 0) {
+ /* CSCM - LSB */
+ val1 = ((df_csc->csc.coeff[i].integer &
+ ISIF_CSC_COEF_INTEG_MASK) <<
+ ISIF_CSC_COEF_INTEG_SHIFT) |
+ ((df_csc->csc.coeff[i].decimal &
+ ISIF_CSC_COEF_DECIMAL_MASK));
+ } else {
+
+ /* CSCM - MSB */
+ val2 = ((df_csc->csc.coeff[i].integer &
+ ISIF_CSC_COEF_INTEG_MASK) <<
+ ISIF_CSC_COEF_INTEG_SHIFT) |
+ ((df_csc->csc.coeff[i].decimal &
+ ISIF_CSC_COEF_DECIMAL_MASK));
+ val2 <<= ISIF_CSCM_MSB_SHIFT;
+ val2 |= val1;
+ isif_write(isif->isif_cfg.base_addr, val2,
+ (CSCM0 + ((i-1) << 1)));
+ }
+ }
+ /* program the active area */
+ isif_write(isif->isif_cfg.base_addr, df_csc->start_pix &
+ ISIF_DF_CSC_SPH_MASK, FMTSPH);
+ /*
+ * one extra pixel as required for CSC. Actually number of
+ * pixel - 1 should be configured in this register. So we
+ * need to subtract 1 before writing to FMTSPH, but we will
+ * not do this since csc requires one extra pixel
+ */
+ isif_write(isif->isif_cfg.base_addr, df_csc->num_pixels &
+ ISIF_DF_CSC_SPH_MASK, FMTLNH);
+ isif_write(isif->isif_cfg.base_addr, df_csc->start_line &
+ ISIF_DF_CSC_SPH_MASK, FMTSLV);
+ /*
+ * one extra line as required for CSC. See reason documented for
+ * num_pixels
+ */
+ isif_write(isif->isif_cfg.base_addr, df_csc->num_lines &
+ ISIF_DF_CSC_SPH_MASK, FMTLNV);
+ /* Enable CSC */
+ isif_write(isif->isif_cfg.base_addr, 1, CSCCTL);
+}
+
+static void
+isif_config_linearization(struct vpfe_isif_device *isif,
+ struct vpfe_isif_linearize *linearize)
+{
+ u32 val;
+ u32 i;
+
+ if (!linearize->en) {
+ isif_write(isif->isif_cfg.base_addr, 0, LINCFG0);
+ return;
+ }
+ /* shift value for correction */
+ val = (linearize->corr_shft & ISIF_LIN_CORRSFT_MASK) <<
+ ISIF_LIN_CORRSFT_SHIFT;
+ /* enable */
+ val |= 1;
+ isif_write(isif->isif_cfg.base_addr, val, LINCFG0);
+ /* Scale factor */
+ val = (linearize->scale_fact.integer & 1) <<
+ ISIF_LIN_SCALE_FACT_INTEG_SHIFT;
+ val |= linearize->scale_fact.decimal & ISIF_LIN_SCALE_FACT_DECIMAL_MASK;
+ isif_write(isif->isif_cfg.base_addr, val, LINCFG1);
+
+ for (i = 0; i < VPFE_ISIF_LINEAR_TAB_SIZE; i++) {
+ val = linearize->table[i] & ISIF_LIN_ENTRY_MASK;
+ if (i%2)
+ isif_regw_lin_tbl(isif, val, ((i >> 1) << 2), 1);
+ else
+ isif_regw_lin_tbl(isif, val, ((i >> 1) << 2), 0);
+ }
+}
+
+static void
+isif_config_culling(struct vpfe_isif_device *isif, struct vpfe_isif_cul *cul)
+{
+ u32 val;
+
+ /* Horizontal pattern */
+ val = cul->hcpat_even << CULL_PAT_EVEN_LINE_SHIFT;
+ val |= cul->hcpat_odd;
+ isif_write(isif->isif_cfg.base_addr, val, CULH);
+ /* vertical pattern */
+ isif_write(isif->isif_cfg.base_addr, cul->vcpat, CULV);
+ /* LPF */
+ isif_merge(isif->isif_cfg.base_addr, ISIF_LPF_MASK << ISIF_LPF_SHIFT,
+ cul->en_lpf << ISIF_LPF_SHIFT, MODESET);
+}
+
+static int isif_get_pix_fmt(u32 mbus_code)
+{
+ switch (mbus_code) {
+ case V4L2_MBUS_FMT_SGRBG10_ALAW8_1X8:
+ case V4L2_MBUS_FMT_SGRBG10_DPCM8_1X8:
+ case V4L2_MBUS_FMT_SGRBG12_1X12:
+ return ISIF_PIXFMT_RAW;
+
+ case V4L2_MBUS_FMT_YUYV8_2X8:
+ case V4L2_MBUS_FMT_UYVY8_2X8:
+ case V4L2_MBUS_FMT_YUYV10_2X10:
+ case V4L2_MBUS_FMT_Y8_1X8:
+ return ISIF_PIXFMT_YCBCR_8BIT;
+
+ case V4L2_MBUS_FMT_YUYV8_1X16:
+ case V4L2_MBUS_FMT_YUYV10_1X20:
+ return ISIF_PIXFMT_YCBCR_16BIT;
+
+ default:
+ break;
+ }
+ return -EINVAL;
+}
+
+#define ISIF_INTERLACE_INVERSE_MODE 0x4b6d
+#define ISIF_INTERLACE_NON_INVERSE_MODE 0x0b6d
+#define ISIF_PROGRESSIVE_INVERSE_MODE 0x4000
+#define ISIF_PROGRESSIVE_NON_INVERSE_MODE 0x0000
+
+static int isif_config_raw(struct v4l2_subdev *sd, int mode)
+{
+ struct vpfe_isif_device *isif = v4l2_get_subdevdata(sd);
+ struct isif_params_raw *params = &isif->isif_cfg.bayer;
+ struct vpfe_isif_raw_config *module_params =
+ &isif->isif_cfg.bayer.config_params;
+ struct v4l2_mbus_framefmt *format;
+ int pix_fmt;
+ u32 val;
+
+ format = &isif->formats[ISIF_PAD_SINK];
+
+ /* In case of user has set BT656IF earlier, it should be reset
+ * when configuring for raw input.
+ */
+ isif_write(isif->isif_cfg.base_addr, 0, REC656IF);
+ /* Configure CCDCFG register
+ * Set CCD Not to swap input since input is RAW data
+ * Set FID detection function to Latch at V-Sync
+ * Set WENLOG - isif valid area
+ * Set TRGSEL
+ * Set EXTRG
+ * Packed to 8 or 16 bits
+ */
+ val = ISIF_YCINSWP_RAW | ISIF_CCDCFG_FIDMD_LATCH_VSYNC |
+ ISIF_CCDCFG_WENLOG_AND | ISIF_CCDCFG_TRGSEL_WEN |
+ ISIF_CCDCFG_EXTRG_DISABLE | (isif->isif_cfg.data_pack &
+ ISIF_DATA_PACK_MASK);
+ isif_write(isif->isif_cfg.base_addr, val, CCDCFG);
+
+ pix_fmt = isif_get_pix_fmt(format->code);
+ if (pix_fmt < 0) {
+ pr_debug("Invalid pix_fmt(input mode)\n");
+ return -EINVAL;
+ }
+ /*
+ * Configure the vertical sync polarity(MODESET.VDPOL)
+ * Configure the horizontal sync polarity (MODESET.HDPOL)
+ * Configure frame id polarity (MODESET.FLDPOL)
+ * Configure data polarity
+ * Configure External WEN Selection
+ * Configure frame format(progressive or interlace)
+ * Configure pixel format (Input mode)
+ * Configure the data shift
+ */
+ val = ISIF_VDHDOUT_INPUT | ((params->vd_pol & ISIF_VD_POL_MASK) <<
+ ISIF_VD_POL_SHIFT) | ((params->hd_pol & ISIF_HD_POL_MASK) <<
+ ISIF_HD_POL_SHIFT) | ((params->fid_pol & ISIF_FID_POL_MASK) <<
+ ISIF_FID_POL_SHIFT) | ((ISIF_DATAPOL_NORMAL &
+ ISIF_DATAPOL_MASK) << ISIF_DATAPOL_SHIFT) | ((ISIF_EXWEN_DISABLE &
+ ISIF_EXWEN_MASK) << ISIF_EXWEN_SHIFT) | ((params->frm_fmt &
+ ISIF_FRM_FMT_MASK) << ISIF_FRM_FMT_SHIFT) | ((pix_fmt &
+ ISIF_INPUT_MASK) << ISIF_INPUT_SHIFT);
+
+ /* currently only V4L2_MBUS_FMT_SGRBG12_1X12 is
+ * supported. shift appropriately depending on
+ * different MBUS fmt's added
+ */
+ if (format->code == V4L2_MBUS_FMT_SGRBG12_1X12)
+ val |= ((VPFE_ISIF_NO_SHIFT &
+ ISIF_DATASFT_MASK) << ISIF_DATASFT_SHIFT);
+
+ isif_write(isif->isif_cfg.base_addr, val, MODESET);
+ /*
+ * Configure GAMMAWD register
+ * CFA pattern setting
+ */
+ val = (params->cfa_pat & ISIF_GAMMAWD_CFA_MASK) <<
+ ISIF_GAMMAWD_CFA_SHIFT;
+ /* Gamma msb */
+ if (params->v4l2_pix_fmt == V4L2_PIX_FMT_SGRBG10ALAW8)
+ val = val | ISIF_ALAW_ENABLE;
+
+ val = val | ((params->data_msb & ISIF_ALAW_GAMA_WD_MASK) <<
+ ISIF_ALAW_GAMA_WD_SHIFT);
+
+ isif_write(isif->isif_cfg.base_addr, val, CGAMMAWD);
+ /* Configure DPCM compression settings */
+ if (params->v4l2_pix_fmt == V4L2_PIX_FMT_SGRBG10DPCM8) {
+ val = 1 << ISIF_DPCM_EN_SHIFT;
+ val |= (params->dpcm_predictor &
+ ISIF_DPCM_PREDICTOR_MASK) << ISIF_DPCM_PREDICTOR_SHIFT;
+ }
+ isif_write(isif->isif_cfg.base_addr, val, MISC);
+ /* Configure Gain & Offset */
+ isif_config_gain_offset(isif);
+ /* Configure Color pattern */
+ if (format->code == V4L2_MBUS_FMT_SGRBG12_1X12)
+ val = isif_sgrbg_pattern;
+ else
+ /* default set to rggb */
+ val = isif_srggb_pattern;
+
+ isif_write(isif->isif_cfg.base_addr, val, CCOLP);
+
+ /* Configure HSIZE register */
+ val = (params->horz_flip_en & ISIF_HSIZE_FLIP_MASK) <<
+ ISIF_HSIZE_FLIP_SHIFT;
+
+ /* calculate line offset in 32 bytes based on pack value */
+ if (isif->isif_cfg.data_pack == ISIF_PACK_8BIT)
+ val |= ((params->win.width + 31) >> 5) & ISIF_LINEOFST_MASK;
+ else if (isif->isif_cfg.data_pack == ISIF_PACK_12BIT)
+ val |= ((((params->win.width + (params->win.width >> 2)) +
+ 31) >> 5) & ISIF_LINEOFST_MASK);
+ else
+ val |= (((params->win.width * 2) + 31) >> 5) &
+ ISIF_LINEOFST_MASK;
+ isif_write(isif->isif_cfg.base_addr, val, HSIZE);
+ /* Configure SDOFST register */
+ if (params->frm_fmt == ISIF_FRMFMT_INTERLACED) {
+ if (params->image_invert_en)
+ /* For interlace inverse mode */
+ isif_write(isif->isif_cfg.base_addr,
+ ISIF_INTERLACE_INVERSE_MODE, SDOFST);
+ else
+ /* For interlace non inverse mode */
+ isif_write(isif->isif_cfg.base_addr,
+ ISIF_INTERLACE_NON_INVERSE_MODE, SDOFST);
+ } else if (params->frm_fmt == ISIF_FRMFMT_PROGRESSIVE) {
+ if (params->image_invert_en)
+ isif_write(isif->isif_cfg.base_addr,
+ ISIF_PROGRESSIVE_INVERSE_MODE, SDOFST);
+ else
+ /* For progessive non inverse mode */
+ isif_write(isif->isif_cfg.base_addr,
+ ISIF_PROGRESSIVE_NON_INVERSE_MODE, SDOFST);
+ }
+ /* Configure video window */
+ isif_setwin(isif, &params->win, params->frm_fmt, 1, mode);
+ /* Configure Black Clamp */
+ isif_config_bclamp(isif, &module_params->bclamp);
+ /* Configure Vertical Defection Pixel Correction */
+ isif_config_dfc(isif, &module_params->dfc);
+ if (!module_params->df_csc.df_or_csc)
+ /* Configure Color Space Conversion */
+ isif_config_csc(isif, &module_params->df_csc);
+
+ isif_config_linearization(isif, &module_params->linearize);
+ /* Configure Culling */
+ isif_config_culling(isif, &module_params->culling);
+ /* Configure Horizontal and vertical offsets(DFC,LSC,Gain) */
+ val = module_params->horz_offset & ISIF_DATA_H_OFFSET_MASK;
+ isif_write(isif->isif_cfg.base_addr, val, DATAHOFST);
+
+ val = module_params->vert_offset & ISIF_DATA_V_OFFSET_MASK;
+ isif_write(isif->isif_cfg.base_addr, val, DATAVOFST);
+
+ return 0;
+}
+
+#define DM365_ISIF_HSIZE_MASK 0xffffffe0
+#define DM365_ISIF_SDOFST_2_LINES 0x00000249
+
+/* This function will configure ISIF for YCbCr parameters. */
+static int isif_config_ycbcr(struct v4l2_subdev *sd, int mode)
+{
+ struct vpfe_isif_device *isif = v4l2_get_subdevdata(sd);
+ struct isif_ycbcr_config *params = &isif->isif_cfg.ycbcr;
+ struct v4l2_mbus_framefmt *format;
+ int pix_fmt;
+ u32 modeset;
+ u32 ccdcfg;
+
+ format = &isif->formats[ISIF_PAD_SINK];
+ /*
+ * first reset the ISIF
+ * all registers have default values after reset
+ * This is important since we assume default values to be set in
+ * a lot of registers that we didn't touch
+ */
+ /* start with all bits zero */
+ ccdcfg = modeset = 0;
+ pix_fmt = isif_get_pix_fmt(format->code);
+ if (pix_fmt < 0) {
+ pr_debug("Invalid pix_fmt(input mode)\n");
+ return -EINVAL;
+ }
+ /* configure pixel format or input mode */
+ modeset = modeset | ((pix_fmt & ISIF_INPUT_MASK) <<
+ ISIF_INPUT_SHIFT) | ((params->frm_fmt & ISIF_FRM_FMT_MASK) <<
+ ISIF_FRM_FMT_SHIFT) | (((params->fid_pol &
+ ISIF_FID_POL_MASK) << ISIF_FID_POL_SHIFT)) |
+ (((params->hd_pol & ISIF_HD_POL_MASK) << ISIF_HD_POL_SHIFT)) |
+ (((params->vd_pol & ISIF_VD_POL_MASK) << ISIF_VD_POL_SHIFT));
+ /* pack the data to 8-bit CCDCCFG */
+ switch (format->code) {
+ case V4L2_MBUS_FMT_YUYV8_2X8:
+ case V4L2_MBUS_FMT_UYVY8_2X8:
+ if (pix_fmt != ISIF_PIXFMT_YCBCR_8BIT) {
+ pr_debug("Invalid pix_fmt(input mode)\n");
+ return -EINVAL;
+ }
+ modeset |= ((VPFE_PINPOL_NEGATIVE & ISIF_VD_POL_MASK) <<
+ ISIF_VD_POL_SHIFT);
+ isif_write(isif->isif_cfg.base_addr, 3, REC656IF);
+ ccdcfg = ccdcfg | ISIF_PACK_8BIT | ISIF_YCINSWP_YCBCR;
+ break;
+
+ case V4L2_MBUS_FMT_YUYV10_2X10:
+ if (pix_fmt != ISIF_PIXFMT_YCBCR_8BIT) {
+ pr_debug("Invalid pix_fmt(input mode)\n");
+ return -EINVAL;
+ }
+ /* setup BT.656, embedded sync */
+ isif_write(isif->isif_cfg.base_addr, 3, REC656IF);
+ /* enable 10 bit mode in ccdcfg */
+ ccdcfg = ccdcfg | ISIF_PACK_8BIT | ISIF_YCINSWP_YCBCR |
+ ISIF_BW656_ENABLE;
+ break;
+
+ case V4L2_MBUS_FMT_YUYV10_1X20:
+ if (pix_fmt != ISIF_PIXFMT_YCBCR_16BIT) {
+ pr_debug("Invalid pix_fmt(input mode)\n");
+ return -EINVAL;
+ }
+ isif_write(isif->isif_cfg.base_addr, 3, REC656IF);
+ break;
+
+ case V4L2_MBUS_FMT_Y8_1X8:
+ ccdcfg |= ISIF_PACK_8BIT;
+ ccdcfg |= ISIF_YCINSWP_YCBCR;
+ if (pix_fmt != ISIF_PIXFMT_YCBCR_8BIT) {
+ pr_debug("Invalid pix_fmt(input mode)\n");
+ return -EINVAL;
+ }
+ break;
+
+ case V4L2_MBUS_FMT_YUYV8_1X16:
+ if (pix_fmt != ISIF_PIXFMT_YCBCR_16BIT) {
+ pr_debug("Invalid pix_fmt(input mode)\n");
+ return -EINVAL;
+ }
+ break;
+
+ default:
+ /* should never come here */
+ pr_debug("Invalid interface type\n");
+ return -EINVAL;
+ }
+ isif_write(isif->isif_cfg.base_addr, modeset, MODESET);
+ /* Set up pix order */
+ ccdcfg |= (params->pix_order & ISIF_PIX_ORDER_MASK) <<
+ ISIF_PIX_ORDER_SHIFT;
+ isif_write(isif->isif_cfg.base_addr, ccdcfg, CCDCFG);
+ /* configure video window */
+ if (format->code == V4L2_MBUS_FMT_YUYV10_1X20 ||
+ format->code == V4L2_MBUS_FMT_YUYV8_1X16)
+ isif_setwin(isif, &params->win, params->frm_fmt, 1, mode);
+ else
+ isif_setwin(isif, &params->win, params->frm_fmt, 2, mode);
+
+ /*
+ * configure the horizontal line offset
+ * this is done by rounding up width to a multiple of 16 pixels
+ * and multiply by two to account for y:cb:cr 4:2:2 data
+ */
+ isif_write(isif->isif_cfg.base_addr,
+ ((((params->win.width * 2) + 31) &
+ DM365_ISIF_HSIZE_MASK) >> 5), HSIZE);
+
+ /* configure the memory line offset */
+ if (params->frm_fmt == ISIF_FRMFMT_INTERLACED &&
+ params->buf_type == ISIF_BUFTYPE_FLD_INTERLEAVED)
+ /* two fields are interleaved in memory */
+ isif_write(isif->isif_cfg.base_addr,
+ DM365_ISIF_SDOFST_2_LINES, SDOFST);
+ return 0;
+}
+
+static int isif_configure(struct v4l2_subdev *sd, int mode)
+{
+ struct vpfe_isif_device *isif = v4l2_get_subdevdata(sd);
+ struct v4l2_mbus_framefmt *format;
+
+ format = &isif->formats[ISIF_PAD_SINK];
+
+ switch (format->code) {
+ case V4L2_MBUS_FMT_SGRBG10_ALAW8_1X8:
+ case V4L2_MBUS_FMT_SGRBG10_DPCM8_1X8:
+ case V4L2_MBUS_FMT_SGRBG12_1X12:
+ return isif_config_raw(sd, mode);
+
+ case V4L2_MBUS_FMT_YUYV8_2X8:
+ case V4L2_MBUS_FMT_UYVY8_2X8:
+ case V4L2_MBUS_FMT_YUYV10_2X10:
+ case V4L2_MBUS_FMT_Y8_1X8:
+ case V4L2_MBUS_FMT_YUYV8_1X16:
+ case V4L2_MBUS_FMT_YUYV10_1X20:
+ return isif_config_ycbcr(sd, mode);
+
+ default:
+ break;
+ }
+ return -EINVAL;
+}
+
+/*
+ * isif_set_stream() - Enable/Disable streaming on the ISIF module
+ * @sd: VPFE ISIF V4L2 subdevice
+ * @enable: Enable/disable stream
+ */
+static int isif_set_stream(struct v4l2_subdev *sd, int enable)
+{
+ struct vpfe_isif_device *isif = v4l2_get_subdevdata(sd);
+ int ret;
+
+ if (enable) {
+ ret = isif_configure(sd,
+ (isif->output == ISIF_OUTPUT_MEMORY) ? 0 : 1);
+ if (ret)
+ return ret;
+ if (isif->output == ISIF_OUTPUT_MEMORY)
+ isif_enable_output_to_sdram(isif, 1);
+ isif_enable(isif, 1);
+ } else {
+ isif_enable(isif, 0);
+ isif_enable_output_to_sdram(isif, 0);
+ }
+
+ return 0;
+}
+
+/*
+ * __isif_get_format() - helper function for getting isif format
+ * @isif: pointer to isif private structure.
+ * @pad: pad number.
+ * @fh: V4L2 subdev file handle.
+ * @which: wanted subdev format.
+ */
+static struct v4l2_mbus_framefmt *
+__isif_get_format(struct vpfe_isif_device *isif, struct v4l2_subdev_fh *fh,
+ unsigned int pad, enum v4l2_subdev_format_whence which)
+{
+ if (which == V4L2_SUBDEV_FORMAT_TRY) {
+ struct v4l2_subdev_format fmt;
+
+ fmt.pad = pad;
+ fmt.which = which;
+
+ return v4l2_subdev_get_try_format(fh, pad);
+ }
+ return &isif->formats[pad];
+}
+
+/*
+* isif_set_format() - set format on pad
+* @sd : VPFE ISIF device
+* @fh : V4L2 subdev file handle
+* @fmt : pointer to v4l2 subdev format structure
+*
+* Return 0 on success or -EINVAL if format or pad is invalid
+*/
+static int
+isif_set_format(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh,
+ struct v4l2_subdev_format *fmt)
+{
+ struct vpfe_isif_device *isif = v4l2_get_subdevdata(sd);
+ struct vpfe_device *vpfe_dev = to_vpfe_device(isif);
+ struct v4l2_mbus_framefmt *format;
+
+ format = __isif_get_format(isif, fh, fmt->pad, fmt->which);
+ if (format == NULL)
+ return -EINVAL;
+
+ isif_try_format(isif, fh, fmt);
+ memcpy(format, &fmt->format, sizeof(*format));
+
+ if (fmt->which == V4L2_SUBDEV_FORMAT_TRY)
+ return 0;
+
+ if (fmt->pad == ISIF_PAD_SOURCE)
+ return isif_config_format(vpfe_dev, fmt->pad);
+
+ return 0;
+}
+
+/*
+ * isif_get_format() - Retrieve the video format on a pad
+ * @sd: VPFE ISIF V4L2 subdevice
+ * @fh: V4L2 subdev file handle
+ * @fmt: pointer to v4l2 subdev format structure
+ *
+ * Return 0 on success or -EINVAL if the pad is invalid or doesn't correspond
+ * to the format type.
+ */
+static int
+isif_get_format(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh,
+ struct v4l2_subdev_format *fmt)
+{
+ struct vpfe_isif_device *vpfe_isif = v4l2_get_subdevdata(sd);
+ struct v4l2_mbus_framefmt *format;
+
+ format = __isif_get_format(vpfe_isif, fh, fmt->pad, fmt->which);
+ if (format == NULL)
+ return -EINVAL;
+
+ memcpy(&fmt->format, format, sizeof(fmt->format));
+
+ return 0;
+}
+
+/*
+ * isif_enum_frame_size() - enum frame sizes on pads
+ * @sd: VPFE isif V4L2 subdevice
+ * @fh: V4L2 subdev file handle
+ * @code: pointer to v4l2_subdev_frame_size_enum structure
+ */
+static int
+isif_enum_frame_size(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh,
+ struct v4l2_subdev_frame_size_enum *fse)
+{
+ struct vpfe_isif_device *isif = v4l2_get_subdevdata(sd);
+ struct v4l2_subdev_format format;
+
+ if (fse->index != 0)
+ return -EINVAL;
+
+ format.pad = fse->pad;
+ format.format.code = fse->code;
+ format.format.width = 1;
+ format.format.height = 1;
+ format.which = V4L2_SUBDEV_FORMAT_TRY;
+ isif_try_format(isif, fh, &format);
+ fse->min_width = format.format.width;
+ fse->min_height = format.format.height;
+
+ if (format.format.code != fse->code)
+ return -EINVAL;
+
+ format.pad = fse->pad;
+ format.format.code = fse->code;
+ format.format.width = -1;
+ format.format.height = -1;
+ format.which = V4L2_SUBDEV_FORMAT_TRY;
+ isif_try_format(isif, fh, &format);
+ fse->max_width = format.format.width;
+ fse->max_height = format.format.height;
+
+ return 0;
+}
+
+/*
+ * isif_enum_mbus_code() - enum mbus codes for pads
+ * @sd: VPFE isif V4L2 subdevice
+ * @fh: V4L2 subdev file handle
+ * @code: pointer to v4l2_subdev_mbus_code_enum structure
+ */
+static int
+isif_enum_mbus_code(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh,
+ struct v4l2_subdev_mbus_code_enum *code)
+{
+ switch (code->pad) {
+ case ISIF_PAD_SINK:
+ case ISIF_PAD_SOURCE:
+ if (code->index >= ARRAY_SIZE(isif_fmts))
+ return -EINVAL;
+ code->code = isif_fmts[code->index];
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/*
+ * isif_pad_set_crop() - set crop rectangle on pad
+ * @sd: VPFE isif V4L2 subdevice
+ * @fh: V4L2 subdev file handle
+ * @code: pointer to v4l2_subdev_mbus_code_enum structure
+ *
+ * Return 0 on success, -EINVAL if pad is invalid
+ */
+static int
+isif_pad_set_crop(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh,
+ struct v4l2_subdev_crop *crop)
+{
+ struct vpfe_isif_device *vpfe_isif = v4l2_get_subdevdata(sd);
+ struct v4l2_mbus_framefmt *format;
+
+ /* check wether its a valid pad */
+ if (crop->pad != ISIF_PAD_SINK)
+ return -EINVAL;
+
+ format = __isif_get_format(vpfe_isif, fh, crop->pad, crop->which);
+ if (format == NULL)
+ return -EINVAL;
+
+ /* check wether crop rect is within limits */
+ if (crop->rect.top < 0 || crop->rect.left < 0 ||
+ (crop->rect.left + crop->rect.width >
+ vpfe_isif->formats[ISIF_PAD_SINK].width) ||
+ (crop->rect.top + crop->rect.height >
+ vpfe_isif->formats[ISIF_PAD_SINK].height)) {
+ crop->rect.left = 0;
+ crop->rect.top = 0;
+ crop->rect.width = format->width;
+ crop->rect.height = format->height;
+ }
+ /* adjust the width to 16 pixel boundary */
+ crop->rect.width = ((crop->rect.width + 15) & ~0xf);
+ vpfe_isif->crop = crop->rect;
+ if (crop->which == V4L2_SUBDEV_FORMAT_ACTIVE) {
+ isif_set_image_window(vpfe_isif);
+ } else {
+ struct v4l2_rect *rect;
+
+ rect = v4l2_subdev_get_try_crop(fh, ISIF_PAD_SINK);
+ memcpy(rect, &vpfe_isif->crop, sizeof(*rect));
+ }
+ return 0;
+}
+
+/*
+ * isif_pad_get_crop() - get crop rectangle on pad
+ * @sd: VPFE isif V4L2 subdevice
+ * @fh: V4L2 subdev file handle
+ * @code: pointer to v4l2_subdev_mbus_code_enum structure
+ *
+ * Return 0 on success, -EINVAL if pad is invalid
+ */
+static int
+isif_pad_get_crop(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh,
+ struct v4l2_subdev_crop *crop)
+{
+ struct vpfe_isif_device *vpfe_isif = v4l2_get_subdevdata(sd);
+
+ /* check wether its a valid pad */
+ if (crop->pad != ISIF_PAD_SINK)
+ return -EINVAL;
+
+ if (crop->which == V4L2_SUBDEV_FORMAT_TRY) {
+ struct v4l2_rect *rect;
+ rect = v4l2_subdev_get_try_crop(fh, ISIF_PAD_SINK);
+ memcpy(&crop->rect, rect, sizeof(*rect));
+ } else {
+ crop->rect = vpfe_isif->crop;
+ }
+
+ return 0;
+}
+
+/*
+ * isif_init_formats() - Initialize formats on all pads
+ * @sd: VPFE isif V4L2 subdevice
+ * @fh: V4L2 subdev file handle
+ *
+ * Initialize all pad formats with default values. If fh is not NULL, try
+ * formats are initialized on the file handle. Otherwise active formats are
+ * initialized on the device.
+ */
+static int
+isif_init_formats(struct v4l2_subdev *sd,
+ struct v4l2_subdev_fh *fh)
+{
+ struct v4l2_subdev_format format;
+ struct v4l2_subdev_crop crop;
+
+ memset(&format, 0, sizeof(format));
+ format.pad = ISIF_PAD_SINK;
+ format.which = fh ? V4L2_SUBDEV_FORMAT_TRY : V4L2_SUBDEV_FORMAT_ACTIVE;
+ format.format.code = V4L2_MBUS_FMT_SGRBG12_1X12;
+ format.format.width = MAX_WIDTH;
+ format.format.height = MAX_HEIGHT;
+ isif_set_format(sd, fh, &format);
+
+ memset(&format, 0, sizeof(format));
+ format.pad = ISIF_PAD_SOURCE;
+ format.which = fh ? V4L2_SUBDEV_FORMAT_TRY : V4L2_SUBDEV_FORMAT_ACTIVE;
+ format.format.code = V4L2_MBUS_FMT_SGRBG12_1X12;
+ format.format.width = MAX_WIDTH;
+ format.format.height = MAX_HEIGHT;
+ isif_set_format(sd, fh, &format);
+
+ memset(&crop, 0, sizeof(crop));
+ crop.pad = ISIF_PAD_SINK;
+ crop.which = fh ? V4L2_SUBDEV_FORMAT_TRY : V4L2_SUBDEV_FORMAT_ACTIVE;
+ crop.rect.width = MAX_WIDTH;
+ crop.rect.height = MAX_HEIGHT;
+ isif_pad_set_crop(sd, fh, &crop);
+
+ return 0;
+}
+
+/* subdev core operations */
+static const struct v4l2_subdev_core_ops isif_v4l2_core_ops = {
+ .ioctl = isif_ioctl,
+};
+
+/* subdev file operations */
+static const struct v4l2_subdev_internal_ops isif_v4l2_internal_ops = {
+ .open = isif_init_formats,
+};
+
+/* subdev video operations */
+static const struct v4l2_subdev_video_ops isif_v4l2_video_ops = {
+ .s_stream = isif_set_stream,
+};
+
+/* subdev pad operations */
+static const struct v4l2_subdev_pad_ops isif_v4l2_pad_ops = {
+ .enum_mbus_code = isif_enum_mbus_code,
+ .enum_frame_size = isif_enum_frame_size,
+ .get_fmt = isif_get_format,
+ .set_fmt = isif_set_format,
+ .set_crop = isif_pad_set_crop,
+ .get_crop = isif_pad_get_crop,
+};
+
+/* subdev operations */
+static const struct v4l2_subdev_ops isif_v4l2_ops = {
+ .core = &isif_v4l2_core_ops,
+ .video = &isif_v4l2_video_ops,
+ .pad = &isif_v4l2_pad_ops,
+};
+
+/*
+ * Media entity operations
+ */
+
+/*
+ * isif_link_setup() - Setup isif connections
+ * @entity: isif media entity
+ * @local: Pad at the local end of the link
+ * @remote: Pad at the remote end of the link
+ * @flags: Link flags
+ *
+ * return -EINVAL or zero on success
+ */
+static int
+isif_link_setup(struct media_entity *entity, const struct media_pad *local,
+ const struct media_pad *remote, u32 flags)
+{
+ struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(entity);
+ struct vpfe_isif_device *isif = v4l2_get_subdevdata(sd);
+
+ switch (local->index | media_entity_type(remote->entity)) {
+ case ISIF_PAD_SINK | MEDIA_ENT_T_V4L2_SUBDEV:
+ /* read from decoder/sensor */
+ if (!(flags & MEDIA_LNK_FL_ENABLED)) {
+ isif->input = ISIF_INPUT_NONE;
+ break;
+ }
+ if (isif->input != ISIF_INPUT_NONE)
+ return -EBUSY;
+ isif->input = ISIF_INPUT_PARALLEL;
+ break;
+
+ case ISIF_PAD_SOURCE | MEDIA_ENT_T_DEVNODE:
+ /* write to memory */
+ if (flags & MEDIA_LNK_FL_ENABLED)
+ isif->output = ISIF_OUTPUT_MEMORY;
+ else
+ isif->output = ISIF_OUTPUT_NONE;
+ break;
+
+ case ISIF_PAD_SOURCE | MEDIA_ENT_T_V4L2_SUBDEV:
+ if (flags & MEDIA_LNK_FL_ENABLED)
+ isif->output = ISIF_OUTPUT_IPIPEIF;
+ else
+ isif->output = ISIF_OUTPUT_NONE;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+static const struct media_entity_operations isif_media_ops = {
+ .link_setup = isif_link_setup,
+};
+
+/*
+ * vpfe_isif_unregister_entities() - isif unregister entity
+ * @isif - pointer to isif subdevice structure.
+ */
+void vpfe_isif_unregister_entities(struct vpfe_isif_device *isif)
+{
+ vpfe_video_unregister(&isif->video_out);
+ /* unregister subdev */
+ v4l2_device_unregister_subdev(&isif->subdev);
+ /* cleanup entity */
+ media_entity_cleanup(&isif->subdev.entity);
+}
+
+static void isif_restore_defaults(struct vpfe_isif_device *isif)
+{
+ enum vpss_ccdc_source_sel source = VPSS_CCDCIN;
+ int i;
+
+ memset(&isif->isif_cfg.bayer.config_params, 0,
+ sizeof(struct vpfe_isif_raw_config));
+
+ isif->isif_cfg.bayer.config_params.linearize.corr_shft =
+ VPFE_ISIF_NO_SHIFT;
+ isif->isif_cfg.bayer.config_params.linearize.scale_fact.integer = 1;
+ isif->isif_cfg.bayer.config_params.culling.hcpat_odd =
+ ISIF_CULLING_HCAPT_ODD;
+ isif->isif_cfg.bayer.config_params.culling.hcpat_even =
+ ISIF_CULLING_HCAPT_EVEN;
+ isif->isif_cfg.bayer.config_params.culling.vcpat = ISIF_CULLING_VCAPT;
+ /* Enable clock to ISIF, IPIPEIF and BL */
+ vpss_enable_clock(VPSS_CCDC_CLOCK, 1);
+ vpss_enable_clock(VPSS_IPIPEIF_CLOCK, 1);
+ vpss_enable_clock(VPSS_BL_CLOCK, 1);
+
+ /* set all registers to default value */
+ for (i = 0; i <= 0x1f8; i += 4)
+ isif_write(isif->isif_cfg.base_addr, 0, i);
+ /* no culling support */
+ isif_write(isif->isif_cfg.base_addr, 0xffff, CULH);
+ isif_write(isif->isif_cfg.base_addr, 0xff, CULV);
+
+ /* Set default offset and gain */
+ isif_config_gain_offset(isif);
+ vpss_select_ccdc_source(source);
+}
+
+/*
+ * vpfe_isif_register_entities() - isif register entity
+ * @isif - pointer to isif subdevice structure.
+ * @vdev: pointer to v4l2 device structure.
+ */
+int vpfe_isif_register_entities(struct vpfe_isif_device *isif,
+ struct v4l2_device *vdev)
+{
+ struct vpfe_device *vpfe_dev = to_vpfe_device(isif);
+ unsigned int flags;
+ int ret;
+
+ /* Register the subdev */
+ ret = v4l2_device_register_subdev(vdev, &isif->subdev);
+ if (ret < 0)
+ return ret;
+
+ isif_restore_defaults(isif);
+ ret = vpfe_video_register(&isif->video_out, vdev);
+ if (ret) {
+ pr_err("Failed to register isif video out device\n");
+ goto out_video_register;
+ }
+ isif->video_out.vpfe_dev = vpfe_dev;
+ flags = 0;
+ /* connect isif to video node */
+ ret = media_entity_create_link(&isif->subdev.entity, 1,
+ &isif->video_out.video_dev.entity,
+ 0, flags);
+ if (ret < 0)
+ goto out_create_link;
+ return 0;
+out_create_link:
+ vpfe_video_unregister(&isif->video_out);
+out_video_register:
+ v4l2_device_unregister_subdev(&isif->subdev);
+ return ret;
+}
+
+/* -------------------------------------------------------------------
+ * V4L2 subdev control operations
+ */
+
+static int vpfe_isif_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+ struct vpfe_isif_device *isif =
+ container_of(ctrl->handler, struct vpfe_isif_device, ctrls);
+ struct isif_oper_config *config = &isif->isif_cfg;
+
+ switch (ctrl->id) {
+ case VPFE_CID_DPCM_PREDICTOR:
+ config->bayer.dpcm_predictor = ctrl->val;
+ break;
+
+ case VPFE_ISIF_CID_CRGAIN:
+ config->isif_gain_params.cr_gain = ctrl->val;
+ break;
+
+ case VPFE_ISIF_CID_CGRGAIN:
+ config->isif_gain_params.cgr_gain = ctrl->val;
+ break;
+
+ case VPFE_ISIF_CID_CGBGAIN:
+ config->isif_gain_params.cgb_gain = ctrl->val;
+ break;
+
+ case VPFE_ISIF_CID_CBGAIN:
+ config->isif_gain_params.cb_gain = ctrl->val;
+ break;
+
+ case VPFE_ISIF_CID_GAIN_OFFSET:
+ config->isif_gain_params.offset = ctrl->val;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static const struct v4l2_ctrl_ops vpfe_isif_ctrl_ops = {
+ .s_ctrl = vpfe_isif_s_ctrl,
+};
+
+static const struct v4l2_ctrl_config vpfe_isif_dpcm_pred = {
+ .ops = &vpfe_isif_ctrl_ops,
+ .id = VPFE_CID_DPCM_PREDICTOR,
+ .name = "DPCM Predictor",
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .min = 0,
+ .max = 1,
+ .step = 1,
+ .def = 0,
+};
+
+static const struct v4l2_ctrl_config vpfe_isif_crgain = {
+ .ops = &vpfe_isif_ctrl_ops,
+ .id = VPFE_ISIF_CID_CRGAIN,
+ .name = "CRGAIN",
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .min = 0,
+ .max = (1 << 12) - 1,
+ .step = 1,
+ .def = 0,
+};
+
+static const struct v4l2_ctrl_config vpfe_isif_cgrgain = {
+ .ops = &vpfe_isif_ctrl_ops,
+ .id = VPFE_ISIF_CID_CGRGAIN,
+ .name = "CGRGAIN",
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .min = 0,
+ .max = (1 << 12) - 1,
+ .step = 1,
+ .def = 0,
+};
+
+static const struct v4l2_ctrl_config vpfe_isif_cgbgain = {
+ .ops = &vpfe_isif_ctrl_ops,
+ .id = VPFE_ISIF_CID_CGBGAIN,
+ .name = "CGBGAIN",
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .min = 0,
+ .max = (1 << 12) - 1,
+ .step = 1,
+ .def = 0,
+};
+
+static const struct v4l2_ctrl_config vpfe_isif_cbgain = {
+ .ops = &vpfe_isif_ctrl_ops,
+ .id = VPFE_ISIF_CID_CBGAIN,
+ .name = "CBGAIN",
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .min = 0,
+ .max = (1 << 12) - 1,
+ .step = 1,
+ .def = 0,
+};
+
+static const struct v4l2_ctrl_config vpfe_isif_gain_offset = {
+ .ops = &vpfe_isif_ctrl_ops,
+ .id = VPFE_ISIF_CID_GAIN_OFFSET,
+ .name = "Gain Offset",
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .min = 0,
+ .max = (1 << 12) - 1,
+ .step = 1,
+ .def = 0,
+};
+
+static void isif_remove(struct vpfe_isif_device *isif,
+ struct platform_device *pdev)
+{
+ struct resource *res;
+ int i = 0;
+
+ iounmap(isif->isif_cfg.base_addr);
+ iounmap(isif->isif_cfg.linear_tbl0_addr);
+ iounmap(isif->isif_cfg.linear_tbl1_addr);
+
+ while (i < 3) {
+ res = platform_get_resource(pdev, IORESOURCE_MEM, i);
+ if (res)
+ release_mem_region(res->start,
+ resource_size(res));
+ i++;
+ }
+}
+
+static void isif_config_defaults(struct vpfe_isif_device *isif)
+{
+ isif->isif_cfg.ycbcr.v4l2_pix_fmt = V4L2_PIX_FMT_UYVY;
+ isif->isif_cfg.ycbcr.pix_fmt = ISIF_PIXFMT_YCBCR_8BIT;
+ isif->isif_cfg.ycbcr.frm_fmt = ISIF_FRMFMT_INTERLACED;
+ isif->isif_cfg.ycbcr.fid_pol = VPFE_PINPOL_POSITIVE;
+ isif->isif_cfg.ycbcr.vd_pol = VPFE_PINPOL_POSITIVE;
+ isif->isif_cfg.ycbcr.hd_pol = VPFE_PINPOL_POSITIVE;
+ isif->isif_cfg.ycbcr.pix_order = ISIF_PIXORDER_CBYCRY;
+ isif->isif_cfg.ycbcr.buf_type = ISIF_BUFTYPE_FLD_INTERLEAVED;
+
+ isif->isif_cfg.bayer.v4l2_pix_fmt = V4L2_PIX_FMT_SGRBG10ALAW8;
+ isif->isif_cfg.bayer.pix_fmt = ISIF_PIXFMT_RAW;
+ isif->isif_cfg.bayer.frm_fmt = ISIF_FRMFMT_PROGRESSIVE;
+ isif->isif_cfg.bayer.fid_pol = VPFE_PINPOL_POSITIVE;
+ isif->isif_cfg.bayer.vd_pol = VPFE_PINPOL_POSITIVE;
+ isif->isif_cfg.bayer.hd_pol = VPFE_PINPOL_POSITIVE;
+ isif->isif_cfg.bayer.cfa_pat = ISIF_CFA_PAT_MOSAIC;
+ isif->isif_cfg.bayer.data_msb = ISIF_BIT_MSB_11;
+ isif->isif_cfg.data_pack = ISIF_PACK_8BIT;
+}
+/*
+ * vpfe_isif_init() - Initialize V4L2 subdev and media entity
+ * @isif: VPFE isif module
+ * @pdev: Pointer to platform device structure.
+ * Return 0 on success and a negative error code on failure.
+ */
+int vpfe_isif_init(struct vpfe_isif_device *isif, struct platform_device *pdev)
+{
+ struct v4l2_subdev *sd = &isif->subdev;
+ struct media_pad *pads = &isif->pads[0];
+ struct media_entity *me = &sd->entity;
+ static resource_size_t res_len;
+ struct resource *res;
+ void *__iomem addr;
+ int status;
+ int i = 0;
+
+ /* Get the ISIF base address, linearization table0 and table1 addr. */
+ while (i < 3) {
+ res = platform_get_resource(pdev, IORESOURCE_MEM, i);
+ if (!res) {
+ status = -ENOENT;
+ goto fail_nobase_res;
+ }
+ res_len = resource_size(res);
+ res = request_mem_region(res->start, res_len, res->name);
+ if (!res) {
+ status = -EBUSY;
+ goto fail_nobase_res;
+ }
+ addr = ioremap_nocache(res->start, res_len);
+ if (!addr) {
+ status = -EBUSY;
+ goto fail_base_iomap;
+ }
+ switch (i) {
+ case 0:
+ /* ISIF base address */
+ isif->isif_cfg.base_addr = addr;
+ break;
+ case 1:
+ /* ISIF linear tbl0 address */
+ isif->isif_cfg.linear_tbl0_addr = addr;
+ break;
+ default:
+ /* ISIF linear tbl0 address */
+ isif->isif_cfg.linear_tbl1_addr = addr;
+ break;
+ }
+ i++;
+ }
+ davinci_cfg_reg(DM365_VIN_CAM_WEN);
+ davinci_cfg_reg(DM365_VIN_CAM_VD);
+ davinci_cfg_reg(DM365_VIN_CAM_HD);
+ davinci_cfg_reg(DM365_VIN_YIN4_7_EN);
+ davinci_cfg_reg(DM365_VIN_YIN0_3_EN);
+
+ /* queue ops */
+ isif->video_out.ops = &isif_video_ops;
+ v4l2_subdev_init(sd, &isif_v4l2_ops);
+ sd->internal_ops = &isif_v4l2_internal_ops;
+ strlcpy(sd->name, "DAVINCI ISIF", sizeof(sd->name));
+ sd->grp_id = 1 << 16; /* group ID for davinci subdevs */
+ v4l2_set_subdevdata(sd, isif);
+ sd->flags |= V4L2_SUBDEV_FL_HAS_EVENTS | V4L2_SUBDEV_FL_HAS_DEVNODE;
+ pads[ISIF_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
+ pads[ISIF_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
+
+ isif->input = ISIF_INPUT_NONE;
+ isif->output = ISIF_OUTPUT_NONE;
+ me->ops = &isif_media_ops;
+ status = media_entity_init(me, ISIF_PADS_NUM, pads, 0);
+ if (status)
+ goto isif_fail;
+ isif->video_out.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ status = vpfe_video_init(&isif->video_out, "ISIF");
+ if (status) {
+ pr_err("Failed to init isif-out video device\n");
+ goto isif_fail;
+ }
+ v4l2_ctrl_handler_init(&isif->ctrls, 6);
+ v4l2_ctrl_new_custom(&isif->ctrls, &vpfe_isif_crgain, NULL);
+ v4l2_ctrl_new_custom(&isif->ctrls, &vpfe_isif_cgrgain, NULL);
+ v4l2_ctrl_new_custom(&isif->ctrls, &vpfe_isif_cgbgain, NULL);
+ v4l2_ctrl_new_custom(&isif->ctrls, &vpfe_isif_cbgain, NULL);
+ v4l2_ctrl_new_custom(&isif->ctrls, &vpfe_isif_gain_offset, NULL);
+ v4l2_ctrl_new_custom(&isif->ctrls, &vpfe_isif_dpcm_pred, NULL);
+
+ v4l2_ctrl_handler_setup(&isif->ctrls);
+ sd->ctrl_handler = &isif->ctrls;
+ isif_config_defaults(isif);
+ return 0;
+fail_base_iomap:
+ release_mem_region(res->start, res_len);
+ i--;
+fail_nobase_res:
+ if (isif->isif_cfg.base_addr)
+ iounmap(isif->isif_cfg.base_addr);
+ if (isif->isif_cfg.linear_tbl0_addr)
+ iounmap(isif->isif_cfg.linear_tbl0_addr);
+
+ while (i >= 0) {
+ res = platform_get_resource(pdev, IORESOURCE_MEM, i);
+ release_mem_region(res->start, res_len);
+ i--;
+ }
+ return status;
+isif_fail:
+ v4l2_ctrl_handler_free(&isif->ctrls);
+ isif_remove(isif, pdev);
+ return status;
+}
+
+/*
+ * vpfe_isif_cleanup - isif module cleanup
+ * @isif: pointer to isif subdevice
+ * @dev: pointer to platform device structure
+ */
+void
+vpfe_isif_cleanup(struct vpfe_isif_device *isif, struct platform_device *pdev)
+{
+ isif_remove(isif, pdev);
+}
diff --git a/drivers/staging/media/davinci_vpfe/dm365_isif.h b/drivers/staging/media/davinci_vpfe/dm365_isif.h
new file mode 100644
index 00000000000..473fd2cfe35
--- /dev/null
+++ b/drivers/staging/media/davinci_vpfe/dm365_isif.h
@@ -0,0 +1,203 @@
+/*
+ * Copyright (C) 2012 Texas Instruments Inc
+ *
+ * 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 version 2.
+ *
+ * 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
+ *
+ * Contributors:
+ * Manjunath Hadli <manjunath.hadli@ti.com>
+ * Prabhakar Lad <prabhakar.lad@ti.com>
+ */
+
+#ifndef _DAVINCI_VPFE_DM365_ISIF_H
+#define _DAVINCI_VPFE_DM365_ISIF_H
+
+#include <linux/platform_device.h>
+
+#include <mach/mux.h>
+
+#include <media/davinci/vpfe_types.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-device.h>
+
+#include "davinci_vpfe_user.h"
+#include "dm365_isif_regs.h"
+#include "vpfe_video.h"
+
+#define ISIF_CULLING_HCAPT_ODD 0xff
+#define ISIF_CULLING_HCAPT_EVEN 0xff
+#define ISIF_CULLING_VCAPT 0xff
+
+#define ISIF_CADU_BITS 0x07ff
+#define ISIF_CADL_BITS 0x0ffff
+
+enum isif_pixfmt {
+ ISIF_PIXFMT_RAW = 0,
+ ISIF_PIXFMT_YCBCR_16BIT = 1,
+ ISIF_PIXFMT_YCBCR_8BIT = 2,
+};
+
+enum isif_frmfmt {
+ ISIF_FRMFMT_PROGRESSIVE = 0,
+ ISIF_FRMFMT_INTERLACED = 1,
+};
+
+/* PIXEL ORDER IN MEMORY from LSB to MSB */
+/* only applicable for 8-bit input mode */
+enum isif_pixorder {
+ ISIF_PIXORDER_YCBYCR = 0,
+ ISIF_PIXORDER_CBYCRY = 1,
+};
+
+enum isif_buftype {
+ ISIF_BUFTYPE_FLD_INTERLEAVED = 0,
+ ISIF_BUFTYPE_FLD_SEPARATED = 1,
+};
+
+struct isif_ycbcr_config {
+ /* v4l2 pixel format */
+ unsigned long v4l2_pix_fmt;
+ /* isif pixel format */
+ enum isif_pixfmt pix_fmt;
+ /* isif frame format */
+ enum isif_frmfmt frm_fmt;
+ /* isif crop window */
+ struct v4l2_rect win;
+ /* field polarity */
+ enum vpfe_pin_pol fid_pol;
+ /* interface VD polarity */
+ enum vpfe_pin_pol vd_pol;
+ /* interface HD polarity */
+ enum vpfe_pin_pol hd_pol;
+ /* isif pix order. Only used for ycbcr capture */
+ enum isif_pixorder pix_order;
+ /* isif buffer type. Only used for ycbcr capture */
+ enum isif_buftype buf_type;
+};
+
+enum isif_cfa_pattern {
+ ISIF_CFA_PAT_MOSAIC = 0,
+ ISIF_CFA_PAT_STRIPE = 1,
+};
+
+enum isif_data_msb {
+ /* MSB b15 */
+ ISIF_BIT_MSB_15 = 0,
+ /* MSB b14 */
+ ISIF_BIT_MSB_14 = 1,
+ /* MSB b13 */
+ ISIF_BIT_MSB_13 = 2,
+ /* MSB b12 */
+ ISIF_BIT_MSB_12 = 3,
+ /* MSB b11 */
+ ISIF_BIT_MSB_11 = 4,
+ /* MSB b10 */
+ ISIF_BIT_MSB_10 = 5,
+ /* MSB b9 */
+ ISIF_BIT_MSB_9 = 6,
+ /* MSB b8 */
+ ISIF_BIT_MSB_8 = 7,
+ /* MSB b7 */
+ ISIF_BIT_MSB_7 = 8,
+};
+
+struct isif_params_raw {
+ /* v4l2 pixel format */
+ unsigned long v4l2_pix_fmt;
+ /* isif pixel format */
+ enum isif_pixfmt pix_fmt;
+ /* isif frame format */
+ enum isif_frmfmt frm_fmt;
+ /* video window */
+ struct v4l2_rect win;
+ /* field polarity */
+ enum vpfe_pin_pol fid_pol;
+ /* interface VD polarity */
+ enum vpfe_pin_pol vd_pol;
+ /* interface HD polarity */
+ enum vpfe_pin_pol hd_pol;
+ /* buffer type. Applicable for interlaced mode */
+ enum isif_buftype buf_type;
+ /* cfa pattern */
+ enum isif_cfa_pattern cfa_pat;
+ /* Data MSB position */
+ enum isif_data_msb data_msb;
+ /* Enable horizontal flip */
+ unsigned char horz_flip_en;
+ /* Enable image invert vertically */
+ unsigned char image_invert_en;
+ unsigned char dpcm_predictor;
+ struct vpfe_isif_raw_config config_params;
+};
+
+enum isif_data_pack {
+ ISIF_PACK_16BIT = 0,
+ ISIF_PACK_12BIT = 1,
+ ISIF_PACK_8BIT = 2,
+};
+
+struct isif_gain_values {
+ unsigned int cr_gain;
+ unsigned int cgr_gain;
+ unsigned int cgb_gain;
+ unsigned int cb_gain;
+ unsigned int offset;
+};
+
+struct isif_oper_config {
+ struct isif_ycbcr_config ycbcr;
+ struct isif_params_raw bayer;
+ enum isif_data_pack data_pack;
+ struct isif_gain_values isif_gain_params;
+ void *__iomem base_addr;
+ void *__iomem linear_tbl0_addr;
+ void *__iomem linear_tbl1_addr;
+};
+
+#define ISIF_PAD_SINK 0
+#define ISIF_PAD_SOURCE 1
+
+#define ISIF_PADS_NUM 2
+
+enum isif_input_entity {
+ ISIF_INPUT_NONE = 0,
+ ISIF_INPUT_PARALLEL = 1,
+};
+
+#define ISIF_OUTPUT_NONE (0)
+#define ISIF_OUTPUT_MEMORY (1 << 0)
+#define ISIF_OUTPUT_IPIPEIF (1 << 1)
+
+struct vpfe_isif_device {
+ struct v4l2_subdev subdev;
+ struct media_pad pads[ISIF_PADS_NUM];
+ struct v4l2_mbus_framefmt formats[ISIF_PADS_NUM];
+ enum isif_input_entity input;
+ unsigned int output;
+ struct v4l2_ctrl_handler ctrls;
+ struct v4l2_rect crop;
+ struct isif_oper_config isif_cfg;
+ struct vpfe_video_device video_out;
+};
+
+enum v4l2_field vpfe_isif_get_fid(struct vpfe_device *vpfe_dev);
+void vpfe_isif_unregister_entities(struct vpfe_isif_device *isif);
+int vpfe_isif_register_entities(struct vpfe_isif_device *isif,
+ struct v4l2_device *dev);
+int vpfe_isif_init(struct vpfe_isif_device *isif, struct platform_device *pdev);
+void vpfe_isif_cleanup(struct vpfe_isif_device *vpfe_isif,
+ struct platform_device *pdev);
+void vpfe_isif_vidint1_isr(struct vpfe_isif_device *isif);
+void vpfe_isif_buffer_isr(struct vpfe_isif_device *isif);
+
+#endif /* _DAVINCI_VPFE_DM365_ISIF_H */
diff --git a/drivers/staging/media/davinci_vpfe/dm365_isif_regs.h b/drivers/staging/media/davinci_vpfe/dm365_isif_regs.h
new file mode 100644
index 00000000000..8aceabb43f8
--- /dev/null
+++ b/drivers/staging/media/davinci_vpfe/dm365_isif_regs.h
@@ -0,0 +1,294 @@
+/*
+ * Copyright (C) 2012 Texas Instruments Inc
+ *
+ * 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 version 2.
+ *
+ * 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
+ *
+ * Contributors:
+ * Manjunath Hadli <manjunath.hadli@ti.com>
+ * Prabhakar Lad <prabhakar.lad@ti.com>
+ */
+
+#ifndef _DAVINCI_VPFE_DM365_ISIF_REGS_H
+#define _DAVINCI_VPFE_DM365_ISIF_REGS_H
+
+/* ISIF registers relative offsets */
+#define SYNCEN 0x00
+#define MODESET 0x04
+#define HDW 0x08
+#define VDW 0x0c
+#define PPLN 0x10
+#define LPFR 0x14
+#define SPH 0x18
+#define LNH 0x1c
+#define SLV0 0x20
+#define SLV1 0x24
+#define LNV 0x28
+#define CULH 0x2c
+#define CULV 0x30
+#define HSIZE 0x34
+#define SDOFST 0x38
+#define CADU 0x3c
+#define CADL 0x40
+#define LINCFG0 0x44
+#define LINCFG1 0x48
+#define CCOLP 0x4c
+#define CRGAIN 0x50
+#define CGRGAIN 0x54
+#define CGBGAIN 0x58
+#define CBGAIN 0x5c
+#define COFSTA 0x60
+#define FLSHCFG0 0x64
+#define FLSHCFG1 0x68
+#define FLSHCFG2 0x6c
+#define VDINT0 0x70
+#define VDINT1 0x74
+#define VDINT2 0x78
+#define MISC 0x7c
+#define CGAMMAWD 0x80
+#define REC656IF 0x84
+#define CCDCFG 0x88
+/*****************************************************
+* Defect Correction registers
+*****************************************************/
+#define DFCCTL 0x8c
+#define VDFSATLV 0x90
+#define DFCMEMCTL 0x94
+#define DFCMEM0 0x98
+#define DFCMEM1 0x9c
+#define DFCMEM2 0xa0
+#define DFCMEM3 0xa4
+#define DFCMEM4 0xa8
+/****************************************************
+* Black Clamp registers
+****************************************************/
+#define CLAMPCFG 0xac
+#define CLDCOFST 0xb0
+#define CLSV 0xb4
+#define CLHWIN0 0xb8
+#define CLHWIN1 0xbc
+#define CLHWIN2 0xc0
+#define CLVRV 0xc4
+#define CLVWIN0 0xc8
+#define CLVWIN1 0xcc
+#define CLVWIN2 0xd0
+#define CLVWIN3 0xd4
+/****************************************************
+* Lense Shading Correction
+****************************************************/
+#define DATAHOFST 0xd8
+#define DATAVOFST 0xdc
+#define LSCHVAL 0xe0
+#define LSCVVAL 0xe4
+#define TWODLSCCFG 0xe8
+#define TWODLSCOFST 0xec
+#define TWODLSCINI 0xf0
+#define TWODLSCGRBU 0xf4
+#define TWODLSCGRBL 0xf8
+#define TWODLSCGROF 0xfc
+#define TWODLSCORBU 0x100
+#define TWODLSCORBL 0x104
+#define TWODLSCOROF 0x108
+#define TWODLSCIRQEN 0x10c
+#define TWODLSCIRQST 0x110
+/****************************************************
+* Data formatter
+****************************************************/
+#define FMTCFG 0x114
+#define FMTPLEN 0x118
+#define FMTSPH 0x11c
+#define FMTLNH 0x120
+#define FMTSLV 0x124
+#define FMTLNV 0x128
+#define FMTRLEN 0x12c
+#define FMTHCNT 0x130
+#define FMTAPTR_BASE 0x134
+/* Below macro for addresses FMTAPTR0 - FMTAPTR15 */
+#define FMTAPTR(i) (FMTAPTR_BASE + (i * 4))
+#define FMTPGMVF0 0x174
+#define FMTPGMVF1 0x178
+#define FMTPGMAPU0 0x17c
+#define FMTPGMAPU1 0x180
+#define FMTPGMAPS0 0x184
+#define FMTPGMAPS1 0x188
+#define FMTPGMAPS2 0x18c
+#define FMTPGMAPS3 0x190
+#define FMTPGMAPS4 0x194
+#define FMTPGMAPS5 0x198
+#define FMTPGMAPS6 0x19c
+#define FMTPGMAPS7 0x1a0
+/************************************************
+* Color Space Converter
+************************************************/
+#define CSCCTL 0x1a4
+#define CSCM0 0x1a8
+#define CSCM1 0x1ac
+#define CSCM2 0x1b0
+#define CSCM3 0x1b4
+#define CSCM4 0x1b8
+#define CSCM5 0x1bc
+#define CSCM6 0x1c0
+#define CSCM7 0x1c4
+#define OBWIN0 0x1c8
+#define OBWIN1 0x1cc
+#define OBWIN2 0x1d0
+#define OBWIN3 0x1d4
+#define OBVAL0 0x1d8
+#define OBVAL1 0x1dc
+#define OBVAL2 0x1e0
+#define OBVAL3 0x1e4
+#define OBVAL4 0x1e8
+#define OBVAL5 0x1ec
+#define OBVAL6 0x1f0
+#define OBVAL7 0x1f4
+#define CLKCTL 0x1f8
+
+/* Masks & Shifts below */
+#define START_PX_HOR_MASK 0x7fff
+#define NUM_PX_HOR_MASK 0x7fff
+#define START_VER_ONE_MASK 0x7fff
+#define START_VER_TWO_MASK 0x7fff
+#define NUM_LINES_VER 0x7fff
+
+/* gain - offset masks */
+#define OFFSET_MASK 0xfff
+#define GAIN_SDRAM_EN_SHIFT 12
+#define GAIN_IPIPE_EN_SHIFT 13
+#define GAIN_H3A_EN_SHIFT 14
+#define OFST_SDRAM_EN_SHIFT 8
+#define OFST_IPIPE_EN_SHIFT 9
+#define OFST_H3A_EN_SHIFT 10
+#define GAIN_OFFSET_EN_MASK 0x7700
+
+/* Culling */
+#define CULL_PAT_EVEN_LINE_SHIFT 8
+
+/* CCDCFG register */
+#define ISIF_YCINSWP_RAW (0x00 << 4)
+#define ISIF_YCINSWP_YCBCR (0x01 << 4)
+#define ISIF_CCDCFG_FIDMD_LATCH_VSYNC (0x00 << 6)
+#define ISIF_CCDCFG_WENLOG_AND (0x00 << 8)
+#define ISIF_CCDCFG_TRGSEL_WEN (0x00 << 9)
+#define ISIF_CCDCFG_EXTRG_DISABLE (0x00 << 10)
+#define ISIF_LATCH_ON_VSYNC_DISABLE (0x01 << 15)
+#define ISIF_LATCH_ON_VSYNC_ENABLE (0x00 << 15)
+#define ISIF_DATA_PACK_MASK 0x03
+#define ISIF_PIX_ORDER_SHIFT 11
+#define ISIF_PIX_ORDER_MASK 0x01
+#define ISIF_BW656_ENABLE (0x01 << 5)
+
+/* MODESET registers */
+#define ISIF_VDHDOUT_INPUT (0x00 << 0)
+#define ISIF_INPUT_MASK 0x03
+#define ISIF_INPUT_SHIFT 12
+#define ISIF_FID_POL_MASK 0x01
+#define ISIF_FID_POL_SHIFT 4
+#define ISIF_HD_POL_MASK 0x01
+#define ISIF_HD_POL_SHIFT 3
+#define ISIF_VD_POL_MASK 0x01
+#define ISIF_VD_POL_SHIFT 2
+#define ISIF_DATAPOL_NORMAL 0x00
+#define ISIF_DATAPOL_MASK 0x01
+#define ISIF_DATAPOL_SHIFT 6
+#define ISIF_EXWEN_DISABLE 0x00
+#define ISIF_EXWEN_MASK 0x01
+#define ISIF_EXWEN_SHIFT 5
+#define ISIF_FRM_FMT_MASK 0x01
+#define ISIF_FRM_FMT_SHIFT 7
+#define ISIF_DATASFT_MASK 0x07
+#define ISIF_DATASFT_SHIFT 8
+#define ISIF_LPF_SHIFT 14
+#define ISIF_LPF_MASK 0x1
+
+/* GAMMAWD registers */
+#define ISIF_ALAW_GAMA_WD_MASK 0xf
+#define ISIF_ALAW_GAMA_WD_SHIFT 1
+#define ISIF_ALAW_ENABLE 0x01
+#define ISIF_GAMMAWD_CFA_MASK 0x01
+#define ISIF_GAMMAWD_CFA_SHIFT 5
+
+/* HSIZE registers */
+#define ISIF_HSIZE_FLIP_MASK 0x01
+#define ISIF_HSIZE_FLIP_SHIFT 12
+#define ISIF_LINEOFST_MASK 0xfff
+
+/* MISC registers */
+#define ISIF_DPCM_EN_SHIFT 12
+#define ISIF_DPCM_PREDICTOR_SHIFT 13
+#define ISIF_DPCM_PREDICTOR_MASK 1
+
+/* Black clamp related */
+#define ISIF_BC_DCOFFSET_MASK 0x1fff
+#define ISIF_BC_MODE_COLOR_MASK 1
+#define ISIF_BC_MODE_COLOR_SHIFT 4
+#define ISIF_HORZ_BC_MODE_MASK 3
+#define ISIF_HORZ_BC_MODE_SHIFT 1
+#define ISIF_HORZ_BC_WIN_COUNT_MASK 0x1f
+#define ISIF_HORZ_BC_WIN_SEL_SHIFT 5
+#define ISIF_HORZ_BC_PIX_LIMIT_SHIFT 6
+#define ISIF_HORZ_BC_WIN_H_SIZE_MASK 3
+#define ISIF_HORZ_BC_WIN_H_SIZE_SHIFT 8
+#define ISIF_HORZ_BC_WIN_V_SIZE_MASK 3
+#define ISIF_HORZ_BC_WIN_V_SIZE_SHIFT 12
+#define ISIF_HORZ_BC_WIN_START_H_MASK 0x1fff
+#define ISIF_HORZ_BC_WIN_START_V_MASK 0x1fff
+#define ISIF_VERT_BC_OB_H_SZ_MASK 7
+#define ISIF_VERT_BC_RST_VAL_SEL_MASK 3
+#define ISIF_VERT_BC_RST_VAL_SEL_SHIFT 4
+#define ISIF_VERT_BC_LINE_AVE_COEF_SHIFT 8
+#define ISIF_VERT_BC_OB_START_HORZ_MASK 0x1fff
+#define ISIF_VERT_BC_OB_START_VERT_MASK 0x1fff
+#define ISIF_VERT_BC_OB_VERT_SZ_MASK 0x1fff
+#define ISIF_VERT_BC_RST_VAL_MASK 0xfff
+#define ISIF_BC_VERT_START_SUB_V_MASK 0x1fff
+
+/* VDFC registers */
+#define ISIF_VDFC_EN_SHIFT 4
+#define ISIF_VDFC_CORR_MOD_MASK 3
+#define ISIF_VDFC_CORR_MOD_SHIFT 5
+#define ISIF_VDFC_CORR_WHOLE_LN_SHIFT 7
+#define ISIF_VDFC_LEVEL_SHFT_MASK 7
+#define ISIF_VDFC_LEVEL_SHFT_SHIFT 8
+#define ISIF_VDFC_SAT_LEVEL_MASK 0xfff
+#define ISIF_VDFC_POS_MASK 0x1fff
+#define ISIF_DFCMEMCTL_DFCMARST_SHIFT 2
+
+/* CSC registers */
+#define ISIF_CSC_COEF_INTEG_MASK 7
+#define ISIF_CSC_COEF_DECIMAL_MASK 0x1f
+#define ISIF_CSC_COEF_INTEG_SHIFT 5
+#define ISIF_CSCM_MSB_SHIFT 8
+#define ISIF_DF_CSC_SPH_MASK 0x1fff
+#define ISIF_DF_CSC_LNH_MASK 0x1fff
+#define ISIF_DF_CSC_SLV_MASK 0x1fff
+#define ISIF_DF_CSC_LNV_MASK 0x1fff
+#define ISIF_DF_NUMLINES 0x7fff
+#define ISIF_DF_NUMPIX 0x1fff
+
+/* Offsets for LSC/DFC/Gain */
+#define ISIF_DATA_H_OFFSET_MASK 0x1fff
+#define ISIF_DATA_V_OFFSET_MASK 0x1fff
+
+/* Linearization */
+#define ISIF_LIN_CORRSFT_MASK 7
+#define ISIF_LIN_CORRSFT_SHIFT 4
+#define ISIF_LIN_SCALE_FACT_INTEG_SHIFT 10
+#define ISIF_LIN_SCALE_FACT_DECIMAL_MASK 0x3ff
+#define ISIF_LIN_ENTRY_MASK 0x3ff
+
+/* masks and shifts*/
+#define ISIF_SYNCEN_VDHDEN_MASK (1 << 0)
+#define ISIF_SYNCEN_WEN_MASK (1 << 1)
+#define ISIF_SYNCEN_WEN_SHIFT 1
+
+#endif /* _DAVINCI_VPFE_DM365_ISIF_REGS_H */
diff --git a/drivers/staging/media/davinci_vpfe/dm365_resizer.c b/drivers/staging/media/davinci_vpfe/dm365_resizer.c
new file mode 100644
index 00000000000..8e13bd494c9
--- /dev/null
+++ b/drivers/staging/media/davinci_vpfe/dm365_resizer.c
@@ -0,0 +1,1999 @@
+/*
+ * Copyright (C) 2012 Texas Instruments Inc
+ *
+ * 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 version 2.
+ *
+ * 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
+ *
+ * Contributors:
+ * Manjunath Hadli <manjunath.hadli@ti.com>
+ * Prabhakar Lad <prabhakar.lad@ti.com>
+ *
+ *
+ * Resizer allows upscaling or downscaling a image to a desired
+ * resolution. There are 2 resizer modules. both operating on the
+ * same input image, but can have different output resolution.
+ */
+
+#include "dm365_ipipe_hw.h"
+#include "dm365_resizer.h"
+
+#define MIN_IN_WIDTH 32
+#define MIN_IN_HEIGHT 32
+#define MAX_IN_WIDTH 4095
+#define MAX_IN_HEIGHT 4095
+#define MIN_OUT_WIDTH 16
+#define MIN_OUT_HEIGHT 2
+
+static const unsigned int resizer_input_formats[] = {
+ V4L2_MBUS_FMT_UYVY8_2X8,
+ V4L2_MBUS_FMT_Y8_1X8,
+ V4L2_MBUS_FMT_UV8_1X8,
+ V4L2_MBUS_FMT_SGRBG12_1X12,
+};
+
+static const unsigned int resizer_output_formats[] = {
+ V4L2_MBUS_FMT_UYVY8_2X8,
+ V4L2_MBUS_FMT_Y8_1X8,
+ V4L2_MBUS_FMT_UV8_1X8,
+ V4L2_MBUS_FMT_YDYUYDYV8_1X16,
+ V4L2_MBUS_FMT_SGRBG12_1X12,
+};
+
+/* resizer_calculate_line_length() - This function calculates the line length of
+ * various image planes at the input and
+ * output.
+ */
+static void
+resizer_calculate_line_length(enum v4l2_mbus_pixelcode pix, int width,
+ int height, int *line_len, int *line_len_c)
+{
+ *line_len = 0;
+ *line_len_c = 0;
+
+ if (pix == V4L2_MBUS_FMT_UYVY8_2X8 ||
+ pix == V4L2_MBUS_FMT_SGRBG12_1X12) {
+ *line_len = width << 1;
+ } else if (pix == V4L2_MBUS_FMT_Y8_1X8 ||
+ pix == V4L2_MBUS_FMT_UV8_1X8) {
+ *line_len = width;
+ *line_len_c = width;
+ } else {
+ /* YUV 420 */
+ /* round width to upper 32 byte boundary */
+ *line_len = width;
+ *line_len_c = width;
+ }
+ /* adjust the line len to be a multiple of 32 */
+ *line_len += 31;
+ *line_len &= ~0x1f;
+ *line_len_c += 31;
+ *line_len_c &= ~0x1f;
+}
+
+static inline int
+resizer_validate_output_image_format(struct device *dev,
+ struct v4l2_mbus_framefmt *format,
+ int *in_line_len, int *in_line_len_c)
+{
+ if (format->code != V4L2_MBUS_FMT_UYVY8_2X8 &&
+ format->code != V4L2_MBUS_FMT_Y8_1X8 &&
+ format->code != V4L2_MBUS_FMT_UV8_1X8 &&
+ format->code != V4L2_MBUS_FMT_YDYUYDYV8_1X16 &&
+ format->code != V4L2_MBUS_FMT_SGRBG12_1X12) {
+ dev_err(dev, "Invalid Mbus format, %d\n", format->code);
+ return -EINVAL;
+ }
+ if (!format->width || !format->height) {
+ dev_err(dev, "invalid width or height\n");
+ return -EINVAL;
+ }
+ resizer_calculate_line_length(format->code, format->width,
+ format->height, in_line_len, in_line_len_c);
+ return 0;
+}
+
+static void
+resizer_configure_passthru(struct vpfe_resizer_device *resizer, int bypass)
+{
+ struct resizer_params *param = &resizer->config;
+
+ param->rsz_rsc_param[RSZ_A].cen = DISABLE;
+ param->rsz_rsc_param[RSZ_A].yen = DISABLE;
+ param->rsz_rsc_param[RSZ_A].v_phs_y = 0;
+ param->rsz_rsc_param[RSZ_A].v_phs_c = 0;
+ param->rsz_rsc_param[RSZ_A].v_dif = 256;
+ param->rsz_rsc_param[RSZ_A].v_lpf_int_y = 0;
+ param->rsz_rsc_param[RSZ_A].v_lpf_int_c = 0;
+ param->rsz_rsc_param[RSZ_A].h_phs = 0;
+ param->rsz_rsc_param[RSZ_A].h_dif = 256;
+ param->rsz_rsc_param[RSZ_A].h_lpf_int_y = 0;
+ param->rsz_rsc_param[RSZ_A].h_lpf_int_c = 0;
+ param->rsz_rsc_param[RSZ_A].dscale_en = DISABLE;
+ param->rsz2rgb[RSZ_A].rgb_en = DISABLE;
+ param->rsz_en[RSZ_A] = ENABLE;
+ param->rsz_en[RSZ_B] = DISABLE;
+ if (bypass) {
+ param->rsz_rsc_param[RSZ_A].i_vps = 0;
+ param->rsz_rsc_param[RSZ_A].i_hps = 0;
+ /* Raw Bypass */
+ param->rsz_common.passthrough = BYPASS_ON;
+ }
+}
+
+static void
+configure_resizer_out_params(struct vpfe_resizer_device *resizer, int index,
+ void *output_spec, unsigned char partial,
+ unsigned flag)
+{
+ struct resizer_params *param = &resizer->config;
+ struct v4l2_mbus_framefmt *outformat;
+ struct vpfe_rsz_output_spec *output;
+
+ if (index == RSZ_A &&
+ resizer->resizer_a.output == RESIZER_OUTPUT_NONE) {
+ param->rsz_en[index] = DISABLE;
+ return;
+ }
+ if (index == RSZ_B &&
+ resizer->resizer_b.output == RESIZER_OUTPUT_NONE) {
+ param->rsz_en[index] = DISABLE;
+ return;
+ }
+ output = (struct vpfe_rsz_output_spec *)output_spec;
+ param->rsz_en[index] = ENABLE;
+ if (partial) {
+ param->rsz_rsc_param[index].h_flip = output->h_flip;
+ param->rsz_rsc_param[index].v_flip = output->v_flip;
+ param->rsz_rsc_param[index].v_typ_y = output->v_typ_y;
+ param->rsz_rsc_param[index].v_typ_c = output->v_typ_c;
+ param->rsz_rsc_param[index].v_lpf_int_y =
+ output->v_lpf_int_y;
+ param->rsz_rsc_param[index].v_lpf_int_c =
+ output->v_lpf_int_c;
+ param->rsz_rsc_param[index].h_typ_y = output->h_typ_y;
+ param->rsz_rsc_param[index].h_typ_c = output->h_typ_c;
+ param->rsz_rsc_param[index].h_lpf_int_y =
+ output->h_lpf_int_y;
+ param->rsz_rsc_param[index].h_lpf_int_c =
+ output->h_lpf_int_c;
+ param->rsz_rsc_param[index].dscale_en =
+ output->en_down_scale;
+ param->rsz_rsc_param[index].h_dscale_ave_sz =
+ output->h_dscale_ave_sz;
+ param->rsz_rsc_param[index].v_dscale_ave_sz =
+ output->v_dscale_ave_sz;
+ param->ext_mem_param[index].user_y_ofst =
+ (output->user_y_ofst + 31) & ~0x1f;
+ param->ext_mem_param[index].user_c_ofst =
+ (output->user_c_ofst + 31) & ~0x1f;
+ return;
+ }
+
+ if (index == RSZ_A)
+ outformat = &resizer->resizer_a.formats[RESIZER_PAD_SOURCE];
+ else
+ outformat = &resizer->resizer_b.formats[RESIZER_PAD_SOURCE];
+ param->rsz_rsc_param[index].o_vsz = outformat->height - 1;
+ param->rsz_rsc_param[index].o_hsz = outformat->width - 1;
+ param->ext_mem_param[index].rsz_sdr_ptr_s_y = output->vst_y;
+ param->ext_mem_param[index].rsz_sdr_ptr_e_y = outformat->height;
+ param->ext_mem_param[index].rsz_sdr_ptr_s_c = output->vst_c;
+ param->ext_mem_param[index].rsz_sdr_ptr_e_c = outformat->height;
+
+ if (!flag)
+ return;
+ /* update common parameters */
+ param->rsz_rsc_param[index].h_flip = output->h_flip;
+ param->rsz_rsc_param[index].v_flip = output->v_flip;
+ param->rsz_rsc_param[index].v_typ_y = output->v_typ_y;
+ param->rsz_rsc_param[index].v_typ_c = output->v_typ_c;
+ param->rsz_rsc_param[index].v_lpf_int_y = output->v_lpf_int_y;
+ param->rsz_rsc_param[index].v_lpf_int_c = output->v_lpf_int_c;
+ param->rsz_rsc_param[index].h_typ_y = output->h_typ_y;
+ param->rsz_rsc_param[index].h_typ_c = output->h_typ_c;
+ param->rsz_rsc_param[index].h_lpf_int_y = output->h_lpf_int_y;
+ param->rsz_rsc_param[index].h_lpf_int_c = output->h_lpf_int_c;
+ param->rsz_rsc_param[index].dscale_en = output->en_down_scale;
+ param->rsz_rsc_param[index].h_dscale_ave_sz = output->h_dscale_ave_sz;
+ param->rsz_rsc_param[index].v_dscale_ave_sz = output->h_dscale_ave_sz;
+ param->ext_mem_param[index].user_y_ofst =
+ (output->user_y_ofst + 31) & ~0x1f;
+ param->ext_mem_param[index].user_c_ofst =
+ (output->user_c_ofst + 31) & ~0x1f;
+}
+
+/*
+ * resizer_calculate_resize_ratios() - Calculates resize ratio for resizer
+ * A or B. This is called after setting
+ * the input size or output size.
+ * @resizer: Pointer to VPFE resizer subdevice.
+ * @index: index RSZ_A-resizer-A RSZ_B-resizer-B.
+ */
+void
+resizer_calculate_resize_ratios(struct vpfe_resizer_device *resizer, int index)
+{
+ struct resizer_params *param = &resizer->config;
+ struct v4l2_mbus_framefmt *informat, *outformat;
+
+ informat = &resizer->crop_resizer.formats[RESIZER_CROP_PAD_SINK];
+
+ if (index == RSZ_A)
+ outformat = &resizer->resizer_a.formats[RESIZER_PAD_SOURCE];
+ else
+ outformat = &resizer->resizer_b.formats[RESIZER_PAD_SOURCE];
+
+ if (outformat->field != V4L2_FIELD_INTERLACED)
+ param->rsz_rsc_param[index].v_dif =
+ ((informat->height) * 256) / (outformat->height);
+ else
+ param->rsz_rsc_param[index].v_dif =
+ ((informat->height >> 1) * 256) / (outformat->height);
+ param->rsz_rsc_param[index].h_dif =
+ ((informat->width) * 256) / (outformat->width);
+}
+
+void
+static resizer_enable_422_420_conversion(struct resizer_params *param,
+ int index, bool en)
+{
+ param->rsz_rsc_param[index].cen = en;
+ param->rsz_rsc_param[index].yen = en;
+}
+
+/* resizer_calculate_sdram_offsets() - This function calculates the offsets from
+ * start of buffer for the C plane when
+ * output format is YUV420SP. It also
+ * calculates the offsets from the start of
+ * the buffer when the image is flipped
+ * vertically or horizontally for ycbcr/y/c
+ * planes.
+ * @resizer: Pointer to resizer subdevice.
+ * @index: index RSZ_A-resizer-A RSZ_B-resizer-B.
+ */
+static int
+resizer_calculate_sdram_offsets(struct vpfe_resizer_device *resizer, int index)
+{
+ struct resizer_params *param = &resizer->config;
+ struct v4l2_mbus_framefmt *outformat;
+ int bytesperpixel = 2;
+ int image_height;
+ int image_width;
+ int yuv_420 = 0;
+ int offset = 0;
+
+ if (index == RSZ_A)
+ outformat = &resizer->resizer_a.formats[RESIZER_PAD_SOURCE];
+ else
+ outformat = &resizer->resizer_b.formats[RESIZER_PAD_SOURCE];
+
+ image_height = outformat->height + 1;
+ image_width = outformat->width + 1;
+ param->ext_mem_param[index].c_offset = 0;
+ param->ext_mem_param[index].flip_ofst_y = 0;
+ param->ext_mem_param[index].flip_ofst_c = 0;
+ if (outformat->code == V4L2_MBUS_FMT_YDYUYDYV8_1X16) {
+ /* YUV 420 */
+ yuv_420 = 1;
+ bytesperpixel = 1;
+ }
+
+ if (param->rsz_rsc_param[index].h_flip)
+ /* width * bytesperpixel - 1 */
+ offset = (image_width * bytesperpixel) - 1;
+ if (param->rsz_rsc_param[index].v_flip)
+ offset += (image_height - 1) *
+ param->ext_mem_param[index].rsz_sdr_oft_y;
+ param->ext_mem_param[index].flip_ofst_y = offset;
+ if (!yuv_420)
+ return 0;
+ offset = 0;
+ /* half height for c-plane */
+ if (param->rsz_rsc_param[index].h_flip)
+ /* width * bytesperpixel - 1 */
+ offset = image_width - 1;
+ if (param->rsz_rsc_param[index].v_flip)
+ offset += (((image_height >> 1) - 1) *
+ param->ext_mem_param[index].rsz_sdr_oft_c);
+ param->ext_mem_param[index].flip_ofst_c = offset;
+ param->ext_mem_param[index].c_offset =
+ param->ext_mem_param[index].rsz_sdr_oft_y * image_height;
+ return 0;
+}
+
+int resizer_configure_output_win(struct vpfe_resizer_device *resizer)
+{
+ struct resizer_params *param = &resizer->config;
+ struct vpfe_rsz_output_spec output_specs;
+ struct v4l2_mbus_framefmt *outformat;
+ int line_len_c;
+ int line_len;
+ int ret;
+
+ outformat = &resizer->resizer_a.formats[RESIZER_PAD_SOURCE];
+
+ output_specs.vst_y = param->user_config.vst;
+ if (outformat->code == V4L2_MBUS_FMT_YDYUYDYV8_1X16)
+ output_specs.vst_c = param->user_config.vst;
+
+ configure_resizer_out_params(resizer, RSZ_A, &output_specs, 0, 0);
+ resizer_calculate_line_length(outformat->code,
+ param->rsz_rsc_param[0].o_hsz + 1,
+ param->rsz_rsc_param[0].o_vsz + 1,
+ &line_len, &line_len_c);
+ param->ext_mem_param[0].rsz_sdr_oft_y = line_len;
+ param->ext_mem_param[0].rsz_sdr_oft_c = line_len_c;
+ resizer_calculate_resize_ratios(resizer, RSZ_A);
+ if (param->rsz_en[RSZ_B])
+ resizer_calculate_resize_ratios(resizer, RSZ_B);
+
+ if (outformat->code == V4L2_MBUS_FMT_YDYUYDYV8_1X16)
+ resizer_enable_422_420_conversion(param, RSZ_A, ENABLE);
+ else
+ resizer_enable_422_420_conversion(param, RSZ_A, DISABLE);
+
+ ret = resizer_calculate_sdram_offsets(resizer, RSZ_A);
+ if (!ret && param->rsz_en[RSZ_B])
+ ret = resizer_calculate_sdram_offsets(resizer, RSZ_B);
+
+ if (ret)
+ pr_err("Error in calculating sdram offsets\n");
+ return ret;
+}
+
+static int
+resizer_calculate_down_scale_f_div_param(struct device *dev,
+ int input_width, int output_width,
+ struct resizer_scale_param *param)
+{
+ /* rsz = R, input_width = H, output width = h in the equation */
+ unsigned int two_power;
+ unsigned int upper_h1;
+ unsigned int upper_h2;
+ unsigned int val1;
+ unsigned int val;
+ unsigned int rsz;
+ unsigned int h1;
+ unsigned int h2;
+ unsigned int o;
+ unsigned int n;
+
+ upper_h1 = input_width >> 1;
+ n = param->h_dscale_ave_sz;
+ /* 2 ^ (scale+1) */
+ two_power = 1 << (n + 1);
+ upper_h1 = (upper_h1 >> (n + 1)) << (n + 1);
+ upper_h2 = input_width - upper_h1;
+ if (upper_h2 % two_power) {
+ dev_err(dev, "frame halves to be a multiple of 2 power n+1\n");
+ return -EINVAL;
+ }
+ two_power = 1 << n;
+ rsz = (input_width << 8) / output_width;
+ val = rsz * two_power;
+ val = ((upper_h1 << 8) / val) + 1;
+ if (!(val % 2)) {
+ h1 = val;
+ } else {
+ val = upper_h1 << 8;
+ val >>= n + 1;
+ val -= rsz >> 1;
+ val /= rsz << 1;
+ val <<= 1;
+ val += 2;
+ h1 = val;
+ }
+ o = 10 + (two_power << 2);
+ if (((input_width << 7) / rsz) % 2)
+ o += (((CEIL(rsz, 1024)) << 1) << n);
+ h2 = output_width - h1;
+ /* phi */
+ val = (h1 * rsz) - (((upper_h1 - (o - 10)) / two_power) << 8);
+ /* skip */
+ val1 = ((val - 1024) >> 9) << 1;
+ param->f_div.num_passes = MAX_PASSES;
+ param->f_div.pass[0].o_hsz = h1 - 1;
+ param->f_div.pass[0].i_hps = 0;
+ param->f_div.pass[0].h_phs = 0;
+ param->f_div.pass[0].src_hps = 0;
+ param->f_div.pass[0].src_hsz = upper_h1 + o;
+ param->f_div.pass[1].o_hsz = h2 - 1;
+ param->f_div.pass[1].i_hps = 10 + (val1 * two_power);
+ param->f_div.pass[1].h_phs = (val - (val1 << 8));
+ param->f_div.pass[1].src_hps = upper_h1 - o;
+ param->f_div.pass[1].src_hsz = upper_h2 + o;
+
+ return 0;
+}
+
+static int
+resizer_configure_common_in_params(struct vpfe_resizer_device *resizer)
+{
+ struct vpfe_device *vpfe_dev = to_vpfe_device(resizer);
+ struct resizer_params *param = &resizer->config;
+ struct vpfe_rsz_config_params *user_config;
+ struct v4l2_mbus_framefmt *informat;
+
+ informat = &resizer->crop_resizer.formats[RESIZER_CROP_PAD_SINK];
+ user_config = &resizer->config.user_config;
+ param->rsz_common.vps = param->user_config.vst;
+ param->rsz_common.hps = param->user_config.hst;
+
+ if (vpfe_ipipeif_decimation_enabled(vpfe_dev))
+ param->rsz_common.hsz = (((informat->width - 1) *
+ IPIPEIF_RSZ_CONST) / vpfe_ipipeif_get_rsz(vpfe_dev));
+ else
+ param->rsz_common.hsz = informat->width - 1;
+
+ if (informat->field == V4L2_FIELD_INTERLACED)
+ param->rsz_common.vsz = (informat->height - 1) >> 1;
+ else
+ param->rsz_common.vsz = informat->height - 1;
+
+ param->rsz_common.raw_flip = 0;
+
+ if (resizer->crop_resizer.input == RESIZER_CROP_INPUT_IPIPEIF)
+ param->rsz_common.source = IPIPEIF_DATA;
+ else
+ param->rsz_common.source = IPIPE_DATA;
+
+ switch (informat->code) {
+ case V4L2_MBUS_FMT_UYVY8_2X8:
+ param->rsz_common.src_img_fmt = RSZ_IMG_422;
+ param->rsz_common.raw_flip = 0;
+ break;
+
+ case V4L2_MBUS_FMT_Y8_1X8:
+ param->rsz_common.src_img_fmt = RSZ_IMG_420;
+ /* Select y */
+ param->rsz_common.y_c = 0;
+ param->rsz_common.raw_flip = 0;
+ break;
+
+ case V4L2_MBUS_FMT_UV8_1X8:
+ param->rsz_common.src_img_fmt = RSZ_IMG_420;
+ /* Select y */
+ param->rsz_common.y_c = 1;
+ param->rsz_common.raw_flip = 0;
+ break;
+
+ case V4L2_MBUS_FMT_SGRBG12_1X12:
+ param->rsz_common.raw_flip = 1;
+ break;
+
+ default:
+ param->rsz_common.src_img_fmt = RSZ_IMG_422;
+ param->rsz_common.source = IPIPE_DATA;
+ }
+
+ param->rsz_common.yuv_y_min = user_config->yuv_y_min;
+ param->rsz_common.yuv_y_max = user_config->yuv_y_max;
+ param->rsz_common.yuv_c_min = user_config->yuv_c_min;
+ param->rsz_common.yuv_c_max = user_config->yuv_c_max;
+ param->rsz_common.out_chr_pos = user_config->out_chr_pos;
+ param->rsz_common.rsz_seq_crv = user_config->chroma_sample_even;
+
+ return 0;
+}
+static int
+resizer_configure_in_continious_mode(struct vpfe_resizer_device *resizer)
+{
+ struct device *dev = resizer->crop_resizer.subdev.v4l2_dev->dev;
+ struct resizer_params *param = &resizer->config;
+ struct vpfe_rsz_config_params *cont_config;
+ int line_len_c;
+ int line_len;
+ int ret;
+
+ if (resizer->resizer_a.output != RESIZER_OUPUT_MEMORY) {
+ dev_err(dev, "enable resizer - Resizer-A\n");
+ return -EINVAL;
+ }
+
+ cont_config = &resizer->config.user_config;
+ param->rsz_en[RSZ_A] = ENABLE;
+ configure_resizer_out_params(resizer, RSZ_A,
+ &cont_config->output1, 1, 0);
+ param->rsz_en[RSZ_B] = DISABLE;
+ param->oper_mode = RESIZER_MODE_CONTINIOUS;
+
+ if (resizer->resizer_b.output == RESIZER_OUPUT_MEMORY) {
+ struct v4l2_mbus_framefmt *outformat2;
+
+ param->rsz_en[RSZ_B] = ENABLE;
+ outformat2 = &resizer->resizer_b.formats[RESIZER_PAD_SOURCE];
+ ret = resizer_validate_output_image_format(dev, outformat2,
+ &line_len, &line_len_c);
+ if (ret)
+ return ret;
+ param->ext_mem_param[RSZ_B].rsz_sdr_oft_y = line_len;
+ param->ext_mem_param[RSZ_B].rsz_sdr_oft_c = line_len_c;
+ configure_resizer_out_params(resizer, RSZ_B,
+ &cont_config->output2, 0, 1);
+ if (outformat2->code == V4L2_MBUS_FMT_YDYUYDYV8_1X16)
+ resizer_enable_422_420_conversion(param,
+ RSZ_B, ENABLE);
+ else
+ resizer_enable_422_420_conversion(param,
+ RSZ_B, DISABLE);
+ }
+ resizer_configure_common_in_params(resizer);
+ ret = resizer_configure_output_win(resizer);
+ if (ret)
+ return ret;
+
+ param->rsz_common.passthrough = cont_config->bypass;
+ if (cont_config->bypass)
+ resizer_configure_passthru(resizer, 1);
+
+ return 0;
+}
+
+static inline int
+resizer_validate_input_image_format(struct device *dev,
+ enum v4l2_mbus_pixelcode pix,
+ int width, int height, int *line_len)
+{
+ int val;
+
+ if (pix != V4L2_MBUS_FMT_UYVY8_2X8 &&
+ pix != V4L2_MBUS_FMT_Y8_1X8 &&
+ pix != V4L2_MBUS_FMT_UV8_1X8 &&
+ pix != V4L2_MBUS_FMT_SGRBG12_1X12) {
+ dev_err(dev,
+ "resizer validate output: pix format not supported, %d\n", pix);
+ return -EINVAL;
+ }
+
+ if (!width || !height) {
+ dev_err(dev,
+ "resizer validate input: invalid width or height\n");
+ return -EINVAL;
+ }
+
+ if (pix == V4L2_MBUS_FMT_UV8_1X8)
+ resizer_calculate_line_length(pix, width,
+ height, &val, line_len);
+ else
+ resizer_calculate_line_length(pix, width,
+ height, line_len, &val);
+
+ return 0;
+}
+
+static int
+resizer_validate_decimation(struct device *dev, enum ipipeif_decimation dec_en,
+ unsigned char rsz, unsigned char frame_div_mode_en,
+ int width)
+{
+ if (dec_en && frame_div_mode_en) {
+ dev_err(dev,
+ "dec_en & frame_div_mode_en can not enabled simultaneously\n");
+ return -EINVAL;
+ }
+
+ if (frame_div_mode_en) {
+ dev_err(dev, "frame_div_mode mode not supported\n");
+ return -EINVAL;
+ }
+
+ if (!dec_en)
+ return 0;
+
+ if (width <= VPFE_IPIPE_MAX_INPUT_WIDTH) {
+ dev_err(dev,
+ "image width to be more than %d for decimation\n",
+ VPFE_IPIPE_MAX_INPUT_WIDTH);
+ return -EINVAL;
+ }
+
+ if (rsz < IPIPEIF_RSZ_MIN || rsz > IPIPEIF_RSZ_MAX) {
+ dev_err(dev, "rsz range is %d to %d\n",
+ IPIPEIF_RSZ_MIN, IPIPEIF_RSZ_MAX);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/* resizer_calculate_normal_f_div_param() - Algorithm to calculate the frame
+ * division parameters for resizer.
+ * in normal mode.
+ */
+static int
+resizer_calculate_normal_f_div_param(struct device *dev, int input_width,
+ int output_width, struct resizer_scale_param *param)
+{
+ /* rsz = R, input_width = H, output width = h in the equation */
+ unsigned int val1;
+ unsigned int rsz;
+ unsigned int val;
+ unsigned int h1;
+ unsigned int h2;
+ unsigned int o;
+
+ if (output_width > input_width) {
+ dev_err(dev, "frame div mode is used for scale down only\n");
+ return -EINVAL;
+ }
+
+ rsz = (input_width << 8) / output_width;
+ val = rsz << 1;
+ val = ((input_width << 8) / val) + 1;
+ o = 14;
+ if (!(val % 2)) {
+ h1 = val;
+ } else {
+ val = (input_width << 7);
+ val -= rsz >> 1;
+ val /= rsz << 1;
+ val <<= 1;
+ val += 2;
+ o += ((CEIL(rsz, 1024)) << 1);
+ h1 = val;
+ }
+ h2 = output_width - h1;
+ /* phi */
+ val = (h1 * rsz) - (((input_width >> 1) - o) << 8);
+ /* skip */
+ val1 = ((val - 1024) >> 9) << 1;
+ param->f_div.num_passes = MAX_PASSES;
+ param->f_div.pass[0].o_hsz = h1 - 1;
+ param->f_div.pass[0].i_hps = 0;
+ param->f_div.pass[0].h_phs = 0;
+ param->f_div.pass[0].src_hps = 0;
+ param->f_div.pass[0].src_hsz = (input_width >> 2) + o;
+ param->f_div.pass[1].o_hsz = h2 - 1;
+ param->f_div.pass[1].i_hps = val1;
+ param->f_div.pass[1].h_phs = (val - (val1 << 8));
+ param->f_div.pass[1].src_hps = (input_width >> 2) - o;
+ param->f_div.pass[1].src_hsz = (input_width >> 2) + o;
+
+ return 0;
+}
+
+static int
+resizer_configure_in_single_shot_mode(struct vpfe_resizer_device *resizer)
+{
+ struct vpfe_rsz_config_params *config = &resizer->config.user_config;
+ struct device *dev = resizer->crop_resizer.subdev.v4l2_dev->dev;
+ struct vpfe_device *vpfe_dev = to_vpfe_device(resizer);
+ struct v4l2_mbus_framefmt *outformat1, *outformat2;
+ struct resizer_params *param = &resizer->config;
+ struct v4l2_mbus_framefmt *informat;
+ int decimation;
+ int line_len_c;
+ int line_len;
+ int rsz;
+ int ret;
+
+ informat = &resizer->crop_resizer.formats[RESIZER_CROP_PAD_SINK];
+ outformat1 = &resizer->resizer_a.formats[RESIZER_PAD_SOURCE];
+ outformat2 = &resizer->resizer_b.formats[RESIZER_PAD_SOURCE];
+
+ decimation = vpfe_ipipeif_decimation_enabled(vpfe_dev);
+ rsz = vpfe_ipipeif_get_rsz(vpfe_dev);
+ if (decimation && param->user_config.frame_div_mode_en) {
+ dev_err(dev,
+ "dec_en & frame_div_mode_en cannot enabled simultaneously\n");
+ return -EINVAL;
+ }
+
+ ret = resizer_validate_decimation(dev, decimation, rsz,
+ param->user_config.frame_div_mode_en, informat->width);
+ if (ret)
+ return -EINVAL;
+
+ ret = resizer_validate_input_image_format(dev, informat->code,
+ informat->width, informat->height, &line_len);
+ if (ret)
+ return -EINVAL;
+
+ if (resizer->resizer_a.output != RESIZER_OUTPUT_NONE) {
+ param->rsz_en[RSZ_A] = ENABLE;
+ ret = resizer_validate_output_image_format(dev, outformat1,
+ &line_len, &line_len_c);
+ if (ret)
+ return ret;
+ param->ext_mem_param[RSZ_A].rsz_sdr_oft_y = line_len;
+ param->ext_mem_param[RSZ_A].rsz_sdr_oft_c = line_len_c;
+ configure_resizer_out_params(resizer, RSZ_A,
+ &param->user_config.output1, 0, 1);
+
+ if (outformat1->code == V4L2_MBUS_FMT_SGRBG12_1X12)
+ param->rsz_common.raw_flip = 1;
+ else
+ param->rsz_common.raw_flip = 0;
+
+ if (outformat1->code == V4L2_MBUS_FMT_YDYUYDYV8_1X16)
+ resizer_enable_422_420_conversion(param,
+ RSZ_A, ENABLE);
+ else
+ resizer_enable_422_420_conversion(param,
+ RSZ_A, DISABLE);
+ }
+
+ if (resizer->resizer_b.output != RESIZER_OUTPUT_NONE) {
+ param->rsz_en[RSZ_B] = ENABLE;
+ ret = resizer_validate_output_image_format(dev, outformat2,
+ &line_len, &line_len_c);
+ if (ret)
+ return ret;
+ param->ext_mem_param[RSZ_B].rsz_sdr_oft_y = line_len;
+ param->ext_mem_param[RSZ_B].rsz_sdr_oft_c = line_len_c;
+ configure_resizer_out_params(resizer, RSZ_B,
+ &param->user_config.output2, 0, 1);
+ if (outformat2->code == V4L2_MBUS_FMT_YDYUYDYV8_1X16)
+ resizer_enable_422_420_conversion(param,
+ RSZ_B, ENABLE);
+ else
+ resizer_enable_422_420_conversion(param,
+ RSZ_B, DISABLE);
+ }
+
+ resizer_configure_common_in_params(resizer);
+ if (resizer->resizer_a.output != RESIZER_OUTPUT_NONE) {
+ resizer_calculate_resize_ratios(resizer, RSZ_A);
+ resizer_calculate_sdram_offsets(resizer, RSZ_A);
+ /* Overriding resize ratio calculation */
+ if (informat->code == V4L2_MBUS_FMT_UV8_1X8) {
+ param->rsz_rsc_param[RSZ_A].v_dif =
+ (((informat->height + 1) * 2) * 256) /
+ (param->rsz_rsc_param[RSZ_A].o_vsz + 1);
+ }
+ }
+
+ if (resizer->resizer_b.output != RESIZER_OUTPUT_NONE) {
+ resizer_calculate_resize_ratios(resizer, RSZ_B);
+ resizer_calculate_sdram_offsets(resizer, RSZ_B);
+ /* Overriding resize ratio calculation */
+ if (informat->code == V4L2_MBUS_FMT_UV8_1X8) {
+ param->rsz_rsc_param[RSZ_B].v_dif =
+ (((informat->height + 1) * 2) * 256) /
+ (param->rsz_rsc_param[RSZ_B].o_vsz + 1);
+ }
+ }
+ if (param->user_config.frame_div_mode_en &&
+ param->rsz_en[RSZ_A]) {
+ if (!param->rsz_rsc_param[RSZ_A].dscale_en)
+ ret = resizer_calculate_normal_f_div_param(dev,
+ informat->width,
+ param->rsz_rsc_param[RSZ_A].o_vsz + 1,
+ &param->rsz_rsc_param[RSZ_A]);
+ else
+ ret = resizer_calculate_down_scale_f_div_param(dev,
+ informat->width,
+ param->rsz_rsc_param[RSZ_A].o_vsz + 1,
+ &param->rsz_rsc_param[RSZ_A]);
+ if (ret)
+ return -EINVAL;
+ }
+ if (param->user_config.frame_div_mode_en &&
+ param->rsz_en[RSZ_B]) {
+ if (!param->rsz_rsc_param[RSZ_B].dscale_en)
+ ret = resizer_calculate_normal_f_div_param(dev,
+ informat->width,
+ param->rsz_rsc_param[RSZ_B].o_vsz + 1,
+ &param->rsz_rsc_param[RSZ_B]);
+ else
+ ret = resizer_calculate_down_scale_f_div_param(dev,
+ informat->width,
+ param->rsz_rsc_param[RSZ_B].o_vsz + 1,
+ &param->rsz_rsc_param[RSZ_B]);
+ if (ret)
+ return -EINVAL;
+ }
+ param->rsz_common.passthrough = config->bypass;
+ if (config->bypass)
+ resizer_configure_passthru(resizer, 1);
+ return 0;
+}
+
+static void
+resizer_set_defualt_configuration(struct vpfe_resizer_device *resizer)
+{
+#define WIDTH_I 640
+#define HEIGHT_I 480
+#define WIDTH_O 640
+#define HEIGHT_O 480
+ const struct resizer_params rsz_default_config = {
+ .oper_mode = RESIZER_MODE_ONE_SHOT,
+ .rsz_common = {
+ .vsz = HEIGHT_I - 1,
+ .hsz = WIDTH_I - 1,
+ .src_img_fmt = RSZ_IMG_422,
+ .raw_flip = 1, /* flip preserve Raw format */
+ .source = IPIPE_DATA,
+ .passthrough = BYPASS_OFF,
+ .yuv_y_max = 255,
+ .yuv_c_max = 255,
+ .rsz_seq_crv = DISABLE,
+ .out_chr_pos = VPFE_IPIPE_YUV422_CHR_POS_COSITE,
+ },
+ .rsz_rsc_param = {
+ {
+ .h_flip = DISABLE,
+ .v_flip = DISABLE,
+ .cen = DISABLE,
+ .yen = DISABLE,
+ .o_vsz = HEIGHT_O - 1,
+ .o_hsz = WIDTH_O - 1,
+ .v_dif = 256,
+ .v_typ_y = VPFE_RSZ_INTP_CUBIC,
+ .h_typ_c = VPFE_RSZ_INTP_CUBIC,
+ .h_dif = 256,
+ .h_typ_y = VPFE_RSZ_INTP_CUBIC,
+ .h_typ_c = VPFE_RSZ_INTP_CUBIC,
+ .h_dscale_ave_sz =
+ VPFE_IPIPE_DWN_SCALE_1_OVER_2,
+ .v_dscale_ave_sz =
+ VPFE_IPIPE_DWN_SCALE_1_OVER_2,
+ },
+ {
+ .h_flip = DISABLE,
+ .v_flip = DISABLE,
+ .cen = DISABLE,
+ .yen = DISABLE,
+ .o_vsz = HEIGHT_O - 1,
+ .o_hsz = WIDTH_O - 1,
+ .v_dif = 256,
+ .v_typ_y = VPFE_RSZ_INTP_CUBIC,
+ .h_typ_c = VPFE_RSZ_INTP_CUBIC,
+ .h_dif = 256,
+ .h_typ_y = VPFE_RSZ_INTP_CUBIC,
+ .h_typ_c = VPFE_RSZ_INTP_CUBIC,
+ .h_dscale_ave_sz =
+ VPFE_IPIPE_DWN_SCALE_1_OVER_2,
+ .v_dscale_ave_sz =
+ VPFE_IPIPE_DWN_SCALE_1_OVER_2,
+ },
+ },
+ .rsz2rgb = {
+ {
+ .rgb_en = DISABLE
+ },
+ {
+ .rgb_en = DISABLE
+ }
+ },
+ .ext_mem_param = {
+ {
+ .rsz_sdr_oft_y = WIDTH_O << 1,
+ .rsz_sdr_ptr_e_y = HEIGHT_O,
+ .rsz_sdr_oft_c = WIDTH_O,
+ .rsz_sdr_ptr_e_c = HEIGHT_O >> 1,
+ },
+ {
+ .rsz_sdr_oft_y = WIDTH_O << 1,
+ .rsz_sdr_ptr_e_y = HEIGHT_O,
+ .rsz_sdr_oft_c = WIDTH_O,
+ .rsz_sdr_ptr_e_c = HEIGHT_O,
+ },
+ },
+ .rsz_en[0] = ENABLE,
+ .rsz_en[1] = DISABLE,
+ .user_config = {
+ .output1 = {
+ .v_typ_y = VPFE_RSZ_INTP_CUBIC,
+ .v_typ_c = VPFE_RSZ_INTP_CUBIC,
+ .h_typ_y = VPFE_RSZ_INTP_CUBIC,
+ .h_typ_c = VPFE_RSZ_INTP_CUBIC,
+ .h_dscale_ave_sz =
+ VPFE_IPIPE_DWN_SCALE_1_OVER_2,
+ .v_dscale_ave_sz =
+ VPFE_IPIPE_DWN_SCALE_1_OVER_2,
+ },
+ .output2 = {
+ .v_typ_y = VPFE_RSZ_INTP_CUBIC,
+ .v_typ_c = VPFE_RSZ_INTP_CUBIC,
+ .h_typ_y = VPFE_RSZ_INTP_CUBIC,
+ .h_typ_c = VPFE_RSZ_INTP_CUBIC,
+ .h_dscale_ave_sz =
+ VPFE_IPIPE_DWN_SCALE_1_OVER_2,
+ .v_dscale_ave_sz =
+ VPFE_IPIPE_DWN_SCALE_1_OVER_2,
+ },
+ .yuv_y_max = 255,
+ .yuv_c_max = 255,
+ .out_chr_pos = VPFE_IPIPE_YUV422_CHR_POS_COSITE,
+ },
+ };
+ memset(&resizer->config, 0, sizeof(struct resizer_params));
+ memcpy(&resizer->config, &rsz_default_config,
+ sizeof(struct resizer_params));
+}
+
+/*
+ * resizer_set_configuration() - set resizer config
+ * @resizer: vpfe resizer device pointer.
+ * @chan_config: resizer channel configuration.
+ */
+static int
+resizer_set_configuration(struct vpfe_resizer_device *resizer,
+ struct vpfe_rsz_config *chan_config)
+{
+ if (!chan_config->config)
+ resizer_set_defualt_configuration(resizer);
+ else
+ if (copy_from_user(&resizer->config.user_config,
+ chan_config->config, sizeof(struct vpfe_rsz_config_params)))
+ return -EFAULT;
+
+ return 0;
+}
+
+/*
+ * resizer_get_configuration() - get resizer config
+ * @resizer: vpfe resizer device pointer.
+ * @channel: image processor logical channel.
+ * @chan_config: resizer channel configuration.
+ */
+static int
+resizer_get_configuration(struct vpfe_resizer_device *resizer,
+ struct vpfe_rsz_config *chan_config)
+{
+ struct device *dev = resizer->crop_resizer.subdev.v4l2_dev->dev;
+
+ if (!chan_config->config) {
+ dev_err(dev, "Resizer channel invalid pointer\n");
+ return -EINVAL;
+ }
+
+ if (copy_to_user((void *)chan_config->config,
+ (void *)&resizer->config.user_config,
+ sizeof(struct vpfe_rsz_config_params))) {
+ dev_err(dev, "resizer_get_configuration: Error in copy to user\n");
+ return -EFAULT;
+ }
+
+ return 0;
+}
+
+/*
+ * VPFE video operations
+ */
+
+/*
+ * resizer_a_video_out_queue() - RESIZER-A video out queue
+ * @vpfe_dev: vpfe device pointer.
+ * @addr: buffer address.
+ */
+static int resizer_a_video_out_queue(struct vpfe_device *vpfe_dev,
+ unsigned long addr)
+{
+ struct vpfe_resizer_device *resizer = &vpfe_dev->vpfe_resizer;
+
+ return resizer_set_outaddr(resizer->base_addr,
+ &resizer->config, RSZ_A, addr);
+}
+
+/*
+ * resizer_b_video_out_queue() - RESIZER-B video out queue
+ * @vpfe_dev: vpfe device pointer.
+ * @addr: buffer address.
+ */
+static int resizer_b_video_out_queue(struct vpfe_device *vpfe_dev,
+ unsigned long addr)
+{
+ struct vpfe_resizer_device *resizer = &vpfe_dev->vpfe_resizer;
+
+ return resizer_set_outaddr(resizer->base_addr,
+ &resizer->config, RSZ_B, addr);
+}
+
+static const struct vpfe_video_operations resizer_a_video_ops = {
+ .queue = resizer_a_video_out_queue,
+};
+
+static const struct vpfe_video_operations resizer_b_video_ops = {
+ .queue = resizer_b_video_out_queue,
+};
+
+static void resizer_enable(struct vpfe_resizer_device *resizer, int en)
+{
+ struct vpfe_device *vpfe_dev = to_vpfe_device(resizer);
+ u16 ipipeif_sink = vpfe_dev->vpfe_ipipeif.input;
+ unsigned char val;
+
+ if (resizer->crop_resizer.input == RESIZER_CROP_INPUT_NONE)
+ return;
+
+ if (resizer->crop_resizer.input == RESIZER_CROP_INPUT_IPIPEIF &&
+ ipipeif_sink == IPIPEIF_INPUT_MEMORY) {
+ do {
+ val = regr_rsz(resizer->base_addr, RSZ_SRC_EN);
+ } while (val);
+
+ if (resizer->resizer_a.output != RESIZER_OUTPUT_NONE) {
+ do {
+ val = regr_rsz(resizer->base_addr, RSZ_A);
+ } while (val);
+ }
+ if (resizer->resizer_b.output != RESIZER_OUTPUT_NONE) {
+ do {
+ val = regr_rsz(resizer->base_addr, RSZ_B);
+ } while (val);
+ }
+ }
+ if (resizer->resizer_a.output != RESIZER_OUTPUT_NONE)
+ rsz_enable(resizer->base_addr, RSZ_A, en);
+
+ if (resizer->resizer_b.output != RESIZER_OUTPUT_NONE)
+ rsz_enable(resizer->base_addr, RSZ_B, en);
+}
+
+
+/*
+ * resizer_ss_isr() - resizer module single-shot buffer scheduling isr
+ * @resizer: vpfe resizer device pointer.
+ */
+static void resizer_ss_isr(struct vpfe_resizer_device *resizer)
+{
+ struct vpfe_video_device *video_out = &resizer->resizer_a.video_out;
+ struct vpfe_video_device *video_out2 = &resizer->resizer_b.video_out;
+ struct vpfe_device *vpfe_dev = to_vpfe_device(resizer);
+ struct vpfe_pipeline *pipe = &video_out->pipe;
+ u16 ipipeif_sink = vpfe_dev->vpfe_ipipeif.input;
+ u32 val;
+
+ if (ipipeif_sink != IPIPEIF_INPUT_MEMORY)
+ return;
+
+ if (resizer->resizer_a.output == RESIZER_OUPUT_MEMORY) {
+ val = vpss_dma_complete_interrupt();
+ if (val != 0 && val != 2)
+ return;
+ }
+
+ if (resizer->resizer_a.output == RESIZER_OUPUT_MEMORY) {
+ spin_lock(&video_out->dma_queue_lock);
+ vpfe_video_process_buffer_complete(video_out);
+ video_out->state = VPFE_VIDEO_BUFFER_NOT_QUEUED;
+ vpfe_video_schedule_next_buffer(video_out);
+ spin_unlock(&video_out->dma_queue_lock);
+ }
+
+ /* If resizer B is enabled */
+ if (pipe->output_num > 1 && resizer->resizer_b.output ==
+ RESIZER_OUPUT_MEMORY) {
+ spin_lock(&video_out->dma_queue_lock);
+ vpfe_video_process_buffer_complete(video_out2);
+ video_out2->state = VPFE_VIDEO_BUFFER_NOT_QUEUED;
+ vpfe_video_schedule_next_buffer(video_out2);
+ spin_unlock(&video_out2->dma_queue_lock);
+ }
+
+ /* start HW if buffers are queued */
+ if (vpfe_video_is_pipe_ready(pipe) &&
+ resizer->resizer_a.output == RESIZER_OUPUT_MEMORY) {
+ resizer_enable(resizer, 1);
+ vpfe_ipipe_enable(vpfe_dev, 1);
+ vpfe_ipipeif_enable(vpfe_dev);
+ }
+}
+
+/*
+ * vpfe_resizer_buffer_isr() - resizer module buffer scheduling isr
+ * @resizer: vpfe resizer device pointer.
+ */
+void vpfe_resizer_buffer_isr(struct vpfe_resizer_device *resizer)
+{
+ struct vpfe_device *vpfe_dev = to_vpfe_device(resizer);
+ struct vpfe_video_device *video_out = &resizer->resizer_a.video_out;
+ struct vpfe_video_device *video_out2 = &resizer->resizer_b.video_out;
+ struct vpfe_pipeline *pipe = &resizer->resizer_a.video_out.pipe;
+ enum v4l2_field field;
+ int fid;
+
+ if (!video_out->started)
+ return;
+
+ if (resizer->crop_resizer.input == RESIZER_CROP_INPUT_NONE)
+ return;
+
+ field = video_out->fmt.fmt.pix.field;
+ if (field == V4L2_FIELD_NONE) {
+ /* handle progressive frame capture */
+ if (video_out->cur_frm != video_out->next_frm) {
+ vpfe_video_process_buffer_complete(video_out);
+ if (pipe->output_num > 1)
+ vpfe_video_process_buffer_complete(video_out2);
+ }
+
+ video_out->skip_frame_count--;
+ if (!video_out->skip_frame_count) {
+ video_out->skip_frame_count =
+ video_out->skip_frame_count_init;
+ rsz_src_enable(resizer->base_addr, 1);
+ } else {
+ rsz_src_enable(resizer->base_addr, 0);
+ }
+ return;
+ }
+
+ /* handle interlaced frame capture */
+ fid = vpfe_isif_get_fid(vpfe_dev);
+
+ /* switch the software maintained field id */
+ video_out->field_id ^= 1;
+ if (fid == video_out->field_id) {
+ /*
+ * we are in-sync here,continue.
+ * One frame is just being captured. If the
+ * next frame is available, release the current
+ * frame and move on
+ */
+ if (fid == 0 && video_out->cur_frm != video_out->next_frm) {
+ vpfe_video_process_buffer_complete(video_out);
+ if (pipe->output_num > 1)
+ vpfe_video_process_buffer_complete(video_out2);
+ }
+ } else if (fid == 0) {
+ /*
+ * out of sync. Recover from any hardware out-of-sync.
+ * May loose one frame
+ */
+ video_out->field_id = fid;
+ }
+}
+
+/*
+ * vpfe_resizer_dma_isr() - resizer module dma isr
+ * @resizer: vpfe resizer device pointer.
+ */
+void vpfe_resizer_dma_isr(struct vpfe_resizer_device *resizer)
+{
+ struct vpfe_video_device *video_out2 = &resizer->resizer_b.video_out;
+ struct vpfe_video_device *video_out = &resizer->resizer_a.video_out;
+ struct vpfe_device *vpfe_dev = to_vpfe_device(resizer);
+ struct vpfe_pipeline *pipe = &video_out->pipe;
+ int schedule_capture = 0;
+ enum v4l2_field field;
+ int fid;
+
+ if (!video_out->started)
+ return;
+
+ if (pipe->state == VPFE_PIPELINE_STREAM_SINGLESHOT) {
+ resizer_ss_isr(resizer);
+ return;
+ }
+
+ field = video_out->fmt.fmt.pix.field;
+ if (field == V4L2_FIELD_NONE) {
+ if (!list_empty(&video_out->dma_queue) &&
+ video_out->cur_frm == video_out->next_frm)
+ schedule_capture = 1;
+ } else {
+ fid = vpfe_isif_get_fid(vpfe_dev);
+ if (fid == video_out->field_id) {
+ /* we are in-sync here,continue */
+ if (fid == 1 && !list_empty(&video_out->dma_queue) &&
+ video_out->cur_frm == video_out->next_frm)
+ schedule_capture = 1;
+ }
+ }
+
+ if (!schedule_capture)
+ return;
+
+ spin_lock(&video_out->dma_queue_lock);
+ vpfe_video_schedule_next_buffer(video_out);
+ spin_unlock(&video_out->dma_queue_lock);
+ if (pipe->output_num > 1) {
+ spin_lock(&video_out2->dma_queue_lock);
+ vpfe_video_schedule_next_buffer(video_out2);
+ spin_unlock(&video_out2->dma_queue_lock);
+ }
+}
+
+/*
+ * V4L2 subdev operations
+ */
+
+/*
+ * resizer_ioctl() - Handle resizer module private ioctl's
+ * @sd: pointer to v4l2 subdev structure
+ * @cmd: configuration command
+ * @arg: configuration argument
+ */
+static long resizer_ioctl(struct v4l2_subdev *sd, unsigned int cmd, void *arg)
+{
+ struct vpfe_resizer_device *resizer = v4l2_get_subdevdata(sd);
+ struct device *dev = resizer->crop_resizer.subdev.v4l2_dev->dev;
+ struct vpfe_rsz_config *user_config;
+ int ret = -ENOIOCTLCMD;
+
+ if (&resizer->crop_resizer.subdev != sd)
+ return ret;
+
+ switch (cmd) {
+ case VIDIOC_VPFE_RSZ_S_CONFIG:
+ user_config = (struct vpfe_rsz_config *)arg;
+ ret = resizer_set_configuration(resizer, user_config);
+ break;
+
+ case VIDIOC_VPFE_RSZ_G_CONFIG:
+ user_config = (struct vpfe_rsz_config *)arg;
+ if (!user_config->config) {
+ dev_err(dev, "error in VIDIOC_VPFE_RSZ_G_CONFIG\n");
+ return -EINVAL;
+ }
+ ret = resizer_get_configuration(resizer, user_config);
+ break;
+ }
+ return ret;
+}
+
+static int resizer_do_hw_setup(struct vpfe_resizer_device *resizer)
+{
+ struct vpfe_device *vpfe_dev = to_vpfe_device(resizer);
+ u16 ipipeif_sink = vpfe_dev->vpfe_ipipeif.input;
+ u16 ipipeif_source = vpfe_dev->vpfe_ipipeif.output;
+ struct resizer_params *param = &resizer->config;
+ int ret = 0;
+
+ if (resizer->resizer_a.output == RESIZER_OUPUT_MEMORY ||
+ resizer->resizer_b.output == RESIZER_OUPUT_MEMORY) {
+ if (ipipeif_sink == IPIPEIF_INPUT_MEMORY &&
+ ipipeif_source == IPIPEIF_OUTPUT_RESIZER)
+ ret = resizer_configure_in_single_shot_mode(resizer);
+ else
+ ret = resizer_configure_in_continious_mode(resizer);
+ if (ret)
+ return ret;
+ ret = config_rsz_hw(resizer, param);
+ }
+ return ret;
+}
+
+/*
+ * resizer_set_stream() - Enable/Disable streaming on resizer subdev
+ * @sd: pointer to v4l2 subdev structure
+ * @enable: 1 == Enable, 0 == Disable
+ */
+static int resizer_set_stream(struct v4l2_subdev *sd, int enable)
+{
+ struct vpfe_resizer_device *resizer = v4l2_get_subdevdata(sd);
+
+ if (&resizer->crop_resizer.subdev != sd)
+ return 0;
+
+ if (resizer->resizer_a.output != RESIZER_OUPUT_MEMORY)
+ return 0;
+
+ switch (enable) {
+ case 1:
+ if (resizer_do_hw_setup(resizer) < 0)
+ return -EINVAL;
+ resizer_enable(resizer, enable);
+ break;
+
+ case 0:
+ resizer_enable(resizer, enable);
+ break;
+ }
+
+ return 0;
+}
+
+/*
+ * __resizer_get_format() - helper function for getting resizer format
+ * @sd: pointer to subdev.
+ * @fh: V4L2 subdev file handle.
+ * @pad: pad number.
+ * @which: wanted subdev format.
+ * Retun wanted mbus frame format.
+ */
+static struct v4l2_mbus_framefmt *
+__resizer_get_format(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh,
+ unsigned int pad, enum v4l2_subdev_format_whence which)
+{
+ struct vpfe_resizer_device *resizer = v4l2_get_subdevdata(sd);
+
+ if (which == V4L2_SUBDEV_FORMAT_TRY)
+ return v4l2_subdev_get_try_format(fh, pad);
+ if (&resizer->crop_resizer.subdev == sd)
+ return &resizer->crop_resizer.formats[pad];
+ if (&resizer->resizer_a.subdev == sd)
+ return &resizer->resizer_a.formats[pad];
+ if (&resizer->resizer_b.subdev == sd)
+ return &resizer->resizer_b.formats[pad];
+ return NULL;
+}
+
+/*
+ * resizer_try_format() - Handle try format by pad subdev method
+ * @sd: pointer to subdev.
+ * @fh: V4L2 subdev file handle.
+ * @pad: pad num.
+ * @fmt: pointer to v4l2 format structure.
+ * @which: wanted subdev format.
+ */
+static void
+resizer_try_format(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh,
+ unsigned int pad, struct v4l2_mbus_framefmt *fmt,
+ enum v4l2_subdev_format_whence which)
+{
+ struct vpfe_resizer_device *resizer = v4l2_get_subdevdata(sd);
+ unsigned int max_out_height;
+ unsigned int max_out_width;
+ unsigned int i;
+
+ if ((&resizer->resizer_a.subdev == sd && pad == RESIZER_PAD_SINK) ||
+ (&resizer->resizer_b.subdev == sd && pad == RESIZER_PAD_SINK) ||
+ (&resizer->crop_resizer.subdev == sd &&
+ (pad == RESIZER_CROP_PAD_SOURCE ||
+ pad == RESIZER_CROP_PAD_SOURCE2 || pad == RESIZER_CROP_PAD_SINK))) {
+ for (i = 0; i < ARRAY_SIZE(resizer_input_formats); i++) {
+ if (fmt->code == resizer_input_formats[i])
+ break;
+ }
+ /* If not found, use UYVY as default */
+ if (i >= ARRAY_SIZE(resizer_input_formats))
+ fmt->code = V4L2_MBUS_FMT_UYVY8_2X8;
+
+ fmt->width = clamp_t(u32, fmt->width, MIN_IN_WIDTH,
+ MAX_IN_WIDTH);
+ fmt->height = clamp_t(u32, fmt->height, MIN_IN_HEIGHT,
+ MAX_IN_HEIGHT);
+ } else if (&resizer->resizer_a.subdev == sd &&
+ pad == RESIZER_PAD_SOURCE) {
+ max_out_width = IPIPE_MAX_OUTPUT_WIDTH_A;
+ max_out_height = IPIPE_MAX_OUTPUT_HEIGHT_A;
+
+ for (i = 0; i < ARRAY_SIZE(resizer_output_formats); i++) {
+ if (fmt->code == resizer_output_formats[i])
+ break;
+ }
+ /* If not found, use UYVY as default */
+ if (i >= ARRAY_SIZE(resizer_output_formats))
+ fmt->code = V4L2_MBUS_FMT_UYVY8_2X8;
+
+ fmt->width = clamp_t(u32, fmt->width, MIN_OUT_WIDTH,
+ max_out_width);
+ fmt->width &= ~15;
+ fmt->height = clamp_t(u32, fmt->height, MIN_OUT_HEIGHT,
+ max_out_height);
+ } else if (&resizer->resizer_b.subdev == sd &&
+ pad == RESIZER_PAD_SOURCE) {
+ max_out_width = IPIPE_MAX_OUTPUT_WIDTH_B;
+ max_out_height = IPIPE_MAX_OUTPUT_HEIGHT_B;
+
+ for (i = 0; i < ARRAY_SIZE(resizer_output_formats); i++) {
+ if (fmt->code == resizer_output_formats[i])
+ break;
+ }
+ /* If not found, use UYVY as default */
+ if (i >= ARRAY_SIZE(resizer_output_formats))
+ fmt->code = V4L2_MBUS_FMT_UYVY8_2X8;
+
+ fmt->width = clamp_t(u32, fmt->width, MIN_OUT_WIDTH,
+ max_out_width);
+ fmt->width &= ~15;
+ fmt->height = clamp_t(u32, fmt->height, MIN_OUT_HEIGHT,
+ max_out_height);
+ }
+}
+
+/*
+ * resizer_set_format() - Handle set format by pads subdev method
+ * @sd: pointer to v4l2 subdev structure
+ * @fh: V4L2 subdev file handle
+ * @fmt: pointer to v4l2 subdev format structure
+ * return -EINVAL or zero on success
+ */
+static int resizer_set_format(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh,
+ struct v4l2_subdev_format *fmt)
+{
+ struct vpfe_resizer_device *resizer = v4l2_get_subdevdata(sd);
+ struct v4l2_mbus_framefmt *format;
+
+ format = __resizer_get_format(sd, fh, fmt->pad, fmt->which);
+ if (format == NULL)
+ return -EINVAL;
+
+ resizer_try_format(sd, fh, fmt->pad, &fmt->format, fmt->which);
+ *format = fmt->format;
+
+ if (fmt->which == V4L2_SUBDEV_FORMAT_TRY)
+ return 0;
+
+ if (&resizer->crop_resizer.subdev == sd) {
+ if (fmt->pad == RESIZER_CROP_PAD_SINK) {
+ resizer->crop_resizer.formats[fmt->pad] = fmt->format;
+ } else if (fmt->pad == RESIZER_CROP_PAD_SOURCE &&
+ resizer->crop_resizer.output == RESIZER_A) {
+ resizer->crop_resizer.formats[fmt->pad] = fmt->format;
+ resizer->crop_resizer.
+ formats[RESIZER_CROP_PAD_SOURCE2] = fmt->format;
+ } else if (fmt->pad == RESIZER_CROP_PAD_SOURCE2 &&
+ resizer->crop_resizer.output2 == RESIZER_B) {
+ resizer->crop_resizer.formats[fmt->pad] = fmt->format;
+ resizer->crop_resizer.
+ formats[RESIZER_CROP_PAD_SOURCE] = fmt->format;
+ } else {
+ return -EINVAL;
+ }
+ } else if (&resizer->resizer_a.subdev == sd) {
+ if (fmt->pad == RESIZER_PAD_SINK)
+ resizer->resizer_a.formats[fmt->pad] = fmt->format;
+ else if (fmt->pad == RESIZER_PAD_SOURCE)
+ resizer->resizer_a.formats[fmt->pad] = fmt->format;
+ else
+ return -EINVAL;
+ } else if (&resizer->resizer_b.subdev == sd) {
+ if (fmt->pad == RESIZER_PAD_SINK)
+ resizer->resizer_b.formats[fmt->pad] = fmt->format;
+ else if (fmt->pad == RESIZER_PAD_SOURCE)
+ resizer->resizer_b.formats[fmt->pad] = fmt->format;
+ else
+ return -EINVAL;
+ } else {
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/*
+ * resizer_get_format() - Retrieve the video format on a pad
+ * @sd: pointer to v4l2 subdev structure.
+ * @fh: V4L2 subdev file handle.
+ * @fmt: pointer to v4l2 subdev format structure
+ * return -EINVAL or zero on success
+ */
+static int resizer_get_format(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh,
+ struct v4l2_subdev_format *fmt)
+{
+ struct v4l2_mbus_framefmt *format;
+
+ format = __resizer_get_format(sd, fh, fmt->pad, fmt->which);
+ if (format == NULL)
+ return -EINVAL;
+
+ fmt->format = *format;
+
+ return 0;
+}
+
+/*
+ * resizer_enum_frame_size() - enum frame sizes on pads
+ * @sd: Pointer to subdevice.
+ * @fh: V4L2 subdev file handle.
+ * @code: pointer to v4l2_subdev_frame_size_enum structure.
+ */
+static int resizer_enum_frame_size(struct v4l2_subdev *sd,
+ struct v4l2_subdev_fh *fh,
+ struct v4l2_subdev_frame_size_enum *fse)
+{
+ struct v4l2_mbus_framefmt format;
+
+ if (fse->index != 0)
+ return -EINVAL;
+
+ format.code = fse->code;
+ format.width = 1;
+ format.height = 1;
+ resizer_try_format(sd, fh, fse->pad, &format,
+ V4L2_SUBDEV_FORMAT_TRY);
+ fse->min_width = format.width;
+ fse->min_height = format.height;
+
+ if (format.code != fse->code)
+ return -EINVAL;
+
+ format.code = fse->code;
+ format.width = -1;
+ format.height = -1;
+ resizer_try_format(sd, fh, fse->pad, &format,
+ V4L2_SUBDEV_FORMAT_TRY);
+ fse->max_width = format.width;
+ fse->max_height = format.height;
+
+ return 0;
+}
+
+/*
+ * resizer_enum_mbus_code() - enum mbus codes for pads
+ * @sd: Pointer to subdevice.
+ * @fh: V4L2 subdev file handle
+ * @code: pointer to v4l2_subdev_mbus_code_enum structure
+ */
+static int resizer_enum_mbus_code(struct v4l2_subdev *sd,
+ struct v4l2_subdev_fh *fh,
+ struct v4l2_subdev_mbus_code_enum *code)
+{
+ if (code->pad == RESIZER_PAD_SINK) {
+ if (code->index >= ARRAY_SIZE(resizer_input_formats))
+ return -EINVAL;
+
+ code->code = resizer_input_formats[code->index];
+ } else if (code->pad == RESIZER_PAD_SOURCE) {
+ if (code->index >= ARRAY_SIZE(resizer_output_formats))
+ return -EINVAL;
+
+ code->code = resizer_output_formats[code->index];
+ }
+
+ return 0;
+}
+
+/*
+ * resizer_init_formats() - Initialize formats on all pads
+ * @sd: Pointer to subdevice.
+ * @fh: V4L2 subdev file handle.
+ *
+ * Initialize all pad formats with default values. If fh is not NULL, try
+ * formats are initialized on the file handle. Otherwise active formats are
+ * initialized on the device.
+ */
+static int resizer_init_formats(struct v4l2_subdev *sd,
+ struct v4l2_subdev_fh *fh)
+{
+ __u32 which = fh ? V4L2_SUBDEV_FORMAT_TRY : V4L2_SUBDEV_FORMAT_ACTIVE;
+ struct vpfe_resizer_device *resizer = v4l2_get_subdevdata(sd);
+ struct v4l2_subdev_format format;
+
+ if (&resizer->crop_resizer.subdev == sd) {
+ memset(&format, 0, sizeof(format));
+ format.pad = RESIZER_CROP_PAD_SINK;
+ format.which = which;
+ format.format.code = V4L2_MBUS_FMT_YUYV8_2X8;
+ format.format.width = MAX_IN_WIDTH;
+ format.format.height = MAX_IN_HEIGHT;
+ resizer_set_format(sd, fh, &format);
+
+ memset(&format, 0, sizeof(format));
+ format.pad = RESIZER_CROP_PAD_SOURCE;
+ format.which = which;
+ format.format.code = V4L2_MBUS_FMT_UYVY8_2X8;
+ format.format.width = MAX_IN_WIDTH;
+ format.format.height = MAX_IN_WIDTH;
+ resizer_set_format(sd, fh, &format);
+
+ memset(&format, 0, sizeof(format));
+ format.pad = RESIZER_CROP_PAD_SOURCE2;
+ format.which = which;
+ format.format.code = V4L2_MBUS_FMT_UYVY8_2X8;
+ format.format.width = MAX_IN_WIDTH;
+ format.format.height = MAX_IN_WIDTH;
+ resizer_set_format(sd, fh, &format);
+ } else if (&resizer->resizer_a.subdev == sd) {
+ memset(&format, 0, sizeof(format));
+ format.pad = RESIZER_PAD_SINK;
+ format.which = which;
+ format.format.code = V4L2_MBUS_FMT_YUYV8_2X8;
+ format.format.width = MAX_IN_WIDTH;
+ format.format.height = MAX_IN_HEIGHT;
+ resizer_set_format(sd, fh, &format);
+
+ memset(&format, 0, sizeof(format));
+ format.pad = RESIZER_PAD_SOURCE;
+ format.which = which;
+ format.format.code = V4L2_MBUS_FMT_UYVY8_2X8;
+ format.format.width = IPIPE_MAX_OUTPUT_WIDTH_A;
+ format.format.height = IPIPE_MAX_OUTPUT_HEIGHT_A;
+ resizer_set_format(sd, fh, &format);
+ } else if (&resizer->resizer_b.subdev == sd) {
+ memset(&format, 0, sizeof(format));
+ format.pad = RESIZER_PAD_SINK;
+ format.which = which;
+ format.format.code = V4L2_MBUS_FMT_YUYV8_2X8;
+ format.format.width = MAX_IN_WIDTH;
+ format.format.height = MAX_IN_HEIGHT;
+ resizer_set_format(sd, fh, &format);
+
+ memset(&format, 0, sizeof(format));
+ format.pad = RESIZER_PAD_SOURCE;
+ format.which = which;
+ format.format.code = V4L2_MBUS_FMT_UYVY8_2X8;
+ format.format.width = IPIPE_MAX_OUTPUT_WIDTH_B;
+ format.format.height = IPIPE_MAX_OUTPUT_HEIGHT_B;
+ resizer_set_format(sd, fh, &format);
+ }
+
+ return 0;
+}
+
+/* subdev core operations */
+static const struct v4l2_subdev_core_ops resizer_v4l2_core_ops = {
+ .ioctl = resizer_ioctl,
+};
+
+/* subdev internal operations */
+static const struct v4l2_subdev_internal_ops resizer_v4l2_internal_ops = {
+ .open = resizer_init_formats,
+};
+
+/* subdev video operations */
+static const struct v4l2_subdev_video_ops resizer_v4l2_video_ops = {
+ .s_stream = resizer_set_stream,
+};
+
+/* subdev pad operations */
+static const struct v4l2_subdev_pad_ops resizer_v4l2_pad_ops = {
+ .enum_mbus_code = resizer_enum_mbus_code,
+ .enum_frame_size = resizer_enum_frame_size,
+ .get_fmt = resizer_get_format,
+ .set_fmt = resizer_set_format,
+};
+
+/* subdev operations */
+static const struct v4l2_subdev_ops resizer_v4l2_ops = {
+ .core = &resizer_v4l2_core_ops,
+ .video = &resizer_v4l2_video_ops,
+ .pad = &resizer_v4l2_pad_ops,
+};
+
+/*
+ * Media entity operations
+ */
+
+/*
+ * resizer_link_setup() - Setup resizer connections
+ * @entity: Pointer to media entity structure
+ * @local: Pointer to local pad array
+ * @remote: Pointer to remote pad array
+ * @flags: Link flags
+ * return -EINVAL or zero on success
+ */
+static int resizer_link_setup(struct media_entity *entity,
+ const struct media_pad *local,
+ const struct media_pad *remote, u32 flags)
+{
+ struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(entity);
+ struct vpfe_resizer_device *resizer = v4l2_get_subdevdata(sd);
+ struct vpfe_device *vpfe_dev = to_vpfe_device(resizer);
+ u16 ipipeif_source = vpfe_dev->vpfe_ipipeif.output;
+ u16 ipipe_source = vpfe_dev->vpfe_ipipe.output;
+
+ if (&resizer->crop_resizer.subdev == sd) {
+ switch (local->index | media_entity_type(remote->entity)) {
+ case RESIZER_CROP_PAD_SINK | MEDIA_ENT_T_V4L2_SUBDEV:
+ if (!(flags & MEDIA_LNK_FL_ENABLED)) {
+ resizer->crop_resizer.input =
+ RESIZER_CROP_INPUT_NONE;
+ break;
+ }
+
+ if (resizer->crop_resizer.input !=
+ RESIZER_CROP_INPUT_NONE)
+ return -EBUSY;
+ if (ipipeif_source == IPIPEIF_OUTPUT_RESIZER)
+ resizer->crop_resizer.input =
+ RESIZER_CROP_INPUT_IPIPEIF;
+ else if (ipipe_source == IPIPE_OUTPUT_RESIZER)
+ resizer->crop_resizer.input =
+ RESIZER_CROP_INPUT_IPIPE;
+ else
+ return -EINVAL;
+ break;
+
+ case RESIZER_CROP_PAD_SOURCE | MEDIA_ENT_T_V4L2_SUBDEV:
+ if (!(flags & MEDIA_LNK_FL_ENABLED)) {
+ resizer->crop_resizer.output =
+ RESIZER_CROP_OUTPUT_NONE;
+ break;
+ }
+ if (resizer->crop_resizer.output !=
+ RESIZER_CROP_OUTPUT_NONE)
+ return -EBUSY;
+ resizer->crop_resizer.output = RESIZER_A;
+ break;
+
+ case RESIZER_CROP_PAD_SOURCE2 | MEDIA_ENT_T_V4L2_SUBDEV:
+ if (!(flags & MEDIA_LNK_FL_ENABLED)) {
+ resizer->crop_resizer.output2 =
+ RESIZER_CROP_OUTPUT_NONE;
+ break;
+ }
+ if (resizer->crop_resizer.output2 !=
+ RESIZER_CROP_OUTPUT_NONE)
+ return -EBUSY;
+ resizer->crop_resizer.output2 = RESIZER_B;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+ } else if (&resizer->resizer_a.subdev == sd) {
+ switch (local->index | media_entity_type(remote->entity)) {
+ case RESIZER_PAD_SINK | MEDIA_ENT_T_V4L2_SUBDEV:
+ if (!(flags & MEDIA_LNK_FL_ENABLED)) {
+ resizer->resizer_a.input = RESIZER_INPUT_NONE;
+ break;
+ }
+ if (resizer->resizer_a.input != RESIZER_INPUT_NONE)
+ return -EBUSY;
+ resizer->resizer_a.input = RESIZER_INPUT_CROP_RESIZER;
+ break;
+
+ case RESIZER_PAD_SOURCE | MEDIA_ENT_T_DEVNODE:
+ if (!(flags & MEDIA_LNK_FL_ENABLED)) {
+ resizer->resizer_a.output = RESIZER_OUTPUT_NONE;
+ break;
+ }
+ if (resizer->resizer_a.output != RESIZER_OUTPUT_NONE)
+ return -EBUSY;
+ resizer->resizer_a.output = RESIZER_OUPUT_MEMORY;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+ } else if (&resizer->resizer_b.subdev == sd) {
+ switch (local->index | media_entity_type(remote->entity)) {
+ case RESIZER_PAD_SINK | MEDIA_ENT_T_V4L2_SUBDEV:
+ if (!(flags & MEDIA_LNK_FL_ENABLED)) {
+ resizer->resizer_b.input = RESIZER_INPUT_NONE;
+ break;
+ }
+ if (resizer->resizer_b.input != RESIZER_INPUT_NONE)
+ return -EBUSY;
+ resizer->resizer_b.input = RESIZER_INPUT_CROP_RESIZER;
+ break;
+
+ case RESIZER_PAD_SOURCE | MEDIA_ENT_T_DEVNODE:
+ if (!(flags & MEDIA_LNK_FL_ENABLED)) {
+ resizer->resizer_b.output = RESIZER_OUTPUT_NONE;
+ break;
+ }
+ if (resizer->resizer_b.output != RESIZER_OUTPUT_NONE)
+ return -EBUSY;
+ resizer->resizer_b.output = RESIZER_OUPUT_MEMORY;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+ } else {
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static const struct media_entity_operations resizer_media_ops = {
+ .link_setup = resizer_link_setup,
+};
+
+/*
+ * vpfe_resizer_unregister_entities() - Unregister entity
+ * @vpfe_rsz - pointer to resizer subdevice structure.
+ */
+void vpfe_resizer_unregister_entities(struct vpfe_resizer_device *vpfe_rsz)
+{
+ /* unregister video devices */
+ vpfe_video_unregister(&vpfe_rsz->resizer_a.video_out);
+ vpfe_video_unregister(&vpfe_rsz->resizer_b.video_out);
+
+ /* unregister subdev */
+ v4l2_device_unregister_subdev(&vpfe_rsz->crop_resizer.subdev);
+ v4l2_device_unregister_subdev(&vpfe_rsz->resizer_a.subdev);
+ v4l2_device_unregister_subdev(&vpfe_rsz->resizer_b.subdev);
+ /* cleanup entity */
+ media_entity_cleanup(&vpfe_rsz->crop_resizer.subdev.entity);
+ media_entity_cleanup(&vpfe_rsz->resizer_a.subdev.entity);
+ media_entity_cleanup(&vpfe_rsz->resizer_b.subdev.entity);
+}
+
+/*
+ * vpfe_resizer_register_entities() - Register entity
+ * @resizer - pointer to resizer devive.
+ * @vdev: pointer to v4l2 device structure.
+ */
+int vpfe_resizer_register_entities(struct vpfe_resizer_device *resizer,
+ struct v4l2_device *vdev)
+{
+ struct vpfe_device *vpfe_dev = to_vpfe_device(resizer);
+ unsigned int flags = 0;
+ int ret;
+
+ /* Register the crop resizer subdev */
+ ret = v4l2_device_register_subdev(vdev, &resizer->crop_resizer.subdev);
+ if (ret < 0) {
+ pr_err("Failed to register crop resizer as v4l2-subdev\n");
+ return ret;
+ }
+ /* Register Resizer-A subdev */
+ ret = v4l2_device_register_subdev(vdev, &resizer->resizer_a.subdev);
+ if (ret < 0) {
+ pr_err("Failed to register resizer-a as v4l2-subdev\n");
+ return ret;
+ }
+ /* Register Resizer-B subdev */
+ ret = v4l2_device_register_subdev(vdev, &resizer->resizer_b.subdev);
+ if (ret < 0) {
+ pr_err("Failed to register resizer-b as v4l2-subdev\n");
+ return ret;
+ }
+ /* Register video-out device for resizer-a */
+ ret = vpfe_video_register(&resizer->resizer_a.video_out, vdev);
+ if (ret) {
+ pr_err("Failed to register RSZ-A video-out device\n");
+ goto out_video_out2_register;
+ }
+ resizer->resizer_a.video_out.vpfe_dev = vpfe_dev;
+
+ /* Register video-out device for resizer-b */
+ ret = vpfe_video_register(&resizer->resizer_b.video_out, vdev);
+ if (ret) {
+ pr_err("Failed to register RSZ-B video-out device\n");
+ goto out_video_out2_register;
+ }
+ resizer->resizer_b.video_out.vpfe_dev = vpfe_dev;
+
+ /* create link between Resizer Crop----> Resizer A*/
+ ret = media_entity_create_link(&resizer->crop_resizer.subdev.entity, 1,
+ &resizer->resizer_a.subdev.entity,
+ 0, flags);
+ if (ret < 0)
+ goto out_create_link;
+
+ /* create link between Resizer Crop----> Resizer B*/
+ ret = media_entity_create_link(&resizer->crop_resizer.subdev.entity, 2,
+ &resizer->resizer_b.subdev.entity,
+ 0, flags);
+ if (ret < 0)
+ goto out_create_link;
+
+ /* create link between Resizer A ----> video out */
+ ret = media_entity_create_link(&resizer->resizer_a.subdev.entity, 1,
+ &resizer->resizer_a.video_out.video_dev.entity, 0, flags);
+ if (ret < 0)
+ goto out_create_link;
+
+ /* create link between Resizer B ----> video out */
+ ret = media_entity_create_link(&resizer->resizer_b.subdev.entity, 1,
+ &resizer->resizer_b.video_out.video_dev.entity, 0, flags);
+ if (ret < 0)
+ goto out_create_link;
+
+ return 0;
+
+out_create_link:
+ vpfe_video_unregister(&resizer->resizer_b.video_out);
+out_video_out2_register:
+ vpfe_video_unregister(&resizer->resizer_a.video_out);
+ v4l2_device_unregister_subdev(&resizer->crop_resizer.subdev);
+ v4l2_device_unregister_subdev(&resizer->resizer_a.subdev);
+ v4l2_device_unregister_subdev(&resizer->resizer_b.subdev);
+ media_entity_cleanup(&resizer->crop_resizer.subdev.entity);
+ media_entity_cleanup(&resizer->resizer_a.subdev.entity);
+ media_entity_cleanup(&resizer->resizer_b.subdev.entity);
+ return ret;
+}
+
+/*
+ * vpfe_resizer_init() - resizer device initialization.
+ * @vpfe_rsz - pointer to resizer device
+ * @pdev: platform device pointer.
+ */
+int vpfe_resizer_init(struct vpfe_resizer_device *vpfe_rsz,
+ struct platform_device *pdev)
+{
+ struct v4l2_subdev *sd = &vpfe_rsz->crop_resizer.subdev;
+ struct media_pad *pads = &vpfe_rsz->crop_resizer.pads[0];
+ struct media_entity *me = &sd->entity;
+ static resource_size_t res_len;
+ struct resource *res;
+ int ret;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 5);
+ if (!res)
+ return -ENOENT;
+
+ res_len = resource_size(res);
+ res = request_mem_region(res->start, res_len, res->name);
+ if (!res)
+ return -EBUSY;
+
+ vpfe_rsz->base_addr = ioremap_nocache(res->start, res_len);
+ if (!vpfe_rsz->base_addr)
+ return -EBUSY;
+
+ v4l2_subdev_init(sd, &resizer_v4l2_ops);
+ sd->internal_ops = &resizer_v4l2_internal_ops;
+ strlcpy(sd->name, "DAVINCI RESIZER CROP", sizeof(sd->name));
+ sd->grp_id = 1 << 16; /* group ID for davinci subdevs */
+ v4l2_set_subdevdata(sd, vpfe_rsz);
+ sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
+
+ pads[RESIZER_CROP_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
+ pads[RESIZER_CROP_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
+ pads[RESIZER_CROP_PAD_SOURCE2].flags = MEDIA_PAD_FL_SOURCE;
+
+ vpfe_rsz->crop_resizer.input = RESIZER_CROP_INPUT_NONE;
+ vpfe_rsz->crop_resizer.output = RESIZER_CROP_OUTPUT_NONE;
+ vpfe_rsz->crop_resizer.output2 = RESIZER_CROP_OUTPUT_NONE;
+ vpfe_rsz->crop_resizer.rsz_device = vpfe_rsz;
+ me->ops = &resizer_media_ops;
+ ret = media_entity_init(me, RESIZER_CROP_PADS_NUM, pads, 0);
+ if (ret)
+ return ret;
+
+ sd = &vpfe_rsz->resizer_a.subdev;
+ pads = &vpfe_rsz->resizer_a.pads[0];
+ me = &sd->entity;
+
+ v4l2_subdev_init(sd, &resizer_v4l2_ops);
+ sd->internal_ops = &resizer_v4l2_internal_ops;
+ strlcpy(sd->name, "DAVINCI RESIZER A", sizeof(sd->name));
+ sd->grp_id = 1 << 16; /* group ID for davinci subdevs */
+ v4l2_set_subdevdata(sd, vpfe_rsz);
+ sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
+
+ pads[RESIZER_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
+ pads[RESIZER_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
+
+ vpfe_rsz->resizer_a.input = RESIZER_INPUT_NONE;
+ vpfe_rsz->resizer_a.output = RESIZER_OUTPUT_NONE;
+ vpfe_rsz->resizer_a.rsz_device = vpfe_rsz;
+ me->ops = &resizer_media_ops;
+ ret = media_entity_init(me, RESIZER_PADS_NUM, pads, 0);
+ if (ret)
+ return ret;
+
+ sd = &vpfe_rsz->resizer_b.subdev;
+ pads = &vpfe_rsz->resizer_b.pads[0];
+ me = &sd->entity;
+
+ v4l2_subdev_init(sd, &resizer_v4l2_ops);
+ sd->internal_ops = &resizer_v4l2_internal_ops;
+ strlcpy(sd->name, "DAVINCI RESIZER B", sizeof(sd->name));
+ sd->grp_id = 1 << 16; /* group ID for davinci subdevs */
+ v4l2_set_subdevdata(sd, vpfe_rsz);
+ sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
+
+ pads[RESIZER_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
+ pads[RESIZER_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
+
+ vpfe_rsz->resizer_b.input = RESIZER_INPUT_NONE;
+ vpfe_rsz->resizer_b.output = RESIZER_OUTPUT_NONE;
+ vpfe_rsz->resizer_b.rsz_device = vpfe_rsz;
+ me->ops = &resizer_media_ops;
+ ret = media_entity_init(me, RESIZER_PADS_NUM, pads, 0);
+ if (ret)
+ return ret;
+
+ vpfe_rsz->resizer_a.video_out.ops = &resizer_a_video_ops;
+ vpfe_rsz->resizer_a.video_out.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ ret = vpfe_video_init(&vpfe_rsz->resizer_a.video_out, "RSZ-A");
+ if (ret) {
+ pr_err("Failed to init RSZ video-out device\n");
+ return ret;
+ }
+ vpfe_rsz->resizer_b.video_out.ops = &resizer_b_video_ops;
+ vpfe_rsz->resizer_b.video_out.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ ret = vpfe_video_init(&vpfe_rsz->resizer_b.video_out, "RSZ-B");
+ if (ret) {
+ pr_err("Failed to init RSZ video-out2 device\n");
+ return ret;
+ }
+ memset(&vpfe_rsz->config, 0, sizeof(struct resizer_params));
+
+ return 0;
+}
+
+void
+vpfe_resizer_cleanup(struct vpfe_resizer_device *vpfe_rsz,
+ struct platform_device *pdev)
+{
+ struct resource *res;
+
+ iounmap(vpfe_rsz->base_addr);
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 5);
+ if (res)
+ release_mem_region(res->start,
+ resource_size(res));
+}
diff --git a/drivers/staging/media/davinci_vpfe/dm365_resizer.h b/drivers/staging/media/davinci_vpfe/dm365_resizer.h
new file mode 100644
index 00000000000..59a79422b91
--- /dev/null
+++ b/drivers/staging/media/davinci_vpfe/dm365_resizer.h
@@ -0,0 +1,244 @@
+/*
+ * Copyright (C) 2012 Texas Instruments Inc
+ *
+ * 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 version 2.
+ *
+ * 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
+ *
+ * Contributors:
+ * Manjunath Hadli <manjunath.hadli@ti.com>
+ * Prabhakar Lad <prabhakar.lad@ti.com>
+ */
+
+#ifndef _DAVINCI_VPFE_DM365_RESIZER_H
+#define _DAVINCI_VPFE_DM365_RESIZER_H
+
+enum resizer_oper_mode {
+ RESIZER_MODE_CONTINIOUS = 0,
+ RESIZER_MODE_ONE_SHOT = 1,
+};
+
+struct f_div_pass {
+ unsigned int o_hsz;
+ unsigned int i_hps;
+ unsigned int h_phs;
+ unsigned int src_hps;
+ unsigned int src_hsz;
+};
+
+#define MAX_PASSES 2
+
+struct f_div_param {
+ unsigned char en;
+ unsigned int num_passes;
+ struct f_div_pass pass[MAX_PASSES];
+};
+
+/* Resizer Rescale Parameters*/
+struct resizer_scale_param {
+ bool h_flip;
+ bool v_flip;
+ bool cen;
+ bool yen;
+ unsigned short i_vps;
+ unsigned short i_hps;
+ unsigned short o_vsz;
+ unsigned short o_hsz;
+ unsigned short v_phs_y;
+ unsigned short v_phs_c;
+ unsigned short v_dif;
+ /* resize method - Luminance */
+ enum vpfe_rsz_intp_t v_typ_y;
+ /* resize method - Chrominance */
+ enum vpfe_rsz_intp_t v_typ_c;
+ /* vertical lpf intensity - Luminance */
+ unsigned char v_lpf_int_y;
+ /* vertical lpf intensity - Chrominance */
+ unsigned char v_lpf_int_c;
+ unsigned short h_phs;
+ unsigned short h_dif;
+ /* resize method - Luminance */
+ enum vpfe_rsz_intp_t h_typ_y;
+ /* resize method - Chrominance */
+ enum vpfe_rsz_intp_t h_typ_c;
+ /* horizontal lpf intensity - Luminance */
+ unsigned char h_lpf_int_y;
+ /* horizontal lpf intensity - Chrominance */
+ unsigned char h_lpf_int_c;
+ bool dscale_en;
+ enum vpfe_rsz_down_scale_ave_sz h_dscale_ave_sz;
+ enum vpfe_rsz_down_scale_ave_sz v_dscale_ave_sz;
+ /* store the calculated frame division parameter */
+ struct f_div_param f_div;
+};
+
+enum resizer_rgb_t {
+ OUTPUT_32BIT,
+ OUTPUT_16BIT
+};
+
+enum resizer_rgb_msk_t {
+ NOMASK = 0,
+ MASKLAST2 = 1,
+};
+
+/* Resizer RGB Conversion Parameters */
+struct resizer_rgb {
+ bool rgb_en;
+ enum resizer_rgb_t rgb_typ;
+ enum resizer_rgb_msk_t rgb_msk0;
+ enum resizer_rgb_msk_t rgb_msk1;
+ unsigned int rgb_alpha_val;
+};
+
+/* Resizer External Memory Parameters */
+struct rsz_ext_mem_param {
+ unsigned int rsz_sdr_oft_y;
+ unsigned int rsz_sdr_ptr_s_y;
+ unsigned int rsz_sdr_ptr_e_y;
+ unsigned int rsz_sdr_oft_c;
+ unsigned int rsz_sdr_ptr_s_c;
+ unsigned int rsz_sdr_ptr_e_c;
+ /* offset to be added to buffer start when flipping for y/ycbcr */
+ unsigned int flip_ofst_y;
+ /* offset to be added to buffer start when flipping for c */
+ unsigned int flip_ofst_c;
+ /* c offset for YUV 420SP */
+ unsigned int c_offset;
+ /* User Defined Y offset for YUV 420SP or YUV420ILE data */
+ unsigned int user_y_ofst;
+ /* User Defined C offset for YUV 420SP data */
+ unsigned int user_c_ofst;
+};
+
+enum rsz_data_source {
+ IPIPE_DATA,
+ IPIPEIF_DATA
+};
+
+enum rsz_src_img_fmt {
+ RSZ_IMG_422,
+ RSZ_IMG_420
+};
+
+enum rsz_dpaths_bypass_t {
+ BYPASS_OFF = 0,
+ BYPASS_ON = 1,
+};
+
+struct rsz_common_params {
+ unsigned int vps;
+ unsigned int vsz;
+ unsigned int hps;
+ unsigned int hsz;
+ /* 420 or 422 */
+ enum rsz_src_img_fmt src_img_fmt;
+ /* Y or C when src_fmt is 420, 0 - y, 1 - c */
+ unsigned char y_c;
+ /* flip raw or ycbcr */
+ unsigned char raw_flip;
+ /* IPIPE or IPIPEIF data */
+ enum rsz_data_source source;
+ enum rsz_dpaths_bypass_t passthrough;
+ unsigned char yuv_y_min;
+ unsigned char yuv_y_max;
+ unsigned char yuv_c_min;
+ unsigned char yuv_c_max;
+ bool rsz_seq_crv;
+ enum vpfe_chr_pos out_chr_pos;
+};
+
+struct resizer_params {
+ enum resizer_oper_mode oper_mode;
+ struct rsz_common_params rsz_common;
+ struct resizer_scale_param rsz_rsc_param[2];
+ struct resizer_rgb rsz2rgb[2];
+ struct rsz_ext_mem_param ext_mem_param[2];
+ bool rsz_en[2];
+ struct vpfe_rsz_config_params user_config;
+};
+
+#define ENABLE 1
+#define DISABLE (!ENABLE)
+
+#define RESIZER_CROP_PAD_SINK 0
+#define RESIZER_CROP_PAD_SOURCE 1
+#define RESIZER_CROP_PAD_SOURCE2 2
+
+#define RESIZER_CROP_PADS_NUM 3
+
+enum resizer_crop_input_entity {
+ RESIZER_CROP_INPUT_NONE = 0,
+ RESIZER_CROP_INPUT_IPIPEIF = 1,
+ RESIZER_CROP_INPUT_IPIPE = 2,
+};
+
+enum resizer_crop_output_entity {
+ RESIZER_CROP_OUTPUT_NONE,
+ RESIZER_A,
+ RESIZER_B,
+};
+
+struct dm365_crop_resizer_device {
+ struct v4l2_subdev subdev;
+ struct media_pad pads[RESIZER_CROP_PADS_NUM];
+ struct v4l2_mbus_framefmt formats[RESIZER_CROP_PADS_NUM];
+ enum resizer_crop_input_entity input;
+ enum resizer_crop_output_entity output;
+ enum resizer_crop_output_entity output2;
+ struct vpfe_resizer_device *rsz_device;
+};
+
+#define RESIZER_PAD_SINK 0
+#define RESIZER_PAD_SOURCE 1
+
+#define RESIZER_PADS_NUM 2
+
+enum resizer_input_entity {
+ RESIZER_INPUT_NONE = 0,
+ RESIZER_INPUT_CROP_RESIZER = 1,
+};
+
+enum resizer_output_entity {
+ RESIZER_OUTPUT_NONE = 0,
+ RESIZER_OUPUT_MEMORY = 1,
+};
+
+struct dm365_resizer_device {
+ struct v4l2_subdev subdev;
+ struct media_pad pads[RESIZER_PADS_NUM];
+ struct v4l2_mbus_framefmt formats[RESIZER_PADS_NUM];
+ enum resizer_input_entity input;
+ enum resizer_output_entity output;
+ struct vpfe_video_device video_out;
+ struct vpfe_resizer_device *rsz_device;
+};
+
+struct vpfe_resizer_device {
+ struct dm365_crop_resizer_device crop_resizer;
+ struct dm365_resizer_device resizer_a;
+ struct dm365_resizer_device resizer_b;
+ struct resizer_params config;
+ void *__iomem base_addr;
+};
+
+int vpfe_resizer_init(struct vpfe_resizer_device *vpfe_rsz,
+ struct platform_device *pdev);
+int vpfe_resizer_register_entities(struct vpfe_resizer_device *vpfe_rsz,
+ struct v4l2_device *v4l2_dev);
+void vpfe_resizer_unregister_entities(struct vpfe_resizer_device *vpfe_rsz);
+void vpfe_resizer_cleanup(struct vpfe_resizer_device *vpfe_rsz,
+ struct platform_device *pdev);
+void vpfe_resizer_buffer_isr(struct vpfe_resizer_device *resizer);
+void vpfe_resizer_dma_isr(struct vpfe_resizer_device *resizer);
+
+#endif /* _DAVINCI_VPFE_DM365_RESIZER_H */
diff --git a/drivers/staging/media/davinci_vpfe/vpfe.h b/drivers/staging/media/davinci_vpfe/vpfe.h
new file mode 100644
index 00000000000..0587bc52a84
--- /dev/null
+++ b/drivers/staging/media/davinci_vpfe/vpfe.h
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2012 Texas Instruments Inc
+ *
+ * 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 version 2.
+ *
+ * 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
+ *
+ * Contributors:
+ * Manjunath Hadli <manjunath.hadli@ti.com>
+ * Prabhakar Lad <prabhakar.lad@ti.com>
+ */
+
+#ifndef _VPFE_H
+#define _VPFE_H
+
+#ifdef __KERNEL__
+#include <linux/v4l2-subdev.h>
+#include <linux/clk.h>
+#include <linux/i2c.h>
+
+#include <media/davinci/vpfe_types.h>
+
+#define CAPTURE_DRV_NAME "vpfe-capture"
+
+struct vpfe_route {
+ __u32 input;
+ __u32 output;
+};
+
+enum vpfe_subdev_id {
+ VPFE_SUBDEV_TVP5146 = 1,
+ VPFE_SUBDEV_MT9T031 = 2,
+ VPFE_SUBDEV_TVP7002 = 3,
+ VPFE_SUBDEV_MT9P031 = 4,
+};
+
+struct vpfe_ext_subdev_info {
+ /* v4l2 subdev */
+ struct v4l2_subdev *subdev;
+ /* Sub device module name */
+ char module_name[32];
+ /* Sub device group id */
+ int grp_id;
+ /* Number of inputs supported */
+ int num_inputs;
+ /* inputs available at the sub device */
+ struct v4l2_input *inputs;
+ /* Sub dev routing information for each input */
+ struct vpfe_route *routes;
+ /* ccdc bus/interface configuration */
+ struct vpfe_hw_if_param ccdc_if_params;
+ /* i2c subdevice board info */
+ struct i2c_board_info board_info;
+ /* Is this a camera sub device ? */
+ unsigned is_camera:1;
+ /* check if sub dev supports routing */
+ unsigned can_route:1;
+ /* registered ? */
+ unsigned registered:1;
+};
+
+struct vpfe_config {
+ /* Number of sub devices connected to vpfe */
+ int num_subdevs;
+ /* information about each subdev */
+ struct vpfe_ext_subdev_info *sub_devs;
+ /* evm card info */
+ char *card_name;
+ /* setup function for the input path */
+ int (*setup_input)(enum vpfe_subdev_id id);
+ /* number of clocks */
+ int num_clocks;
+ /* clocks used for vpfe capture */
+ char *clocks[];
+};
+#endif
+#endif
diff --git a/drivers/staging/media/davinci_vpfe/vpfe_mc_capture.c b/drivers/staging/media/davinci_vpfe/vpfe_mc_capture.c
new file mode 100644
index 00000000000..cda8388cbb8
--- /dev/null
+++ b/drivers/staging/media/davinci_vpfe/vpfe_mc_capture.c
@@ -0,0 +1,724 @@
+/*
+ * Copyright (C) 2012 Texas Instruments Inc
+ *
+ * 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 version 2.
+ *
+ * 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
+ *
+ * Contributors:
+ * Manjunath Hadli <manjunath.hadli@ti.com>
+ * Prabhakar Lad <prabhakar.lad@ti.com>
+ *
+ *
+ * Driver name : VPFE Capture driver
+ * VPFE Capture driver allows applications to capture and stream video
+ * frames on DaVinci SoCs (DM6446, DM355 etc) from a YUV source such as
+ * TVP5146 or Raw Bayer RGB image data from an image sensor
+ * such as Microns' MT9T001, MT9T031 etc.
+ *
+ * These SoCs have, in common, a Video Processing Subsystem (VPSS) that
+ * consists of a Video Processing Front End (VPFE) for capturing
+ * video/raw image data and Video Processing Back End (VPBE) for displaying
+ * YUV data through an in-built analog encoder or Digital LCD port. This
+ * driver is for capture through VPFE. A typical EVM using these SoCs have
+ * following high level configuration.
+ *
+ * decoder(TVP5146/ YUV/
+ * MT9T001) --> Raw Bayer RGB ---> MUX -> VPFE (CCDC/ISIF)
+ * data input | |
+ * V |
+ * SDRAM |
+ * V
+ * Image Processor
+ * |
+ * V
+ * SDRAM
+ * The data flow happens from a decoder connected to the VPFE over a
+ * YUV embedded (BT.656/BT.1120) or separate sync or raw bayer rgb interface
+ * and to the input of VPFE through an optional MUX (if more inputs are
+ * to be interfaced on the EVM). The input data is first passed through
+ * CCDC (CCD Controller, a.k.a Image Sensor Interface, ISIF). The CCDC
+ * does very little or no processing on YUV data and does pre-process Raw
+ * Bayer RGB data through modules such as Defect Pixel Correction (DFC)
+ * Color Space Conversion (CSC), data gain/offset etc. After this, data
+ * can be written to SDRAM or can be connected to the image processing
+ * block such as IPIPE (on DM355/DM365 only).
+ *
+ * Features supported
+ * - MMAP IO
+ * - USERPTR IO
+ * - Capture using TVP5146 over BT.656
+ * - Support for interfacing decoders using sub device model
+ * - Work with DM365 or DM355 or DM6446 CCDC to do Raw Bayer
+ * RGB/YUV data capture to SDRAM.
+ * - Chaining of Image Processor
+ * - SINGLE-SHOT mode
+ */
+
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+
+#include "vpfe.h"
+#include "vpfe_mc_capture.h"
+
+static bool debug;
+static bool interface;
+
+module_param(interface, bool, S_IRUGO);
+module_param(debug, bool, 0644);
+
+/**
+ * VPFE capture can be used for capturing video such as from TVP5146 or TVP7002
+ * and for capture raw bayer data from camera sensors such as mt9p031. At this
+ * point there is problem in co-existence of mt9p031 and tvp5146 due to i2c
+ * address collision. So set the variable below from bootargs to do either video
+ * capture or camera capture.
+ * interface = 0 - video capture (from TVP514x or such),
+ * interface = 1 - Camera capture (from mt9p031 or such)
+ * Re-visit this when we fix the co-existence issue
+ */
+MODULE_PARM_DESC(interface, "interface 0-1 (default:0)");
+MODULE_PARM_DESC(debug, "Debug level 0-1");
+
+MODULE_DESCRIPTION("VPFE Video for Linux Capture Driver");
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Texas Instruments");
+
+/* map mbus_fmt to pixelformat */
+void mbus_to_pix(const struct v4l2_mbus_framefmt *mbus,
+ struct v4l2_pix_format *pix)
+{
+ switch (mbus->code) {
+ case V4L2_MBUS_FMT_UYVY8_2X8:
+ pix->pixelformat = V4L2_PIX_FMT_UYVY;
+ pix->bytesperline = pix->width * 2;
+ break;
+
+ case V4L2_MBUS_FMT_YUYV8_2X8:
+ pix->pixelformat = V4L2_PIX_FMT_YUYV;
+ pix->bytesperline = pix->width * 2;
+ break;
+
+ case V4L2_MBUS_FMT_YUYV10_1X20:
+ pix->pixelformat = V4L2_PIX_FMT_UYVY;
+ pix->bytesperline = pix->width * 2;
+ break;
+
+ case V4L2_MBUS_FMT_SGRBG12_1X12:
+ pix->pixelformat = V4L2_PIX_FMT_SBGGR16;
+ pix->bytesperline = pix->width * 2;
+ break;
+
+ case V4L2_MBUS_FMT_SGRBG10_DPCM8_1X8:
+ pix->pixelformat = V4L2_PIX_FMT_SGRBG10DPCM8;
+ pix->bytesperline = pix->width;
+ break;
+
+ case V4L2_MBUS_FMT_SGRBG10_ALAW8_1X8:
+ pix->pixelformat = V4L2_PIX_FMT_SGRBG10ALAW8;
+ pix->bytesperline = pix->width;
+ break;
+
+ case V4L2_MBUS_FMT_YDYUYDYV8_1X16:
+ pix->pixelformat = V4L2_PIX_FMT_NV12;
+ pix->bytesperline = pix->width;
+ break;
+
+ case V4L2_MBUS_FMT_Y8_1X8:
+ pix->pixelformat = V4L2_PIX_FMT_GREY;
+ pix->bytesperline = pix->width;
+ break;
+
+ case V4L2_MBUS_FMT_UV8_1X8:
+ pix->pixelformat = V4L2_PIX_FMT_UV8;
+ pix->bytesperline = pix->width;
+ break;
+
+ default:
+ pr_err("Invalid mbus code set\n");
+ }
+ /* pitch should be 32 bytes aligned */
+ pix->bytesperline = ALIGN(pix->bytesperline, 32);
+ if (pix->pixelformat == V4L2_PIX_FMT_NV12)
+ pix->sizeimage = pix->bytesperline * pix->height +
+ ((pix->bytesperline * pix->height) >> 1);
+ else
+ pix->sizeimage = pix->bytesperline * pix->height;
+}
+
+/* ISR for VINT0*/
+static irqreturn_t vpfe_isr(int irq, void *dev_id)
+{
+ struct vpfe_device *vpfe_dev = dev_id;
+
+ v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_isr\n");
+ vpfe_isif_buffer_isr(&vpfe_dev->vpfe_isif);
+ vpfe_resizer_buffer_isr(&vpfe_dev->vpfe_resizer);
+ return IRQ_HANDLED;
+}
+
+/* vpfe_vdint1_isr() - isr handler for VINT1 interrupt */
+static irqreturn_t vpfe_vdint1_isr(int irq, void *dev_id)
+{
+ struct vpfe_device *vpfe_dev = dev_id;
+
+ v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_vdint1_isr\n");
+ vpfe_isif_vidint1_isr(&vpfe_dev->vpfe_isif);
+ return IRQ_HANDLED;
+}
+
+/* vpfe_imp_dma_isr() - ISR for ipipe dma completion */
+static irqreturn_t vpfe_imp_dma_isr(int irq, void *dev_id)
+{
+ struct vpfe_device *vpfe_dev = dev_id;
+
+ v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_imp_dma_isr\n");
+ vpfe_ipipeif_ss_buffer_isr(&vpfe_dev->vpfe_ipipeif);
+ vpfe_resizer_dma_isr(&vpfe_dev->vpfe_resizer);
+ return IRQ_HANDLED;
+}
+
+/*
+ * vpfe_disable_clock() - Disable clocks for vpfe capture driver
+ * @vpfe_dev - ptr to vpfe capture device
+ *
+ * Disables clocks defined in vpfe configuration. The function
+ * assumes that at least one clock is to be defined which is
+ * true as of now.
+ */
+static void vpfe_disable_clock(struct vpfe_device *vpfe_dev)
+{
+ struct vpfe_config *vpfe_cfg = vpfe_dev->cfg;
+ int i;
+
+ for (i = 0; i < vpfe_cfg->num_clocks; i++) {
+ clk_disable_unprepare(vpfe_dev->clks[i]);
+ clk_put(vpfe_dev->clks[i]);
+ }
+ kzfree(vpfe_dev->clks);
+ v4l2_info(vpfe_dev->pdev->driver, "vpfe capture clocks disabled\n");
+}
+
+/*
+ * vpfe_enable_clock() - Enable clocks for vpfe capture driver
+ * @vpfe_dev - ptr to vpfe capture device
+ *
+ * Enables clocks defined in vpfe configuration. The function
+ * assumes that at least one clock is to be defined which is
+ * true as of now.
+ */
+static int vpfe_enable_clock(struct vpfe_device *vpfe_dev)
+{
+ struct vpfe_config *vpfe_cfg = vpfe_dev->cfg;
+ int ret = -EFAULT;
+ int i;
+
+ if (!vpfe_cfg->num_clocks)
+ return 0;
+
+ vpfe_dev->clks = kzalloc(vpfe_cfg->num_clocks *
+ sizeof(struct clock *), GFP_KERNEL);
+ if (vpfe_dev->clks == NULL) {
+ v4l2_err(vpfe_dev->pdev->driver, "Memory allocation failed\n");
+ return -ENOMEM;
+ }
+
+ for (i = 0; i < vpfe_cfg->num_clocks; i++) {
+ if (vpfe_cfg->clocks[i] == NULL) {
+ v4l2_err(vpfe_dev->pdev->driver,
+ "clock %s is not defined in vpfe config\n",
+ vpfe_cfg->clocks[i]);
+ goto out;
+ }
+
+ vpfe_dev->clks[i] =
+ clk_get(vpfe_dev->pdev, vpfe_cfg->clocks[i]);
+ if (IS_ERR(vpfe_dev->clks[i])) {
+ v4l2_err(vpfe_dev->pdev->driver,
+ "Failed to get clock %s\n",
+ vpfe_cfg->clocks[i]);
+ goto out;
+ }
+
+ if (clk_prepare_enable(vpfe_dev->clks[i])) {
+ v4l2_err(vpfe_dev->pdev->driver,
+ "vpfe clock %s not enabled\n",
+ vpfe_cfg->clocks[i]);
+ goto out;
+ }
+
+ v4l2_info(vpfe_dev->pdev->driver, "vpss clock %s enabled",
+ vpfe_cfg->clocks[i]);
+ }
+
+ return 0;
+out:
+ for (i = 0; i < vpfe_cfg->num_clocks; i++)
+ if (!IS_ERR(vpfe_dev->clks[i])) {
+ clk_disable_unprepare(vpfe_dev->clks[i]);
+ clk_put(vpfe_dev->clks[i]);
+ }
+
+ v4l2_err(vpfe_dev->pdev->driver, "Failed to enable clocks\n");
+ kzfree(vpfe_dev->clks);
+
+ return ret;
+}
+
+/*
+ * vpfe_detach_irq() - Detach IRQs for vpfe capture driver
+ * @vpfe_dev - ptr to vpfe capture device
+ *
+ * Detach all IRQs defined in vpfe configuration.
+ */
+static void vpfe_detach_irq(struct vpfe_device *vpfe_dev)
+{
+ free_irq(vpfe_dev->ccdc_irq0, vpfe_dev);
+ free_irq(vpfe_dev->ccdc_irq1, vpfe_dev);
+ free_irq(vpfe_dev->imp_dma_irq, vpfe_dev);
+}
+
+/*
+ * vpfe_attach_irq() - Attach IRQs for vpfe capture driver
+ * @vpfe_dev - ptr to vpfe capture device
+ *
+ * Attach all IRQs defined in vpfe configuration.
+ */
+static int vpfe_attach_irq(struct vpfe_device *vpfe_dev)
+{
+ int ret = 0;
+
+ ret = request_irq(vpfe_dev->ccdc_irq0, vpfe_isr, 0,
+ "vpfe_capture0", vpfe_dev);
+ if (ret < 0) {
+ v4l2_err(&vpfe_dev->v4l2_dev,
+ "Error: requesting VINT0 interrupt\n");
+ return ret;
+ }
+
+ ret = request_irq(vpfe_dev->ccdc_irq1, vpfe_vdint1_isr, 0,
+ "vpfe_capture1", vpfe_dev);
+ if (ret < 0) {
+ v4l2_err(&vpfe_dev->v4l2_dev,
+ "Error: requesting VINT1 interrupt\n");
+ free_irq(vpfe_dev->ccdc_irq0, vpfe_dev);
+ return ret;
+ }
+
+ ret = request_irq(vpfe_dev->imp_dma_irq, vpfe_imp_dma_isr,
+ 0, "Imp_Sdram_Irq", vpfe_dev);
+ if (ret < 0) {
+ v4l2_err(&vpfe_dev->v4l2_dev,
+ "Error: requesting IMP IRQ interrupt\n");
+ free_irq(vpfe_dev->ccdc_irq1, vpfe_dev);
+ free_irq(vpfe_dev->ccdc_irq0, vpfe_dev);
+ return ret;
+ }
+
+ return 0;
+}
+
+/*
+ * register_i2c_devices() - register all i2c v4l2 subdevs
+ * @vpfe_dev - ptr to vpfe capture device
+ *
+ * register all i2c v4l2 subdevs
+ */
+static int register_i2c_devices(struct vpfe_device *vpfe_dev)
+{
+ struct vpfe_ext_subdev_info *sdinfo;
+ struct vpfe_config *vpfe_cfg;
+ struct i2c_adapter *i2c_adap;
+ unsigned int num_subdevs;
+ int ret;
+ int i;
+ int k;
+
+ vpfe_cfg = vpfe_dev->cfg;
+ i2c_adap = i2c_get_adapter(1);
+ num_subdevs = vpfe_cfg->num_subdevs;
+ vpfe_dev->sd =
+ kzalloc(sizeof(struct v4l2_subdev *)*num_subdevs, GFP_KERNEL);
+ if (vpfe_dev->sd == NULL) {
+ v4l2_err(&vpfe_dev->v4l2_dev,
+ "unable to allocate memory for subdevice\n");
+ return -ENOMEM;
+ }
+
+ for (i = 0, k = 0; i < num_subdevs; i++) {
+ sdinfo = &vpfe_cfg->sub_devs[i];
+ /*
+ * register subdevices based on interface setting. Currently
+ * tvp5146 and mt9p031 cannot co-exists due to i2c address
+ * conflicts. So only one of them is registered. Re-visit this
+ * once we have support for i2c switch handling in i2c driver
+ * framework
+ */
+ if (interface == sdinfo->is_camera) {
+ /* setup input path */
+ if (vpfe_cfg->setup_input &&
+ vpfe_cfg->setup_input(sdinfo->grp_id) < 0) {
+ ret = -EFAULT;
+ v4l2_info(&vpfe_dev->v4l2_dev,
+ "could not setup input for %s\n",
+ sdinfo->module_name);
+ goto probe_sd_out;
+ }
+ /* Load up the subdevice */
+ vpfe_dev->sd[k] =
+ v4l2_i2c_new_subdev_board(&vpfe_dev->v4l2_dev,
+ i2c_adap, &sdinfo->board_info,
+ NULL);
+ if (vpfe_dev->sd[k]) {
+ v4l2_info(&vpfe_dev->v4l2_dev,
+ "v4l2 sub device %s registered\n",
+ sdinfo->module_name);
+
+ vpfe_dev->sd[k]->grp_id = sdinfo->grp_id;
+ k++;
+
+ sdinfo->registered = 1;
+ }
+ } else {
+ v4l2_info(&vpfe_dev->v4l2_dev,
+ "v4l2 sub device %s is not registered\n",
+ sdinfo->module_name);
+ }
+ }
+ vpfe_dev->num_ext_subdevs = k;
+
+ return 0;
+
+probe_sd_out:
+ kzfree(vpfe_dev->sd);
+
+ return ret;
+}
+
+/*
+ * vpfe_register_entities() - register all v4l2 subdevs and media entities
+ * @vpfe_dev - ptr to vpfe capture device
+ *
+ * register all v4l2 subdevs, media entities, and creates links
+ * between entities
+ */
+static int vpfe_register_entities(struct vpfe_device *vpfe_dev)
+{
+ unsigned int flags = 0;
+ int ret;
+ int i;
+
+ /* register i2c devices first */
+ ret = register_i2c_devices(vpfe_dev);
+ if (ret)
+ return ret;
+
+ /* register rest of the sub-devs */
+ ret = vpfe_isif_register_entities(&vpfe_dev->vpfe_isif,
+ &vpfe_dev->v4l2_dev);
+ if (ret)
+ return ret;
+
+ ret = vpfe_ipipeif_register_entities(&vpfe_dev->vpfe_ipipeif,
+ &vpfe_dev->v4l2_dev);
+ if (ret)
+ goto out_isif_register;
+
+ ret = vpfe_ipipe_register_entities(&vpfe_dev->vpfe_ipipe,
+ &vpfe_dev->v4l2_dev);
+ if (ret)
+ goto out_ipipeif_register;
+
+ ret = vpfe_resizer_register_entities(&vpfe_dev->vpfe_resizer,
+ &vpfe_dev->v4l2_dev);
+ if (ret)
+ goto out_ipipe_register;
+
+ /* create links now, starting with external(i2c) entities */
+ for (i = 0; i < vpfe_dev->num_ext_subdevs; i++)
+ /* if entity has no pads (ex: amplifier),
+ cant establish link */
+ if (vpfe_dev->sd[i]->entity.num_pads) {
+ ret = media_entity_create_link(&vpfe_dev->sd[i]->entity,
+ 0, &vpfe_dev->vpfe_isif.subdev.entity,
+ 0, flags);
+ if (ret < 0)
+ goto out_resizer_register;
+ }
+
+ ret = media_entity_create_link(&vpfe_dev->vpfe_isif.subdev.entity, 1,
+ &vpfe_dev->vpfe_ipipeif.subdev.entity,
+ 0, flags);
+ if (ret < 0)
+ goto out_resizer_register;
+
+ ret = media_entity_create_link(&vpfe_dev->vpfe_ipipeif.subdev.entity, 1,
+ &vpfe_dev->vpfe_ipipe.subdev.entity,
+ 0, flags);
+ if (ret < 0)
+ goto out_resizer_register;
+
+ ret = media_entity_create_link(&vpfe_dev->vpfe_ipipe.subdev.entity,
+ 1, &vpfe_dev->vpfe_resizer.crop_resizer.subdev.entity,
+ 0, flags);
+ if (ret < 0)
+ goto out_resizer_register;
+
+ ret = media_entity_create_link(&vpfe_dev->vpfe_ipipeif.subdev.entity, 1,
+ &vpfe_dev->vpfe_resizer.crop_resizer.subdev.entity,
+ 0, flags);
+ if (ret < 0)
+ goto out_resizer_register;
+
+ ret = v4l2_device_register_subdev_nodes(&vpfe_dev->v4l2_dev);
+ if (ret < 0)
+ goto out_resizer_register;
+
+ return 0;
+
+out_resizer_register:
+ vpfe_resizer_unregister_entities(&vpfe_dev->vpfe_resizer);
+out_ipipe_register:
+ vpfe_ipipe_unregister_entities(&vpfe_dev->vpfe_ipipe);
+out_ipipeif_register:
+ vpfe_ipipeif_unregister_entities(&vpfe_dev->vpfe_ipipeif);
+out_isif_register:
+ vpfe_isif_unregister_entities(&vpfe_dev->vpfe_isif);
+
+ return ret;
+}
+
+/*
+ * vpfe_unregister_entities() - unregister all v4l2 subdevs and media entities
+ * @vpfe_dev - ptr to vpfe capture device
+ *
+ * unregister all v4l2 subdevs and media entities
+ */
+static void vpfe_unregister_entities(struct vpfe_device *vpfe_dev)
+{
+ vpfe_isif_unregister_entities(&vpfe_dev->vpfe_isif);
+ vpfe_ipipeif_unregister_entities(&vpfe_dev->vpfe_ipipeif);
+ vpfe_ipipe_unregister_entities(&vpfe_dev->vpfe_ipipe);
+ vpfe_resizer_unregister_entities(&vpfe_dev->vpfe_resizer);
+}
+
+/*
+ * vpfe_cleanup_modules() - cleanup all non-i2c v4l2 subdevs
+ * @vpfe_dev - ptr to vpfe capture device
+ * @pdev - pointer to platform device
+ *
+ * cleanup all v4l2 subdevs
+ */
+static void vpfe_cleanup_modules(struct vpfe_device *vpfe_dev,
+ struct platform_device *pdev)
+{
+ vpfe_isif_cleanup(&vpfe_dev->vpfe_isif, pdev);
+ vpfe_ipipeif_cleanup(&vpfe_dev->vpfe_ipipeif, pdev);
+ vpfe_ipipe_cleanup(&vpfe_dev->vpfe_ipipe, pdev);
+ vpfe_resizer_cleanup(&vpfe_dev->vpfe_resizer, pdev);
+}
+
+/*
+ * vpfe_initialize_modules() - initialize all non-i2c v4l2 subdevs
+ * @vpfe_dev - ptr to vpfe capture device
+ * @pdev - pointer to platform device
+ *
+ * intialize all v4l2 subdevs and media entities
+ */
+static int vpfe_initialize_modules(struct vpfe_device *vpfe_dev,
+ struct platform_device *pdev)
+{
+ int ret;
+
+ ret = vpfe_isif_init(&vpfe_dev->vpfe_isif, pdev);
+ if (ret)
+ return ret;
+
+ ret = vpfe_ipipeif_init(&vpfe_dev->vpfe_ipipeif, pdev);
+ if (ret)
+ goto out_isif_init;
+
+ ret = vpfe_ipipe_init(&vpfe_dev->vpfe_ipipe, pdev);
+ if (ret)
+ goto out_ipipeif_init;
+
+ ret = vpfe_resizer_init(&vpfe_dev->vpfe_resizer, pdev);
+ if (ret)
+ goto out_ipipe_init;
+
+ return 0;
+
+out_ipipe_init:
+ vpfe_ipipe_cleanup(&vpfe_dev->vpfe_ipipe, pdev);
+out_ipipeif_init:
+ vpfe_ipipeif_cleanup(&vpfe_dev->vpfe_ipipeif, pdev);
+out_isif_init:
+ vpfe_isif_cleanup(&vpfe_dev->vpfe_isif, pdev);
+
+ return ret;
+}
+
+/*
+ * vpfe_probe() : vpfe probe function
+ * @pdev: platform device pointer
+ *
+ * This function creates device entries by register itself to the V4L2 driver
+ * and initializes fields of each device objects
+ */
+static int vpfe_probe(struct platform_device *pdev)
+{
+ struct vpfe_device *vpfe_dev;
+ struct resource *res1;
+ int ret = -ENOMEM;
+
+ vpfe_dev = kzalloc(sizeof(*vpfe_dev), GFP_KERNEL);
+ if (!vpfe_dev) {
+ v4l2_err(pdev->dev.driver,
+ "Failed to allocate memory for vpfe_dev\n");
+ return ret;
+ }
+
+ if (pdev->dev.platform_data == NULL) {
+ v4l2_err(pdev->dev.driver, "Unable to get vpfe config\n");
+ ret = -ENOENT;
+ goto probe_free_dev_mem;
+ }
+
+ vpfe_dev->cfg = pdev->dev.platform_data;
+ if (vpfe_dev->cfg->card_name == NULL ||
+ vpfe_dev->cfg->sub_devs == NULL) {
+ v4l2_err(pdev->dev.driver, "null ptr in vpfe_cfg\n");
+ ret = -ENOENT;
+ goto probe_free_dev_mem;
+ }
+
+ /* Get VINT0 irq resource */
+ res1 = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
+ if (!res1) {
+ v4l2_err(pdev->dev.driver,
+ "Unable to get interrupt for VINT0\n");
+ ret = -ENOENT;
+ goto probe_free_dev_mem;
+ }
+ vpfe_dev->ccdc_irq0 = res1->start;
+
+ /* Get VINT1 irq resource */
+ res1 = platform_get_resource(pdev, IORESOURCE_IRQ, 1);
+ if (!res1) {
+ v4l2_err(pdev->dev.driver,
+ "Unable to get interrupt for VINT1\n");
+ ret = -ENOENT;
+ goto probe_free_dev_mem;
+ }
+ vpfe_dev->ccdc_irq1 = res1->start;
+
+ /* Get DMA irq resource */
+ res1 = platform_get_resource(pdev, IORESOURCE_IRQ, 2);
+ if (!res1) {
+ v4l2_err(pdev->dev.driver,
+ "Unable to get interrupt for DMA\n");
+ ret = -ENOENT;
+ goto probe_free_dev_mem;
+ }
+ vpfe_dev->imp_dma_irq = res1->start;
+
+ vpfe_dev->pdev = &pdev->dev;
+
+ /* enable vpss clocks */
+ ret = vpfe_enable_clock(vpfe_dev);
+ if (ret)
+ goto probe_free_dev_mem;
+
+ ret = vpfe_initialize_modules(vpfe_dev, pdev);
+ if (ret)
+ goto probe_disable_clock;
+
+ vpfe_dev->media_dev.dev = vpfe_dev->pdev;
+ strcpy((char *)&vpfe_dev->media_dev.model, "davinci-media");
+
+ ret = media_device_register(&vpfe_dev->media_dev);
+ if (ret) {
+ v4l2_err(pdev->dev.driver,
+ "Unable to register media device.\n");
+ goto probe_out_entities_cleanup;
+ }
+
+ vpfe_dev->v4l2_dev.mdev = &vpfe_dev->media_dev;
+ ret = v4l2_device_register(&pdev->dev, &vpfe_dev->v4l2_dev);
+ if (ret) {
+ v4l2_err(pdev->dev.driver, "Unable to register v4l2 device.\n");
+ goto probe_out_media_unregister;
+ }
+
+ v4l2_info(&vpfe_dev->v4l2_dev, "v4l2 device registered\n");
+ /* set the driver data in platform device */
+ platform_set_drvdata(pdev, vpfe_dev);
+ /* register subdevs/entities */
+ ret = vpfe_register_entities(vpfe_dev);
+ if (ret)
+ goto probe_out_v4l2_unregister;
+
+ ret = vpfe_attach_irq(vpfe_dev);
+ if (ret)
+ goto probe_out_entities_unregister;
+
+ return 0;
+
+probe_out_entities_unregister:
+ vpfe_unregister_entities(vpfe_dev);
+ kzfree(vpfe_dev->sd);
+probe_out_v4l2_unregister:
+ v4l2_device_unregister(&vpfe_dev->v4l2_dev);
+probe_out_media_unregister:
+ media_device_unregister(&vpfe_dev->media_dev);
+probe_out_entities_cleanup:
+ vpfe_cleanup_modules(vpfe_dev, pdev);
+probe_disable_clock:
+ vpfe_disable_clock(vpfe_dev);
+probe_free_dev_mem:
+ kzfree(vpfe_dev);
+
+ return ret;
+}
+
+/*
+ * vpfe_remove : This function un-registers device from V4L2 driver
+ */
+static int vpfe_remove(struct platform_device *pdev)
+{
+ struct vpfe_device *vpfe_dev = platform_get_drvdata(pdev);
+
+ v4l2_info(pdev->dev.driver, "vpfe_remove\n");
+
+ kzfree(vpfe_dev->sd);
+ vpfe_detach_irq(vpfe_dev);
+ vpfe_unregister_entities(vpfe_dev);
+ vpfe_cleanup_modules(vpfe_dev, pdev);
+ v4l2_device_unregister(&vpfe_dev->v4l2_dev);
+ media_device_unregister(&vpfe_dev->media_dev);
+ vpfe_disable_clock(vpfe_dev);
+ kzfree(vpfe_dev);
+
+ return 0;
+}
+
+static struct platform_driver vpfe_driver = {
+ .driver = {
+ .name = CAPTURE_DRV_NAME,
+ .owner = THIS_MODULE,
+ },
+ .probe = vpfe_probe,
+ .remove = vpfe_remove,
+};
+
+module_platform_driver(vpfe_driver);
diff --git a/drivers/staging/media/davinci_vpfe/vpfe_mc_capture.h b/drivers/staging/media/davinci_vpfe/vpfe_mc_capture.h
new file mode 100644
index 00000000000..2632a806c4a
--- /dev/null
+++ b/drivers/staging/media/davinci_vpfe/vpfe_mc_capture.h
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2012 Texas Instruments Inc
+ *
+ * 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 version 2.
+ *
+ * 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
+ *
+ * Contributors:
+ * Manjunath Hadli <manjunath.hadli@ti.com>
+ * Prabhakar Lad <prabhakar.lad@ti.com>
+ */
+
+#ifndef _DAVINCI_VPFE_MC_CAPTURE_H
+#define _DAVINCI_VPFE_MC_CAPTURE_H
+
+#include "dm365_ipipe.h"
+#include "dm365_ipipeif.h"
+#include "dm365_isif.h"
+#include "dm365_resizer.h"
+#include "vpfe_video.h"
+
+#define VPFE_MAJOR_RELEASE 0
+#define VPFE_MINOR_RELEASE 0
+#define VPFE_BUILD 1
+#define VPFE_CAPTURE_VERSION_CODE ((VPFE_MAJOR_RELEASE << 16) | \
+ (VPFE_MINOR_RELEASE << 8) | \
+ VPFE_BUILD)
+
+/* IPIPE hardware limits */
+#define IPIPE_MAX_OUTPUT_WIDTH_A 2176
+#define IPIPE_MAX_OUTPUT_WIDTH_B 640
+
+/* Based on max resolution supported. QXGA */
+#define IPIPE_MAX_OUTPUT_HEIGHT_A 1536
+/* Based on max resolution supported. VGA */
+#define IPIPE_MAX_OUTPUT_HEIGHT_B 480
+
+#define to_vpfe_device(ptr_module) \
+ container_of(ptr_module, struct vpfe_device, vpfe_##ptr_module)
+#define to_device(ptr_module) \
+ (to_vpfe_device(ptr_module)->dev)
+
+struct vpfe_device {
+ /* external registered sub devices */
+ struct v4l2_subdev **sd;
+ /* number of registered external subdevs */
+ unsigned int num_ext_subdevs;
+ /* vpfe cfg */
+ struct vpfe_config *cfg;
+ /* clock ptrs for vpfe capture */
+ struct clk **clks;
+ /* V4l2 device */
+ struct v4l2_device v4l2_dev;
+ /* parent device */
+ struct device *pdev;
+ /* IRQ number for DMA transfer completion at the image processor */
+ unsigned int imp_dma_irq;
+ /* CCDC IRQs used when CCDC/ISIF output to SDRAM */
+ unsigned int ccdc_irq0;
+ unsigned int ccdc_irq1;
+ /* maximum video memory that is available*/
+ unsigned int video_limit;
+ /* media device */
+ struct media_device media_dev;
+ /* ccdc subdevice */
+ struct vpfe_isif_device vpfe_isif;
+ /* ipipeif subdevice */
+ struct vpfe_ipipeif_device vpfe_ipipeif;
+ /* ipipe subdevice */
+ struct vpfe_ipipe_device vpfe_ipipe;
+ /* resizer subdevice */
+ struct vpfe_resizer_device vpfe_resizer;
+};
+
+/* File handle structure */
+struct vpfe_fh {
+ struct v4l2_fh vfh;
+ struct vpfe_video_device *video;
+ /* Indicates whether this file handle is doing IO */
+ u8 io_allowed;
+};
+
+void mbus_to_pix(const struct v4l2_mbus_framefmt *mbus,
+ struct v4l2_pix_format *pix);
+
+#endif /* _DAVINCI_VPFE_MC_CAPTURE_H */
diff --git a/drivers/staging/media/davinci_vpfe/vpfe_video.c b/drivers/staging/media/davinci_vpfe/vpfe_video.c
new file mode 100644
index 00000000000..d95c427043d
--- /dev/null
+++ b/drivers/staging/media/davinci_vpfe/vpfe_video.c
@@ -0,0 +1,1637 @@
+/*
+ * Copyright (C) 2012 Texas Instruments Inc
+ *
+ * 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 version 2.
+ *
+ * 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
+ *
+ * Contributors:
+ * Manjunath Hadli <manjunath.hadli@ti.com>
+ * Prabhakar Lad <prabhakar.lad@ti.com>
+ */
+
+#include <linux/module.h>
+#include <linux/slab.h>
+
+#include <media/v4l2-ioctl.h>
+
+#include "vpfe.h"
+#include "vpfe_mc_capture.h"
+
+/* minimum number of buffers needed in cont-mode */
+#define MIN_NUM_BUFFERS 3
+
+static int debug;
+
+/* get v4l2 subdev pointer to external subdev which is active */
+static struct media_entity *vpfe_get_input_entity
+ (struct vpfe_video_device *video)
+{
+ struct vpfe_device *vpfe_dev = video->vpfe_dev;
+ struct media_pad *remote;
+
+ remote = media_entity_remote_pad(&vpfe_dev->vpfe_isif.pads[0]);
+ if (remote == NULL) {
+ pr_err("Invalid media connection to isif/ccdc\n");
+ return NULL;
+ }
+ return remote->entity;
+}
+
+/* updates external subdev(sensor/decoder) which is active */
+static int vpfe_update_current_ext_subdev(struct vpfe_video_device *video)
+{
+ struct vpfe_device *vpfe_dev = video->vpfe_dev;
+ struct vpfe_config *vpfe_cfg;
+ struct v4l2_subdev *subdev;
+ struct media_pad *remote;
+ int i;
+
+ remote = media_entity_remote_pad(&vpfe_dev->vpfe_isif.pads[0]);
+ if (remote == NULL) {
+ pr_err("Invalid media connection to isif/ccdc\n");
+ return -EINVAL;
+ }
+
+ subdev = media_entity_to_v4l2_subdev(remote->entity);
+ vpfe_cfg = vpfe_dev->pdev->platform_data;
+ for (i = 0; i < vpfe_cfg->num_subdevs; i++) {
+ if (!strcmp(vpfe_cfg->sub_devs[i].module_name, subdev->name)) {
+ video->current_ext_subdev = &vpfe_cfg->sub_devs[i];
+ break;
+ }
+ }
+
+ /* if user not linked decoder/sensor to isif/ccdc */
+ if (i == vpfe_cfg->num_subdevs) {
+ pr_err("Invalid media chain connection to isif/ccdc\n");
+ return -EINVAL;
+ }
+ /* find the v4l2 subdev pointer */
+ for (i = 0; i < vpfe_dev->num_ext_subdevs; i++) {
+ if (!strcmp(video->current_ext_subdev->module_name,
+ vpfe_dev->sd[i]->name))
+ video->current_ext_subdev->subdev = vpfe_dev->sd[i];
+ }
+ return 0;
+}
+
+/* get the subdev which is connected to the output video node */
+static struct v4l2_subdev *
+vpfe_video_remote_subdev(struct vpfe_video_device *video, u32 *pad)
+{
+ struct media_pad *remote = media_entity_remote_pad(&video->pad);
+
+ if (remote == NULL || remote->entity->type != MEDIA_ENT_T_V4L2_SUBDEV)
+ return NULL;
+ if (pad)
+ *pad = remote->index;
+ return media_entity_to_v4l2_subdev(remote->entity);
+}
+
+/* get the format set at output pad of the adjacent subdev */
+static int
+__vpfe_video_get_format(struct vpfe_video_device *video,
+ struct v4l2_format *format)
+{
+ struct v4l2_subdev_format fmt;
+ struct v4l2_subdev *subdev;
+ struct media_pad *remote;
+ u32 pad;
+ int ret;
+
+ subdev = vpfe_video_remote_subdev(video, &pad);
+ if (subdev == NULL)
+ return -EINVAL;
+
+ fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE;
+ remote = media_entity_remote_pad(&video->pad);
+ fmt.pad = remote->index;
+
+ ret = v4l2_subdev_call(subdev, pad, get_fmt, NULL, &fmt);
+ if (ret == -ENOIOCTLCMD)
+ return -EINVAL;
+
+ format->type = video->type;
+ /* convert mbus_format to v4l2_format */
+ v4l2_fill_pix_format(&format->fmt.pix, &fmt.format);
+ mbus_to_pix(&fmt.format, &format->fmt.pix);
+
+ return 0;
+}
+
+/* make a note of pipeline details */
+static void vpfe_prepare_pipeline(struct vpfe_video_device *video)
+{
+ struct media_entity *entity = &video->video_dev.entity;
+ struct media_device *mdev = entity->parent;
+ struct vpfe_pipeline *pipe = &video->pipe;
+ struct vpfe_video_device *far_end = NULL;
+ struct media_entity_graph graph;
+
+ pipe->input_num = 0;
+ pipe->output_num = 0;
+
+ if (video->type == V4L2_BUF_TYPE_VIDEO_OUTPUT)
+ pipe->inputs[pipe->input_num++] = video;
+ else
+ pipe->outputs[pipe->output_num++] = video;
+
+ mutex_lock(&mdev->graph_mutex);
+ media_entity_graph_walk_start(&graph, entity);
+ while ((entity = media_entity_graph_walk_next(&graph))) {
+ if (entity == &video->video_dev.entity)
+ continue;
+ if (media_entity_type(entity) != MEDIA_ENT_T_DEVNODE)
+ continue;
+ far_end = to_vpfe_video(media_entity_to_video_device(entity));
+ if (far_end->type == V4L2_BUF_TYPE_VIDEO_OUTPUT)
+ pipe->inputs[pipe->input_num++] = far_end;
+ else
+ pipe->outputs[pipe->output_num++] = far_end;
+ }
+ mutex_unlock(&mdev->graph_mutex);
+}
+
+/* update pipe state selected by user */
+static int vpfe_update_pipe_state(struct vpfe_video_device *video)
+{
+ struct vpfe_pipeline *pipe = &video->pipe;
+ int ret;
+
+ vpfe_prepare_pipeline(video);
+
+ /* Find out if there is any input video
+ if yes, it is single shot.
+ */
+ if (pipe->input_num == 0) {
+ pipe->state = VPFE_PIPELINE_STREAM_CONTINUOUS;
+ ret = vpfe_update_current_ext_subdev(video);
+ if (ret) {
+ pr_err("Invalid external subdev\n");
+ return ret;
+ }
+ } else {
+ pipe->state = VPFE_PIPELINE_STREAM_SINGLESHOT;
+ }
+ video->initialized = 1;
+ video->skip_frame_count = 1;
+ video->skip_frame_count_init = 1;
+ return 0;
+}
+
+/* checks wether pipeline is ready for enabling */
+int vpfe_video_is_pipe_ready(struct vpfe_pipeline *pipe)
+{
+ int i;
+
+ for (i = 0; i < pipe->input_num; i++)
+ if (!pipe->inputs[i]->started ||
+ pipe->inputs[i]->state != VPFE_VIDEO_BUFFER_QUEUED)
+ return 0;
+ for (i = 0; i < pipe->output_num; i++)
+ if (!pipe->outputs[i]->started ||
+ pipe->outputs[i]->state != VPFE_VIDEO_BUFFER_QUEUED)
+ return 0;
+ return 1;
+}
+
+/**
+ * Validate a pipeline by checking both ends of all links for format
+ * discrepancies.
+ *
+ * Return 0 if all formats match, or -EPIPE if at least one link is found with
+ * different formats on its two ends.
+ */
+static int vpfe_video_validate_pipeline(struct vpfe_pipeline *pipe)
+{
+ struct v4l2_subdev_format fmt_source;
+ struct v4l2_subdev_format fmt_sink;
+ struct v4l2_subdev *subdev;
+ struct media_pad *pad;
+ int ret;
+
+ /*
+ * Should not matter if it is output[0] or 1 as
+ * the general ideas is to traverse backwards and
+ * the fact that the out video node always has the
+ * format of the connected pad.
+ */
+ subdev = vpfe_video_remote_subdev(pipe->outputs[0], NULL);
+ if (subdev == NULL)
+ return -EPIPE;
+
+ while (1) {
+ /* Retrieve the sink format */
+ pad = &subdev->entity.pads[0];
+ if (!(pad->flags & MEDIA_PAD_FL_SINK))
+ break;
+
+ fmt_sink.which = V4L2_SUBDEV_FORMAT_ACTIVE;
+ fmt_sink.pad = pad->index;
+ ret = v4l2_subdev_call(subdev, pad, get_fmt, NULL,
+ &fmt_sink);
+
+ if (ret < 0 && ret != -ENOIOCTLCMD)
+ return -EPIPE;
+
+ /* Retrieve the source format */
+ pad = media_entity_remote_pad(pad);
+ if (pad == NULL ||
+ pad->entity->type != MEDIA_ENT_T_V4L2_SUBDEV)
+ break;
+
+ subdev = media_entity_to_v4l2_subdev(pad->entity);
+
+ fmt_source.which = V4L2_SUBDEV_FORMAT_ACTIVE;
+ fmt_source.pad = pad->index;
+ ret = v4l2_subdev_call(subdev, pad, get_fmt, NULL, &fmt_source);
+ if (ret < 0 && ret != -ENOIOCTLCMD)
+ return -EPIPE;
+
+ /* Check if the two ends match */
+ if (fmt_source.format.code != fmt_sink.format.code ||
+ fmt_source.format.width != fmt_sink.format.width ||
+ fmt_source.format.height != fmt_sink.format.height)
+ return -EPIPE;
+ }
+ return 0;
+}
+
+/*
+ * vpfe_pipeline_enable() - Enable streaming on a pipeline
+ * @vpfe_dev: vpfe device
+ * @pipe: vpfe pipeline
+ *
+ * Walk the entities chain starting at the pipeline output video node and start
+ * all modules in the chain in the given mode.
+ *
+ * Return 0 if successful, or the return value of the failed video::s_stream
+ * operation otherwise.
+ */
+static int vpfe_pipeline_enable(struct vpfe_pipeline *pipe)
+{
+ struct media_entity_graph graph;
+ struct media_entity *entity;
+ struct v4l2_subdev *subdev;
+ struct media_device *mdev;
+ int ret = 0;
+
+ if (pipe->state == VPFE_PIPELINE_STREAM_CONTINUOUS)
+ entity = vpfe_get_input_entity(pipe->outputs[0]);
+ else
+ entity = &pipe->inputs[0]->video_dev.entity;
+
+ mdev = entity->parent;
+ mutex_lock(&mdev->graph_mutex);
+ media_entity_graph_walk_start(&graph, entity);
+ while ((entity = media_entity_graph_walk_next(&graph))) {
+
+ if (media_entity_type(entity) == MEDIA_ENT_T_DEVNODE)
+ continue;
+ subdev = media_entity_to_v4l2_subdev(entity);
+ ret = v4l2_subdev_call(subdev, video, s_stream, 1);
+ if (ret < 0 && ret != -ENOIOCTLCMD)
+ break;
+ }
+ mutex_unlock(&mdev->graph_mutex);
+ return ret;
+}
+
+/*
+ * vpfe_pipeline_disable() - Disable streaming on a pipeline
+ * @vpfe_dev: vpfe device
+ * @pipe: VPFE pipeline
+ *
+ * Walk the entities chain starting at the pipeline output video node and stop
+ * all modules in the chain.
+ *
+ * Return 0 if all modules have been properly stopped, or -ETIMEDOUT if a module
+ * can't be stopped.
+ */
+static int vpfe_pipeline_disable(struct vpfe_pipeline *pipe)
+{
+ struct media_entity_graph graph;
+ struct media_entity *entity;
+ struct v4l2_subdev *subdev;
+ struct media_device *mdev;
+ int ret = 0;
+
+ if (pipe->state == VPFE_PIPELINE_STREAM_CONTINUOUS)
+ entity = vpfe_get_input_entity(pipe->outputs[0]);
+ else
+ entity = &pipe->inputs[0]->video_dev.entity;
+
+ mdev = entity->parent;
+ mutex_lock(&mdev->graph_mutex);
+ media_entity_graph_walk_start(&graph, entity);
+
+ while ((entity = media_entity_graph_walk_next(&graph))) {
+
+ if (media_entity_type(entity) == MEDIA_ENT_T_DEVNODE)
+ continue;
+ subdev = media_entity_to_v4l2_subdev(entity);
+ ret = v4l2_subdev_call(subdev, video, s_stream, 0);
+ if (ret < 0 && ret != -ENOIOCTLCMD)
+ break;
+ }
+ mutex_unlock(&mdev->graph_mutex);
+
+ return ret ? -ETIMEDOUT : 0;
+}
+
+/*
+ * vpfe_pipeline_set_stream() - Enable/disable streaming on a pipeline
+ * @vpfe_dev: VPFE device
+ * @pipe: VPFE pipeline
+ * @state: Stream state (stopped or active)
+ *
+ * Set the pipeline to the given stream state.
+ *
+ * Return 0 if successful, or the return value of the failed video::s_stream
+ * operation otherwise.
+ */
+static int vpfe_pipeline_set_stream(struct vpfe_pipeline *pipe,
+ enum vpfe_pipeline_stream_state state)
+{
+ if (state == VPFE_PIPELINE_STREAM_STOPPED)
+ return vpfe_pipeline_disable(pipe);
+
+ return vpfe_pipeline_enable(pipe);
+}
+
+static int all_videos_stopped(struct vpfe_video_device *video)
+{
+ struct vpfe_pipeline *pipe = &video->pipe;
+ int i;
+
+ for (i = 0; i < pipe->input_num; i++)
+ if (pipe->inputs[i]->started)
+ return 0;
+ for (i = 0; i < pipe->output_num; i++)
+ if (pipe->outputs[i]->started)
+ return 0;
+ return 1;
+}
+
+/*
+ * vpfe_open() - open video device
+ * @file: file pointer
+ *
+ * initialize media pipeline state, allocate memory for file handle
+ *
+ * Return 0 if successful, or the return -ENODEV otherwise.
+ */
+static int vpfe_open(struct file *file)
+{
+ struct vpfe_video_device *video = video_drvdata(file);
+ struct vpfe_fh *handle;
+
+ /* Allocate memory for the file handle object */
+ handle = kzalloc(sizeof(struct vpfe_fh), GFP_KERNEL);
+
+ if (handle == NULL)
+ return -ENOMEM;
+
+ v4l2_fh_init(&handle->vfh, &video->video_dev);
+ v4l2_fh_add(&handle->vfh);
+
+ mutex_lock(&video->lock);
+ /* If decoder is not initialized. initialize it */
+ if (!video->initialized && vpfe_update_pipe_state(video)) {
+ mutex_unlock(&video->lock);
+ return -ENODEV;
+ }
+ /* Increment device users counter */
+ video->usrs++;
+ /* Set io_allowed member to false */
+ handle->io_allowed = 0;
+ handle->video = video;
+ file->private_data = &handle->vfh;
+ mutex_unlock(&video->lock);
+
+ return 0;
+}
+
+/* get the next buffer available from dma queue */
+static unsigned long
+vpfe_video_get_next_buffer(struct vpfe_video_device *video)
+{
+ video->cur_frm = video->next_frm =
+ list_entry(video->dma_queue.next,
+ struct vpfe_cap_buffer, list);
+
+ list_del(&video->next_frm->list);
+ video->next_frm->vb.state = VB2_BUF_STATE_ACTIVE;
+ return vb2_dma_contig_plane_dma_addr(&video->next_frm->vb, 0);
+}
+
+/* schedule the next buffer which is available on dma queue */
+void vpfe_video_schedule_next_buffer(struct vpfe_video_device *video)
+{
+ struct vpfe_device *vpfe_dev = video->vpfe_dev;
+ unsigned long addr;
+
+ if (list_empty(&video->dma_queue))
+ return;
+
+ video->next_frm = list_entry(video->dma_queue.next,
+ struct vpfe_cap_buffer, list);
+
+ if (VPFE_PIPELINE_STREAM_SINGLESHOT == video->pipe.state)
+ video->cur_frm = video->next_frm;
+
+ list_del(&video->next_frm->list);
+ video->next_frm->vb.state = VB2_BUF_STATE_ACTIVE;
+ addr = vb2_dma_contig_plane_dma_addr(&video->next_frm->vb, 0);
+ video->ops->queue(vpfe_dev, addr);
+ video->state = VPFE_VIDEO_BUFFER_QUEUED;
+}
+
+/* schedule the buffer for capturing bottom field */
+void vpfe_video_schedule_bottom_field(struct vpfe_video_device *video)
+{
+ struct vpfe_device *vpfe_dev = video->vpfe_dev;
+ unsigned long addr;
+
+ addr = vb2_dma_contig_plane_dma_addr(&video->cur_frm->vb, 0);
+ addr += video->field_off;
+ video->ops->queue(vpfe_dev, addr);
+}
+
+/* make buffer available for dequeue */
+void vpfe_video_process_buffer_complete(struct vpfe_video_device *video)
+{
+ struct vpfe_pipeline *pipe = &video->pipe;
+
+ do_gettimeofday(&video->cur_frm->vb.v4l2_buf.timestamp);
+ vb2_buffer_done(&video->cur_frm->vb, VB2_BUF_STATE_DONE);
+ if (pipe->state == VPFE_PIPELINE_STREAM_CONTINUOUS)
+ video->cur_frm = video->next_frm;
+}
+
+/* vpfe_stop_capture() - stop streaming */
+static void vpfe_stop_capture(struct vpfe_video_device *video)
+{
+ struct vpfe_pipeline *pipe = &video->pipe;
+
+ video->started = 0;
+
+ if (video->type == V4L2_BUF_TYPE_VIDEO_OUTPUT)
+ return;
+ if (all_videos_stopped(video))
+ vpfe_pipeline_set_stream(pipe,
+ VPFE_PIPELINE_STREAM_STOPPED);
+}
+
+/*
+ * vpfe_release() - release video device
+ * @file: file pointer
+ *
+ * deletes buffer queue, frees the buffers and the vpfe file handle
+ *
+ * Return 0
+ */
+static int vpfe_release(struct file *file)
+{
+ struct vpfe_video_device *video = video_drvdata(file);
+ struct v4l2_fh *vfh = file->private_data;
+ struct vpfe_device *vpfe_dev = video->vpfe_dev;
+ struct vpfe_fh *fh = container_of(vfh, struct vpfe_fh, vfh);
+
+ v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_release\n");
+
+ /* Get the device lock */
+ mutex_lock(&video->lock);
+ /* if this instance is doing IO */
+ if (fh->io_allowed) {
+ if (video->started) {
+ vpfe_stop_capture(video);
+ /* mark pipe state as stopped in vpfe_release(),
+ as app might call streamon() after streamoff()
+ in which case driver has to start streaming.
+ */
+ video->pipe.state = VPFE_PIPELINE_STREAM_STOPPED;
+ vb2_streamoff(&video->buffer_queue,
+ video->buffer_queue.type);
+ }
+ video->io_usrs = 0;
+ /* Free buffers allocated */
+ vb2_queue_release(&video->buffer_queue);
+ vb2_dma_contig_cleanup_ctx(video->alloc_ctx);
+ }
+ /* Decrement device users counter */
+ video->usrs--;
+ v4l2_fh_del(&fh->vfh);
+ v4l2_fh_exit(&fh->vfh);
+ /* If this is the last file handle */
+ if (!video->usrs)
+ video->initialized = 0;
+ mutex_unlock(&video->lock);
+ file->private_data = NULL;
+ /* Free memory allocated to file handle object */
+ v4l2_fh_del(vfh);
+ kzfree(fh);
+ return 0;
+}
+
+/*
+ * vpfe_mmap() - It is used to map kernel space buffers
+ * into user spaces
+ */
+static int vpfe_mmap(struct file *file, struct vm_area_struct *vma)
+{
+ struct vpfe_video_device *video = video_drvdata(file);
+ struct vpfe_device *vpfe_dev = video->vpfe_dev;
+
+ v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_mmap\n");
+ return vb2_mmap(&video->buffer_queue, vma);
+}
+
+/*
+ * vpfe_poll() - It is used for select/poll system call
+ */
+static unsigned int vpfe_poll(struct file *file, poll_table *wait)
+{
+ struct vpfe_video_device *video = video_drvdata(file);
+ struct vpfe_device *vpfe_dev = video->vpfe_dev;
+
+ v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_poll\n");
+ if (video->started)
+ return vb2_poll(&video->buffer_queue, file, wait);
+ return 0;
+}
+
+/* vpfe capture driver file operations */
+static const struct v4l2_file_operations vpfe_fops = {
+ .owner = THIS_MODULE,
+ .open = vpfe_open,
+ .release = vpfe_release,
+ .unlocked_ioctl = video_ioctl2,
+ .mmap = vpfe_mmap,
+ .poll = vpfe_poll
+};
+
+/*
+ * vpfe_querycap() - query capabilities of video device
+ * @file: file pointer
+ * @priv: void pointer
+ * @cap: pointer to v4l2_capability structure
+ *
+ * fills v4l2 capabilities structure
+ *
+ * Return 0
+ */
+static int vpfe_querycap(struct file *file, void *priv,
+ struct v4l2_capability *cap)
+{
+ struct vpfe_video_device *video = video_drvdata(file);
+ struct vpfe_device *vpfe_dev = video->vpfe_dev;
+
+ v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_querycap\n");
+
+ if (video->type == V4L2_BUF_TYPE_VIDEO_CAPTURE)
+ cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;
+ else
+ cap->capabilities = V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_STREAMING;
+ cap->device_caps = cap->capabilities;
+ cap->version = VPFE_CAPTURE_VERSION_CODE;
+ strlcpy(cap->driver, CAPTURE_DRV_NAME, sizeof(cap->driver));
+ strlcpy(cap->bus_info, "VPFE", sizeof(cap->bus_info));
+ strlcpy(cap->card, vpfe_dev->cfg->card_name, sizeof(cap->card));
+
+ return 0;
+}
+
+/*
+ * vpfe_g_fmt() - get the format which is active on video device
+ * @file: file pointer
+ * @priv: void pointer
+ * @fmt: pointer to v4l2_format structure
+ *
+ * fills v4l2 format structure with active format
+ *
+ * Return 0
+ */
+static int vpfe_g_fmt(struct file *file, void *priv,
+ struct v4l2_format *fmt)
+{
+ struct vpfe_video_device *video = video_drvdata(file);
+ struct vpfe_device *vpfe_dev = video->vpfe_dev;
+
+ v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_g_fmt\n");
+ /* Fill in the information about format */
+ *fmt = video->fmt;
+ return 0;
+}
+
+/*
+ * vpfe_enum_fmt() - enum formats supported on media chain
+ * @file: file pointer
+ * @priv: void pointer
+ * @fmt: pointer to v4l2_fmtdesc structure
+ *
+ * fills v4l2_fmtdesc structure with output format set on adjacent subdev,
+ * only one format is enumearted as subdevs are already configured
+ *
+ * Return 0 if successful, error code otherwise
+ */
+static int vpfe_enum_fmt(struct file *file, void *priv,
+ struct v4l2_fmtdesc *fmt)
+{
+ struct vpfe_video_device *video = video_drvdata(file);
+ struct vpfe_device *vpfe_dev = video->vpfe_dev;
+ struct v4l2_subdev_format sd_fmt;
+ struct v4l2_mbus_framefmt mbus;
+ struct v4l2_subdev *subdev;
+ struct v4l2_format format;
+ struct media_pad *remote;
+ int ret;
+
+ v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_enum_fmt\n");
+
+ /* since already subdev pad format is set,
+ only one pixel format is available */
+ if (fmt->index > 0) {
+ v4l2_err(&vpfe_dev->v4l2_dev, "Invalid index\n");
+ return -EINVAL;
+ }
+ /* get the remote pad */
+ remote = media_entity_remote_pad(&video->pad);
+ if (remote == NULL) {
+ v4l2_err(&vpfe_dev->v4l2_dev,
+ "invalid remote pad for video node\n");
+ return -EINVAL;
+ }
+ /* get the remote subdev */
+ subdev = vpfe_video_remote_subdev(video, NULL);
+ if (subdev == NULL) {
+ v4l2_err(&vpfe_dev->v4l2_dev,
+ "invalid remote subdev for video node\n");
+ return -EINVAL;
+ }
+ sd_fmt.pad = remote->index;
+ sd_fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE;
+ /* get output format of remote subdev */
+ ret = v4l2_subdev_call(subdev, pad, get_fmt, NULL, &sd_fmt);
+ if (ret) {
+ v4l2_err(&vpfe_dev->v4l2_dev,
+ "invalid remote subdev for video node\n");
+ return ret;
+ }
+ /* convert to pix format */
+ mbus.code = sd_fmt.format.code;
+ mbus_to_pix(&mbus, &format.fmt.pix);
+ /* copy the result */
+ fmt->pixelformat = format.fmt.pix.pixelformat;
+
+ return 0;
+}
+
+/*
+ * vpfe_s_fmt() - set the format on video device
+ * @file: file pointer
+ * @priv: void pointer
+ * @fmt: pointer to v4l2_format structure
+ *
+ * validate and set the format on video device
+ *
+ * Return 0 on success, error code otherwise
+ */
+static int vpfe_s_fmt(struct file *file, void *priv,
+ struct v4l2_format *fmt)
+{
+ struct vpfe_video_device *video = video_drvdata(file);
+ struct vpfe_device *vpfe_dev = video->vpfe_dev;
+ struct v4l2_format format;
+ int ret;
+
+ v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_s_fmt\n");
+ /* If streaming is started, return error */
+ if (video->started) {
+ v4l2_err(&vpfe_dev->v4l2_dev, "Streaming is started\n");
+ return -EBUSY;
+ }
+ /* get adjacent subdev's output pad format */
+ ret = __vpfe_video_get_format(video, &format);
+ if (ret)
+ return ret;
+ *fmt = format;
+ video->fmt = *fmt;
+ return 0;
+}
+
+/*
+ * vpfe_try_fmt() - try the format on video device
+ * @file: file pointer
+ * @priv: void pointer
+ * @fmt: pointer to v4l2_format structure
+ *
+ * validate the format, update with correct format
+ * based on output format set on adjacent subdev
+ *
+ * Return 0 on success, error code otherwise
+ */
+static int vpfe_try_fmt(struct file *file, void *priv,
+ struct v4l2_format *fmt)
+{
+ struct vpfe_video_device *video = video_drvdata(file);
+ struct vpfe_device *vpfe_dev = video->vpfe_dev;
+ struct v4l2_format format;
+ int ret;
+
+ v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_try_fmt\n");
+ /* get adjacent subdev's output pad format */
+ ret = __vpfe_video_get_format(video, &format);
+ if (ret)
+ return ret;
+
+ *fmt = format;
+ return 0;
+}
+
+/*
+ * vpfe_enum_input() - enum inputs supported on media chain
+ * @file: file pointer
+ * @priv: void pointer
+ * @fmt: pointer to v4l2_fmtdesc structure
+ *
+ * fills v4l2_input structure with input available on media chain,
+ * only one input is enumearted as media chain is setup by this time
+ *
+ * Return 0 if successful, -EINVAL is media chain is invalid
+ */
+static int vpfe_enum_input(struct file *file, void *priv,
+ struct v4l2_input *inp)
+{
+ struct vpfe_video_device *video = video_drvdata(file);
+ struct vpfe_ext_subdev_info *sdinfo = video->current_ext_subdev;
+ struct vpfe_device *vpfe_dev = video->vpfe_dev;
+
+ v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_enum_input\n");
+ /* enumerate from the subdev user has chosen through mc */
+ if (inp->index < sdinfo->num_inputs) {
+ memcpy(inp, &sdinfo->inputs[inp->index],
+ sizeof(struct v4l2_input));
+ return 0;
+ }
+ return -EINVAL;
+}
+
+/*
+ * vpfe_g_input() - get index of the input which is active
+ * @file: file pointer
+ * @priv: void pointer
+ * @index: pointer to unsigned int
+ *
+ * set index with input index which is active
+ */
+static int vpfe_g_input(struct file *file, void *priv, unsigned int *index)
+{
+ struct vpfe_video_device *video = video_drvdata(file);
+ struct vpfe_device *vpfe_dev = video->vpfe_dev;
+
+ v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_g_input\n");
+
+ *index = video->current_input;
+ return 0;
+}
+
+/*
+ * vpfe_s_input() - set input which is pointed by input index
+ * @file: file pointer
+ * @priv: void pointer
+ * @index: pointer to unsigned int
+ *
+ * set input on external subdev
+ *
+ * Return 0 on success, error code otherwise
+ */
+static int vpfe_s_input(struct file *file, void *priv, unsigned int index)
+{
+ struct vpfe_video_device *video = video_drvdata(file);
+ struct vpfe_device *vpfe_dev = video->vpfe_dev;
+ struct vpfe_ext_subdev_info *sdinfo;
+ struct vpfe_route *route;
+ struct v4l2_input *inps;
+ u32 output;
+ u32 input;
+ int ret;
+ int i;
+
+ v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_s_input\n");
+
+ ret = mutex_lock_interruptible(&video->lock);
+ if (ret)
+ return ret;
+ /*
+ * If streaming is started return device busy
+ * error
+ */
+ if (video->started) {
+ v4l2_err(&vpfe_dev->v4l2_dev, "Streaming is on\n");
+ ret = -EBUSY;
+ goto unlock_out;
+ }
+
+ sdinfo = video->current_ext_subdev;
+ if (!sdinfo->registered) {
+ ret = -EINVAL;
+ goto unlock_out;
+ }
+ if (vpfe_dev->cfg->setup_input &&
+ vpfe_dev->cfg->setup_input(sdinfo->grp_id) < 0) {
+ ret = -EFAULT;
+ v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev,
+ "couldn't setup input for %s\n",
+ sdinfo->module_name);
+ goto unlock_out;
+ }
+ route = &sdinfo->routes[index];
+ if (route && sdinfo->can_route) {
+ input = route->input;
+ output = route->output;
+ ret = v4l2_device_call_until_err(&vpfe_dev->v4l2_dev,
+ sdinfo->grp_id, video,
+ s_routing, input, output, 0);
+ if (ret) {
+ v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev,
+ "s_input:error in setting input in decoder\n");
+ ret = -EINVAL;
+ goto unlock_out;
+ }
+ }
+ /* set standards set by subdev in video device */
+ for (i = 0; i < sdinfo->num_inputs; i++) {
+ inps = &sdinfo->inputs[i];
+ video->video_dev.tvnorms |= inps->std;
+ }
+ video->current_input = index;
+unlock_out:
+ mutex_unlock(&video->lock);
+ return ret;
+}
+
+/*
+ * vpfe_querystd() - query std which is being input on external subdev
+ * @file: file pointer
+ * @priv: void pointer
+ * @std_id: pointer to v4l2_std_id structure
+ *
+ * call external subdev through v4l2_device_call_until_err to
+ * get the std that is being active.
+ *
+ * Return 0 on success, error code otherwise
+ */
+static int vpfe_querystd(struct file *file, void *priv, v4l2_std_id *std_id)
+{
+ struct vpfe_video_device *video = video_drvdata(file);
+ struct vpfe_device *vpfe_dev = video->vpfe_dev;
+ struct vpfe_ext_subdev_info *sdinfo;
+ int ret;
+
+ v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_querystd\n");
+
+ ret = mutex_lock_interruptible(&video->lock);
+ sdinfo = video->current_ext_subdev;
+ if (ret)
+ return ret;
+ /* Call querystd function of decoder device */
+ ret = v4l2_device_call_until_err(&vpfe_dev->v4l2_dev, sdinfo->grp_id,
+ video, querystd, std_id);
+ mutex_unlock(&video->lock);
+ return ret;
+}
+
+/*
+ * vpfe_s_std() - set std on external subdev
+ * @file: file pointer
+ * @priv: void pointer
+ * @std_id: pointer to v4l2_std_id structure
+ *
+ * set std pointed by std_id on external subdev by calling it using
+ * v4l2_device_call_until_err
+ *
+ * Return 0 on success, error code otherwise
+ */
+static int vpfe_s_std(struct file *file, void *priv, v4l2_std_id std_id)
+{
+ struct vpfe_video_device *video = video_drvdata(file);
+ struct vpfe_device *vpfe_dev = video->vpfe_dev;
+ struct vpfe_ext_subdev_info *sdinfo;
+ int ret;
+
+ v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_s_std\n");
+
+ /* Call decoder driver function to set the standard */
+ ret = mutex_lock_interruptible(&video->lock);
+ if (ret)
+ return ret;
+ sdinfo = video->current_ext_subdev;
+ /* If streaming is started, return device busy error */
+ if (video->started) {
+ v4l2_err(&vpfe_dev->v4l2_dev, "streaming is started\n");
+ ret = -EBUSY;
+ goto unlock_out;
+ }
+ ret = v4l2_device_call_until_err(&vpfe_dev->v4l2_dev, sdinfo->grp_id,
+ video, s_std, std_id);
+ if (ret < 0) {
+ v4l2_err(&vpfe_dev->v4l2_dev, "Failed to set standard\n");
+ video->stdid = V4L2_STD_UNKNOWN;
+ goto unlock_out;
+ }
+ video->stdid = std_id;
+unlock_out:
+ mutex_unlock(&video->lock);
+ return ret;
+}
+
+static int vpfe_g_std(struct file *file, void *priv, v4l2_std_id *tvnorm)
+{
+ struct vpfe_video_device *video = video_drvdata(file);
+ struct vpfe_device *vpfe_dev = video->vpfe_dev;
+
+ v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_g_std\n");
+ *tvnorm = video->stdid;
+ return 0;
+}
+
+/*
+ * vpfe_enum_dv_timings() - enumerate dv_timings which are supported by
+ * to external subdev
+ * @file: file pointer
+ * @priv: void pointer
+ * @timings: pointer to v4l2_enum_dv_timings structure
+ *
+ * enum dv_timings's which are supported by external subdev through
+ * v4l2_subdev_call
+ *
+ * Return 0 on success, error code otherwise
+ */
+static int
+vpfe_enum_dv_timings(struct file *file, void *fh,
+ struct v4l2_enum_dv_timings *timings)
+{
+ struct vpfe_video_device *video = video_drvdata(file);
+ struct vpfe_device *vpfe_dev = video->vpfe_dev;
+ struct v4l2_subdev *subdev = video->current_ext_subdev->subdev;
+
+ timings->pad = 0;
+
+ v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_enum_dv_timings\n");
+ return v4l2_subdev_call(subdev, pad, enum_dv_timings, timings);
+}
+
+/*
+ * vpfe_query_dv_timings() - query the dv_timings which is being input
+ * to external subdev
+ * @file: file pointer
+ * @priv: void pointer
+ * @timings: pointer to v4l2_dv_timings structure
+ *
+ * get dv_timings which is being input on external subdev through
+ * v4l2_subdev_call
+ *
+ * Return 0 on success, error code otherwise
+ */
+static int
+vpfe_query_dv_timings(struct file *file, void *fh,
+ struct v4l2_dv_timings *timings)
+{
+ struct vpfe_video_device *video = video_drvdata(file);
+ struct vpfe_device *vpfe_dev = video->vpfe_dev;
+ struct v4l2_subdev *subdev = video->current_ext_subdev->subdev;
+
+ v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_query_dv_timings\n");
+ return v4l2_subdev_call(subdev, video, query_dv_timings, timings);
+}
+
+/*
+ * vpfe_s_dv_timings() - set dv_timings on external subdev
+ * @file: file pointer
+ * @priv: void pointer
+ * @timings: pointer to v4l2_dv_timings structure
+ *
+ * set dv_timings pointed by timings on external subdev through
+ * v4l2_device_call_until_err, this configures amplifier also
+ *
+ * Return 0 on success, error code otherwise
+ */
+static int
+vpfe_s_dv_timings(struct file *file, void *fh,
+ struct v4l2_dv_timings *timings)
+{
+ struct vpfe_video_device *video = video_drvdata(file);
+ struct vpfe_device *vpfe_dev = video->vpfe_dev;
+
+ v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_s_dv_timings\n");
+
+ video->stdid = V4L2_STD_UNKNOWN;
+ return v4l2_device_call_until_err(&vpfe_dev->v4l2_dev,
+ video->current_ext_subdev->grp_id,
+ video, s_dv_timings, timings);
+}
+
+/*
+ * vpfe_g_dv_timings() - get dv_timings which is set on external subdev
+ * @file: file pointer
+ * @priv: void pointer
+ * @timings: pointer to v4l2_dv_timings structure
+ *
+ * get dv_timings which is set on external subdev through
+ * v4l2_subdev_call
+ *
+ * Return 0 on success, error code otherwise
+ */
+static int
+vpfe_g_dv_timings(struct file *file, void *fh,
+ struct v4l2_dv_timings *timings)
+{
+ struct vpfe_video_device *video = video_drvdata(file);
+ struct vpfe_device *vpfe_dev = video->vpfe_dev;
+ struct v4l2_subdev *subdev = video->current_ext_subdev->subdev;
+
+ v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_g_dv_timings\n");
+ return v4l2_subdev_call(subdev, video, g_dv_timings, timings);
+}
+
+/*
+ * Videobuf operations
+ */
+/*
+ * vpfe_buffer_queue_setup : Callback function for buffer setup.
+ * @vq: vb2_queue ptr
+ * @fmt: v4l2 format
+ * @nbuffers: ptr to number of buffers requested by application
+ * @nplanes:: contains number of distinct video planes needed to hold a frame
+ * @sizes[]: contains the size (in bytes) of each plane.
+ * @alloc_ctxs: ptr to allocation context
+ *
+ * This callback function is called when reqbuf() is called to adjust
+ * the buffer nbuffers and buffer size
+ */
+static int
+vpfe_buffer_queue_setup(struct vb2_queue *vq, const struct v4l2_format *fmt,
+ unsigned int *nbuffers, unsigned int *nplanes,
+ unsigned int sizes[], void *alloc_ctxs[])
+{
+ struct vpfe_fh *fh = vb2_get_drv_priv(vq);
+ struct vpfe_video_device *video = fh->video;
+ struct vpfe_device *vpfe_dev = video->vpfe_dev;
+ struct vpfe_pipeline *pipe = &video->pipe;
+ unsigned long size;
+
+ v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_buffer_queue_setup\n");
+ size = video->fmt.fmt.pix.sizeimage;
+
+ if (vpfe_dev->video_limit) {
+ while (size * *nbuffers > vpfe_dev->video_limit)
+ (*nbuffers)--;
+ }
+ if (pipe->state == VPFE_PIPELINE_STREAM_CONTINUOUS) {
+ if (*nbuffers < MIN_NUM_BUFFERS)
+ *nbuffers = MIN_NUM_BUFFERS;
+ }
+ *nplanes = 1;
+ sizes[0] = size;
+ alloc_ctxs[0] = video->alloc_ctx;
+ v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev,
+ "nbuffers=%d, size=%lu\n", *nbuffers, size);
+ return 0;
+}
+
+/*
+ * vpfe_buffer_prepare : callback function for buffer prepare
+ * @vb: ptr to vb2_buffer
+ *
+ * This is the callback function for buffer prepare when vb2_qbuf()
+ * function is called. The buffer is prepared and user space virtual address
+ * or user address is converted into physical address
+ */
+static int vpfe_buffer_prepare(struct vb2_buffer *vb)
+{
+ struct vpfe_fh *fh = vb2_get_drv_priv(vb->vb2_queue);
+ struct vpfe_video_device *video = fh->video;
+ struct vpfe_device *vpfe_dev = video->vpfe_dev;
+ unsigned long addr;
+
+ v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_buffer_prepare\n");
+
+ if (vb->state != VB2_BUF_STATE_ACTIVE &&
+ vb->state != VB2_BUF_STATE_PREPARED)
+ return 0;
+
+ /* Initialize buffer */
+ vb2_set_plane_payload(vb, 0, video->fmt.fmt.pix.sizeimage);
+ if (vb2_plane_vaddr(vb, 0) &&
+ vb2_get_plane_payload(vb, 0) > vb2_plane_size(vb, 0))
+ return -EINVAL;
+
+ addr = vb2_dma_contig_plane_dma_addr(vb, 0);
+ /* Make sure user addresses are aligned to 32 bytes */
+ if (!ALIGN(addr, 32))
+ return -EINVAL;
+
+ return 0;
+}
+
+static void vpfe_buffer_queue(struct vb2_buffer *vb)
+{
+ /* Get the file handle object and device object */
+ struct vpfe_fh *fh = vb2_get_drv_priv(vb->vb2_queue);
+ struct vpfe_video_device *video = fh->video;
+ struct vpfe_device *vpfe_dev = video->vpfe_dev;
+ struct vpfe_pipeline *pipe = &video->pipe;
+ struct vpfe_cap_buffer *buf = container_of(vb,
+ struct vpfe_cap_buffer, vb);
+ unsigned long flags;
+ unsigned long empty;
+ unsigned long addr;
+
+ spin_lock_irqsave(&video->dma_queue_lock, flags);
+ empty = list_empty(&video->dma_queue);
+ /* add the buffer to the DMA queue */
+ list_add_tail(&buf->list, &video->dma_queue);
+ spin_unlock_irqrestore(&video->dma_queue_lock, flags);
+ /* this case happens in case of single shot */
+ if (empty && video->started && pipe->state ==
+ VPFE_PIPELINE_STREAM_SINGLESHOT &&
+ video->state == VPFE_VIDEO_BUFFER_NOT_QUEUED) {
+ spin_lock(&video->dma_queue_lock);
+ addr = vpfe_video_get_next_buffer(video);
+ video->ops->queue(vpfe_dev, addr);
+
+ video->state = VPFE_VIDEO_BUFFER_QUEUED;
+ spin_unlock(&video->dma_queue_lock);
+
+ /* enable h/w each time in single shot */
+ if (vpfe_video_is_pipe_ready(pipe))
+ vpfe_pipeline_set_stream(pipe,
+ VPFE_PIPELINE_STREAM_SINGLESHOT);
+ }
+}
+
+/* vpfe_start_capture() - start streaming on all the subdevs */
+static int vpfe_start_capture(struct vpfe_video_device *video)
+{
+ struct vpfe_pipeline *pipe = &video->pipe;
+ int ret = 0;
+
+ video->started = 1;
+ if (vpfe_video_is_pipe_ready(pipe))
+ ret = vpfe_pipeline_set_stream(pipe, pipe->state);
+
+ return ret;
+}
+
+static int vpfe_start_streaming(struct vb2_queue *vq, unsigned int count)
+{
+ struct vpfe_fh *fh = vb2_get_drv_priv(vq);
+ struct vpfe_video_device *video = fh->video;
+ struct vpfe_device *vpfe_dev = video->vpfe_dev;
+ unsigned long addr;
+ int ret;
+
+ ret = mutex_lock_interruptible(&video->lock);
+ if (ret)
+ goto streamoff;
+
+ /* Get the next frame from the buffer queue */
+ video->cur_frm = video->next_frm =
+ list_entry(video->dma_queue.next, struct vpfe_cap_buffer, list);
+ /* Remove buffer from the buffer queue */
+ list_del(&video->cur_frm->list);
+ /* Mark state of the current frame to active */
+ video->cur_frm->vb.state = VB2_BUF_STATE_ACTIVE;
+ /* Initialize field_id and started member */
+ video->field_id = 0;
+ addr = vb2_dma_contig_plane_dma_addr(&video->cur_frm->vb, 0);
+ video->ops->queue(vpfe_dev, addr);
+ video->state = VPFE_VIDEO_BUFFER_QUEUED;
+
+ ret = vpfe_start_capture(video);
+ if (ret) {
+ struct vpfe_cap_buffer *buf, *tmp;
+
+ vb2_buffer_done(&video->cur_frm->vb, VB2_BUF_STATE_QUEUED);
+ list_for_each_entry_safe(buf, tmp, &video->dma_queue, list) {
+ list_del(&buf->list);
+ vb2_buffer_done(&buf->vb, VB2_BUF_STATE_QUEUED);
+ }
+ goto unlock_out;
+ }
+
+ mutex_unlock(&video->lock);
+
+ return ret;
+unlock_out:
+ mutex_unlock(&video->lock);
+streamoff:
+ ret = vb2_streamoff(&video->buffer_queue, video->buffer_queue.type);
+ return 0;
+}
+
+static int vpfe_buffer_init(struct vb2_buffer *vb)
+{
+ struct vpfe_cap_buffer *buf = container_of(vb,
+ struct vpfe_cap_buffer, vb);
+
+ INIT_LIST_HEAD(&buf->list);
+ return 0;
+}
+
+/* abort streaming and wait for last buffer */
+static void vpfe_stop_streaming(struct vb2_queue *vq)
+{
+ struct vpfe_fh *fh = vb2_get_drv_priv(vq);
+ struct vpfe_video_device *video = fh->video;
+
+ /* release all active buffers */
+ if (video->cur_frm == video->next_frm) {
+ vb2_buffer_done(&video->cur_frm->vb, VB2_BUF_STATE_ERROR);
+ } else {
+ if (video->cur_frm != NULL)
+ vb2_buffer_done(&video->cur_frm->vb,
+ VB2_BUF_STATE_ERROR);
+ if (video->next_frm != NULL)
+ vb2_buffer_done(&video->next_frm->vb,
+ VB2_BUF_STATE_ERROR);
+ }
+
+ while (!list_empty(&video->dma_queue)) {
+ video->next_frm = list_entry(video->dma_queue.next,
+ struct vpfe_cap_buffer, list);
+ list_del(&video->next_frm->list);
+ vb2_buffer_done(&video->next_frm->vb, VB2_BUF_STATE_ERROR);
+ }
+}
+
+static void vpfe_buf_cleanup(struct vb2_buffer *vb)
+{
+ struct vpfe_fh *fh = vb2_get_drv_priv(vb->vb2_queue);
+ struct vpfe_video_device *video = fh->video;
+ struct vpfe_device *vpfe_dev = video->vpfe_dev;
+ struct vpfe_cap_buffer *buf = container_of(vb,
+ struct vpfe_cap_buffer, vb);
+
+ v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_buf_cleanup\n");
+ if (vb->state == VB2_BUF_STATE_ACTIVE)
+ list_del_init(&buf->list);
+}
+
+static struct vb2_ops video_qops = {
+ .queue_setup = vpfe_buffer_queue_setup,
+ .buf_init = vpfe_buffer_init,
+ .buf_prepare = vpfe_buffer_prepare,
+ .start_streaming = vpfe_start_streaming,
+ .stop_streaming = vpfe_stop_streaming,
+ .buf_cleanup = vpfe_buf_cleanup,
+ .buf_queue = vpfe_buffer_queue,
+};
+
+/*
+ * vpfe_reqbufs() - supported REQBUF only once opening
+ * the device.
+ */
+static int vpfe_reqbufs(struct file *file, void *priv,
+ struct v4l2_requestbuffers *req_buf)
+{
+ struct vpfe_video_device *video = video_drvdata(file);
+ struct vpfe_device *vpfe_dev = video->vpfe_dev;
+ struct vpfe_fh *fh = file->private_data;
+ struct vb2_queue *q;
+ int ret;
+
+ v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_reqbufs\n");
+
+ if (V4L2_BUF_TYPE_VIDEO_CAPTURE != req_buf->type &&
+ V4L2_BUF_TYPE_VIDEO_OUTPUT != req_buf->type) {
+ v4l2_err(&vpfe_dev->v4l2_dev, "Invalid buffer type\n");
+ return -EINVAL;
+ }
+
+ ret = mutex_lock_interruptible(&video->lock);
+ if (ret)
+ return ret;
+
+ if (video->io_usrs != 0) {
+ v4l2_err(&vpfe_dev->v4l2_dev, "Only one IO user allowed\n");
+ ret = -EBUSY;
+ goto unlock_out;
+ }
+ video->memory = req_buf->memory;
+
+ /* Initialize videobuf2 queue as per the buffer type */
+ video->alloc_ctx = vb2_dma_contig_init_ctx(vpfe_dev->pdev);
+ if (IS_ERR(video->alloc_ctx)) {
+ v4l2_err(&vpfe_dev->v4l2_dev, "Failed to get the context\n");
+ return PTR_ERR(video->alloc_ctx);
+ }
+
+ q = &video->buffer_queue;
+ q->type = req_buf->type;
+ q->io_modes = VB2_MMAP | VB2_USERPTR;
+ q->drv_priv = fh;
+ q->min_buffers_needed = 1;
+ q->ops = &video_qops;
+ q->mem_ops = &vb2_dma_contig_memops;
+ q->buf_struct_size = sizeof(struct vpfe_cap_buffer);
+
+ ret = vb2_queue_init(q);
+ if (ret) {
+ v4l2_err(&vpfe_dev->v4l2_dev, "vb2_queue_init() failed\n");
+ vb2_dma_contig_cleanup_ctx(vpfe_dev->pdev);
+ return ret;
+ }
+
+ fh->io_allowed = 1;
+ video->io_usrs = 1;
+ INIT_LIST_HEAD(&video->dma_queue);
+ ret = vb2_reqbufs(&video->buffer_queue, req_buf);
+
+unlock_out:
+ mutex_unlock(&video->lock);
+ return ret;
+}
+
+/*
+ * vpfe_querybuf() - query buffers for exchange
+ */
+static int vpfe_querybuf(struct file *file, void *priv,
+ struct v4l2_buffer *buf)
+{
+ struct vpfe_video_device *video = video_drvdata(file);
+ struct vpfe_device *vpfe_dev = video->vpfe_dev;
+
+ v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_querybuf\n");
+
+ if (V4L2_BUF_TYPE_VIDEO_CAPTURE != buf->type &&
+ V4L2_BUF_TYPE_VIDEO_OUTPUT != buf->type) {
+ v4l2_err(&vpfe_dev->v4l2_dev, "Invalid buf type\n");
+ return -EINVAL;
+ }
+
+ if (video->memory != V4L2_MEMORY_MMAP) {
+ v4l2_err(&vpfe_dev->v4l2_dev, "Invalid memory\n");
+ return -EINVAL;
+ }
+
+ /* Call vb2_querybuf to get information */
+ return vb2_querybuf(&video->buffer_queue, buf);
+}
+
+/*
+ * vpfe_qbuf() - queue buffers for capture or processing
+ */
+static int vpfe_qbuf(struct file *file, void *priv,
+ struct v4l2_buffer *p)
+{
+ struct vpfe_video_device *video = video_drvdata(file);
+ struct vpfe_device *vpfe_dev = video->vpfe_dev;
+ struct vpfe_fh *fh = file->private_data;
+
+ v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_qbuf\n");
+
+ if (V4L2_BUF_TYPE_VIDEO_CAPTURE != p->type &&
+ V4L2_BUF_TYPE_VIDEO_OUTPUT != p->type) {
+ v4l2_err(&vpfe_dev->v4l2_dev, "Invalid buf type\n");
+ return -EINVAL;
+ }
+ /*
+ * If this file handle is not allowed to do IO,
+ * return error
+ */
+ if (!fh->io_allowed) {
+ v4l2_err(&vpfe_dev->v4l2_dev, "fh->io_allowed\n");
+ return -EACCES;
+ }
+
+ return vb2_qbuf(&video->buffer_queue, p);
+}
+
+/*
+ * vpfe_dqbuf() - deque buffer which is done with processing
+ */
+static int vpfe_dqbuf(struct file *file, void *priv,
+ struct v4l2_buffer *buf)
+{
+ struct vpfe_video_device *video = video_drvdata(file);
+ struct vpfe_device *vpfe_dev = video->vpfe_dev;
+
+ v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_dqbuf\n");
+
+ if (V4L2_BUF_TYPE_VIDEO_CAPTURE != buf->type &&
+ V4L2_BUF_TYPE_VIDEO_OUTPUT != buf->type) {
+ v4l2_err(&vpfe_dev->v4l2_dev, "Invalid buf type\n");
+ return -EINVAL;
+ }
+
+ return vb2_dqbuf(&video->buffer_queue,
+ buf, (file->f_flags & O_NONBLOCK));
+}
+
+/*
+ * vpfe_streamon() - start streaming
+ * @file: file pointer
+ * @priv: void pointer
+ * @buf_type: enum v4l2_buf_type
+ *
+ * queue buffer onto hardware for capture/processing and
+ * start all the subdevs which are in media chain
+ *
+ * Return 0 on success, error code otherwise
+ */
+static int vpfe_streamon(struct file *file, void *priv,
+ enum v4l2_buf_type buf_type)
+{
+ struct vpfe_video_device *video = video_drvdata(file);
+ struct vpfe_device *vpfe_dev = video->vpfe_dev;
+ struct vpfe_pipeline *pipe = &video->pipe;
+ struct vpfe_fh *fh = file->private_data;
+ struct vpfe_ext_subdev_info *sdinfo;
+ int ret = -EINVAL;
+
+ v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_streamon\n");
+
+ if (V4L2_BUF_TYPE_VIDEO_CAPTURE != buf_type &&
+ V4L2_BUF_TYPE_VIDEO_OUTPUT != buf_type) {
+ v4l2_err(&vpfe_dev->v4l2_dev, "Invalid buf type\n");
+ return ret;
+ }
+ /* If file handle is not allowed IO, return error */
+ if (!fh->io_allowed) {
+ v4l2_err(&vpfe_dev->v4l2_dev, "fh->io_allowed\n");
+ return -EACCES;
+ }
+ sdinfo = video->current_ext_subdev;
+ /* If buffer queue is empty, return error */
+ if (list_empty(&video->buffer_queue.queued_list)) {
+ v4l2_err(&vpfe_dev->v4l2_dev, "buffer queue is empty\n");
+ return -EIO;
+ }
+ /* Validate the pipeline */
+ if (V4L2_BUF_TYPE_VIDEO_CAPTURE == buf_type) {
+ ret = vpfe_video_validate_pipeline(pipe);
+ if (ret < 0)
+ return ret;
+ }
+ /* Call vb2_streamon to start streaming */
+ return vb2_streamon(&video->buffer_queue, buf_type);
+}
+
+/*
+ * vpfe_streamoff() - stop streaming
+ * @file: file pointer
+ * @priv: void pointer
+ * @buf_type: enum v4l2_buf_type
+ *
+ * stop all the subdevs which are in media chain
+ *
+ * Return 0 on success, error code otherwise
+ */
+static int vpfe_streamoff(struct file *file, void *priv,
+ enum v4l2_buf_type buf_type)
+{
+ struct vpfe_video_device *video = video_drvdata(file);
+ struct vpfe_device *vpfe_dev = video->vpfe_dev;
+ struct vpfe_fh *fh = file->private_data;
+ int ret = 0;
+
+ v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_streamoff\n");
+
+ if (buf_type != V4L2_BUF_TYPE_VIDEO_CAPTURE &&
+ buf_type != V4L2_BUF_TYPE_VIDEO_OUTPUT) {
+ v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "Invalid buf type\n");
+ return -EINVAL;
+ }
+
+ /* If io is allowed for this file handle, return error */
+ if (!fh->io_allowed) {
+ v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "fh->io_allowed\n");
+ return -EACCES;
+ }
+
+ /* If streaming is not started, return error */
+ if (!video->started) {
+ v4l2_err(&vpfe_dev->v4l2_dev, "device is not started\n");
+ return -EINVAL;
+ }
+
+ ret = mutex_lock_interruptible(&video->lock);
+ if (ret)
+ return ret;
+
+ vpfe_stop_capture(video);
+ ret = vb2_streamoff(&video->buffer_queue, buf_type);
+ mutex_unlock(&video->lock);
+
+ return ret;
+}
+
+/* vpfe capture ioctl operations */
+static const struct v4l2_ioctl_ops vpfe_ioctl_ops = {
+ .vidioc_querycap = vpfe_querycap,
+ .vidioc_g_fmt_vid_cap = vpfe_g_fmt,
+ .vidioc_s_fmt_vid_cap = vpfe_s_fmt,
+ .vidioc_try_fmt_vid_cap = vpfe_try_fmt,
+ .vidioc_enum_fmt_vid_cap = vpfe_enum_fmt,
+ .vidioc_g_fmt_vid_out = vpfe_g_fmt,
+ .vidioc_s_fmt_vid_out = vpfe_s_fmt,
+ .vidioc_try_fmt_vid_out = vpfe_try_fmt,
+ .vidioc_enum_fmt_vid_out = vpfe_enum_fmt,
+ .vidioc_enum_input = vpfe_enum_input,
+ .vidioc_g_input = vpfe_g_input,
+ .vidioc_s_input = vpfe_s_input,
+ .vidioc_querystd = vpfe_querystd,
+ .vidioc_s_std = vpfe_s_std,
+ .vidioc_g_std = vpfe_g_std,
+ .vidioc_enum_dv_timings = vpfe_enum_dv_timings,
+ .vidioc_query_dv_timings = vpfe_query_dv_timings,
+ .vidioc_s_dv_timings = vpfe_s_dv_timings,
+ .vidioc_g_dv_timings = vpfe_g_dv_timings,
+ .vidioc_reqbufs = vpfe_reqbufs,
+ .vidioc_querybuf = vpfe_querybuf,
+ .vidioc_qbuf = vpfe_qbuf,
+ .vidioc_dqbuf = vpfe_dqbuf,
+ .vidioc_streamon = vpfe_streamon,
+ .vidioc_streamoff = vpfe_streamoff,
+};
+
+/* VPFE video init function */
+int vpfe_video_init(struct vpfe_video_device *video, const char *name)
+{
+ const char *direction;
+ int ret;
+
+ switch (video->type) {
+ case V4L2_BUF_TYPE_VIDEO_CAPTURE:
+ direction = "output";
+ video->pad.flags = MEDIA_PAD_FL_SINK;
+ video->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ break;
+
+ case V4L2_BUF_TYPE_VIDEO_OUTPUT:
+ direction = "input";
+ video->pad.flags = MEDIA_PAD_FL_SOURCE;
+ video->type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+ /* Initialize field of video device */
+ video->video_dev.release = video_device_release;
+ video->video_dev.fops = &vpfe_fops;
+ video->video_dev.ioctl_ops = &vpfe_ioctl_ops;
+ video->video_dev.minor = -1;
+ video->video_dev.tvnorms = 0;
+ snprintf(video->video_dev.name, sizeof(video->video_dev.name),
+ "DAVINCI VIDEO %s %s", name, direction);
+
+ spin_lock_init(&video->irqlock);
+ spin_lock_init(&video->dma_queue_lock);
+ mutex_init(&video->lock);
+ ret = media_entity_init(&video->video_dev.entity,
+ 1, &video->pad, 0);
+ if (ret < 0)
+ return ret;
+
+ set_bit(V4L2_FL_USE_FH_PRIO, &video->video_dev.flags);
+ video_set_drvdata(&video->video_dev, video);
+
+ return 0;
+}
+
+/* vpfe video device register function */
+int vpfe_video_register(struct vpfe_video_device *video,
+ struct v4l2_device *vdev)
+{
+ int ret;
+
+ video->video_dev.v4l2_dev = vdev;
+
+ ret = video_register_device(&video->video_dev, VFL_TYPE_GRABBER, -1);
+ if (ret < 0)
+ pr_err("%s: could not register video device (%d)\n",
+ __func__, ret);
+ return ret;
+}
+
+/* vpfe video device unregister function */
+void vpfe_video_unregister(struct vpfe_video_device *video)
+{
+ if (video_is_registered(&video->video_dev)) {
+ video_unregister_device(&video->video_dev);
+ media_entity_cleanup(&video->video_dev.entity);
+ }
+}
diff --git a/drivers/staging/media/davinci_vpfe/vpfe_video.h b/drivers/staging/media/davinci_vpfe/vpfe_video.h
new file mode 100644
index 00000000000..1b1b6c4a56b
--- /dev/null
+++ b/drivers/staging/media/davinci_vpfe/vpfe_video.h
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2012 Texas Instruments Inc
+ *
+ * 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 version 2.
+ *
+ * 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
+ *
+ * Contributors:
+ * Manjunath Hadli <manjunath.hadli@ti.com>
+ * Prabhakar Lad <prabhakar.lad@ti.com>
+ */
+
+#ifndef _DAVINCI_VPFE_VIDEO_H
+#define _DAVINCI_VPFE_VIDEO_H
+
+#include <media/videobuf2-dma-contig.h>
+
+struct vpfe_device;
+
+/*
+ * struct vpfe_video_operations - VPFE video operations
+ * @queue: Resume streaming when a buffer is queued. Called on VIDIOC_QBUF
+ * if there was no buffer previously queued.
+ */
+struct vpfe_video_operations {
+ int (*queue)(struct vpfe_device *vpfe_dev, unsigned long addr);
+};
+
+enum vpfe_pipeline_stream_state {
+ VPFE_PIPELINE_STREAM_STOPPED = 0,
+ VPFE_PIPELINE_STREAM_CONTINUOUS = 1,
+ VPFE_PIPELINE_STREAM_SINGLESHOT = 2,
+};
+
+enum vpfe_video_state {
+ /* indicates that buffer is not queued */
+ VPFE_VIDEO_BUFFER_NOT_QUEUED = 0,
+ /* indicates that buffer is queued */
+ VPFE_VIDEO_BUFFER_QUEUED = 1,
+};
+
+struct vpfe_pipeline {
+ /* media pipeline */
+ struct media_pipeline *pipe;
+ /* state of the pipeline, continuous,
+ * single-shot or stopped
+ */
+ enum vpfe_pipeline_stream_state state;
+ /* number of active input video entities */
+ unsigned int input_num;
+ /* number of active output video entities */
+ unsigned int output_num;
+ /* input video nodes in case of single-shot mode */
+ struct vpfe_video_device *inputs[10];
+ /* capturing video nodes */
+ struct vpfe_video_device *outputs[10];
+};
+
+#define to_vpfe_pipeline(__e) \
+ container_of((__e)->pipe, struct vpfe_pipeline, pipe)
+
+#define to_vpfe_video(vdev) \
+ container_of(vdev, struct vpfe_video_device, video_dev)
+
+struct vpfe_cap_buffer {
+ struct vb2_buffer vb;
+ struct list_head list;
+};
+
+struct vpfe_video_device {
+ /* vpfe device */
+ struct vpfe_device *vpfe_dev;
+ /* video dev */
+ struct video_device video_dev;
+ /* media pad of video entity */
+ struct media_pad pad;
+ /* video operations supported by video device */
+ const struct vpfe_video_operations *ops;
+ /* type of the video buffers used by user */
+ enum v4l2_buf_type type;
+ /* Indicates id of the field which is being captured */
+ u32 field_id;
+ /* pipeline for which video device is part of */
+ struct vpfe_pipeline pipe;
+ /* Indicates whether streaming started */
+ u8 started;
+ /* Indicates state of the stream */
+ unsigned int state;
+ /* current input at the sub device */
+ int current_input;
+ /*
+ * This field keeps track of type of buffer exchange mechanism
+ * user has selected
+ */
+ enum v4l2_memory memory;
+ /* number of open instances of the channel */
+ u32 usrs;
+ /* flag to indicate whether decoder is initialized */
+ u8 initialized;
+ /* skip frame count */
+ u8 skip_frame_count;
+ /* skip frame count init value */
+ u8 skip_frame_count_init;
+ /* time per frame for skipping */
+ struct v4l2_fract timeperframe;
+ /* ptr to currently selected sub device */
+ struct vpfe_ext_subdev_info *current_ext_subdev;
+ /* Pointer pointing to current vpfe_cap_buffer */
+ struct vpfe_cap_buffer *cur_frm;
+ /* Pointer pointing to next vpfe_cap_buffer */
+ struct vpfe_cap_buffer *next_frm;
+ /* Used to store pixel format */
+ struct v4l2_format fmt;
+ struct vb2_queue buffer_queue;
+ /* allocator-specific contexts for each plane */
+ struct vb2_alloc_ctx *alloc_ctx;
+ /* Queue of filled frames */
+ struct list_head dma_queue;
+ spinlock_t irqlock;
+ /* IRQ lock for DMA queue */
+ spinlock_t dma_queue_lock;
+ /* lock used to access this structure */
+ struct mutex lock;
+ /* number of users performing IO */
+ u32 io_usrs;
+ /* Currently selected or default standard */
+ v4l2_std_id stdid;
+ /*
+ * offset where second field starts from the starting of the
+ * buffer for field separated YCbCr formats
+ */
+ u32 field_off;
+};
+
+int vpfe_video_is_pipe_ready(struct vpfe_pipeline *pipe);
+void vpfe_video_unregister(struct vpfe_video_device *video);
+int vpfe_video_register(struct vpfe_video_device *video,
+ struct v4l2_device *vdev);
+int vpfe_video_init(struct vpfe_video_device *video, const char *name);
+void vpfe_video_process_buffer_complete(struct vpfe_video_device *video);
+void vpfe_video_schedule_bottom_field(struct vpfe_video_device *video);
+void vpfe_video_schedule_next_buffer(struct vpfe_video_device *video);
+
+#endif /* _DAVINCI_VPFE_VIDEO_H */
diff --git a/drivers/staging/media/dt3155v4l/Kconfig b/drivers/staging/media/dt3155v4l/Kconfig
new file mode 100644
index 00000000000..226a1ca90b3
--- /dev/null
+++ b/drivers/staging/media/dt3155v4l/Kconfig
@@ -0,0 +1,28 @@
+config VIDEO_DT3155
+ tristate "DT3155 frame grabber, Video4Linux interface"
+ depends on PCI && VIDEO_DEV && VIDEO_V4L2
+ select VIDEOBUF2_DMA_CONTIG
+ default n
+ ---help---
+ Enables dt3155 device driver for the DataTranslation DT3155 frame grabber.
+ Say Y here if you have this hardware.
+ In doubt, say N.
+
+ To compile this driver as a module, choose M here: the
+ module will be called dt3155v4l.
+
+config DT3155_CCIR
+ bool "Selects CCIR/50Hz vertical refresh"
+ depends on VIDEO_DT3155
+ default y
+ ---help---
+ Select it for CCIR/50Hz (European region),
+ or leave it unselected for RS-170/60Hz (North America).
+
+config DT3155_STREAMING
+ bool "Selects streaming capture method"
+ depends on VIDEO_DT3155
+ default y
+ ---help---
+ Select it if you want to use streaming of memory mapped buffers
+ or leave it unselected if you want to use read method (one copy more).
diff --git a/drivers/staging/media/dt3155v4l/Makefile b/drivers/staging/media/dt3155v4l/Makefile
new file mode 100644
index 00000000000..ce7a3ec2faf
--- /dev/null
+++ b/drivers/staging/media/dt3155v4l/Makefile
@@ -0,0 +1 @@
+obj-$(CONFIG_VIDEO_DT3155) += dt3155v4l.o
diff --git a/drivers/staging/media/dt3155v4l/dt3155v4l.c b/drivers/staging/media/dt3155v4l/dt3155v4l.c
new file mode 100644
index 00000000000..40580228a6c
--- /dev/null
+++ b/drivers/staging/media/dt3155v4l/dt3155v4l.c
@@ -0,0 +1,995 @@
+/***************************************************************************
+ * Copyright (C) 2006-2010 by Marin Mitov *
+ * mitov@issp.bas.bg *
+ * *
+ * 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/module.h>
+#include <linux/version.h>
+#include <linux/stringify.h>
+#include <linux/delay.h>
+#include <linux/kthread.h>
+#include <linux/slab.h>
+#include <media/v4l2-dev.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-common.h>
+#include <media/videobuf2-dma-contig.h>
+
+#include "dt3155v4l.h"
+
+#define DT3155_DEVICE_ID 0x1223
+
+/* DT3155_CHUNK_SIZE is 4M (2^22) 8 full size buffers */
+#define DT3155_CHUNK_SIZE (1U << 22)
+
+#define DT3155_COH_FLAGS (GFP_KERNEL | GFP_DMA32 | __GFP_COLD | __GFP_NOWARN)
+
+#define DT3155_BUF_SIZE (768 * 576)
+
+#ifdef CONFIG_DT3155_STREAMING
+#define DT3155_CAPTURE_METHOD V4L2_CAP_STREAMING
+#else
+#define DT3155_CAPTURE_METHOD V4L2_CAP_READWRITE
+#endif
+
+/* global initializers (for all boards) */
+#ifdef CONFIG_DT3155_CCIR
+static const u8 csr2_init = VT_50HZ;
+#define DT3155_CURRENT_NORM V4L2_STD_625_50
+static const unsigned int img_width = 768;
+static const unsigned int img_height = 576;
+static const unsigned int frames_per_sec = 25;
+static const struct v4l2_fmtdesc frame_std[] = {
+ {
+ .index = 0,
+ .type = V4L2_BUF_TYPE_VIDEO_CAPTURE,
+ .flags = 0,
+ .description = "CCIR/50Hz 8 bits gray",
+ .pixelformat = V4L2_PIX_FMT_GREY,
+ },
+};
+#else
+static const u8 csr2_init = VT_60HZ;
+#define DT3155_CURRENT_NORM V4L2_STD_525_60
+static const unsigned int img_width = 640;
+static const unsigned int img_height = 480;
+static const unsigned int frames_per_sec = 30;
+static const struct v4l2_fmtdesc frame_std[] = {
+ {
+ .index = 0,
+ .type = V4L2_BUF_TYPE_VIDEO_CAPTURE,
+ .flags = 0,
+ .description = "RS-170/60Hz 8 bits gray",
+ .pixelformat = V4L2_PIX_FMT_GREY,
+ },
+};
+#endif
+
+#define NUM_OF_FORMATS ARRAY_SIZE(frame_std)
+
+static u8 config_init = ACQ_MODE_EVEN;
+
+/**
+ * read_i2c_reg - reads an internal i2c register
+ *
+ * @addr: dt3155 mmio base address
+ * @index: index (internal address) of register to read
+ * @data: pointer to byte the read data will be placed in
+ *
+ * returns: zero on success or error code
+ *
+ * This function starts reading the specified (by index) register
+ * and busy waits for the process to finish. The result is placed
+ * in a byte pointed by data.
+ */
+static int
+read_i2c_reg(void __iomem *addr, u8 index, u8 *data)
+{
+ u32 tmp = index;
+
+ iowrite32((tmp<<17) | IIC_READ, addr + IIC_CSR2);
+ mmiowb();
+ udelay(45); /* wait at least 43 usec for NEW_CYCLE to clear */
+ if (ioread32(addr + IIC_CSR2) & NEW_CYCLE)
+ return -EIO; /* error: NEW_CYCLE not cleared */
+ tmp = ioread32(addr + IIC_CSR1);
+ if (tmp & DIRECT_ABORT) {
+ /* reset DIRECT_ABORT bit */
+ iowrite32(DIRECT_ABORT, addr + IIC_CSR1);
+ return -EIO; /* error: DIRECT_ABORT set */
+ }
+ *data = tmp>>24;
+ return 0;
+}
+
+/**
+ * write_i2c_reg - writes to an internal i2c register
+ *
+ * @addr: dt3155 mmio base address
+ * @index: index (internal address) of register to read
+ * @data: data to be written
+ *
+ * returns: zero on success or error code
+ *
+ * This function starts writting the specified (by index) register
+ * and busy waits for the process to finish.
+ */
+static int
+write_i2c_reg(void __iomem *addr, u8 index, u8 data)
+{
+ u32 tmp = index;
+
+ iowrite32((tmp<<17) | IIC_WRITE | data, addr + IIC_CSR2);
+ mmiowb();
+ udelay(65); /* wait at least 63 usec for NEW_CYCLE to clear */
+ if (ioread32(addr + IIC_CSR2) & NEW_CYCLE)
+ return -EIO; /* error: NEW_CYCLE not cleared */
+ if (ioread32(addr + IIC_CSR1) & DIRECT_ABORT) {
+ /* reset DIRECT_ABORT bit */
+ iowrite32(DIRECT_ABORT, addr + IIC_CSR1);
+ return -EIO; /* error: DIRECT_ABORT set */
+ }
+ return 0;
+}
+
+/**
+ * write_i2c_reg_nowait - writes to an internal i2c register
+ *
+ * @addr: dt3155 mmio base address
+ * @index: index (internal address) of register to read
+ * @data: data to be written
+ *
+ * This function starts writting the specified (by index) register
+ * and then returns.
+ */
+static void write_i2c_reg_nowait(void __iomem *addr, u8 index, u8 data)
+{
+ u32 tmp = index;
+
+ iowrite32((tmp<<17) | IIC_WRITE | data, addr + IIC_CSR2);
+ mmiowb();
+}
+
+/**
+ * wait_i2c_reg - waits the read/write to finish
+ *
+ * @addr: dt3155 mmio base address
+ *
+ * returns: zero on success or error code
+ *
+ * This function waits reading/writting to finish.
+ */
+static int wait_i2c_reg(void __iomem *addr)
+{
+ if (ioread32(addr + IIC_CSR2) & NEW_CYCLE)
+ udelay(65); /* wait at least 63 usec for NEW_CYCLE to clear */
+ if (ioread32(addr + IIC_CSR2) & NEW_CYCLE)
+ return -EIO; /* error: NEW_CYCLE not cleared */
+ if (ioread32(addr + IIC_CSR1) & DIRECT_ABORT) {
+ /* reset DIRECT_ABORT bit */
+ iowrite32(DIRECT_ABORT, addr + IIC_CSR1);
+ return -EIO; /* error: DIRECT_ABORT set */
+ }
+ return 0;
+}
+
+static int
+dt3155_start_acq(struct dt3155_priv *pd)
+{
+ struct vb2_buffer *vb = pd->curr_buf;
+ dma_addr_t dma_addr;
+
+ dma_addr = vb2_dma_contig_plane_dma_addr(vb, 0);
+ iowrite32(dma_addr, pd->regs + EVEN_DMA_START);
+ iowrite32(dma_addr + img_width, pd->regs + ODD_DMA_START);
+ iowrite32(img_width, pd->regs + EVEN_DMA_STRIDE);
+ iowrite32(img_width, pd->regs + ODD_DMA_STRIDE);
+ /* enable interrupts, clear all irq flags */
+ iowrite32(FLD_START_EN | FLD_END_ODD_EN | FLD_START |
+ FLD_END_EVEN | FLD_END_ODD, pd->regs + INT_CSR);
+ iowrite32(FIFO_EN | SRST | FLD_CRPT_ODD | FLD_CRPT_EVEN |
+ FLD_DN_ODD | FLD_DN_EVEN | CAP_CONT_EVEN | CAP_CONT_ODD,
+ pd->regs + CSR1);
+ wait_i2c_reg(pd->regs);
+ write_i2c_reg(pd->regs, CONFIG, pd->config);
+ write_i2c_reg(pd->regs, EVEN_CSR, CSR_ERROR | CSR_DONE);
+ write_i2c_reg(pd->regs, ODD_CSR, CSR_ERROR | CSR_DONE);
+
+ /* start the board */
+ write_i2c_reg(pd->regs, CSR2, pd->csr2 | BUSY_EVEN | BUSY_ODD);
+ return 0; /* success */
+}
+
+/*
+ * driver-specific callbacks (vb2_ops)
+ */
+static int
+dt3155_queue_setup(struct vb2_queue *q, const struct v4l2_format *fmt,
+ unsigned int *num_buffers, unsigned int *num_planes,
+ unsigned int sizes[], void *alloc_ctxs[])
+
+{
+ struct dt3155_priv *pd = vb2_get_drv_priv(q);
+ void *ret;
+
+ if (*num_buffers == 0)
+ *num_buffers = 1;
+ *num_planes = 1;
+ sizes[0] = img_width * img_height;
+ if (pd->q->alloc_ctx[0])
+ return 0;
+ ret = vb2_dma_contig_init_ctx(&pd->pdev->dev);
+ if (IS_ERR(ret))
+ return PTR_ERR(ret);
+ pd->q->alloc_ctx[0] = ret;
+ return 0;
+}
+
+static void
+dt3155_wait_prepare(struct vb2_queue *q)
+{
+ struct dt3155_priv *pd = vb2_get_drv_priv(q);
+
+ mutex_unlock(pd->vdev->lock);
+}
+
+static void
+dt3155_wait_finish(struct vb2_queue *q)
+{
+ struct dt3155_priv *pd = vb2_get_drv_priv(q);
+
+ mutex_lock(pd->vdev->lock);
+}
+
+static int
+dt3155_buf_prepare(struct vb2_buffer *vb)
+{
+ vb2_set_plane_payload(vb, 0, img_width * img_height);
+ return 0;
+}
+
+static void
+dt3155_stop_streaming(struct vb2_queue *q)
+{
+ struct dt3155_priv *pd = vb2_get_drv_priv(q);
+ struct vb2_buffer *vb;
+
+ spin_lock_irq(&pd->lock);
+ while (!list_empty(&pd->dmaq)) {
+ vb = list_first_entry(&pd->dmaq, typeof(*vb), done_entry);
+ list_del(&vb->done_entry);
+ vb2_buffer_done(vb, VB2_BUF_STATE_ERROR);
+ }
+ spin_unlock_irq(&pd->lock);
+ msleep(45); /* irq hendler will stop the hardware */
+}
+
+static void
+dt3155_buf_queue(struct vb2_buffer *vb)
+{
+ struct dt3155_priv *pd = vb2_get_drv_priv(vb->vb2_queue);
+
+ /* pd->q->streaming = 1 when dt3155_buf_queue() is invoked */
+ spin_lock_irq(&pd->lock);
+ if (pd->curr_buf)
+ list_add_tail(&vb->done_entry, &pd->dmaq);
+ else {
+ pd->curr_buf = vb;
+ dt3155_start_acq(pd);
+ }
+ spin_unlock_irq(&pd->lock);
+}
+/*
+ * end driver-specific callbacks
+ */
+
+static const struct vb2_ops q_ops = {
+ .queue_setup = dt3155_queue_setup,
+ .wait_prepare = dt3155_wait_prepare,
+ .wait_finish = dt3155_wait_finish,
+ .buf_prepare = dt3155_buf_prepare,
+ .stop_streaming = dt3155_stop_streaming,
+ .buf_queue = dt3155_buf_queue,
+};
+
+static irqreturn_t
+dt3155_irq_handler_even(int irq, void *dev_id)
+{
+ struct dt3155_priv *ipd = dev_id;
+ struct vb2_buffer *ivb;
+ dma_addr_t dma_addr;
+ u32 tmp;
+
+ tmp = ioread32(ipd->regs + INT_CSR) & (FLD_START | FLD_END_ODD);
+ if (!tmp)
+ return IRQ_NONE; /* not our irq */
+ if ((tmp & FLD_START) && !(tmp & FLD_END_ODD)) {
+ iowrite32(FLD_START_EN | FLD_END_ODD_EN | FLD_START,
+ ipd->regs + INT_CSR);
+ ipd->field_count++;
+ return IRQ_HANDLED; /* start of field irq */
+ }
+ if ((tmp & FLD_START) && (tmp & FLD_END_ODD))
+ ipd->stats.start_before_end++;
+ /* check for corrupted fields */
+/* write_i2c_reg(ipd->regs, EVEN_CSR, CSR_ERROR | CSR_DONE); */
+/* write_i2c_reg(ipd->regs, ODD_CSR, CSR_ERROR | CSR_DONE); */
+ tmp = ioread32(ipd->regs + CSR1) & (FLD_CRPT_EVEN | FLD_CRPT_ODD);
+ if (tmp) {
+ ipd->stats.corrupted_fields++;
+ iowrite32(FIFO_EN | SRST | FLD_CRPT_ODD | FLD_CRPT_EVEN |
+ FLD_DN_ODD | FLD_DN_EVEN |
+ CAP_CONT_EVEN | CAP_CONT_ODD,
+ ipd->regs + CSR1);
+ mmiowb();
+ }
+
+ spin_lock(&ipd->lock);
+ if (ipd->curr_buf) {
+ v4l2_get_timestamp(&ipd->curr_buf->v4l2_buf.timestamp);
+ ipd->curr_buf->v4l2_buf.sequence = (ipd->field_count) >> 1;
+ vb2_buffer_done(ipd->curr_buf, VB2_BUF_STATE_DONE);
+ }
+
+ if (!ipd->q->streaming || list_empty(&ipd->dmaq))
+ goto stop_dma;
+ ivb = list_first_entry(&ipd->dmaq, typeof(*ivb), done_entry);
+ list_del(&ivb->done_entry);
+ ipd->curr_buf = ivb;
+ dma_addr = vb2_dma_contig_plane_dma_addr(ivb, 0);
+ iowrite32(dma_addr, ipd->regs + EVEN_DMA_START);
+ iowrite32(dma_addr + img_width, ipd->regs + ODD_DMA_START);
+ iowrite32(img_width, ipd->regs + EVEN_DMA_STRIDE);
+ iowrite32(img_width, ipd->regs + ODD_DMA_STRIDE);
+ mmiowb();
+ /* enable interrupts, clear all irq flags */
+ iowrite32(FLD_START_EN | FLD_END_ODD_EN | FLD_START |
+ FLD_END_EVEN | FLD_END_ODD, ipd->regs + INT_CSR);
+ spin_unlock(&ipd->lock);
+ return IRQ_HANDLED;
+
+stop_dma:
+ ipd->curr_buf = NULL;
+ /* stop the board */
+ write_i2c_reg_nowait(ipd->regs, CSR2, ipd->csr2);
+ iowrite32(FIFO_EN | SRST | FLD_CRPT_ODD | FLD_CRPT_EVEN |
+ FLD_DN_ODD | FLD_DN_EVEN, ipd->regs + CSR1);
+ /* disable interrupts, clear all irq flags */
+ iowrite32(FLD_START | FLD_END_EVEN | FLD_END_ODD, ipd->regs + INT_CSR);
+ spin_unlock(&ipd->lock);
+ return IRQ_HANDLED;
+}
+
+static int
+dt3155_open(struct file *filp)
+{
+ int ret = 0;
+ struct dt3155_priv *pd = video_drvdata(filp);
+
+ if (mutex_lock_interruptible(&pd->mux))
+ return -ERESTARTSYS;
+ if (!pd->users) {
+ pd->q = kzalloc(sizeof(*pd->q), GFP_KERNEL);
+ if (!pd->q) {
+ ret = -ENOMEM;
+ goto err_alloc_queue;
+ }
+ pd->q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ pd->q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
+ pd->q->io_modes = VB2_READ | VB2_MMAP;
+ pd->q->ops = &q_ops;
+ pd->q->mem_ops = &vb2_dma_contig_memops;
+ pd->q->drv_priv = pd;
+ pd->curr_buf = NULL;
+ pd->field_count = 0;
+ ret = vb2_queue_init(pd->q);
+ if (ret < 0)
+ goto err_request_irq;
+ INIT_LIST_HEAD(&pd->dmaq);
+ spin_lock_init(&pd->lock);
+ /* disable all irqs, clear all irq flags */
+ iowrite32(FLD_START | FLD_END_EVEN | FLD_END_ODD,
+ pd->regs + INT_CSR);
+ ret = request_irq(pd->pdev->irq, dt3155_irq_handler_even,
+ IRQF_SHARED, DT3155_NAME, pd);
+ if (ret)
+ goto err_request_irq;
+ }
+ pd->users++;
+ mutex_unlock(&pd->mux);
+ return 0; /* success */
+err_request_irq:
+ kfree(pd->q);
+ pd->q = NULL;
+err_alloc_queue:
+ mutex_unlock(&pd->mux);
+ return ret;
+}
+
+static int
+dt3155_release(struct file *filp)
+{
+ struct dt3155_priv *pd = video_drvdata(filp);
+
+ mutex_lock(&pd->mux);
+ pd->users--;
+ BUG_ON(pd->users < 0);
+ if (!pd->users) {
+ vb2_queue_release(pd->q);
+ free_irq(pd->pdev->irq, pd);
+ if (pd->q->alloc_ctx[0])
+ vb2_dma_contig_cleanup_ctx(pd->q->alloc_ctx[0]);
+ kfree(pd->q);
+ pd->q = NULL;
+ }
+ mutex_unlock(&pd->mux);
+ return 0;
+}
+
+static ssize_t
+dt3155_read(struct file *filp, char __user *user, size_t size, loff_t *loff)
+{
+ struct dt3155_priv *pd = video_drvdata(filp);
+ ssize_t res;
+
+ if (mutex_lock_interruptible(&pd->mux))
+ return -ERESTARTSYS;
+ res = vb2_read(pd->q, user, size, loff, filp->f_flags & O_NONBLOCK);
+ mutex_unlock(&pd->mux);
+ return res;
+}
+
+static unsigned int
+dt3155_poll(struct file *filp, struct poll_table_struct *polltbl)
+{
+ struct dt3155_priv *pd = video_drvdata(filp);
+ unsigned int res;
+
+ mutex_lock(&pd->mux);
+ res = vb2_poll(pd->q, filp, polltbl);
+ mutex_unlock(&pd->mux);
+ return res;
+}
+
+static int
+dt3155_mmap(struct file *filp, struct vm_area_struct *vma)
+{
+ struct dt3155_priv *pd = video_drvdata(filp);
+ int res;
+
+ if (mutex_lock_interruptible(&pd->mux))
+ return -ERESTARTSYS;
+ res = vb2_mmap(pd->q, vma);
+ mutex_unlock(&pd->mux);
+ return res;
+}
+
+static const struct v4l2_file_operations dt3155_fops = {
+ .owner = THIS_MODULE,
+ .open = dt3155_open,
+ .release = dt3155_release,
+ .read = dt3155_read,
+ .poll = dt3155_poll,
+ .unlocked_ioctl = video_ioctl2, /* V4L2 ioctl handler */
+ .mmap = dt3155_mmap,
+};
+
+static int
+dt3155_ioc_streamon(struct file *filp, void *p, enum v4l2_buf_type type)
+{
+ struct dt3155_priv *pd = video_drvdata(filp);
+
+ return vb2_streamon(pd->q, type);
+}
+
+static int
+dt3155_ioc_streamoff(struct file *filp, void *p, enum v4l2_buf_type type)
+{
+ struct dt3155_priv *pd = video_drvdata(filp);
+
+ return vb2_streamoff(pd->q, type);
+}
+
+static int
+dt3155_ioc_querycap(struct file *filp, void *p, struct v4l2_capability *cap)
+{
+ struct dt3155_priv *pd = video_drvdata(filp);
+
+ strcpy(cap->driver, DT3155_NAME);
+ strcpy(cap->card, DT3155_NAME " frame grabber");
+ sprintf(cap->bus_info, "PCI:%s", pci_name(pd->pdev));
+ cap->version =
+ KERNEL_VERSION(DT3155_VER_MAJ, DT3155_VER_MIN, DT3155_VER_EXT);
+ cap->capabilities = V4L2_CAP_VIDEO_CAPTURE |
+ DT3155_CAPTURE_METHOD;
+ return 0;
+}
+
+static int
+dt3155_ioc_enum_fmt_vid_cap(struct file *filp, void *p, struct v4l2_fmtdesc *f)
+{
+ if (f->index >= NUM_OF_FORMATS)
+ return -EINVAL;
+ *f = frame_std[f->index];
+ return 0;
+}
+
+static int
+dt3155_ioc_g_fmt_vid_cap(struct file *filp, void *p, struct v4l2_format *f)
+{
+ if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+ return -EINVAL;
+ f->fmt.pix.width = img_width;
+ f->fmt.pix.height = img_height;
+ f->fmt.pix.pixelformat = V4L2_PIX_FMT_GREY;
+ f->fmt.pix.field = V4L2_FIELD_NONE;
+ f->fmt.pix.bytesperline = f->fmt.pix.width;
+ f->fmt.pix.sizeimage = f->fmt.pix.width * f->fmt.pix.height;
+ f->fmt.pix.colorspace = 0;
+ f->fmt.pix.priv = 0;
+ return 0;
+}
+
+static int
+dt3155_ioc_try_fmt_vid_cap(struct file *filp, void *p, struct v4l2_format *f)
+{
+ if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+ return -EINVAL;
+ if (f->fmt.pix.width == img_width &&
+ f->fmt.pix.height == img_height &&
+ f->fmt.pix.pixelformat == V4L2_PIX_FMT_GREY &&
+ f->fmt.pix.field == V4L2_FIELD_NONE &&
+ f->fmt.pix.bytesperline == f->fmt.pix.width &&
+ f->fmt.pix.sizeimage == f->fmt.pix.width * f->fmt.pix.height)
+ return 0;
+ else
+ return -EINVAL;
+}
+
+static int
+dt3155_ioc_s_fmt_vid_cap(struct file *filp, void *p, struct v4l2_format *f)
+{
+ return dt3155_ioc_g_fmt_vid_cap(filp, p, f);
+}
+
+static int
+dt3155_ioc_reqbufs(struct file *filp, void *p, struct v4l2_requestbuffers *b)
+{
+ struct dt3155_priv *pd = video_drvdata(filp);
+
+ return vb2_reqbufs(pd->q, b);
+}
+
+static int
+dt3155_ioc_querybuf(struct file *filp, void *p, struct v4l2_buffer *b)
+{
+ struct dt3155_priv *pd = video_drvdata(filp);
+
+ return vb2_querybuf(pd->q, b);
+}
+
+static int
+dt3155_ioc_qbuf(struct file *filp, void *p, struct v4l2_buffer *b)
+{
+ struct dt3155_priv *pd = video_drvdata(filp);
+
+ return vb2_qbuf(pd->q, b);
+}
+
+static int
+dt3155_ioc_dqbuf(struct file *filp, void *p, struct v4l2_buffer *b)
+{
+ struct dt3155_priv *pd = video_drvdata(filp);
+
+ return vb2_dqbuf(pd->q, b, filp->f_flags & O_NONBLOCK);
+}
+
+static int
+dt3155_ioc_querystd(struct file *filp, void *p, v4l2_std_id *norm)
+{
+ *norm = DT3155_CURRENT_NORM;
+ return 0;
+}
+
+static int
+dt3155_ioc_g_std(struct file *filp, void *p, v4l2_std_id *norm)
+{
+ *norm = DT3155_CURRENT_NORM;
+ return 0;
+}
+
+static int
+dt3155_ioc_s_std(struct file *filp, void *p, v4l2_std_id norm)
+{
+ if (norm & DT3155_CURRENT_NORM)
+ return 0;
+ return -EINVAL;
+}
+
+static int
+dt3155_ioc_enum_input(struct file *filp, void *p, struct v4l2_input *input)
+{
+ if (input->index)
+ return -EINVAL;
+ strcpy(input->name, "Coax in");
+ input->type = V4L2_INPUT_TYPE_CAMERA;
+ /*
+ * FIXME: input->std = 0 according to v4l2 API
+ * VIDIOC_G_STD, VIDIOC_S_STD, VIDIOC_QUERYSTD and VIDIOC_ENUMSTD
+ * should return -EINVAL
+ */
+ input->std = DT3155_CURRENT_NORM;
+ input->status = 0;/* FIXME: add sync detection & V4L2_IN_ST_NO_H_LOCK */
+ return 0;
+}
+
+static int
+dt3155_ioc_g_input(struct file *filp, void *p, unsigned int *i)
+{
+ *i = 0;
+ return 0;
+}
+
+static int
+dt3155_ioc_s_input(struct file *filp, void *p, unsigned int i)
+{
+ if (i)
+ return -EINVAL;
+ return 0;
+}
+
+static int
+dt3155_ioc_g_parm(struct file *filp, void *p, struct v4l2_streamparm *parms)
+{
+ if (parms->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+ return -EINVAL;
+ parms->parm.capture.capability = V4L2_CAP_TIMEPERFRAME;
+ parms->parm.capture.capturemode = 0;
+ parms->parm.capture.timeperframe.numerator = 1001;
+ parms->parm.capture.timeperframe.denominator = frames_per_sec * 1000;
+ parms->parm.capture.extendedmode = 0;
+ parms->parm.capture.readbuffers = 1; /* FIXME: 2 buffers? */
+ return 0;
+}
+
+static int
+dt3155_ioc_s_parm(struct file *filp, void *p, struct v4l2_streamparm *parms)
+{
+ if (parms->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+ return -EINVAL;
+ parms->parm.capture.capability = V4L2_CAP_TIMEPERFRAME;
+ parms->parm.capture.capturemode = 0;
+ parms->parm.capture.timeperframe.numerator = 1001;
+ parms->parm.capture.timeperframe.denominator = frames_per_sec * 1000;
+ parms->parm.capture.extendedmode = 0;
+ parms->parm.capture.readbuffers = 1; /* FIXME: 2 buffers? */
+ return 0;
+}
+
+static const struct v4l2_ioctl_ops dt3155_ioctl_ops = {
+ .vidioc_streamon = dt3155_ioc_streamon,
+ .vidioc_streamoff = dt3155_ioc_streamoff,
+ .vidioc_querycap = dt3155_ioc_querycap,
+/*
+ .vidioc_g_priority = dt3155_ioc_g_priority,
+ .vidioc_s_priority = dt3155_ioc_s_priority,
+*/
+ .vidioc_enum_fmt_vid_cap = dt3155_ioc_enum_fmt_vid_cap,
+ .vidioc_try_fmt_vid_cap = dt3155_ioc_try_fmt_vid_cap,
+ .vidioc_g_fmt_vid_cap = dt3155_ioc_g_fmt_vid_cap,
+ .vidioc_s_fmt_vid_cap = dt3155_ioc_s_fmt_vid_cap,
+ .vidioc_reqbufs = dt3155_ioc_reqbufs,
+ .vidioc_querybuf = dt3155_ioc_querybuf,
+ .vidioc_qbuf = dt3155_ioc_qbuf,
+ .vidioc_dqbuf = dt3155_ioc_dqbuf,
+ .vidioc_querystd = dt3155_ioc_querystd,
+ .vidioc_g_std = dt3155_ioc_g_std,
+ .vidioc_s_std = dt3155_ioc_s_std,
+ .vidioc_enum_input = dt3155_ioc_enum_input,
+ .vidioc_g_input = dt3155_ioc_g_input,
+ .vidioc_s_input = dt3155_ioc_s_input,
+/*
+ .vidioc_queryctrl = dt3155_ioc_queryctrl,
+ .vidioc_g_ctrl = dt3155_ioc_g_ctrl,
+ .vidioc_s_ctrl = dt3155_ioc_s_ctrl,
+ .vidioc_querymenu = dt3155_ioc_querymenu,
+ .vidioc_g_ext_ctrls = dt3155_ioc_g_ext_ctrls,
+ .vidioc_s_ext_ctrls = dt3155_ioc_s_ext_ctrls,
+*/
+ .vidioc_g_parm = dt3155_ioc_g_parm,
+ .vidioc_s_parm = dt3155_ioc_s_parm,
+/*
+ .vidioc_cropcap = dt3155_ioc_cropcap,
+ .vidioc_g_crop = dt3155_ioc_g_crop,
+ .vidioc_s_crop = dt3155_ioc_s_crop,
+ .vidioc_enum_framesizes = dt3155_ioc_enum_framesizes,
+ .vidioc_enum_frameintervals = dt3155_ioc_enum_frameintervals,
+*/
+};
+
+static int
+dt3155_init_board(struct pci_dev *pdev)
+{
+ struct dt3155_priv *pd = pci_get_drvdata(pdev);
+ void *buf_cpu;
+ dma_addr_t buf_dma;
+ int i;
+ u8 tmp;
+
+ pci_set_master(pdev); /* dt3155 needs it */
+
+ /* resetting the adapter */
+ iowrite32(FLD_CRPT_ODD | FLD_CRPT_EVEN | FLD_DN_ODD | FLD_DN_EVEN,
+ pd->regs + CSR1);
+ mmiowb();
+ msleep(20);
+
+ /* initializing adaper registers */
+ iowrite32(FIFO_EN | SRST, pd->regs + CSR1);
+ mmiowb();
+ iowrite32(0xEEEEEE01, pd->regs + EVEN_PIXEL_FMT);
+ iowrite32(0xEEEEEE01, pd->regs + ODD_PIXEL_FMT);
+ iowrite32(0x00000020, pd->regs + FIFO_TRIGER);
+ iowrite32(0x00000103, pd->regs + XFER_MODE);
+ iowrite32(0, pd->regs + RETRY_WAIT_CNT);
+ iowrite32(0, pd->regs + INT_CSR);
+ iowrite32(1, pd->regs + EVEN_FLD_MASK);
+ iowrite32(1, pd->regs + ODD_FLD_MASK);
+ iowrite32(0, pd->regs + MASK_LENGTH);
+ iowrite32(0x0005007C, pd->regs + FIFO_FLAG_CNT);
+ iowrite32(0x01010101, pd->regs + IIC_CLK_DUR);
+ mmiowb();
+
+ /* verifying that we have a DT3155 board (not just a SAA7116 chip) */
+ read_i2c_reg(pd->regs, DT_ID, &tmp);
+ if (tmp != DT3155_ID)
+ return -ENODEV;
+
+ /* initialize AD LUT */
+ write_i2c_reg(pd->regs, AD_ADDR, 0);
+ for (i = 0; i < 256; i++)
+ write_i2c_reg(pd->regs, AD_LUT, i);
+
+ /* initialize ADC references */
+ /* FIXME: pos_ref & neg_ref depend on VT_50HZ */
+ write_i2c_reg(pd->regs, AD_ADDR, AD_CMD_REG);
+ write_i2c_reg(pd->regs, AD_CMD, VIDEO_CNL_1 | SYNC_CNL_1 | SYNC_LVL_3);
+ write_i2c_reg(pd->regs, AD_ADDR, AD_POS_REF);
+ write_i2c_reg(pd->regs, AD_CMD, 34);
+ write_i2c_reg(pd->regs, AD_ADDR, AD_NEG_REF);
+ write_i2c_reg(pd->regs, AD_CMD, 0);
+
+ /* initialize PM LUT */
+ write_i2c_reg(pd->regs, CONFIG, pd->config | PM_LUT_PGM);
+ for (i = 0; i < 256; i++) {
+ write_i2c_reg(pd->regs, PM_LUT_ADDR, i);
+ write_i2c_reg(pd->regs, PM_LUT_DATA, i);
+ }
+ write_i2c_reg(pd->regs, CONFIG, pd->config | PM_LUT_PGM | PM_LUT_SEL);
+ for (i = 0; i < 256; i++) {
+ write_i2c_reg(pd->regs, PM_LUT_ADDR, i);
+ write_i2c_reg(pd->regs, PM_LUT_DATA, i);
+ }
+ write_i2c_reg(pd->regs, CONFIG, pd->config); /* ACQ_MODE_EVEN */
+
+ /* select channel 1 for input and set sync level */
+ write_i2c_reg(pd->regs, AD_ADDR, AD_CMD_REG);
+ write_i2c_reg(pd->regs, AD_CMD, VIDEO_CNL_1 | SYNC_CNL_1 | SYNC_LVL_3);
+
+ /* allocate memory, and initialize the DMA machine */
+ buf_cpu = dma_alloc_coherent(&pdev->dev, DT3155_BUF_SIZE, &buf_dma,
+ GFP_KERNEL);
+ if (!buf_cpu)
+ return -ENOMEM;
+ iowrite32(buf_dma, pd->regs + EVEN_DMA_START);
+ iowrite32(buf_dma, pd->regs + ODD_DMA_START);
+ iowrite32(0, pd->regs + EVEN_DMA_STRIDE);
+ iowrite32(0, pd->regs + ODD_DMA_STRIDE);
+
+ /* Perform a pseudo even field acquire */
+ iowrite32(FIFO_EN | SRST | CAP_CONT_ODD, pd->regs + CSR1);
+ write_i2c_reg(pd->regs, CSR2, pd->csr2 | SYNC_SNTL);
+ write_i2c_reg(pd->regs, CONFIG, pd->config);
+ write_i2c_reg(pd->regs, EVEN_CSR, CSR_SNGL);
+ write_i2c_reg(pd->regs, CSR2, pd->csr2 | BUSY_EVEN | SYNC_SNTL);
+ msleep(100);
+ read_i2c_reg(pd->regs, CSR2, &tmp);
+ write_i2c_reg(pd->regs, EVEN_CSR, CSR_ERROR | CSR_SNGL | CSR_DONE);
+ write_i2c_reg(pd->regs, ODD_CSR, CSR_ERROR | CSR_SNGL | CSR_DONE);
+ write_i2c_reg(pd->regs, CSR2, pd->csr2);
+ iowrite32(FIFO_EN | SRST | FLD_DN_EVEN | FLD_DN_ODD, pd->regs + CSR1);
+
+ /* deallocate memory */
+ dma_free_coherent(&pdev->dev, DT3155_BUF_SIZE, buf_cpu, buf_dma);
+ if (tmp & BUSY_EVEN)
+ return -EIO;
+ return 0;
+}
+
+static struct video_device dt3155_vdev = {
+ .name = DT3155_NAME,
+ .fops = &dt3155_fops,
+ .ioctl_ops = &dt3155_ioctl_ops,
+ .minor = -1,
+ .release = video_device_release,
+ .tvnorms = DT3155_CURRENT_NORM,
+};
+
+/* same as in drivers/base/dma-coherent.c */
+struct dma_coherent_mem {
+ void *virt_base;
+ dma_addr_t device_base;
+ int size;
+ int flags;
+ unsigned long *bitmap;
+};
+
+static int
+dt3155_alloc_coherent(struct device *dev, size_t size, int flags)
+{
+ struct dma_coherent_mem *mem;
+ dma_addr_t dev_base;
+ int pages = size >> PAGE_SHIFT;
+ int bitmap_size = BITS_TO_LONGS(pages) * sizeof(long);
+
+ if ((flags & DMA_MEMORY_MAP) == 0)
+ goto out;
+ if (!size)
+ goto out;
+ if (dev->dma_mem)
+ goto out;
+
+ mem = kzalloc(sizeof(*mem), GFP_KERNEL);
+ if (!mem)
+ goto out;
+ mem->virt_base = dma_alloc_coherent(dev, size, &dev_base,
+ DT3155_COH_FLAGS);
+ if (!mem->virt_base)
+ goto err_alloc_coherent;
+ mem->bitmap = kzalloc(bitmap_size, GFP_KERNEL);
+ if (!mem->bitmap)
+ goto err_bitmap;
+
+ /* coherent_dma_mask is already set to 32 bits */
+ mem->device_base = dev_base;
+ mem->size = pages;
+ mem->flags = flags;
+ dev->dma_mem = mem;
+ return DMA_MEMORY_MAP;
+
+err_bitmap:
+ dma_free_coherent(dev, size, mem->virt_base, dev_base);
+err_alloc_coherent:
+ kfree(mem);
+out:
+ return 0;
+}
+
+static void
+dt3155_free_coherent(struct device *dev)
+{
+ struct dma_coherent_mem *mem = dev->dma_mem;
+
+ if (!mem)
+ return;
+ dev->dma_mem = NULL;
+ dma_free_coherent(dev, mem->size << PAGE_SHIFT,
+ mem->virt_base, mem->device_base);
+ kfree(mem->bitmap);
+ kfree(mem);
+}
+
+static int
+dt3155_probe(struct pci_dev *pdev, const struct pci_device_id *id)
+{
+ int err;
+ struct dt3155_priv *pd;
+
+ err = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32));
+ if (err)
+ return -ENODEV;
+ pd = kzalloc(sizeof(*pd), GFP_KERNEL);
+ if (!pd)
+ return -ENOMEM;
+ pd->vdev = video_device_alloc();
+ if (!pd->vdev) {
+ err = -ENOMEM;
+ goto err_video_device_alloc;
+ }
+ *pd->vdev = dt3155_vdev;
+ pci_set_drvdata(pdev, pd); /* for use in dt3155_remove() */
+ video_set_drvdata(pd->vdev, pd); /* for use in video_fops */
+ pd->users = 0;
+ pd->pdev = pdev;
+ INIT_LIST_HEAD(&pd->dmaq);
+ mutex_init(&pd->mux);
+ pd->vdev->lock = &pd->mux; /* for locking v4l2_file_operations */
+ spin_lock_init(&pd->lock);
+ pd->csr2 = csr2_init;
+ pd->config = config_init;
+ err = pci_enable_device(pdev);
+ if (err)
+ goto err_enable_dev;
+ err = pci_request_region(pdev, 0, pci_name(pdev));
+ if (err)
+ goto err_req_region;
+ pd->regs = pci_iomap(pdev, 0, pci_resource_len(pd->pdev, 0));
+ if (!pd->regs) {
+ err = -ENOMEM;
+ goto err_pci_iomap;
+ }
+ err = dt3155_init_board(pdev);
+ if (err)
+ goto err_init_board;
+ err = video_register_device(pd->vdev, VFL_TYPE_GRABBER, -1);
+ if (err)
+ goto err_init_board;
+ if (dt3155_alloc_coherent(&pdev->dev, DT3155_CHUNK_SIZE,
+ DMA_MEMORY_MAP))
+ dev_info(&pdev->dev, "preallocated 8 buffers\n");
+ dev_info(&pdev->dev, "/dev/video%i is ready\n", pd->vdev->minor);
+ return 0; /* success */
+
+err_init_board:
+ pci_iounmap(pdev, pd->regs);
+err_pci_iomap:
+ pci_release_region(pdev, 0);
+err_req_region:
+ pci_disable_device(pdev);
+err_enable_dev:
+ video_device_release(pd->vdev);
+err_video_device_alloc:
+ kfree(pd);
+ return err;
+}
+
+static void
+dt3155_remove(struct pci_dev *pdev)
+{
+ struct dt3155_priv *pd = pci_get_drvdata(pdev);
+
+ dt3155_free_coherent(&pdev->dev);
+ video_unregister_device(pd->vdev);
+ pci_iounmap(pdev, pd->regs);
+ pci_release_region(pdev, 0);
+ pci_disable_device(pdev);
+ /*
+ * video_device_release() is invoked automatically
+ * see: struct video_device dt3155_vdev
+ */
+ kfree(pd);
+}
+
+static const struct pci_device_id pci_ids[] = {
+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, DT3155_DEVICE_ID) },
+ { 0, /* zero marks the end */ },
+};
+MODULE_DEVICE_TABLE(pci, pci_ids);
+
+static struct pci_driver pci_driver = {
+ .name = DT3155_NAME,
+ .id_table = pci_ids,
+ .probe = dt3155_probe,
+ .remove = dt3155_remove,
+};
+
+module_pci_driver(pci_driver);
+
+MODULE_DESCRIPTION("video4linux pci-driver for dt3155 frame grabber");
+MODULE_AUTHOR("Marin Mitov <mitov@issp.bas.bg>");
+MODULE_VERSION(DT3155_VERSION);
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/media/dt3155v4l/dt3155v4l.h b/drivers/staging/media/dt3155v4l/dt3155v4l.h
new file mode 100644
index 00000000000..2e4f89d402e
--- /dev/null
+++ b/drivers/staging/media/dt3155v4l/dt3155v4l.h
@@ -0,0 +1,212 @@
+/***************************************************************************
+ * Copyright (C) 2006-2010 by Marin Mitov *
+ * mitov@issp.bas.bg *
+ * *
+ * 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. *
+ ***************************************************************************/
+
+/* DT3155 header file */
+#ifndef _DT3155_H_
+#define _DT3155_H_
+
+#ifdef __KERNEL__
+
+#include <linux/pci.h>
+#include <linux/interrupt.h>
+
+#define DT3155_NAME "dt3155"
+#define DT3155_VER_MAJ 1
+#define DT3155_VER_MIN 1
+#define DT3155_VER_EXT 0
+#define DT3155_VERSION __stringify(DT3155_VER_MAJ) "." \
+ __stringify(DT3155_VER_MIN) "." \
+ __stringify(DT3155_VER_EXT)
+
+/* DT3155 Base Register offsets (memory mapped) */
+#define EVEN_DMA_START 0x00
+#define ODD_DMA_START 0x0C
+#define EVEN_DMA_STRIDE 0x18
+#define ODD_DMA_STRIDE 0x24
+#define EVEN_PIXEL_FMT 0x30
+#define ODD_PIXEL_FMT 0x34
+#define FIFO_TRIGER 0x38
+#define XFER_MODE 0x3C
+#define CSR1 0x40
+#define RETRY_WAIT_CNT 0x44
+#define INT_CSR 0x48
+#define EVEN_FLD_MASK 0x4C
+#define ODD_FLD_MASK 0x50
+#define MASK_LENGTH 0x54
+#define FIFO_FLAG_CNT 0x58
+#define IIC_CLK_DUR 0x5C
+#define IIC_CSR1 0x60
+#define IIC_CSR2 0x64
+
+/* DT3155 Internal Registers indexes (i2c/IIC mapped) */
+#define CSR2 0x10
+#define EVEN_CSR 0x11
+#define ODD_CSR 0x12
+#define CONFIG 0x13
+#define DT_ID 0x1F
+#define X_CLIP_START 0x20
+#define Y_CLIP_START 0x22
+#define X_CLIP_END 0x24
+#define Y_CLIP_END 0x26
+#define AD_ADDR 0x30
+#define AD_LUT 0x31
+#define AD_CMD 0x32
+#define DIG_OUT 0x40
+#define PM_LUT_ADDR 0x50
+#define PM_LUT_DATA 0x51
+
+/* AD command register values */
+#define AD_CMD_REG 0x00
+#define AD_POS_REF 0x01
+#define AD_NEG_REF 0x02
+
+/* CSR1 bit masks */
+#define CRPT_DIS 0x00004000
+#define FLD_CRPT_ODD 0x00000200
+#define FLD_CRPT_EVEN 0x00000100
+#define FIFO_EN 0x00000080
+#define SRST 0x00000040
+#define FLD_DN_ODD 0x00000020
+#define FLD_DN_EVEN 0x00000010
+/* These should not be used.
+ * Use CAP_CONT_ODD/EVEN instead
+#define CAP_SNGL_ODD 0x00000008
+#define CAP_SNGL_EVEN 0x00000004
+*/
+#define CAP_CONT_ODD 0x00000002
+#define CAP_CONT_EVEN 0x00000001
+
+/* INT_CSR bit masks */
+#define FLD_START_EN 0x00000400
+#define FLD_END_ODD_EN 0x00000200
+#define FLD_END_EVEN_EN 0x00000100
+#define FLD_START 0x00000004
+#define FLD_END_ODD 0x00000002
+#define FLD_END_EVEN 0x00000001
+
+/* IIC_CSR1 bit masks */
+#define DIRECT_ABORT 0x00000200
+
+/* IIC_CSR2 bit masks */
+#define NEW_CYCLE 0x01000000
+#define DIR_RD 0x00010000
+#define IIC_READ 0x01010000
+#define IIC_WRITE 0x01000000
+
+/* CSR2 bit masks */
+#define DISP_PASS 0x40
+#define BUSY_ODD 0x20
+#define BUSY_EVEN 0x10
+#define SYNC_PRESENT 0x08
+#define VT_50HZ 0x04
+#define SYNC_SNTL 0x02
+#define CHROM_FILT 0x01
+#define VT_60HZ 0x00
+
+/* CSR_EVEN/ODD bit masks */
+#define CSR_ERROR 0x04
+#define CSR_SNGL 0x02
+#define CSR_DONE 0x01
+
+/* CONFIG bit masks */
+#define PM_LUT_PGM 0x80
+#define PM_LUT_SEL 0x40
+#define CLIP_EN 0x20
+#define HSCALE_EN 0x10
+#define EXT_TRIG_UP 0x0C
+#define EXT_TRIG_DOWN 0x04
+#define ACQ_MODE_NEXT 0x02
+#define ACQ_MODE_ODD 0x01
+#define ACQ_MODE_EVEN 0x00
+
+/* AD_CMD bit masks */
+#define VIDEO_CNL_1 0x00
+#define VIDEO_CNL_2 0x40
+#define VIDEO_CNL_3 0x80
+#define VIDEO_CNL_4 0xC0
+#define SYNC_CNL_1 0x00
+#define SYNC_CNL_2 0x10
+#define SYNC_CNL_3 0x20
+#define SYNC_CNL_4 0x30
+#define SYNC_LVL_1 0x00
+#define SYNC_LVL_2 0x04
+#define SYNC_LVL_3 0x08
+#define SYNC_LVL_4 0x0C
+
+/* DT3155 identificator */
+#define DT3155_ID 0x20
+
+#ifdef CONFIG_DT3155_CCIR
+#define DMA_STRIDE 768
+#else
+#define DMA_STRIDE 640
+#endif
+
+/**
+ * struct dt3155_stats - statistics structure
+ *
+ * @free_bufs_empty: no free image buffers
+ * @corrupted_fields: corrupted fields
+ * @dma_map_failed: dma mapping failed
+ * @start_before_end: new started before old ended
+ */
+struct dt3155_stats {
+ int free_bufs_empty;
+ int corrupted_fields;
+ int dma_map_failed;
+ int start_before_end;
+};
+
+/* per board private data structure */
+/**
+ * struct dt3155_priv - private data structure
+ *
+ * @vdev: pointer to video_device structure
+ * @pdev: pointer to pci_dev structure
+ * @q pointer to vb2_queue structure
+ * @curr_buf: pointer to curren buffer
+ * @mux: mutex to protect the instance
+ * @dmaq queue for dma buffers
+ * @lock spinlock for dma queue
+ * @field_count fields counter
+ * @stats: statistics structure
+ * @users open count
+ * @regs: local copy of mmio base register
+ * @csr2: local copy of csr2 register
+ * @config: local copy of config register
+ */
+struct dt3155_priv {
+ struct video_device *vdev;
+ struct pci_dev *pdev;
+ struct vb2_queue *q;
+ struct vb2_buffer *curr_buf;
+ struct mutex mux;
+ struct list_head dmaq;
+ spinlock_t lock;
+ unsigned int field_count;
+ struct dt3155_stats stats;
+ void __iomem *regs;
+ int users;
+ u8 csr2, config;
+};
+
+#endif /* __KERNEL__ */
+
+#endif /* _DT3155_H_ */
diff --git a/drivers/staging/media/go7007/Kconfig b/drivers/staging/media/go7007/Kconfig
new file mode 100644
index 00000000000..95a3af644a9
--- /dev/null
+++ b/drivers/staging/media/go7007/Kconfig
@@ -0,0 +1,51 @@
+config VIDEO_GO7007
+ tristate "WIS GO7007 MPEG encoder support"
+ depends on VIDEO_DEV && I2C
+ depends on SND && USB
+ select VIDEOBUF2_VMALLOC
+ select VIDEO_TUNER
+ select CYPRESS_FIRMWARE
+ select SND_PCM
+ select VIDEO_SONY_BTF_MPX if MEDIA_SUBDRV_AUTOSELECT
+ select VIDEO_SAA711X if MEDIA_SUBDRV_AUTOSELECT
+ select VIDEO_TW2804 if MEDIA_SUBDRV_AUTOSELECT
+ select VIDEO_TW9903 if MEDIA_SUBDRV_AUTOSELECT
+ select VIDEO_TW9906 if MEDIA_SUBDRV_AUTOSELECT
+ select VIDEO_OV7640 if MEDIA_SUBDRV_AUTOSELECT
+ select VIDEO_UDA1342 if MEDIA_SUBDRV_AUTOSELECT
+ ---help---
+ This is a video4linux driver for the WIS GO7007 MPEG
+ encoder chip.
+
+ To compile this driver as a module, choose M here: the
+ module will be called go7007.
+
+config VIDEO_GO7007_USB
+ tristate "WIS GO7007 USB support"
+ depends on VIDEO_GO7007 && USB
+ ---help---
+ This is a video4linux driver for the WIS GO7007 MPEG
+ encoder chip over USB.
+
+ To compile this driver as a module, choose M here: the
+ module will be called go7007-usb.
+
+config VIDEO_GO7007_LOADER
+ tristate "WIS GO7007 Loader support"
+ depends on VIDEO_GO7007
+ default y
+ ---help---
+ This is a go7007 firmware loader driver for the WIS GO7007
+ MPEG encoder chip over USB.
+
+ To compile this driver as a module, choose M here: the
+ module will be called go7007-loader.
+
+config VIDEO_GO7007_USB_S2250_BOARD
+ tristate "Sensoray 2250/2251 support"
+ depends on VIDEO_GO7007_USB && USB
+ ---help---
+ This is a video4linux driver for the Sensoray 2250/2251 device.
+
+ To compile this driver as a module, choose M here: the
+ module will be called s2250.
diff --git a/drivers/staging/media/go7007/Makefile b/drivers/staging/media/go7007/Makefile
new file mode 100644
index 00000000000..9c6ad4a263e
--- /dev/null
+++ b/drivers/staging/media/go7007/Makefile
@@ -0,0 +1,15 @@
+obj-$(CONFIG_VIDEO_GO7007) += go7007.o
+obj-$(CONFIG_VIDEO_GO7007_USB) += go7007-usb.o
+obj-$(CONFIG_VIDEO_GO7007_LOADER) += go7007-loader.o
+obj-$(CONFIG_VIDEO_GO7007_USB_S2250_BOARD) += s2250.o
+
+go7007-y := go7007-v4l2.o go7007-driver.o go7007-i2c.o go7007-fw.o \
+ snd-go7007.o
+
+s2250-y := s2250-board.o
+
+# Uncomment when the saa7134 patches get into upstream
+#obj-$(CONFIG_VIDEO_SAA7134) += saa7134-go7007.o
+#ccflags-$(CONFIG_VIDEO_SAA7134:m=y) += -Idrivers/media/pci/saa7134
+
+ccflags-$(CONFIG_VIDEO_GO7007_LOADER:m=y) += -Idrivers/media/common
diff --git a/drivers/staging/media/go7007/README b/drivers/staging/media/go7007/README
new file mode 100644
index 00000000000..3af0d906281
--- /dev/null
+++ b/drivers/staging/media/go7007/README
@@ -0,0 +1,137 @@
+Todo:
+ - create an API for motion detection
+ - let s2250-board use i2c subdevs as well instead of hardcoding
+ support for the i2c devices.
+ - when the driver is moved out of staging, support for saa7134-go7007
+ should be added to the saa7134 driver. The patch for that is
+ included below.
+
+Patch for saa7134:
+
+diff --git a/drivers/media/pci/saa7134/saa7134-cards.c b/drivers/media/pci/saa7134/saa7134-cards.c
+index dc68cf1..9a53794 100644
+--- a/drivers/media/pci/saa7134/saa7134-cards.c
++++ b/drivers/media/pci/saa7134/saa7134-cards.c
+@@ -5790,6 +5790,29 @@ struct saa7134_board saa7134_boards[] = {
+ .gpio = 0x6010000,
+ } },
+ },
++ [SAA7134_BOARD_WIS_VOYAGER] = {
++ .name = "WIS Voyager or compatible",
++ .audio_clock = 0x00200000,
++ .tuner_type = TUNER_PHILIPS_TDA8290,
++ .radio_type = UNSET,
++ .tuner_addr = ADDR_UNSET,
++ .radio_addr = ADDR_UNSET,
++ .mpeg = SAA7134_MPEG_GO7007,
++ .inputs = { {
++ .name = name_comp1,
++ .vmux = 0,
++ .amux = LINE2,
++ }, {
++ .name = name_tv,
++ .vmux = 3,
++ .amux = TV,
++ .tv = 1,
++ }, {
++ .name = name_svideo,
++ .vmux = 6,
++ .amux = LINE1,
++ } },
++ },
+
+ };
+
+@@ -7037,6 +7060,12 @@ struct pci_device_id saa7134_pci_tbl[] = {
+ .subdevice = 0x0911,
+ .driver_data = SAA7134_BOARD_SENSORAY811_911,
+ }, {
++ .vendor = PCI_VENDOR_ID_PHILIPS,
++ .device = PCI_DEVICE_ID_PHILIPS_SAA7133,
++ .subvendor = 0x1905, /* WIS */
++ .subdevice = 0x7007,
++ .driver_data = SAA7134_BOARD_WIS_VOYAGER,
++ }, {
+ /* --- boards without eeprom + subsystem ID --- */
+ .vendor = PCI_VENDOR_ID_PHILIPS,
+ .device = PCI_DEVICE_ID_PHILIPS_SAA7134,
+diff --git a/drivers/media/pci/saa7134/saa7134-core.c b/drivers/media/pci/saa7134/saa7134-core.c
+index 8fd24e7..0a849ea 100644
+--- a/drivers/media/pci/saa7134/saa7134-core.c
++++ b/drivers/media/pci/saa7134/saa7134-core.c
+@@ -156,6 +156,8 @@ static void request_module_async(struct work_struct *work){
+ request_module("saa7134-empress");
+ if (card_is_dvb(dev))
+ request_module("saa7134-dvb");
++ if (card_is_go7007(dev))
++ request_module("saa7134-go7007");
+ if (alsa) {
+ if (dev->pci->device != PCI_DEVICE_ID_PHILIPS_SAA7130)
+ request_module("saa7134-alsa");
+@@ -557,8 +559,12 @@ static irqreturn_t saa7134_irq(int irq, void *dev_id)
+ saa7134_irq_vbi_done(dev,status);
+
+ if ((report & SAA7134_IRQ_REPORT_DONE_RA2) &&
+- card_has_mpeg(dev))
+- saa7134_irq_ts_done(dev,status);
++ card_has_mpeg(dev)) {
++ if (dev->mops->irq_ts_done != NULL)
++ dev->mops->irq_ts_done(dev, status);
++ else
++ saa7134_irq_ts_done(dev, status);
++ }
+
+ if (report & SAA7134_IRQ_REPORT_GPIO16) {
+ switch (dev->has_remote) {
+diff --git a/drivers/media/pci/saa7134/saa7134.h b/drivers/media/pci/saa7134/saa7134.h
+index 62169dd..5fad39a 100644
+--- a/drivers/media/pci/saa7134/saa7134.h
++++ b/drivers/media/pci/saa7134/saa7134.h
+@@ -334,6 +334,7 @@ struct saa7134_card_ir {
+ #define SAA7134_BOARD_KWORLD_PC150U 189
+ #define SAA7134_BOARD_ASUSTeK_PS3_100 190
+ #define SAA7134_BOARD_HAWELL_HW_9004V1 191
++#define SAA7134_BOARD_WIS_VOYAGER 192
+
+ #define SAA7134_MAXBOARDS 32
+ #define SAA7134_INPUT_MAX 8
+@@ -364,6 +365,7 @@ enum saa7134_mpeg_type {
+ SAA7134_MPEG_UNUSED,
+ SAA7134_MPEG_EMPRESS,
+ SAA7134_MPEG_DVB,
++ SAA7134_MPEG_GO7007,
+ };
+
+ enum saa7134_mpeg_ts_type {
+@@ -403,6 +405,7 @@ struct saa7134_board {
+ #define card_has_radio(dev) (NULL != saa7134_boards[dev->board].radio.name)
+ #define card_is_empress(dev) (SAA7134_MPEG_EMPRESS == saa7134_boards[dev->board].mpeg)
+ #define card_is_dvb(dev) (SAA7134_MPEG_DVB == saa7134_boards[dev->board].mpeg)
++#define card_is_go7007(dev) (SAA7134_MPEG_GO7007 == saa7134_boards[dev->board].mpeg)
+ #define card_has_mpeg(dev) (SAA7134_MPEG_UNUSED != saa7134_boards[dev->board].mpeg)
+ #define card(dev) (saa7134_boards[dev->board])
+ #define card_in(dev,n) (saa7134_boards[dev->board].inputs[n])
+@@ -535,6 +538,8 @@ struct saa7134_mpeg_ops {
+ int (*init)(struct saa7134_dev *dev);
+ int (*fini)(struct saa7134_dev *dev);
+ void (*signal_change)(struct saa7134_dev *dev);
++ void (*irq_ts_done)(struct saa7134_dev *dev,
++ unsigned long status);
+ };
+
+ /* global device status */
+diff --git a/drivers/staging/media/go7007/Makefile b/drivers/staging/media/go7007/Makefile
+index 9c6ad4a..1b23689 100644
+--- a/drivers/staging/media/go7007/Makefile
++++ b/drivers/staging/media/go7007/Makefile
+@@ -8,8 +8,7 @@ go7007-y := go7007-v4l2.o go7007-driver.o go7007-i2c.o go7007-fw.o \
+
+ s2250-y := s2250-board.o
+
+-# Uncomment when the saa7134 patches get into upstream
+-#obj-$(CONFIG_VIDEO_SAA7134) += saa7134-go7007.o
+-#ccflags-$(CONFIG_VIDEO_SAA7134:m=y) += -Idrivers/media/pci/saa7134
++obj-$(CONFIG_VIDEO_SAA7134) += saa7134-go7007.o
++ccflags-$(CONFIG_VIDEO_SAA7134:m=y) += -Idrivers/media/pci/saa7134
+
+ ccflags-$(CONFIG_VIDEO_GO7007_LOADER:m=y) += -Idrivers/media/common
diff --git a/drivers/staging/media/go7007/go7007-driver.c b/drivers/staging/media/go7007/go7007-driver.c
new file mode 100644
index 00000000000..6f1beca86b2
--- /dev/null
+++ b/drivers/staging/media/go7007/go7007-driver.c
@@ -0,0 +1,711 @@
+/*
+ * Copyright (C) 2005-2006 Micronas USA Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License (Version 2) as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA.
+ */
+
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/sched.h>
+#include <linux/spinlock.h>
+#include <linux/unistd.h>
+#include <linux/time.h>
+#include <linux/mm.h>
+#include <linux/vmalloc.h>
+#include <linux/device.h>
+#include <linux/i2c.h>
+#include <linux/firmware.h>
+#include <linux/mutex.h>
+#include <linux/uaccess.h>
+#include <linux/slab.h>
+#include <linux/videodev2.h>
+#include <media/tuner.h>
+#include <media/v4l2-common.h>
+
+#include "go7007-priv.h"
+
+/*
+ * Wait for an interrupt to be delivered from the GO7007SB and return
+ * the associated value and data.
+ *
+ * Must be called with the hw_lock held.
+ */
+int go7007_read_interrupt(struct go7007 *go, u16 *value, u16 *data)
+{
+ go->interrupt_available = 0;
+ go->hpi_ops->read_interrupt(go);
+ if (wait_event_timeout(go->interrupt_waitq,
+ go->interrupt_available, 5*HZ) < 0) {
+ v4l2_err(&go->v4l2_dev, "timeout waiting for read interrupt\n");
+ return -1;
+ }
+ if (!go->interrupt_available)
+ return -1;
+ go->interrupt_available = 0;
+ *value = go->interrupt_value & 0xfffe;
+ *data = go->interrupt_data;
+ return 0;
+}
+EXPORT_SYMBOL(go7007_read_interrupt);
+
+/*
+ * Read a register/address on the GO7007SB.
+ *
+ * Must be called with the hw_lock held.
+ */
+int go7007_read_addr(struct go7007 *go, u16 addr, u16 *data)
+{
+ int count = 100;
+ u16 value;
+
+ if (go7007_write_interrupt(go, 0x0010, addr) < 0)
+ return -EIO;
+ while (count-- > 0) {
+ if (go7007_read_interrupt(go, &value, data) == 0 &&
+ value == 0xa000)
+ return 0;
+ }
+ return -EIO;
+}
+EXPORT_SYMBOL(go7007_read_addr);
+
+/*
+ * Send the boot firmware to the encoder, which just wakes it up and lets
+ * us talk to the GPIO pins and on-board I2C adapter.
+ *
+ * Must be called with the hw_lock held.
+ */
+static int go7007_load_encoder(struct go7007 *go)
+{
+ const struct firmware *fw_entry;
+ char fw_name[] = "go7007/go7007fw.bin";
+ void *bounce;
+ int fw_len, rv = 0;
+ u16 intr_val, intr_data;
+
+ if (go->boot_fw == NULL) {
+ if (request_firmware(&fw_entry, fw_name, go->dev)) {
+ v4l2_err(go, "unable to load firmware from file \"%s\"\n", fw_name);
+ return -1;
+ }
+ if (fw_entry->size < 16 || memcmp(fw_entry->data, "WISGO7007FW", 11)) {
+ v4l2_err(go, "file \"%s\" does not appear to be go7007 firmware\n", fw_name);
+ release_firmware(fw_entry);
+ return -1;
+ }
+ fw_len = fw_entry->size - 16;
+ bounce = kmemdup(fw_entry->data + 16, fw_len, GFP_KERNEL);
+ if (bounce == NULL) {
+ v4l2_err(go, "unable to allocate %d bytes for firmware transfer\n", fw_len);
+ release_firmware(fw_entry);
+ return -1;
+ }
+ release_firmware(fw_entry);
+ go->boot_fw_len = fw_len;
+ go->boot_fw = bounce;
+ }
+ if (go7007_interface_reset(go) < 0 ||
+ go7007_send_firmware(go, go->boot_fw, go->boot_fw_len) < 0 ||
+ go7007_read_interrupt(go, &intr_val, &intr_data) < 0 ||
+ (intr_val & ~0x1) != 0x5a5a) {
+ v4l2_err(go, "error transferring firmware\n");
+ rv = -1;
+ }
+ return rv;
+}
+
+MODULE_FIRMWARE("go7007/go7007fw.bin");
+
+/*
+ * Boot the encoder and register the I2C adapter if requested. Do the
+ * minimum initialization necessary, since the board-specific code may
+ * still need to probe the board ID.
+ *
+ * Must NOT be called with the hw_lock held.
+ */
+int go7007_boot_encoder(struct go7007 *go, int init_i2c)
+{
+ int ret;
+
+ mutex_lock(&go->hw_lock);
+ ret = go7007_load_encoder(go);
+ mutex_unlock(&go->hw_lock);
+ if (ret < 0)
+ return -1;
+ if (!init_i2c)
+ return 0;
+ if (go7007_i2c_init(go) < 0)
+ return -1;
+ go->i2c_adapter_online = 1;
+ return 0;
+}
+EXPORT_SYMBOL(go7007_boot_encoder);
+
+/*
+ * Configure any hardware-related registers in the GO7007, such as GPIO
+ * pins and bus parameters, which are board-specific. This assumes
+ * the boot firmware has already been downloaded.
+ *
+ * Must be called with the hw_lock held.
+ */
+static int go7007_init_encoder(struct go7007 *go)
+{
+ if (go->board_info->audio_flags & GO7007_AUDIO_I2S_MASTER) {
+ go7007_write_addr(go, 0x1000, 0x0811);
+ go7007_write_addr(go, 0x1000, 0x0c11);
+ }
+ switch (go->board_id) {
+ case GO7007_BOARDID_MATRIX_REV:
+ /* Set GPIO pin 0 to be an output (audio clock control) */
+ go7007_write_addr(go, 0x3c82, 0x0001);
+ go7007_write_addr(go, 0x3c80, 0x00fe);
+ break;
+ case GO7007_BOARDID_ADLINK_MPG24:
+ /* set GPIO5 to be an output, currently low */
+ go7007_write_addr(go, 0x3c82, 0x0000);
+ go7007_write_addr(go, 0x3c80, 0x00df);
+ break;
+ case GO7007_BOARDID_ADS_USBAV_709:
+ /* GPIO pin 0: audio clock control */
+ /* pin 2: TW9906 reset */
+ /* pin 3: capture LED */
+ go7007_write_addr(go, 0x3c82, 0x000d);
+ go7007_write_addr(go, 0x3c80, 0x00f2);
+ break;
+ }
+ return 0;
+}
+
+/*
+ * Send the boot firmware to the GO7007 and configure the registers. This
+ * is the only way to stop the encoder once it has started streaming video.
+ *
+ * Must be called with the hw_lock held.
+ */
+int go7007_reset_encoder(struct go7007 *go)
+{
+ if (go7007_load_encoder(go) < 0)
+ return -1;
+ return go7007_init_encoder(go);
+}
+
+/*
+ * Attempt to instantiate an I2C client by ID, probably loading a module.
+ */
+static int init_i2c_module(struct i2c_adapter *adapter, const struct go_i2c *const i2c)
+{
+ struct go7007 *go = i2c_get_adapdata(adapter);
+ struct v4l2_device *v4l2_dev = &go->v4l2_dev;
+ struct v4l2_subdev *sd;
+ struct i2c_board_info info;
+
+ memset(&info, 0, sizeof(info));
+ strlcpy(info.type, i2c->type, sizeof(info.type));
+ info.addr = i2c->addr;
+ info.flags = i2c->flags;
+
+ sd = v4l2_i2c_new_subdev_board(v4l2_dev, adapter, &info, NULL);
+ if (sd) {
+ if (i2c->is_video)
+ go->sd_video = sd;
+ if (i2c->is_audio)
+ go->sd_audio = sd;
+ return 0;
+ }
+
+ printk(KERN_INFO "go7007: probing for module i2c:%s failed\n", i2c->type);
+ return -EINVAL;
+}
+
+/*
+ * Detach and unregister the encoder. The go7007 struct won't be freed
+ * until v4l2 finishes releasing its resources and all associated fds are
+ * closed by applications.
+ */
+static void go7007_remove(struct v4l2_device *v4l2_dev)
+{
+ struct go7007 *go = container_of(v4l2_dev, struct go7007, v4l2_dev);
+
+ v4l2_device_unregister(v4l2_dev);
+ if (go->hpi_ops->release)
+ go->hpi_ops->release(go);
+ if (go->i2c_adapter_online) {
+ i2c_del_adapter(&go->i2c_adapter);
+ go->i2c_adapter_online = 0;
+ }
+
+ kfree(go->boot_fw);
+ go7007_v4l2_remove(go);
+ kfree(go);
+}
+
+/*
+ * Finalize the GO7007 hardware setup, register the on-board I2C adapter
+ * (if used on this board), load the I2C client driver for the sensor
+ * (SAA7115 or whatever) and other devices, and register the ALSA and V4L2
+ * interfaces.
+ *
+ * Must NOT be called with the hw_lock held.
+ */
+int go7007_register_encoder(struct go7007 *go, unsigned num_i2c_devs)
+{
+ int i, ret;
+
+ dev_info(go->dev, "go7007: registering new %s\n", go->name);
+
+ go->v4l2_dev.release = go7007_remove;
+ ret = v4l2_device_register(go->dev, &go->v4l2_dev);
+ if (ret < 0)
+ return ret;
+
+ mutex_lock(&go->hw_lock);
+ ret = go7007_init_encoder(go);
+ mutex_unlock(&go->hw_lock);
+ if (ret < 0)
+ return ret;
+
+ ret = go7007_v4l2_ctrl_init(go);
+ if (ret < 0)
+ return ret;
+
+ if (!go->i2c_adapter_online &&
+ go->board_info->flags & GO7007_BOARD_USE_ONBOARD_I2C) {
+ ret = go7007_i2c_init(go);
+ if (ret < 0)
+ return ret;
+ go->i2c_adapter_online = 1;
+ }
+ if (go->i2c_adapter_online) {
+ if (go->board_id == GO7007_BOARDID_ADS_USBAV_709) {
+ /* Reset the TW9906 */
+ go7007_write_addr(go, 0x3c82, 0x0009);
+ msleep(50);
+ go7007_write_addr(go, 0x3c82, 0x000d);
+ }
+ for (i = 0; i < num_i2c_devs; ++i)
+ init_i2c_module(&go->i2c_adapter, &go->board_info->i2c_devs[i]);
+
+ if (go->tuner_type >= 0) {
+ struct tuner_setup setup = {
+ .addr = ADDR_UNSET,
+ .type = go->tuner_type,
+ .mode_mask = T_ANALOG_TV,
+ };
+
+ v4l2_device_call_all(&go->v4l2_dev, 0, tuner,
+ s_type_addr, &setup);
+ }
+ if (go->board_id == GO7007_BOARDID_ADLINK_MPG24)
+ v4l2_subdev_call(go->sd_video, video, s_routing,
+ 0, 0, go->channel_number + 1);
+ }
+
+ ret = go7007_v4l2_init(go);
+ if (ret < 0)
+ return ret;
+
+ if (go->board_info->flags & GO7007_BOARD_HAS_AUDIO) {
+ go->audio_enabled = 1;
+ go7007_snd_init(go);
+ }
+ return 0;
+}
+EXPORT_SYMBOL(go7007_register_encoder);
+
+/*
+ * Send the encode firmware to the encoder, which will cause it
+ * to immediately start delivering the video and audio streams.
+ *
+ * Must be called with the hw_lock held.
+ */
+int go7007_start_encoder(struct go7007 *go)
+{
+ u8 *fw;
+ int fw_len, rv = 0, i;
+ u16 intr_val, intr_data;
+
+ go->modet_enable = 0;
+ if (!go->dvd_mode)
+ for (i = 0; i < 4; ++i) {
+ if (go->modet[i].enable) {
+ go->modet_enable = 1;
+ continue;
+ }
+ go->modet[i].pixel_threshold = 32767;
+ go->modet[i].motion_threshold = 32767;
+ go->modet[i].mb_threshold = 32767;
+ }
+
+ if (go7007_construct_fw_image(go, &fw, &fw_len) < 0)
+ return -1;
+
+ if (go7007_send_firmware(go, fw, fw_len) < 0 ||
+ go7007_read_interrupt(go, &intr_val, &intr_data) < 0) {
+ v4l2_err(&go->v4l2_dev, "error transferring firmware\n");
+ rv = -1;
+ goto start_error;
+ }
+
+ go->state = STATE_DATA;
+ go->parse_length = 0;
+ go->seen_frame = 0;
+ if (go7007_stream_start(go) < 0) {
+ v4l2_err(&go->v4l2_dev, "error starting stream transfer\n");
+ rv = -1;
+ goto start_error;
+ }
+
+start_error:
+ kfree(fw);
+ return rv;
+}
+
+/*
+ * Store a byte in the current video buffer, if there is one.
+ */
+static inline void store_byte(struct go7007_buffer *vb, u8 byte)
+{
+ if (vb && vb->vb.v4l2_planes[0].bytesused < GO7007_BUF_SIZE) {
+ u8 *ptr = vb2_plane_vaddr(&vb->vb, 0);
+
+ ptr[vb->vb.v4l2_planes[0].bytesused++] = byte;
+ }
+}
+
+/*
+ * Deliver the last video buffer and get a new one to start writing to.
+ */
+static struct go7007_buffer *frame_boundary(struct go7007 *go, struct go7007_buffer *vb)
+{
+ struct go7007_buffer *vb_tmp = NULL;
+ u32 *bytesused = &vb->vb.v4l2_planes[0].bytesused;
+ int i;
+
+ if (vb) {
+ if (vb->modet_active) {
+ if (*bytesused + 216 < GO7007_BUF_SIZE) {
+ for (i = 0; i < 216; ++i)
+ store_byte(vb, go->active_map[i]);
+ *bytesused -= 216;
+ } else
+ vb->modet_active = 0;
+ }
+ vb->vb.v4l2_buf.sequence = go->next_seq++;
+ v4l2_get_timestamp(&vb->vb.v4l2_buf.timestamp);
+ vb_tmp = vb;
+ spin_lock(&go->spinlock);
+ list_del(&vb->list);
+ if (list_empty(&go->vidq_active))
+ vb = NULL;
+ else
+ vb = list_first_entry(&go->vidq_active, struct go7007_buffer, list);
+ go->active_buf = vb;
+ spin_unlock(&go->spinlock);
+ vb2_buffer_done(&vb_tmp->vb, VB2_BUF_STATE_DONE);
+ return vb;
+ }
+ spin_lock(&go->spinlock);
+ if (!list_empty(&go->vidq_active))
+ vb = go->active_buf =
+ list_first_entry(&go->vidq_active, struct go7007_buffer, list);
+ spin_unlock(&go->spinlock);
+ go->next_seq++;
+ return vb;
+}
+
+static void write_bitmap_word(struct go7007 *go)
+{
+ int x, y, i, stride = ((go->width >> 4) + 7) >> 3;
+
+ for (i = 0; i < 16; ++i) {
+ y = (((go->parse_length - 1) << 3) + i) / (go->width >> 4);
+ x = (((go->parse_length - 1) << 3) + i) % (go->width >> 4);
+ if (stride * y + (x >> 3) < sizeof(go->active_map))
+ go->active_map[stride * y + (x >> 3)] |=
+ (go->modet_word & 1) << (x & 0x7);
+ go->modet_word >>= 1;
+ }
+}
+
+/*
+ * Parse a chunk of the video stream into frames. The frames are not
+ * delimited by the hardware, so we have to parse the frame boundaries
+ * based on the type of video stream we're receiving.
+ */
+void go7007_parse_video_stream(struct go7007 *go, u8 *buf, int length)
+{
+ struct go7007_buffer *vb = go->active_buf;
+ int i, seq_start_code = -1, gop_start_code = -1, frame_start_code = -1;
+
+ switch (go->format) {
+ case V4L2_PIX_FMT_MPEG4:
+ seq_start_code = 0xB0;
+ gop_start_code = 0xB3;
+ frame_start_code = 0xB6;
+ break;
+ case V4L2_PIX_FMT_MPEG1:
+ case V4L2_PIX_FMT_MPEG2:
+ seq_start_code = 0xB3;
+ gop_start_code = 0xB8;
+ frame_start_code = 0x00;
+ break;
+ }
+
+ for (i = 0; i < length; ++i) {
+ if (vb && vb->vb.v4l2_planes[0].bytesused >= GO7007_BUF_SIZE - 3) {
+ v4l2_info(&go->v4l2_dev, "dropping oversized frame\n");
+ vb->vb.v4l2_planes[0].bytesused = 0;
+ vb->frame_offset = 0;
+ vb->modet_active = 0;
+ vb = go->active_buf = NULL;
+ }
+
+ switch (go->state) {
+ case STATE_DATA:
+ switch (buf[i]) {
+ case 0x00:
+ go->state = STATE_00;
+ break;
+ case 0xFF:
+ go->state = STATE_FF;
+ break;
+ default:
+ store_byte(vb, buf[i]);
+ break;
+ }
+ break;
+ case STATE_00:
+ switch (buf[i]) {
+ case 0x00:
+ go->state = STATE_00_00;
+ break;
+ case 0xFF:
+ store_byte(vb, 0x00);
+ go->state = STATE_FF;
+ break;
+ default:
+ store_byte(vb, 0x00);
+ store_byte(vb, buf[i]);
+ go->state = STATE_DATA;
+ break;
+ }
+ break;
+ case STATE_00_00:
+ switch (buf[i]) {
+ case 0x00:
+ store_byte(vb, 0x00);
+ /* go->state remains STATE_00_00 */
+ break;
+ case 0x01:
+ go->state = STATE_00_00_01;
+ break;
+ case 0xFF:
+ store_byte(vb, 0x00);
+ store_byte(vb, 0x00);
+ go->state = STATE_FF;
+ break;
+ default:
+ store_byte(vb, 0x00);
+ store_byte(vb, 0x00);
+ store_byte(vb, buf[i]);
+ go->state = STATE_DATA;
+ break;
+ }
+ break;
+ case STATE_00_00_01:
+ if (buf[i] == 0xF8 && go->modet_enable == 0) {
+ /* MODET start code, but MODET not enabled */
+ store_byte(vb, 0x00);
+ store_byte(vb, 0x00);
+ store_byte(vb, 0x01);
+ store_byte(vb, 0xF8);
+ go->state = STATE_DATA;
+ break;
+ }
+ /* If this is the start of a new MPEG frame,
+ * get a new buffer */
+ if ((go->format == V4L2_PIX_FMT_MPEG1 ||
+ go->format == V4L2_PIX_FMT_MPEG2 ||
+ go->format == V4L2_PIX_FMT_MPEG4) &&
+ (buf[i] == seq_start_code ||
+ buf[i] == gop_start_code ||
+ buf[i] == frame_start_code)) {
+ if (vb == NULL || go->seen_frame)
+ vb = frame_boundary(go, vb);
+ go->seen_frame = buf[i] == frame_start_code;
+ if (vb && go->seen_frame)
+ vb->frame_offset = vb->vb.v4l2_planes[0].bytesused;
+ }
+ /* Handle any special chunk types, or just write the
+ * start code to the (potentially new) buffer */
+ switch (buf[i]) {
+ case 0xF5: /* timestamp */
+ go->parse_length = 12;
+ go->state = STATE_UNPARSED;
+ break;
+ case 0xF6: /* vbi */
+ go->state = STATE_VBI_LEN_A;
+ break;
+ case 0xF8: /* MD map */
+ go->parse_length = 0;
+ memset(go->active_map, 0,
+ sizeof(go->active_map));
+ go->state = STATE_MODET_MAP;
+ break;
+ case 0xFF: /* Potential JPEG start code */
+ store_byte(vb, 0x00);
+ store_byte(vb, 0x00);
+ store_byte(vb, 0x01);
+ go->state = STATE_FF;
+ break;
+ default:
+ store_byte(vb, 0x00);
+ store_byte(vb, 0x00);
+ store_byte(vb, 0x01);
+ store_byte(vb, buf[i]);
+ go->state = STATE_DATA;
+ break;
+ }
+ break;
+ case STATE_FF:
+ switch (buf[i]) {
+ case 0x00:
+ store_byte(vb, 0xFF);
+ go->state = STATE_00;
+ break;
+ case 0xFF:
+ store_byte(vb, 0xFF);
+ /* go->state remains STATE_FF */
+ break;
+ case 0xD8:
+ if (go->format == V4L2_PIX_FMT_MJPEG)
+ vb = frame_boundary(go, vb);
+ /* fall through */
+ default:
+ store_byte(vb, 0xFF);
+ store_byte(vb, buf[i]);
+ go->state = STATE_DATA;
+ break;
+ }
+ break;
+ case STATE_VBI_LEN_A:
+ go->parse_length = buf[i] << 8;
+ go->state = STATE_VBI_LEN_B;
+ break;
+ case STATE_VBI_LEN_B:
+ go->parse_length |= buf[i];
+ if (go->parse_length > 0)
+ go->state = STATE_UNPARSED;
+ else
+ go->state = STATE_DATA;
+ break;
+ case STATE_MODET_MAP:
+ if (go->parse_length < 204) {
+ if (go->parse_length & 1) {
+ go->modet_word |= buf[i];
+ write_bitmap_word(go);
+ } else
+ go->modet_word = buf[i] << 8;
+ } else if (go->parse_length == 207 && vb) {
+ vb->modet_active = buf[i];
+ }
+ if (++go->parse_length == 208)
+ go->state = STATE_DATA;
+ break;
+ case STATE_UNPARSED:
+ if (--go->parse_length == 0)
+ go->state = STATE_DATA;
+ break;
+ }
+ }
+}
+EXPORT_SYMBOL(go7007_parse_video_stream);
+
+/*
+ * Allocate a new go7007 struct. Used by the hardware-specific probe.
+ */
+struct go7007 *go7007_alloc(const struct go7007_board_info *board,
+ struct device *dev)
+{
+ struct go7007 *go;
+ int i;
+
+ go = kzalloc(sizeof(struct go7007), GFP_KERNEL);
+ if (go == NULL)
+ return NULL;
+ go->dev = dev;
+ go->board_info = board;
+ go->board_id = 0;
+ go->tuner_type = -1;
+ go->channel_number = 0;
+ go->name[0] = 0;
+ mutex_init(&go->hw_lock);
+ init_waitqueue_head(&go->frame_waitq);
+ spin_lock_init(&go->spinlock);
+ go->status = STATUS_INIT;
+ memset(&go->i2c_adapter, 0, sizeof(go->i2c_adapter));
+ go->i2c_adapter_online = 0;
+ go->interrupt_available = 0;
+ init_waitqueue_head(&go->interrupt_waitq);
+ go->input = 0;
+ go7007_update_board(go);
+ go->encoder_h_halve = 0;
+ go->encoder_v_halve = 0;
+ go->encoder_subsample = 0;
+ go->format = V4L2_PIX_FMT_MJPEG;
+ go->bitrate = 1500000;
+ go->fps_scale = 1;
+ go->pali = 0;
+ go->aspect_ratio = GO7007_RATIO_1_1;
+ go->gop_size = 0;
+ go->ipb = 0;
+ go->closed_gop = 0;
+ go->repeat_seqhead = 0;
+ go->seq_header_enable = 0;
+ go->gop_header_enable = 0;
+ go->dvd_mode = 0;
+ go->interlace_coding = 0;
+ for (i = 0; i < 4; ++i)
+ go->modet[i].enable = 0;
+ for (i = 0; i < 1624; ++i)
+ go->modet_map[i] = 0;
+ go->audio_deliver = NULL;
+ go->audio_enabled = 0;
+
+ return go;
+}
+EXPORT_SYMBOL(go7007_alloc);
+
+void go7007_update_board(struct go7007 *go)
+{
+ const struct go7007_board_info *board = go->board_info;
+
+ if (board->sensor_flags & GO7007_SENSOR_TV) {
+ go->standard = GO7007_STD_NTSC;
+ go->std = V4L2_STD_NTSC_M;
+ go->width = 720;
+ go->height = 480;
+ go->sensor_framerate = 30000;
+ } else {
+ go->standard = GO7007_STD_OTHER;
+ go->width = board->sensor_width;
+ go->height = board->sensor_height;
+ go->sensor_framerate = board->sensor_framerate;
+ }
+ go->encoder_v_offset = board->sensor_v_offset;
+ go->encoder_h_offset = board->sensor_h_offset;
+}
+EXPORT_SYMBOL(go7007_update_board);
+
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/staging/media/go7007/go7007-fw.c b/drivers/staging/media/go7007/go7007-fw.c
new file mode 100644
index 00000000000..814ce08bc44
--- /dev/null
+++ b/drivers/staging/media/go7007/go7007-fw.c
@@ -0,0 +1,1628 @@
+/*
+ * Copyright (C) 2005-2006 Micronas USA Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License (Version 2) as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA.
+ */
+
+/*
+ * This file contains code to generate a firmware image for the GO7007SB
+ * encoder. Much of the firmware is read verbatim from a file, but some of
+ * it concerning bitrate control and other things that can be configured at
+ * run-time are generated dynamically. Note that the format headers
+ * generated here do not affect the functioning of the encoder; they are
+ * merely parroted back to the host at the start of each frame.
+ */
+
+#include <linux/module.h>
+#include <linux/time.h>
+#include <linux/mm.h>
+#include <linux/device.h>
+#include <linux/i2c.h>
+#include <linux/firmware.h>
+#include <linux/slab.h>
+#include <asm/byteorder.h>
+
+#include "go7007-priv.h"
+
+#define GO7007_FW_NAME "go7007/go7007tv.bin"
+
+/* Constants used in the source firmware image to describe code segments */
+
+#define FLAG_MODE_MJPEG (1)
+#define FLAG_MODE_MPEG1 (1<<1)
+#define FLAG_MODE_MPEG2 (1<<2)
+#define FLAG_MODE_MPEG4 (1<<3)
+#define FLAG_MODE_H263 (1<<4)
+#define FLAG_MODE_ALL (FLAG_MODE_MJPEG | FLAG_MODE_MPEG1 | \
+ FLAG_MODE_MPEG2 | FLAG_MODE_MPEG4 | \
+ FLAG_MODE_H263)
+#define FLAG_SPECIAL (1<<8)
+
+#define SPECIAL_FRM_HEAD 0
+#define SPECIAL_BRC_CTRL 1
+#define SPECIAL_CONFIG 2
+#define SPECIAL_SEQHEAD 3
+#define SPECIAL_AV_SYNC 4
+#define SPECIAL_FINAL 5
+#define SPECIAL_AUDIO 6
+#define SPECIAL_MODET 7
+
+/* Little data class for creating MPEG headers bit-by-bit */
+
+struct code_gen {
+ unsigned char *p; /* destination */
+ u32 a; /* collects bits at the top of the variable */
+ int b; /* bit position of most recently-written bit */
+ int len; /* written out so far */
+};
+
+#define CODE_GEN(name, dest) struct code_gen name = { dest, 0, 32, 0 }
+
+#define CODE_ADD(name, val, length) do { \
+ name.b -= (length); \
+ name.a |= (val) << name.b; \
+ while (name.b <= 24) { \
+ *name.p = name.a >> 24; \
+ ++name.p; \
+ name.a <<= 8; \
+ name.b += 8; \
+ name.len += 8; \
+ } \
+} while (0)
+
+#define CODE_LENGTH(name) (name.len + (32 - name.b))
+
+/* Tables for creating the bitrate control data */
+
+static const s16 converge_speed_ip[101] = {
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 3,
+ 3, 3, 3, 3, 3, 4, 4, 4, 4, 4,
+ 5, 5, 5, 6, 6, 6, 7, 7, 8, 8,
+ 9, 10, 10, 11, 12, 13, 14, 15, 16, 17,
+ 19, 20, 22, 23, 25, 27, 30, 32, 35, 38,
+ 41, 45, 49, 53, 58, 63, 69, 76, 83, 91,
+ 100
+};
+
+static const s16 converge_speed_ipb[101] = {
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 5, 5, 5, 5, 5, 6,
+ 6, 6, 6, 7, 7, 7, 7, 8, 8, 9,
+ 9, 9, 10, 10, 11, 12, 12, 13, 14, 14,
+ 15, 16, 17, 18, 19, 20, 22, 23, 25, 26,
+ 28, 30, 32, 34, 37, 40, 42, 46, 49, 53,
+ 57, 61, 66, 71, 77, 83, 90, 97, 106, 115,
+ 125, 135, 147, 161, 175, 191, 209, 228, 249, 273,
+ 300
+};
+
+static const s16 LAMBDA_table[4][101] = {
+ { 16, 16, 16, 16, 17, 17, 17, 18, 18, 18,
+ 19, 19, 19, 20, 20, 20, 21, 21, 22, 22,
+ 22, 23, 23, 24, 24, 25, 25, 25, 26, 26,
+ 27, 27, 28, 28, 29, 29, 30, 31, 31, 32,
+ 32, 33, 33, 34, 35, 35, 36, 37, 37, 38,
+ 39, 39, 40, 41, 42, 42, 43, 44, 45, 46,
+ 46, 47, 48, 49, 50, 51, 52, 53, 54, 55,
+ 56, 57, 58, 59, 60, 61, 62, 63, 64, 65,
+ 67, 68, 69, 70, 72, 73, 74, 76, 77, 78,
+ 80, 81, 83, 84, 86, 87, 89, 90, 92, 94,
+ 96
+ },
+ {
+ 20, 20, 20, 21, 21, 21, 22, 22, 23, 23,
+ 23, 24, 24, 25, 25, 26, 26, 27, 27, 28,
+ 28, 29, 29, 30, 30, 31, 31, 32, 33, 33,
+ 34, 34, 35, 36, 36, 37, 38, 38, 39, 40,
+ 40, 41, 42, 43, 43, 44, 45, 46, 47, 48,
+ 48, 49, 50, 51, 52, 53, 54, 55, 56, 57,
+ 58, 59, 60, 61, 62, 64, 65, 66, 67, 68,
+ 70, 71, 72, 73, 75, 76, 78, 79, 80, 82,
+ 83, 85, 86, 88, 90, 91, 93, 95, 96, 98,
+ 100, 102, 103, 105, 107, 109, 111, 113, 115, 117,
+ 120
+ },
+ {
+ 24, 24, 24, 25, 25, 26, 26, 27, 27, 28,
+ 28, 29, 29, 30, 30, 31, 31, 32, 33, 33,
+ 34, 34, 35, 36, 36, 37, 38, 38, 39, 40,
+ 41, 41, 42, 43, 44, 44, 45, 46, 47, 48,
+ 49, 50, 50, 51, 52, 53, 54, 55, 56, 57,
+ 58, 59, 60, 62, 63, 64, 65, 66, 67, 69,
+ 70, 71, 72, 74, 75, 76, 78, 79, 81, 82,
+ 84, 85, 87, 88, 90, 92, 93, 95, 97, 98,
+ 100, 102, 104, 106, 108, 110, 112, 114, 116, 118,
+ 120, 122, 124, 127, 129, 131, 134, 136, 138, 141,
+ 144
+ },
+ {
+ 32, 32, 33, 33, 34, 34, 35, 36, 36, 37,
+ 38, 38, 39, 40, 41, 41, 42, 43, 44, 44,
+ 45, 46, 47, 48, 49, 50, 50, 51, 52, 53,
+ 54, 55, 56, 57, 58, 59, 60, 62, 63, 64,
+ 65, 66, 67, 69, 70, 71, 72, 74, 75, 76,
+ 78, 79, 81, 82, 84, 85, 87, 88, 90, 92,
+ 93, 95, 97, 98, 100, 102, 104, 106, 108, 110,
+ 112, 114, 116, 118, 120, 122, 124, 127, 129, 131,
+ 134, 136, 139, 141, 144, 146, 149, 152, 154, 157,
+ 160, 163, 166, 169, 172, 175, 178, 181, 185, 188,
+ 192
+ }
+};
+
+/* MPEG blank frame generation tables */
+
+enum mpeg_frame_type {
+ PFRAME,
+ BFRAME_PRE,
+ BFRAME_POST,
+ BFRAME_BIDIR,
+ BFRAME_EMPTY
+};
+
+static const u32 addrinctab[33][2] = {
+ { 0x01, 1 }, { 0x03, 3 }, { 0x02, 3 }, { 0x03, 4 },
+ { 0x02, 4 }, { 0x03, 5 }, { 0x02, 5 }, { 0x07, 7 },
+ { 0x06, 7 }, { 0x0b, 8 }, { 0x0a, 8 }, { 0x09, 8 },
+ { 0x08, 8 }, { 0x07, 8 }, { 0x06, 8 }, { 0x17, 10 },
+ { 0x16, 10 }, { 0x15, 10 }, { 0x14, 10 }, { 0x13, 10 },
+ { 0x12, 10 }, { 0x23, 11 }, { 0x22, 11 }, { 0x21, 11 },
+ { 0x20, 11 }, { 0x1f, 11 }, { 0x1e, 11 }, { 0x1d, 11 },
+ { 0x1c, 11 }, { 0x1b, 11 }, { 0x1a, 11 }, { 0x19, 11 },
+ { 0x18, 11 }
+};
+
+/* Standard JPEG tables */
+
+static const u8 default_intra_quant_table[] = {
+ 8, 16, 19, 22, 26, 27, 29, 34,
+ 16, 16, 22, 24, 27, 29, 34, 37,
+ 19, 22, 26, 27, 29, 34, 34, 38,
+ 22, 22, 26, 27, 29, 34, 37, 40,
+ 22, 26, 27, 29, 32, 35, 40, 48,
+ 26, 27, 29, 32, 35, 40, 48, 58,
+ 26, 27, 29, 34, 38, 46, 56, 69,
+ 27, 29, 35, 38, 46, 56, 69, 83
+};
+
+static const u8 bits_dc_luminance[] = {
+ 0, 0, 1, 5, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0
+};
+
+static const u8 val_dc_luminance[] = {
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11
+};
+
+static const u8 bits_dc_chrominance[] = {
+ 0, 0, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0
+};
+
+static const u8 val_dc_chrominance[] = {
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11
+};
+
+static const u8 bits_ac_luminance[] = {
+ 0, 0, 2, 1, 3, 3, 2, 4, 3, 5, 5, 4, 4, 0, 0, 1, 0x7d
+};
+
+static const u8 val_ac_luminance[] = {
+ 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
+};
+
+static const u8 bits_ac_chrominance[] = {
+ 0, 0, 2, 1, 2, 4, 4, 3, 4, 7, 5, 4, 4, 0, 1, 2, 0x77
+};
+
+static const u8 val_ac_chrominance[] = {
+ 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
+};
+
+/* Zig-zag mapping for quant table
+ *
+ * OK, let's do this mapping on the actual table above so it doesn't have
+ * to be done on the fly.
+ */
+static const int zz[64] = {
+ 0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5,
+ 12, 19, 26, 33, 40, 48, 41, 34, 27, 20, 13, 6, 7, 14, 21, 28,
+ 35, 42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23, 30, 37, 44, 51,
+ 58, 59, 52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63
+};
+
+static int copy_packages(__le16 *dest, u16 *src, int pkg_cnt, int space)
+{
+ int i, cnt = pkg_cnt * 32;
+
+ if (space < cnt)
+ return -1;
+
+ for (i = 0; i < cnt; ++i)
+ dest[i] = cpu_to_le16p(src + i);
+
+ return cnt;
+}
+
+static int mjpeg_frame_header(struct go7007 *go, unsigned char *buf, int q)
+{
+ int i, p = 0;
+
+ buf[p++] = 0xff;
+ buf[p++] = 0xd8;
+ buf[p++] = 0xff;
+ buf[p++] = 0xdb;
+ buf[p++] = 0;
+ buf[p++] = 2 + 65;
+ buf[p++] = 0;
+ buf[p++] = default_intra_quant_table[0];
+ for (i = 1; i < 64; ++i)
+ /* buf[p++] = (default_intra_quant_table[i] * q) >> 3; */
+ buf[p++] = (default_intra_quant_table[zz[i]] * q) >> 3;
+ buf[p++] = 0xff;
+ buf[p++] = 0xc0;
+ buf[p++] = 0;
+ buf[p++] = 17;
+ buf[p++] = 8;
+ buf[p++] = go->height >> 8;
+ buf[p++] = go->height & 0xff;
+ buf[p++] = go->width >> 8;
+ buf[p++] = go->width & 0xff;
+ buf[p++] = 3;
+ buf[p++] = 1;
+ buf[p++] = 0x22;
+ buf[p++] = 0;
+ buf[p++] = 2;
+ buf[p++] = 0x11;
+ buf[p++] = 0;
+ buf[p++] = 3;
+ buf[p++] = 0x11;
+ buf[p++] = 0;
+ buf[p++] = 0xff;
+ buf[p++] = 0xc4;
+ buf[p++] = 418 >> 8;
+ buf[p++] = 418 & 0xff;
+ buf[p++] = 0x00;
+ memcpy(buf + p, bits_dc_luminance + 1, 16);
+ p += 16;
+ memcpy(buf + p, val_dc_luminance, sizeof(val_dc_luminance));
+ p += sizeof(val_dc_luminance);
+ buf[p++] = 0x01;
+ memcpy(buf + p, bits_dc_chrominance + 1, 16);
+ p += 16;
+ memcpy(buf + p, val_dc_chrominance, sizeof(val_dc_chrominance));
+ p += sizeof(val_dc_chrominance);
+ buf[p++] = 0x10;
+ memcpy(buf + p, bits_ac_luminance + 1, 16);
+ p += 16;
+ memcpy(buf + p, val_ac_luminance, sizeof(val_ac_luminance));
+ p += sizeof(val_ac_luminance);
+ buf[p++] = 0x11;
+ memcpy(buf + p, bits_ac_chrominance + 1, 16);
+ p += 16;
+ memcpy(buf + p, val_ac_chrominance, sizeof(val_ac_chrominance));
+ p += sizeof(val_ac_chrominance);
+ buf[p++] = 0xff;
+ buf[p++] = 0xda;
+ buf[p++] = 0;
+ buf[p++] = 12;
+ buf[p++] = 3;
+ buf[p++] = 1;
+ buf[p++] = 0x00;
+ buf[p++] = 2;
+ buf[p++] = 0x11;
+ buf[p++] = 3;
+ buf[p++] = 0x11;
+ buf[p++] = 0;
+ buf[p++] = 63;
+ buf[p++] = 0;
+ return p;
+}
+
+static int gen_mjpeghdr_to_package(struct go7007 *go, __le16 *code, int space)
+{
+ u8 *buf;
+ u16 mem = 0x3e00;
+ unsigned int addr = 0x19;
+ int size = 0, i, off = 0, chunk;
+
+ buf = kzalloc(4096, GFP_KERNEL);
+ if (buf == NULL)
+ return -1;
+
+ for (i = 1; i < 32; ++i) {
+ mjpeg_frame_header(go, buf + size, i);
+ size += 80;
+ }
+ chunk = mjpeg_frame_header(go, buf + size, 1);
+ memmove(buf + size, buf + size + 80, chunk - 80);
+ size += chunk - 80;
+
+ for (i = 0; i < size; i += chunk * 2) {
+ if (space - off < 32) {
+ off = -1;
+ goto done;
+ }
+
+ code[off + 1] = __cpu_to_le16(0x8000 | mem);
+
+ chunk = 28;
+ if (mem + chunk > 0x4000)
+ chunk = 0x4000 - mem;
+ if (i + 2 * chunk > size)
+ chunk = (size - i) / 2;
+
+ if (chunk < 28) {
+ code[off] = __cpu_to_le16(0x4000 | chunk);
+ code[off + 31] = __cpu_to_le16(addr++);
+ mem = 0x3e00;
+ } else {
+ code[off] = __cpu_to_le16(0x1000 | 28);
+ code[off + 31] = 0;
+ mem += 28;
+ }
+
+ memcpy(&code[off + 2], buf + i, chunk * 2);
+ off += 32;
+ }
+done:
+ kfree(buf);
+ return off;
+}
+
+static int mpeg1_frame_header(struct go7007 *go, unsigned char *buf,
+ int modulo, int pict_struct, enum mpeg_frame_type frame)
+{
+ int i, j, mb_code, mb_len;
+ int rows = go->interlace_coding ? go->height / 32 : go->height / 16;
+ CODE_GEN(c, buf + 6);
+
+ switch (frame) {
+ case PFRAME:
+ mb_code = 0x1;
+ mb_len = 3;
+ break;
+ case BFRAME_PRE:
+ mb_code = 0x2;
+ mb_len = 4;
+ break;
+ case BFRAME_POST:
+ mb_code = 0x2;
+ mb_len = 3;
+ break;
+ case BFRAME_BIDIR:
+ mb_code = 0x2;
+ mb_len = 2;
+ break;
+ default: /* keep the compiler happy */
+ mb_code = mb_len = 0;
+ break;
+ }
+
+ CODE_ADD(c, frame == PFRAME ? 0x2 : 0x3, 13);
+ CODE_ADD(c, 0xffff, 16);
+ CODE_ADD(c, go->format == V4L2_PIX_FMT_MPEG2 ? 0x7 : 0x4, 4);
+ if (frame != PFRAME)
+ CODE_ADD(c, go->format == V4L2_PIX_FMT_MPEG2 ? 0x7 : 0x4, 4);
+ else
+ CODE_ADD(c, 0, 4); /* Is this supposed to be here?? */
+ CODE_ADD(c, 0, 3); /* What is this?? */
+ /* Byte-align with zeros */
+ j = 8 - (CODE_LENGTH(c) % 8);
+ if (j != 8)
+ CODE_ADD(c, 0, j);
+
+ if (go->format == V4L2_PIX_FMT_MPEG2) {
+ CODE_ADD(c, 0x1, 24);
+ CODE_ADD(c, 0xb5, 8);
+ CODE_ADD(c, 0x844, 12);
+ CODE_ADD(c, frame == PFRAME ? 0xff : 0x44, 8);
+ if (go->interlace_coding) {
+ CODE_ADD(c, pict_struct, 4);
+ if (go->dvd_mode)
+ CODE_ADD(c, 0x000, 11);
+ else
+ CODE_ADD(c, 0x200, 11);
+ } else {
+ CODE_ADD(c, 0x3, 4);
+ CODE_ADD(c, 0x20c, 11);
+ }
+ /* Byte-align with zeros */
+ j = 8 - (CODE_LENGTH(c) % 8);
+ if (j != 8)
+ CODE_ADD(c, 0, j);
+ }
+
+ for (i = 0; i < rows; ++i) {
+ CODE_ADD(c, 1, 24);
+ CODE_ADD(c, i + 1, 8);
+ CODE_ADD(c, 0x2, 6);
+ CODE_ADD(c, 0x1, 1);
+ CODE_ADD(c, mb_code, mb_len);
+ if (go->interlace_coding) {
+ CODE_ADD(c, 0x1, 2);
+ CODE_ADD(c, pict_struct == 1 ? 0x0 : 0x1, 1);
+ }
+ if (frame == BFRAME_BIDIR) {
+ CODE_ADD(c, 0x3, 2);
+ if (go->interlace_coding)
+ CODE_ADD(c, pict_struct == 1 ? 0x0 : 0x1, 1);
+ }
+ CODE_ADD(c, 0x3, 2);
+ for (j = (go->width >> 4) - 2; j >= 33; j -= 33)
+ CODE_ADD(c, 0x8, 11);
+ CODE_ADD(c, addrinctab[j][0], addrinctab[j][1]);
+ CODE_ADD(c, mb_code, mb_len);
+ if (go->interlace_coding) {
+ CODE_ADD(c, 0x1, 2);
+ CODE_ADD(c, pict_struct == 1 ? 0x0 : 0x1, 1);
+ }
+ if (frame == BFRAME_BIDIR) {
+ CODE_ADD(c, 0x3, 2);
+ if (go->interlace_coding)
+ CODE_ADD(c, pict_struct == 1 ? 0x0 : 0x1, 1);
+ }
+ CODE_ADD(c, 0x3, 2);
+
+ /* Byte-align with zeros */
+ j = 8 - (CODE_LENGTH(c) % 8);
+ if (j != 8)
+ CODE_ADD(c, 0, j);
+ }
+
+ i = CODE_LENGTH(c) + 4 * 8;
+ buf[2] = 0x00;
+ buf[3] = 0x00;
+ buf[4] = 0x01;
+ buf[5] = 0x00;
+ return i;
+}
+
+static int mpeg1_sequence_header(struct go7007 *go, unsigned char *buf, int ext)
+{
+ int i, aspect_ratio, picture_rate;
+ CODE_GEN(c, buf + 6);
+
+ if (go->format == V4L2_PIX_FMT_MPEG1) {
+ switch (go->aspect_ratio) {
+ case GO7007_RATIO_4_3:
+ aspect_ratio = go->standard == GO7007_STD_NTSC ? 3 : 2;
+ break;
+ case GO7007_RATIO_16_9:
+ aspect_ratio = go->standard == GO7007_STD_NTSC ? 5 : 4;
+ break;
+ default:
+ aspect_ratio = 1;
+ break;
+ }
+ } else {
+ switch (go->aspect_ratio) {
+ case GO7007_RATIO_4_3:
+ aspect_ratio = 2;
+ break;
+ case GO7007_RATIO_16_9:
+ aspect_ratio = 3;
+ break;
+ default:
+ aspect_ratio = 1;
+ break;
+ }
+ }
+ switch (go->sensor_framerate) {
+ case 24000:
+ picture_rate = 1;
+ break;
+ case 24024:
+ picture_rate = 2;
+ break;
+ case 25025:
+ picture_rate = go->interlace_coding ? 6 : 3;
+ break;
+ case 30000:
+ picture_rate = go->interlace_coding ? 7 : 4;
+ break;
+ case 30030:
+ picture_rate = go->interlace_coding ? 8 : 5;
+ break;
+ default:
+ picture_rate = 5; /* 30 fps seems like a reasonable default */
+ break;
+ }
+
+ CODE_ADD(c, go->width, 12);
+ CODE_ADD(c, go->height, 12);
+ CODE_ADD(c, aspect_ratio, 4);
+ CODE_ADD(c, picture_rate, 4);
+ CODE_ADD(c, go->format == V4L2_PIX_FMT_MPEG2 ? 20000 : 0x3ffff, 18);
+ CODE_ADD(c, 1, 1);
+ CODE_ADD(c, go->format == V4L2_PIX_FMT_MPEG2 ? 112 : 20, 10);
+ CODE_ADD(c, 0, 3);
+
+ /* Byte-align with zeros */
+ i = 8 - (CODE_LENGTH(c) % 8);
+ if (i != 8)
+ CODE_ADD(c, 0, i);
+
+ if (go->format == V4L2_PIX_FMT_MPEG2) {
+ CODE_ADD(c, 0x1, 24);
+ CODE_ADD(c, 0xb5, 8);
+ CODE_ADD(c, 0x148, 12);
+ if (go->interlace_coding)
+ CODE_ADD(c, 0x20001, 20);
+ else
+ CODE_ADD(c, 0xa0001, 20);
+ CODE_ADD(c, 0, 16);
+
+ /* Byte-align with zeros */
+ i = 8 - (CODE_LENGTH(c) % 8);
+ if (i != 8)
+ CODE_ADD(c, 0, i);
+
+ if (ext) {
+ CODE_ADD(c, 0x1, 24);
+ CODE_ADD(c, 0xb52, 12);
+ CODE_ADD(c, go->standard == GO7007_STD_NTSC ? 2 : 1, 3);
+ CODE_ADD(c, 0x105, 9);
+ CODE_ADD(c, 0x505, 16);
+ CODE_ADD(c, go->width, 14);
+ CODE_ADD(c, 1, 1);
+ CODE_ADD(c, go->height, 14);
+
+ /* Byte-align with zeros */
+ i = 8 - (CODE_LENGTH(c) % 8);
+ if (i != 8)
+ CODE_ADD(c, 0, i);
+ }
+ }
+
+ i = CODE_LENGTH(c) + 4 * 8;
+ buf[0] = i & 0xff;
+ buf[1] = i >> 8;
+ buf[2] = 0x00;
+ buf[3] = 0x00;
+ buf[4] = 0x01;
+ buf[5] = 0xb3;
+ return i;
+}
+
+static int gen_mpeg1hdr_to_package(struct go7007 *go,
+ __le16 *code, int space, int *framelen)
+{
+ u8 *buf;
+ u16 mem = 0x3e00;
+ unsigned int addr = 0x19;
+ int i, off = 0, chunk;
+
+ buf = kzalloc(5120, GFP_KERNEL);
+ if (buf == NULL)
+ return -1;
+
+ framelen[0] = mpeg1_frame_header(go, buf, 0, 1, PFRAME);
+ if (go->interlace_coding)
+ framelen[0] += mpeg1_frame_header(go, buf + framelen[0] / 8,
+ 0, 2, PFRAME);
+ buf[0] = framelen[0] & 0xff;
+ buf[1] = framelen[0] >> 8;
+ i = 368;
+ framelen[1] = mpeg1_frame_header(go, buf + i, 0, 1, BFRAME_PRE);
+ if (go->interlace_coding)
+ framelen[1] += mpeg1_frame_header(go, buf + i + framelen[1] / 8,
+ 0, 2, BFRAME_PRE);
+ buf[i] = framelen[1] & 0xff;
+ buf[i + 1] = framelen[1] >> 8;
+ i += 1632;
+ framelen[2] = mpeg1_frame_header(go, buf + i, 0, 1, BFRAME_POST);
+ if (go->interlace_coding)
+ framelen[2] += mpeg1_frame_header(go, buf + i + framelen[2] / 8,
+ 0, 2, BFRAME_POST);
+ buf[i] = framelen[2] & 0xff;
+ buf[i + 1] = framelen[2] >> 8;
+ i += 1432;
+ framelen[3] = mpeg1_frame_header(go, buf + i, 0, 1, BFRAME_BIDIR);
+ if (go->interlace_coding)
+ framelen[3] += mpeg1_frame_header(go, buf + i + framelen[3] / 8,
+ 0, 2, BFRAME_BIDIR);
+ buf[i] = framelen[3] & 0xff;
+ buf[i + 1] = framelen[3] >> 8;
+ i += 1632 + 16;
+ mpeg1_sequence_header(go, buf + i, 0);
+ i += 40;
+ for (i = 0; i < 5120; i += chunk * 2) {
+ if (space - off < 32) {
+ off = -1;
+ goto done;
+ }
+
+ code[off + 1] = __cpu_to_le16(0x8000 | mem);
+
+ chunk = 28;
+ if (mem + chunk > 0x4000)
+ chunk = 0x4000 - mem;
+ if (i + 2 * chunk > 5120)
+ chunk = (5120 - i) / 2;
+
+ if (chunk < 28) {
+ code[off] = __cpu_to_le16(0x4000 | chunk);
+ code[off + 31] = __cpu_to_le16(addr);
+ if (mem + chunk == 0x4000) {
+ mem = 0x3e00;
+ ++addr;
+ }
+ } else {
+ code[off] = __cpu_to_le16(0x1000 | 28);
+ code[off + 31] = 0;
+ mem += 28;
+ }
+
+ memcpy(&code[off + 2], buf + i, chunk * 2);
+ off += 32;
+ }
+done:
+ kfree(buf);
+ return off;
+}
+
+static int vti_bitlen(struct go7007 *go)
+{
+ unsigned int i, max_time_incr = go->sensor_framerate / go->fps_scale;
+
+ for (i = 31; (max_time_incr & ((1 << i) - 1)) == max_time_incr; --i)
+ ;
+ return i + 1;
+}
+
+static int mpeg4_frame_header(struct go7007 *go, unsigned char *buf,
+ int modulo, enum mpeg_frame_type frame)
+{
+ int i;
+ CODE_GEN(c, buf + 6);
+ int mb_count = (go->width >> 4) * (go->height >> 4);
+
+ CODE_ADD(c, frame == PFRAME ? 0x1 : 0x2, 2);
+ if (modulo)
+ CODE_ADD(c, 0x1, 1);
+ CODE_ADD(c, 0x1, 2);
+ CODE_ADD(c, 0, vti_bitlen(go));
+ CODE_ADD(c, 0x3, 2);
+ if (frame == PFRAME)
+ CODE_ADD(c, 0, 1);
+ CODE_ADD(c, 0xc, 11);
+ if (frame != PFRAME)
+ CODE_ADD(c, 0x4, 3);
+ if (frame != BFRAME_EMPTY) {
+ for (i = 0; i < mb_count; ++i) {
+ switch (frame) {
+ case PFRAME:
+ CODE_ADD(c, 0x1, 1);
+ break;
+ case BFRAME_PRE:
+ CODE_ADD(c, 0x47, 8);
+ break;
+ case BFRAME_POST:
+ CODE_ADD(c, 0x27, 7);
+ break;
+ case BFRAME_BIDIR:
+ CODE_ADD(c, 0x5f, 8);
+ break;
+ case BFRAME_EMPTY: /* keep compiler quiet */
+ break;
+ }
+ }
+ }
+
+ /* Byte-align with a zero followed by ones */
+ i = 8 - (CODE_LENGTH(c) % 8);
+ CODE_ADD(c, 0, 1);
+ CODE_ADD(c, (1 << (i - 1)) - 1, i - 1);
+
+ i = CODE_LENGTH(c) + 4 * 8;
+ buf[0] = i & 0xff;
+ buf[1] = i >> 8;
+ buf[2] = 0x00;
+ buf[3] = 0x00;
+ buf[4] = 0x01;
+ buf[5] = 0xb6;
+ return i;
+}
+
+static int mpeg4_sequence_header(struct go7007 *go, unsigned char *buf, int ext)
+{
+ const unsigned char head[] = { 0x00, 0x00, 0x01, 0xb0, go->pali,
+ 0x00, 0x00, 0x01, 0xb5, 0x09,
+ 0x00, 0x00, 0x01, 0x00,
+ 0x00, 0x00, 0x01, 0x20, };
+ int i, aspect_ratio;
+ int fps = go->sensor_framerate / go->fps_scale;
+ CODE_GEN(c, buf + 2 + sizeof(head));
+
+ switch (go->aspect_ratio) {
+ case GO7007_RATIO_4_3:
+ aspect_ratio = go->standard == GO7007_STD_NTSC ? 3 : 2;
+ break;
+ case GO7007_RATIO_16_9:
+ aspect_ratio = go->standard == GO7007_STD_NTSC ? 5 : 4;
+ break;
+ default:
+ aspect_ratio = 1;
+ break;
+ }
+
+ memcpy(buf + 2, head, sizeof(head));
+ CODE_ADD(c, 0x191, 17);
+ CODE_ADD(c, aspect_ratio, 4);
+ CODE_ADD(c, 0x1, 4);
+ CODE_ADD(c, fps, 16);
+ CODE_ADD(c, 0x3, 2);
+ CODE_ADD(c, 1001, vti_bitlen(go));
+ CODE_ADD(c, 1, 1);
+ CODE_ADD(c, go->width, 13);
+ CODE_ADD(c, 1, 1);
+ CODE_ADD(c, go->height, 13);
+ CODE_ADD(c, 0x2830, 14);
+
+ /* Byte-align */
+ i = 8 - (CODE_LENGTH(c) % 8);
+ CODE_ADD(c, 0, 1);
+ CODE_ADD(c, (1 << (i - 1)) - 1, i - 1);
+
+ i = CODE_LENGTH(c) + sizeof(head) * 8;
+ buf[0] = i & 0xff;
+ buf[1] = i >> 8;
+ return i;
+}
+
+static int gen_mpeg4hdr_to_package(struct go7007 *go,
+ __le16 *code, int space, int *framelen)
+{
+ u8 *buf;
+ u16 mem = 0x3e00;
+ unsigned int addr = 0x19;
+ int i, off = 0, chunk;
+
+ buf = kzalloc(5120, GFP_KERNEL);
+ if (buf == NULL)
+ return -1;
+
+ framelen[0] = mpeg4_frame_header(go, buf, 0, PFRAME);
+ i = 368;
+ framelen[1] = mpeg4_frame_header(go, buf + i, 0, BFRAME_PRE);
+ i += 1632;
+ framelen[2] = mpeg4_frame_header(go, buf + i, 0, BFRAME_POST);
+ i += 1432;
+ framelen[3] = mpeg4_frame_header(go, buf + i, 0, BFRAME_BIDIR);
+ i += 1632;
+ mpeg4_frame_header(go, buf + i, 0, BFRAME_EMPTY);
+ i += 16;
+ mpeg4_sequence_header(go, buf + i, 0);
+ i += 40;
+ for (i = 0; i < 5120; i += chunk * 2) {
+ if (space - off < 32) {
+ off = -1;
+ goto done;
+ }
+
+ code[off + 1] = __cpu_to_le16(0x8000 | mem);
+
+ chunk = 28;
+ if (mem + chunk > 0x4000)
+ chunk = 0x4000 - mem;
+ if (i + 2 * chunk > 5120)
+ chunk = (5120 - i) / 2;
+
+ if (chunk < 28) {
+ code[off] = __cpu_to_le16(0x4000 | chunk);
+ code[off + 31] = __cpu_to_le16(addr);
+ if (mem + chunk == 0x4000) {
+ mem = 0x3e00;
+ ++addr;
+ }
+ } else {
+ code[off] = __cpu_to_le16(0x1000 | 28);
+ code[off + 31] = 0;
+ mem += 28;
+ }
+
+ memcpy(&code[off + 2], buf + i, chunk * 2);
+ off += 32;
+ }
+ mem = 0x3e00;
+ addr = go->ipb ? 0x14f9 : 0x0af9;
+ memset(buf, 0, 5120);
+ framelen[4] = mpeg4_frame_header(go, buf, 1, PFRAME);
+ i = 368;
+ framelen[5] = mpeg4_frame_header(go, buf + i, 1, BFRAME_PRE);
+ i += 1632;
+ framelen[6] = mpeg4_frame_header(go, buf + i, 1, BFRAME_POST);
+ i += 1432;
+ framelen[7] = mpeg4_frame_header(go, buf + i, 1, BFRAME_BIDIR);
+ i += 1632;
+ mpeg4_frame_header(go, buf + i, 1, BFRAME_EMPTY);
+ i += 16;
+ for (i = 0; i < 5120; i += chunk * 2) {
+ if (space - off < 32) {
+ off = -1;
+ goto done;
+ }
+
+ code[off + 1] = __cpu_to_le16(0x8000 | mem);
+
+ chunk = 28;
+ if (mem + chunk > 0x4000)
+ chunk = 0x4000 - mem;
+ if (i + 2 * chunk > 5120)
+ chunk = (5120 - i) / 2;
+
+ if (chunk < 28) {
+ code[off] = __cpu_to_le16(0x4000 | chunk);
+ code[off + 31] = __cpu_to_le16(addr);
+ if (mem + chunk == 0x4000) {
+ mem = 0x3e00;
+ ++addr;
+ }
+ } else {
+ code[off] = __cpu_to_le16(0x1000 | 28);
+ code[off + 31] = 0;
+ mem += 28;
+ }
+
+ memcpy(&code[off + 2], buf + i, chunk * 2);
+ off += 32;
+ }
+done:
+ kfree(buf);
+ return off;
+}
+
+static int brctrl_to_package(struct go7007 *go,
+ __le16 *code, int space, int *framelen)
+{
+ int converge_speed = 0;
+ int lambda = (go->format == V4L2_PIX_FMT_MJPEG || go->dvd_mode) ?
+ 100 : 0;
+ int peak_rate = 6 * go->bitrate / 5;
+ int vbv_buffer = go->format == V4L2_PIX_FMT_MJPEG ?
+ go->bitrate :
+ (go->dvd_mode ? 900000 : peak_rate);
+ int fps = go->sensor_framerate / go->fps_scale;
+ int q = 0;
+ /* Bizarre math below depends on rounding errors in division */
+ u32 sgop_expt_addr = go->bitrate / 32 * (go->ipb ? 3 : 1) * 1001 / fps;
+ u32 sgop_peak_addr = peak_rate / 32 * 1001 / fps;
+ u32 total_expt_addr = go->bitrate / 32 * 1000 / fps * (fps / 1000);
+ u32 vbv_alert_addr = vbv_buffer * 3 / (4 * 32);
+ u32 cplx[] = {
+ q > 0 ? sgop_expt_addr * q :
+ 2 * go->width * go->height * (go->ipb ? 6 : 4) / 32,
+ q > 0 ? sgop_expt_addr * q :
+ 2 * go->width * go->height * (go->ipb ? 6 : 4) / 32,
+ q > 0 ? sgop_expt_addr * q :
+ 2 * go->width * go->height * (go->ipb ? 6 : 4) / 32,
+ q > 0 ? sgop_expt_addr * q :
+ 2 * go->width * go->height * (go->ipb ? 6 : 4) / 32,
+ };
+ u32 calc_q = q > 0 ? q : cplx[0] / sgop_expt_addr;
+ u16 pack[] = {
+ 0x200e, 0x0000,
+ 0xBF20, go->ipb ? converge_speed_ipb[converge_speed]
+ : converge_speed_ip[converge_speed],
+ 0xBF21, go->ipb ? 2 : 0,
+ 0xBF22, go->ipb ? LAMBDA_table[0][lambda / 2 + 50]
+ : 32767,
+ 0xBF23, go->ipb ? LAMBDA_table[1][lambda] : 32767,
+ 0xBF24, 32767,
+ 0xBF25, lambda > 99 ? 32767 : LAMBDA_table[3][lambda],
+ 0xBF26, sgop_expt_addr & 0x0000FFFF,
+ 0xBF27, sgop_expt_addr >> 16,
+ 0xBF28, sgop_peak_addr & 0x0000FFFF,
+ 0xBF29, sgop_peak_addr >> 16,
+ 0xBF2A, vbv_alert_addr & 0x0000FFFF,
+ 0xBF2B, vbv_alert_addr >> 16,
+ 0xBF2C, 0,
+ 0xBF2D, 0,
+ 0, 0,
+
+ 0x200e, 0x0000,
+ 0xBF2E, vbv_alert_addr & 0x0000FFFF,
+ 0xBF2F, vbv_alert_addr >> 16,
+ 0xBF30, cplx[0] & 0x0000FFFF,
+ 0xBF31, cplx[0] >> 16,
+ 0xBF32, cplx[1] & 0x0000FFFF,
+ 0xBF33, cplx[1] >> 16,
+ 0xBF34, cplx[2] & 0x0000FFFF,
+ 0xBF35, cplx[2] >> 16,
+ 0xBF36, cplx[3] & 0x0000FFFF,
+ 0xBF37, cplx[3] >> 16,
+ 0xBF38, 0,
+ 0xBF39, 0,
+ 0xBF3A, total_expt_addr & 0x0000FFFF,
+ 0xBF3B, total_expt_addr >> 16,
+ 0, 0,
+
+ 0x200e, 0x0000,
+ 0xBF3C, total_expt_addr & 0x0000FFFF,
+ 0xBF3D, total_expt_addr >> 16,
+ 0xBF3E, 0,
+ 0xBF3F, 0,
+ 0xBF48, 0,
+ 0xBF49, 0,
+ 0xBF4A, calc_q < 4 ? 4 : (calc_q > 124 ? 124 : calc_q),
+ 0xBF4B, 4,
+ 0xBF4C, 0,
+ 0xBF4D, 0,
+ 0xBF4E, 0,
+ 0xBF4F, 0,
+ 0xBF50, 0,
+ 0xBF51, 0,
+ 0, 0,
+
+ 0x200e, 0x0000,
+ 0xBF40, sgop_expt_addr & 0x0000FFFF,
+ 0xBF41, sgop_expt_addr >> 16,
+ 0xBF42, 0,
+ 0xBF43, 0,
+ 0xBF44, 0,
+ 0xBF45, 0,
+ 0xBF46, (go->width >> 4) * (go->height >> 4),
+ 0xBF47, 0,
+ 0xBF64, 0,
+ 0xBF65, 0,
+ 0xBF18, framelen[4],
+ 0xBF19, framelen[5],
+ 0xBF1A, framelen[6],
+ 0xBF1B, framelen[7],
+ 0, 0,
+
+#if 0
+ /* Remove once we don't care about matching */
+ 0x200e, 0x0000,
+ 0xBF56, 4,
+ 0xBF57, 0,
+ 0xBF58, 5,
+ 0xBF59, 0,
+ 0xBF5A, 6,
+ 0xBF5B, 0,
+ 0xBF5C, 8,
+ 0xBF5D, 0,
+ 0xBF5E, 1,
+ 0xBF5F, 0,
+ 0xBF60, 1,
+ 0xBF61, 0,
+ 0xBF62, 0,
+ 0xBF63, 0,
+ 0, 0,
+#else
+ 0x2008, 0x0000,
+ 0xBF56, 4,
+ 0xBF57, 0,
+ 0xBF58, 5,
+ 0xBF59, 0,
+ 0xBF5A, 6,
+ 0xBF5B, 0,
+ 0xBF5C, 8,
+ 0xBF5D, 0,
+ 0, 0,
+ 0, 0,
+ 0, 0,
+ 0, 0,
+ 0, 0,
+ 0, 0,
+ 0, 0,
+#endif
+
+ 0x200e, 0x0000,
+ 0xBF10, 0,
+ 0xBF11, 0,
+ 0xBF12, 0,
+ 0xBF13, 0,
+ 0xBF14, 0,
+ 0xBF15, 0,
+ 0xBF16, 0,
+ 0xBF17, 0,
+ 0xBF7E, 0,
+ 0xBF7F, 1,
+ 0xBF52, framelen[0],
+ 0xBF53, framelen[1],
+ 0xBF54, framelen[2],
+ 0xBF55, framelen[3],
+ 0, 0,
+ };
+
+ return copy_packages(code, pack, 6, space);
+}
+
+static int config_package(struct go7007 *go, __le16 *code, int space)
+{
+ int fps = go->sensor_framerate / go->fps_scale / 1000;
+ int rows = go->interlace_coding ? go->height / 32 : go->height / 16;
+ int brc_window_size = fps;
+ int q_min = 2, q_max = 31;
+ int THACCoeffSet0 = 0;
+ u16 pack[] = {
+ 0x200e, 0x0000,
+ 0xc002, 0x14b4,
+ 0xc003, 0x28b4,
+ 0xc004, 0x3c5a,
+ 0xdc05, 0x2a77,
+ 0xc6c3, go->format == V4L2_PIX_FMT_MPEG4 ? 0 :
+ (go->format == V4L2_PIX_FMT_H263 ? 0 : 1),
+ 0xc680, go->format == V4L2_PIX_FMT_MPEG4 ? 0xf1 :
+ (go->format == V4L2_PIX_FMT_H263 ? 0x61 :
+ 0xd3),
+ 0xc780, 0x0140,
+ 0xe009, 0x0001,
+ 0xc60f, 0x0008,
+ 0xd4ff, 0x0002,
+ 0xe403, 2340,
+ 0xe406, 75,
+ 0xd411, 0x0001,
+ 0xd410, 0xa1d6,
+ 0x0001, 0x2801,
+
+ 0x200d, 0x0000,
+ 0xe402, 0x018b,
+ 0xe401, 0x8b01,
+ 0xd472, (go->board_info->sensor_flags &
+ GO7007_SENSOR_TV) &&
+ (!go->interlace_coding) ?
+ 0x01b0 : 0x0170,
+ 0xd475, (go->board_info->sensor_flags &
+ GO7007_SENSOR_TV) &&
+ (!go->interlace_coding) ?
+ 0x0008 : 0x0009,
+ 0xc404, go->interlace_coding ? 0x44 :
+ (go->format == V4L2_PIX_FMT_MPEG4 ? 0x11 :
+ (go->format == V4L2_PIX_FMT_MPEG1 ? 0x02 :
+ (go->format == V4L2_PIX_FMT_MPEG2 ? 0x04 :
+ (go->format == V4L2_PIX_FMT_H263 ? 0x08 :
+ 0x20)))),
+ 0xbf0a, (go->format == V4L2_PIX_FMT_MPEG4 ? 8 :
+ (go->format == V4L2_PIX_FMT_MPEG1 ? 1 :
+ (go->format == V4L2_PIX_FMT_MPEG2 ? 2 :
+ (go->format == V4L2_PIX_FMT_H263 ? 4 : 16)))) |
+ ((go->repeat_seqhead ? 1 : 0) << 6) |
+ ((go->dvd_mode ? 1 : 0) << 9) |
+ ((go->gop_header_enable ? 1 : 0) << 10),
+ 0xbf0b, 0,
+ 0xdd5a, go->ipb ? 0x14 : 0x0a,
+ 0xbf0c, 0,
+ 0xbf0d, 0,
+ 0xc683, THACCoeffSet0,
+ 0xc40a, (go->width << 4) | rows,
+ 0xe01a, go->board_info->hpi_buffer_cap,
+ 0, 0,
+ 0, 0,
+
+ 0x2008, 0,
+ 0xe402, 0x88,
+ 0xe401, 0x8f01,
+ 0xbf6a, 0,
+ 0xbf6b, 0,
+ 0xbf6c, 0,
+ 0xbf6d, 0,
+ 0xbf6e, 0,
+ 0xbf6f, 0,
+ 0, 0,
+ 0, 0,
+ 0, 0,
+ 0, 0,
+ 0, 0,
+ 0, 0,
+ 0, 0,
+
+ 0x200e, 0,
+ 0xbf66, brc_window_size,
+ 0xbf67, 0,
+ 0xbf68, q_min,
+ 0xbf69, q_max,
+ 0xbfe0, 0,
+ 0xbfe1, 0,
+ 0xbfe2, 0,
+ 0xbfe3, go->ipb ? 3 : 1,
+ 0xc031, go->board_info->sensor_flags &
+ GO7007_SENSOR_VBI ? 1 : 0,
+ 0xc01c, 0x1f,
+ 0xdd8c, 0x15,
+ 0xdd94, 0x15,
+ 0xdd88, go->ipb ? 0x1401 : 0x0a01,
+ 0xdd90, go->ipb ? 0x1401 : 0x0a01,
+ 0, 0,
+
+ 0x200e, 0,
+ 0xbfe4, 0,
+ 0xbfe5, 0,
+ 0xbfe6, 0,
+ 0xbfe7, fps << 8,
+ 0xbfe8, 0x3a00,
+ 0xbfe9, 0,
+ 0xbfea, 0,
+ 0xbfeb, 0,
+ 0xbfec, (go->interlace_coding ? 1 << 15 : 0) |
+ (go->modet_enable ? 0xa : 0) |
+ (go->board_info->sensor_flags &
+ GO7007_SENSOR_VBI ? 1 : 0),
+ 0xbfed, 0,
+ 0xbfee, 0,
+ 0xbfef, 0,
+ 0xbff0, go->board_info->sensor_flags &
+ GO7007_SENSOR_TV ? 0xf060 : 0xb060,
+ 0xbff1, 0,
+ 0, 0,
+ };
+
+ return copy_packages(code, pack, 5, space);
+}
+
+static int seqhead_to_package(struct go7007 *go, __le16 *code, int space,
+ int (*sequence_header_func)(struct go7007 *go,
+ unsigned char *buf, int ext))
+{
+ int vop_time_increment_bitlength = vti_bitlen(go);
+ int fps = go->sensor_framerate / go->fps_scale *
+ (go->interlace_coding ? 2 : 1);
+ unsigned char buf[40] = { };
+ int len = sequence_header_func(go, buf, 1);
+ u16 pack[] = {
+ 0x2006, 0,
+ 0xbf08, fps,
+ 0xbf09, 0,
+ 0xbff2, vop_time_increment_bitlength,
+ 0xbff3, (1 << vop_time_increment_bitlength) - 1,
+ 0xbfe6, 0,
+ 0xbfe7, (fps / 1000) << 8,
+ 0, 0,
+ 0, 0,
+ 0, 0,
+ 0, 0,
+ 0, 0,
+ 0, 0,
+ 0, 0,
+ 0, 0,
+ 0, 0,
+
+ 0x2007, 0,
+ 0xc800, buf[2] << 8 | buf[3],
+ 0xc801, buf[4] << 8 | buf[5],
+ 0xc802, buf[6] << 8 | buf[7],
+ 0xc803, buf[8] << 8 | buf[9],
+ 0xc406, 64,
+ 0xc407, len - 64,
+ 0xc61b, 1,
+ 0, 0,
+ 0, 0,
+ 0, 0,
+ 0, 0,
+ 0, 0,
+ 0, 0,
+ 0, 0,
+ 0, 0,
+
+ 0x200e, 0,
+ 0xc808, buf[10] << 8 | buf[11],
+ 0xc809, buf[12] << 8 | buf[13],
+ 0xc80a, buf[14] << 8 | buf[15],
+ 0xc80b, buf[16] << 8 | buf[17],
+ 0xc80c, buf[18] << 8 | buf[19],
+ 0xc80d, buf[20] << 8 | buf[21],
+ 0xc80e, buf[22] << 8 | buf[23],
+ 0xc80f, buf[24] << 8 | buf[25],
+ 0xc810, buf[26] << 8 | buf[27],
+ 0xc811, buf[28] << 8 | buf[29],
+ 0xc812, buf[30] << 8 | buf[31],
+ 0xc813, buf[32] << 8 | buf[33],
+ 0xc814, buf[34] << 8 | buf[35],
+ 0xc815, buf[36] << 8 | buf[37],
+ 0, 0,
+ 0, 0,
+ 0, 0,
+ };
+
+ return copy_packages(code, pack, 3, space);
+}
+
+static int relative_prime(int big, int little)
+{
+ int remainder;
+
+ while (little != 0) {
+ remainder = big % little;
+ big = little;
+ little = remainder;
+ }
+ return big;
+}
+
+static int avsync_to_package(struct go7007 *go, __le16 *code, int space)
+{
+ int arate = go->board_info->audio_rate * 1001 * go->fps_scale;
+ int ratio = arate / go->sensor_framerate;
+ int adjratio = ratio * 215 / 100;
+ int rprime = relative_prime(go->sensor_framerate,
+ arate % go->sensor_framerate);
+ int f1 = (arate % go->sensor_framerate) / rprime;
+ int f2 = (go->sensor_framerate - arate % go->sensor_framerate) / rprime;
+ u16 pack[] = {
+ 0x200e, 0,
+ 0xbf98, (u16)((-adjratio) & 0xffff),
+ 0xbf99, (u16)((-adjratio) >> 16),
+ 0xbf92, 0,
+ 0xbf93, 0,
+ 0xbff4, f1 > f2 ? f1 : f2,
+ 0xbff5, f1 < f2 ? f1 : f2,
+ 0xbff6, f1 < f2 ? ratio : ratio + 1,
+ 0xbff7, f1 > f2 ? ratio : ratio + 1,
+ 0xbff8, 0,
+ 0xbff9, 0,
+ 0xbffa, adjratio & 0xffff,
+ 0xbffb, adjratio >> 16,
+ 0xbf94, 0,
+ 0xbf95, 0,
+ 0, 0,
+ };
+
+ return copy_packages(code, pack, 1, space);
+}
+
+static int final_package(struct go7007 *go, __le16 *code, int space)
+{
+ int rows = go->interlace_coding ? go->height / 32 : go->height / 16;
+ u16 pack[] = {
+ 0x8000,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 2,
+ ((go->board_info->sensor_flags & GO7007_SENSOR_TV) &&
+ (!go->interlace_coding) ?
+ (1 << 14) | (1 << 9) : 0) |
+ ((go->encoder_subsample ? 1 : 0) << 8) |
+ (go->board_info->sensor_flags &
+ GO7007_SENSOR_CONFIG_MASK),
+ ((go->encoder_v_halve ? 1 : 0) << 14) |
+ (go->encoder_v_halve ? rows << 9 : rows << 8) |
+ (go->encoder_h_halve ? 1 << 6 : 0) |
+ (go->encoder_h_halve ? go->width >> 3 : go->width >> 4),
+ (1 << 15) | (go->encoder_v_offset << 6) |
+ (1 << 7) | (go->encoder_h_offset >> 2),
+ (1 << 6),
+ 0,
+ 0,
+ ((go->fps_scale - 1) << 8) |
+ (go->board_info->sensor_flags & GO7007_SENSOR_TV ?
+ (1 << 7) : 0) |
+ 0x41,
+ go->ipb ? 0xd4c : 0x36b,
+ (rows << 8) | (go->width >> 4),
+ go->format == V4L2_PIX_FMT_MPEG4 ? 0x0404 : 0,
+ (1 << 15) | ((go->interlace_coding ? 1 : 0) << 13) |
+ ((go->closed_gop ? 1 : 0) << 12) |
+ ((go->format == V4L2_PIX_FMT_MPEG4 ? 1 : 0) << 11) |
+ /* (1 << 9) | */
+ ((go->ipb ? 3 : 0) << 7) |
+ ((go->modet_enable ? 1 : 0) << 2) |
+ ((go->dvd_mode ? 1 : 0) << 1) | 1,
+ (go->format == V4L2_PIX_FMT_MPEG1 ? 0x89a0 :
+ (go->format == V4L2_PIX_FMT_MPEG2 ? 0x89a0 :
+ (go->format == V4L2_PIX_FMT_MJPEG ? 0x89a0 :
+ (go->format == V4L2_PIX_FMT_MPEG4 ? 0x8920 :
+ (go->format == V4L2_PIX_FMT_H263 ? 0x8920 : 0))))),
+ go->ipb ? 0x1f15 : 0x1f0b,
+ go->ipb ? 0x0015 : 0x000b,
+ go->ipb ? 0xa800 : 0x5800,
+ 0xffff,
+ 0x0020 + 0x034b * 0,
+ 0x0020 + 0x034b * 1,
+ 0x0020 + 0x034b * 2,
+ 0x0020 + 0x034b * 3,
+ 0x0020 + 0x034b * 4,
+ 0x0020 + 0x034b * 5,
+ go->ipb ? (go->gop_size / 3) : go->gop_size,
+ (go->height >> 4) * (go->width >> 4) * 110 / 100,
+ };
+
+ return copy_packages(code, pack, 1, space);
+}
+
+static int audio_to_package(struct go7007 *go, __le16 *code, int space)
+{
+ int clock_config = ((go->board_info->audio_flags &
+ GO7007_AUDIO_I2S_MASTER ? 1 : 0) << 11) |
+ ((go->board_info->audio_flags &
+ GO7007_AUDIO_OKI_MODE ? 1 : 0) << 8) |
+ (((go->board_info->audio_bclk_div / 4) - 1) << 4) |
+ (go->board_info->audio_main_div - 1);
+ u16 pack[] = {
+ 0x200d, 0,
+ 0x9002, 0,
+ 0x9002, 0,
+ 0x9031, 0,
+ 0x9032, 0,
+ 0x9033, 0,
+ 0x9034, 0,
+ 0x9035, 0,
+ 0x9036, 0,
+ 0x9037, 0,
+ 0x9040, 0,
+ 0x9000, clock_config,
+ 0x9001, (go->board_info->audio_flags & 0xffff) |
+ (1 << 9),
+ 0x9000, ((go->board_info->audio_flags &
+ GO7007_AUDIO_I2S_MASTER ?
+ 1 : 0) << 10) |
+ clock_config,
+ 0, 0,
+ 0, 0,
+ 0x2005, 0,
+ 0x9041, 0,
+ 0x9042, 256,
+ 0x9043, 0,
+ 0x9044, 16,
+ 0x9045, 16,
+ 0, 0,
+ 0, 0,
+ 0, 0,
+ 0, 0,
+ 0, 0,
+ 0, 0,
+ 0, 0,
+ 0, 0,
+ 0, 0,
+ 0, 0,
+ };
+
+ return copy_packages(code, pack, 2, space);
+}
+
+static int modet_to_package(struct go7007 *go, __le16 *code, int space)
+{
+ int ret, mb, i, addr, cnt = 0;
+ u16 pack[32];
+ u16 thresholds[] = {
+ 0x200e, 0,
+ 0xbf82, go->modet[0].pixel_threshold,
+ 0xbf83, go->modet[1].pixel_threshold,
+ 0xbf84, go->modet[2].pixel_threshold,
+ 0xbf85, go->modet[3].pixel_threshold,
+ 0xbf86, go->modet[0].motion_threshold,
+ 0xbf87, go->modet[1].motion_threshold,
+ 0xbf88, go->modet[2].motion_threshold,
+ 0xbf89, go->modet[3].motion_threshold,
+ 0xbf8a, go->modet[0].mb_threshold,
+ 0xbf8b, go->modet[1].mb_threshold,
+ 0xbf8c, go->modet[2].mb_threshold,
+ 0xbf8d, go->modet[3].mb_threshold,
+ 0xbf8e, 0,
+ 0xbf8f, 0,
+ 0, 0,
+ };
+
+ ret = copy_packages(code, thresholds, 1, space);
+ if (ret < 0)
+ return -1;
+ cnt += ret;
+
+ addr = 0xbac0;
+ memset(pack, 0, 64);
+ i = 0;
+ for (mb = 0; mb < 1624; ++mb) {
+ pack[i * 2 + 3] <<= 2;
+ pack[i * 2 + 3] |= go->modet_map[mb];
+ if (mb % 8 != 7)
+ continue;
+ pack[i * 2 + 2] = addr++;
+ ++i;
+ if (i == 10 || mb == 1623) {
+ pack[0] = 0x2000 | i;
+ ret = copy_packages(code + cnt, pack, 1, space - cnt);
+ if (ret < 0)
+ return -1;
+ cnt += ret;
+ i = 0;
+ memset(pack, 0, 64);
+ }
+ pack[i * 2 + 3] = 0;
+ }
+
+ memset(pack, 0, 64);
+ i = 0;
+ for (addr = 0xbb90; addr < 0xbbfa; ++addr) {
+ pack[i * 2 + 2] = addr;
+ pack[i * 2 + 3] = 0;
+ ++i;
+ if (i == 10 || addr == 0xbbf9) {
+ pack[0] = 0x2000 | i;
+ ret = copy_packages(code + cnt, pack, 1, space - cnt);
+ if (ret < 0)
+ return -1;
+ cnt += ret;
+ i = 0;
+ memset(pack, 0, 64);
+ }
+ }
+ return cnt;
+}
+
+static int do_special(struct go7007 *go, u16 type, __le16 *code, int space,
+ int *framelen)
+{
+ switch (type) {
+ case SPECIAL_FRM_HEAD:
+ switch (go->format) {
+ case V4L2_PIX_FMT_MJPEG:
+ return gen_mjpeghdr_to_package(go, code, space);
+ case V4L2_PIX_FMT_MPEG1:
+ case V4L2_PIX_FMT_MPEG2:
+ return gen_mpeg1hdr_to_package(go, code, space,
+ framelen);
+ case V4L2_PIX_FMT_MPEG4:
+ return gen_mpeg4hdr_to_package(go, code, space,
+ framelen);
+ }
+ case SPECIAL_BRC_CTRL:
+ return brctrl_to_package(go, code, space, framelen);
+ case SPECIAL_CONFIG:
+ return config_package(go, code, space);
+ case SPECIAL_SEQHEAD:
+ switch (go->format) {
+ case V4L2_PIX_FMT_MPEG1:
+ case V4L2_PIX_FMT_MPEG2:
+ return seqhead_to_package(go, code, space,
+ mpeg1_sequence_header);
+ case V4L2_PIX_FMT_MPEG4:
+ return seqhead_to_package(go, code, space,
+ mpeg4_sequence_header);
+ default:
+ return 0;
+ }
+ case SPECIAL_AV_SYNC:
+ return avsync_to_package(go, code, space);
+ case SPECIAL_FINAL:
+ return final_package(go, code, space);
+ case SPECIAL_AUDIO:
+ return audio_to_package(go, code, space);
+ case SPECIAL_MODET:
+ return modet_to_package(go, code, space);
+ }
+ dev_err(go->dev,
+ "firmware file contains unsupported feature %04x\n", type);
+ return -1;
+}
+
+int go7007_construct_fw_image(struct go7007 *go, u8 **fw, int *fwlen)
+{
+ const struct firmware *fw_entry;
+ __le16 *code, *src;
+ int framelen[8] = { }; /* holds the lengths of empty frame templates */
+ int codespace = 64 * 1024, i = 0, srclen, chunk_len, chunk_flags;
+ int mode_flag;
+ int ret;
+
+ switch (go->format) {
+ case V4L2_PIX_FMT_MJPEG:
+ mode_flag = FLAG_MODE_MJPEG;
+ break;
+ case V4L2_PIX_FMT_MPEG1:
+ mode_flag = FLAG_MODE_MPEG1;
+ break;
+ case V4L2_PIX_FMT_MPEG2:
+ mode_flag = FLAG_MODE_MPEG2;
+ break;
+ case V4L2_PIX_FMT_MPEG4:
+ mode_flag = FLAG_MODE_MPEG4;
+ break;
+ default:
+ return -1;
+ }
+ if (request_firmware(&fw_entry, GO7007_FW_NAME, go->dev)) {
+ dev_err(go->dev,
+ "unable to load firmware from file \"%s\"\n",
+ GO7007_FW_NAME);
+ return -1;
+ }
+ code = kzalloc(codespace * 2, GFP_KERNEL);
+ if (code == NULL)
+ goto fw_failed;
+
+ src = (__le16 *)fw_entry->data;
+ srclen = fw_entry->size / 2;
+ while (srclen >= 2) {
+ chunk_flags = __le16_to_cpu(src[0]);
+ chunk_len = __le16_to_cpu(src[1]);
+ if (chunk_len + 2 > srclen) {
+ dev_err(go->dev,
+ "firmware file \"%s\" appears to be corrupted\n",
+ GO7007_FW_NAME);
+ goto fw_failed;
+ }
+ if (chunk_flags & mode_flag) {
+ if (chunk_flags & FLAG_SPECIAL) {
+ ret = do_special(go, __le16_to_cpu(src[2]),
+ &code[i], codespace - i, framelen);
+ if (ret < 0) {
+ dev_err(go->dev,
+ "insufficient memory for firmware construction\n");
+ goto fw_failed;
+ }
+ i += ret;
+ } else {
+ if (codespace - i < chunk_len) {
+ dev_err(go->dev,
+ "insufficient memory for firmware construction\n");
+ goto fw_failed;
+ }
+ memcpy(&code[i], &src[2], chunk_len * 2);
+ i += chunk_len;
+ }
+ }
+ srclen -= chunk_len + 2;
+ src += chunk_len + 2;
+ }
+ release_firmware(fw_entry);
+ *fw = (u8 *)code;
+ *fwlen = i * 2;
+ return 0;
+
+fw_failed:
+ kfree(code);
+ release_firmware(fw_entry);
+ return -1;
+}
+
+MODULE_FIRMWARE(GO7007_FW_NAME);
diff --git a/drivers/staging/media/go7007/go7007-i2c.c b/drivers/staging/media/go7007/go7007-i2c.c
new file mode 100644
index 00000000000..4cf4c0d6508
--- /dev/null
+++ b/drivers/staging/media/go7007/go7007-i2c.c
@@ -0,0 +1,222 @@
+/*
+ * Copyright (C) 2005-2006 Micronas USA Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License (Version 2) as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA.
+ */
+
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/sched.h>
+#include <linux/list.h>
+#include <linux/unistd.h>
+#include <linux/time.h>
+#include <linux/device.h>
+#include <linux/i2c.h>
+#include <linux/mutex.h>
+#include <linux/uaccess.h>
+
+#include "go7007-priv.h"
+
+/********************* Driver for on-board I2C adapter *********************/
+
+/* #define GO7007_I2C_DEBUG */
+
+#define SPI_I2C_ADDR_BASE 0x1400
+#define STATUS_REG_ADDR (SPI_I2C_ADDR_BASE + 0x2)
+#define I2C_CTRL_REG_ADDR (SPI_I2C_ADDR_BASE + 0x6)
+#define I2C_DEV_UP_ADDR_REG_ADDR (SPI_I2C_ADDR_BASE + 0x7)
+#define I2C_LO_ADDR_REG_ADDR (SPI_I2C_ADDR_BASE + 0x8)
+#define I2C_DATA_REG_ADDR (SPI_I2C_ADDR_BASE + 0x9)
+#define I2C_CLKFREQ_REG_ADDR (SPI_I2C_ADDR_BASE + 0xa)
+
+#define I2C_STATE_MASK 0x0007
+#define I2C_READ_READY_MASK 0x0008
+
+/* There is only one I2C port on the TW2804 that feeds all four GO7007 VIPs
+ * on the Adlink PCI-MPG24, so access is shared between all of them. */
+static DEFINE_MUTEX(adlink_mpg24_i2c_lock);
+
+static int go7007_i2c_xfer(struct go7007 *go, u16 addr, int read,
+ u16 command, int flags, u8 *data)
+{
+ int i, ret = -EIO;
+ u16 val;
+
+ if (go->status == STATUS_SHUTDOWN)
+ return -ENODEV;
+
+#ifdef GO7007_I2C_DEBUG
+ if (read)
+ dev_dbg(go->dev, "go7007-i2c: reading 0x%02x on 0x%02x\n",
+ command, addr);
+ else
+ dev_dbg(go->dev,
+ "go7007-i2c: writing 0x%02x to 0x%02x on 0x%02x\n",
+ *data, command, addr);
+#endif
+
+ mutex_lock(&go->hw_lock);
+
+ if (go->board_id == GO7007_BOARDID_ADLINK_MPG24) {
+ /* Bridge the I2C port on this GO7007 to the shared bus */
+ mutex_lock(&adlink_mpg24_i2c_lock);
+ go7007_write_addr(go, 0x3c82, 0x0020);
+ }
+
+ /* Wait for I2C adapter to be ready */
+ for (i = 0; i < 10; ++i) {
+ if (go7007_read_addr(go, STATUS_REG_ADDR, &val) < 0)
+ goto i2c_done;
+ if (!(val & I2C_STATE_MASK))
+ break;
+ msleep(100);
+ }
+ if (i == 10) {
+ dev_err(go->dev, "go7007-i2c: I2C adapter is hung\n");
+ goto i2c_done;
+ }
+
+ /* Set target register (command) */
+ go7007_write_addr(go, I2C_CTRL_REG_ADDR, flags);
+ go7007_write_addr(go, I2C_LO_ADDR_REG_ADDR, command);
+
+ /* If we're writing, send the data and target address and we're done */
+ if (!read) {
+ go7007_write_addr(go, I2C_DATA_REG_ADDR, *data);
+ go7007_write_addr(go, I2C_DEV_UP_ADDR_REG_ADDR,
+ (addr << 9) | (command >> 8));
+ ret = 0;
+ goto i2c_done;
+ }
+
+ /* Otherwise, we're reading. First clear i2c_rx_data_rdy. */
+ if (go7007_read_addr(go, I2C_DATA_REG_ADDR, &val) < 0)
+ goto i2c_done;
+
+ /* Send the target address plus read flag */
+ go7007_write_addr(go, I2C_DEV_UP_ADDR_REG_ADDR,
+ (addr << 9) | 0x0100 | (command >> 8));
+
+ /* Wait for i2c_rx_data_rdy */
+ for (i = 0; i < 10; ++i) {
+ if (go7007_read_addr(go, STATUS_REG_ADDR, &val) < 0)
+ goto i2c_done;
+ if (val & I2C_READ_READY_MASK)
+ break;
+ msleep(100);
+ }
+ if (i == 10) {
+ dev_err(go->dev, "go7007-i2c: I2C adapter is hung\n");
+ goto i2c_done;
+ }
+
+ /* Retrieve the read byte */
+ if (go7007_read_addr(go, I2C_DATA_REG_ADDR, &val) < 0)
+ goto i2c_done;
+ *data = val;
+ ret = 0;
+
+i2c_done:
+ if (go->board_id == GO7007_BOARDID_ADLINK_MPG24) {
+ /* Isolate the I2C port on this GO7007 from the shared bus */
+ go7007_write_addr(go, 0x3c82, 0x0000);
+ mutex_unlock(&adlink_mpg24_i2c_lock);
+ }
+ mutex_unlock(&go->hw_lock);
+ return ret;
+}
+
+static int go7007_smbus_xfer(struct i2c_adapter *adapter, u16 addr,
+ unsigned short flags, char read_write,
+ u8 command, int size, union i2c_smbus_data *data)
+{
+ struct go7007 *go = i2c_get_adapdata(adapter);
+
+ if (size != I2C_SMBUS_BYTE_DATA)
+ return -EIO;
+ return go7007_i2c_xfer(go, addr, read_write == I2C_SMBUS_READ, command,
+ flags & I2C_CLIENT_SCCB ? 0x10 : 0x00, &data->byte);
+}
+
+/* VERY LIMITED I2C master xfer function -- only needed because the
+ * SMBus functions only support 8-bit commands and the SAA7135 uses
+ * 16-bit commands. The I2C interface on the GO7007, as limited as
+ * it is, does support this mode. */
+
+static int go7007_i2c_master_xfer(struct i2c_adapter *adapter,
+ struct i2c_msg msgs[], int num)
+{
+ struct go7007 *go = i2c_get_adapdata(adapter);
+ int i;
+
+ for (i = 0; i < num; ++i) {
+ /* We can only do two things here -- write three bytes, or
+ * write two bytes and read one byte. */
+ if (msgs[i].len == 2) {
+ if (i + 1 == num || msgs[i].addr != msgs[i + 1].addr ||
+ (msgs[i].flags & I2C_M_RD) ||
+ !(msgs[i + 1].flags & I2C_M_RD) ||
+ msgs[i + 1].len != 1)
+ return -EIO;
+ if (go7007_i2c_xfer(go, msgs[i].addr, 1,
+ (msgs[i].buf[0] << 8) | msgs[i].buf[1],
+ 0x01, &msgs[i + 1].buf[0]) < 0)
+ return -EIO;
+ ++i;
+ } else if (msgs[i].len == 3) {
+ if (msgs[i].flags & I2C_M_RD)
+ return -EIO;
+ if (msgs[i].len != 3)
+ return -EIO;
+ if (go7007_i2c_xfer(go, msgs[i].addr, 0,
+ (msgs[i].buf[0] << 8) | msgs[i].buf[1],
+ 0x01, &msgs[i].buf[2]) < 0)
+ return -EIO;
+ } else
+ return -EIO;
+ }
+
+ return num;
+}
+
+static u32 go7007_functionality(struct i2c_adapter *adapter)
+{
+ return I2C_FUNC_SMBUS_BYTE_DATA;
+}
+
+static struct i2c_algorithm go7007_algo = {
+ .smbus_xfer = go7007_smbus_xfer,
+ .master_xfer = go7007_i2c_master_xfer,
+ .functionality = go7007_functionality,
+};
+
+static struct i2c_adapter go7007_adap_templ = {
+ .owner = THIS_MODULE,
+ .name = "WIS GO7007SB",
+ .algo = &go7007_algo,
+};
+
+int go7007_i2c_init(struct go7007 *go)
+{
+ memcpy(&go->i2c_adapter, &go7007_adap_templ,
+ sizeof(go7007_adap_templ));
+ go->i2c_adapter.dev.parent = go->dev;
+ i2c_set_adapdata(&go->i2c_adapter, go);
+ if (i2c_add_adapter(&go->i2c_adapter) < 0) {
+ dev_err(go->dev,
+ "go7007-i2c: error: i2c_add_adapter failed\n");
+ return -1;
+ }
+ return 0;
+}
diff --git a/drivers/staging/media/go7007/go7007-loader.c b/drivers/staging/media/go7007/go7007-loader.c
new file mode 100644
index 00000000000..491d0e697f5
--- /dev/null
+++ b/drivers/staging/media/go7007/go7007-loader.c
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2008 Sensoray Company Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License (Version 2) as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA.
+ */
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/usb.h>
+#include <linux/firmware.h>
+#include <cypress_firmware.h>
+
+struct fw_config {
+ u16 vendor;
+ u16 product;
+ const char * const fw_name1;
+ const char * const fw_name2;
+};
+
+static struct fw_config fw_configs[] = {
+ { 0x1943, 0xa250, "go7007/s2250-1.fw", "go7007/s2250-2.fw" },
+ { 0x093b, 0xa002, "go7007/px-m402u.fw", NULL },
+ { 0x093b, 0xa004, "go7007/px-tv402u.fw", NULL },
+ { 0x0eb1, 0x6666, "go7007/lr192.fw", NULL },
+ { 0x0eb1, 0x6668, "go7007/wis-startrek.fw", NULL },
+ { 0, 0, NULL, NULL }
+};
+MODULE_FIRMWARE("go7007/s2250-1.fw");
+MODULE_FIRMWARE("go7007/s2250-2.fw");
+MODULE_FIRMWARE("go7007/px-m402u.fw");
+MODULE_FIRMWARE("go7007/px-tv402u.fw");
+MODULE_FIRMWARE("go7007/lr192.fw");
+MODULE_FIRMWARE("go7007/wis-startrek.fw");
+
+static int go7007_loader_probe(struct usb_interface *interface,
+ const struct usb_device_id *id)
+{
+ struct usb_device *usbdev;
+ const struct firmware *fw;
+ u16 vendor, product;
+ const char *fw1, *fw2;
+ int ret;
+ int i;
+
+ usbdev = usb_get_dev(interface_to_usbdev(interface));
+ if (!usbdev)
+ goto failed2;
+
+ if (usbdev->descriptor.bNumConfigurations != 1) {
+ dev_err(&interface->dev, "can't handle multiple config\n");
+ goto failed2;
+ }
+
+ vendor = le16_to_cpu(usbdev->descriptor.idVendor);
+ product = le16_to_cpu(usbdev->descriptor.idProduct);
+
+ for (i = 0; fw_configs[i].fw_name1; i++)
+ if (fw_configs[i].vendor == vendor &&
+ fw_configs[i].product == product)
+ break;
+
+ /* Should never happen */
+ if (fw_configs[i].fw_name1 == NULL)
+ goto failed2;
+
+ fw1 = fw_configs[i].fw_name1;
+ fw2 = fw_configs[i].fw_name2;
+
+ dev_info(&interface->dev, "loading firmware %s\n", fw1);
+
+ if (request_firmware(&fw, fw1, &usbdev->dev)) {
+ dev_err(&interface->dev,
+ "unable to load firmware from file \"%s\"\n", fw1);
+ goto failed2;
+ }
+ ret = cypress_load_firmware(usbdev, fw, CYPRESS_FX2);
+ release_firmware(fw);
+ if (0 != ret) {
+ dev_err(&interface->dev, "loader download failed\n");
+ goto failed2;
+ }
+
+ if (fw2 == NULL)
+ return 0;
+
+ if (request_firmware(&fw, fw2, &usbdev->dev)) {
+ dev_err(&interface->dev,
+ "unable to load firmware from file \"%s\"\n", fw2);
+ goto failed2;
+ }
+ ret = cypress_load_firmware(usbdev, fw, CYPRESS_FX2);
+ release_firmware(fw);
+ if (0 != ret) {
+ dev_err(&interface->dev, "firmware download failed\n");
+ goto failed2;
+ }
+ return 0;
+
+failed2:
+ usb_put_dev(usbdev);
+ dev_err(&interface->dev, "probe failed\n");
+ return -ENODEV;
+}
+
+static void go7007_loader_disconnect(struct usb_interface *interface)
+{
+ dev_info(&interface->dev, "disconnect\n");
+ usb_put_dev(interface_to_usbdev(interface));
+ usb_set_intfdata(interface, NULL);
+}
+
+static const struct usb_device_id go7007_loader_ids[] = {
+ { USB_DEVICE(0x1943, 0xa250) },
+ { USB_DEVICE(0x093b, 0xa002) },
+ { USB_DEVICE(0x093b, 0xa004) },
+ { USB_DEVICE(0x0eb1, 0x6666) },
+ { USB_DEVICE(0x0eb1, 0x6668) },
+ {} /* Terminating entry */
+};
+
+MODULE_DEVICE_TABLE(usb, go7007_loader_ids);
+
+static struct usb_driver go7007_loader_driver = {
+ .name = "go7007-loader",
+ .probe = go7007_loader_probe,
+ .disconnect = go7007_loader_disconnect,
+ .id_table = go7007_loader_ids,
+};
+
+module_usb_driver(go7007_loader_driver);
+
+MODULE_AUTHOR("");
+MODULE_DESCRIPTION("firmware loader for go7007-usb");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/staging/media/go7007/go7007-priv.h b/drivers/staging/media/go7007/go7007-priv.h
new file mode 100644
index 00000000000..6e16af72050
--- /dev/null
+++ b/drivers/staging/media/go7007/go7007-priv.h
@@ -0,0 +1,294 @@
+/*
+ * Copyright (C) 2005-2006 Micronas USA Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License (Version 2) as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA.
+ */
+
+/*
+ * This is the private include file for the go7007 driver. It should not
+ * be included by anybody but the driver itself, and especially not by
+ * user-space applications.
+ */
+
+#include <media/v4l2-device.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-fh.h>
+#include <media/videobuf2-core.h>
+
+struct go7007;
+
+/* IDs to activate board-specific support code */
+#define GO7007_BOARDID_MATRIX_II 0
+#define GO7007_BOARDID_MATRIX_RELOAD 1
+#define GO7007_BOARDID_STAR_TREK 2
+#define GO7007_BOARDID_PCI_VOYAGER 3
+#define GO7007_BOARDID_XMEN 4
+#define GO7007_BOARDID_XMEN_II 5
+#define GO7007_BOARDID_XMEN_III 6
+#define GO7007_BOARDID_MATRIX_REV 7
+#define GO7007_BOARDID_PX_M402U 8
+#define GO7007_BOARDID_PX_TV402U 9
+#define GO7007_BOARDID_LIFEVIEW_LR192 10 /* TV Walker Ultra */
+#define GO7007_BOARDID_ENDURA 11
+#define GO7007_BOARDID_ADLINK_MPG24 12
+#define GO7007_BOARDID_SENSORAY_2250 13 /* Sensoray 2250/2251 */
+#define GO7007_BOARDID_ADS_USBAV_709 14
+
+/* Various characteristics of each board */
+#define GO7007_BOARD_HAS_AUDIO (1<<0)
+#define GO7007_BOARD_USE_ONBOARD_I2C (1<<1)
+#define GO7007_BOARD_HAS_TUNER (1<<2)
+
+/* Characteristics of sensor devices */
+#define GO7007_SENSOR_VALID_POLAR (1<<0)
+#define GO7007_SENSOR_HREF_POLAR (1<<1)
+#define GO7007_SENSOR_VREF_POLAR (1<<2)
+#define GO7007_SENSOR_FIELD_ID_POLAR (1<<3)
+#define GO7007_SENSOR_BIT_WIDTH (1<<4)
+#define GO7007_SENSOR_VALID_ENABLE (1<<5)
+#define GO7007_SENSOR_656 (1<<6)
+#define GO7007_SENSOR_CONFIG_MASK 0x7f
+#define GO7007_SENSOR_TV (1<<7)
+#define GO7007_SENSOR_VBI (1<<8)
+#define GO7007_SENSOR_SCALING (1<<9)
+#define GO7007_SENSOR_SAA7115 (1<<10)
+
+/* Characteristics of audio sensor devices */
+#define GO7007_AUDIO_I2S_MODE_1 (1)
+#define GO7007_AUDIO_I2S_MODE_2 (2)
+#define GO7007_AUDIO_I2S_MODE_3 (3)
+#define GO7007_AUDIO_BCLK_POLAR (1<<2)
+#define GO7007_AUDIO_WORD_14 (14<<4)
+#define GO7007_AUDIO_WORD_16 (16<<4)
+#define GO7007_AUDIO_ONE_CHANNEL (1<<11)
+#define GO7007_AUDIO_I2S_MASTER (1<<16)
+#define GO7007_AUDIO_OKI_MODE (1<<17)
+
+struct go7007_board_info {
+ unsigned int flags;
+ int hpi_buffer_cap;
+ unsigned int sensor_flags;
+ int sensor_width;
+ int sensor_height;
+ int sensor_framerate;
+ int sensor_h_offset;
+ int sensor_v_offset;
+ unsigned int audio_flags;
+ int audio_rate;
+ int audio_bclk_div;
+ int audio_main_div;
+ int num_i2c_devs;
+ struct go_i2c {
+ const char *type;
+ unsigned int is_video:1;
+ unsigned int is_audio:1;
+ int addr;
+ u32 flags;
+ } i2c_devs[5];
+ int num_inputs;
+ struct {
+ int video_input;
+ int audio_index;
+ char *name;
+ } inputs[4];
+ int video_config;
+ int num_aud_inputs;
+ struct {
+ int audio_input;
+ char *name;
+ } aud_inputs[3];
+};
+
+struct go7007_hpi_ops {
+ int (*interface_reset)(struct go7007 *go);
+ int (*write_interrupt)(struct go7007 *go, int addr, int data);
+ int (*read_interrupt)(struct go7007 *go);
+ int (*stream_start)(struct go7007 *go);
+ int (*stream_stop)(struct go7007 *go);
+ int (*send_firmware)(struct go7007 *go, u8 *data, int len);
+ int (*send_command)(struct go7007 *go, unsigned int cmd, void *arg);
+ void (*release)(struct go7007 *go);
+};
+
+/* The video buffer size must be a multiple of PAGE_SIZE */
+#define GO7007_BUF_PAGES (128 * 1024 / PAGE_SIZE)
+#define GO7007_BUF_SIZE (GO7007_BUF_PAGES << PAGE_SHIFT)
+
+struct go7007_buffer {
+ struct vb2_buffer vb;
+ struct list_head list;
+ unsigned int frame_offset;
+ u32 modet_active;
+};
+
+#define GO7007_RATIO_1_1 0
+#define GO7007_RATIO_4_3 1
+#define GO7007_RATIO_16_9 2
+
+enum go7007_parser_state {
+ STATE_DATA,
+ STATE_00,
+ STATE_00_00,
+ STATE_00_00_01,
+ STATE_FF,
+ STATE_VBI_LEN_A,
+ STATE_VBI_LEN_B,
+ STATE_MODET_MAP,
+ STATE_UNPARSED,
+};
+
+struct go7007 {
+ struct device *dev;
+ u8 bus_info[32];
+ const struct go7007_board_info *board_info;
+ unsigned int board_id;
+ int tuner_type;
+ int channel_number; /* for multi-channel boards like Adlink PCI-MPG24 */
+ char name[64];
+ struct video_device vdev;
+ void *boot_fw;
+ unsigned boot_fw_len;
+ struct v4l2_device v4l2_dev;
+ struct v4l2_ctrl_handler hdl;
+ struct v4l2_ctrl *mpeg_video_encoding;
+ struct v4l2_ctrl *mpeg_video_gop_size;
+ struct v4l2_ctrl *mpeg_video_gop_closure;
+ struct v4l2_ctrl *mpeg_video_bitrate;
+ struct v4l2_ctrl *mpeg_video_aspect_ratio;
+ struct v4l2_ctrl *mpeg_video_b_frames;
+ struct v4l2_ctrl *mpeg_video_rep_seqheader;
+ enum { STATUS_INIT, STATUS_ONLINE, STATUS_SHUTDOWN } status;
+ spinlock_t spinlock;
+ struct mutex hw_lock;
+ struct mutex serialize_lock;
+ int audio_enabled;
+ struct v4l2_subdev *sd_video;
+ struct v4l2_subdev *sd_audio;
+ u8 usb_buf[16];
+
+ /* Video input */
+ int input;
+ int aud_input;
+ enum { GO7007_STD_NTSC, GO7007_STD_PAL, GO7007_STD_OTHER } standard;
+ v4l2_std_id std;
+ int sensor_framerate;
+ int width;
+ int height;
+ int encoder_h_offset;
+ int encoder_v_offset;
+ unsigned int encoder_h_halve:1;
+ unsigned int encoder_v_halve:1;
+ unsigned int encoder_subsample:1;
+
+ /* Encoder config */
+ u32 format;
+ int bitrate;
+ int fps_scale;
+ int pali;
+ int aspect_ratio;
+ int gop_size;
+ unsigned int ipb:1;
+ unsigned int closed_gop:1;
+ unsigned int repeat_seqhead:1;
+ unsigned int seq_header_enable:1;
+ unsigned int gop_header_enable:1;
+ unsigned int dvd_mode:1;
+ unsigned int interlace_coding:1;
+
+ /* Motion detection */
+ unsigned int modet_enable:1;
+ struct {
+ unsigned int enable:1;
+ int pixel_threshold;
+ int motion_threshold;
+ int mb_threshold;
+ } modet[4];
+ unsigned char modet_map[1624];
+ unsigned char active_map[216];
+
+ /* Video streaming */
+ struct mutex queue_lock;
+ struct vb2_queue vidq;
+ enum go7007_parser_state state;
+ int parse_length;
+ u16 modet_word;
+ int seen_frame;
+ u32 next_seq;
+ struct list_head vidq_active;
+ wait_queue_head_t frame_waitq;
+ struct go7007_buffer *active_buf;
+
+ /* Audio streaming */
+ void (*audio_deliver)(struct go7007 *go, u8 *buf, int length);
+ void *snd_context;
+
+ /* I2C */
+ int i2c_adapter_online;
+ struct i2c_adapter i2c_adapter;
+
+ /* HPI driver */
+ struct go7007_hpi_ops *hpi_ops;
+ void *hpi_context;
+ int interrupt_available;
+ wait_queue_head_t interrupt_waitq;
+ unsigned short interrupt_value;
+ unsigned short interrupt_data;
+};
+
+static inline struct go7007 *to_go7007(struct v4l2_device *v4l2_dev)
+{
+ return container_of(v4l2_dev, struct go7007, v4l2_dev);
+}
+
+/* All of these must be called with the hpi_lock mutex held! */
+#define go7007_interface_reset(go) \
+ ((go)->hpi_ops->interface_reset(go))
+#define go7007_write_interrupt(go, x, y) \
+ ((go)->hpi_ops->write_interrupt)((go), (x), (y))
+#define go7007_stream_start(go) \
+ ((go)->hpi_ops->stream_start(go))
+#define go7007_stream_stop(go) \
+ ((go)->hpi_ops->stream_stop(go))
+#define go7007_send_firmware(go, x, y) \
+ ((go)->hpi_ops->send_firmware)((go), (x), (y))
+#define go7007_write_addr(go, x, y) \
+ ((go)->hpi_ops->write_interrupt)((go), (x)|0x8000, (y))
+
+/* go7007-driver.c */
+int go7007_read_addr(struct go7007 *go, u16 addr, u16 *data);
+int go7007_read_interrupt(struct go7007 *go, u16 *value, u16 *data);
+int go7007_boot_encoder(struct go7007 *go, int init_i2c);
+int go7007_reset_encoder(struct go7007 *go);
+int go7007_register_encoder(struct go7007 *go, unsigned num_i2c_devs);
+int go7007_start_encoder(struct go7007 *go);
+void go7007_parse_video_stream(struct go7007 *go, u8 *buf, int length);
+struct go7007 *go7007_alloc(const struct go7007_board_info *board,
+ struct device *dev);
+void go7007_update_board(struct go7007 *go);
+
+/* go7007-fw.c */
+int go7007_construct_fw_image(struct go7007 *go, u8 **fw, int *fwlen);
+
+/* go7007-i2c.c */
+int go7007_i2c_init(struct go7007 *go);
+int go7007_i2c_remove(struct go7007 *go);
+
+/* go7007-v4l2.c */
+int go7007_v4l2_init(struct go7007 *go);
+int go7007_v4l2_ctrl_init(struct go7007 *go);
+void go7007_v4l2_remove(struct go7007 *go);
+
+/* snd-go7007.c */
+int go7007_snd_init(struct go7007 *go);
+int go7007_snd_remove(struct go7007 *go);
diff --git a/drivers/staging/media/go7007/go7007-usb.c b/drivers/staging/media/go7007/go7007-usb.c
new file mode 100644
index 00000000000..2f62be905cd
--- /dev/null
+++ b/drivers/staging/media/go7007/go7007-usb.c
@@ -0,0 +1,1349 @@
+/*
+ * Copyright (C) 2005-2006 Micronas USA Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License (Version 2) as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/wait.h>
+#include <linux/list.h>
+#include <linux/slab.h>
+#include <linux/time.h>
+#include <linux/mm.h>
+#include <linux/usb.h>
+#include <linux/i2c.h>
+#include <asm/byteorder.h>
+#include <media/saa7115.h>
+#include <media/tuner.h>
+#include <media/uda1342.h>
+
+#include "go7007-priv.h"
+
+static unsigned int assume_endura;
+module_param(assume_endura, int, 0644);
+MODULE_PARM_DESC(assume_endura,
+ "when probing fails, hardware is a Pelco Endura");
+
+/* #define GO7007_I2C_DEBUG */ /* for debugging the EZ-USB I2C adapter */
+
+#define HPI_STATUS_ADDR 0xFFF4
+#define INT_PARAM_ADDR 0xFFF6
+#define INT_INDEX_ADDR 0xFFF8
+
+/*
+ * Pipes on EZ-USB interface:
+ * 0 snd - Control
+ * 0 rcv - Control
+ * 2 snd - Download firmware (control)
+ * 4 rcv - Read Interrupt (interrupt)
+ * 6 rcv - Read Video (bulk)
+ * 8 rcv - Read Audio (bulk)
+ */
+
+#define GO7007_USB_EZUSB (1<<0)
+#define GO7007_USB_EZUSB_I2C (1<<1)
+
+struct go7007_usb_board {
+ unsigned int flags;
+ struct go7007_board_info main_info;
+};
+
+struct go7007_usb {
+ const struct go7007_usb_board *board;
+ struct mutex i2c_lock;
+ struct usb_device *usbdev;
+ struct urb *video_urbs[8];
+ struct urb *audio_urbs[8];
+ struct urb *intr_urb;
+};
+
+/*********************** Product specification data ***********************/
+
+static const struct go7007_usb_board board_matrix_ii = {
+ .flags = GO7007_USB_EZUSB,
+ .main_info = {
+ .flags = GO7007_BOARD_HAS_AUDIO |
+ GO7007_BOARD_USE_ONBOARD_I2C,
+ .audio_flags = GO7007_AUDIO_I2S_MODE_1 |
+ GO7007_AUDIO_WORD_16,
+ .audio_rate = 48000,
+ .audio_bclk_div = 8,
+ .audio_main_div = 2,
+ .hpi_buffer_cap = 7,
+ .sensor_flags = GO7007_SENSOR_656 |
+ GO7007_SENSOR_VALID_ENABLE |
+ GO7007_SENSOR_TV |
+ GO7007_SENSOR_SAA7115 |
+ GO7007_SENSOR_VBI |
+ GO7007_SENSOR_SCALING,
+ .num_i2c_devs = 1,
+ .i2c_devs = {
+ {
+ .type = "saa7115",
+ .addr = 0x20,
+ .is_video = 1,
+ },
+ },
+ .num_inputs = 2,
+ .inputs = {
+ {
+ .video_input = 0,
+ .name = "Composite",
+ },
+ {
+ .video_input = 9,
+ .name = "S-Video",
+ },
+ },
+ .video_config = SAA7115_IDQ_IS_DEFAULT,
+ },
+};
+
+static const struct go7007_usb_board board_matrix_reload = {
+ .flags = GO7007_USB_EZUSB,
+ .main_info = {
+ .flags = GO7007_BOARD_HAS_AUDIO |
+ GO7007_BOARD_USE_ONBOARD_I2C,
+ .audio_flags = GO7007_AUDIO_I2S_MODE_1 |
+ GO7007_AUDIO_I2S_MASTER |
+ GO7007_AUDIO_WORD_16,
+ .audio_rate = 48000,
+ .audio_bclk_div = 8,
+ .audio_main_div = 2,
+ .hpi_buffer_cap = 7,
+ .sensor_flags = GO7007_SENSOR_656 |
+ GO7007_SENSOR_TV,
+ .num_i2c_devs = 1,
+ .i2c_devs = {
+ {
+ .type = "saa7113",
+ .addr = 0x25,
+ .is_video = 1,
+ },
+ },
+ .num_inputs = 2,
+ .inputs = {
+ {
+ .video_input = 0,
+ .name = "Composite",
+ },
+ {
+ .video_input = 9,
+ .name = "S-Video",
+ },
+ },
+ .video_config = SAA7115_IDQ_IS_DEFAULT,
+ },
+};
+
+static const struct go7007_usb_board board_star_trek = {
+ .flags = GO7007_USB_EZUSB | GO7007_USB_EZUSB_I2C,
+ .main_info = {
+ .flags = GO7007_BOARD_HAS_AUDIO, /* |
+ GO7007_BOARD_HAS_TUNER, */
+ .sensor_flags = GO7007_SENSOR_656 |
+ GO7007_SENSOR_VALID_ENABLE |
+ GO7007_SENSOR_TV |
+ GO7007_SENSOR_SAA7115 |
+ GO7007_SENSOR_VBI |
+ GO7007_SENSOR_SCALING,
+ .audio_flags = GO7007_AUDIO_I2S_MODE_1 |
+ GO7007_AUDIO_WORD_16,
+ .audio_bclk_div = 8,
+ .audio_main_div = 2,
+ .hpi_buffer_cap = 7,
+ .num_i2c_devs = 1,
+ .i2c_devs = {
+ {
+ .type = "saa7115",
+ .addr = 0x20,
+ .is_video = 1,
+ },
+ },
+ .num_inputs = 2,
+ .inputs = {
+ /* {
+ * .video_input = 3,
+ * .audio_index = AUDIO_TUNER,
+ * .name = "Tuner",
+ * },
+ */
+ {
+ .video_input = 1,
+ /* .audio_index = AUDIO_EXTERN, */
+ .name = "Composite",
+ },
+ {
+ .video_input = 8,
+ /* .audio_index = AUDIO_EXTERN, */
+ .name = "S-Video",
+ },
+ },
+ .video_config = SAA7115_IDQ_IS_DEFAULT,
+ },
+};
+
+static const struct go7007_usb_board board_px_tv402u = {
+ .flags = GO7007_USB_EZUSB | GO7007_USB_EZUSB_I2C,
+ .main_info = {
+ .flags = GO7007_BOARD_HAS_AUDIO |
+ GO7007_BOARD_HAS_TUNER,
+ .sensor_flags = GO7007_SENSOR_656 |
+ GO7007_SENSOR_VALID_ENABLE |
+ GO7007_SENSOR_TV |
+ GO7007_SENSOR_SAA7115 |
+ GO7007_SENSOR_VBI |
+ GO7007_SENSOR_SCALING,
+ .audio_flags = GO7007_AUDIO_I2S_MODE_1 |
+ GO7007_AUDIO_WORD_16,
+ .audio_bclk_div = 8,
+ .audio_main_div = 2,
+ .hpi_buffer_cap = 7,
+ .num_i2c_devs = 5,
+ .i2c_devs = {
+ {
+ .type = "saa7115",
+ .addr = 0x20,
+ .is_video = 1,
+ },
+ {
+ .type = "uda1342",
+ .addr = 0x1a,
+ .is_audio = 1,
+ },
+ {
+ .type = "tuner",
+ .addr = 0x60,
+ },
+ {
+ .type = "tuner",
+ .addr = 0x43,
+ },
+ {
+ .type = "sony-btf-mpx",
+ .addr = 0x44,
+ },
+ },
+ .num_inputs = 3,
+ .inputs = {
+ {
+ .video_input = 3,
+ .audio_index = 0,
+ .name = "Tuner",
+ },
+ {
+ .video_input = 1,
+ .audio_index = 1,
+ .name = "Composite",
+ },
+ {
+ .video_input = 8,
+ .audio_index = 1,
+ .name = "S-Video",
+ },
+ },
+ .video_config = SAA7115_IDQ_IS_DEFAULT,
+ .num_aud_inputs = 2,
+ .aud_inputs = {
+ {
+ .audio_input = UDA1342_IN2,
+ .name = "Tuner",
+ },
+ {
+ .audio_input = UDA1342_IN1,
+ .name = "Line In",
+ },
+ },
+ },
+};
+
+static const struct go7007_usb_board board_xmen = {
+ .flags = 0,
+ .main_info = {
+ .flags = GO7007_BOARD_USE_ONBOARD_I2C,
+ .hpi_buffer_cap = 0,
+ .sensor_flags = GO7007_SENSOR_VREF_POLAR,
+ .sensor_width = 320,
+ .sensor_height = 240,
+ .sensor_framerate = 30030,
+ .audio_flags = GO7007_AUDIO_ONE_CHANNEL |
+ GO7007_AUDIO_I2S_MODE_3 |
+ GO7007_AUDIO_WORD_14 |
+ GO7007_AUDIO_I2S_MASTER |
+ GO7007_AUDIO_BCLK_POLAR |
+ GO7007_AUDIO_OKI_MODE,
+ .audio_rate = 8000,
+ .audio_bclk_div = 48,
+ .audio_main_div = 1,
+ .num_i2c_devs = 1,
+ .i2c_devs = {
+ {
+ .type = "ov7640",
+ .addr = 0x21,
+ },
+ },
+ .num_inputs = 1,
+ .inputs = {
+ {
+ .name = "Camera",
+ },
+ },
+ },
+};
+
+static const struct go7007_usb_board board_matrix_revolution = {
+ .flags = GO7007_USB_EZUSB,
+ .main_info = {
+ .flags = GO7007_BOARD_HAS_AUDIO |
+ GO7007_BOARD_USE_ONBOARD_I2C,
+ .audio_flags = GO7007_AUDIO_I2S_MODE_1 |
+ GO7007_AUDIO_I2S_MASTER |
+ GO7007_AUDIO_WORD_16,
+ .audio_rate = 48000,
+ .audio_bclk_div = 8,
+ .audio_main_div = 2,
+ .hpi_buffer_cap = 7,
+ .sensor_flags = GO7007_SENSOR_656 |
+ GO7007_SENSOR_TV |
+ GO7007_SENSOR_VBI,
+ .num_i2c_devs = 1,
+ .i2c_devs = {
+ {
+ .type = "tw9903",
+ .is_video = 1,
+ .addr = 0x44,
+ },
+ },
+ .num_inputs = 2,
+ .inputs = {
+ {
+ .video_input = 2,
+ .name = "Composite",
+ },
+ {
+ .video_input = 8,
+ .name = "S-Video",
+ },
+ },
+ },
+};
+
+static const struct go7007_usb_board board_lifeview_lr192 = {
+ .flags = GO7007_USB_EZUSB,
+ .main_info = {
+ .flags = GO7007_BOARD_HAS_AUDIO |
+ GO7007_BOARD_USE_ONBOARD_I2C,
+ .audio_flags = GO7007_AUDIO_I2S_MODE_1 |
+ GO7007_AUDIO_WORD_16,
+ .audio_rate = 48000,
+ .audio_bclk_div = 8,
+ .audio_main_div = 2,
+ .hpi_buffer_cap = 7,
+ .sensor_flags = GO7007_SENSOR_656 |
+ GO7007_SENSOR_VALID_ENABLE |
+ GO7007_SENSOR_TV |
+ GO7007_SENSOR_VBI |
+ GO7007_SENSOR_SCALING,
+ .num_i2c_devs = 0,
+ .num_inputs = 1,
+ .inputs = {
+ {
+ .video_input = 0,
+ .name = "Composite",
+ },
+ },
+ },
+};
+
+static const struct go7007_usb_board board_endura = {
+ .flags = 0,
+ .main_info = {
+ .flags = 0,
+ .audio_flags = GO7007_AUDIO_I2S_MODE_1 |
+ GO7007_AUDIO_I2S_MASTER |
+ GO7007_AUDIO_WORD_16,
+ .audio_rate = 8000,
+ .audio_bclk_div = 48,
+ .audio_main_div = 8,
+ .hpi_buffer_cap = 0,
+ .sensor_flags = GO7007_SENSOR_656 |
+ GO7007_SENSOR_TV,
+ .sensor_h_offset = 8,
+ .num_i2c_devs = 0,
+ .num_inputs = 1,
+ .inputs = {
+ {
+ .name = "Camera",
+ },
+ },
+ },
+};
+
+static const struct go7007_usb_board board_adlink_mpg24 = {
+ .flags = 0,
+ .main_info = {
+ .flags = GO7007_BOARD_USE_ONBOARD_I2C,
+ .audio_flags = GO7007_AUDIO_I2S_MODE_1 |
+ GO7007_AUDIO_I2S_MASTER |
+ GO7007_AUDIO_WORD_16,
+ .audio_rate = 48000,
+ .audio_bclk_div = 8,
+ .audio_main_div = 2,
+ .hpi_buffer_cap = 0,
+ .sensor_flags = GO7007_SENSOR_656 |
+ GO7007_SENSOR_TV |
+ GO7007_SENSOR_VBI,
+ .num_i2c_devs = 1,
+ .i2c_devs = {
+ {
+ .type = "tw2804",
+ .addr = 0x00, /* yes, really */
+ .flags = I2C_CLIENT_TEN,
+ .is_video = 1,
+ },
+ },
+ .num_inputs = 1,
+ .inputs = {
+ {
+ .name = "Composite",
+ },
+ },
+ },
+};
+
+static const struct go7007_usb_board board_sensoray_2250 = {
+ .flags = GO7007_USB_EZUSB | GO7007_USB_EZUSB_I2C,
+ .main_info = {
+ .audio_flags = GO7007_AUDIO_I2S_MODE_1 |
+ GO7007_AUDIO_I2S_MASTER |
+ GO7007_AUDIO_WORD_16,
+ .flags = GO7007_BOARD_HAS_AUDIO,
+ .audio_rate = 48000,
+ .audio_bclk_div = 8,
+ .audio_main_div = 2,
+ .hpi_buffer_cap = 7,
+ .sensor_flags = GO7007_SENSOR_656 |
+ GO7007_SENSOR_TV,
+ .num_i2c_devs = 1,
+ .i2c_devs = {
+ {
+ .type = "s2250",
+ .addr = 0x43,
+ .is_video = 1,
+ .is_audio = 1,
+ },
+ },
+ .num_inputs = 2,
+ .inputs = {
+ {
+ .video_input = 0,
+ .name = "Composite",
+ },
+ {
+ .video_input = 1,
+ .name = "S-Video",
+ },
+ },
+ .num_aud_inputs = 3,
+ .aud_inputs = {
+ {
+ .audio_input = 0,
+ .name = "Line In",
+ },
+ {
+ .audio_input = 1,
+ .name = "Mic",
+ },
+ {
+ .audio_input = 2,
+ .name = "Mic Boost",
+ },
+ },
+ },
+};
+
+static const struct go7007_usb_board board_ads_usbav_709 = {
+ .flags = GO7007_USB_EZUSB,
+ .main_info = {
+ .flags = GO7007_BOARD_HAS_AUDIO |
+ GO7007_BOARD_USE_ONBOARD_I2C,
+ .audio_flags = GO7007_AUDIO_I2S_MODE_1 |
+ GO7007_AUDIO_I2S_MASTER |
+ GO7007_AUDIO_WORD_16,
+ .audio_rate = 48000,
+ .audio_bclk_div = 8,
+ .audio_main_div = 2,
+ .hpi_buffer_cap = 7,
+ .sensor_flags = GO7007_SENSOR_656 |
+ GO7007_SENSOR_TV |
+ GO7007_SENSOR_VBI,
+ .num_i2c_devs = 1,
+ .i2c_devs = {
+ {
+ .type = "tw9906",
+ .is_video = 1,
+ .addr = 0x44,
+ },
+ },
+ .num_inputs = 2,
+ .inputs = {
+ {
+ .video_input = 0,
+ .name = "Composite",
+ },
+ {
+ .video_input = 10,
+ .name = "S-Video",
+ },
+ },
+ },
+};
+
+static const struct usb_device_id go7007_usb_id_table[] = {
+ {
+ .match_flags = USB_DEVICE_ID_MATCH_DEVICE_AND_VERSION |
+ USB_DEVICE_ID_MATCH_INT_INFO,
+ .idVendor = 0x0eb1, /* Vendor ID of WIS Technologies */
+ .idProduct = 0x7007, /* Product ID of GO7007SB chip */
+ .bcdDevice_lo = 0x200, /* Revision number of XMen */
+ .bcdDevice_hi = 0x200,
+ .bInterfaceClass = 255,
+ .bInterfaceSubClass = 0,
+ .bInterfaceProtocol = 255,
+ .driver_info = (kernel_ulong_t)GO7007_BOARDID_XMEN,
+ },
+ {
+ .match_flags = USB_DEVICE_ID_MATCH_DEVICE_AND_VERSION,
+ .idVendor = 0x0eb1, /* Vendor ID of WIS Technologies */
+ .idProduct = 0x7007, /* Product ID of GO7007SB chip */
+ .bcdDevice_lo = 0x202, /* Revision number of Matrix II */
+ .bcdDevice_hi = 0x202,
+ .driver_info = (kernel_ulong_t)GO7007_BOARDID_MATRIX_II,
+ },
+ {
+ .match_flags = USB_DEVICE_ID_MATCH_DEVICE_AND_VERSION,
+ .idVendor = 0x0eb1, /* Vendor ID of WIS Technologies */
+ .idProduct = 0x7007, /* Product ID of GO7007SB chip */
+ .bcdDevice_lo = 0x204, /* Revision number of Matrix */
+ .bcdDevice_hi = 0x204, /* Reloaded */
+ .driver_info = (kernel_ulong_t)GO7007_BOARDID_MATRIX_RELOAD,
+ },
+ {
+ .match_flags = USB_DEVICE_ID_MATCH_DEVICE_AND_VERSION |
+ USB_DEVICE_ID_MATCH_INT_INFO,
+ .idVendor = 0x0eb1, /* Vendor ID of WIS Technologies */
+ .idProduct = 0x7007, /* Product ID of GO7007SB chip */
+ .bcdDevice_lo = 0x205, /* Revision number of XMen-II */
+ .bcdDevice_hi = 0x205,
+ .bInterfaceClass = 255,
+ .bInterfaceSubClass = 0,
+ .bInterfaceProtocol = 255,
+ .driver_info = (kernel_ulong_t)GO7007_BOARDID_XMEN_II,
+ },
+ {
+ .match_flags = USB_DEVICE_ID_MATCH_DEVICE_AND_VERSION,
+ .idVendor = 0x0eb1, /* Vendor ID of WIS Technologies */
+ .idProduct = 0x7007, /* Product ID of GO7007SB chip */
+ .bcdDevice_lo = 0x208, /* Revision number of Star Trek */
+ .bcdDevice_hi = 0x208,
+ .driver_info = (kernel_ulong_t)GO7007_BOARDID_STAR_TREK,
+ },
+ {
+ .match_flags = USB_DEVICE_ID_MATCH_DEVICE_AND_VERSION |
+ USB_DEVICE_ID_MATCH_INT_INFO,
+ .idVendor = 0x0eb1, /* Vendor ID of WIS Technologies */
+ .idProduct = 0x7007, /* Product ID of GO7007SB chip */
+ .bcdDevice_lo = 0x209, /* Revision number of XMen-III */
+ .bcdDevice_hi = 0x209,
+ .bInterfaceClass = 255,
+ .bInterfaceSubClass = 0,
+ .bInterfaceProtocol = 255,
+ .driver_info = (kernel_ulong_t)GO7007_BOARDID_XMEN_III,
+ },
+ {
+ .match_flags = USB_DEVICE_ID_MATCH_DEVICE_AND_VERSION,
+ .idVendor = 0x0eb1, /* Vendor ID of WIS Technologies */
+ .idProduct = 0x7007, /* Product ID of GO7007SB chip */
+ .bcdDevice_lo = 0x210, /* Revision number of Matrix */
+ .bcdDevice_hi = 0x210, /* Revolution */
+ .driver_info = (kernel_ulong_t)GO7007_BOARDID_MATRIX_REV,
+ },
+ {
+ .match_flags = USB_DEVICE_ID_MATCH_DEVICE_AND_VERSION,
+ .idVendor = 0x093b, /* Vendor ID of Plextor */
+ .idProduct = 0xa102, /* Product ID of M402U */
+ .bcdDevice_lo = 0x1, /* revision number of Blueberry */
+ .bcdDevice_hi = 0x1,
+ .driver_info = (kernel_ulong_t)GO7007_BOARDID_PX_M402U,
+ },
+ {
+ .match_flags = USB_DEVICE_ID_MATCH_DEVICE_AND_VERSION,
+ .idVendor = 0x093b, /* Vendor ID of Plextor */
+ .idProduct = 0xa104, /* Product ID of TV402U */
+ .bcdDevice_lo = 0x1,
+ .bcdDevice_hi = 0x1,
+ .driver_info = (kernel_ulong_t)GO7007_BOARDID_PX_TV402U,
+ },
+ {
+ .match_flags = USB_DEVICE_ID_MATCH_DEVICE_AND_VERSION,
+ .idVendor = 0x10fd, /* Vendor ID of Anubis Electronics */
+ .idProduct = 0xde00, /* Product ID of Lifeview LR192 */
+ .bcdDevice_lo = 0x1,
+ .bcdDevice_hi = 0x1,
+ .driver_info = (kernel_ulong_t)GO7007_BOARDID_LIFEVIEW_LR192,
+ },
+ {
+ .match_flags = USB_DEVICE_ID_MATCH_DEVICE_AND_VERSION,
+ .idVendor = 0x1943, /* Vendor ID Sensoray */
+ .idProduct = 0x2250, /* Product ID of 2250/2251 */
+ .bcdDevice_lo = 0x1,
+ .bcdDevice_hi = 0x1,
+ .driver_info = (kernel_ulong_t)GO7007_BOARDID_SENSORAY_2250,
+ },
+ {
+ .match_flags = USB_DEVICE_ID_MATCH_DEVICE_AND_VERSION,
+ .idVendor = 0x06e1, /* Vendor ID of ADS Technologies */
+ .idProduct = 0x0709, /* Product ID of DVD Xpress DX2 */
+ .bcdDevice_lo = 0x204,
+ .bcdDevice_hi = 0x204,
+ .driver_info = (kernel_ulong_t)GO7007_BOARDID_ADS_USBAV_709,
+ },
+ { } /* Terminating entry */
+};
+
+MODULE_DEVICE_TABLE(usb, go7007_usb_id_table);
+
+/********************* Driver for EZ-USB HPI interface *********************/
+
+static int go7007_usb_vendor_request(struct go7007 *go, int request,
+ int value, int index, void *transfer_buffer, int length, int in)
+{
+ struct go7007_usb *usb = go->hpi_context;
+ int timeout = 5000;
+
+ if (in) {
+ return usb_control_msg(usb->usbdev,
+ usb_rcvctrlpipe(usb->usbdev, 0), request,
+ USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN,
+ value, index, transfer_buffer, length, timeout);
+ } else {
+ return usb_control_msg(usb->usbdev,
+ usb_sndctrlpipe(usb->usbdev, 0), request,
+ USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+ value, index, transfer_buffer, length, timeout);
+ }
+}
+
+static int go7007_usb_interface_reset(struct go7007 *go)
+{
+ struct go7007_usb *usb = go->hpi_context;
+ u16 intr_val, intr_data;
+
+ if (go->status == STATUS_SHUTDOWN)
+ return -1;
+ /* Reset encoder */
+ if (go7007_write_interrupt(go, 0x0001, 0x0001) < 0)
+ return -1;
+ msleep(100);
+
+ if (usb->board->flags & GO7007_USB_EZUSB) {
+ /* Reset buffer in EZ-USB */
+ pr_debug("resetting EZ-USB buffers\n");
+ if (go7007_usb_vendor_request(go, 0x10, 0, 0, NULL, 0, 0) < 0 ||
+ go7007_usb_vendor_request(go, 0x10, 0, 0, NULL, 0, 0) < 0)
+ return -1;
+
+ /* Reset encoder again */
+ if (go7007_write_interrupt(go, 0x0001, 0x0001) < 0)
+ return -1;
+ msleep(100);
+ }
+
+ /* Wait for an interrupt to indicate successful hardware reset */
+ if (go7007_read_interrupt(go, &intr_val, &intr_data) < 0 ||
+ (intr_val & ~0x1) != 0x55aa) {
+ dev_err(go->dev, "unable to reset the USB interface\n");
+ return -1;
+ }
+ return 0;
+}
+
+static int go7007_usb_ezusb_write_interrupt(struct go7007 *go,
+ int addr, int data)
+{
+ struct go7007_usb *usb = go->hpi_context;
+ int i, r;
+ u16 status_reg = 0;
+ int timeout = 500;
+
+ pr_debug("WriteInterrupt: %04x %04x\n", addr, data);
+
+ for (i = 0; i < 100; ++i) {
+ r = usb_control_msg(usb->usbdev,
+ usb_rcvctrlpipe(usb->usbdev, 0), 0x14,
+ USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN,
+ 0, HPI_STATUS_ADDR, go->usb_buf,
+ sizeof(status_reg), timeout);
+ if (r < 0)
+ break;
+ status_reg = le16_to_cpu(*((u16 *)go->usb_buf));
+ if (!(status_reg & 0x0010))
+ break;
+ msleep(10);
+ }
+ if (r < 0)
+ goto write_int_error;
+ if (i == 100) {
+ dev_err(go->dev, "device is hung, status reg = 0x%04x\n", status_reg);
+ return -1;
+ }
+ r = usb_control_msg(usb->usbdev, usb_sndctrlpipe(usb->usbdev, 0), 0x12,
+ USB_TYPE_VENDOR | USB_RECIP_DEVICE, data,
+ INT_PARAM_ADDR, NULL, 0, timeout);
+ if (r < 0)
+ goto write_int_error;
+ r = usb_control_msg(usb->usbdev, usb_sndctrlpipe(usb->usbdev, 0),
+ 0x12, USB_TYPE_VENDOR | USB_RECIP_DEVICE, addr,
+ INT_INDEX_ADDR, NULL, 0, timeout);
+ if (r < 0)
+ goto write_int_error;
+ return 0;
+
+write_int_error:
+ dev_err(go->dev, "error in WriteInterrupt: %d\n", r);
+ return r;
+}
+
+static int go7007_usb_onboard_write_interrupt(struct go7007 *go,
+ int addr, int data)
+{
+ struct go7007_usb *usb = go->hpi_context;
+ int r;
+ int timeout = 500;
+
+ pr_debug("WriteInterrupt: %04x %04x\n", addr, data);
+
+ go->usb_buf[0] = data & 0xff;
+ go->usb_buf[1] = data >> 8;
+ go->usb_buf[2] = addr & 0xff;
+ go->usb_buf[3] = addr >> 8;
+ go->usb_buf[4] = go->usb_buf[5] = go->usb_buf[6] = go->usb_buf[7] = 0;
+ r = usb_control_msg(usb->usbdev, usb_sndctrlpipe(usb->usbdev, 2), 0x00,
+ USB_TYPE_VENDOR | USB_RECIP_ENDPOINT, 0x55aa,
+ 0xf0f0, go->usb_buf, 8, timeout);
+ if (r < 0) {
+ dev_err(go->dev, "error in WriteInterrupt: %d\n", r);
+ return r;
+ }
+ return 0;
+}
+
+static void go7007_usb_readinterrupt_complete(struct urb *urb)
+{
+ struct go7007 *go = (struct go7007 *)urb->context;
+ u16 *regs = (u16 *)urb->transfer_buffer;
+ int status = urb->status;
+
+ if (status) {
+ if (status != -ESHUTDOWN &&
+ go->status != STATUS_SHUTDOWN) {
+ dev_err(go->dev, "error in read interrupt: %d\n", urb->status);
+ } else {
+ wake_up(&go->interrupt_waitq);
+ return;
+ }
+ } else if (urb->actual_length != urb->transfer_buffer_length) {
+ dev_err(go->dev, "short read in interrupt pipe!\n");
+ } else {
+ go->interrupt_available = 1;
+ go->interrupt_data = __le16_to_cpu(regs[0]);
+ go->interrupt_value = __le16_to_cpu(regs[1]);
+ pr_debug("ReadInterrupt: %04x %04x\n",
+ go->interrupt_value, go->interrupt_data);
+ }
+
+ wake_up(&go->interrupt_waitq);
+}
+
+static int go7007_usb_read_interrupt(struct go7007 *go)
+{
+ struct go7007_usb *usb = go->hpi_context;
+ int r;
+
+ r = usb_submit_urb(usb->intr_urb, GFP_KERNEL);
+ if (r < 0) {
+ dev_err(go->dev, "unable to submit interrupt urb: %d\n", r);
+ return r;
+ }
+ return 0;
+}
+
+static void go7007_usb_read_video_pipe_complete(struct urb *urb)
+{
+ struct go7007 *go = (struct go7007 *)urb->context;
+ int r, status = urb->status;
+
+ if (!vb2_is_streaming(&go->vidq)) {
+ wake_up_interruptible(&go->frame_waitq);
+ return;
+ }
+ if (status) {
+ dev_err(go->dev, "error in video pipe: %d\n", status);
+ return;
+ }
+ if (urb->actual_length != urb->transfer_buffer_length) {
+ dev_err(go->dev, "short read in video pipe!\n");
+ return;
+ }
+ go7007_parse_video_stream(go, urb->transfer_buffer, urb->actual_length);
+ r = usb_submit_urb(urb, GFP_ATOMIC);
+ if (r < 0)
+ dev_err(go->dev, "error in video pipe: %d\n", r);
+}
+
+static void go7007_usb_read_audio_pipe_complete(struct urb *urb)
+{
+ struct go7007 *go = (struct go7007 *)urb->context;
+ int r, status = urb->status;
+
+ if (!vb2_is_streaming(&go->vidq))
+ return;
+ if (status) {
+ dev_err(go->dev, "error in audio pipe: %d\n",
+ status);
+ return;
+ }
+ if (urb->actual_length != urb->transfer_buffer_length) {
+ dev_err(go->dev, "short read in audio pipe!\n");
+ return;
+ }
+ if (go->audio_deliver != NULL)
+ go->audio_deliver(go, urb->transfer_buffer, urb->actual_length);
+ r = usb_submit_urb(urb, GFP_ATOMIC);
+ if (r < 0)
+ dev_err(go->dev, "error in audio pipe: %d\n", r);
+}
+
+static int go7007_usb_stream_start(struct go7007 *go)
+{
+ struct go7007_usb *usb = go->hpi_context;
+ int i, r;
+
+ for (i = 0; i < 8; ++i) {
+ r = usb_submit_urb(usb->video_urbs[i], GFP_KERNEL);
+ if (r < 0) {
+ dev_err(go->dev, "error submitting video urb %d: %d\n", i, r);
+ goto video_submit_failed;
+ }
+ }
+ if (!go->audio_enabled)
+ return 0;
+
+ for (i = 0; i < 8; ++i) {
+ r = usb_submit_urb(usb->audio_urbs[i], GFP_KERNEL);
+ if (r < 0) {
+ dev_err(go->dev, "error submitting audio urb %d: %d\n", i, r);
+ goto audio_submit_failed;
+ }
+ }
+ return 0;
+
+audio_submit_failed:
+ for (i = 0; i < 7; ++i)
+ usb_kill_urb(usb->audio_urbs[i]);
+video_submit_failed:
+ for (i = 0; i < 8; ++i)
+ usb_kill_urb(usb->video_urbs[i]);
+ return -1;
+}
+
+static int go7007_usb_stream_stop(struct go7007 *go)
+{
+ struct go7007_usb *usb = go->hpi_context;
+ int i;
+
+ if (go->status == STATUS_SHUTDOWN)
+ return 0;
+ for (i = 0; i < 8; ++i)
+ usb_kill_urb(usb->video_urbs[i]);
+ if (go->audio_enabled)
+ for (i = 0; i < 8; ++i)
+ usb_kill_urb(usb->audio_urbs[i]);
+ return 0;
+}
+
+static int go7007_usb_send_firmware(struct go7007 *go, u8 *data, int len)
+{
+ struct go7007_usb *usb = go->hpi_context;
+ int transferred, pipe;
+ int timeout = 500;
+
+ pr_debug("DownloadBuffer sending %d bytes\n", len);
+
+ if (usb->board->flags & GO7007_USB_EZUSB)
+ pipe = usb_sndbulkpipe(usb->usbdev, 2);
+ else
+ pipe = usb_sndbulkpipe(usb->usbdev, 3);
+
+ return usb_bulk_msg(usb->usbdev, pipe, data, len,
+ &transferred, timeout);
+}
+
+static void go7007_usb_release(struct go7007 *go)
+{
+ struct go7007_usb *usb = go->hpi_context;
+ struct urb *vurb, *aurb;
+ int i;
+
+ if (usb->intr_urb) {
+ usb_kill_urb(usb->intr_urb);
+ kfree(usb->intr_urb->transfer_buffer);
+ usb_free_urb(usb->intr_urb);
+ }
+
+ /* Free USB-related structs */
+ for (i = 0; i < 8; ++i) {
+ vurb = usb->video_urbs[i];
+ if (vurb) {
+ usb_kill_urb(vurb);
+ kfree(vurb->transfer_buffer);
+ usb_free_urb(vurb);
+ }
+ aurb = usb->audio_urbs[i];
+ if (aurb) {
+ usb_kill_urb(aurb);
+ kfree(aurb->transfer_buffer);
+ usb_free_urb(aurb);
+ }
+ }
+
+ kfree(go->hpi_context);
+}
+
+static struct go7007_hpi_ops go7007_usb_ezusb_hpi_ops = {
+ .interface_reset = go7007_usb_interface_reset,
+ .write_interrupt = go7007_usb_ezusb_write_interrupt,
+ .read_interrupt = go7007_usb_read_interrupt,
+ .stream_start = go7007_usb_stream_start,
+ .stream_stop = go7007_usb_stream_stop,
+ .send_firmware = go7007_usb_send_firmware,
+ .release = go7007_usb_release,
+};
+
+static struct go7007_hpi_ops go7007_usb_onboard_hpi_ops = {
+ .interface_reset = go7007_usb_interface_reset,
+ .write_interrupt = go7007_usb_onboard_write_interrupt,
+ .read_interrupt = go7007_usb_read_interrupt,
+ .stream_start = go7007_usb_stream_start,
+ .stream_stop = go7007_usb_stream_stop,
+ .send_firmware = go7007_usb_send_firmware,
+ .release = go7007_usb_release,
+};
+
+/********************* Driver for EZ-USB I2C adapter *********************/
+
+static int go7007_usb_i2c_master_xfer(struct i2c_adapter *adapter,
+ struct i2c_msg msgs[], int num)
+{
+ struct go7007 *go = i2c_get_adapdata(adapter);
+ struct go7007_usb *usb = go->hpi_context;
+ u8 *buf = go->usb_buf;
+ int buf_len, i;
+ int ret = -EIO;
+
+ if (go->status == STATUS_SHUTDOWN)
+ return -ENODEV;
+
+ mutex_lock(&usb->i2c_lock);
+
+ for (i = 0; i < num; ++i) {
+ /* The hardware command is "write some bytes then read some
+ * bytes", so we try to coalesce a write followed by a read
+ * into a single USB transaction */
+ if (i + 1 < num && msgs[i].addr == msgs[i + 1].addr &&
+ !(msgs[i].flags & I2C_M_RD) &&
+ (msgs[i + 1].flags & I2C_M_RD)) {
+#ifdef GO7007_I2C_DEBUG
+ pr_debug("i2c write/read %d/%d bytes on %02x\n",
+ msgs[i].len, msgs[i + 1].len, msgs[i].addr);
+#endif
+ buf[0] = 0x01;
+ buf[1] = msgs[i].len + 1;
+ buf[2] = msgs[i].addr << 1;
+ memcpy(&buf[3], msgs[i].buf, msgs[i].len);
+ buf_len = msgs[i].len + 3;
+ buf[buf_len++] = msgs[++i].len;
+ } else if (msgs[i].flags & I2C_M_RD) {
+#ifdef GO7007_I2C_DEBUG
+ pr_debug("i2c read %d bytes on %02x\n",
+ msgs[i].len, msgs[i].addr);
+#endif
+ buf[0] = 0x01;
+ buf[1] = 1;
+ buf[2] = msgs[i].addr << 1;
+ buf[3] = msgs[i].len;
+ buf_len = 4;
+ } else {
+#ifdef GO7007_I2C_DEBUG
+ pr_debug("i2c write %d bytes on %02x\n",
+ msgs[i].len, msgs[i].addr);
+#endif
+ buf[0] = 0x00;
+ buf[1] = msgs[i].len + 1;
+ buf[2] = msgs[i].addr << 1;
+ memcpy(&buf[3], msgs[i].buf, msgs[i].len);
+ buf_len = msgs[i].len + 3;
+ buf[buf_len++] = 0;
+ }
+ if (go7007_usb_vendor_request(go, 0x24, 0, 0,
+ buf, buf_len, 0) < 0)
+ goto i2c_done;
+ if (msgs[i].flags & I2C_M_RD) {
+ memset(buf, 0, msgs[i].len + 1);
+ if (go7007_usb_vendor_request(go, 0x25, 0, 0, buf,
+ msgs[i].len + 1, 1) < 0)
+ goto i2c_done;
+ memcpy(msgs[i].buf, buf + 1, msgs[i].len);
+ }
+ }
+ ret = num;
+
+i2c_done:
+ mutex_unlock(&usb->i2c_lock);
+ return ret;
+}
+
+static u32 go7007_usb_functionality(struct i2c_adapter *adapter)
+{
+ /* No errors are reported by the hardware, so we don't bother
+ * supporting quick writes to avoid confusing probing */
+ return (I2C_FUNC_SMBUS_EMUL) & ~I2C_FUNC_SMBUS_QUICK;
+}
+
+static struct i2c_algorithm go7007_usb_algo = {
+ .master_xfer = go7007_usb_i2c_master_xfer,
+ .functionality = go7007_usb_functionality,
+};
+
+static struct i2c_adapter go7007_usb_adap_templ = {
+ .owner = THIS_MODULE,
+ .name = "WIS GO7007SB EZ-USB",
+ .algo = &go7007_usb_algo,
+};
+
+/********************* USB add/remove functions *********************/
+
+static int go7007_usb_probe(struct usb_interface *intf,
+ const struct usb_device_id *id)
+{
+ struct go7007 *go;
+ struct go7007_usb *usb;
+ const struct go7007_usb_board *board;
+ struct usb_device *usbdev = interface_to_usbdev(intf);
+ unsigned num_i2c_devs;
+ char *name;
+ int video_pipe, i, v_urb_len;
+
+ pr_debug("probing new GO7007 USB board\n");
+
+ switch (id->driver_info) {
+ case GO7007_BOARDID_MATRIX_II:
+ name = "WIS Matrix II or compatible";
+ board = &board_matrix_ii;
+ break;
+ case GO7007_BOARDID_MATRIX_RELOAD:
+ name = "WIS Matrix Reloaded or compatible";
+ board = &board_matrix_reload;
+ break;
+ case GO7007_BOARDID_MATRIX_REV:
+ name = "WIS Matrix Revolution or compatible";
+ board = &board_matrix_revolution;
+ break;
+ case GO7007_BOARDID_STAR_TREK:
+ name = "WIS Star Trek or compatible";
+ board = &board_star_trek;
+ break;
+ case GO7007_BOARDID_XMEN:
+ name = "WIS XMen or compatible";
+ board = &board_xmen;
+ break;
+ case GO7007_BOARDID_XMEN_II:
+ name = "WIS XMen II or compatible";
+ board = &board_xmen;
+ break;
+ case GO7007_BOARDID_XMEN_III:
+ name = "WIS XMen III or compatible";
+ board = &board_xmen;
+ break;
+ case GO7007_BOARDID_PX_M402U:
+ name = "Plextor PX-M402U";
+ board = &board_matrix_ii;
+ break;
+ case GO7007_BOARDID_PX_TV402U:
+ name = "Plextor PX-TV402U (unknown tuner)";
+ board = &board_px_tv402u;
+ break;
+ case GO7007_BOARDID_LIFEVIEW_LR192:
+ dev_err(&intf->dev, "The Lifeview TV Walker Ultra is not supported. Sorry!\n");
+ return -ENODEV;
+ name = "Lifeview TV Walker Ultra";
+ board = &board_lifeview_lr192;
+ break;
+ case GO7007_BOARDID_SENSORAY_2250:
+ dev_info(&intf->dev, "Sensoray 2250 found\n");
+ name = "Sensoray 2250/2251";
+ board = &board_sensoray_2250;
+ break;
+ case GO7007_BOARDID_ADS_USBAV_709:
+ name = "ADS Tech DVD Xpress DX2";
+ board = &board_ads_usbav_709;
+ break;
+ default:
+ dev_err(&intf->dev, "unknown board ID %d!\n",
+ (unsigned int)id->driver_info);
+ return -ENODEV;
+ }
+
+ go = go7007_alloc(&board->main_info, &intf->dev);
+ if (go == NULL)
+ return -ENOMEM;
+
+ usb = kzalloc(sizeof(struct go7007_usb), GFP_KERNEL);
+ if (usb == NULL) {
+ kfree(go);
+ return -ENOMEM;
+ }
+
+ usb->board = board;
+ usb->usbdev = usbdev;
+ usb_make_path(usbdev, go->bus_info, sizeof(go->bus_info));
+ go->board_id = id->driver_info;
+ strncpy(go->name, name, sizeof(go->name));
+ if (board->flags & GO7007_USB_EZUSB)
+ go->hpi_ops = &go7007_usb_ezusb_hpi_ops;
+ else
+ go->hpi_ops = &go7007_usb_onboard_hpi_ops;
+ go->hpi_context = usb;
+
+ /* Allocate the URB and buffer for receiving incoming interrupts */
+ usb->intr_urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (usb->intr_urb == NULL)
+ goto allocfail;
+ usb->intr_urb->transfer_buffer = kmalloc(2*sizeof(u16), GFP_KERNEL);
+ if (usb->intr_urb->transfer_buffer == NULL)
+ goto allocfail;
+
+ if (go->board_id == GO7007_BOARDID_SENSORAY_2250)
+ usb_fill_bulk_urb(usb->intr_urb, usb->usbdev,
+ usb_rcvbulkpipe(usb->usbdev, 4),
+ usb->intr_urb->transfer_buffer, 2*sizeof(u16),
+ go7007_usb_readinterrupt_complete, go);
+ else
+ usb_fill_int_urb(usb->intr_urb, usb->usbdev,
+ usb_rcvintpipe(usb->usbdev, 4),
+ usb->intr_urb->transfer_buffer, 2*sizeof(u16),
+ go7007_usb_readinterrupt_complete, go, 8);
+ usb_set_intfdata(intf, &go->v4l2_dev);
+
+ /* Boot the GO7007 */
+ if (go7007_boot_encoder(go, go->board_info->flags &
+ GO7007_BOARD_USE_ONBOARD_I2C) < 0)
+ goto allocfail;
+
+ /* Register the EZ-USB I2C adapter, if we're using it */
+ if (board->flags & GO7007_USB_EZUSB_I2C) {
+ memcpy(&go->i2c_adapter, &go7007_usb_adap_templ,
+ sizeof(go7007_usb_adap_templ));
+ mutex_init(&usb->i2c_lock);
+ go->i2c_adapter.dev.parent = go->dev;
+ i2c_set_adapdata(&go->i2c_adapter, go);
+ if (i2c_add_adapter(&go->i2c_adapter) < 0) {
+ dev_err(go->dev, "error: i2c_add_adapter failed\n");
+ goto allocfail;
+ }
+ go->i2c_adapter_online = 1;
+ }
+
+ /* Pelco and Adlink reused the XMen and XMen-III vendor and product
+ * IDs for their own incompatible designs. We can detect XMen boards
+ * by probing the sensor, but there is no way to probe the sensors on
+ * the Pelco and Adlink designs so we default to the Adlink. If it
+ * is actually a Pelco, the user must set the assume_endura module
+ * parameter. */
+ if ((go->board_id == GO7007_BOARDID_XMEN ||
+ go->board_id == GO7007_BOARDID_XMEN_III) &&
+ go->i2c_adapter_online) {
+ union i2c_smbus_data data;
+
+ /* Check to see if register 0x0A is 0x76 */
+ i2c_smbus_xfer(&go->i2c_adapter, 0x21, I2C_CLIENT_SCCB,
+ I2C_SMBUS_READ, 0x0A, I2C_SMBUS_BYTE_DATA, &data);
+ if (data.byte != 0x76) {
+ if (assume_endura) {
+ go->board_id = GO7007_BOARDID_ENDURA;
+ usb->board = board = &board_endura;
+ go->board_info = &board->main_info;
+ strncpy(go->name, "Pelco Endura",
+ sizeof(go->name));
+ } else {
+ u16 channel;
+
+ /* read channel number from GPIO[1:0] */
+ go7007_read_addr(go, 0x3c81, &channel);
+ channel &= 0x3;
+ go->board_id = GO7007_BOARDID_ADLINK_MPG24;
+ usb->board = board = &board_adlink_mpg24;
+ go->board_info = &board->main_info;
+ go->channel_number = channel;
+ snprintf(go->name, sizeof(go->name),
+ "Adlink PCI-MPG24, channel #%d",
+ channel);
+ }
+ go7007_update_board(go);
+ }
+ }
+
+ num_i2c_devs = go->board_info->num_i2c_devs;
+
+ /* Probe the tuner model on the TV402U */
+ if (go->board_id == GO7007_BOARDID_PX_TV402U) {
+ /* Board strapping indicates tuner model */
+ if (go7007_usb_vendor_request(go, 0x41, 0, 0, go->usb_buf, 3,
+ 1) < 0) {
+ dev_err(go->dev, "GPIO read failed!\n");
+ goto allocfail;
+ }
+ switch (go->usb_buf[0] >> 6) {
+ case 1:
+ go->tuner_type = TUNER_SONY_BTF_PG472Z;
+ go->std = V4L2_STD_PAL;
+ strncpy(go->name, "Plextor PX-TV402U-EU",
+ sizeof(go->name));
+ break;
+ case 2:
+ go->tuner_type = TUNER_SONY_BTF_PK467Z;
+ go->std = V4L2_STD_NTSC_M_JP;
+ num_i2c_devs -= 2;
+ strncpy(go->name, "Plextor PX-TV402U-JP",
+ sizeof(go->name));
+ break;
+ case 3:
+ go->tuner_type = TUNER_SONY_BTF_PB463Z;
+ num_i2c_devs -= 2;
+ strncpy(go->name, "Plextor PX-TV402U-NA",
+ sizeof(go->name));
+ break;
+ default:
+ pr_debug("unable to detect tuner type!\n");
+ break;
+ }
+ /* Configure tuner mode selection inputs connected
+ * to the EZ-USB GPIO output pins */
+ if (go7007_usb_vendor_request(go, 0x40, 0x7f02, 0,
+ NULL, 0, 0) < 0) {
+ dev_err(go->dev, "GPIO write failed!\n");
+ goto allocfail;
+ }
+ }
+
+ /* Print a nasty message if the user attempts to use a USB2.0 device in
+ * a USB1.1 port. There will be silent corruption of the stream. */
+ if ((board->flags & GO7007_USB_EZUSB) &&
+ usbdev->speed != USB_SPEED_HIGH)
+ dev_err(go->dev, "*** WARNING *** This device must be connected to a USB 2.0 port! Attempting to capture video through a USB 1.1 port will result in stream corruption, even at low bitrates!\n");
+
+ /* Allocate the URBs and buffers for receiving the video stream */
+ if (board->flags & GO7007_USB_EZUSB) {
+ v_urb_len = 1024;
+ video_pipe = usb_rcvbulkpipe(usb->usbdev, 6);
+ } else {
+ v_urb_len = 512;
+ video_pipe = usb_rcvbulkpipe(usb->usbdev, 1);
+ }
+ for (i = 0; i < 8; ++i) {
+ usb->video_urbs[i] = usb_alloc_urb(0, GFP_KERNEL);
+ if (usb->video_urbs[i] == NULL)
+ goto allocfail;
+ usb->video_urbs[i]->transfer_buffer =
+ kmalloc(v_urb_len, GFP_KERNEL);
+ if (usb->video_urbs[i]->transfer_buffer == NULL)
+ goto allocfail;
+ usb_fill_bulk_urb(usb->video_urbs[i], usb->usbdev, video_pipe,
+ usb->video_urbs[i]->transfer_buffer, v_urb_len,
+ go7007_usb_read_video_pipe_complete, go);
+ }
+
+ /* Allocate the URBs and buffers for receiving the audio stream */
+ if ((board->flags & GO7007_USB_EZUSB) &&
+ (board->flags & GO7007_BOARD_HAS_AUDIO)) {
+ for (i = 0; i < 8; ++i) {
+ usb->audio_urbs[i] = usb_alloc_urb(0, GFP_KERNEL);
+ if (usb->audio_urbs[i] == NULL)
+ goto allocfail;
+ usb->audio_urbs[i]->transfer_buffer = kmalloc(4096,
+ GFP_KERNEL);
+ if (usb->audio_urbs[i]->transfer_buffer == NULL)
+ goto allocfail;
+ usb_fill_bulk_urb(usb->audio_urbs[i], usb->usbdev,
+ usb_rcvbulkpipe(usb->usbdev, 8),
+ usb->audio_urbs[i]->transfer_buffer, 4096,
+ go7007_usb_read_audio_pipe_complete, go);
+ }
+ }
+
+ /* Do any final GO7007 initialization, then register the
+ * V4L2 and ALSA interfaces */
+ if (go7007_register_encoder(go, num_i2c_devs) < 0)
+ goto allocfail;
+
+ go->status = STATUS_ONLINE;
+ return 0;
+
+allocfail:
+ go7007_usb_release(go);
+ kfree(go);
+ return -ENOMEM;
+}
+
+static void go7007_usb_disconnect(struct usb_interface *intf)
+{
+ struct go7007 *go = to_go7007(usb_get_intfdata(intf));
+
+ mutex_lock(&go->queue_lock);
+ mutex_lock(&go->serialize_lock);
+
+ if (go->audio_enabled)
+ go7007_snd_remove(go);
+
+ go->status = STATUS_SHUTDOWN;
+ v4l2_device_disconnect(&go->v4l2_dev);
+ video_unregister_device(&go->vdev);
+ mutex_unlock(&go->serialize_lock);
+ mutex_unlock(&go->queue_lock);
+
+ v4l2_device_put(&go->v4l2_dev);
+}
+
+static struct usb_driver go7007_usb_driver = {
+ .name = "go7007",
+ .probe = go7007_usb_probe,
+ .disconnect = go7007_usb_disconnect,
+ .id_table = go7007_usb_id_table,
+};
+
+module_usb_driver(go7007_usb_driver);
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/staging/media/go7007/go7007-v4l2.c b/drivers/staging/media/go7007/go7007-v4l2.c
new file mode 100644
index 00000000000..da7b5493e13
--- /dev/null
+++ b/drivers/staging/media/go7007/go7007-v4l2.c
@@ -0,0 +1,1055 @@
+/*
+ * Copyright (C) 2005-2006 Micronas USA Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License (Version 2) as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA.
+ */
+
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/sched.h>
+#include <linux/spinlock.h>
+#include <linux/slab.h>
+#include <linux/fs.h>
+#include <linux/unistd.h>
+#include <linux/time.h>
+#include <linux/vmalloc.h>
+#include <linux/pagemap.h>
+#include <linux/i2c.h>
+#include <linux/mutex.h>
+#include <linux/uaccess.h>
+#include <linux/videodev2.h>
+#include <media/v4l2-common.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-subdev.h>
+#include <media/v4l2-event.h>
+#include <media/videobuf2-vmalloc.h>
+#include <media/saa7115.h>
+
+#include "go7007.h"
+#include "go7007-priv.h"
+
+#define call_all(dev, o, f, args...) \
+ v4l2_device_call_until_err(dev, 0, o, f, ##args)
+
+static bool valid_pixelformat(u32 pixelformat)
+{
+ switch (pixelformat) {
+ case V4L2_PIX_FMT_MJPEG:
+ case V4L2_PIX_FMT_MPEG1:
+ case V4L2_PIX_FMT_MPEG2:
+ case V4L2_PIX_FMT_MPEG4:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static u32 get_frame_type_flag(struct go7007_buffer *vb, int format)
+{
+ u8 *ptr = vb2_plane_vaddr(&vb->vb, 0);
+
+ switch (format) {
+ case V4L2_PIX_FMT_MJPEG:
+ return V4L2_BUF_FLAG_KEYFRAME;
+ case V4L2_PIX_FMT_MPEG4:
+ switch ((ptr[vb->frame_offset + 4] >> 6) & 0x3) {
+ case 0:
+ return V4L2_BUF_FLAG_KEYFRAME;
+ case 1:
+ return V4L2_BUF_FLAG_PFRAME;
+ case 2:
+ return V4L2_BUF_FLAG_BFRAME;
+ default:
+ return 0;
+ }
+ case V4L2_PIX_FMT_MPEG1:
+ case V4L2_PIX_FMT_MPEG2:
+ switch ((ptr[vb->frame_offset + 5] >> 3) & 0x7) {
+ case 1:
+ return V4L2_BUF_FLAG_KEYFRAME;
+ case 2:
+ return V4L2_BUF_FLAG_PFRAME;
+ case 3:
+ return V4L2_BUF_FLAG_BFRAME;
+ default:
+ return 0;
+ }
+ }
+
+ return 0;
+}
+
+static void get_resolution(struct go7007 *go, int *width, int *height)
+{
+ switch (go->standard) {
+ case GO7007_STD_NTSC:
+ *width = 720;
+ *height = 480;
+ break;
+ case GO7007_STD_PAL:
+ *width = 720;
+ *height = 576;
+ break;
+ case GO7007_STD_OTHER:
+ default:
+ *width = go->board_info->sensor_width;
+ *height = go->board_info->sensor_height;
+ break;
+ }
+}
+
+static void set_formatting(struct go7007 *go)
+{
+ if (go->format == V4L2_PIX_FMT_MJPEG) {
+ go->pali = 0;
+ go->aspect_ratio = GO7007_RATIO_1_1;
+ go->gop_size = 0;
+ go->ipb = 0;
+ go->closed_gop = 0;
+ go->repeat_seqhead = 0;
+ go->seq_header_enable = 0;
+ go->gop_header_enable = 0;
+ go->dvd_mode = 0;
+ return;
+ }
+
+ switch (go->format) {
+ case V4L2_PIX_FMT_MPEG1:
+ go->pali = 0;
+ break;
+ default:
+ case V4L2_PIX_FMT_MPEG2:
+ go->pali = 0x48;
+ break;
+ case V4L2_PIX_FMT_MPEG4:
+ /* For future reference: this is the list of MPEG4
+ * profiles that are available, although they are
+ * untested:
+ *
+ * Profile pali
+ * -------------- ----
+ * PROFILE_S_L0 0x08
+ * PROFILE_S_L1 0x01
+ * PROFILE_S_L2 0x02
+ * PROFILE_S_L3 0x03
+ * PROFILE_ARTS_L1 0x91
+ * PROFILE_ARTS_L2 0x92
+ * PROFILE_ARTS_L3 0x93
+ * PROFILE_ARTS_L4 0x94
+ * PROFILE_AS_L0 0xf0
+ * PROFILE_AS_L1 0xf1
+ * PROFILE_AS_L2 0xf2
+ * PROFILE_AS_L3 0xf3
+ * PROFILE_AS_L4 0xf4
+ * PROFILE_AS_L5 0xf5
+ */
+ go->pali = 0xf5;
+ break;
+ }
+ go->gop_size = v4l2_ctrl_g_ctrl(go->mpeg_video_gop_size);
+ go->closed_gop = v4l2_ctrl_g_ctrl(go->mpeg_video_gop_closure);
+ go->ipb = v4l2_ctrl_g_ctrl(go->mpeg_video_b_frames) != 0;
+ go->bitrate = v4l2_ctrl_g_ctrl(go->mpeg_video_bitrate);
+ go->repeat_seqhead = v4l2_ctrl_g_ctrl(go->mpeg_video_rep_seqheader);
+ go->gop_header_enable = 1;
+ go->dvd_mode = 0;
+ if (go->format == V4L2_PIX_FMT_MPEG2)
+ go->dvd_mode =
+ go->bitrate == 9800000 &&
+ go->gop_size == 15 &&
+ go->ipb == 0 &&
+ go->repeat_seqhead == 1 &&
+ go->closed_gop;
+
+ switch (v4l2_ctrl_g_ctrl(go->mpeg_video_aspect_ratio)) {
+ default:
+ case V4L2_MPEG_VIDEO_ASPECT_1x1:
+ go->aspect_ratio = GO7007_RATIO_1_1;
+ break;
+ case V4L2_MPEG_VIDEO_ASPECT_4x3:
+ go->aspect_ratio = GO7007_RATIO_4_3;
+ break;
+ case V4L2_MPEG_VIDEO_ASPECT_16x9:
+ go->aspect_ratio = GO7007_RATIO_16_9;
+ break;
+ }
+}
+
+static int set_capture_size(struct go7007 *go, struct v4l2_format *fmt, int try)
+{
+ int sensor_height = 0, sensor_width = 0;
+ int width, height, i;
+
+ if (fmt != NULL && !valid_pixelformat(fmt->fmt.pix.pixelformat))
+ return -EINVAL;
+
+ get_resolution(go, &sensor_width, &sensor_height);
+
+ if (fmt == NULL) {
+ width = sensor_width;
+ height = sensor_height;
+ } else if (go->board_info->sensor_flags & GO7007_SENSOR_SCALING) {
+ if (fmt->fmt.pix.width > sensor_width)
+ width = sensor_width;
+ else if (fmt->fmt.pix.width < 144)
+ width = 144;
+ else
+ width = fmt->fmt.pix.width & ~0x0f;
+
+ if (fmt->fmt.pix.height > sensor_height)
+ height = sensor_height;
+ else if (fmt->fmt.pix.height < 96)
+ height = 96;
+ else
+ height = fmt->fmt.pix.height & ~0x0f;
+ } else {
+ width = fmt->fmt.pix.width;
+
+ if (width <= sensor_width / 4) {
+ width = sensor_width / 4;
+ height = sensor_height / 4;
+ } else if (width <= sensor_width / 2) {
+ width = sensor_width / 2;
+ height = sensor_height / 2;
+ } else {
+ width = sensor_width;
+ height = sensor_height;
+ }
+ width &= ~0xf;
+ height &= ~0xf;
+ }
+
+ if (fmt != NULL) {
+ u32 pixelformat = fmt->fmt.pix.pixelformat;
+
+ memset(fmt, 0, sizeof(*fmt));
+ fmt->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ fmt->fmt.pix.width = width;
+ fmt->fmt.pix.height = height;
+ fmt->fmt.pix.pixelformat = pixelformat;
+ fmt->fmt.pix.field = V4L2_FIELD_NONE;
+ fmt->fmt.pix.bytesperline = 0;
+ fmt->fmt.pix.sizeimage = GO7007_BUF_SIZE;
+ fmt->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M;
+ }
+
+ if (try)
+ return 0;
+
+ if (fmt)
+ go->format = fmt->fmt.pix.pixelformat;
+ go->width = width;
+ go->height = height;
+ go->encoder_h_offset = go->board_info->sensor_h_offset;
+ go->encoder_v_offset = go->board_info->sensor_v_offset;
+ for (i = 0; i < 4; ++i)
+ go->modet[i].enable = 0;
+ for (i = 0; i < 1624; ++i)
+ go->modet_map[i] = 0;
+
+ if (go->board_info->sensor_flags & GO7007_SENSOR_SCALING) {
+ struct v4l2_mbus_framefmt mbus_fmt;
+
+ mbus_fmt.code = V4L2_MBUS_FMT_FIXED;
+ mbus_fmt.width = fmt ? fmt->fmt.pix.width : width;
+ mbus_fmt.height = height;
+ go->encoder_h_halve = 0;
+ go->encoder_v_halve = 0;
+ go->encoder_subsample = 0;
+ call_all(&go->v4l2_dev, video, s_mbus_fmt, &mbus_fmt);
+ } else {
+ if (width <= sensor_width / 4) {
+ go->encoder_h_halve = 1;
+ go->encoder_v_halve = 1;
+ go->encoder_subsample = 1;
+ } else if (width <= sensor_width / 2) {
+ go->encoder_h_halve = 1;
+ go->encoder_v_halve = 1;
+ go->encoder_subsample = 0;
+ } else {
+ go->encoder_h_halve = 0;
+ go->encoder_v_halve = 0;
+ go->encoder_subsample = 0;
+ }
+ }
+ return 0;
+}
+
+#if 0
+static int clip_to_modet_map(struct go7007 *go, int region,
+ struct v4l2_clip *clip_list)
+{
+ struct v4l2_clip clip, *clip_ptr;
+ int x, y, mbnum;
+
+ /* Check if coordinates are OK and if any macroblocks are already
+ * used by other regions (besides 0) */
+ clip_ptr = clip_list;
+ while (clip_ptr) {
+ if (copy_from_user(&clip, clip_ptr, sizeof(clip)))
+ return -EFAULT;
+ if (clip.c.left < 0 || (clip.c.left & 0xF) ||
+ clip.c.width <= 0 || (clip.c.width & 0xF))
+ return -EINVAL;
+ if (clip.c.left + clip.c.width > go->width)
+ return -EINVAL;
+ if (clip.c.top < 0 || (clip.c.top & 0xF) ||
+ clip.c.height <= 0 || (clip.c.height & 0xF))
+ return -EINVAL;
+ if (clip.c.top + clip.c.height > go->height)
+ return -EINVAL;
+ for (y = 0; y < clip.c.height; y += 16)
+ for (x = 0; x < clip.c.width; x += 16) {
+ mbnum = (go->width >> 4) *
+ ((clip.c.top + y) >> 4) +
+ ((clip.c.left + x) >> 4);
+ if (go->modet_map[mbnum] != 0 &&
+ go->modet_map[mbnum] != region)
+ return -EBUSY;
+ }
+ clip_ptr = clip.next;
+ }
+
+ /* Clear old region macroblocks */
+ for (mbnum = 0; mbnum < 1624; ++mbnum)
+ if (go->modet_map[mbnum] == region)
+ go->modet_map[mbnum] = 0;
+
+ /* Claim macroblocks in this list */
+ clip_ptr = clip_list;
+ while (clip_ptr) {
+ if (copy_from_user(&clip, clip_ptr, sizeof(clip)))
+ return -EFAULT;
+ for (y = 0; y < clip.c.height; y += 16)
+ for (x = 0; x < clip.c.width; x += 16) {
+ mbnum = (go->width >> 4) *
+ ((clip.c.top + y) >> 4) +
+ ((clip.c.left + x) >> 4);
+ go->modet_map[mbnum] = region;
+ }
+ clip_ptr = clip.next;
+ }
+ return 0;
+}
+#endif
+
+static int vidioc_querycap(struct file *file, void *priv,
+ struct v4l2_capability *cap)
+{
+ struct go7007 *go = video_drvdata(file);
+
+ strlcpy(cap->driver, "go7007", sizeof(cap->driver));
+ strlcpy(cap->card, go->name, sizeof(cap->card));
+ strlcpy(cap->bus_info, go->bus_info, sizeof(cap->bus_info));
+
+ cap->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_READWRITE |
+ V4L2_CAP_STREAMING;
+
+ if (go->board_info->num_aud_inputs)
+ cap->device_caps |= V4L2_CAP_AUDIO;
+ if (go->board_info->flags & GO7007_BOARD_HAS_TUNER)
+ cap->device_caps |= V4L2_CAP_TUNER;
+ cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS;
+ return 0;
+}
+
+static int vidioc_enum_fmt_vid_cap(struct file *file, void *priv,
+ struct v4l2_fmtdesc *fmt)
+{
+ char *desc = NULL;
+
+ switch (fmt->index) {
+ case 0:
+ fmt->pixelformat = V4L2_PIX_FMT_MJPEG;
+ desc = "Motion JPEG";
+ break;
+ case 1:
+ fmt->pixelformat = V4L2_PIX_FMT_MPEG1;
+ desc = "MPEG-1 ES";
+ break;
+ case 2:
+ fmt->pixelformat = V4L2_PIX_FMT_MPEG2;
+ desc = "MPEG-2 ES";
+ break;
+ case 3:
+ fmt->pixelformat = V4L2_PIX_FMT_MPEG4;
+ desc = "MPEG-4 ES";
+ break;
+ default:
+ return -EINVAL;
+ }
+ fmt->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ fmt->flags = V4L2_FMT_FLAG_COMPRESSED;
+
+ strncpy(fmt->description, desc, sizeof(fmt->description));
+
+ return 0;
+}
+
+static int vidioc_g_fmt_vid_cap(struct file *file, void *priv,
+ struct v4l2_format *fmt)
+{
+ struct go7007 *go = video_drvdata(file);
+
+ fmt->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ fmt->fmt.pix.width = go->width;
+ fmt->fmt.pix.height = go->height;
+ fmt->fmt.pix.pixelformat = go->format;
+ fmt->fmt.pix.field = V4L2_FIELD_NONE;
+ fmt->fmt.pix.bytesperline = 0;
+ fmt->fmt.pix.sizeimage = GO7007_BUF_SIZE;
+ fmt->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M;
+
+ return 0;
+}
+
+static int vidioc_try_fmt_vid_cap(struct file *file, void *priv,
+ struct v4l2_format *fmt)
+{
+ struct go7007 *go = video_drvdata(file);
+
+ return set_capture_size(go, fmt, 1);
+}
+
+static int vidioc_s_fmt_vid_cap(struct file *file, void *priv,
+ struct v4l2_format *fmt)
+{
+ struct go7007 *go = video_drvdata(file);
+
+ if (vb2_is_busy(&go->vidq))
+ return -EBUSY;
+
+ return set_capture_size(go, fmt, 0);
+}
+
+static int go7007_queue_setup(struct vb2_queue *q,
+ const struct v4l2_format *fmt,
+ unsigned int *num_buffers, unsigned int *num_planes,
+ unsigned int sizes[], void *alloc_ctxs[])
+{
+ sizes[0] = GO7007_BUF_SIZE;
+ *num_planes = 1;
+
+ if (*num_buffers < 2)
+ *num_buffers = 2;
+
+ return 0;
+}
+
+static void go7007_buf_queue(struct vb2_buffer *vb)
+{
+ struct vb2_queue *vq = vb->vb2_queue;
+ struct go7007 *go = vb2_get_drv_priv(vq);
+ struct go7007_buffer *go7007_vb =
+ container_of(vb, struct go7007_buffer, vb);
+ unsigned long flags;
+
+ spin_lock_irqsave(&go->spinlock, flags);
+ list_add_tail(&go7007_vb->list, &go->vidq_active);
+ spin_unlock_irqrestore(&go->spinlock, flags);
+}
+
+static int go7007_buf_prepare(struct vb2_buffer *vb)
+{
+ struct go7007_buffer *go7007_vb =
+ container_of(vb, struct go7007_buffer, vb);
+
+ go7007_vb->modet_active = 0;
+ go7007_vb->frame_offset = 0;
+ vb->v4l2_planes[0].bytesused = 0;
+ return 0;
+}
+
+static void go7007_buf_finish(struct vb2_buffer *vb)
+{
+ struct vb2_queue *vq = vb->vb2_queue;
+ struct go7007 *go = vb2_get_drv_priv(vq);
+ struct go7007_buffer *go7007_vb =
+ container_of(vb, struct go7007_buffer, vb);
+ u32 frame_type_flag = get_frame_type_flag(go7007_vb, go->format);
+ struct v4l2_buffer *buf = &vb->v4l2_buf;
+
+ buf->flags &= ~(V4L2_BUF_FLAG_KEYFRAME | V4L2_BUF_FLAG_BFRAME |
+ V4L2_BUF_FLAG_PFRAME);
+ buf->flags |= frame_type_flag;
+ buf->field = V4L2_FIELD_NONE;
+}
+
+static int go7007_start_streaming(struct vb2_queue *q, unsigned int count)
+{
+ struct go7007 *go = vb2_get_drv_priv(q);
+ int ret;
+
+ set_formatting(go);
+ mutex_lock(&go->hw_lock);
+ go->next_seq = 0;
+ go->active_buf = NULL;
+ q->streaming = 1;
+ if (go7007_start_encoder(go) < 0)
+ ret = -EIO;
+ else
+ ret = 0;
+ mutex_unlock(&go->hw_lock);
+ if (ret) {
+ q->streaming = 0;
+ return ret;
+ }
+ call_all(&go->v4l2_dev, video, s_stream, 1);
+ v4l2_ctrl_grab(go->mpeg_video_gop_size, true);
+ v4l2_ctrl_grab(go->mpeg_video_gop_closure, true);
+ v4l2_ctrl_grab(go->mpeg_video_bitrate, true);
+ v4l2_ctrl_grab(go->mpeg_video_aspect_ratio, true);
+ /* Turn on Capture LED */
+ if (go->board_id == GO7007_BOARDID_ADS_USBAV_709)
+ go7007_write_addr(go, 0x3c82, 0x0005);
+ return ret;
+}
+
+static void go7007_stop_streaming(struct vb2_queue *q)
+{
+ struct go7007 *go = vb2_get_drv_priv(q);
+ unsigned long flags;
+
+ q->streaming = 0;
+ go7007_stream_stop(go);
+ mutex_lock(&go->hw_lock);
+ go7007_reset_encoder(go);
+ mutex_unlock(&go->hw_lock);
+ call_all(&go->v4l2_dev, video, s_stream, 0);
+
+ spin_lock_irqsave(&go->spinlock, flags);
+ INIT_LIST_HEAD(&go->vidq_active);
+ spin_unlock_irqrestore(&go->spinlock, flags);
+ v4l2_ctrl_grab(go->mpeg_video_gop_size, false);
+ v4l2_ctrl_grab(go->mpeg_video_gop_closure, false);
+ v4l2_ctrl_grab(go->mpeg_video_bitrate, false);
+ v4l2_ctrl_grab(go->mpeg_video_aspect_ratio, false);
+ /* Turn on Capture LED */
+ if (go->board_id == GO7007_BOARDID_ADS_USBAV_709)
+ go7007_write_addr(go, 0x3c82, 0x000d);
+}
+
+static struct vb2_ops go7007_video_qops = {
+ .queue_setup = go7007_queue_setup,
+ .buf_queue = go7007_buf_queue,
+ .buf_prepare = go7007_buf_prepare,
+ .buf_finish = go7007_buf_finish,
+ .start_streaming = go7007_start_streaming,
+ .stop_streaming = go7007_stop_streaming,
+ .wait_prepare = vb2_ops_wait_prepare,
+ .wait_finish = vb2_ops_wait_finish,
+};
+
+static int vidioc_g_parm(struct file *filp, void *priv,
+ struct v4l2_streamparm *parm)
+{
+ struct go7007 *go = video_drvdata(filp);
+ struct v4l2_fract timeperframe = {
+ .numerator = 1001 * go->fps_scale,
+ .denominator = go->sensor_framerate,
+ };
+
+ if (parm->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+ return -EINVAL;
+
+ parm->parm.capture.readbuffers = 2;
+ parm->parm.capture.capability = V4L2_CAP_TIMEPERFRAME;
+ parm->parm.capture.timeperframe = timeperframe;
+
+ return 0;
+}
+
+static int vidioc_s_parm(struct file *filp, void *priv,
+ struct v4l2_streamparm *parm)
+{
+ struct go7007 *go = video_drvdata(filp);
+ unsigned int n, d;
+
+ if (parm->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+ return -EINVAL;
+
+ n = go->sensor_framerate *
+ parm->parm.capture.timeperframe.numerator;
+ d = 1001 * parm->parm.capture.timeperframe.denominator;
+ if (n != 0 && d != 0 && n > d)
+ go->fps_scale = (n + d/2) / d;
+ else
+ go->fps_scale = 1;
+
+ return vidioc_g_parm(filp, priv, parm);
+}
+
+/* VIDIOC_ENUMSTD on go7007 were used for enumerating the supported fps and
+ its resolution, when the device is not connected to TV.
+ This is were an API abuse, probably used by the lack of specific IOCTL's to
+ enumerate it, by the time the driver was written.
+
+ However, since kernel 2.6.19, two new ioctls (VIDIOC_ENUM_FRAMEINTERVALS
+ and VIDIOC_ENUM_FRAMESIZES) were added for this purpose.
+
+ The two functions below implement the newer ioctls
+*/
+static int vidioc_enum_framesizes(struct file *filp, void *priv,
+ struct v4l2_frmsizeenum *fsize)
+{
+ struct go7007 *go = video_drvdata(filp);
+ int width, height;
+
+ if (fsize->index > 2)
+ return -EINVAL;
+
+ if (!valid_pixelformat(fsize->pixel_format))
+ return -EINVAL;
+
+ get_resolution(go, &width, &height);
+ fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE;
+ fsize->discrete.width = (width >> fsize->index) & ~0xf;
+ fsize->discrete.height = (height >> fsize->index) & ~0xf;
+ return 0;
+}
+
+static int vidioc_enum_frameintervals(struct file *filp, void *priv,
+ struct v4l2_frmivalenum *fival)
+{
+ struct go7007 *go = video_drvdata(filp);
+ int width, height;
+ int i;
+
+ if (fival->index > 4)
+ return -EINVAL;
+
+ if (!valid_pixelformat(fival->pixel_format))
+ return -EINVAL;
+
+ if (!(go->board_info->sensor_flags & GO7007_SENSOR_SCALING)) {
+ get_resolution(go, &width, &height);
+ for (i = 0; i <= 2; i++)
+ if (fival->width == ((width >> i) & ~0xf) &&
+ fival->height == ((height >> i) & ~0xf))
+ break;
+ if (i > 2)
+ return -EINVAL;
+ }
+ fival->type = V4L2_FRMIVAL_TYPE_DISCRETE;
+ fival->discrete.numerator = 1001 * (fival->index + 1);
+ fival->discrete.denominator = go->sensor_framerate;
+ return 0;
+}
+
+static int vidioc_g_std(struct file *file, void *priv, v4l2_std_id *std)
+{
+ struct go7007 *go = video_drvdata(file);
+
+ *std = go->std;
+ return 0;
+}
+
+static int go7007_s_std(struct go7007 *go)
+{
+ if (go->std & V4L2_STD_625_50) {
+ go->standard = GO7007_STD_PAL;
+ go->sensor_framerate = 25025;
+ } else {
+ go->standard = GO7007_STD_NTSC;
+ go->sensor_framerate = 30000;
+ }
+
+ call_all(&go->v4l2_dev, video, s_std, go->std);
+ set_capture_size(go, NULL, 0);
+ return 0;
+}
+
+static int vidioc_s_std(struct file *file, void *priv, v4l2_std_id std)
+{
+ struct go7007 *go = video_drvdata(file);
+
+ if (vb2_is_busy(&go->vidq))
+ return -EBUSY;
+
+ go->std = std;
+
+ return go7007_s_std(go);
+}
+
+static int vidioc_querystd(struct file *file, void *priv, v4l2_std_id *std)
+{
+ struct go7007 *go = video_drvdata(file);
+
+ return call_all(&go->v4l2_dev, video, querystd, std);
+}
+
+static int vidioc_enum_input(struct file *file, void *priv,
+ struct v4l2_input *inp)
+{
+ struct go7007 *go = video_drvdata(file);
+
+ if (inp->index >= go->board_info->num_inputs)
+ return -EINVAL;
+
+ strncpy(inp->name, go->board_info->inputs[inp->index].name,
+ sizeof(inp->name));
+
+ /* If this board has a tuner, it will be the first input */
+ if ((go->board_info->flags & GO7007_BOARD_HAS_TUNER) &&
+ inp->index == 0)
+ inp->type = V4L2_INPUT_TYPE_TUNER;
+ else
+ inp->type = V4L2_INPUT_TYPE_CAMERA;
+
+ if (go->board_info->num_aud_inputs)
+ inp->audioset = (1 << go->board_info->num_aud_inputs) - 1;
+ else
+ inp->audioset = 0;
+ inp->tuner = 0;
+ if (go->board_info->sensor_flags & GO7007_SENSOR_TV)
+ inp->std = video_devdata(file)->tvnorms;
+ else
+ inp->std = 0;
+
+ return 0;
+}
+
+
+static int vidioc_g_input(struct file *file, void *priv, unsigned int *input)
+{
+ struct go7007 *go = video_drvdata(file);
+
+ *input = go->input;
+
+ return 0;
+}
+
+static int vidioc_enumaudio(struct file *file, void *fh, struct v4l2_audio *a)
+{
+ struct go7007 *go = video_drvdata(file);
+
+ if (a->index >= go->board_info->num_aud_inputs)
+ return -EINVAL;
+ strlcpy(a->name, go->board_info->aud_inputs[a->index].name,
+ sizeof(a->name));
+ a->capability = V4L2_AUDCAP_STEREO;
+ return 0;
+}
+
+static int vidioc_g_audio(struct file *file, void *fh, struct v4l2_audio *a)
+{
+ struct go7007 *go = video_drvdata(file);
+
+ a->index = go->aud_input;
+ strlcpy(a->name, go->board_info->aud_inputs[go->aud_input].name,
+ sizeof(a->name));
+ a->capability = V4L2_AUDCAP_STEREO;
+ return 0;
+}
+
+static int vidioc_s_audio(struct file *file, void *fh,
+ const struct v4l2_audio *a)
+{
+ struct go7007 *go = video_drvdata(file);
+
+ if (a->index >= go->board_info->num_aud_inputs)
+ return -EINVAL;
+ go->aud_input = a->index;
+ v4l2_subdev_call(go->sd_audio, audio, s_routing,
+ go->board_info->aud_inputs[go->aud_input].audio_input, 0, 0);
+ return 0;
+}
+
+static void go7007_s_input(struct go7007 *go)
+{
+ unsigned int input = go->input;
+
+ v4l2_subdev_call(go->sd_video, video, s_routing,
+ go->board_info->inputs[input].video_input, 0,
+ go->board_info->video_config);
+ if (go->board_info->num_aud_inputs) {
+ int aud_input = go->board_info->inputs[input].audio_index;
+
+ v4l2_subdev_call(go->sd_audio, audio, s_routing,
+ go->board_info->aud_inputs[aud_input].audio_input, 0, 0);
+ go->aud_input = aud_input;
+ }
+}
+
+static int vidioc_s_input(struct file *file, void *priv, unsigned int input)
+{
+ struct go7007 *go = video_drvdata(file);
+
+ if (input >= go->board_info->num_inputs)
+ return -EINVAL;
+ if (vb2_is_busy(&go->vidq))
+ return -EBUSY;
+
+ go->input = input;
+ go7007_s_input(go);
+
+ return 0;
+}
+
+static int vidioc_g_tuner(struct file *file, void *priv,
+ struct v4l2_tuner *t)
+{
+ struct go7007 *go = video_drvdata(file);
+
+ if (t->index != 0)
+ return -EINVAL;
+
+ strlcpy(t->name, "Tuner", sizeof(t->name));
+ return call_all(&go->v4l2_dev, tuner, g_tuner, t);
+}
+
+static int vidioc_s_tuner(struct file *file, void *priv,
+ const struct v4l2_tuner *t)
+{
+ struct go7007 *go = video_drvdata(file);
+
+ if (t->index != 0)
+ return -EINVAL;
+
+ return call_all(&go->v4l2_dev, tuner, s_tuner, t);
+}
+
+static int vidioc_g_frequency(struct file *file, void *priv,
+ struct v4l2_frequency *f)
+{
+ struct go7007 *go = video_drvdata(file);
+
+ if (f->tuner)
+ return -EINVAL;
+
+ return call_all(&go->v4l2_dev, tuner, g_frequency, f);
+}
+
+static int vidioc_s_frequency(struct file *file, void *priv,
+ const struct v4l2_frequency *f)
+{
+ struct go7007 *go = video_drvdata(file);
+
+ if (f->tuner)
+ return -EINVAL;
+
+ return call_all(&go->v4l2_dev, tuner, s_frequency, f);
+}
+
+static int vidioc_log_status(struct file *file, void *priv)
+{
+ struct go7007 *go = video_drvdata(file);
+
+ v4l2_ctrl_log_status(file, priv);
+ return call_all(&go->v4l2_dev, core, log_status);
+}
+
+/* FIXME:
+ Those ioctls are private, and not needed, since several standard
+ extended controls already provide streaming control.
+ So, those ioctls should be converted into vidioc_g_ext_ctrls()
+ and vidioc_s_ext_ctrls()
+ */
+
+#if 0
+ case GO7007IOC_S_MD_PARAMS:
+ {
+ struct go7007_md_params *mdp = arg;
+
+ if (mdp->region > 3)
+ return -EINVAL;
+ if (mdp->trigger > 0) {
+ go->modet[mdp->region].pixel_threshold =
+ mdp->pixel_threshold >> 1;
+ go->modet[mdp->region].motion_threshold =
+ mdp->motion_threshold >> 1;
+ go->modet[mdp->region].mb_threshold =
+ mdp->trigger >> 1;
+ go->modet[mdp->region].enable = 1;
+ } else
+ go->modet[mdp->region].enable = 0;
+ /* fall-through */
+ }
+ case GO7007IOC_S_MD_REGION:
+ {
+ struct go7007_md_region *region = arg;
+
+ if (region->region < 1 || region->region > 3)
+ return -EINVAL;
+ return clip_to_modet_map(go, region->region, region->clips);
+ }
+#endif
+
+static struct v4l2_file_operations go7007_fops = {
+ .owner = THIS_MODULE,
+ .open = v4l2_fh_open,
+ .release = vb2_fop_release,
+ .unlocked_ioctl = video_ioctl2,
+ .read = vb2_fop_read,
+ .mmap = vb2_fop_mmap,
+ .poll = vb2_fop_poll,
+};
+
+static const struct v4l2_ioctl_ops video_ioctl_ops = {
+ .vidioc_querycap = vidioc_querycap,
+ .vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_vid_cap,
+ .vidioc_g_fmt_vid_cap = vidioc_g_fmt_vid_cap,
+ .vidioc_try_fmt_vid_cap = vidioc_try_fmt_vid_cap,
+ .vidioc_s_fmt_vid_cap = vidioc_s_fmt_vid_cap,
+ .vidioc_reqbufs = vb2_ioctl_reqbufs,
+ .vidioc_querybuf = vb2_ioctl_querybuf,
+ .vidioc_qbuf = vb2_ioctl_qbuf,
+ .vidioc_dqbuf = vb2_ioctl_dqbuf,
+ .vidioc_g_std = vidioc_g_std,
+ .vidioc_s_std = vidioc_s_std,
+ .vidioc_querystd = vidioc_querystd,
+ .vidioc_enum_input = vidioc_enum_input,
+ .vidioc_g_input = vidioc_g_input,
+ .vidioc_s_input = vidioc_s_input,
+ .vidioc_enumaudio = vidioc_enumaudio,
+ .vidioc_g_audio = vidioc_g_audio,
+ .vidioc_s_audio = vidioc_s_audio,
+ .vidioc_streamon = vb2_ioctl_streamon,
+ .vidioc_streamoff = vb2_ioctl_streamoff,
+ .vidioc_g_tuner = vidioc_g_tuner,
+ .vidioc_s_tuner = vidioc_s_tuner,
+ .vidioc_g_frequency = vidioc_g_frequency,
+ .vidioc_s_frequency = vidioc_s_frequency,
+ .vidioc_g_parm = vidioc_g_parm,
+ .vidioc_s_parm = vidioc_s_parm,
+ .vidioc_enum_framesizes = vidioc_enum_framesizes,
+ .vidioc_enum_frameintervals = vidioc_enum_frameintervals,
+ .vidioc_log_status = vidioc_log_status,
+ .vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
+ .vidioc_unsubscribe_event = v4l2_event_unsubscribe,
+};
+
+static struct video_device go7007_template = {
+ .name = "go7007",
+ .fops = &go7007_fops,
+ .release = video_device_release_empty,
+ .ioctl_ops = &video_ioctl_ops,
+ .tvnorms = V4L2_STD_ALL,
+};
+
+int go7007_v4l2_ctrl_init(struct go7007 *go)
+{
+ struct v4l2_ctrl_handler *hdl = &go->hdl;
+ struct v4l2_ctrl *ctrl;
+
+ v4l2_ctrl_handler_init(hdl, 13);
+ go->mpeg_video_gop_size = v4l2_ctrl_new_std(hdl, NULL,
+ V4L2_CID_MPEG_VIDEO_GOP_SIZE, 0, 34, 1, 15);
+ go->mpeg_video_gop_closure = v4l2_ctrl_new_std(hdl, NULL,
+ V4L2_CID_MPEG_VIDEO_GOP_CLOSURE, 0, 1, 1, 1);
+ go->mpeg_video_bitrate = v4l2_ctrl_new_std(hdl, NULL,
+ V4L2_CID_MPEG_VIDEO_BITRATE,
+ 64000, 10000000, 1, 9800000);
+ go->mpeg_video_b_frames = v4l2_ctrl_new_std(hdl, NULL,
+ V4L2_CID_MPEG_VIDEO_B_FRAMES, 0, 2, 2, 0);
+ go->mpeg_video_rep_seqheader = v4l2_ctrl_new_std(hdl, NULL,
+ V4L2_CID_MPEG_VIDEO_REPEAT_SEQ_HEADER, 0, 1, 1, 1);
+
+ go->mpeg_video_aspect_ratio = v4l2_ctrl_new_std_menu(hdl, NULL,
+ V4L2_CID_MPEG_VIDEO_ASPECT,
+ V4L2_MPEG_VIDEO_ASPECT_16x9, 0,
+ V4L2_MPEG_VIDEO_ASPECT_1x1);
+ ctrl = v4l2_ctrl_new_std(hdl, NULL,
+ V4L2_CID_JPEG_ACTIVE_MARKER, 0,
+ V4L2_JPEG_ACTIVE_MARKER_DQT |
+ V4L2_JPEG_ACTIVE_MARKER_DHT, 0,
+ V4L2_JPEG_ACTIVE_MARKER_DQT |
+ V4L2_JPEG_ACTIVE_MARKER_DHT);
+ if (ctrl)
+ ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY;
+ if (hdl->error) {
+ int rv = hdl->error;
+
+ v4l2_err(&go->v4l2_dev, "Could not register controls\n");
+ return rv;
+ }
+ go->v4l2_dev.ctrl_handler = hdl;
+ return 0;
+}
+
+int go7007_v4l2_init(struct go7007 *go)
+{
+ struct video_device *vdev = &go->vdev;
+ int rv;
+
+ mutex_init(&go->serialize_lock);
+ mutex_init(&go->queue_lock);
+
+ INIT_LIST_HEAD(&go->vidq_active);
+ go->vidq.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ go->vidq.io_modes = VB2_MMAP | VB2_USERPTR | VB2_READ;
+ go->vidq.ops = &go7007_video_qops;
+ go->vidq.mem_ops = &vb2_vmalloc_memops;
+ go->vidq.drv_priv = go;
+ go->vidq.buf_struct_size = sizeof(struct go7007_buffer);
+ go->vidq.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
+ go->vidq.lock = &go->queue_lock;
+ rv = vb2_queue_init(&go->vidq);
+ if (rv)
+ return rv;
+ *vdev = go7007_template;
+ vdev->lock = &go->serialize_lock;
+ vdev->queue = &go->vidq;
+ set_bit(V4L2_FL_USE_FH_PRIO, &vdev->flags);
+ video_set_drvdata(vdev, go);
+ vdev->v4l2_dev = &go->v4l2_dev;
+ if (!v4l2_device_has_op(&go->v4l2_dev, video, querystd))
+ v4l2_disable_ioctl(vdev, VIDIOC_QUERYSTD);
+ if (!(go->board_info->flags & GO7007_BOARD_HAS_TUNER)) {
+ v4l2_disable_ioctl(vdev, VIDIOC_S_FREQUENCY);
+ v4l2_disable_ioctl(vdev, VIDIOC_G_FREQUENCY);
+ v4l2_disable_ioctl(vdev, VIDIOC_S_TUNER);
+ v4l2_disable_ioctl(vdev, VIDIOC_G_TUNER);
+ } else {
+ struct v4l2_frequency f = {
+ .type = V4L2_TUNER_ANALOG_TV,
+ .frequency = 980,
+ };
+
+ call_all(&go->v4l2_dev, tuner, s_frequency, &f);
+ }
+ if (!(go->board_info->sensor_flags & GO7007_SENSOR_TV)) {
+ v4l2_disable_ioctl(vdev, VIDIOC_G_STD);
+ v4l2_disable_ioctl(vdev, VIDIOC_S_STD);
+ vdev->tvnorms = 0;
+ }
+ if (go->board_info->sensor_flags & GO7007_SENSOR_SCALING)
+ v4l2_disable_ioctl(vdev, VIDIOC_ENUM_FRAMESIZES);
+ if (go->board_info->num_aud_inputs == 0) {
+ v4l2_disable_ioctl(vdev, VIDIOC_G_AUDIO);
+ v4l2_disable_ioctl(vdev, VIDIOC_S_AUDIO);
+ v4l2_disable_ioctl(vdev, VIDIOC_ENUMAUDIO);
+ }
+ /* Setup correct crystal frequency on this board */
+ if (go->board_info->sensor_flags & GO7007_SENSOR_SAA7115)
+ v4l2_subdev_call(go->sd_video, video, s_crystal_freq,
+ SAA7115_FREQ_24_576_MHZ,
+ SAA7115_FREQ_FL_APLL | SAA7115_FREQ_FL_UCGC |
+ SAA7115_FREQ_FL_DOUBLE_ASCLK);
+ go7007_s_input(go);
+ if (go->board_info->sensor_flags & GO7007_SENSOR_TV)
+ go7007_s_std(go);
+ rv = video_register_device(vdev, VFL_TYPE_GRABBER, -1);
+ if (rv < 0)
+ return rv;
+ dev_info(go->dev, "registered device %s [v4l2]\n",
+ video_device_node_name(vdev));
+
+ return 0;
+}
+
+void go7007_v4l2_remove(struct go7007 *go)
+{
+ v4l2_ctrl_handler_free(&go->hdl);
+}
diff --git a/drivers/staging/media/go7007/go7007.h b/drivers/staging/media/go7007/go7007.h
new file mode 100644
index 00000000000..54b98973898
--- /dev/null
+++ b/drivers/staging/media/go7007/go7007.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2005-2006 Micronas USA Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and the associated README documentation file (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+struct go7007_md_params {
+ __u16 region;
+ __u16 trigger;
+ __u16 pixel_threshold;
+ __u16 motion_threshold;
+ __u32 reserved[8];
+};
+
+struct go7007_md_region {
+ __u16 region;
+ __u16 flags;
+ struct v4l2_clip *clips;
+ __u32 reserved[8];
+};
+
+#define GO7007IOC_S_MD_PARAMS _IOWR('V', BASE_VIDIOC_PRIVATE + 6, \
+ struct go7007_md_params)
+#define GO7007IOC_G_MD_PARAMS _IOR('V', BASE_VIDIOC_PRIVATE + 7, \
+ struct go7007_md_params)
+#define GO7007IOC_S_MD_REGION _IOW('V', BASE_VIDIOC_PRIVATE + 8, \
+ struct go7007_md_region)
diff --git a/drivers/staging/media/go7007/go7007.txt b/drivers/staging/media/go7007/go7007.txt
new file mode 100644
index 00000000000..c8e5eb09d38
--- /dev/null
+++ b/drivers/staging/media/go7007/go7007.txt
@@ -0,0 +1,478 @@
+This is a driver for the WIS GO7007SB multi-format video encoder.
+
+Pete Eberlein <pete@sensoray.com>
+
+The driver was originally released under the GPL and is currently hosted at:
+http://nikosapi.org/wiki/index.php/WIS_Go7007_Linux_driver
+The go7007 firmware can be acquired from the package on the site above.
+
+I've modified the driver to support the following Video4Linux2 MPEG
+controls, with acceptable values:
+
+V4L2_CID_MPEG_STREAM_TYPE V4L2_MPEG_STREAM_TYPE_MPEG2_DVD
+ V4L2_MPEG_STREAM_TYPE_MPEG_ELEM
+V4L2_CID_MPEG_VIDEO_ENCODING V4L2_MPEG_VIDEO_ENCODING_MPEG_1
+ V4L2_MPEG_VIDEO_ENCODING_MPEG_2
+ V4L2_MPEG_VIDEO_ENCODING_MPEG_4
+V4L2_CID_MPEG_VIDEO_ASPECT V4L2_MPEG_VIDEO_ASPECT_1x1
+ V4L2_MPEG_VIDEO_ASPECT_4x3
+ V4L2_MPEG_VIDEO_ASPECT_16x9
+V4L2_CID_MPEG_VIDEO_GOP_SIZE integer
+V4L2_CID_MPEG_VIDEO_BITRATE 64000 .. 10000000
+
+These should be used instead of the non-standard GO7007 ioctls described
+below.
+
+
+The README files from the orignal package appear below:
+
+---------------------------------------------------------------------------
+ WIS GO7007SB Public Linux Driver
+---------------------------------------------------------------------------
+
+
+*** Please see the file RELEASE-NOTES for important last-minute updates ***
+
+
+ 0. OVERVIEW AND LICENSING/DISCLAIMER
+
+
+This driver kit contains Linux drivers for the WIS GO7007SB multi-format
+video encoder. Only kernel version 2.6.x is supported. The video stream
+is available through the Video4Linux2 API and the audio stream is available
+through the ALSA API (or the OSS emulation layer of the ALSA system).
+
+The files in kernel/ and hotplug/ are licensed under the GNU General Public
+License Version 2 from the Free Software Foundation. A copy of the license
+is included in the file COPYING.
+
+The example applications in apps/ and C header files in include/ are
+licensed under a permissive license included in the source files which
+allows copying, modification and redistribution for any purpose without
+attribution.
+
+The firmware files included in the firmware/ directory may be freely
+redistributed only in conjunction with this document; but modification,
+tampering and reverse engineering are prohibited.
+
+MICRONAS USA, INC., MAKES NO WARRANTIES TO ANY PERSON OR ENTITY WITH
+RESPECT TO THE SOFTWARE OR ANY DERIVATIVES THEREOF OR ANY SERVICES OR
+LICENSES AND DISCLAIMS ALL IMPLIED WARRANTIES, INCLUDING WITHOUT LIMITATION
+WARRANTIES OF MERCHANTABILITY, SUPPORT, AND FITNESS FOR A PARTICULAR
+PURPOSE AND NON-INFRINGEMENT.
+
+
+ 1. SYSTEM REQUIREMENTS
+
+
+This driver requires Linux kernel 2.6. Kernel 2.4 is not supported. Using
+kernel 2.6.10 or later is recommended, as earlier kernels are known to have
+unstable USB 2.0 support.
+
+A fully built kernel source tree must be available. Typically this will be
+linked from "/lib/modules/<KERNEL VERSION>/build" for convenience. If this
+link does not exist, an extra parameter will need to be passed to the
+`make` command.
+
+All vendor-built kernels should already be configured properly. However,
+for custom-built kernels, the following options need to be enabled in the
+kernel as built-in or modules:
+
+ CONFIG_MODULES - Enable loadable module support
+ CONFIG_FW_LOADER - Hotplug firmware loading support
+ CONFIG_I2C - I2C support
+ CONFIG_VIDEO_DEV - Video For Linux
+ CONFIG_SOUND - Sound card support
+ CONFIG_SND - Advanced Linux Sound Architecture
+ CONFIG_USB - Support for Host-side USB
+ CONFIG_USB_EHCI_HCD - EHCI HCD (USB 2.0) support
+
+Additionally, to use the example application, the following options need to
+be enabled in the ALSA section:
+
+ CONFIG_SND_MIXER_OSS - OSS Mixer API
+ CONFIG_SND_PCM_OSS - OSS PCM (digital audio) API
+
+The hotplug scripts, along with the fxload utility, must also be installed.
+These scripts can be obtained from <http://linux-hotplug.sourceforge.net/>.
+Hotplugging is used for loading firmware into the Cypruss EZ-USB chip using
+fxload and for loading firmware into the driver using the firmware agent.
+
+
+ 2. COMPILING AND INSTALLING THE DRIVER
+
+
+Most users should be able to compile the driver by simply running:
+
+ $ make
+
+in the top-level directory of the driver kit. First the kernel modules
+will be built, followed by the example applications.
+
+If the build system is unable to locate the kernel source tree for the
+currently-running kernel, or if the module should be built for a kernel
+other than the currently-running kernel, an additional parameter will need
+to be passed to make to specify the appropriate kernel source directory:
+
+ $ make KERNELSRC=/usr/src/linux-2.6.10-custom3
+
+Once the compile completes, the driver and firmware files should be
+installed by running:
+
+ $ make install
+
+The kernel modules will be placed in "/lib/modules/<KERNEL VERSION>/extra"
+and the firmware files will be placed in the appropriate hotplug firmware
+directory, usually /lib/firmware. In addition, USB maps and scripts will
+be placed in /etc/hotplug/usb to enable fxload to initialize the EZ-USB
+control chip when the device is connected.
+
+
+ 3. PAL/SECAM TUNER CONFIGURATION (TV402U-EU only)
+
+
+The PAL model of the Plextor ConvertX TV402U may require additional
+configuration to correctly select the appropriate TV frequency band and
+audio subchannel.
+
+Users with a device other than the Plextor ConvertX TV402U-EU should skip
+this section.
+
+The wide variety of PAL TV systems used in Europe requires that additional
+information about the local TV standards be passed to the driver in order
+to properly tune TV channels. The two necessary parameters are (a) the PAL
+TV band, and (b) the audio subchannel format in use.
+
+In many cases, the appropriate TV band selection is passed to the driver
+from applications. However, in some cases, the application only specifies
+that the driver should use PAL but not the specific information about the
+appropriate TV band. To work around this issue, the correct TV band may be
+specified in the "force_band" parameter to the wis-sony-tuner module:
+
+ TV band force_band
+ ------- ----------
+ PAL B/G B
+ PAL I I
+ PAL D/K D
+ SECAM L L
+
+If the "force_band" parameter is specified, the driver will ignore any TV
+band specified by applications and will always use the band provided in the
+module parameter.
+
+The other parameter that can be specified is the audio subchannel format.
+There are several stereo audio carrier systems in use, including NICAM and
+three varieties of A2. To receive audio broadcast on one of these stereo
+carriers, the "force_mpx_mode" parameter must be specified to the
+wis-sony-tuner module.
+
+ TV band Audio subcarrier force_mpx_mode
+ ------- ---------------- --------------
+ PAL B/G Mono (default) 1
+ PAL B/G A2 2
+ PAL B/G NICAM 3
+ PAL I Mono (default) 4
+ PAL I NICAM 5
+ PAL D/K Mono (default) 6
+ PAL D/K A2 (1) 7
+ PAL D/K A2 (2) 8
+ PAL D/K A2 (3) 9
+ PAL D/K NICAM 10
+ SECAM L Mono (default) 11
+ SECAM L NICAM 12
+
+If the "force_mpx_mode" parameter is not specified, the correct mono-only
+mode will be chosen based on the TV band. However, the tuner will not
+receive stereo audio or bilingual broadcasts correctly.
+
+To pass the "force_band" or "force_mpx_mode" parameters to the
+wis-sony-tuner module, the following line must be added to the modprobe
+configuration file, which varies from one Linux distribution to another.
+
+ options wis-sony-tuner force_band=B force_mpx_mode=2
+
+The above example would force the tuner to the PAL B/G TV band and receive
+stereo audio broadcasts on the A2 carrier.
+
+To verify that the configuration has been placed in the correct location,
+execute:
+
+ $ modprobe -c | grep wis-sony-tuner
+
+If the configuration line appears, then modprobe will pass the parameters
+correctly the next time the wis-sony-tuner module is loaded into the
+kernel.
+
+
+ 4. TESTING THE DRIVER
+
+
+Because few Linux applications are able to correctly capture from
+Video4Linux2 devices with only compressed formats supported, the new driver
+should be tested with the "gorecord" application in the apps/ directory.
+
+First connect a video source to the device, such as a DVD player or VCR.
+This will be captured to a file for testing the driver. If an input source
+is unavailable, a test file can still be captured, but the video will be
+black and the audio will be silent.
+
+This application will auto-detect the V4L2 and ALSA/OSS device names of the
+hardware and will record video and audio to an AVI file for a specified
+number of seconds. For example:
+
+ $ apps/gorecord -duration 60 capture.avi
+
+If this application does not successfully record an AVI file, the error
+messages produced by gorecord and recorded in the system log (usually in
+/var/log/messages) should provide information to help resolve the problem.
+
+Supplying no parameters to gorecord will cause it to probe the available
+devices and exit. Use the -help flag for usage information.
+
+
+ 5. USING THE DRIVER
+
+
+The V4L2 device implemented by the driver provides a standard compressed
+format API, within the following criteria:
+
+ * Applications that only support the original Video4Linux1 API will not
+ be able to communicate with this driver at all.
+
+ * No raw video modes are supported, so applications like xawtv that
+ expect only uncompressed video will not function.
+
+ * Supported compression formats are: Motion-JPEG, MPEG1, MPEG2 and MPEG4.
+
+ * MPEG video formats are delivered as Video Elementary Streams only.
+ Program Stream (PS), Transport Stream (TS) and Packetized Elementary
+ Stream (PES) formats are not supported.
+
+ * Video parameters such as format and input port may not be changed while
+ the encoder is active.
+
+ * The audio capture device only functions when the video encoder is
+ actively capturing video. Attempts to read from the audio device when
+ the encoder is inactive will result in an I/O error.
+
+ * The native format of the audio device is 48Khz 2-channel 16-bit
+ little-endian PCM, delivered through the ALSA system. No audio
+ compression is implemented in the hardware. ALSA may convert to other
+ uncompressed formats on the fly.
+
+The include/ directory contains a C header file describing non-standard
+features of the GO7007SB encoder, which are described below:
+
+
+ GO7007IOC_S_COMP_PARAMS, GO7007IOC_G_COMP_PARAMS
+
+ These ioctls are used to negotiate general compression parameters.
+
+ To query the current parameters, call the GO7007IOC_G_COMP_PARAMS ioctl
+ with a pointer to a struct go7007_comp_params. If the driver is not
+ set to MPEG format, the EINVAL error code will be returned.
+
+ To change the current parameters, initialize all fields of a struct
+ go7007_comp_params and call the GO7007_IOC_S_COMP_PARAMS ioctl with a
+ pointer to this structure. The driver will return the current
+ parameters with any necessary changes to conform to the limitations of
+ the hardware or current compression mode. Any or all fields can be set
+ to zero to request a reasonable default value. If the driver is not
+ set to MPEG format, the EINVAL error code will be returned. When I/O
+ is in progress, the EBUSY error code will be returned.
+
+ Fields in struct go7007_comp_params:
+
+ __u32 The maximum number of frames in each
+ gop_size Group Of Pictures; i.e. the maximum
+ number of frames minus one between
+ each key frame.
+
+ __u32 The maximum number of sequential
+ max_b_frames bidirectionally-predicted frames.
+ (B-frames are not yet supported.)
+
+ enum go7007_aspect_ratio The aspect ratio to be encoded in the
+ aspect_ratio meta-data of the compressed format.
+
+ Choices are:
+ GO7007_ASPECT_RATIO_1_1
+ GO7007_ASPECT_RATIO_4_3_NTSC
+ GO7007_ASPECT_RATIO_4_3_PAL
+ GO7007_ASPECT_RATIO_16_9_NTSC
+ GO7007_ASPECT_RATIO_16_9_PAL
+
+ __u32 Bit-wise OR of control flags (below)
+ flags
+
+ Flags in struct go7007_comp_params:
+
+ GO7007_COMP_CLOSED_GOP Only produce self-contained GOPs, used
+ to produce streams appropriate for
+ random seeking.
+
+ GO7007_COMP_OMIT_SEQ_HEADER Omit the stream sequence header.
+
+
+ GO7007IOC_S_MPEG_PARAMS, GO7007IOC_G_MPEG_PARAMS
+
+ These ioctls are used to negotiate MPEG-specific stream parameters when
+ the pixelformat has been set to V4L2_PIX_FMT_MPEG.
+
+ To query the current parameters, call the GO7007IOC_G_MPEG_PARAMS ioctl
+ with a pointer to a struct go7007_mpeg_params. If the driver is not
+ set to MPEG format, the EINVAL error code will be returned.
+
+ To change the current parameters, initialize all fields of a struct
+ go7007_mpeg_params and call the GO7007_IOC_S_MPEG_PARAMS ioctl with a
+ pointer to this structure. The driver will return the current
+ parameters with any necessary changes to conform to the limitations of
+ the hardware or selected MPEG mode. Any or all fields can be set to
+ zero to request a reasonable default value. If the driver is not set
+ to MPEG format, the EINVAL error code will be returned. When I/O is in
+ progress, the EBUSY error code will be returned.
+
+ Fields in struct go7007_mpeg_params:
+
+ enum go7007_mpeg_video_standard
+ mpeg_video_standard The MPEG video standard in which to
+ compress the video.
+
+ Choices are:
+ GO7007_MPEG_VIDEO_MPEG1
+ GO7007_MPEG_VIDEO_MPEG2
+ GO7007_MPEG_VIDEO_MPEG4
+
+ __u32 Bit-wise OR of control flags (below)
+ flags
+
+ __u32 The profile and level indication to be
+ pali stored in the sequence header. This
+ is only used as an indicator to the
+ decoder, and does not affect the MPEG
+ features used in the video stream.
+ Not valid for MPEG1.
+
+ Choices for MPEG2 are:
+ GO7007_MPEG2_PROFILE_MAIN_MAIN
+
+ Choices for MPEG4 are:
+ GO7007_MPEG4_PROFILE_S_L0
+ GO7007_MPEG4_PROFILE_S_L1
+ GO7007_MPEG4_PROFILE_S_L2
+ GO7007_MPEG4_PROFILE_S_L3
+ GO7007_MPEG4_PROFILE_ARTS_L1
+ GO7007_MPEG4_PROFILE_ARTS_L2
+ GO7007_MPEG4_PROFILE_ARTS_L3
+ GO7007_MPEG4_PROFILE_ARTS_L4
+ GO7007_MPEG4_PROFILE_AS_L0
+ GO7007_MPEG4_PROFILE_AS_L1
+ GO7007_MPEG4_PROFILE_AS_L2
+ GO7007_MPEG4_PROFILE_AS_L3
+ GO7007_MPEG4_PROFILE_AS_L4
+ GO7007_MPEG4_PROFILE_AS_L5
+
+ Flags in struct go7007_mpeg_params:
+
+ GO7007_MPEG_FORCE_DVD_MODE Force all compression parameters and
+ bitrate control settings to comply
+ with DVD MPEG2 stream requirements.
+ This overrides most compression and
+ bitrate settings!
+
+ GO7007_MPEG_OMIT_GOP_HEADER Omit the GOP header.
+
+ GO7007_MPEG_REPEAT_SEQHEADER Repeat the MPEG sequence header at
+ the start of each GOP.
+
+
+ GO7007IOC_S_BITRATE, GO7007IOC_G_BITRATE
+
+ These ioctls are used to set and query the target bitrate value for the
+ compressed video stream. The bitrate may be selected by storing the
+ target bits per second in an int and calling GO7007IOC_S_BITRATE with a
+ pointer to the int. The bitrate may be queried by calling
+ GO7007IOC_G_BITRATE with a pointer to an int where the current bitrate
+ will be stored.
+
+ Note that this is the primary means of controlling the video quality
+ for all compression modes, including V4L2_PIX_FMT_MJPEG. The
+ VIDIOC_S_JPEGCOMP ioctl is not supported.
+
+
+----------------------------------------------------------------------------
+ Installing the WIS PCI Voyager Driver
+---------------------------------------------------------------------------
+
+The WIS PCI Voyager driver requires several patches to the Linux 2.6.11.x
+kernel source tree before compiling the driver. These patches update the
+in-kernel SAA7134 driver to the newest development version and patch bugs
+in the TDA8290/TDA8275 tuner driver.
+
+The following patches must be downloaded from Gerd Knorr's website and
+applied in the order listed:
+
+ http://dl.bytesex.org/patches/2.6.11-2/i2c-tuner
+ http://dl.bytesex.org/patches/2.6.11-2/i2c-tuner2
+ http://dl.bytesex.org/patches/2.6.11-2/v4l2-api-mpeg
+ http://dl.bytesex.org/patches/2.6.11-2/saa7134-update
+
+The following patches are included with this SDK and can be applied in any
+order:
+
+ patches/2.6.11/saa7134-voyager.diff
+ patches/2.6.11/tda8275-newaddr.diff
+ patches/2.6.11/tda8290-ntsc.diff
+
+Check to make sure the CONFIG_VIDEO_SAA7134 option is enabled in the kernel
+configuration, and build and install the kernel.
+
+After rebooting into the new kernel, the GO7007 driver can be compiled and
+installed:
+
+ $ make SAA7134_BUILD=y
+ $ make install
+ $ modprobe saa7134-go7007
+
+There will be two V4L video devices associated with the PCI Voyager. The
+first device (most likely /dev/video0) provides access to the raw video
+capture mode of the SAA7133 device and is used to configure the source
+video parameters and tune the TV tuner. This device can be used with xawtv
+or other V4L(2) video software as a standard uncompressed device.
+
+The second device (most likely /dev/video1) provides access to the
+compression functions of the GO7007. It can be tested using the gorecord
+application in the apps/ directory of this SDK:
+
+ $ apps/gorecord -vdevice /dev/video1 -noaudio test.avi
+
+Currently the frame resolution is fixed at 720x480 (NTSC) or 720x576 (PAL),
+and the video standard must be specified to both the raw and the compressed
+video devices (xawtv and gorecord, for example).
+
+
+--------------------------------------------------------------------------
+RELEASE NOTES FOR WIS GO7007SB LINUX DRIVER
+---------------------------------------------------------------------------
+
+Last updated: 5 November 2005
+
+ - Release 0.9.7 includes new support for using udev to run fxload. The
+ install script should automatically detect whether the old hotplug
+ scripts or the new udev rules should be used. To force the use of
+ hotplug, run "make install USE_UDEV=n". To force the use of udev, run
+ "make install USE_UDEV=y".
+
+ - Motion detection is supported but undocumented. Try the `modet` app
+ for a demonstration of how to use the facility.
+
+ - Using USB2.0 devices such as the TV402U with USB1.1 HCDs or hubs can
+ cause buffer overruns and frame drops, even at low framerates, due to
+ inconsistency in the bitrate control mechanism.
+
+ - On devices with an SAA7115, including the Plextor ConvertX, video height
+ values of 96, 128, 160, 192, 256, 320, and 384 do not work in NTSC mode.
+ All valid heights up to 512 work correctly in PAL mode.
+
+ - The WIS Star Trek and PCI Voyager boards have no support yet for audio
+ or the TV tuner.
diff --git a/drivers/staging/media/go7007/s2250-board.c b/drivers/staging/media/go7007/s2250-board.c
new file mode 100644
index 00000000000..eaa2b0990a1
--- /dev/null
+++ b/drivers/staging/media/go7007/s2250-board.c
@@ -0,0 +1,631 @@
+/*
+ * Copyright (C) 2008 Sensoray Company Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License (Version 2) as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA.
+ */
+
+#include <linux/module.h>
+#include <linux/usb.h>
+#include <linux/i2c.h>
+#include <linux/videodev2.h>
+#include <linux/slab.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-common.h>
+#include <media/v4l2-subdev.h>
+#include "go7007-priv.h"
+
+MODULE_DESCRIPTION("Sensoray 2250/2251 i2c v4l2 subdev driver");
+MODULE_LICENSE("GPL v2");
+
+/*
+ * Note: this board has two i2c devices: a vpx3226f and a tlv320aic23b.
+ * Due to the unusual way these are accessed on this device we do not
+ * reuse the i2c drivers, but instead they are implemented in this
+ * driver. It would be nice to improve on this, though.
+ */
+
+#define TLV320_ADDRESS 0x34
+#define VPX322_ADDR_ANALOGCONTROL1 0x02
+#define VPX322_ADDR_BRIGHTNESS0 0x0127
+#define VPX322_ADDR_BRIGHTNESS1 0x0131
+#define VPX322_ADDR_CONTRAST0 0x0128
+#define VPX322_ADDR_CONTRAST1 0x0132
+#define VPX322_ADDR_HUE 0x00dc
+#define VPX322_ADDR_SAT 0x0030
+
+struct go7007_usb_board {
+ unsigned int flags;
+ struct go7007_board_info main_info;
+};
+
+struct go7007_usb {
+ struct go7007_usb_board *board;
+ struct mutex i2c_lock;
+ struct usb_device *usbdev;
+ struct urb *video_urbs[8];
+ struct urb *audio_urbs[8];
+ struct urb *intr_urb;
+};
+
+static unsigned char aud_regs[] = {
+ 0x1e, 0x00,
+ 0x00, 0x17,
+ 0x02, 0x17,
+ 0x04, 0xf9,
+ 0x06, 0xf9,
+ 0x08, 0x02,
+ 0x0a, 0x00,
+ 0x0c, 0x00,
+ 0x0a, 0x00,
+ 0x0c, 0x00,
+ 0x0e, 0x02,
+ 0x10, 0x00,
+ 0x12, 0x01,
+ 0x00, 0x00,
+};
+
+
+static unsigned char vid_regs[] = {
+ 0xF2, 0x0f,
+ 0xAA, 0x00,
+ 0xF8, 0xff,
+ 0x00, 0x00,
+};
+
+static u16 vid_regs_fp[] = {
+ 0x028, 0x067,
+ 0x120, 0x016,
+ 0x121, 0xcF2,
+ 0x122, 0x0F2,
+ 0x123, 0x00c,
+ 0x124, 0x2d0,
+ 0x125, 0x2e0,
+ 0x126, 0x004,
+ 0x128, 0x1E0,
+ 0x12A, 0x016,
+ 0x12B, 0x0F2,
+ 0x12C, 0x0F2,
+ 0x12D, 0x00c,
+ 0x12E, 0x2d0,
+ 0x12F, 0x2e0,
+ 0x130, 0x004,
+ 0x132, 0x1E0,
+ 0x140, 0x060,
+ 0x153, 0x00C,
+ 0x154, 0x200,
+ 0x150, 0x801,
+ 0x000, 0x000
+};
+
+/* PAL specific values */
+static u16 vid_regs_fp_pal[] = {
+ 0x120, 0x017,
+ 0x121, 0xd22,
+ 0x122, 0x122,
+ 0x12A, 0x017,
+ 0x12B, 0x122,
+ 0x12C, 0x122,
+ 0x140, 0x060,
+ 0x000, 0x000,
+};
+
+struct s2250 {
+ struct v4l2_subdev sd;
+ struct v4l2_ctrl_handler hdl;
+ v4l2_std_id std;
+ int input;
+ int brightness;
+ int contrast;
+ int saturation;
+ int hue;
+ int reg12b_val;
+ int audio_input;
+ struct i2c_client *audio;
+};
+
+static inline struct s2250 *to_state(struct v4l2_subdev *sd)
+{
+ return container_of(sd, struct s2250, sd);
+}
+
+/* from go7007-usb.c which is Copyright (C) 2005-2006 Micronas USA Inc.*/
+static int go7007_usb_vendor_request(struct go7007 *go, u16 request,
+ u16 value, u16 index, void *transfer_buffer, int length, int in)
+{
+ struct go7007_usb *usb = go->hpi_context;
+ int timeout = 5000;
+
+ if (in) {
+ return usb_control_msg(usb->usbdev,
+ usb_rcvctrlpipe(usb->usbdev, 0), request,
+ USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN,
+ value, index, transfer_buffer, length, timeout);
+ } else {
+ return usb_control_msg(usb->usbdev,
+ usb_sndctrlpipe(usb->usbdev, 0), request,
+ USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+ value, index, transfer_buffer, length, timeout);
+ }
+}
+/* end from go7007-usb.c which is Copyright (C) 2005-2006 Micronas USA Inc.*/
+
+static int write_reg(struct i2c_client *client, u8 reg, u8 value)
+{
+ struct go7007 *go = i2c_get_adapdata(client->adapter);
+ struct go7007_usb *usb;
+ int rc;
+ int dev_addr = client->addr << 1; /* firmware wants 8-bit address */
+ u8 *buf;
+
+ if (go == NULL)
+ return -ENODEV;
+
+ if (go->status == STATUS_SHUTDOWN)
+ return -EBUSY;
+
+ buf = kzalloc(16, GFP_KERNEL);
+ if (buf == NULL)
+ return -ENOMEM;
+
+ usb = go->hpi_context;
+ if (mutex_lock_interruptible(&usb->i2c_lock) != 0) {
+ dev_info(&client->dev, "i2c lock failed\n");
+ kfree(buf);
+ return -EINTR;
+ }
+ rc = go7007_usb_vendor_request(go, 0x55, dev_addr,
+ (reg<<8 | value),
+ buf,
+ 16, 1);
+
+ mutex_unlock(&usb->i2c_lock);
+ kfree(buf);
+ return rc;
+}
+
+static int write_reg_fp(struct i2c_client *client, u16 addr, u16 val)
+{
+ struct go7007 *go = i2c_get_adapdata(client->adapter);
+ struct go7007_usb *usb;
+ int rc;
+ u8 *buf;
+ struct s2250 *dec = i2c_get_clientdata(client);
+
+ if (go == NULL)
+ return -ENODEV;
+
+ if (go->status == STATUS_SHUTDOWN)
+ return -EBUSY;
+
+ buf = kzalloc(16, GFP_KERNEL);
+
+ if (buf == NULL)
+ return -ENOMEM;
+
+
+
+ memset(buf, 0xcd, 6);
+
+ usb = go->hpi_context;
+ if (mutex_lock_interruptible(&usb->i2c_lock) != 0) {
+ dev_info(&client->dev, "i2c lock failed\n");
+ kfree(buf);
+ return -EINTR;
+ }
+ rc = go7007_usb_vendor_request(go, 0x57, addr, val, buf, 16, 1);
+ mutex_unlock(&usb->i2c_lock);
+ if (rc < 0) {
+ kfree(buf);
+ return rc;
+ }
+
+ if (buf[0] == 0) {
+ unsigned int subaddr, val_read;
+
+ subaddr = (buf[4] << 8) + buf[5];
+ val_read = (buf[2] << 8) + buf[3];
+ kfree(buf);
+ if (val_read != val) {
+ dev_info(&client->dev, "invalid fp write %x %x\n",
+ val_read, val);
+ return -EFAULT;
+ }
+ if (subaddr != addr) {
+ dev_info(&client->dev, "invalid fp write addr %x %x\n",
+ subaddr, addr);
+ return -EFAULT;
+ }
+ } else {
+ kfree(buf);
+ return -EFAULT;
+ }
+
+ /* save last 12b value */
+ if (addr == 0x12b)
+ dec->reg12b_val = val;
+
+ return 0;
+}
+
+static int read_reg_fp(struct i2c_client *client, u16 addr, u16 *val)
+{
+ struct go7007 *go = i2c_get_adapdata(client->adapter);
+ struct go7007_usb *usb;
+ int rc;
+ u8 *buf;
+
+ if (go == NULL)
+ return -ENODEV;
+
+ if (go->status == STATUS_SHUTDOWN)
+ return -EBUSY;
+
+ buf = kzalloc(16, GFP_KERNEL);
+
+ if (buf == NULL)
+ return -ENOMEM;
+
+
+
+ memset(buf, 0xcd, 6);
+ usb = go->hpi_context;
+ if (mutex_lock_interruptible(&usb->i2c_lock) != 0) {
+ dev_info(&client->dev, "i2c lock failed\n");
+ kfree(buf);
+ return -EINTR;
+ }
+ rc = go7007_usb_vendor_request(go, 0x58, addr, 0, buf, 16, 1);
+ mutex_unlock(&usb->i2c_lock);
+ if (rc < 0) {
+ kfree(buf);
+ return rc;
+ }
+
+ *val = (buf[0] << 8) | buf[1];
+ kfree(buf);
+
+ return 0;
+}
+
+
+static int write_regs(struct i2c_client *client, u8 *regs)
+{
+ int i;
+
+ for (i = 0; !((regs[i] == 0x00) && (regs[i+1] == 0x00)); i += 2) {
+ if (write_reg(client, regs[i], regs[i+1]) < 0) {
+ dev_info(&client->dev, "failed\n");
+ return -1;
+ }
+ }
+ return 0;
+}
+
+static int write_regs_fp(struct i2c_client *client, u16 *regs)
+{
+ int i;
+
+ for (i = 0; !((regs[i] == 0x00) && (regs[i+1] == 0x00)); i += 2) {
+ if (write_reg_fp(client, regs[i], regs[i+1]) < 0) {
+ dev_info(&client->dev, "failed fp\n");
+ return -1;
+ }
+ }
+ return 0;
+}
+
+
+/* ------------------------------------------------------------------------- */
+
+static int s2250_s_video_routing(struct v4l2_subdev *sd, u32 input, u32 output,
+ u32 config)
+{
+ struct s2250 *state = to_state(sd);
+ struct i2c_client *client = v4l2_get_subdevdata(sd);
+ int vidsys;
+
+ vidsys = (state->std == V4L2_STD_NTSC) ? 0x01 : 0x00;
+ if (input == 0) {
+ /* composite */
+ write_reg_fp(client, 0x20, 0x020 | vidsys);
+ write_reg_fp(client, 0x21, 0x662);
+ write_reg_fp(client, 0x140, 0x060);
+ } else if (input == 1) {
+ /* S-Video */
+ write_reg_fp(client, 0x20, 0x040 | vidsys);
+ write_reg_fp(client, 0x21, 0x666);
+ write_reg_fp(client, 0x140, 0x060);
+ } else {
+ return -EINVAL;
+ }
+ state->input = input;
+ return 0;
+}
+
+static int s2250_s_std(struct v4l2_subdev *sd, v4l2_std_id norm)
+{
+ struct s2250 *state = to_state(sd);
+ struct i2c_client *client = v4l2_get_subdevdata(sd);
+ u16 vidsource;
+
+ vidsource = (state->input == 1) ? 0x040 : 0x020;
+ if (norm & V4L2_STD_625_50) {
+ write_regs_fp(client, vid_regs_fp);
+ write_regs_fp(client, vid_regs_fp_pal);
+ write_reg_fp(client, 0x20, vidsource);
+ } else {
+ write_regs_fp(client, vid_regs_fp);
+ write_reg_fp(client, 0x20, vidsource | 1);
+ }
+ state->std = norm;
+ return 0;
+}
+
+static int s2250_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+ struct s2250 *state = container_of(ctrl->handler, struct s2250, hdl);
+ struct i2c_client *client = v4l2_get_subdevdata(&state->sd);
+ u16 oldvalue;
+
+ switch (ctrl->id) {
+ case V4L2_CID_BRIGHTNESS:
+ read_reg_fp(client, VPX322_ADDR_BRIGHTNESS0, &oldvalue);
+ write_reg_fp(client, VPX322_ADDR_BRIGHTNESS0,
+ ctrl->val | (oldvalue & ~0xff));
+ read_reg_fp(client, VPX322_ADDR_BRIGHTNESS1, &oldvalue);
+ write_reg_fp(client, VPX322_ADDR_BRIGHTNESS1,
+ ctrl->val | (oldvalue & ~0xff));
+ write_reg_fp(client, 0x140, 0x60);
+ break;
+ case V4L2_CID_CONTRAST:
+ read_reg_fp(client, VPX322_ADDR_CONTRAST0, &oldvalue);
+ write_reg_fp(client, VPX322_ADDR_CONTRAST0,
+ ctrl->val | (oldvalue & ~0x3f));
+ read_reg_fp(client, VPX322_ADDR_CONTRAST1, &oldvalue);
+ write_reg_fp(client, VPX322_ADDR_CONTRAST1,
+ ctrl->val | (oldvalue & ~0x3f));
+ write_reg_fp(client, 0x140, 0x60);
+ break;
+ case V4L2_CID_SATURATION:
+ write_reg_fp(client, VPX322_ADDR_SAT, ctrl->val);
+ break;
+ case V4L2_CID_HUE:
+ write_reg_fp(client, VPX322_ADDR_HUE, ctrl->val);
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int s2250_s_mbus_fmt(struct v4l2_subdev *sd,
+ struct v4l2_mbus_framefmt *fmt)
+{
+ struct s2250 *state = to_state(sd);
+ struct i2c_client *client = v4l2_get_subdevdata(sd);
+
+ if (fmt->height < 640) {
+ write_reg_fp(client, 0x12b, state->reg12b_val | 0x400);
+ write_reg_fp(client, 0x140, 0x060);
+ } else {
+ write_reg_fp(client, 0x12b, state->reg12b_val & ~0x400);
+ write_reg_fp(client, 0x140, 0x060);
+ }
+ return 0;
+}
+
+static int s2250_s_audio_routing(struct v4l2_subdev *sd, u32 input, u32 output,
+ u32 config)
+{
+ struct s2250 *state = to_state(sd);
+
+ switch (input) {
+ case 0:
+ write_reg(state->audio, 0x08, 0x02); /* Line In */
+ break;
+ case 1:
+ write_reg(state->audio, 0x08, 0x04); /* Mic */
+ break;
+ case 2:
+ write_reg(state->audio, 0x08, 0x05); /* Mic Boost */
+ break;
+ default:
+ return -EINVAL;
+ }
+ state->audio_input = input;
+ return 0;
+}
+
+
+static int s2250_log_status(struct v4l2_subdev *sd)
+{
+ struct s2250 *state = to_state(sd);
+
+ v4l2_info(sd, "Standard: %s\n", state->std == V4L2_STD_NTSC ? "NTSC" :
+ state->std == V4L2_STD_PAL ? "PAL" :
+ state->std == V4L2_STD_SECAM ? "SECAM" :
+ "unknown");
+ v4l2_info(sd, "Input: %s\n", state->input == 0 ? "Composite" :
+ state->input == 1 ? "S-video" :
+ "error");
+ v4l2_info(sd, "Audio input: %s\n", state->audio_input == 0 ? "Line In" :
+ state->audio_input == 1 ? "Mic" :
+ state->audio_input == 2 ? "Mic Boost" :
+ "error");
+ return v4l2_ctrl_subdev_log_status(sd);
+}
+
+/* --------------------------------------------------------------------------*/
+
+static const struct v4l2_ctrl_ops s2250_ctrl_ops = {
+ .s_ctrl = s2250_s_ctrl,
+};
+
+static const struct v4l2_subdev_core_ops s2250_core_ops = {
+ .log_status = s2250_log_status,
+};
+
+static const struct v4l2_subdev_audio_ops s2250_audio_ops = {
+ .s_routing = s2250_s_audio_routing,
+};
+
+static const struct v4l2_subdev_video_ops s2250_video_ops = {
+ .s_std = s2250_s_std,
+ .s_routing = s2250_s_video_routing,
+ .s_mbus_fmt = s2250_s_mbus_fmt,
+};
+
+static const struct v4l2_subdev_ops s2250_ops = {
+ .core = &s2250_core_ops,
+ .audio = &s2250_audio_ops,
+ .video = &s2250_video_ops,
+};
+
+/* --------------------------------------------------------------------------*/
+
+static int s2250_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct i2c_client *audio;
+ struct i2c_adapter *adapter = client->adapter;
+ struct s2250 *state;
+ struct v4l2_subdev *sd;
+ u8 *data;
+ struct go7007 *go = i2c_get_adapdata(adapter);
+ struct go7007_usb *usb = go->hpi_context;
+
+ audio = i2c_new_dummy(adapter, TLV320_ADDRESS >> 1);
+ if (audio == NULL)
+ return -ENOMEM;
+
+ state = kzalloc(sizeof(struct s2250), GFP_KERNEL);
+ if (state == NULL) {
+ i2c_unregister_device(audio);
+ return -ENOMEM;
+ }
+
+ sd = &state->sd;
+ v4l2_i2c_subdev_init(sd, client, &s2250_ops);
+
+ v4l2_info(sd, "initializing %s at address 0x%x on %s\n",
+ "Sensoray 2250/2251", client->addr, client->adapter->name);
+
+ v4l2_ctrl_handler_init(&state->hdl, 4);
+ v4l2_ctrl_new_std(&state->hdl, &s2250_ctrl_ops,
+ V4L2_CID_BRIGHTNESS, -128, 127, 1, 0);
+ v4l2_ctrl_new_std(&state->hdl, &s2250_ctrl_ops,
+ V4L2_CID_CONTRAST, 0, 0x3f, 1, 0x32);
+ v4l2_ctrl_new_std(&state->hdl, &s2250_ctrl_ops,
+ V4L2_CID_SATURATION, 0, 4094, 1, 2070);
+ v4l2_ctrl_new_std(&state->hdl, &s2250_ctrl_ops,
+ V4L2_CID_HUE, -512, 511, 1, 0);
+ sd->ctrl_handler = &state->hdl;
+ if (state->hdl.error) {
+ int err = state->hdl.error;
+
+ v4l2_ctrl_handler_free(&state->hdl);
+ kfree(state);
+ return err;
+ }
+
+ state->std = V4L2_STD_NTSC;
+ state->brightness = 50;
+ state->contrast = 50;
+ state->saturation = 50;
+ state->hue = 0;
+ state->audio = audio;
+
+ /* initialize the audio */
+ if (write_regs(audio, aud_regs) < 0) {
+ dev_err(&client->dev, "error initializing audio\n");
+ goto fail;
+ }
+
+ if (write_regs(client, vid_regs) < 0) {
+ dev_err(&client->dev, "error initializing decoder\n");
+ goto fail;
+ }
+ if (write_regs_fp(client, vid_regs_fp) < 0) {
+ dev_err(&client->dev, "error initializing decoder\n");
+ goto fail;
+ }
+ /* set default channel */
+ /* composite */
+ write_reg_fp(client, 0x20, 0x020 | 1);
+ write_reg_fp(client, 0x21, 0x662);
+ write_reg_fp(client, 0x140, 0x060);
+
+ /* set default audio input */
+ state->audio_input = 0;
+ write_reg(client, 0x08, 0x02); /* Line In */
+
+ if (mutex_lock_interruptible(&usb->i2c_lock) == 0) {
+ data = kzalloc(16, GFP_KERNEL);
+ if (data != NULL) {
+ int rc;
+ rc = go7007_usb_vendor_request(go, 0x41, 0, 0,
+ data, 16, 1);
+ if (rc > 0) {
+ u8 mask;
+ data[0] = 0;
+ mask = 1<<5;
+ data[0] &= ~mask;
+ data[1] |= mask;
+ go7007_usb_vendor_request(go, 0x40, 0,
+ (data[1]<<8)
+ + data[1],
+ data, 16, 0);
+ }
+ kfree(data);
+ }
+ mutex_unlock(&usb->i2c_lock);
+ }
+
+ v4l2_info(sd, "initialized successfully\n");
+ return 0;
+
+fail:
+ i2c_unregister_device(audio);
+ v4l2_ctrl_handler_free(&state->hdl);
+ kfree(state);
+ return -EIO;
+}
+
+static int s2250_remove(struct i2c_client *client)
+{
+ struct s2250 *state = to_state(i2c_get_clientdata(client));
+
+ v4l2_device_unregister_subdev(&state->sd);
+ v4l2_ctrl_handler_free(&state->hdl);
+ kfree(state);
+ return 0;
+}
+
+static const struct i2c_device_id s2250_id[] = {
+ { "s2250", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, s2250_id);
+
+static struct i2c_driver s2250_driver = {
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "s2250",
+ },
+ .probe = s2250_probe,
+ .remove = s2250_remove,
+ .id_table = s2250_id,
+};
+
+module_i2c_driver(s2250_driver);
diff --git a/drivers/staging/media/go7007/saa7134-go7007.c b/drivers/staging/media/go7007/saa7134-go7007.c
new file mode 100644
index 00000000000..e40f7fbfc0a
--- /dev/null
+++ b/drivers/staging/media/go7007/saa7134-go7007.c
@@ -0,0 +1,567 @@
+/*
+ * Copyright (C) 2005-2006 Micronas USA Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License (Version 2) as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA.
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/spinlock.h>
+#include <linux/wait.h>
+#include <linux/list.h>
+#include <linux/slab.h>
+#include <linux/time.h>
+#include <linux/mm.h>
+#include <linux/usb.h>
+#include <linux/i2c.h>
+#include <asm/byteorder.h>
+#include <media/v4l2-common.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-subdev.h>
+
+#include "saa7134.h"
+#include "saa7134-reg.h"
+#include "go7007.h"
+#include "go7007-priv.h"
+
+/*#define GO7007_HPI_DEBUG*/
+
+enum hpi_address {
+ HPI_ADDR_VIDEO_BUFFER = 0xe4,
+ HPI_ADDR_INIT_BUFFER = 0xea,
+ HPI_ADDR_INTR_RET_VALUE = 0xee,
+ HPI_ADDR_INTR_RET_DATA = 0xec,
+ HPI_ADDR_INTR_STATUS = 0xf4,
+ HPI_ADDR_INTR_WR_PARAM = 0xf6,
+ HPI_ADDR_INTR_WR_INDEX = 0xf8,
+};
+
+enum gpio_command {
+ GPIO_COMMAND_RESET = 0x00, /* 000b */
+ GPIO_COMMAND_REQ1 = 0x04, /* 001b */
+ GPIO_COMMAND_WRITE = 0x20, /* 010b */
+ GPIO_COMMAND_REQ2 = 0x24, /* 011b */
+ GPIO_COMMAND_READ = 0x80, /* 100b */
+ GPIO_COMMAND_VIDEO = 0x84, /* 101b */
+ GPIO_COMMAND_IDLE = 0xA0, /* 110b */
+ GPIO_COMMAND_ADDR = 0xA4, /* 111b */
+};
+
+struct saa7134_go7007 {
+ struct v4l2_subdev sd;
+ struct saa7134_dev *dev;
+ u8 *top;
+ u8 *bottom;
+ dma_addr_t top_dma;
+ dma_addr_t bottom_dma;
+};
+
+static inline struct saa7134_go7007 *to_state(struct v4l2_subdev *sd)
+{
+ return container_of(sd, struct saa7134_go7007, sd);
+}
+
+static const struct go7007_board_info board_voyager = {
+ .flags = 0,
+ .sensor_flags = GO7007_SENSOR_656 |
+ GO7007_SENSOR_VALID_ENABLE |
+ GO7007_SENSOR_TV |
+ GO7007_SENSOR_VBI,
+ .audio_flags = GO7007_AUDIO_I2S_MODE_1 |
+ GO7007_AUDIO_WORD_16,
+ .audio_rate = 48000,
+ .audio_bclk_div = 8,
+ .audio_main_div = 2,
+ .hpi_buffer_cap = 7,
+ .num_inputs = 1,
+ .inputs = {
+ {
+ .name = "SAA7134",
+ },
+ },
+};
+
+/********************* Driver for GPIO HPI interface *********************/
+
+static int gpio_write(struct saa7134_dev *dev, u8 addr, u16 data)
+{
+ saa_writeb(SAA7134_GPIO_GPMODE0, 0xff);
+
+ /* Write HPI address */
+ saa_writeb(SAA7134_GPIO_GPSTATUS0, addr);
+ saa_writeb(SAA7134_GPIO_GPSTATUS2, GPIO_COMMAND_ADDR);
+ saa_writeb(SAA7134_GPIO_GPSTATUS2, GPIO_COMMAND_IDLE);
+
+ /* Write low byte */
+ saa_writeb(SAA7134_GPIO_GPSTATUS0, data & 0xff);
+ saa_writeb(SAA7134_GPIO_GPSTATUS2, GPIO_COMMAND_WRITE);
+ saa_writeb(SAA7134_GPIO_GPSTATUS2, GPIO_COMMAND_IDLE);
+
+ /* Write high byte */
+ saa_writeb(SAA7134_GPIO_GPSTATUS0, data >> 8);
+ saa_writeb(SAA7134_GPIO_GPSTATUS2, GPIO_COMMAND_WRITE);
+ saa_writeb(SAA7134_GPIO_GPSTATUS2, GPIO_COMMAND_IDLE);
+
+ return 0;
+}
+
+static int gpio_read(struct saa7134_dev *dev, u8 addr, u16 *data)
+{
+ saa_writeb(SAA7134_GPIO_GPMODE0, 0xff);
+
+ /* Write HPI address */
+ saa_writeb(SAA7134_GPIO_GPSTATUS0, addr);
+ saa_writeb(SAA7134_GPIO_GPSTATUS2, GPIO_COMMAND_ADDR);
+ saa_writeb(SAA7134_GPIO_GPSTATUS2, GPIO_COMMAND_IDLE);
+
+ saa_writeb(SAA7134_GPIO_GPMODE0, 0x00);
+
+ /* Read low byte */
+ saa_writeb(SAA7134_GPIO_GPSTATUS2, GPIO_COMMAND_READ);
+ saa_clearb(SAA7134_GPIO_GPMODE3, SAA7134_GPIO_GPRESCAN);
+ saa_setb(SAA7134_GPIO_GPMODE3, SAA7134_GPIO_GPRESCAN);
+ *data = saa_readb(SAA7134_GPIO_GPSTATUS0);
+ saa_writeb(SAA7134_GPIO_GPSTATUS2, GPIO_COMMAND_IDLE);
+
+ /* Read high byte */
+ saa_writeb(SAA7134_GPIO_GPSTATUS2, GPIO_COMMAND_READ);
+ saa_clearb(SAA7134_GPIO_GPMODE3, SAA7134_GPIO_GPRESCAN);
+ saa_setb(SAA7134_GPIO_GPMODE3, SAA7134_GPIO_GPRESCAN);
+ *data |= saa_readb(SAA7134_GPIO_GPSTATUS0) << 8;
+ saa_writeb(SAA7134_GPIO_GPSTATUS2, GPIO_COMMAND_IDLE);
+
+ return 0;
+}
+
+static int saa7134_go7007_interface_reset(struct go7007 *go)
+{
+ struct saa7134_go7007 *saa = go->hpi_context;
+ struct saa7134_dev *dev = saa->dev;
+ u32 status;
+ u16 intr_val, intr_data;
+ int count = 20;
+
+ saa_clearb(SAA7134_TS_PARALLEL, 0x80); /* Disable TS interface */
+ saa_writeb(SAA7134_GPIO_GPMODE2, 0xa4);
+ saa_writeb(SAA7134_GPIO_GPMODE0, 0xff);
+
+ saa_writeb(SAA7134_GPIO_GPSTATUS2, GPIO_COMMAND_REQ1);
+ saa_writeb(SAA7134_GPIO_GPSTATUS2, GPIO_COMMAND_RESET);
+ msleep(1);
+ saa_writeb(SAA7134_GPIO_GPSTATUS2, GPIO_COMMAND_REQ1);
+ saa_writeb(SAA7134_GPIO_GPSTATUS2, GPIO_COMMAND_REQ2);
+ msleep(10);
+
+ saa_clearb(SAA7134_GPIO_GPMODE3, SAA7134_GPIO_GPRESCAN);
+ saa_setb(SAA7134_GPIO_GPMODE3, SAA7134_GPIO_GPRESCAN);
+
+ status = saa_readb(SAA7134_GPIO_GPSTATUS2);
+ /*printk(KERN_DEBUG "status is %s\n", status & 0x40 ? "OK" : "not OK"); */
+
+ /* enter command mode...(?) */
+ saa_writeb(SAA7134_GPIO_GPSTATUS2, GPIO_COMMAND_REQ1);
+ saa_writeb(SAA7134_GPIO_GPSTATUS2, GPIO_COMMAND_REQ2);
+
+ do {
+ saa_clearb(SAA7134_GPIO_GPMODE3, SAA7134_GPIO_GPRESCAN);
+ saa_setb(SAA7134_GPIO_GPMODE3, SAA7134_GPIO_GPRESCAN);
+ status = saa_readb(SAA7134_GPIO_GPSTATUS2);
+ /*printk(KERN_INFO "gpio is %08x\n", saa_readl(SAA7134_GPIO_GPSTATUS0 >> 2)); */
+ } while (--count > 0);
+
+ /* Wait for an interrupt to indicate successful hardware reset */
+ if (go7007_read_interrupt(go, &intr_val, &intr_data) < 0 ||
+ (intr_val & ~0x1) != 0x55aa) {
+ printk(KERN_ERR
+ "saa7134-go7007: unable to reset the GO7007\n");
+ return -1;
+ }
+ return 0;
+}
+
+static int saa7134_go7007_write_interrupt(struct go7007 *go, int addr, int data)
+{
+ struct saa7134_go7007 *saa = go->hpi_context;
+ struct saa7134_dev *dev = saa->dev;
+ int i;
+ u16 status_reg;
+
+#ifdef GO7007_HPI_DEBUG
+ printk(KERN_DEBUG
+ "saa7134-go7007: WriteInterrupt: %04x %04x\n", addr, data);
+#endif
+
+ for (i = 0; i < 100; ++i) {
+ gpio_read(dev, HPI_ADDR_INTR_STATUS, &status_reg);
+ if (!(status_reg & 0x0010))
+ break;
+ msleep(10);
+ }
+ if (i == 100) {
+ printk(KERN_ERR
+ "saa7134-go7007: device is hung, status reg = 0x%04x\n",
+ status_reg);
+ return -1;
+ }
+ gpio_write(dev, HPI_ADDR_INTR_WR_PARAM, data);
+ gpio_write(dev, HPI_ADDR_INTR_WR_INDEX, addr);
+
+ return 0;
+}
+
+static int saa7134_go7007_read_interrupt(struct go7007 *go)
+{
+ struct saa7134_go7007 *saa = go->hpi_context;
+ struct saa7134_dev *dev = saa->dev;
+
+ /* XXX we need to wait if there is no interrupt available */
+ go->interrupt_available = 1;
+ gpio_read(dev, HPI_ADDR_INTR_RET_VALUE, &go->interrupt_value);
+ gpio_read(dev, HPI_ADDR_INTR_RET_DATA, &go->interrupt_data);
+#ifdef GO7007_HPI_DEBUG
+ printk(KERN_DEBUG "saa7134-go7007: ReadInterrupt: %04x %04x\n",
+ go->interrupt_value, go->interrupt_data);
+#endif
+ return 0;
+}
+
+static void saa7134_go7007_irq_ts_done(struct saa7134_dev *dev,
+ unsigned long status)
+{
+ struct go7007 *go = video_get_drvdata(dev->empress_dev);
+ struct saa7134_go7007 *saa = go->hpi_context;
+
+ if (!vb2_is_streaming(&go->vidq))
+ return;
+ if (0 != (status & 0x000f0000))
+ printk(KERN_DEBUG "saa7134-go7007: irq: lost %ld\n",
+ (status >> 16) & 0x0f);
+ if (status & 0x100000) {
+ dma_sync_single_for_cpu(&dev->pci->dev,
+ saa->bottom_dma, PAGE_SIZE, DMA_FROM_DEVICE);
+ go7007_parse_video_stream(go, saa->bottom, PAGE_SIZE);
+ saa_writel(SAA7134_RS_BA2(5), cpu_to_le32(saa->bottom_dma));
+ } else {
+ dma_sync_single_for_cpu(&dev->pci->dev,
+ saa->top_dma, PAGE_SIZE, DMA_FROM_DEVICE);
+ go7007_parse_video_stream(go, saa->top, PAGE_SIZE);
+ saa_writel(SAA7134_RS_BA1(5), cpu_to_le32(saa->top_dma));
+ }
+}
+
+static int saa7134_go7007_stream_start(struct go7007 *go)
+{
+ struct saa7134_go7007 *saa = go->hpi_context;
+ struct saa7134_dev *dev = saa->dev;
+
+ saa->top_dma = dma_map_page(&dev->pci->dev, virt_to_page(saa->top),
+ 0, PAGE_SIZE, DMA_FROM_DEVICE);
+ if (dma_mapping_error(&dev->pci->dev, saa->top_dma))
+ return -ENOMEM;
+ saa->bottom_dma = dma_map_page(&dev->pci->dev,
+ virt_to_page(saa->bottom),
+ 0, PAGE_SIZE, DMA_FROM_DEVICE);
+ if (dma_mapping_error(&dev->pci->dev, saa->bottom_dma)) {
+ dma_unmap_page(&dev->pci->dev, saa->top_dma, PAGE_SIZE,
+ DMA_FROM_DEVICE);
+ return -ENOMEM;
+ }
+
+ saa_writel(SAA7134_VIDEO_PORT_CTRL0 >> 2, 0xA300B000);
+ saa_writel(SAA7134_VIDEO_PORT_CTRL4 >> 2, 0x40000200);
+
+ /* Set HPI interface for video */
+ saa_writeb(SAA7134_GPIO_GPMODE0, 0xff);
+ saa_writeb(SAA7134_GPIO_GPSTATUS0, HPI_ADDR_VIDEO_BUFFER);
+ saa_writeb(SAA7134_GPIO_GPSTATUS2, GPIO_COMMAND_ADDR);
+ saa_writeb(SAA7134_GPIO_GPMODE0, 0x00);
+
+ /* Enable TS interface */
+ saa_writeb(SAA7134_TS_PARALLEL, 0xe6);
+
+ /* Reset TS interface */
+ saa_setb(SAA7134_TS_SERIAL1, 0x01);
+ saa_clearb(SAA7134_TS_SERIAL1, 0x01);
+
+ /* Set up transfer block size */
+ saa_writeb(SAA7134_TS_PARALLEL_SERIAL, 128 - 1);
+ saa_writeb(SAA7134_TS_DMA0, (PAGE_SIZE >> 7) - 1);
+ saa_writeb(SAA7134_TS_DMA1, 0);
+ saa_writeb(SAA7134_TS_DMA2, 0);
+
+ /* Enable video streaming mode */
+ saa_writeb(SAA7134_GPIO_GPSTATUS2, GPIO_COMMAND_VIDEO);
+
+ saa_writel(SAA7134_RS_BA1(5), cpu_to_le32(saa->top_dma));
+ saa_writel(SAA7134_RS_BA2(5), cpu_to_le32(saa->bottom_dma));
+ saa_writel(SAA7134_RS_PITCH(5), 128);
+ saa_writel(SAA7134_RS_CONTROL(5), SAA7134_RS_CONTROL_BURST_MAX);
+
+ /* Enable TS FIFO */
+ saa_setl(SAA7134_MAIN_CTRL, SAA7134_MAIN_CTRL_TE5);
+
+ /* Enable DMA IRQ */
+ saa_setl(SAA7134_IRQ1,
+ SAA7134_IRQ1_INTE_RA2_1 | SAA7134_IRQ1_INTE_RA2_0);
+
+ return 0;
+}
+
+static int saa7134_go7007_stream_stop(struct go7007 *go)
+{
+ struct saa7134_go7007 *saa = go->hpi_context;
+ struct saa7134_dev *dev;
+
+ if (!saa)
+ return -EINVAL;
+ dev = saa->dev;
+ if (!dev)
+ return -EINVAL;
+
+ /* Shut down TS FIFO */
+ saa_clearl(SAA7134_MAIN_CTRL, SAA7134_MAIN_CTRL_TE5);
+
+ /* Disable DMA IRQ */
+ saa_clearl(SAA7134_IRQ1,
+ SAA7134_IRQ1_INTE_RA2_1 | SAA7134_IRQ1_INTE_RA2_0);
+
+ /* Disable TS interface */
+ saa_clearb(SAA7134_TS_PARALLEL, 0x80);
+
+ dma_unmap_page(&dev->pci->dev, saa->top_dma, PAGE_SIZE,
+ DMA_FROM_DEVICE);
+ dma_unmap_page(&dev->pci->dev, saa->bottom_dma, PAGE_SIZE,
+ DMA_FROM_DEVICE);
+
+ return 0;
+}
+
+static int saa7134_go7007_send_firmware(struct go7007 *go, u8 *data, int len)
+{
+ struct saa7134_go7007 *saa = go->hpi_context;
+ struct saa7134_dev *dev = saa->dev;
+ u16 status_reg;
+ int i;
+
+#ifdef GO7007_HPI_DEBUG
+ printk(KERN_DEBUG "saa7134-go7007: DownloadBuffer "
+ "sending %d bytes\n", len);
+#endif
+
+ while (len > 0) {
+ i = len > 64 ? 64 : len;
+ saa_writeb(SAA7134_GPIO_GPMODE0, 0xff);
+ saa_writeb(SAA7134_GPIO_GPSTATUS0, HPI_ADDR_INIT_BUFFER);
+ saa_writeb(SAA7134_GPIO_GPSTATUS2, GPIO_COMMAND_ADDR);
+ saa_writeb(SAA7134_GPIO_GPSTATUS2, GPIO_COMMAND_IDLE);
+ while (i-- > 0) {
+ saa_writeb(SAA7134_GPIO_GPSTATUS0, *data);
+ saa_writeb(SAA7134_GPIO_GPSTATUS2, GPIO_COMMAND_WRITE);
+ saa_writeb(SAA7134_GPIO_GPSTATUS2, GPIO_COMMAND_IDLE);
+ ++data;
+ --len;
+ }
+ for (i = 0; i < 100; ++i) {
+ gpio_read(dev, HPI_ADDR_INTR_STATUS, &status_reg);
+ if (!(status_reg & 0x0002))
+ break;
+ }
+ if (i == 100) {
+ printk(KERN_ERR "saa7134-go7007: device is hung, "
+ "status reg = 0x%04x\n", status_reg);
+ return -1;
+ }
+ }
+ return 0;
+}
+
+static struct go7007_hpi_ops saa7134_go7007_hpi_ops = {
+ .interface_reset = saa7134_go7007_interface_reset,
+ .write_interrupt = saa7134_go7007_write_interrupt,
+ .read_interrupt = saa7134_go7007_read_interrupt,
+ .stream_start = saa7134_go7007_stream_start,
+ .stream_stop = saa7134_go7007_stream_stop,
+ .send_firmware = saa7134_go7007_send_firmware,
+};
+MODULE_FIRMWARE("go7007/go7007tv.bin");
+
+/* --------------------------------------------------------------------------*/
+
+static int saa7134_go7007_s_std(struct v4l2_subdev *sd, v4l2_std_id norm)
+{
+ struct saa7134_go7007 *saa = to_state(sd);
+ struct saa7134_dev *dev = saa->dev;
+
+ return saa7134_s_std_internal(dev, NULL, norm);
+}
+
+static int saa7134_go7007_queryctrl(struct v4l2_subdev *sd,
+ struct v4l2_queryctrl *query)
+{
+ return saa7134_queryctrl(NULL, NULL, query);
+}
+static int saa7134_go7007_s_ctrl(struct v4l2_subdev *sd,
+ struct v4l2_control *ctrl)
+{
+ struct saa7134_go7007 *saa = to_state(sd);
+ struct saa7134_dev *dev = saa->dev;
+ return saa7134_s_ctrl_internal(dev, NULL, ctrl);
+}
+
+static int saa7134_go7007_g_ctrl(struct v4l2_subdev *sd,
+ struct v4l2_control *ctrl)
+{
+ struct saa7134_go7007 *saa = to_state(sd);
+ struct saa7134_dev *dev = saa->dev;
+ return saa7134_g_ctrl_internal(dev, NULL, ctrl);
+}
+
+/* --------------------------------------------------------------------------*/
+
+static const struct v4l2_subdev_core_ops saa7134_go7007_core_ops = {
+ .g_ctrl = saa7134_go7007_g_ctrl,
+ .s_ctrl = saa7134_go7007_s_ctrl,
+ .queryctrl = saa7134_go7007_queryctrl,
+};
+
+static const struct v4l2_subdev_video_ops saa7134_go7007_video_ops = {
+ .s_std = saa7134_go7007_s_std,
+};
+
+static const struct v4l2_subdev_ops saa7134_go7007_sd_ops = {
+ .core = &saa7134_go7007_core_ops,
+ .video = &saa7134_go7007_video_ops,
+};
+
+/* --------------------------------------------------------------------------*/
+
+
+/********************* Add/remove functions *********************/
+
+static int saa7134_go7007_init(struct saa7134_dev *dev)
+{
+ struct go7007 *go;
+ struct saa7134_go7007 *saa;
+ struct v4l2_subdev *sd;
+
+ printk(KERN_DEBUG "saa7134-go7007: probing new SAA713X board\n");
+
+ go = go7007_alloc(&board_voyager, &dev->pci->dev);
+ if (go == NULL)
+ return -ENOMEM;
+
+ saa = kzalloc(sizeof(struct saa7134_go7007), GFP_KERNEL);
+ if (saa == NULL) {
+ kfree(go);
+ return -ENOMEM;
+ }
+
+ go->board_id = GO7007_BOARDID_PCI_VOYAGER;
+ snprintf(go->bus_info, sizeof(go->bus_info), "PCI:%s", pci_name(dev->pci));
+ strlcpy(go->name, saa7134_boards[dev->board].name, sizeof(go->name));
+ go->hpi_ops = &saa7134_go7007_hpi_ops;
+ go->hpi_context = saa;
+ saa->dev = dev;
+
+ /* Init the subdevice interface */
+ sd = &saa->sd;
+ v4l2_subdev_init(sd, &saa7134_go7007_sd_ops);
+ v4l2_set_subdevdata(sd, saa);
+ strncpy(sd->name, "saa7134-go7007", sizeof(sd->name));
+
+ /* Allocate a couple pages for receiving the compressed stream */
+ saa->top = (u8 *)get_zeroed_page(GFP_KERNEL);
+ if (!saa->top)
+ goto allocfail;
+ saa->bottom = (u8 *)get_zeroed_page(GFP_KERNEL);
+ if (!saa->bottom)
+ goto allocfail;
+
+ /* Boot the GO7007 */
+ if (go7007_boot_encoder(go, go->board_info->flags &
+ GO7007_BOARD_USE_ONBOARD_I2C) < 0)
+ goto allocfail;
+
+ /* Do any final GO7007 initialization, then register the
+ * V4L2 and ALSA interfaces */
+ if (go7007_register_encoder(go, go->board_info->num_i2c_devs) < 0)
+ goto allocfail;
+
+ /* Register the subdevice interface with the go7007 device */
+ if (v4l2_device_register_subdev(&go->v4l2_dev, sd) < 0)
+ printk(KERN_INFO "saa7134-go7007: register subdev failed\n");
+
+ dev->empress_dev = &go->vdev;
+
+ go->status = STATUS_ONLINE;
+ return 0;
+
+allocfail:
+ if (saa->top)
+ free_page((unsigned long)saa->top);
+ if (saa->bottom)
+ free_page((unsigned long)saa->bottom);
+ kfree(saa);
+ kfree(go);
+ return -ENOMEM;
+}
+
+static int saa7134_go7007_fini(struct saa7134_dev *dev)
+{
+ struct go7007 *go;
+ struct saa7134_go7007 *saa;
+
+ if (NULL == dev->empress_dev)
+ return 0;
+
+ go = video_get_drvdata(dev->empress_dev);
+ if (go->audio_enabled)
+ go7007_snd_remove(go);
+
+ saa = go->hpi_context;
+ go->status = STATUS_SHUTDOWN;
+ free_page((unsigned long)saa->top);
+ free_page((unsigned long)saa->bottom);
+ v4l2_device_unregister_subdev(&saa->sd);
+ kfree(saa);
+ video_unregister_device(&go->vdev);
+
+ v4l2_device_put(&go->v4l2_dev);
+ dev->empress_dev = NULL;
+
+ return 0;
+}
+
+static struct saa7134_mpeg_ops saa7134_go7007_ops = {
+ .type = SAA7134_MPEG_GO7007,
+ .init = saa7134_go7007_init,
+ .fini = saa7134_go7007_fini,
+ .irq_ts_done = saa7134_go7007_irq_ts_done,
+};
+
+static int __init saa7134_go7007_mod_init(void)
+{
+ return saa7134_ts_register(&saa7134_go7007_ops);
+}
+
+static void __exit saa7134_go7007_mod_cleanup(void)
+{
+ saa7134_ts_unregister(&saa7134_go7007_ops);
+}
+
+module_init(saa7134_go7007_mod_init);
+module_exit(saa7134_go7007_mod_cleanup);
+
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/staging/media/go7007/snd-go7007.c b/drivers/staging/media/go7007/snd-go7007.c
new file mode 100644
index 00000000000..9eb2a20ae40
--- /dev/null
+++ b/drivers/staging/media/go7007/snd-go7007.c
@@ -0,0 +1,302 @@
+/*
+ * Copyright (C) 2005-2006 Micronas USA Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License (Version 2) as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/spinlock.h>
+#include <linux/delay.h>
+#include <linux/sched.h>
+#include <linux/vmalloc.h>
+#include <linux/time.h>
+#include <linux/mm.h>
+#include <linux/i2c.h>
+#include <linux/mutex.h>
+#include <linux/uaccess.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/initval.h>
+
+#include "go7007-priv.h"
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;
+static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;
+
+module_param_array(index, int, NULL, 0444);
+module_param_array(id, charp, NULL, 0444);
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for the go7007 audio driver");
+MODULE_PARM_DESC(id, "ID string for the go7007 audio driver");
+MODULE_PARM_DESC(enable, "Enable for the go7007 audio driver");
+
+struct go7007_snd {
+ struct snd_card *card;
+ struct snd_pcm *pcm;
+ struct snd_pcm_substream *substream;
+ spinlock_t lock;
+ int w_idx;
+ int hw_ptr;
+ int avail;
+ int capturing;
+};
+
+static struct snd_pcm_hardware go7007_snd_capture_hw = {
+ .info = (SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_MMAP_VALID),
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ .rates = SNDRV_PCM_RATE_48000,
+ .rate_min = 48000,
+ .rate_max = 48000,
+ .channels_min = 2,
+ .channels_max = 2,
+ .buffer_bytes_max = (128*1024),
+ .period_bytes_min = 4096,
+ .period_bytes_max = (128*1024),
+ .periods_min = 1,
+ .periods_max = 32,
+};
+
+static void parse_audio_stream_data(struct go7007 *go, u8 *buf, int length)
+{
+ struct go7007_snd *gosnd = go->snd_context;
+ struct snd_pcm_runtime *runtime = gosnd->substream->runtime;
+ int frames = bytes_to_frames(runtime, length);
+
+ spin_lock(&gosnd->lock);
+ gosnd->hw_ptr += frames;
+ if (gosnd->hw_ptr >= runtime->buffer_size)
+ gosnd->hw_ptr -= runtime->buffer_size;
+ gosnd->avail += frames;
+ spin_unlock(&gosnd->lock);
+ if (gosnd->w_idx + length > runtime->dma_bytes) {
+ int cpy = runtime->dma_bytes - gosnd->w_idx;
+
+ memcpy(runtime->dma_area + gosnd->w_idx, buf, cpy);
+ length -= cpy;
+ buf += cpy;
+ gosnd->w_idx = 0;
+ }
+ memcpy(runtime->dma_area + gosnd->w_idx, buf, length);
+ gosnd->w_idx += length;
+ spin_lock(&gosnd->lock);
+ if (gosnd->avail < runtime->period_size) {
+ spin_unlock(&gosnd->lock);
+ return;
+ }
+ gosnd->avail -= runtime->period_size;
+ spin_unlock(&gosnd->lock);
+ if (gosnd->capturing)
+ snd_pcm_period_elapsed(gosnd->substream);
+}
+
+static int go7007_snd_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *hw_params)
+{
+ struct go7007 *go = snd_pcm_substream_chip(substream);
+ unsigned int bytes;
+
+ bytes = params_buffer_bytes(hw_params);
+ if (substream->runtime->dma_bytes > 0)
+ vfree(substream->runtime->dma_area);
+ substream->runtime->dma_bytes = 0;
+ substream->runtime->dma_area = vmalloc(bytes);
+ if (substream->runtime->dma_area == NULL)
+ return -ENOMEM;
+ substream->runtime->dma_bytes = bytes;
+ go->audio_deliver = parse_audio_stream_data;
+ return 0;
+}
+
+static int go7007_snd_hw_free(struct snd_pcm_substream *substream)
+{
+ struct go7007 *go = snd_pcm_substream_chip(substream);
+
+ go->audio_deliver = NULL;
+ if (substream->runtime->dma_bytes > 0)
+ vfree(substream->runtime->dma_area);
+ substream->runtime->dma_bytes = 0;
+ return 0;
+}
+
+static int go7007_snd_capture_open(struct snd_pcm_substream *substream)
+{
+ struct go7007 *go = snd_pcm_substream_chip(substream);
+ struct go7007_snd *gosnd = go->snd_context;
+ unsigned long flags;
+ int r;
+
+ spin_lock_irqsave(&gosnd->lock, flags);
+ if (gosnd->substream == NULL) {
+ gosnd->substream = substream;
+ substream->runtime->hw = go7007_snd_capture_hw;
+ r = 0;
+ } else
+ r = -EBUSY;
+ spin_unlock_irqrestore(&gosnd->lock, flags);
+ return r;
+}
+
+static int go7007_snd_capture_close(struct snd_pcm_substream *substream)
+{
+ struct go7007 *go = snd_pcm_substream_chip(substream);
+ struct go7007_snd *gosnd = go->snd_context;
+
+ gosnd->substream = NULL;
+ return 0;
+}
+
+static int go7007_snd_pcm_prepare(struct snd_pcm_substream *substream)
+{
+ return 0;
+}
+
+static int go7007_snd_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct go7007 *go = snd_pcm_substream_chip(substream);
+ struct go7007_snd *gosnd = go->snd_context;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ /* Just set a flag to indicate we should signal ALSA when
+ * sound comes in */
+ gosnd->capturing = 1;
+ return 0;
+ case SNDRV_PCM_TRIGGER_STOP:
+ gosnd->hw_ptr = gosnd->w_idx = gosnd->avail = 0;
+ gosnd->capturing = 0;
+ return 0;
+ default:
+ return -EINVAL;
+ }
+}
+
+static snd_pcm_uframes_t go7007_snd_pcm_pointer(struct snd_pcm_substream *substream)
+{
+ struct go7007 *go = snd_pcm_substream_chip(substream);
+ struct go7007_snd *gosnd = go->snd_context;
+
+ return gosnd->hw_ptr;
+}
+
+static struct page *go7007_snd_pcm_page(struct snd_pcm_substream *substream,
+ unsigned long offset)
+{
+ return vmalloc_to_page(substream->runtime->dma_area + offset);
+}
+
+static struct snd_pcm_ops go7007_snd_capture_ops = {
+ .open = go7007_snd_capture_open,
+ .close = go7007_snd_capture_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = go7007_snd_hw_params,
+ .hw_free = go7007_snd_hw_free,
+ .prepare = go7007_snd_pcm_prepare,
+ .trigger = go7007_snd_pcm_trigger,
+ .pointer = go7007_snd_pcm_pointer,
+ .page = go7007_snd_pcm_page,
+};
+
+static int go7007_snd_free(struct snd_device *device)
+{
+ struct go7007 *go = device->device_data;
+
+ kfree(go->snd_context);
+ go->snd_context = NULL;
+ return 0;
+}
+
+static struct snd_device_ops go7007_snd_device_ops = {
+ .dev_free = go7007_snd_free,
+};
+
+int go7007_snd_init(struct go7007 *go)
+{
+ static int dev;
+ struct go7007_snd *gosnd;
+ int ret = 0;
+
+ if (dev >= SNDRV_CARDS)
+ return -ENODEV;
+ if (!enable[dev]) {
+ dev++;
+ return -ENOENT;
+ }
+ gosnd = kmalloc(sizeof(struct go7007_snd), GFP_KERNEL);
+ if (gosnd == NULL)
+ return -ENOMEM;
+ spin_lock_init(&gosnd->lock);
+ gosnd->hw_ptr = gosnd->w_idx = gosnd->avail = 0;
+ gosnd->capturing = 0;
+ ret = snd_card_new(go->dev, index[dev], id[dev], THIS_MODULE, 0,
+ &gosnd->card);
+ if (ret < 0) {
+ kfree(gosnd);
+ return ret;
+ }
+ ret = snd_device_new(gosnd->card, SNDRV_DEV_LOWLEVEL, go,
+ &go7007_snd_device_ops);
+ if (ret < 0) {
+ kfree(gosnd);
+ return ret;
+ }
+ ret = snd_pcm_new(gosnd->card, "go7007", 0, 0, 1, &gosnd->pcm);
+ if (ret < 0) {
+ snd_card_free(gosnd->card);
+ kfree(gosnd);
+ return ret;
+ }
+ strlcpy(gosnd->card->driver, "go7007", sizeof(gosnd->card->driver));
+ strlcpy(gosnd->card->shortname, go->name, sizeof(gosnd->card->driver));
+ strlcpy(gosnd->card->longname, gosnd->card->shortname,
+ sizeof(gosnd->card->longname));
+
+ gosnd->pcm->private_data = go;
+ snd_pcm_set_ops(gosnd->pcm, SNDRV_PCM_STREAM_CAPTURE,
+ &go7007_snd_capture_ops);
+
+ ret = snd_card_register(gosnd->card);
+ if (ret < 0) {
+ snd_card_free(gosnd->card);
+ kfree(gosnd);
+ return ret;
+ }
+
+ gosnd->substream = NULL;
+ go->snd_context = gosnd;
+ v4l2_device_get(&go->v4l2_dev);
+ ++dev;
+
+ return 0;
+}
+EXPORT_SYMBOL(go7007_snd_init);
+
+int go7007_snd_remove(struct go7007 *go)
+{
+ struct go7007_snd *gosnd = go->snd_context;
+
+ snd_card_disconnect(gosnd->card);
+ snd_card_free_when_closed(gosnd->card);
+ v4l2_device_put(&go->v4l2_dev);
+ return 0;
+}
+EXPORT_SYMBOL(go7007_snd_remove);
+
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/staging/media/lirc/Kconfig b/drivers/staging/media/lirc/Kconfig
new file mode 100644
index 00000000000..e60a59fc252
--- /dev/null
+++ b/drivers/staging/media/lirc/Kconfig
@@ -0,0 +1,72 @@
+#
+# LIRC driver(s) configuration
+#
+menuconfig LIRC_STAGING
+ bool "Linux Infrared Remote Control IR receiver/transmitter drivers"
+ depends on LIRC
+ help
+ Say Y here, and all supported Linux Infrared Remote Control IR and
+ RF receiver and transmitter drivers will be displayed. When paired
+ with a remote control and the lirc daemon, the receiver drivers
+ allow control of your Linux system via remote control.
+
+if LIRC_STAGING
+
+config LIRC_BT829
+ tristate "BT829 based hardware"
+ depends on LIRC && PCI
+ help
+ Driver for the IR interface on BT829-based hardware
+
+config LIRC_IGORPLUGUSB
+ tristate "Igor Cesko's USB IR Receiver"
+ depends on LIRC && USB
+ help
+ Driver for Igor Cesko's USB IR Receiver
+
+config LIRC_IMON
+ tristate "Legacy SoundGraph iMON Receiver and Display"
+ depends on LIRC && USB
+ help
+ Driver for the original SoundGraph iMON IR Receiver and Display
+
+ Current generation iMON devices use the input layer imon driver.
+
+config LIRC_PARALLEL
+ tristate "Homebrew Parallel Port Receiver"
+ depends on LIRC && PARPORT
+ help
+ Driver for Homebrew Parallel Port Receivers
+
+config LIRC_SASEM
+ tristate "Sasem USB IR Remote"
+ depends on LIRC && USB
+ help
+ Driver for the Sasem OnAir Remocon-V or Dign HV5 HTPC IR/VFD Module
+
+config LIRC_SERIAL
+ tristate "Homebrew Serial Port Receiver"
+ depends on LIRC
+ help
+ Driver for Homebrew Serial Port Receivers
+
+config LIRC_SERIAL_TRANSMITTER
+ bool "Serial Port Transmitter"
+ default y
+ depends on LIRC_SERIAL
+ help
+ Serial Port Transmitter support
+
+config LIRC_SIR
+ tristate "Built-in SIR IrDA port"
+ depends on LIRC
+ help
+ Driver for the SIR IrDA port
+
+config LIRC_ZILOG
+ tristate "Zilog/Hauppauge IR Transmitter"
+ depends on LIRC && I2C
+ help
+ Driver for the Zilog/Hauppauge IR Transmitter, found on
+ PVR-150/500, HVR-1200/1250/1700/1800, HD-PVR and other cards
+endif
diff --git a/drivers/staging/media/lirc/Makefile b/drivers/staging/media/lirc/Makefile
new file mode 100644
index 00000000000..b90fcabddab
--- /dev/null
+++ b/drivers/staging/media/lirc/Makefile
@@ -0,0 +1,13 @@
+# Makefile for the lirc drivers.
+#
+
+# Each configuration option enables a list of files.
+
+obj-$(CONFIG_LIRC_BT829) += lirc_bt829.o
+obj-$(CONFIG_LIRC_IGORPLUGUSB) += lirc_igorplugusb.o
+obj-$(CONFIG_LIRC_IMON) += lirc_imon.o
+obj-$(CONFIG_LIRC_PARALLEL) += lirc_parallel.o
+obj-$(CONFIG_LIRC_SASEM) += lirc_sasem.o
+obj-$(CONFIG_LIRC_SERIAL) += lirc_serial.o
+obj-$(CONFIG_LIRC_SIR) += lirc_sir.o
+obj-$(CONFIG_LIRC_ZILOG) += lirc_zilog.o
diff --git a/drivers/staging/media/lirc/TODO b/drivers/staging/media/lirc/TODO
new file mode 100644
index 00000000000..cbea5d84fed
--- /dev/null
+++ b/drivers/staging/media/lirc/TODO
@@ -0,0 +1,13 @@
+- All drivers should either be ported to ir-core, or dropped entirely
+ (see drivers/media/IR/mceusb.c vs. lirc_mceusb.c in lirc cvs for an
+ example of a previously completed port).
+
+- lirc_bt829 uses registers on a Mach64 VT, which has a separate kernel
+ framebuffer driver (atyfb) and userland X driver (mach64). It can't
+ simply be converted to a normal PCI driver, but ideally it should be
+ coordinated with the other drivers.
+
+Please send patches to:
+Jarod Wilson <jarod@wilsonet.com>
+Greg Kroah-Hartman <greg@kroah.com>
+
diff --git a/drivers/staging/media/lirc/TODO.lirc_zilog b/drivers/staging/media/lirc/TODO.lirc_zilog
new file mode 100644
index 00000000000..a97800a8e12
--- /dev/null
+++ b/drivers/staging/media/lirc/TODO.lirc_zilog
@@ -0,0 +1,36 @@
+1. Both ir-kbd-i2c and lirc_zilog provide support for RX events for
+the chips supported by lirc_zilog. Before moving lirc_zilog out of staging:
+
+a. ir-kbd-i2c needs a module parameter added to allow the user to tell
+ ir-kbd-i2c to ignore Z8 IR units.
+
+b. lirc_zilog should provide Rx key presses to the rc core like ir-kbd-i2c
+ does.
+
+
+2. lirc_zilog module ref-counting need examination. It has not been
+verified that cdev and lirc_dev will take the proper module references on
+lirc_zilog to prevent removal of lirc_zilog when the /dev/lircN device node
+is open.
+
+(The good news is ref-counting of lirc_zilog internal structures appears to be
+complete. Testing has shown the cx18 module can be unloaded out from under
+irw + lircd + lirc_dev, with the /dev/lirc0 device node open, with no adverse
+effects. The cx18 module could then be reloaded and irw properly began
+receiving button presses again and ir_send worked without error.)
+
+
+3. Bridge drivers, if able, should provide a chip reset() callback
+to lirc_zilog via struct IR_i2c_init_data. cx18 and ivtv already have routines
+to perform Z8 chip resets via GPIO manipulations. This would allow lirc_zilog
+to bring the chip back to normal when it hangs, in the same places the
+original lirc_pvr150 driver code does. This is not strictly needed, so it
+is not required to move lirc_zilog out of staging.
+
+Note: Both lirc_zilog and ir-kbd-i2c support the Zilog Z8 for IR, as programmed
+and installed on Hauppauge products. When working on either module, developers
+must consider at least the following bridge drivers which mention an IR Rx unit
+at address 0x71 (indicative of a Z8):
+
+ ivtv cx18 hdpvr pvrusb2 bt8xx cx88 saa7134
+
diff --git a/drivers/staging/media/lirc/lirc_bt829.c b/drivers/staging/media/lirc/lirc_bt829.c
new file mode 100644
index 00000000000..fe010542ab4
--- /dev/null
+++ b/drivers/staging/media/lirc/lirc_bt829.c
@@ -0,0 +1,405 @@
+/*
+ * Remote control driver for the TV-card based on bt829
+ *
+ * by Leonid Froenchenko <lfroen@galileo.co.il>
+ *
+ * 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
+*/
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/threads.h>
+#include <linux/sched.h>
+#include <linux/ioport.h>
+#include <linux/pci.h>
+#include <linux/delay.h>
+
+#include <media/lirc_dev.h>
+
+static int poll_main(void);
+static int atir_init_start(void);
+
+static void write_index(unsigned char index, unsigned int value);
+static unsigned int read_index(unsigned char index);
+
+static void do_i2c_start(void);
+static void do_i2c_stop(void);
+
+static void seems_wr_byte(unsigned char al);
+static unsigned char seems_rd_byte(void);
+
+static unsigned int read_index(unsigned char al);
+static void write_index(unsigned char ah, unsigned int edx);
+
+static void cycle_delay(int cycle);
+
+static void do_set_bits(unsigned char bl);
+static unsigned char do_get_bits(void);
+
+#define DATA_PCI_OFF 0x7FFC00
+#define WAIT_CYCLE 20
+
+#define DRIVER_NAME "lirc_bt829"
+
+static bool debug;
+#define dprintk(fmt, args...) \
+ do { \
+ if (debug) \
+ printk(KERN_DEBUG DRIVER_NAME ": "fmt, ## args); \
+ } while (0)
+
+static int atir_minor;
+static phys_addr_t pci_addr_phys;
+static unsigned char __iomem *pci_addr_lin;
+
+static struct lirc_driver atir_driver;
+
+static struct pci_dev *do_pci_probe(void)
+{
+ struct pci_dev *my_dev;
+ my_dev = pci_get_device(PCI_VENDOR_ID_ATI,
+ PCI_DEVICE_ID_ATI_264VT, NULL);
+ if (my_dev) {
+ pr_err("Using device: %s\n", pci_name(my_dev));
+ pci_addr_phys = 0;
+ if (my_dev->resource[0].flags & IORESOURCE_MEM) {
+ pci_addr_phys = my_dev->resource[0].start;
+ pr_info("memory at %pa\n", &pci_addr_phys);
+ }
+ if (pci_addr_phys == 0) {
+ pr_err("no memory resource ?\n");
+ pci_dev_put(my_dev);
+ return NULL;
+ }
+ } else {
+ pr_err("pci_probe failed\n");
+ return NULL;
+ }
+ return my_dev;
+}
+
+static int atir_add_to_buf(void *data, struct lirc_buffer *buf)
+{
+ unsigned char key;
+ int status;
+ status = poll_main();
+ key = (status >> 8) & 0xFF;
+ if (status & 0xFF) {
+ dprintk("reading key %02X\n", key);
+ lirc_buffer_write(buf, &key);
+ return 0;
+ }
+ return -ENODATA;
+}
+
+static int atir_set_use_inc(void *data)
+{
+ dprintk("driver is opened\n");
+ return 0;
+}
+
+static void atir_set_use_dec(void *data)
+{
+ dprintk("driver is closed\n");
+}
+
+int init_module(void)
+{
+ struct pci_dev *pdev;
+ int rc;
+
+ pdev = do_pci_probe();
+ if (pdev == NULL)
+ return -ENODEV;
+
+ rc = pci_enable_device(pdev);
+ if (rc)
+ goto err_put_dev;
+
+ if (!atir_init_start()) {
+ rc = -ENODEV;
+ goto err_disable;
+ }
+
+ strcpy(atir_driver.name, "ATIR");
+ atir_driver.minor = -1;
+ atir_driver.code_length = 8;
+ atir_driver.sample_rate = 10;
+ atir_driver.data = NULL;
+ atir_driver.add_to_buf = atir_add_to_buf;
+ atir_driver.set_use_inc = atir_set_use_inc;
+ atir_driver.set_use_dec = atir_set_use_dec;
+ atir_driver.dev = &pdev->dev;
+ atir_driver.owner = THIS_MODULE;
+
+ atir_minor = lirc_register_driver(&atir_driver);
+ if (atir_minor < 0) {
+ pr_err("failed to register driver!\n");
+ rc = atir_minor;
+ goto err_unmap;
+ }
+ dprintk("driver is registered on minor %d\n", atir_minor);
+
+ return 0;
+
+err_unmap:
+ iounmap(pci_addr_lin);
+err_disable:
+ pci_disable_device(pdev);
+err_put_dev:
+ pci_dev_put(pdev);
+ return rc;
+}
+
+
+void cleanup_module(void)
+{
+ struct pci_dev *pdev = to_pci_dev(atir_driver.dev);
+
+ lirc_unregister_driver(atir_minor);
+ iounmap(pci_addr_lin);
+ pci_disable_device(pdev);
+ pci_dev_put(pdev);
+}
+
+
+static int atir_init_start(void)
+{
+ pci_addr_lin = ioremap(pci_addr_phys + DATA_PCI_OFF, 0x400);
+ if (!pci_addr_lin) {
+ pr_info("pci mem must be mapped\n");
+ return 0;
+ }
+ return 1;
+}
+
+static void cycle_delay(int cycle)
+{
+ udelay(WAIT_CYCLE*cycle);
+}
+
+
+static int poll_main(void)
+{
+ unsigned char status_high, status_low;
+
+ do_i2c_start();
+
+ seems_wr_byte(0xAA);
+ seems_wr_byte(0x01);
+
+ do_i2c_start();
+
+ seems_wr_byte(0xAB);
+
+ status_low = seems_rd_byte();
+ status_high = seems_rd_byte();
+
+ do_i2c_stop();
+
+ return (status_high << 8) | status_low;
+}
+
+static void do_i2c_start(void)
+{
+ do_set_bits(3);
+ cycle_delay(4);
+
+ do_set_bits(1);
+ cycle_delay(7);
+
+ do_set_bits(0);
+ cycle_delay(2);
+}
+
+static void do_i2c_stop(void)
+{
+ unsigned char bits;
+ bits = do_get_bits() & 0xFD;
+ do_set_bits(bits);
+ cycle_delay(1);
+
+ bits |= 1;
+ do_set_bits(bits);
+ cycle_delay(2);
+
+ bits |= 2;
+ do_set_bits(bits);
+ bits = 3;
+ do_set_bits(bits);
+ cycle_delay(2);
+}
+
+static void seems_wr_byte(unsigned char value)
+{
+ int i;
+ unsigned char reg;
+
+ reg = do_get_bits();
+ for (i = 0; i < 8; i++) {
+ if (value & 0x80)
+ reg |= 0x02;
+ else
+ reg &= 0xFD;
+
+ do_set_bits(reg);
+ cycle_delay(1);
+
+ reg |= 1;
+ do_set_bits(reg);
+ cycle_delay(1);
+
+ reg &= 0xFE;
+ do_set_bits(reg);
+ cycle_delay(1);
+ value <<= 1;
+ }
+ cycle_delay(2);
+
+ reg |= 2;
+ do_set_bits(reg);
+
+ reg |= 1;
+ do_set_bits(reg);
+
+ cycle_delay(1);
+ do_get_bits();
+
+ reg &= 0xFE;
+ do_set_bits(reg);
+ cycle_delay(3);
+}
+
+static unsigned char seems_rd_byte(void)
+{
+ int i;
+ int rd_byte;
+ unsigned char bits_2, bits_1;
+
+ bits_1 = do_get_bits() | 2;
+ do_set_bits(bits_1);
+
+ rd_byte = 0;
+ for (i = 0; i < 8; i++) {
+ bits_1 &= 0xFE;
+ do_set_bits(bits_1);
+ cycle_delay(2);
+
+ bits_1 |= 1;
+ do_set_bits(bits_1);
+ cycle_delay(1);
+
+ bits_2 = do_get_bits();
+ if (bits_2 & 2)
+ rd_byte |= 1;
+
+ rd_byte <<= 1;
+ }
+
+ bits_1 = 0;
+ if (bits_2 == 0)
+ bits_1 |= 2;
+
+ do_set_bits(bits_1);
+ cycle_delay(2);
+
+ bits_1 |= 1;
+ do_set_bits(bits_1);
+ cycle_delay(3);
+
+ bits_1 &= 0xFE;
+ do_set_bits(bits_1);
+ cycle_delay(2);
+
+ rd_byte >>= 1;
+ rd_byte &= 0xFF;
+ return rd_byte;
+}
+
+static void do_set_bits(unsigned char new_bits)
+{
+ int reg_val;
+ reg_val = read_index(0x34);
+ if (new_bits & 2) {
+ reg_val &= 0xFFFFFFDF;
+ reg_val |= 1;
+ } else {
+ reg_val &= 0xFFFFFFFE;
+ reg_val |= 0x20;
+ }
+ reg_val |= 0x10;
+ write_index(0x34, reg_val);
+
+ reg_val = read_index(0x31);
+ if (new_bits & 1)
+ reg_val |= 0x1000000;
+ else
+ reg_val &= 0xFEFFFFFF;
+
+ reg_val |= 0x8000000;
+ write_index(0x31, reg_val);
+}
+
+static unsigned char do_get_bits(void)
+{
+ unsigned char bits;
+ int reg_val;
+
+ reg_val = read_index(0x34);
+ reg_val |= 0x10;
+ reg_val &= 0xFFFFFFDF;
+ write_index(0x34, reg_val);
+
+ reg_val = read_index(0x34);
+ bits = 0;
+ if (reg_val & 8)
+ bits |= 2;
+ else
+ bits &= 0xFD;
+
+ reg_val = read_index(0x31);
+ if (reg_val & 0x1000000)
+ bits |= 1;
+ else
+ bits &= 0xFE;
+
+ return bits;
+}
+
+static unsigned int read_index(unsigned char index)
+{
+ unsigned char __iomem *addr;
+ unsigned int value;
+ /* addr = pci_addr_lin + DATA_PCI_OFF + ((index & 0xFF) << 2); */
+ addr = pci_addr_lin + ((index & 0xFF) << 2);
+ value = readl(addr);
+ return value;
+}
+
+static void write_index(unsigned char index, unsigned int reg_val)
+{
+ unsigned char __iomem *addr;
+ addr = pci_addr_lin + ((index & 0xFF) << 2);
+ writel(reg_val, addr);
+}
+
+MODULE_AUTHOR("Froenchenko Leonid");
+MODULE_DESCRIPTION("IR remote driver for bt829 based TV cards");
+MODULE_LICENSE("GPL");
+
+module_param(debug, bool, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(debug, "Debug enabled or not");
diff --git a/drivers/staging/media/lirc/lirc_igorplugusb.c b/drivers/staging/media/lirc/lirc_igorplugusb.c
new file mode 100644
index 00000000000..44b0d076608
--- /dev/null
+++ b/drivers/staging/media/lirc/lirc_igorplugusb.c
@@ -0,0 +1,514 @@
+/*
+ * lirc_igorplugusb - USB remote support for LIRC
+ *
+ * Supports the standard homebrew IgorPlugUSB receiver with Igor's firmware.
+ * See http://www.cesko.host.sk/IgorPlugUSB/IgorPlug-USB%20(AVR)_eng.htm
+ *
+ * The device can only record bursts of up to 36 pulses/spaces.
+ * Works fine with RC5. Longer commands lead to device buffer overrun.
+ * (Maybe a better firmware or a microcontroller with more ram can help?)
+ *
+ * Version 0.1 [beta status]
+ *
+ * Copyright (C) 2004 Jan M. Hochstein
+ * <hochstein@algo.informatik.tu-darmstadt.de>
+ *
+ * This driver was derived from:
+ * Paul Miller <pmiller9@users.sourceforge.net>
+ * "lirc_atiusb" module
+ * Vladimir Dergachev <volodya@minspring.com>'s 2002
+ * "USB ATI Remote support" (input device)
+ * Adrian Dewhurst <sailor-lk@sailorfrag.net>'s 2002
+ * "USB StreamZap remote driver" (LIRC)
+ * Artur Lipowski <alipowski@kki.net.pl>'s 2002
+ * "lirc_dev" and "lirc_gpio" LIRC modules
+ */
+
+/*
+ * 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/module.h>
+#include <linux/kernel.h>
+#include <linux/kmod.h>
+#include <linux/sched.h>
+#include <linux/errno.h>
+#include <linux/fs.h>
+#include <linux/usb.h>
+#include <linux/time.h>
+
+#include <media/lirc.h>
+#include <media/lirc_dev.h>
+
+
+/* module identification */
+#define DRIVER_VERSION "0.2"
+#define DRIVER_AUTHOR \
+ "Jan M. Hochstein <hochstein@algo.informatik.tu-darmstadt.de>"
+#define DRIVER_DESC "Igorplug USB remote driver for LIRC"
+#define DRIVER_NAME "lirc_igorplugusb"
+
+/* One mode2 pulse/space has 4 bytes. */
+#define CODE_LENGTH sizeof(int)
+
+/* Igor's firmware cannot record bursts longer than 36. */
+#define DEVICE_BUFLEN 36
+
+/*
+ * Header at the beginning of the device's buffer:
+ * unsigned char data_length
+ * unsigned char data_start (!=0 means ring-buffer overrun)
+ * unsigned char counter (incremented by each burst)
+ */
+#define DEVICE_HEADERLEN 3
+
+/* This is for the gap */
+#define ADDITIONAL_LIRC_BYTES 2
+
+/* times to poll per second */
+#define SAMPLE_RATE 100
+static int sample_rate = SAMPLE_RATE;
+
+
+/**** Igor's USB Request Codes */
+
+#define SET_INFRABUFFER_EMPTY 1
+/**
+ * Params: none
+ * Answer: empty
+ */
+
+#define GET_INFRACODE 2
+/**
+ * Params:
+ * wValue: offset to begin reading infra buffer
+ *
+ * Answer: infra data
+ */
+
+#define SET_DATAPORT_DIRECTION 3
+/**
+ * Params:
+ * wValue: (byte) 1 bit for each data port pin (0=in, 1=out)
+ *
+ * Answer: empty
+ */
+
+#define GET_DATAPORT_DIRECTION 4
+/**
+ * Params: none
+ *
+ * Answer: (byte) 1 bit for each data port pin (0=in, 1=out)
+ */
+
+#define SET_OUT_DATAPORT 5
+/**
+ * Params:
+ * wValue: byte to write to output data port
+ *
+ * Answer: empty
+ */
+
+#define GET_OUT_DATAPORT 6
+/**
+ * Params: none
+ *
+ * Answer: least significant 3 bits read from output data port
+ */
+
+#define GET_IN_DATAPORT 7
+/**
+ * Params: none
+ *
+ * Answer: least significant 3 bits read from input data port
+ */
+
+#define READ_EEPROM 8
+/**
+ * Params:
+ * wValue: offset to begin reading EEPROM
+ *
+ * Answer: EEPROM bytes
+ */
+
+#define WRITE_EEPROM 9
+/**
+ * Params:
+ * wValue: offset to EEPROM byte
+ * wIndex: byte to write
+ *
+ * Answer: empty
+ */
+
+#define SEND_RS232 10
+/**
+ * Params:
+ * wValue: byte to send
+ *
+ * Answer: empty
+ */
+
+#define RECV_RS232 11
+/**
+ * Params: none
+ *
+ * Answer: byte received
+ */
+
+#define SET_RS232_BAUD 12
+/**
+ * Params:
+ * wValue: byte to write to UART bit rate register (UBRR)
+ *
+ * Answer: empty
+ */
+
+#define GET_RS232_BAUD 13
+/**
+ * Params: none
+ *
+ * Answer: byte read from UART bit rate register (UBRR)
+ */
+
+
+/* data structure for each usb remote */
+struct igorplug {
+
+ /* usb */
+ struct usb_device *usbdev;
+ int devnum;
+
+ unsigned char *buf_in;
+ unsigned int len_in;
+ int in_space;
+ struct timeval last_time;
+
+ dma_addr_t dma_in;
+
+ /* lirc */
+ struct lirc_driver *d;
+
+ /* handle sending (init strings) */
+ int send_flags;
+};
+
+static int unregister_from_lirc(struct igorplug *ir)
+{
+ struct lirc_driver *d;
+ int devnum;
+
+ if (!ir) {
+ dev_err(&ir->usbdev->dev,
+ "%s: called with NULL device struct!\n", __func__);
+ return -EINVAL;
+ }
+
+ devnum = ir->devnum;
+ d = ir->d;
+
+ if (!d) {
+ dev_err(&ir->usbdev->dev,
+ "%s: called with NULL lirc driver struct!\n", __func__);
+ return -EINVAL;
+ }
+
+ dev_dbg(&ir->usbdev->dev, "calling lirc_unregister_driver\n");
+ lirc_unregister_driver(d->minor);
+
+ return devnum;
+}
+
+static int set_use_inc(void *data)
+{
+ struct igorplug *ir = data;
+
+ if (!ir) {
+ printk(DRIVER_NAME "[?]: set_use_inc called with no context\n");
+ return -EIO;
+ }
+
+ dev_dbg(&ir->usbdev->dev, "set use inc\n");
+
+ if (!ir->usbdev)
+ return -ENODEV;
+
+ return 0;
+}
+
+static void set_use_dec(void *data)
+{
+ struct igorplug *ir = data;
+
+ if (!ir) {
+ printk(DRIVER_NAME "[?]: set_use_dec called with no context\n");
+ return;
+ }
+
+ dev_dbg(&ir->usbdev->dev, "set use dec\n");
+}
+
+static void send_fragment(struct igorplug *ir, struct lirc_buffer *buf,
+ int i, int max)
+{
+ int code;
+
+ /* MODE2: pulse/space (PULSE_BIT) in 1us units */
+ while (i < max) {
+ /* 1 Igor-tick = 85.333333 us */
+ code = (unsigned int)ir->buf_in[i] * 85 +
+ (unsigned int)ir->buf_in[i] / 3;
+ ir->last_time.tv_usec += code;
+ if (ir->in_space)
+ code |= PULSE_BIT;
+ lirc_buffer_write(buf, (unsigned char *)&code);
+ /* 1 chunk = CODE_LENGTH bytes */
+ ir->in_space ^= 1;
+ ++i;
+ }
+}
+
+/**
+ * Called in user context.
+ * return 0 if data was added to the buffer and
+ * -ENODATA if none was available. This should add some number of bits
+ * evenly divisible by code_length to the buffer
+ */
+static int igorplugusb_remote_poll(void *data, struct lirc_buffer *buf)
+{
+ int ret;
+ struct igorplug *ir = (struct igorplug *)data;
+
+ if (!ir || !ir->usbdev) /* Has the device been removed? */
+ return -ENODEV;
+
+ memset(ir->buf_in, 0, ir->len_in);
+
+ ret = usb_control_msg(ir->usbdev, usb_rcvctrlpipe(ir->usbdev, 0),
+ GET_INFRACODE, USB_TYPE_VENDOR | USB_DIR_IN,
+ 0/* offset */, /*unused*/0,
+ ir->buf_in, ir->len_in,
+ /*timeout*/HZ * USB_CTRL_GET_TIMEOUT);
+ if (ret > 0) {
+ int code, timediff;
+ struct timeval now;
+
+ /* ACK packet has 1 byte --> ignore */
+ if (ret < DEVICE_HEADERLEN)
+ return -ENODATA;
+
+ dev_dbg(&ir->usbdev->dev, "Got %d bytes. Header: %*ph\n",
+ ret, 3, ir->buf_in);
+
+ do_gettimeofday(&now);
+ timediff = now.tv_sec - ir->last_time.tv_sec;
+ if (timediff + 1 > PULSE_MASK / 1000000)
+ timediff = PULSE_MASK;
+ else {
+ timediff *= 1000000;
+ timediff += now.tv_usec - ir->last_time.tv_usec;
+ }
+ ir->last_time.tv_sec = now.tv_sec;
+ ir->last_time.tv_usec = now.tv_usec;
+
+ /* create leading gap */
+ code = timediff;
+ lirc_buffer_write(buf, (unsigned char *)&code);
+ ir->in_space = 1; /* next comes a pulse */
+
+ if (ir->buf_in[2] == 0)
+ send_fragment(ir, buf, DEVICE_HEADERLEN, ret);
+ else {
+ dev_warn(&ir->usbdev->dev,
+ "[%d]: Device buffer overrun.\n", ir->devnum);
+ /* HHHNNNNNNNNNNNOOOOOOOO H = header
+ <---[2]---> N = newer
+ <---------ret--------> O = older */
+ ir->buf_in[2] %= ret - DEVICE_HEADERLEN; /* sanitize */
+ /* keep even-ness to not desync pulse/pause */
+ send_fragment(ir, buf, DEVICE_HEADERLEN +
+ ir->buf_in[2] - (ir->buf_in[2] & 1), ret);
+ send_fragment(ir, buf, DEVICE_HEADERLEN,
+ DEVICE_HEADERLEN + ir->buf_in[2]);
+ }
+
+ ret = usb_control_msg(
+ ir->usbdev, usb_rcvctrlpipe(ir->usbdev, 0),
+ SET_INFRABUFFER_EMPTY, USB_TYPE_VENDOR|USB_DIR_IN,
+ /*unused*/0, /*unused*/0,
+ /*dummy*/ir->buf_in, /*dummy*/ir->len_in,
+ /*timeout*/HZ * USB_CTRL_GET_TIMEOUT);
+ if (ret < 0)
+ printk(DRIVER_NAME "[%d]: SET_INFRABUFFER_EMPTY: error %d\n",
+ ir->devnum, ret);
+ return 0;
+ } else if (ret < 0)
+ printk(DRIVER_NAME "[%d]: GET_INFRACODE: error %d\n",
+ ir->devnum, ret);
+
+ return -ENODATA;
+}
+
+static int igorplugusb_remote_probe(struct usb_interface *intf,
+ const struct usb_device_id *id)
+{
+ struct usb_device *dev;
+ struct usb_host_interface *idesc = NULL;
+ struct usb_endpoint_descriptor *ep;
+ struct igorplug *ir = NULL;
+ struct lirc_driver *driver = NULL;
+ int devnum, pipe, maxp;
+ char buf[63], name[128] = "";
+ int ret;
+
+ dev_dbg(&intf->dev, "%s: usb probe called.\n", __func__);
+
+ dev = interface_to_usbdev(intf);
+
+ idesc = intf->cur_altsetting;
+
+ if (idesc->desc.bNumEndpoints != 1)
+ return -ENODEV;
+
+ ep = &idesc->endpoint->desc;
+ if (((ep->bEndpointAddress & USB_ENDPOINT_DIR_MASK)
+ != USB_DIR_IN)
+ || (ep->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK)
+ != USB_ENDPOINT_XFER_CONTROL)
+ return -ENODEV;
+
+ pipe = usb_rcvctrlpipe(dev, ep->bEndpointAddress);
+ devnum = dev->devnum;
+ maxp = usb_maxpacket(dev, pipe, usb_pipeout(pipe));
+
+ dev_dbg(&intf->dev, "%s: bytes_in_key=%zu maxp=%d\n",
+ __func__, CODE_LENGTH, maxp);
+
+ ir = devm_kzalloc(&intf->dev, sizeof(*ir), GFP_KERNEL);
+ if (!ir)
+ return -ENOMEM;
+
+ driver = devm_kzalloc(&intf->dev, sizeof(*driver), GFP_KERNEL);
+ if (!driver)
+ return -ENOMEM;
+
+ ir->buf_in = usb_alloc_coherent(dev, DEVICE_BUFLEN + DEVICE_HEADERLEN,
+ GFP_ATOMIC, &ir->dma_in);
+ if (!ir->buf_in)
+ return -ENOMEM;
+
+ strcpy(driver->name, DRIVER_NAME " ");
+ driver->minor = -1;
+ driver->code_length = CODE_LENGTH * 8; /* in bits */
+ driver->features = LIRC_CAN_REC_MODE2;
+ driver->data = ir;
+ driver->chunk_size = CODE_LENGTH;
+ driver->buffer_size = DEVICE_BUFLEN + ADDITIONAL_LIRC_BYTES;
+ driver->set_use_inc = &set_use_inc;
+ driver->set_use_dec = &set_use_dec;
+ driver->sample_rate = sample_rate; /* per second */
+ driver->add_to_buf = &igorplugusb_remote_poll;
+ driver->dev = &intf->dev;
+ driver->owner = THIS_MODULE;
+
+ ret = lirc_register_driver(driver);
+ if (ret < 0) {
+ usb_free_coherent(dev, DEVICE_BUFLEN + DEVICE_HEADERLEN,
+ ir->buf_in, ir->dma_in);
+ return ret;
+ }
+
+ driver->minor = ret;
+ ir->d = driver;
+ ir->devnum = devnum;
+ ir->usbdev = dev;
+ ir->len_in = DEVICE_BUFLEN + DEVICE_HEADERLEN;
+ ir->in_space = 1; /* First mode2 event is a space. */
+ do_gettimeofday(&ir->last_time);
+
+ if (dev->descriptor.iManufacturer
+ && usb_string(dev, dev->descriptor.iManufacturer,
+ buf, sizeof(buf)) > 0)
+ strlcpy(name, buf, sizeof(name));
+ if (dev->descriptor.iProduct
+ && usb_string(dev, dev->descriptor.iProduct, buf, sizeof(buf)) > 0)
+ snprintf(name + strlen(name), sizeof(name) - strlen(name),
+ " %s", buf);
+ printk(DRIVER_NAME "[%d]: %s on usb%d:%d\n", devnum, name,
+ dev->bus->busnum, devnum);
+
+ /* clear device buffer */
+ ret = usb_control_msg(ir->usbdev, usb_rcvctrlpipe(ir->usbdev, 0),
+ SET_INFRABUFFER_EMPTY, USB_TYPE_VENDOR|USB_DIR_IN,
+ /*unused*/0, /*unused*/0,
+ /*dummy*/ir->buf_in, /*dummy*/ir->len_in,
+ /*timeout*/HZ * USB_CTRL_GET_TIMEOUT);
+ if (ret < 0)
+ printk(DRIVER_NAME "[%d]: SET_INFRABUFFER_EMPTY: error %d\n",
+ devnum, ret);
+
+ usb_set_intfdata(intf, ir);
+ return 0;
+}
+
+static void igorplugusb_remote_disconnect(struct usb_interface *intf)
+{
+ struct usb_device *usbdev = interface_to_usbdev(intf);
+ struct igorplug *ir = usb_get_intfdata(intf);
+ struct device *dev = &intf->dev;
+ int devnum;
+
+ usb_set_intfdata(intf, NULL);
+
+ if (!ir || !ir->d)
+ return;
+
+ ir->usbdev = NULL;
+
+ usb_free_coherent(usbdev, ir->len_in, ir->buf_in, ir->dma_in);
+
+ devnum = unregister_from_lirc(ir);
+
+ dev_info(dev, DRIVER_NAME "[%d]: %s done\n", devnum, __func__);
+}
+
+static struct usb_device_id igorplugusb_remote_id_table[] = {
+ /* Igor Plug USB (Atmel's Manufact. ID) */
+ { USB_DEVICE(0x03eb, 0x0002) },
+ /* Fit PC2 Infrared Adapter */
+ { USB_DEVICE(0x03eb, 0x21fe) },
+
+ /* Terminating entry */
+ { }
+};
+
+static struct usb_driver igorplugusb_remote_driver = {
+ .name = DRIVER_NAME,
+ .probe = igorplugusb_remote_probe,
+ .disconnect = igorplugusb_remote_disconnect,
+ .id_table = igorplugusb_remote_id_table
+};
+
+module_usb_driver(igorplugusb_remote_driver);
+
+#include <linux/vermagic.h>
+MODULE_INFO(vermagic, VERMAGIC_STRING);
+
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_LICENSE("GPL");
+MODULE_DEVICE_TABLE(usb, igorplugusb_remote_id_table);
+
+module_param(sample_rate, int, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(sample_rate, "Sampling rate in Hz (default: 100)");
diff --git a/drivers/staging/media/lirc/lirc_imon.c b/drivers/staging/media/lirc/lirc_imon.c
new file mode 100644
index 00000000000..a5b62eec5e2
--- /dev/null
+++ b/drivers/staging/media/lirc/lirc_imon.c
@@ -0,0 +1,1042 @@
+/*
+ * lirc_imon.c: LIRC/VFD/LCD driver for SoundGraph iMON IR/VFD/LCD
+ * including the iMON PAD model
+ *
+ * Copyright(C) 2004 Venky Raju(dev@venky.ws)
+ * Copyright(C) 2009 Jarod Wilson <jarod@wilsonet.com>
+ *
+ * lirc_imon 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+#include <linux/usb.h>
+
+#include <media/lirc.h>
+#include <media/lirc_dev.h>
+
+
+#define MOD_AUTHOR "Venky Raju <dev@venky.ws>"
+#define MOD_DESC "Driver for SoundGraph iMON MultiMedia IR/Display"
+#define MOD_NAME "lirc_imon"
+#define MOD_VERSION "0.8"
+
+#define DISPLAY_MINOR_BASE 144
+#define DEVICE_NAME "lcd%d"
+
+#define BUF_CHUNK_SIZE 4
+#define BUF_SIZE 128
+
+#define BIT_DURATION 250 /* each bit received is 250us */
+
+/*** P R O T O T Y P E S ***/
+
+/* USB Callback prototypes */
+static int imon_probe(struct usb_interface *interface,
+ const struct usb_device_id *id);
+static void imon_disconnect(struct usb_interface *interface);
+static void usb_rx_callback(struct urb *urb);
+static void usb_tx_callback(struct urb *urb);
+
+/* suspend/resume support */
+static int imon_resume(struct usb_interface *intf);
+static int imon_suspend(struct usb_interface *intf, pm_message_t message);
+
+/* Display file_operations function prototypes */
+static int display_open(struct inode *inode, struct file *file);
+static int display_close(struct inode *inode, struct file *file);
+
+/* VFD write operation */
+static ssize_t vfd_write(struct file *file, const char __user *buf,
+ size_t n_bytes, loff_t *pos);
+
+/* LIRC driver function prototypes */
+static int ir_open(void *data);
+static void ir_close(void *data);
+
+/*** G L O B A L S ***/
+#define IMON_DATA_BUF_SZ 35
+
+struct imon_context {
+ struct usb_device *usbdev;
+ /* Newer devices have two interfaces */
+ int display; /* not all controllers do */
+ int display_isopen; /* display port has been opened */
+ int ir_isopen; /* IR port open */
+ int dev_present; /* USB device presence */
+ struct mutex ctx_lock; /* to lock this object */
+ wait_queue_head_t remove_ok; /* For unexpected USB disconnects */
+
+ int vfd_proto_6p; /* some VFD require a 6th packet */
+
+ struct lirc_driver *driver;
+ struct usb_endpoint_descriptor *rx_endpoint;
+ struct usb_endpoint_descriptor *tx_endpoint;
+ struct urb *rx_urb;
+ struct urb *tx_urb;
+ unsigned char usb_rx_buf[8];
+ unsigned char usb_tx_buf[8];
+
+ struct rx_data {
+ int count; /* length of 0 or 1 sequence */
+ int prev_bit; /* logic level of sequence */
+ int initial_space; /* initial space flag */
+ } rx;
+
+ struct tx_t {
+ unsigned char data_buf[IMON_DATA_BUF_SZ]; /* user data buffer */
+ struct completion finished; /* wait for write to finish */
+ atomic_t busy; /* write in progress */
+ int status; /* status of tx completion */
+ } tx;
+};
+
+static const struct file_operations display_fops = {
+ .owner = THIS_MODULE,
+ .open = &display_open,
+ .write = &vfd_write,
+ .release = &display_close,
+ .llseek = noop_llseek,
+};
+
+/*
+ * USB Device ID for iMON USB Control Boards
+ *
+ * The Windows drivers contain 6 different inf files, more or less one for
+ * each new device until the 0x0034-0x0046 devices, which all use the same
+ * driver. Some of the devices in the 34-46 range haven't been definitively
+ * identified yet. Early devices have either a TriGem Computer, Inc. or a
+ * Samsung vendor ID (0x0aa8 and 0x04e8 respectively), while all later
+ * devices use the SoundGraph vendor ID (0x15c2).
+ */
+static struct usb_device_id imon_usb_id_table[] = {
+ /* TriGem iMON (IR only) -- TG_iMON.inf */
+ { USB_DEVICE(0x0aa8, 0x8001) },
+
+ /* SoundGraph iMON (IR only) -- sg_imon.inf */
+ { USB_DEVICE(0x04e8, 0xff30) },
+
+ /* SoundGraph iMON VFD (IR & VFD) -- iMON_VFD.inf */
+ { USB_DEVICE(0x0aa8, 0xffda) },
+
+ /* SoundGraph iMON SS (IR & VFD) -- iMON_SS.inf */
+ { USB_DEVICE(0x15c2, 0xffda) },
+
+ {}
+};
+
+/* Some iMON VFD models requires a 6th packet for VFD writes */
+static struct usb_device_id vfd_proto_6p_list[] = {
+ { USB_DEVICE(0x15c2, 0xffda) },
+ {}
+};
+
+/* Some iMON devices have no lcd/vfd, don't set one up */
+static struct usb_device_id ir_only_list[] = {
+ { USB_DEVICE(0x0aa8, 0x8001) },
+ { USB_DEVICE(0x04e8, 0xff30) },
+ {}
+};
+
+/* USB Device data */
+static struct usb_driver imon_driver = {
+ .name = MOD_NAME,
+ .probe = imon_probe,
+ .disconnect = imon_disconnect,
+ .suspend = imon_suspend,
+ .resume = imon_resume,
+ .id_table = imon_usb_id_table,
+};
+
+static struct usb_class_driver imon_class = {
+ .name = DEVICE_NAME,
+ .fops = &display_fops,
+ .minor_base = DISPLAY_MINOR_BASE,
+};
+
+/* to prevent races between open() and disconnect(), probing, etc */
+static DEFINE_MUTEX(driver_lock);
+
+static int debug;
+
+/*** M O D U L E C O D E ***/
+
+MODULE_AUTHOR(MOD_AUTHOR);
+MODULE_DESCRIPTION(MOD_DESC);
+MODULE_VERSION(MOD_VERSION);
+MODULE_LICENSE("GPL");
+MODULE_DEVICE_TABLE(usb, imon_usb_id_table);
+module_param(debug, int, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(debug, "Debug messages: 0=no, 1=yes(default: no)");
+
+static void free_imon_context(struct imon_context *context)
+{
+ struct device *dev = context->driver->dev;
+ usb_free_urb(context->tx_urb);
+ usb_free_urb(context->rx_urb);
+ lirc_buffer_free(context->driver->rbuf);
+ kfree(context->driver->rbuf);
+ kfree(context->driver);
+ kfree(context);
+
+ dev_dbg(dev, "%s: iMON context freed\n", __func__);
+}
+
+static void deregister_from_lirc(struct imon_context *context)
+{
+ int retval;
+ int minor = context->driver->minor;
+
+ retval = lirc_unregister_driver(minor);
+ if (retval)
+ dev_err(&context->usbdev->dev,
+ ": %s: unable to deregister from lirc(%d)",
+ __func__, retval);
+ else
+ dev_info(&context->usbdev->dev,
+ "Deregistered iMON driver (minor:%d)\n", minor);
+
+}
+
+/**
+ * Called when the Display device (e.g. /dev/lcd0)
+ * is opened by the application.
+ */
+static int display_open(struct inode *inode, struct file *file)
+{
+ struct usb_interface *interface;
+ struct imon_context *context = NULL;
+ int subminor;
+ int retval = 0;
+
+ /* prevent races with disconnect */
+ mutex_lock(&driver_lock);
+
+ subminor = iminor(inode);
+ interface = usb_find_interface(&imon_driver, subminor);
+ if (!interface) {
+ pr_err("%s: could not find interface for minor %d\n",
+ __func__, subminor);
+ retval = -ENODEV;
+ goto exit;
+ }
+ context = usb_get_intfdata(interface);
+
+ if (!context) {
+ dev_err(&interface->dev,
+ "%s: no context found for minor %d\n",
+ __func__, subminor);
+ retval = -ENODEV;
+ goto exit;
+ }
+
+ mutex_lock(&context->ctx_lock);
+
+ if (!context->display) {
+ dev_err(&interface->dev,
+ "%s: display not supported by device\n", __func__);
+ retval = -ENODEV;
+ } else if (context->display_isopen) {
+ dev_err(&interface->dev,
+ "%s: display port is already open\n", __func__);
+ retval = -EBUSY;
+ } else {
+ context->display_isopen = 1;
+ file->private_data = context;
+ dev_info(context->driver->dev, "display port opened\n");
+ }
+
+ mutex_unlock(&context->ctx_lock);
+
+exit:
+ mutex_unlock(&driver_lock);
+ return retval;
+}
+
+/**
+ * Called when the display device (e.g. /dev/lcd0)
+ * is closed by the application.
+ */
+static int display_close(struct inode *inode, struct file *file)
+{
+ struct imon_context *context = NULL;
+ int retval = 0;
+
+ context = file->private_data;
+
+ if (!context) {
+ pr_err("%s: no context for device\n", __func__);
+ return -ENODEV;
+ }
+
+ mutex_lock(&context->ctx_lock);
+
+ if (!context->display) {
+ dev_err(&context->usbdev->dev,
+ "%s: display not supported by device\n", __func__);
+ retval = -ENODEV;
+ } else if (!context->display_isopen) {
+ dev_err(&context->usbdev->dev,
+ "%s: display is not open\n", __func__);
+ retval = -EIO;
+ } else {
+ context->display_isopen = 0;
+ dev_info(context->driver->dev, "display port closed\n");
+ if (!context->dev_present && !context->ir_isopen) {
+ /*
+ * Device disconnected before close and IR port is not
+ * open. If IR port is open, context will be deleted by
+ * ir_close.
+ */
+ mutex_unlock(&context->ctx_lock);
+ free_imon_context(context);
+ return retval;
+ }
+ }
+
+ mutex_unlock(&context->ctx_lock);
+ return retval;
+}
+
+/**
+ * Sends a packet to the device -- this function must be called
+ * with context->ctx_lock held.
+ */
+static int send_packet(struct imon_context *context)
+{
+ unsigned int pipe;
+ int interval = 0;
+ int retval = 0;
+
+ /* Check if we need to use control or interrupt urb */
+ pipe = usb_sndintpipe(context->usbdev,
+ context->tx_endpoint->bEndpointAddress);
+ interval = context->tx_endpoint->bInterval;
+
+ usb_fill_int_urb(context->tx_urb, context->usbdev, pipe,
+ context->usb_tx_buf,
+ sizeof(context->usb_tx_buf),
+ usb_tx_callback, context, interval);
+
+ context->tx_urb->actual_length = 0;
+
+ init_completion(&context->tx.finished);
+ atomic_set(&(context->tx.busy), 1);
+
+ retval = usb_submit_urb(context->tx_urb, GFP_KERNEL);
+ if (retval) {
+ atomic_set(&(context->tx.busy), 0);
+ dev_err(&context->usbdev->dev,
+ "%s: error submitting urb(%d)\n", __func__, retval);
+ } else {
+ /* Wait for transmission to complete (or abort) */
+ mutex_unlock(&context->ctx_lock);
+ retval = wait_for_completion_interruptible(
+ &context->tx.finished);
+ if (retval)
+ dev_err(&context->usbdev->dev,
+ "%s: task interrupted\n", __func__);
+ mutex_lock(&context->ctx_lock);
+
+ retval = context->tx.status;
+ if (retval)
+ dev_err(&context->usbdev->dev,
+ "%s: packet tx failed (%d)\n",
+ __func__, retval);
+ }
+
+ return retval;
+}
+
+/**
+ * Writes data to the VFD. The iMON VFD is 2x16 characters
+ * and requires data in 5 consecutive USB interrupt packets,
+ * each packet but the last carrying 7 bytes.
+ *
+ * I don't know if the VFD board supports features such as
+ * scrolling, clearing rows, blanking, etc. so at
+ * the caller must provide a full screen of data. If fewer
+ * than 32 bytes are provided spaces will be appended to
+ * generate a full screen.
+ */
+static ssize_t vfd_write(struct file *file, const char __user *buf,
+ size_t n_bytes, loff_t *pos)
+{
+ int i;
+ int offset;
+ int seq;
+ int retval = 0;
+ struct imon_context *context;
+ const unsigned char vfd_packet6[] = {
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF };
+ int *data_buf = NULL;
+
+ context = file->private_data;
+ if (!context) {
+ pr_err("%s: no context for device\n", __func__);
+ return -ENODEV;
+ }
+
+ mutex_lock(&context->ctx_lock);
+
+ if (!context->dev_present) {
+ dev_err(&context->usbdev->dev,
+ "%s: no iMON device present\n", __func__);
+ retval = -ENODEV;
+ goto exit;
+ }
+
+ if (n_bytes <= 0 || n_bytes > IMON_DATA_BUF_SZ - 3) {
+ dev_err(&context->usbdev->dev,
+ "%s: invalid payload size\n", __func__);
+ retval = -EINVAL;
+ goto exit;
+ }
+
+ data_buf = memdup_user(buf, n_bytes);
+ if (IS_ERR(data_buf)) {
+ retval = PTR_ERR(data_buf);
+ goto exit;
+ }
+
+ memcpy(context->tx.data_buf, data_buf, n_bytes);
+
+ /* Pad with spaces */
+ for (i = n_bytes; i < IMON_DATA_BUF_SZ - 3; ++i)
+ context->tx.data_buf[i] = ' ';
+
+ for (i = IMON_DATA_BUF_SZ - 3; i < IMON_DATA_BUF_SZ; ++i)
+ context->tx.data_buf[i] = 0xFF;
+
+ offset = 0;
+ seq = 0;
+
+ do {
+ memcpy(context->usb_tx_buf, context->tx.data_buf + offset, 7);
+ context->usb_tx_buf[7] = (unsigned char) seq;
+
+ retval = send_packet(context);
+ if (retval) {
+ dev_err(&context->usbdev->dev,
+ "%s: send packet failed for packet #%d\n",
+ __func__, seq/2);
+ goto exit;
+ } else {
+ seq += 2;
+ offset += 7;
+ }
+
+ } while (offset < IMON_DATA_BUF_SZ);
+
+ if (context->vfd_proto_6p) {
+ /* Send packet #6 */
+ memcpy(context->usb_tx_buf, &vfd_packet6, sizeof(vfd_packet6));
+ context->usb_tx_buf[7] = (unsigned char) seq;
+ retval = send_packet(context);
+ if (retval)
+ dev_err(&context->usbdev->dev,
+ "%s: send packet failed for packet #%d\n",
+ __func__, seq/2);
+ }
+
+exit:
+ mutex_unlock(&context->ctx_lock);
+ kfree(data_buf);
+
+ return (!retval) ? n_bytes : retval;
+}
+
+/**
+ * Callback function for USB core API: transmit data
+ */
+static void usb_tx_callback(struct urb *urb)
+{
+ struct imon_context *context;
+
+ if (!urb)
+ return;
+ context = (struct imon_context *)urb->context;
+ if (!context)
+ return;
+
+ context->tx.status = urb->status;
+
+ /* notify waiters that write has finished */
+ atomic_set(&context->tx.busy, 0);
+ complete(&context->tx.finished);
+
+ return;
+}
+
+/**
+ * Called by lirc_dev when the application opens /dev/lirc
+ */
+static int ir_open(void *data)
+{
+ int retval = 0;
+ struct imon_context *context;
+
+ /* prevent races with disconnect */
+ mutex_lock(&driver_lock);
+
+ context = (struct imon_context *)data;
+
+ /* initial IR protocol decode variables */
+ context->rx.count = 0;
+ context->rx.initial_space = 1;
+ context->rx.prev_bit = 0;
+
+ context->ir_isopen = 1;
+ dev_info(context->driver->dev, "IR port opened\n");
+
+ mutex_unlock(&driver_lock);
+ return retval;
+}
+
+/**
+ * Called by lirc_dev when the application closes /dev/lirc
+ */
+static void ir_close(void *data)
+{
+ struct imon_context *context;
+
+ context = (struct imon_context *)data;
+ if (!context) {
+ pr_err("%s: no context for device\n", __func__);
+ return;
+ }
+
+ mutex_lock(&context->ctx_lock);
+
+ context->ir_isopen = 0;
+ dev_info(context->driver->dev, "IR port closed\n");
+
+ if (!context->dev_present) {
+ /*
+ * Device disconnected while IR port was still open. Driver
+ * was not deregistered at disconnect time, so do it now.
+ */
+ deregister_from_lirc(context);
+
+ if (!context->display_isopen) {
+ mutex_unlock(&context->ctx_lock);
+ free_imon_context(context);
+ return;
+ }
+ /*
+ * If display port is open, context will be deleted by
+ * display_close
+ */
+ }
+
+ mutex_unlock(&context->ctx_lock);
+ return;
+}
+
+/**
+ * Convert bit count to time duration (in us) and submit
+ * the value to lirc_dev.
+ */
+static void submit_data(struct imon_context *context)
+{
+ unsigned char buf[4];
+ int value = context->rx.count;
+ int i;
+
+ dev_dbg(context->driver->dev, "submitting data to LIRC\n");
+
+ value *= BIT_DURATION;
+ value &= PULSE_MASK;
+ if (context->rx.prev_bit)
+ value |= PULSE_BIT;
+
+ for (i = 0; i < 4; ++i)
+ buf[i] = value>>(i*8);
+
+ lirc_buffer_write(context->driver->rbuf, buf);
+ wake_up(&context->driver->rbuf->wait_poll);
+ return;
+}
+
+static inline int tv2int(const struct timeval *a, const struct timeval *b)
+{
+ int usecs = 0;
+ int sec = 0;
+
+ if (b->tv_usec > a->tv_usec) {
+ usecs = 1000000;
+ sec--;
+ }
+
+ usecs += a->tv_usec - b->tv_usec;
+
+ sec += a->tv_sec - b->tv_sec;
+ sec *= 1000;
+ usecs /= 1000;
+ sec += usecs;
+
+ if (sec < 0)
+ sec = 1000;
+
+ return sec;
+}
+
+/**
+ * Process the incoming packet
+ */
+static void imon_incoming_packet(struct imon_context *context,
+ struct urb *urb, int intf)
+{
+ int len = urb->actual_length;
+ unsigned char *buf = urb->transfer_buffer;
+ struct device *dev = context->driver->dev;
+ int octet, bit;
+ unsigned char mask;
+ int i;
+
+ /*
+ * just bail out if no listening IR client
+ */
+ if (!context->ir_isopen)
+ return;
+
+ if (len != 8) {
+ dev_warn(dev, "imon %s: invalid incoming packet "
+ "size (len = %d, intf%d)\n", __func__, len, intf);
+ return;
+ }
+
+ if (debug) {
+ dev_info(dev, "raw packet: ");
+ for (i = 0; i < len; ++i)
+ printk("%02x ", buf[i]);
+ printk("\n");
+ }
+
+ /*
+ * Translate received data to pulse and space lengths.
+ * Received data is active low, i.e. pulses are 0 and
+ * spaces are 1.
+ *
+ * My original algorithm was essentially similar to
+ * Changwoo Ryu's with the exception that he switched
+ * the incoming bits to active high and also fed an
+ * initial space to LIRC at the start of a new sequence
+ * if the previous bit was a pulse.
+ *
+ * I've decided to adopt his algorithm.
+ */
+
+ if (buf[7] == 1 && context->rx.initial_space) {
+ /* LIRC requires a leading space */
+ context->rx.prev_bit = 0;
+ context->rx.count = 4;
+ submit_data(context);
+ context->rx.count = 0;
+ }
+
+ for (octet = 0; octet < 5; ++octet) {
+ mask = 0x80;
+ for (bit = 0; bit < 8; ++bit) {
+ int curr_bit = !(buf[octet] & mask);
+ if (curr_bit != context->rx.prev_bit) {
+ if (context->rx.count) {
+ submit_data(context);
+ context->rx.count = 0;
+ }
+ context->rx.prev_bit = curr_bit;
+ }
+ ++context->rx.count;
+ mask >>= 1;
+ }
+ }
+
+ if (buf[7] == 10) {
+ if (context->rx.count) {
+ submit_data(context);
+ context->rx.count = 0;
+ }
+ context->rx.initial_space = context->rx.prev_bit;
+ }
+}
+
+/**
+ * Callback function for USB core API: receive data
+ */
+static void usb_rx_callback(struct urb *urb)
+{
+ struct imon_context *context;
+ int intfnum = 0;
+
+ if (!urb)
+ return;
+
+ context = (struct imon_context *)urb->context;
+ if (!context)
+ return;
+
+ switch (urb->status) {
+ case -ENOENT: /* usbcore unlink successful! */
+ return;
+
+ case 0:
+ imon_incoming_packet(context, urb, intfnum);
+ break;
+
+ default:
+ dev_warn(context->driver->dev, "imon %s: status(%d): ignored\n",
+ __func__, urb->status);
+ break;
+ }
+
+ usb_submit_urb(context->rx_urb, GFP_ATOMIC);
+
+ return;
+}
+
+/**
+ * Callback function for USB core API: Probe
+ */
+static int imon_probe(struct usb_interface *interface,
+ const struct usb_device_id *id)
+{
+ struct usb_device *usbdev = NULL;
+ struct usb_host_interface *iface_desc = NULL;
+ struct usb_endpoint_descriptor *rx_endpoint = NULL;
+ struct usb_endpoint_descriptor *tx_endpoint = NULL;
+ struct urb *rx_urb = NULL;
+ struct urb *tx_urb = NULL;
+ struct lirc_driver *driver = NULL;
+ struct lirc_buffer *rbuf = NULL;
+ struct device *dev = &interface->dev;
+ int ifnum;
+ int lirc_minor = 0;
+ int num_endpts;
+ int retval = 0;
+ int display_ep_found = 0;
+ int ir_ep_found = 0;
+ int alloc_status = 0;
+ int vfd_proto_6p = 0;
+ struct imon_context *context = NULL;
+ int i;
+ u16 vendor, product;
+
+ /* prevent races probing devices w/multiple interfaces */
+ mutex_lock(&driver_lock);
+
+ context = kzalloc(sizeof(struct imon_context), GFP_KERNEL);
+ if (!context) {
+ alloc_status = 1;
+ goto alloc_status_switch;
+ }
+
+ /*
+ * Try to auto-detect the type of display if the user hasn't set
+ * it by hand via the display_type modparam. Default is VFD.
+ */
+ if (usb_match_id(interface, ir_only_list))
+ context->display = 0;
+ else
+ context->display = 1;
+
+ usbdev = usb_get_dev(interface_to_usbdev(interface));
+ iface_desc = interface->cur_altsetting;
+ num_endpts = iface_desc->desc.bNumEndpoints;
+ ifnum = iface_desc->desc.bInterfaceNumber;
+ vendor = le16_to_cpu(usbdev->descriptor.idVendor);
+ product = le16_to_cpu(usbdev->descriptor.idProduct);
+
+ dev_dbg(dev, "%s: found iMON device (%04x:%04x, intf%d)\n",
+ __func__, vendor, product, ifnum);
+
+ /*
+ * Scan the endpoint list and set:
+ * first input endpoint = IR endpoint
+ * first output endpoint = display endpoint
+ */
+ for (i = 0; i < num_endpts && !(ir_ep_found && display_ep_found); ++i) {
+ struct usb_endpoint_descriptor *ep;
+ int ep_dir;
+ int ep_type;
+ ep = &iface_desc->endpoint[i].desc;
+ ep_dir = ep->bEndpointAddress & USB_ENDPOINT_DIR_MASK;
+ ep_type = ep->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK;
+
+ if (!ir_ep_found &&
+ ep_dir == USB_DIR_IN &&
+ ep_type == USB_ENDPOINT_XFER_INT) {
+
+ rx_endpoint = ep;
+ ir_ep_found = 1;
+ dev_dbg(dev, "%s: found IR endpoint\n", __func__);
+
+ } else if (!display_ep_found && ep_dir == USB_DIR_OUT &&
+ ep_type == USB_ENDPOINT_XFER_INT) {
+ tx_endpoint = ep;
+ display_ep_found = 1;
+ dev_dbg(dev, "%s: found display endpoint\n", __func__);
+ }
+ }
+
+ /*
+ * Some iMON receivers have no display. Unfortunately, it seems
+ * that SoundGraph recycles device IDs between devices both with
+ * and without... :\
+ */
+ if (context->display == 0) {
+ display_ep_found = 0;
+ dev_dbg(dev, "%s: device has no display\n", __func__);
+ }
+
+ /* Input endpoint is mandatory */
+ if (!ir_ep_found) {
+ dev_err(dev, "%s: no valid input (IR) endpoint found.\n",
+ __func__);
+ retval = -ENODEV;
+ alloc_status = 2;
+ goto alloc_status_switch;
+ }
+
+ /* Determine if display requires 6 packets */
+ if (display_ep_found) {
+ if (usb_match_id(interface, vfd_proto_6p_list))
+ vfd_proto_6p = 1;
+
+ dev_dbg(dev, "%s: vfd_proto_6p: %d\n",
+ __func__, vfd_proto_6p);
+ }
+
+ driver = kzalloc(sizeof(struct lirc_driver), GFP_KERNEL);
+ if (!driver) {
+ alloc_status = 2;
+ goto alloc_status_switch;
+ }
+ rbuf = kmalloc(sizeof(struct lirc_buffer), GFP_KERNEL);
+ if (!rbuf) {
+ alloc_status = 3;
+ goto alloc_status_switch;
+ }
+ if (lirc_buffer_init(rbuf, BUF_CHUNK_SIZE, BUF_SIZE)) {
+ dev_err(dev, "%s: lirc_buffer_init failed\n", __func__);
+ alloc_status = 4;
+ goto alloc_status_switch;
+ }
+ rx_urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!rx_urb) {
+ dev_err(dev, "%s: usb_alloc_urb failed for IR urb\n", __func__);
+ alloc_status = 5;
+ goto alloc_status_switch;
+ }
+ tx_urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!tx_urb) {
+ dev_err(dev, "%s: usb_alloc_urb failed for display urb\n",
+ __func__);
+ alloc_status = 6;
+ goto alloc_status_switch;
+ }
+
+ mutex_init(&context->ctx_lock);
+ context->vfd_proto_6p = vfd_proto_6p;
+
+ strcpy(driver->name, MOD_NAME);
+ driver->minor = -1;
+ driver->code_length = BUF_CHUNK_SIZE * 8;
+ driver->sample_rate = 0;
+ driver->features = LIRC_CAN_REC_MODE2;
+ driver->data = context;
+ driver->rbuf = rbuf;
+ driver->set_use_inc = ir_open;
+ driver->set_use_dec = ir_close;
+ driver->dev = &interface->dev;
+ driver->owner = THIS_MODULE;
+
+ mutex_lock(&context->ctx_lock);
+
+ context->driver = driver;
+ /* start out in keyboard mode */
+
+ lirc_minor = lirc_register_driver(driver);
+ if (lirc_minor < 0) {
+ dev_err(dev, "%s: lirc_register_driver failed\n", __func__);
+ alloc_status = 7;
+ goto unlock;
+ } else
+ dev_info(dev, "Registered iMON driver (lirc minor: %d)\n",
+ lirc_minor);
+
+ /* Needed while unregistering! */
+ driver->minor = lirc_minor;
+
+ context->usbdev = usbdev;
+ context->dev_present = 1;
+ context->rx_endpoint = rx_endpoint;
+ context->rx_urb = rx_urb;
+
+ /*
+ * tx is used to send characters to lcd/vfd, associate RF
+ * remotes, set IR protocol, and maybe more...
+ */
+ context->tx_endpoint = tx_endpoint;
+ context->tx_urb = tx_urb;
+
+ if (display_ep_found)
+ context->display = 1;
+
+ usb_fill_int_urb(context->rx_urb, context->usbdev,
+ usb_rcvintpipe(context->usbdev,
+ context->rx_endpoint->bEndpointAddress),
+ context->usb_rx_buf, sizeof(context->usb_rx_buf),
+ usb_rx_callback, context,
+ context->rx_endpoint->bInterval);
+
+ retval = usb_submit_urb(context->rx_urb, GFP_KERNEL);
+
+ if (retval) {
+ dev_err(dev, "%s: usb_submit_urb failed for intf0 (%d)\n",
+ __func__, retval);
+ alloc_status = 8;
+ goto unlock;
+ }
+
+ usb_set_intfdata(interface, context);
+
+ if (context->display && ifnum == 0) {
+ dev_dbg(dev, "%s: Registering iMON display with sysfs\n",
+ __func__);
+
+ if (usb_register_dev(interface, &imon_class)) {
+ /* Not a fatal error, so ignore */
+ dev_info(dev, "%s: could not get a minor number for display\n",
+ __func__);
+ }
+ }
+
+ dev_info(dev, "iMON device (%04x:%04x, intf%d) on "
+ "usb<%d:%d> initialized\n", vendor, product, ifnum,
+ usbdev->bus->busnum, usbdev->devnum);
+
+unlock:
+ mutex_unlock(&context->ctx_lock);
+alloc_status_switch:
+
+ switch (alloc_status) {
+ case 8:
+ lirc_unregister_driver(driver->minor);
+ case 7:
+ usb_free_urb(tx_urb);
+ case 6:
+ usb_free_urb(rx_urb);
+ /* fall-through */
+ case 5:
+ if (rbuf)
+ lirc_buffer_free(rbuf);
+ /* fall-through */
+ case 4:
+ kfree(rbuf);
+ /* fall-through */
+ case 3:
+ kfree(driver);
+ /* fall-through */
+ case 2:
+ kfree(context);
+ context = NULL;
+ case 1:
+ if (retval != -ENODEV)
+ retval = -ENOMEM;
+ break;
+ case 0:
+ retval = 0;
+ }
+
+ mutex_unlock(&driver_lock);
+
+ return retval;
+}
+
+/**
+ * Callback function for USB core API: disconnect
+ */
+static void imon_disconnect(struct usb_interface *interface)
+{
+ struct imon_context *context;
+ int ifnum;
+
+ /* prevent races with ir_open()/display_open() */
+ mutex_lock(&driver_lock);
+
+ context = usb_get_intfdata(interface);
+ ifnum = interface->cur_altsetting->desc.bInterfaceNumber;
+
+ mutex_lock(&context->ctx_lock);
+
+ usb_set_intfdata(interface, NULL);
+
+ /* Abort ongoing write */
+ if (atomic_read(&context->tx.busy)) {
+ usb_kill_urb(context->tx_urb);
+ complete_all(&context->tx.finished);
+ }
+
+ context->dev_present = 0;
+ usb_kill_urb(context->rx_urb);
+ if (context->display)
+ usb_deregister_dev(interface, &imon_class);
+
+ if (!context->ir_isopen && !context->dev_present) {
+ deregister_from_lirc(context);
+ mutex_unlock(&context->ctx_lock);
+ if (!context->display_isopen)
+ free_imon_context(context);
+ } else
+ mutex_unlock(&context->ctx_lock);
+
+ mutex_unlock(&driver_lock);
+
+ dev_info(&interface->dev, "%s: iMON device (intf%d) disconnected\n",
+ __func__, ifnum);
+}
+
+static int imon_suspend(struct usb_interface *intf, pm_message_t message)
+{
+ struct imon_context *context = usb_get_intfdata(intf);
+
+ usb_kill_urb(context->rx_urb);
+
+ return 0;
+}
+
+static int imon_resume(struct usb_interface *intf)
+{
+ int rc = 0;
+ struct imon_context *context = usb_get_intfdata(intf);
+
+ usb_fill_int_urb(context->rx_urb, context->usbdev,
+ usb_rcvintpipe(context->usbdev,
+ context->rx_endpoint->bEndpointAddress),
+ context->usb_rx_buf, sizeof(context->usb_rx_buf),
+ usb_rx_callback, context,
+ context->rx_endpoint->bInterval);
+
+ rc = usb_submit_urb(context->rx_urb, GFP_ATOMIC);
+
+ return rc;
+}
+
+module_usb_driver(imon_driver);
diff --git a/drivers/staging/media/lirc/lirc_parallel.c b/drivers/staging/media/lirc/lirc_parallel.c
new file mode 100644
index 00000000000..1394f020e46
--- /dev/null
+++ b/drivers/staging/media/lirc/lirc_parallel.c
@@ -0,0 +1,746 @@
+/*
+ * lirc_parallel.c
+ *
+ * lirc_parallel - device driver for infra-red signal receiving and
+ * transmitting unit built by the author
+ *
+ * Copyright (C) 1998 Christoph Bartelmus <lirc@bartelmus.de>
+ *
+ * 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
+ *
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+/*** Includes ***/
+
+#include <linux/module.h>
+#include <linux/sched.h>
+#include <linux/errno.h>
+#include <linux/signal.h>
+#include <linux/fs.h>
+#include <linux/kernel.h>
+#include <linux/ioport.h>
+#include <linux/time.h>
+#include <linux/mm.h>
+#include <linux/delay.h>
+
+#include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/uaccess.h>
+#include <asm/div64.h>
+
+#include <linux/poll.h>
+#include <linux/parport.h>
+#include <linux/platform_device.h>
+
+#include <media/lirc.h>
+#include <media/lirc_dev.h>
+
+#include "lirc_parallel.h"
+
+#define LIRC_DRIVER_NAME "lirc_parallel"
+
+#ifndef LIRC_IRQ
+#define LIRC_IRQ 7
+#endif
+#ifndef LIRC_PORT
+#define LIRC_PORT 0x378
+#endif
+#ifndef LIRC_TIMER
+#define LIRC_TIMER 65536
+#endif
+
+/*** Global Variables ***/
+
+static bool debug;
+static bool check_pselecd;
+
+static unsigned int irq = LIRC_IRQ;
+static unsigned int io = LIRC_PORT;
+#ifdef LIRC_TIMER
+static unsigned int timer;
+static unsigned int default_timer = LIRC_TIMER;
+#endif
+
+#define RBUF_SIZE (256) /* this must be a power of 2 larger than 1 */
+
+static int rbuf[RBUF_SIZE];
+
+static DECLARE_WAIT_QUEUE_HEAD(lirc_wait);
+
+static unsigned int rptr;
+static unsigned int wptr;
+static unsigned int lost_irqs;
+static int is_open;
+
+static struct parport *pport;
+static struct pardevice *ppdevice;
+static int is_claimed;
+
+static unsigned int tx_mask = 1;
+
+/*** Internal Functions ***/
+
+static unsigned int in(int offset)
+{
+ switch (offset) {
+ case LIRC_LP_BASE:
+ return parport_read_data(pport);
+ case LIRC_LP_STATUS:
+ return parport_read_status(pport);
+ case LIRC_LP_CONTROL:
+ return parport_read_control(pport);
+ }
+ return 0; /* make compiler happy */
+}
+
+static void out(int offset, int value)
+{
+ switch (offset) {
+ case LIRC_LP_BASE:
+ parport_write_data(pport, value);
+ break;
+ case LIRC_LP_CONTROL:
+ parport_write_control(pport, value);
+ break;
+ case LIRC_LP_STATUS:
+ pr_info("attempt to write to status register\n");
+ break;
+ }
+}
+
+static unsigned int lirc_get_timer(void)
+{
+ return in(LIRC_PORT_TIMER) & LIRC_PORT_TIMER_BIT;
+}
+
+static unsigned int lirc_get_signal(void)
+{
+ return in(LIRC_PORT_SIGNAL) & LIRC_PORT_SIGNAL_BIT;
+}
+
+static void lirc_on(void)
+{
+ out(LIRC_PORT_DATA, tx_mask);
+}
+
+static void lirc_off(void)
+{
+ out(LIRC_PORT_DATA, 0);
+}
+
+static unsigned int init_lirc_timer(void)
+{
+ struct timeval tv, now;
+ unsigned int level, newlevel, timeelapsed, newtimer;
+ int count = 0;
+
+ do_gettimeofday(&tv);
+ tv.tv_sec++; /* wait max. 1 sec. */
+ level = lirc_get_timer();
+ do {
+ newlevel = lirc_get_timer();
+ if (level == 0 && newlevel != 0)
+ count++;
+ level = newlevel;
+ do_gettimeofday(&now);
+ } while (count < 1000 && (now.tv_sec < tv.tv_sec
+ || (now.tv_sec == tv.tv_sec
+ && now.tv_usec < tv.tv_usec)));
+
+ timeelapsed = ((now.tv_sec + 1 - tv.tv_sec)*1000000
+ + (now.tv_usec - tv.tv_usec));
+ if (count >= 1000 && timeelapsed > 0) {
+ if (default_timer == 0) {
+ /* autodetect timer */
+ newtimer = (1000000*count)/timeelapsed;
+ pr_info("%u Hz timer detected\n", newtimer);
+ return newtimer;
+ } else {
+ newtimer = (1000000*count)/timeelapsed;
+ if (abs(newtimer - default_timer) > default_timer/10) {
+ /* bad timer */
+ pr_notice("bad timer: %u Hz\n", newtimer);
+ pr_notice("using default timer: %u Hz\n",
+ default_timer);
+ return default_timer;
+ } else {
+ pr_info("%u Hz timer detected\n", newtimer);
+ return newtimer; /* use detected value */
+ }
+ }
+ } else {
+ pr_notice("no timer detected\n");
+ return 0;
+ }
+}
+
+static int lirc_claim(void)
+{
+ if (parport_claim(ppdevice) != 0) {
+ pr_warn("could not claim port\n");
+ pr_warn("waiting for port becoming available\n");
+ if (parport_claim_or_block(ppdevice) < 0) {
+ pr_notice("could not claim port, giving up\n");
+ return 0;
+ }
+ }
+ out(LIRC_LP_CONTROL, LP_PSELECP|LP_PINITP);
+ is_claimed = 1;
+ return 1;
+}
+
+/*** interrupt handler ***/
+
+static void rbuf_write(int signal)
+{
+ unsigned int nwptr;
+
+ nwptr = (wptr + 1) & (RBUF_SIZE - 1);
+ if (nwptr == rptr) {
+ /* no new signals will be accepted */
+ lost_irqs++;
+ pr_notice("buffer overrun\n");
+ return;
+ }
+ rbuf[wptr] = signal;
+ wptr = nwptr;
+}
+
+static void lirc_lirc_irq_handler(void *blah)
+{
+ struct timeval tv;
+ static struct timeval lasttv;
+ static int init;
+ long signal;
+ int data;
+ unsigned int level, newlevel;
+ unsigned int timeout;
+
+ if (!is_open)
+ return;
+
+ if (!is_claimed)
+ return;
+
+#if 0
+ /* disable interrupt */
+ disable_irq(irq);
+ out(LIRC_PORT_IRQ, in(LIRC_PORT_IRQ) & (~LP_PINTEN));
+#endif
+ if (check_pselecd && (in(1) & LP_PSELECD))
+ return;
+
+#ifdef LIRC_TIMER
+ if (init) {
+ do_gettimeofday(&tv);
+
+ signal = tv.tv_sec - lasttv.tv_sec;
+ if (signal > 15)
+ /* really long time */
+ data = PULSE_MASK;
+ else
+ data = (int) (signal*1000000 +
+ tv.tv_usec - lasttv.tv_usec +
+ LIRC_SFH506_DELAY);
+
+ rbuf_write(data); /* space */
+ } else {
+ if (timer == 0) {
+ /*
+ * wake up; we'll lose this signal, but it will be
+ * garbage if the device is turned on anyway
+ */
+ timer = init_lirc_timer();
+ /* enable_irq(irq); */
+ return;
+ }
+ init = 1;
+ }
+
+ timeout = timer/10; /* timeout after 1/10 sec. */
+ signal = 1;
+ level = lirc_get_timer();
+ do {
+ newlevel = lirc_get_timer();
+ if (level == 0 && newlevel != 0)
+ signal++;
+ level = newlevel;
+
+ /* giving up */
+ if (signal > timeout
+ || (check_pselecd && (in(1) & LP_PSELECD))) {
+ signal = 0;
+ pr_notice("timeout\n");
+ break;
+ }
+ } while (lirc_get_signal());
+
+ if (signal != 0) {
+ /* adjust value to usecs */
+ __u64 helper;
+
+ helper = ((__u64) signal)*1000000;
+ do_div(helper, timer);
+ signal = (long) helper;
+
+ if (signal > LIRC_SFH506_DELAY)
+ data = signal - LIRC_SFH506_DELAY;
+ else
+ data = 1;
+ rbuf_write(PULSE_BIT|data); /* pulse */
+ }
+ do_gettimeofday(&lasttv);
+#else
+ /* add your code here */
+#endif
+
+ wake_up_interruptible(&lirc_wait);
+
+ /* enable interrupt */
+ /*
+ enable_irq(irq);
+ out(LIRC_PORT_IRQ, in(LIRC_PORT_IRQ)|LP_PINTEN);
+ */
+}
+
+/*** file operations ***/
+
+static loff_t lirc_lseek(struct file *filep, loff_t offset, int orig)
+{
+ return -ESPIPE;
+}
+
+static ssize_t lirc_read(struct file *filep, char __user *buf, size_t n,
+ loff_t *ppos)
+{
+ int result = 0;
+ int count = 0;
+ DECLARE_WAITQUEUE(wait, current);
+
+ if (n % sizeof(int))
+ return -EINVAL;
+
+ add_wait_queue(&lirc_wait, &wait);
+ set_current_state(TASK_INTERRUPTIBLE);
+ while (count < n) {
+ if (rptr != wptr) {
+ if (copy_to_user(buf+count, (char *) &rbuf[rptr],
+ sizeof(int))) {
+ result = -EFAULT;
+ break;
+ }
+ rptr = (rptr + 1) & (RBUF_SIZE - 1);
+ count += sizeof(int);
+ } else {
+ if (filep->f_flags & O_NONBLOCK) {
+ result = -EAGAIN;
+ break;
+ }
+ if (signal_pending(current)) {
+ result = -ERESTARTSYS;
+ break;
+ }
+ schedule();
+ set_current_state(TASK_INTERRUPTIBLE);
+ }
+ }
+ remove_wait_queue(&lirc_wait, &wait);
+ set_current_state(TASK_RUNNING);
+ return count ? count : result;
+}
+
+static ssize_t lirc_write(struct file *filep, const char __user *buf, size_t n,
+ loff_t *ppos)
+{
+ int count;
+ unsigned int i;
+ unsigned int level, newlevel;
+ unsigned long flags;
+ int counttimer;
+ int *wbuf;
+ ssize_t ret;
+
+ if (!is_claimed)
+ return -EBUSY;
+
+ count = n / sizeof(int);
+
+ if (n % sizeof(int) || count % 2 == 0)
+ return -EINVAL;
+
+ wbuf = memdup_user(buf, n);
+ if (IS_ERR(wbuf))
+ return PTR_ERR(wbuf);
+
+#ifdef LIRC_TIMER
+ if (timer == 0) {
+ /* try again if device is ready */
+ timer = init_lirc_timer();
+ if (timer == 0) {
+ ret = -EIO;
+ goto out;
+ }
+ }
+
+ /* adjust values from usecs */
+ for (i = 0; i < count; i++) {
+ __u64 helper;
+
+ helper = ((__u64) wbuf[i])*timer;
+ do_div(helper, 1000000);
+ wbuf[i] = (int) helper;
+ }
+
+ local_irq_save(flags);
+ i = 0;
+ while (i < count) {
+ level = lirc_get_timer();
+ counttimer = 0;
+ lirc_on();
+ do {
+ newlevel = lirc_get_timer();
+ if (level == 0 && newlevel != 0)
+ counttimer++;
+ level = newlevel;
+ if (check_pselecd && (in(1) & LP_PSELECD)) {
+ lirc_off();
+ local_irq_restore(flags);
+ ret = -EIO;
+ goto out;
+ }
+ } while (counttimer < wbuf[i]);
+ i++;
+
+ lirc_off();
+ if (i == count)
+ break;
+ counttimer = 0;
+ do {
+ newlevel = lirc_get_timer();
+ if (level == 0 && newlevel != 0)
+ counttimer++;
+ level = newlevel;
+ if (check_pselecd && (in(1) & LP_PSELECD)) {
+ local_irq_restore(flags);
+ ret = -EIO;
+ goto out;
+ }
+ } while (counttimer < wbuf[i]);
+ i++;
+ }
+ local_irq_restore(flags);
+#else
+ /* place code that handles write without external timer here */
+#endif
+ ret = n;
+out:
+ kfree(wbuf);
+
+ return ret;
+}
+
+static unsigned int lirc_poll(struct file *file, poll_table *wait)
+{
+ poll_wait(file, &lirc_wait, wait);
+ if (rptr != wptr)
+ return POLLIN | POLLRDNORM;
+ return 0;
+}
+
+static long lirc_ioctl(struct file *filep, unsigned int cmd, unsigned long arg)
+{
+ int result;
+ u32 __user *uptr = (u32 __user *)arg;
+ u32 features = LIRC_CAN_SET_TRANSMITTER_MASK |
+ LIRC_CAN_SEND_PULSE | LIRC_CAN_REC_MODE2;
+ u32 mode;
+ u32 value;
+
+ switch (cmd) {
+ case LIRC_GET_FEATURES:
+ result = put_user(features, uptr);
+ if (result)
+ return result;
+ break;
+ case LIRC_GET_SEND_MODE:
+ result = put_user(LIRC_MODE_PULSE, uptr);
+ if (result)
+ return result;
+ break;
+ case LIRC_GET_REC_MODE:
+ result = put_user(LIRC_MODE_MODE2, uptr);
+ if (result)
+ return result;
+ break;
+ case LIRC_SET_SEND_MODE:
+ result = get_user(mode, uptr);
+ if (result)
+ return result;
+ if (mode != LIRC_MODE_PULSE)
+ return -EINVAL;
+ break;
+ case LIRC_SET_REC_MODE:
+ result = get_user(mode, uptr);
+ if (result)
+ return result;
+ if (mode != LIRC_MODE_MODE2)
+ return -ENOSYS;
+ break;
+ case LIRC_SET_TRANSMITTER_MASK:
+ result = get_user(value, uptr);
+ if (result)
+ return result;
+ if ((value & LIRC_PARALLEL_TRANSMITTER_MASK) != value)
+ return LIRC_PARALLEL_MAX_TRANSMITTERS;
+ tx_mask = value;
+ break;
+ default:
+ return -ENOIOCTLCMD;
+ }
+ return 0;
+}
+
+static int lirc_open(struct inode *node, struct file *filep)
+{
+ if (is_open || !lirc_claim())
+ return -EBUSY;
+
+ parport_enable_irq(pport);
+
+ /* init read ptr */
+ rptr = 0;
+ wptr = 0;
+ lost_irqs = 0;
+
+ is_open = 1;
+ return 0;
+}
+
+static int lirc_close(struct inode *node, struct file *filep)
+{
+ if (is_claimed) {
+ is_claimed = 0;
+ parport_release(ppdevice);
+ }
+ is_open = 0;
+ return 0;
+}
+
+static const struct file_operations lirc_fops = {
+ .owner = THIS_MODULE,
+ .llseek = lirc_lseek,
+ .read = lirc_read,
+ .write = lirc_write,
+ .poll = lirc_poll,
+ .unlocked_ioctl = lirc_ioctl,
+#ifdef CONFIG_COMPAT
+ .compat_ioctl = lirc_ioctl,
+#endif
+ .open = lirc_open,
+ .release = lirc_close
+};
+
+static int set_use_inc(void *data)
+{
+ return 0;
+}
+
+static void set_use_dec(void *data)
+{
+}
+
+static struct lirc_driver driver = {
+ .name = LIRC_DRIVER_NAME,
+ .minor = -1,
+ .code_length = 1,
+ .sample_rate = 0,
+ .data = NULL,
+ .add_to_buf = NULL,
+ .set_use_inc = set_use_inc,
+ .set_use_dec = set_use_dec,
+ .fops = &lirc_fops,
+ .dev = NULL,
+ .owner = THIS_MODULE,
+};
+
+static struct platform_device *lirc_parallel_dev;
+
+static int lirc_parallel_probe(struct platform_device *dev)
+{
+ return 0;
+}
+
+static int lirc_parallel_remove(struct platform_device *dev)
+{
+ return 0;
+}
+
+static int lirc_parallel_suspend(struct platform_device *dev,
+ pm_message_t state)
+{
+ return 0;
+}
+
+static int lirc_parallel_resume(struct platform_device *dev)
+{
+ return 0;
+}
+
+static struct platform_driver lirc_parallel_driver = {
+ .probe = lirc_parallel_probe,
+ .remove = lirc_parallel_remove,
+ .suspend = lirc_parallel_suspend,
+ .resume = lirc_parallel_resume,
+ .driver = {
+ .name = LIRC_DRIVER_NAME,
+ .owner = THIS_MODULE,
+ },
+};
+
+static int pf(void *handle)
+{
+ parport_disable_irq(pport);
+ is_claimed = 0;
+ return 0;
+}
+
+static void kf(void *handle)
+{
+ if (!is_open)
+ return;
+ if (!lirc_claim())
+ return;
+ parport_enable_irq(pport);
+ lirc_off();
+ /* this is a bit annoying when you actually print...*/
+ /*
+ printk(KERN_INFO "%s: reclaimed port\n", LIRC_DRIVER_NAME);
+ */
+}
+
+/*** module initialization and cleanup ***/
+
+static int __init lirc_parallel_init(void)
+{
+ int result;
+
+ result = platform_driver_register(&lirc_parallel_driver);
+ if (result) {
+ pr_notice("platform_driver_register returned %d\n", result);
+ return result;
+ }
+
+ lirc_parallel_dev = platform_device_alloc(LIRC_DRIVER_NAME, 0);
+ if (!lirc_parallel_dev) {
+ result = -ENOMEM;
+ goto exit_driver_unregister;
+ }
+
+ result = platform_device_add(lirc_parallel_dev);
+ if (result)
+ goto exit_device_put;
+
+ pport = parport_find_base(io);
+ if (pport == NULL) {
+ pr_notice("no port at %x found\n", io);
+ result = -ENXIO;
+ goto exit_device_put;
+ }
+ ppdevice = parport_register_device(pport, LIRC_DRIVER_NAME,
+ pf, kf, lirc_lirc_irq_handler, 0, NULL);
+ parport_put_port(pport);
+ if (ppdevice == NULL) {
+ pr_notice("parport_register_device() failed\n");
+ result = -ENXIO;
+ goto exit_device_put;
+ }
+ if (parport_claim(ppdevice) != 0)
+ goto skip_init;
+ is_claimed = 1;
+ out(LIRC_LP_CONTROL, LP_PSELECP|LP_PINITP);
+
+#ifdef LIRC_TIMER
+ if (debug)
+ out(LIRC_PORT_DATA, tx_mask);
+
+ timer = init_lirc_timer();
+
+#if 0 /* continue even if device is offline */
+ if (timer == 0) {
+ is_claimed = 0;
+ parport_release(pport);
+ parport_unregister_device(ppdevice);
+ result = -EIO;
+ goto exit_device_put;
+ }
+
+#endif
+ if (debug)
+ out(LIRC_PORT_DATA, 0);
+#endif
+
+ is_claimed = 0;
+ parport_release(ppdevice);
+ skip_init:
+ driver.dev = &lirc_parallel_dev->dev;
+ driver.minor = lirc_register_driver(&driver);
+ if (driver.minor < 0) {
+ pr_notice("register_chrdev() failed\n");
+ parport_unregister_device(ppdevice);
+ result = -EIO;
+ goto exit_device_put;
+ }
+ pr_info("installed using port 0x%04x irq %d\n", io, irq);
+ return 0;
+
+exit_device_put:
+ platform_device_put(lirc_parallel_dev);
+exit_driver_unregister:
+ platform_driver_unregister(&lirc_parallel_driver);
+ return result;
+}
+
+static void __exit lirc_parallel_exit(void)
+{
+ parport_unregister_device(ppdevice);
+ lirc_unregister_driver(driver.minor);
+
+ platform_device_unregister(lirc_parallel_dev);
+ platform_driver_unregister(&lirc_parallel_driver);
+}
+
+module_init(lirc_parallel_init);
+module_exit(lirc_parallel_exit);
+
+MODULE_DESCRIPTION("Infrared receiver driver for parallel ports.");
+MODULE_AUTHOR("Christoph Bartelmus");
+MODULE_LICENSE("GPL");
+
+module_param(io, int, S_IRUGO);
+MODULE_PARM_DESC(io, "I/O address base (0x3bc, 0x378 or 0x278)");
+
+module_param(irq, int, S_IRUGO);
+MODULE_PARM_DESC(irq, "Interrupt (7 or 5)");
+
+module_param(tx_mask, int, S_IRUGO);
+MODULE_PARM_DESC(tx_maxk, "Transmitter mask (default: 0x01)");
+
+module_param(debug, bool, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(debug, "Enable debugging messages");
+
+module_param(check_pselecd, bool, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(check_pselecd, "Check for printer (default: 0)");
diff --git a/drivers/staging/media/lirc/lirc_parallel.h b/drivers/staging/media/lirc/lirc_parallel.h
new file mode 100644
index 00000000000..4bed6afe063
--- /dev/null
+++ b/drivers/staging/media/lirc/lirc_parallel.h
@@ -0,0 +1,26 @@
+/* lirc_parallel.h */
+
+#ifndef _LIRC_PARALLEL_H
+#define _LIRC_PARALLEL_H
+
+#include <linux/lp.h>
+
+#define LIRC_PORT_LEN 3
+
+#define LIRC_LP_BASE 0
+#define LIRC_LP_STATUS 1
+#define LIRC_LP_CONTROL 2
+
+#define LIRC_PORT_DATA LIRC_LP_BASE /* base */
+#define LIRC_PORT_TIMER LIRC_LP_STATUS /* status port */
+#define LIRC_PORT_TIMER_BIT LP_PBUSY /* busy signal */
+#define LIRC_PORT_SIGNAL LIRC_LP_STATUS /* status port */
+#define LIRC_PORT_SIGNAL_BIT LP_PACK /* ack signal */
+#define LIRC_PORT_IRQ LIRC_LP_CONTROL /* control port */
+
+#define LIRC_SFH506_DELAY 0 /* delay t_phl in usecs */
+
+#define LIRC_PARALLEL_MAX_TRANSMITTERS 8
+#define LIRC_PARALLEL_TRANSMITTER_MASK ((1<<LIRC_PARALLEL_MAX_TRANSMITTERS) - 1)
+
+#endif
diff --git a/drivers/staging/media/lirc/lirc_sasem.c b/drivers/staging/media/lirc/lirc_sasem.c
new file mode 100644
index 00000000000..81f90e17e1e
--- /dev/null
+++ b/drivers/staging/media/lirc/lirc_sasem.c
@@ -0,0 +1,934 @@
+/*
+ * lirc_sasem.c - USB remote support for LIRC
+ * Version 0.5
+ *
+ * Copyright (C) 2004-2005 Oliver Stabel <oliver.stabel@gmx.de>
+ * Tim Davies <tim@opensystems.net.au>
+ *
+ * This driver was derived from:
+ * Venky Raju <dev@venky.ws>
+ * "lirc_imon - "LIRC/VFD driver for Ahanix/Soundgraph IMON IR/VFD"
+ * Paul Miller <pmiller9@users.sourceforge.net>'s 2003-2004
+ * "lirc_atiusb - USB remote support for LIRC"
+ * Culver Consulting Services <henry@culcon.com>'s 2003
+ * "Sasem OnAir VFD/IR USB driver"
+ *
+ *
+ * NOTE - The LCDproc iMon driver should work with this module. More info at
+ * http://www.frogstorm.info/sasem
+ */
+
+/*
+ * 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
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+#include <linux/usb.h>
+
+#include <media/lirc.h>
+#include <media/lirc_dev.h>
+
+
+#define MOD_AUTHOR "Oliver Stabel <oliver.stabel@gmx.de>, " \
+ "Tim Davies <tim@opensystems.net.au>"
+#define MOD_DESC "USB Driver for Sasem Remote Controller V1.1"
+#define MOD_NAME "lirc_sasem"
+#define MOD_VERSION "0.5"
+
+#define VFD_MINOR_BASE 144 /* Same as LCD */
+#define DEVICE_NAME "lcd%d"
+
+#define BUF_CHUNK_SIZE 8
+#define BUF_SIZE 128
+
+#define IOCTL_LCD_CONTRAST 1
+
+/*** P R O T O T Y P E S ***/
+
+/* USB Callback prototypes */
+static int sasem_probe(struct usb_interface *interface,
+ const struct usb_device_id *id);
+static void sasem_disconnect(struct usb_interface *interface);
+static void usb_rx_callback(struct urb *urb);
+static void usb_tx_callback(struct urb *urb);
+
+/* VFD file_operations function prototypes */
+static int vfd_open(struct inode *inode, struct file *file);
+static long vfd_ioctl(struct file *file, unsigned cmd, unsigned long arg);
+static int vfd_close(struct inode *inode, struct file *file);
+static ssize_t vfd_write(struct file *file, const char __user *buf,
+ size_t n_bytes, loff_t *pos);
+
+/* LIRC driver function prototypes */
+static int ir_open(void *data);
+static void ir_close(void *data);
+
+/*** G L O B A L S ***/
+#define SASEM_DATA_BUF_SZ 32
+
+struct sasem_context {
+
+ struct usb_device *dev;
+ int vfd_isopen; /* VFD port has been opened */
+ unsigned int vfd_contrast; /* VFD contrast */
+ int ir_isopen; /* IR port has been opened */
+ int dev_present; /* USB device presence */
+ struct mutex ctx_lock; /* to lock this object */
+ wait_queue_head_t remove_ok; /* For unexpected USB disconnects */
+
+ struct lirc_driver *driver;
+ struct usb_endpoint_descriptor *rx_endpoint;
+ struct usb_endpoint_descriptor *tx_endpoint;
+ struct urb *rx_urb;
+ struct urb *tx_urb;
+ unsigned char usb_rx_buf[8];
+ unsigned char usb_tx_buf[8];
+
+ struct tx_t {
+ unsigned char data_buf[SASEM_DATA_BUF_SZ]; /* user data
+ * buffer */
+ struct completion finished; /* wait for write to finish */
+ atomic_t busy; /* write in progress */
+ int status; /* status of tx completion */
+ } tx;
+
+ /* for dealing with repeat codes (wish there was a toggle bit!) */
+ struct timeval presstime;
+ char lastcode[8];
+ int codesaved;
+};
+
+/* VFD file operations */
+static const struct file_operations vfd_fops = {
+ .owner = THIS_MODULE,
+ .open = &vfd_open,
+ .write = vfd_write,
+ .unlocked_ioctl = &vfd_ioctl,
+ .release = &vfd_close,
+ .llseek = noop_llseek,
+};
+
+/* USB Device ID for Sasem USB Control Board */
+static struct usb_device_id sasem_usb_id_table[] = {
+ /* Sasem USB Control Board */
+ { USB_DEVICE(0x11ba, 0x0101) },
+ /* Terminating entry */
+ {}
+};
+
+/* USB Device data */
+static struct usb_driver sasem_driver = {
+ .name = MOD_NAME,
+ .probe = sasem_probe,
+ .disconnect = sasem_disconnect,
+ .id_table = sasem_usb_id_table,
+};
+
+static struct usb_class_driver sasem_class = {
+ .name = DEVICE_NAME,
+ .fops = &vfd_fops,
+ .minor_base = VFD_MINOR_BASE,
+};
+
+/* to prevent races between open() and disconnect() */
+static DEFINE_MUTEX(disconnect_lock);
+
+static int debug;
+
+
+/*** M O D U L E C O D E ***/
+
+MODULE_AUTHOR(MOD_AUTHOR);
+MODULE_DESCRIPTION(MOD_DESC);
+MODULE_LICENSE("GPL");
+module_param(debug, int, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(debug, "Debug messages: 0=no, 1=yes (default: no)");
+
+static void delete_context(struct sasem_context *context)
+{
+ usb_free_urb(context->tx_urb); /* VFD */
+ usb_free_urb(context->rx_urb); /* IR */
+ lirc_buffer_free(context->driver->rbuf);
+ kfree(context->driver->rbuf);
+ kfree(context->driver);
+ kfree(context);
+
+ if (debug)
+ pr_info("%s: context deleted\n", __func__);
+}
+
+static void deregister_from_lirc(struct sasem_context *context)
+{
+ int retval;
+ int minor = context->driver->minor;
+
+ retval = lirc_unregister_driver(minor);
+ if (retval)
+ pr_err("%s: unable to deregister from lirc (%d)\n",
+ __func__, retval);
+ else
+ pr_info("Deregistered Sasem driver (minor:%d)\n", minor);
+
+}
+
+/**
+ * Called when the VFD device (e.g. /dev/usb/lcd)
+ * is opened by the application.
+ */
+static int vfd_open(struct inode *inode, struct file *file)
+{
+ struct usb_interface *interface;
+ struct sasem_context *context = NULL;
+ int subminor;
+ int retval = 0;
+
+ /* prevent races with disconnect */
+ mutex_lock(&disconnect_lock);
+
+ subminor = iminor(inode);
+ interface = usb_find_interface(&sasem_driver, subminor);
+ if (!interface) {
+ pr_err("%s: could not find interface for minor %d\n",
+ __func__, subminor);
+ retval = -ENODEV;
+ goto exit;
+ }
+ context = usb_get_intfdata(interface);
+
+ if (!context) {
+ dev_err(&interface->dev,
+ "%s: no context found for minor %d\n",
+ __func__, subminor);
+ retval = -ENODEV;
+ goto exit;
+ }
+
+ mutex_lock(&context->ctx_lock);
+
+ if (context->vfd_isopen) {
+ dev_err(&interface->dev,
+ "%s: VFD port is already open", __func__);
+ retval = -EBUSY;
+ } else {
+ context->vfd_isopen = 1;
+ file->private_data = context;
+ dev_info(&interface->dev, "VFD port opened\n");
+ }
+
+ mutex_unlock(&context->ctx_lock);
+
+exit:
+ mutex_unlock(&disconnect_lock);
+ return retval;
+}
+
+/**
+ * Called when the VFD device (e.g. /dev/usb/lcd)
+ * is closed by the application.
+ */
+static long vfd_ioctl(struct file *file, unsigned cmd, unsigned long arg)
+{
+ struct sasem_context *context = NULL;
+
+ context = (struct sasem_context *) file->private_data;
+
+ if (!context) {
+ pr_err("%s: no context for device\n", __func__);
+ return -ENODEV;
+ }
+
+ mutex_lock(&context->ctx_lock);
+
+ switch (cmd) {
+ case IOCTL_LCD_CONTRAST:
+ if (arg > 1000)
+ arg = 1000;
+ context->vfd_contrast = (unsigned int)arg;
+ break;
+ default:
+ pr_info("Unknown IOCTL command\n");
+ mutex_unlock(&context->ctx_lock);
+ return -ENOIOCTLCMD; /* not supported */
+ }
+
+ mutex_unlock(&context->ctx_lock);
+ return 0;
+}
+
+/**
+ * Called when the VFD device (e.g. /dev/usb/lcd)
+ * is closed by the application.
+ */
+static int vfd_close(struct inode *inode, struct file *file)
+{
+ struct sasem_context *context = NULL;
+ int retval = 0;
+
+ context = (struct sasem_context *) file->private_data;
+
+ if (!context) {
+ pr_err("%s: no context for device\n", __func__);
+ return -ENODEV;
+ }
+
+ mutex_lock(&context->ctx_lock);
+
+ if (!context->vfd_isopen) {
+ dev_err(&context->dev->dev, "%s: VFD is not open\n", __func__);
+ retval = -EIO;
+ } else {
+ context->vfd_isopen = 0;
+ dev_info(&context->dev->dev, "VFD port closed\n");
+ if (!context->dev_present && !context->ir_isopen) {
+
+ /* Device disconnected before close and IR port is
+ * not open. If IR port is open, context will be
+ * deleted by ir_close. */
+ mutex_unlock(&context->ctx_lock);
+ delete_context(context);
+ return retval;
+ }
+ }
+
+ mutex_unlock(&context->ctx_lock);
+ return retval;
+}
+
+/**
+ * Sends a packet to the VFD.
+ */
+static int send_packet(struct sasem_context *context)
+{
+ unsigned int pipe;
+ int interval = 0;
+ int retval = 0;
+
+ pipe = usb_sndintpipe(context->dev,
+ context->tx_endpoint->bEndpointAddress);
+ interval = context->tx_endpoint->bInterval;
+
+ usb_fill_int_urb(context->tx_urb, context->dev, pipe,
+ context->usb_tx_buf, sizeof(context->usb_tx_buf),
+ usb_tx_callback, context, interval);
+
+ context->tx_urb->actual_length = 0;
+
+ init_completion(&context->tx.finished);
+ atomic_set(&(context->tx.busy), 1);
+
+ retval = usb_submit_urb(context->tx_urb, GFP_KERNEL);
+ if (retval) {
+ atomic_set(&(context->tx.busy), 0);
+ dev_err(&context->dev->dev, "%s: error submitting urb (%d)\n",
+ __func__, retval);
+ } else {
+ /* Wait for transmission to complete (or abort) */
+ mutex_unlock(&context->ctx_lock);
+ wait_for_completion(&context->tx.finished);
+ mutex_lock(&context->ctx_lock);
+
+ retval = context->tx.status;
+ if (retval)
+ dev_err(&context->dev->dev,
+ "%s: packet tx failed (%d)\n",
+ __func__, retval);
+ }
+
+ return retval;
+}
+
+/**
+ * Writes data to the VFD. The Sasem VFD is 2x16 characters
+ * and requires data in 9 consecutive USB interrupt packets,
+ * each packet carrying 8 bytes.
+ */
+static ssize_t vfd_write(struct file *file, const char __user *buf,
+ size_t n_bytes, loff_t *pos)
+{
+ int i;
+ int retval = 0;
+ struct sasem_context *context;
+ int *data_buf = NULL;
+
+ context = (struct sasem_context *) file->private_data;
+ if (!context) {
+ pr_err("%s: no context for device\n", __func__);
+ return -ENODEV;
+ }
+
+ mutex_lock(&context->ctx_lock);
+
+ if (!context->dev_present) {
+ pr_err("%s: no Sasem device present\n", __func__);
+ retval = -ENODEV;
+ goto exit;
+ }
+
+ if (n_bytes <= 0 || n_bytes > SASEM_DATA_BUF_SZ) {
+ dev_err(&context->dev->dev, "%s: invalid payload size\n",
+ __func__);
+ retval = -EINVAL;
+ goto exit;
+ }
+
+ data_buf = memdup_user((void const __user *)buf, n_bytes);
+ if (IS_ERR(data_buf)) {
+ retval = PTR_ERR(data_buf);
+ goto exit;
+ }
+
+ memcpy(context->tx.data_buf, data_buf, n_bytes);
+
+ /* Pad with spaces */
+ for (i = n_bytes; i < SASEM_DATA_BUF_SZ; ++i)
+ context->tx.data_buf[i] = ' ';
+
+ /* Nine 8 byte packets to be sent */
+ /* NOTE: "\x07\x01\0\0\0\0\0\0" or "\x0c\0\0\0\0\0\0\0"
+ * will clear the VFD */
+ for (i = 0; i < 9; i++) {
+ switch (i) {
+ case 0:
+ memcpy(context->usb_tx_buf, "\x07\0\0\0\0\0\0\0", 8);
+ context->usb_tx_buf[1] = (context->vfd_contrast) ?
+ (0x2B - (context->vfd_contrast - 1) / 250)
+ : 0x2B;
+ break;
+ case 1:
+ memcpy(context->usb_tx_buf, "\x09\x01\0\0\0\0\0\0", 8);
+ break;
+ case 2:
+ memcpy(context->usb_tx_buf, "\x0b\x01\0\0\0\0\0\0", 8);
+ break;
+ case 3:
+ memcpy(context->usb_tx_buf, context->tx.data_buf, 8);
+ break;
+ case 4:
+ memcpy(context->usb_tx_buf,
+ context->tx.data_buf + 8, 8);
+ break;
+ case 5:
+ memcpy(context->usb_tx_buf, "\x09\x01\0\0\0\0\0\0", 8);
+ break;
+ case 6:
+ memcpy(context->usb_tx_buf, "\x0b\x02\0\0\0\0\0\0", 8);
+ break;
+ case 7:
+ memcpy(context->usb_tx_buf,
+ context->tx.data_buf + 16, 8);
+ break;
+ case 8:
+ memcpy(context->usb_tx_buf,
+ context->tx.data_buf + 24, 8);
+ break;
+ }
+ retval = send_packet(context);
+ if (retval) {
+ dev_err(&context->dev->dev,
+ "%s: send packet failed for packet #%d\n",
+ __func__, i);
+ goto exit;
+ }
+ }
+exit:
+
+ mutex_unlock(&context->ctx_lock);
+ kfree(data_buf);
+
+ return (!retval) ? n_bytes : retval;
+}
+
+/**
+ * Callback function for USB core API: transmit data
+ */
+static void usb_tx_callback(struct urb *urb)
+{
+ struct sasem_context *context;
+
+ if (!urb)
+ return;
+ context = (struct sasem_context *) urb->context;
+ if (!context)
+ return;
+
+ context->tx.status = urb->status;
+
+ /* notify waiters that write has finished */
+ atomic_set(&context->tx.busy, 0);
+ complete(&context->tx.finished);
+
+ return;
+}
+
+/**
+ * Called by lirc_dev when the application opens /dev/lirc
+ */
+static int ir_open(void *data)
+{
+ int retval = 0;
+ struct sasem_context *context;
+
+ /* prevent races with disconnect */
+ mutex_lock(&disconnect_lock);
+
+ context = (struct sasem_context *) data;
+
+ mutex_lock(&context->ctx_lock);
+
+ if (context->ir_isopen) {
+ dev_err(&context->dev->dev, "%s: IR port is already open\n",
+ __func__);
+ retval = -EBUSY;
+ goto exit;
+ }
+
+ usb_fill_int_urb(context->rx_urb, context->dev,
+ usb_rcvintpipe(context->dev,
+ context->rx_endpoint->bEndpointAddress),
+ context->usb_rx_buf, sizeof(context->usb_rx_buf),
+ usb_rx_callback, context, context->rx_endpoint->bInterval);
+
+ retval = usb_submit_urb(context->rx_urb, GFP_KERNEL);
+
+ if (retval)
+ dev_err(&context->dev->dev,
+ "%s: usb_submit_urb failed for ir_open (%d)\n",
+ __func__, retval);
+ else {
+ context->ir_isopen = 1;
+ dev_info(&context->dev->dev, "IR port opened\n");
+ }
+
+exit:
+ mutex_unlock(&context->ctx_lock);
+
+ mutex_unlock(&disconnect_lock);
+ return retval;
+}
+
+/**
+ * Called by lirc_dev when the application closes /dev/lirc
+ */
+static void ir_close(void *data)
+{
+ struct sasem_context *context;
+
+ context = (struct sasem_context *)data;
+ if (!context) {
+ pr_err("%s: no context for device\n", __func__);
+ return;
+ }
+
+ mutex_lock(&context->ctx_lock);
+
+ usb_kill_urb(context->rx_urb);
+ context->ir_isopen = 0;
+ pr_info("IR port closed\n");
+
+ if (!context->dev_present) {
+
+ /*
+ * Device disconnected while IR port was
+ * still open. Driver was not deregistered
+ * at disconnect time, so do it now.
+ */
+ deregister_from_lirc(context);
+
+ if (!context->vfd_isopen) {
+
+ mutex_unlock(&context->ctx_lock);
+ delete_context(context);
+ return;
+ }
+ /* If VFD port is open, context will be deleted by vfd_close */
+ }
+
+ mutex_unlock(&context->ctx_lock);
+ return;
+}
+
+/**
+ * Process the incoming packet
+ */
+static void incoming_packet(struct sasem_context *context,
+ struct urb *urb)
+{
+ int len = urb->actual_length;
+ unsigned char *buf = urb->transfer_buffer;
+ long ms;
+ struct timeval tv;
+ int i;
+
+ if (len != 8) {
+ dev_warn(&context->dev->dev,
+ "%s: invalid incoming packet size (%d)\n",
+ __func__, len);
+ return;
+ }
+
+ if (debug) {
+ printk(KERN_INFO "Incoming data: ");
+ for (i = 0; i < 8; ++i)
+ printk(KERN_CONT "%02x ", buf[i]);
+ printk(KERN_CONT "\n");
+ }
+
+ /*
+ * Lirc could deal with the repeat code, but we really need to block it
+ * if it arrives too late. Otherwise we could repeat the wrong code.
+ */
+
+ /* get the time since the last button press */
+ do_gettimeofday(&tv);
+ ms = (tv.tv_sec - context->presstime.tv_sec) * 1000 +
+ (tv.tv_usec - context->presstime.tv_usec) / 1000;
+
+ if (memcmp(buf, "\x08\0\0\0\0\0\0\0", 8) == 0) {
+ /*
+ * the repeat code is being sent, so we copy
+ * the old code to LIRC
+ */
+
+ /*
+ * NOTE: Only if the last code was less than 250ms ago
+ * - no one should be able to push another (undetected) button
+ * in that time and then get a false repeat of the previous
+ * press but it is long enough for a genuine repeat
+ */
+ if ((ms < 250) && (context->codesaved != 0)) {
+ memcpy(buf, &context->lastcode, 8);
+ context->presstime.tv_sec = tv.tv_sec;
+ context->presstime.tv_usec = tv.tv_usec;
+ }
+ } else {
+ /* save the current valid code for repeats */
+ memcpy(&context->lastcode, buf, 8);
+ /*
+ * set flag to signal a valid code was save;
+ * just for safety reasons
+ */
+ context->codesaved = 1;
+ context->presstime.tv_sec = tv.tv_sec;
+ context->presstime.tv_usec = tv.tv_usec;
+ }
+
+ lirc_buffer_write(context->driver->rbuf, buf);
+ wake_up(&context->driver->rbuf->wait_poll);
+}
+
+/**
+ * Callback function for USB core API: receive data
+ */
+static void usb_rx_callback(struct urb *urb)
+{
+ struct sasem_context *context;
+
+ if (!urb)
+ return;
+ context = (struct sasem_context *) urb->context;
+ if (!context)
+ return;
+
+ switch (urb->status) {
+
+ case -ENOENT: /* usbcore unlink successful! */
+ return;
+
+ case 0:
+ if (context->ir_isopen)
+ incoming_packet(context, urb);
+ break;
+
+ default:
+ dev_warn(&urb->dev->dev, "%s: status (%d): ignored",
+ __func__, urb->status);
+ break;
+ }
+
+ usb_submit_urb(context->rx_urb, GFP_ATOMIC);
+ return;
+}
+
+
+
+/**
+ * Callback function for USB core API: Probe
+ */
+static int sasem_probe(struct usb_interface *interface,
+ const struct usb_device_id *id)
+{
+ struct usb_device *dev = NULL;
+ struct usb_host_interface *iface_desc = NULL;
+ struct usb_endpoint_descriptor *rx_endpoint = NULL;
+ struct usb_endpoint_descriptor *tx_endpoint = NULL;
+ struct urb *rx_urb = NULL;
+ struct urb *tx_urb = NULL;
+ struct lirc_driver *driver = NULL;
+ struct lirc_buffer *rbuf = NULL;
+ int lirc_minor = 0;
+ int num_endpoints;
+ int retval = 0;
+ int vfd_ep_found;
+ int ir_ep_found;
+ int alloc_status;
+ struct sasem_context *context = NULL;
+ int i;
+
+ dev_info(&interface->dev, "%s: found Sasem device\n", __func__);
+
+
+ dev = usb_get_dev(interface_to_usbdev(interface));
+ iface_desc = interface->cur_altsetting;
+ num_endpoints = iface_desc->desc.bNumEndpoints;
+
+ /*
+ * Scan the endpoint list and set:
+ * first input endpoint = IR endpoint
+ * first output endpoint = VFD endpoint
+ */
+
+ ir_ep_found = 0;
+ vfd_ep_found = 0;
+
+ for (i = 0; i < num_endpoints && !(ir_ep_found && vfd_ep_found); ++i) {
+
+ struct usb_endpoint_descriptor *ep;
+ int ep_dir;
+ int ep_type;
+ ep = &iface_desc->endpoint [i].desc;
+ ep_dir = ep->bEndpointAddress & USB_ENDPOINT_DIR_MASK;
+ ep_type = ep->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK;
+
+ if (!ir_ep_found &&
+ ep_dir == USB_DIR_IN &&
+ ep_type == USB_ENDPOINT_XFER_INT) {
+
+ rx_endpoint = ep;
+ ir_ep_found = 1;
+ if (debug)
+ dev_info(&interface->dev,
+ "%s: found IR endpoint\n", __func__);
+
+ } else if (!vfd_ep_found &&
+ ep_dir == USB_DIR_OUT &&
+ ep_type == USB_ENDPOINT_XFER_INT) {
+
+ tx_endpoint = ep;
+ vfd_ep_found = 1;
+ if (debug)
+ dev_info(&interface->dev,
+ "%s: found VFD endpoint\n", __func__);
+ }
+ }
+
+ /* Input endpoint is mandatory */
+ if (!ir_ep_found) {
+ dev_err(&interface->dev,
+ "%s: no valid input (IR) endpoint found.\n", __func__);
+ retval = -ENODEV;
+ goto exit;
+ }
+
+ if (!vfd_ep_found)
+ dev_info(&interface->dev,
+ "%s: no valid output (VFD) endpoint found.\n",
+ __func__);
+
+
+ /* Allocate memory */
+ alloc_status = 0;
+
+ context = kzalloc(sizeof(struct sasem_context), GFP_KERNEL);
+ if (!context) {
+ alloc_status = 1;
+ goto alloc_status_switch;
+ }
+ driver = kzalloc(sizeof(struct lirc_driver), GFP_KERNEL);
+ if (!driver) {
+ alloc_status = 2;
+ goto alloc_status_switch;
+ }
+ rbuf = kmalloc(sizeof(struct lirc_buffer), GFP_KERNEL);
+ if (!rbuf) {
+ alloc_status = 3;
+ goto alloc_status_switch;
+ }
+ if (lirc_buffer_init(rbuf, BUF_CHUNK_SIZE, BUF_SIZE)) {
+ dev_err(&interface->dev,
+ "%s: lirc_buffer_init failed\n", __func__);
+ alloc_status = 4;
+ goto alloc_status_switch;
+ }
+ rx_urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!rx_urb) {
+ dev_err(&interface->dev,
+ "%s: usb_alloc_urb failed for IR urb\n", __func__);
+ alloc_status = 5;
+ goto alloc_status_switch;
+ }
+ if (vfd_ep_found) {
+ tx_urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!tx_urb) {
+ dev_err(&interface->dev,
+ "%s: usb_alloc_urb failed for VFD urb",
+ __func__);
+ alloc_status = 6;
+ goto alloc_status_switch;
+ }
+ }
+
+ mutex_init(&context->ctx_lock);
+
+ strcpy(driver->name, MOD_NAME);
+ driver->minor = -1;
+ driver->code_length = 64;
+ driver->sample_rate = 0;
+ driver->features = LIRC_CAN_REC_LIRCCODE;
+ driver->data = context;
+ driver->rbuf = rbuf;
+ driver->set_use_inc = ir_open;
+ driver->set_use_dec = ir_close;
+ driver->dev = &interface->dev;
+ driver->owner = THIS_MODULE;
+
+ mutex_lock(&context->ctx_lock);
+
+ lirc_minor = lirc_register_driver(driver);
+ if (lirc_minor < 0) {
+ dev_err(&interface->dev,
+ "%s: lirc_register_driver failed\n", __func__);
+ alloc_status = 7;
+ retval = lirc_minor;
+ goto unlock;
+ } else
+ dev_info(&interface->dev,
+ "%s: Registered Sasem driver (minor:%d)\n",
+ __func__, lirc_minor);
+
+ /* Needed while unregistering! */
+ driver->minor = lirc_minor;
+
+ context->dev = dev;
+ context->dev_present = 1;
+ context->rx_endpoint = rx_endpoint;
+ context->rx_urb = rx_urb;
+ if (vfd_ep_found) {
+ context->tx_endpoint = tx_endpoint;
+ context->tx_urb = tx_urb;
+ context->vfd_contrast = 1000; /* range 0 - 1000 */
+ }
+ context->driver = driver;
+
+ usb_set_intfdata(interface, context);
+
+ if (vfd_ep_found) {
+
+ if (debug)
+ dev_info(&interface->dev,
+ "Registering VFD with sysfs\n");
+ if (usb_register_dev(interface, &sasem_class))
+ /* Not a fatal error, so ignore */
+ dev_info(&interface->dev,
+ "%s: could not get a minor number for VFD\n",
+ __func__);
+ }
+
+ dev_info(&interface->dev,
+ "%s: Sasem device on usb<%d:%d> initialized\n",
+ __func__, dev->bus->busnum, dev->devnum);
+unlock:
+ mutex_unlock(&context->ctx_lock);
+
+alloc_status_switch:
+ switch (alloc_status) {
+
+ case 7:
+ if (vfd_ep_found)
+ usb_free_urb(tx_urb);
+ case 6:
+ usb_free_urb(rx_urb);
+ /* fall-through */
+ case 5:
+ lirc_buffer_free(rbuf);
+ /* fall-through */
+ case 4:
+ kfree(rbuf);
+ /* fall-through */
+ case 3:
+ kfree(driver);
+ /* fall-through */
+ case 2:
+ kfree(context);
+ context = NULL;
+ /* fall-through */
+ case 1:
+ if (retval == 0)
+ retval = -ENOMEM;
+ }
+
+exit:
+ return retval;
+}
+
+/**
+ * Callback function for USB core API: disconnect
+ */
+static void sasem_disconnect(struct usb_interface *interface)
+{
+ struct sasem_context *context;
+
+ /* prevent races with ir_open()/vfd_open() */
+ mutex_lock(&disconnect_lock);
+
+ context = usb_get_intfdata(interface);
+ mutex_lock(&context->ctx_lock);
+
+ dev_info(&interface->dev, "%s: Sasem device disconnected\n",
+ __func__);
+
+ usb_set_intfdata(interface, NULL);
+ context->dev_present = 0;
+
+ /* Stop reception */
+ usb_kill_urb(context->rx_urb);
+
+ /* Abort ongoing write */
+ if (atomic_read(&context->tx.busy)) {
+
+ usb_kill_urb(context->tx_urb);
+ wait_for_completion(&context->tx.finished);
+ }
+
+ /* De-register from lirc_dev if IR port is not open */
+ if (!context->ir_isopen)
+ deregister_from_lirc(context);
+
+ usb_deregister_dev(interface, &sasem_class);
+
+ mutex_unlock(&context->ctx_lock);
+
+ if (!context->ir_isopen && !context->vfd_isopen)
+ delete_context(context);
+
+ mutex_unlock(&disconnect_lock);
+}
+
+module_usb_driver(sasem_driver);
diff --git a/drivers/staging/media/lirc/lirc_serial.c b/drivers/staging/media/lirc/lirc_serial.c
new file mode 100644
index 00000000000..efe561cd093
--- /dev/null
+++ b/drivers/staging/media/lirc/lirc_serial.c
@@ -0,0 +1,1240 @@
+/*
+ * lirc_serial.c
+ *
+ * lirc_serial - Device driver that records pulse- and pause-lengths
+ * (space-lengths) between DDCD event on a serial port.
+ *
+ * Copyright (C) 1996,97 Ralph Metzler <rjkm@thp.uni-koeln.de>
+ * Copyright (C) 1998 Trent Piepho <xyzzy@u.washington.edu>
+ * Copyright (C) 1998 Ben Pfaff <blp@gnu.org>
+ * Copyright (C) 1999 Christoph Bartelmus <lirc@bartelmus.de>
+ * Copyright (C) 2007 Andrei Tanas <andrei@tanas.ca> (suspend/resume support)
+ * 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
+ *
+ */
+
+/*
+ * Steve's changes to improve transmission fidelity:
+ * - for systems with the rdtsc instruction and the clock counter, a
+ * send_pule that times the pulses directly using the counter.
+ * This means that the LIRC_SERIAL_TRANSMITTER_LATENCY fudge is
+ * not needed. Measurement shows very stable waveform, even where
+ * PCI activity slows the access to the UART, which trips up other
+ * versions.
+ * - For other system, non-integer-microsecond pulse/space lengths,
+ * done using fixed point binary. So, much more accurate carrier
+ * frequency.
+ * - fine tuned transmitter latency, taking advantage of fractional
+ * microseconds in previous change
+ * - Fixed bug in the way transmitter latency was accounted for by
+ * tuning the pulse lengths down - the send_pulse routine ignored
+ * this overhead as it timed the overall pulse length - so the
+ * pulse frequency was right but overall pulse length was too
+ * long. Fixed by accounting for latency on each pulse/space
+ * iteration.
+ *
+ * Steve Davies <steve@daviesfam.org> July 2001
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/signal.h>
+#include <linux/sched.h>
+#include <linux/fs.h>
+#include <linux/interrupt.h>
+#include <linux/ioport.h>
+#include <linux/kernel.h>
+#include <linux/serial_reg.h>
+#include <linux/time.h>
+#include <linux/string.h>
+#include <linux/types.h>
+#include <linux/wait.h>
+#include <linux/mm.h>
+#include <linux/delay.h>
+#include <linux/poll.h>
+#include <linux/platform_device.h>
+#include <linux/gpio.h>
+#include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/fcntl.h>
+#include <linux/spinlock.h>
+
+/* From Intel IXP42X Developer's Manual (#252480-005): */
+/* ftp://download.intel.com/design/network/manuals/25248005.pdf */
+#define UART_IE_IXP42X_UUE 0x40 /* IXP42X UART Unit enable */
+#define UART_IE_IXP42X_RTOIE 0x10 /* IXP42X Receiver Data Timeout int.enable */
+
+#include <media/lirc.h>
+#include <media/lirc_dev.h>
+
+#define LIRC_DRIVER_NAME "lirc_serial"
+
+struct lirc_serial {
+ int signal_pin;
+ int signal_pin_change;
+ u8 on;
+ u8 off;
+ long (*send_pulse)(unsigned long length);
+ void (*send_space)(long length);
+ int features;
+ spinlock_t lock;
+};
+
+#define LIRC_HOMEBREW 0
+#define LIRC_IRDEO 1
+#define LIRC_IRDEO_REMOTE 2
+#define LIRC_ANIMAX 3
+#define LIRC_IGOR 4
+#define LIRC_NSLU2 5
+
+/*** module parameters ***/
+static int type;
+static int io;
+static int irq;
+static bool iommap;
+static int ioshift;
+static bool softcarrier = 1;
+static bool share_irq;
+static bool debug;
+static int sense = -1; /* -1 = auto, 0 = active high, 1 = active low */
+static bool txsense; /* 0 = active high, 1 = active low */
+
+#define dprintk(fmt, args...) \
+ do { \
+ if (debug) \
+ printk(KERN_DEBUG LIRC_DRIVER_NAME ": " \
+ fmt, ## args); \
+ } while (0)
+
+/* forward declarations */
+static long send_pulse_irdeo(unsigned long length);
+static long send_pulse_homebrew(unsigned long length);
+static void send_space_irdeo(long length);
+static void send_space_homebrew(long length);
+
+static struct lirc_serial hardware[] = {
+ [LIRC_HOMEBREW] = {
+ .lock = __SPIN_LOCK_UNLOCKED(hardware[LIRC_HOMEBREW].lock),
+ .signal_pin = UART_MSR_DCD,
+ .signal_pin_change = UART_MSR_DDCD,
+ .on = (UART_MCR_RTS | UART_MCR_OUT2 | UART_MCR_DTR),
+ .off = (UART_MCR_RTS | UART_MCR_OUT2),
+ .send_pulse = send_pulse_homebrew,
+ .send_space = send_space_homebrew,
+#ifdef CONFIG_LIRC_SERIAL_TRANSMITTER
+ .features = (LIRC_CAN_SET_SEND_DUTY_CYCLE |
+ LIRC_CAN_SET_SEND_CARRIER |
+ LIRC_CAN_SEND_PULSE | LIRC_CAN_REC_MODE2)
+#else
+ .features = LIRC_CAN_REC_MODE2
+#endif
+ },
+
+ [LIRC_IRDEO] = {
+ .lock = __SPIN_LOCK_UNLOCKED(hardware[LIRC_IRDEO].lock),
+ .signal_pin = UART_MSR_DSR,
+ .signal_pin_change = UART_MSR_DDSR,
+ .on = UART_MCR_OUT2,
+ .off = (UART_MCR_RTS | UART_MCR_DTR | UART_MCR_OUT2),
+ .send_pulse = send_pulse_irdeo,
+ .send_space = send_space_irdeo,
+ .features = (LIRC_CAN_SET_SEND_DUTY_CYCLE |
+ LIRC_CAN_SEND_PULSE | LIRC_CAN_REC_MODE2)
+ },
+
+ [LIRC_IRDEO_REMOTE] = {
+ .lock = __SPIN_LOCK_UNLOCKED(hardware[LIRC_IRDEO_REMOTE].lock),
+ .signal_pin = UART_MSR_DSR,
+ .signal_pin_change = UART_MSR_DDSR,
+ .on = (UART_MCR_RTS | UART_MCR_DTR | UART_MCR_OUT2),
+ .off = (UART_MCR_RTS | UART_MCR_DTR | UART_MCR_OUT2),
+ .send_pulse = send_pulse_irdeo,
+ .send_space = send_space_irdeo,
+ .features = (LIRC_CAN_SET_SEND_DUTY_CYCLE |
+ LIRC_CAN_SEND_PULSE | LIRC_CAN_REC_MODE2)
+ },
+
+ [LIRC_ANIMAX] = {
+ .lock = __SPIN_LOCK_UNLOCKED(hardware[LIRC_ANIMAX].lock),
+ .signal_pin = UART_MSR_DCD,
+ .signal_pin_change = UART_MSR_DDCD,
+ .on = 0,
+ .off = (UART_MCR_RTS | UART_MCR_DTR | UART_MCR_OUT2),
+ .send_pulse = NULL,
+ .send_space = NULL,
+ .features = LIRC_CAN_REC_MODE2
+ },
+
+ [LIRC_IGOR] = {
+ .lock = __SPIN_LOCK_UNLOCKED(hardware[LIRC_IGOR].lock),
+ .signal_pin = UART_MSR_DSR,
+ .signal_pin_change = UART_MSR_DDSR,
+ .on = (UART_MCR_RTS | UART_MCR_OUT2 | UART_MCR_DTR),
+ .off = (UART_MCR_RTS | UART_MCR_OUT2),
+ .send_pulse = send_pulse_homebrew,
+ .send_space = send_space_homebrew,
+#ifdef CONFIG_LIRC_SERIAL_TRANSMITTER
+ .features = (LIRC_CAN_SET_SEND_DUTY_CYCLE |
+ LIRC_CAN_SET_SEND_CARRIER |
+ LIRC_CAN_SEND_PULSE | LIRC_CAN_REC_MODE2)
+#else
+ .features = LIRC_CAN_REC_MODE2
+#endif
+ },
+};
+
+#define RS_ISR_PASS_LIMIT 256
+
+/*
+ * A long pulse code from a remote might take up to 300 bytes. The
+ * daemon should read the bytes as soon as they are generated, so take
+ * the number of keys you think you can push before the daemon runs
+ * and multiply by 300. The driver will warn you if you overrun this
+ * buffer. If you have a slow computer or non-busmastering IDE disks,
+ * maybe you will need to increase this.
+ */
+
+/* This MUST be a power of two! It has to be larger than 1 as well. */
+
+#define RBUF_LEN 256
+
+static struct timeval lasttv = {0, 0};
+
+static struct lirc_buffer rbuf;
+
+static unsigned int freq = 38000;
+static unsigned int duty_cycle = 50;
+
+/* Initialized in init_timing_params() */
+static unsigned long period;
+static unsigned long pulse_width;
+static unsigned long space_width;
+
+#if defined(__i386__)
+/*
+ * From:
+ * Linux I/O port programming mini-HOWTO
+ * Author: Riku Saikkonen <Riku.Saikkonen@hut.fi>
+ * v, 28 December 1997
+ *
+ * [...]
+ * Actually, a port I/O instruction on most ports in the 0-0x3ff range
+ * takes almost exactly 1 microsecond, so if you're, for example, using
+ * the parallel port directly, just do additional inb()s from that port
+ * to delay.
+ * [...]
+ */
+/* transmitter latency 1.5625us 0x1.90 - this figure arrived at from
+ * comment above plus trimming to match actual measured frequency.
+ * This will be sensitive to cpu speed, though hopefully most of the 1.5us
+ * is spent in the uart access. Still - for reference test machine was a
+ * 1.13GHz Athlon system - Steve
+ */
+
+/*
+ * changed from 400 to 450 as this works better on slower machines;
+ * faster machines will use the rdtsc code anyway
+ */
+#define LIRC_SERIAL_TRANSMITTER_LATENCY 450
+
+#else
+
+/* does anybody have information on other platforms ? */
+/* 256 = 1<<8 */
+#define LIRC_SERIAL_TRANSMITTER_LATENCY 256
+
+#endif /* __i386__ */
+/*
+ * FIXME: should we be using hrtimers instead of this
+ * LIRC_SERIAL_TRANSMITTER_LATENCY nonsense?
+ */
+
+/* fetch serial input packet (1 byte) from register offset */
+static u8 sinp(int offset)
+{
+ if (iommap != 0)
+ /* the register is memory-mapped */
+ offset <<= ioshift;
+
+ return inb(io + offset);
+}
+
+/* write serial output packet (1 byte) of value to register offset */
+static void soutp(int offset, u8 value)
+{
+ if (iommap != 0)
+ /* the register is memory-mapped */
+ offset <<= ioshift;
+
+ outb(value, io + offset);
+}
+
+static void on(void)
+{
+ if (txsense)
+ soutp(UART_MCR, hardware[type].off);
+ else
+ soutp(UART_MCR, hardware[type].on);
+}
+
+static void off(void)
+{
+ if (txsense)
+ soutp(UART_MCR, hardware[type].on);
+ else
+ soutp(UART_MCR, hardware[type].off);
+}
+
+#ifndef MAX_UDELAY_MS
+#define MAX_UDELAY_US 5000
+#else
+#define MAX_UDELAY_US (MAX_UDELAY_MS*1000)
+#endif
+
+static void safe_udelay(unsigned long usecs)
+{
+ while (usecs > MAX_UDELAY_US) {
+ udelay(MAX_UDELAY_US);
+ usecs -= MAX_UDELAY_US;
+ }
+ udelay(usecs);
+}
+
+#ifdef USE_RDTSC
+/*
+ * This is an overflow/precision juggle, complicated in that we can't
+ * do long long divide in the kernel
+ */
+
+/*
+ * When we use the rdtsc instruction to measure clocks, we keep the
+ * pulse and space widths as clock cycles. As this is CPU speed
+ * dependent, the widths must be calculated in init_port and ioctl
+ * time
+ */
+
+/* So send_pulse can quickly convert microseconds to clocks */
+static unsigned long conv_us_to_clocks;
+
+static int init_timing_params(unsigned int new_duty_cycle,
+ unsigned int new_freq)
+{
+ __u64 loops_per_sec, work;
+
+ duty_cycle = new_duty_cycle;
+ freq = new_freq;
+
+ loops_per_sec = __this_cpu_read(cpu.info.loops_per_jiffy);
+ loops_per_sec *= HZ;
+
+ /* How many clocks in a microsecond?, avoiding long long divide */
+ work = loops_per_sec;
+ work *= 4295; /* 4295 = 2^32 / 1e6 */
+ conv_us_to_clocks = (work >> 32);
+
+ /*
+ * Carrier period in clocks, approach good up to 32GHz clock,
+ * gets carrier frequency within 8Hz
+ */
+ period = loops_per_sec >> 3;
+ period /= (freq >> 3);
+
+ /* Derive pulse and space from the period */
+ pulse_width = period * duty_cycle / 100;
+ space_width = period - pulse_width;
+ dprintk("in init_timing_params, freq=%d, duty_cycle=%d, "
+ "clk/jiffy=%ld, pulse=%ld, space=%ld, "
+ "conv_us_to_clocks=%ld\n",
+ freq, duty_cycle, __this_cpu_read(cpu_info.loops_per_jiffy),
+ pulse_width, space_width, conv_us_to_clocks);
+ return 0;
+}
+#else /* ! USE_RDTSC */
+static int init_timing_params(unsigned int new_duty_cycle,
+ unsigned int new_freq)
+{
+/*
+ * period, pulse/space width are kept with 8 binary places -
+ * IE multiplied by 256.
+ */
+ if (256 * 1000000L / new_freq * new_duty_cycle / 100 <=
+ LIRC_SERIAL_TRANSMITTER_LATENCY)
+ return -EINVAL;
+ if (256 * 1000000L / new_freq * (100 - new_duty_cycle) / 100 <=
+ LIRC_SERIAL_TRANSMITTER_LATENCY)
+ return -EINVAL;
+ duty_cycle = new_duty_cycle;
+ freq = new_freq;
+ period = 256 * 1000000L / freq;
+ pulse_width = period * duty_cycle / 100;
+ space_width = period - pulse_width;
+ dprintk("in init_timing_params, freq=%d pulse=%ld, space=%ld\n",
+ freq, pulse_width, space_width);
+ return 0;
+}
+#endif /* USE_RDTSC */
+
+
+/* return value: space length delta */
+
+static long send_pulse_irdeo(unsigned long length)
+{
+ long rawbits, ret;
+ int i;
+ unsigned char output;
+ unsigned char chunk, shifted;
+
+ /* how many bits have to be sent ? */
+ rawbits = length * 1152 / 10000;
+ if (duty_cycle > 50)
+ chunk = 3;
+ else
+ chunk = 1;
+ for (i = 0, output = 0x7f; rawbits > 0; rawbits -= 3) {
+ shifted = chunk << (i * 3);
+ shifted >>= 1;
+ output &= (~shifted);
+ i++;
+ if (i == 3) {
+ soutp(UART_TX, output);
+ while (!(sinp(UART_LSR) & UART_LSR_THRE))
+ ;
+ output = 0x7f;
+ i = 0;
+ }
+ }
+ if (i != 0) {
+ soutp(UART_TX, output);
+ while (!(sinp(UART_LSR) & UART_LSR_TEMT))
+ ;
+ }
+
+ if (i == 0)
+ ret = (-rawbits) * 10000 / 1152;
+ else
+ ret = (3 - i) * 3 * 10000 / 1152 + (-rawbits) * 10000 / 1152;
+
+ return ret;
+}
+
+#ifdef USE_RDTSC
+/* Version that uses Pentium rdtsc instruction to measure clocks */
+
+/*
+ * This version does sub-microsecond timing using rdtsc instruction,
+ * and does away with the fudged LIRC_SERIAL_TRANSMITTER_LATENCY
+ * Implicitly i586 architecture... - Steve
+ */
+
+static long send_pulse_homebrew_softcarrier(unsigned long length)
+{
+ int flag;
+ unsigned long target, start, now;
+
+ /* Get going quick as we can */
+ rdtscl(start);
+ on();
+ /* Convert length from microseconds to clocks */
+ length *= conv_us_to_clocks;
+ /* And loop till time is up - flipping at right intervals */
+ now = start;
+ target = pulse_width;
+ flag = 1;
+ /*
+ * FIXME: This looks like a hard busy wait, without even an occasional,
+ * polite, cpu_relax() call. There's got to be a better way?
+ *
+ * The i2c code has the result of a lot of bit-banging work, I wonder if
+ * there's something there which could be helpful here.
+ */
+ while ((now - start) < length) {
+ /* Delay till flip time */
+ do {
+ rdtscl(now);
+ } while ((now - start) < target);
+
+ /* flip */
+ if (flag) {
+ rdtscl(now);
+ off();
+ target += space_width;
+ } else {
+ rdtscl(now); on();
+ target += pulse_width;
+ }
+ flag = !flag;
+ }
+ rdtscl(now);
+ return ((now - start) - length) / conv_us_to_clocks;
+}
+#else /* ! USE_RDTSC */
+/* Version using udelay() */
+
+/*
+ * here we use fixed point arithmetic, with 8
+ * fractional bits. that gets us within 0.1% or so of the right average
+ * frequency, albeit with some jitter in pulse length - Steve
+ */
+
+/* To match 8 fractional bits used for pulse/space length */
+
+static long send_pulse_homebrew_softcarrier(unsigned long length)
+{
+ int flag;
+ unsigned long actual, target, d;
+ length <<= 8;
+
+ actual = 0; target = 0; flag = 0;
+ while (actual < length) {
+ if (flag) {
+ off();
+ target += space_width;
+ } else {
+ on();
+ target += pulse_width;
+ }
+ d = (target - actual -
+ LIRC_SERIAL_TRANSMITTER_LATENCY + 128) >> 8;
+ /*
+ * Note - we've checked in ioctl that the pulse/space
+ * widths are big enough so that d is > 0
+ */
+ udelay(d);
+ actual += (d << 8) + LIRC_SERIAL_TRANSMITTER_LATENCY;
+ flag = !flag;
+ }
+ return (actual-length) >> 8;
+}
+#endif /* USE_RDTSC */
+
+static long send_pulse_homebrew(unsigned long length)
+{
+ if (length <= 0)
+ return 0;
+
+ if (softcarrier)
+ return send_pulse_homebrew_softcarrier(length);
+ else {
+ on();
+ safe_udelay(length);
+ return 0;
+ }
+}
+
+static void send_space_irdeo(long length)
+{
+ if (length <= 0)
+ return;
+
+ safe_udelay(length);
+}
+
+static void send_space_homebrew(long length)
+{
+ off();
+ if (length <= 0)
+ return;
+ safe_udelay(length);
+}
+
+static void rbwrite(int l)
+{
+ if (lirc_buffer_full(&rbuf)) {
+ /* no new signals will be accepted */
+ dprintk("Buffer overrun\n");
+ return;
+ }
+ lirc_buffer_write(&rbuf, (void *)&l);
+}
+
+static void frbwrite(int l)
+{
+ /* simple noise filter */
+ static int pulse, space;
+ static unsigned int ptr;
+
+ if (ptr > 0 && (l & PULSE_BIT)) {
+ pulse += l & PULSE_MASK;
+ if (pulse > 250) {
+ rbwrite(space);
+ rbwrite(pulse | PULSE_BIT);
+ ptr = 0;
+ pulse = 0;
+ }
+ return;
+ }
+ if (!(l & PULSE_BIT)) {
+ if (ptr == 0) {
+ if (l > 20000) {
+ space = l;
+ ptr++;
+ return;
+ }
+ } else {
+ if (l > 20000) {
+ space += pulse;
+ if (space > PULSE_MASK)
+ space = PULSE_MASK;
+ space += l;
+ if (space > PULSE_MASK)
+ space = PULSE_MASK;
+ pulse = 0;
+ return;
+ }
+ rbwrite(space);
+ rbwrite(pulse | PULSE_BIT);
+ ptr = 0;
+ pulse = 0;
+ }
+ }
+ rbwrite(l);
+}
+
+static irqreturn_t lirc_irq_handler(int i, void *blah)
+{
+ struct timeval tv;
+ int counter, dcd;
+ u8 status;
+ long deltv;
+ int data;
+ static int last_dcd = -1;
+
+ if ((sinp(UART_IIR) & UART_IIR_NO_INT)) {
+ /* not our interrupt */
+ return IRQ_NONE;
+ }
+
+ counter = 0;
+ do {
+ counter++;
+ status = sinp(UART_MSR);
+ if (counter > RS_ISR_PASS_LIMIT) {
+ pr_warn("AIEEEE: We're caught!\n");
+ break;
+ }
+ if ((status & hardware[type].signal_pin_change)
+ && sense != -1) {
+ /* get current time */
+ do_gettimeofday(&tv);
+
+ /* New mode, written by Trent Piepho
+ <xyzzy@u.washington.edu>. */
+
+ /*
+ * The old format was not very portable.
+ * We now use an int to pass pulses
+ * and spaces to user space.
+ *
+ * If PULSE_BIT is set a pulse has been
+ * received, otherwise a space has been
+ * received. The driver needs to know if your
+ * receiver is active high or active low, or
+ * the space/pulse sense could be
+ * inverted. The bits denoted by PULSE_MASK are
+ * the length in microseconds. Lengths greater
+ * than or equal to 16 seconds are clamped to
+ * PULSE_MASK. All other bits are unused.
+ * This is a much simpler interface for user
+ * programs, as well as eliminating "out of
+ * phase" errors with space/pulse
+ * autodetection.
+ */
+
+ /* calc time since last interrupt in microseconds */
+ dcd = (status & hardware[type].signal_pin) ? 1 : 0;
+
+ if (dcd == last_dcd) {
+ pr_warn("ignoring spike: %d %d %lx %lx %lx %lx\n",
+ dcd, sense,
+ tv.tv_sec, lasttv.tv_sec,
+ (unsigned long)tv.tv_usec,
+ (unsigned long)lasttv.tv_usec);
+ continue;
+ }
+
+ deltv = tv.tv_sec-lasttv.tv_sec;
+ if (tv.tv_sec < lasttv.tv_sec ||
+ (tv.tv_sec == lasttv.tv_sec &&
+ tv.tv_usec < lasttv.tv_usec)) {
+ pr_warn("AIEEEE: your clock just jumped backwards\n");
+ pr_warn("%d %d %lx %lx %lx %lx\n",
+ dcd, sense,
+ tv.tv_sec, lasttv.tv_sec,
+ (unsigned long)tv.tv_usec,
+ (unsigned long)lasttv.tv_usec);
+ data = PULSE_MASK;
+ } else if (deltv > 15) {
+ data = PULSE_MASK; /* really long time */
+ if (!(dcd^sense)) {
+ /* sanity check */
+ pr_warn("AIEEEE: %d %d %lx %lx %lx %lx\n",
+ dcd, sense,
+ tv.tv_sec, lasttv.tv_sec,
+ (unsigned long)tv.tv_usec,
+ (unsigned long)lasttv.tv_usec);
+ /*
+ * detecting pulse while this
+ * MUST be a space!
+ */
+ sense = sense ? 0 : 1;
+ }
+ } else
+ data = (int) (deltv*1000000 +
+ tv.tv_usec -
+ lasttv.tv_usec);
+ frbwrite(dcd^sense ? data : (data|PULSE_BIT));
+ lasttv = tv;
+ last_dcd = dcd;
+ wake_up_interruptible(&rbuf.wait_poll);
+ }
+ } while (!(sinp(UART_IIR) & UART_IIR_NO_INT)); /* still pending ? */
+ return IRQ_HANDLED;
+}
+
+
+static int hardware_init_port(void)
+{
+ u8 scratch, scratch2, scratch3;
+
+ /*
+ * This is a simple port existence test, borrowed from the autoconfig
+ * function in drivers/serial/8250.c
+ */
+ scratch = sinp(UART_IER);
+ soutp(UART_IER, 0);
+#ifdef __i386__
+ outb(0xff, 0x080);
+#endif
+ scratch2 = sinp(UART_IER) & 0x0f;
+ soutp(UART_IER, 0x0f);
+#ifdef __i386__
+ outb(0x00, 0x080);
+#endif
+ scratch3 = sinp(UART_IER) & 0x0f;
+ soutp(UART_IER, scratch);
+ if (scratch2 != 0 || scratch3 != 0x0f) {
+ /* we fail, there's nothing here */
+ pr_err("port existence test failed, cannot continue\n");
+ return -ENODEV;
+ }
+
+
+
+ /* Set DLAB 0. */
+ soutp(UART_LCR, sinp(UART_LCR) & (~UART_LCR_DLAB));
+
+ /* First of all, disable all interrupts */
+ soutp(UART_IER, sinp(UART_IER) &
+ (~(UART_IER_MSI|UART_IER_RLSI|UART_IER_THRI|UART_IER_RDI)));
+
+ /* Clear registers. */
+ sinp(UART_LSR);
+ sinp(UART_RX);
+ sinp(UART_IIR);
+ sinp(UART_MSR);
+
+ /* Set line for power source */
+ off();
+
+ /* Clear registers again to be sure. */
+ sinp(UART_LSR);
+ sinp(UART_RX);
+ sinp(UART_IIR);
+ sinp(UART_MSR);
+
+ switch (type) {
+ case LIRC_IRDEO:
+ case LIRC_IRDEO_REMOTE:
+ /* setup port to 7N1 @ 115200 Baud */
+ /* 7N1+start = 9 bits at 115200 ~ 3 bits at 38kHz */
+
+ /* Set DLAB 1. */
+ soutp(UART_LCR, sinp(UART_LCR) | UART_LCR_DLAB);
+ /* Set divisor to 1 => 115200 Baud */
+ soutp(UART_DLM, 0);
+ soutp(UART_DLL, 1);
+ /* Set DLAB 0 + 7N1 */
+ soutp(UART_LCR, UART_LCR_WLEN7);
+ /* THR interrupt already disabled at this point */
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static int lirc_serial_probe(struct platform_device *dev)
+{
+ int i, nlow, nhigh, result;
+
+ result = request_irq(irq, lirc_irq_handler,
+ (share_irq ? IRQF_SHARED : 0),
+ LIRC_DRIVER_NAME, (void *)&hardware);
+ if (result < 0) {
+ if (result == -EBUSY)
+ dev_err(&dev->dev, "IRQ %d busy\n", irq);
+ else if (result == -EINVAL)
+ dev_err(&dev->dev, "Bad irq number or handler\n");
+ return result;
+ }
+
+ /* Reserve io region. */
+ /*
+ * Future MMAP-Developers: Attention!
+ * For memory mapped I/O you *might* need to use ioremap() first,
+ * for the NSLU2 it's done in boot code.
+ */
+ if (((iommap != 0)
+ && (request_mem_region(iommap, 8 << ioshift,
+ LIRC_DRIVER_NAME) == NULL))
+ || ((iommap == 0)
+ && (request_region(io, 8, LIRC_DRIVER_NAME) == NULL))) {
+ dev_err(&dev->dev, "port %04x already in use\n", io);
+ dev_warn(&dev->dev, "use 'setserial /dev/ttySX uart none'\n");
+ dev_warn(&dev->dev,
+ "or compile the serial port driver as module and\n");
+ dev_warn(&dev->dev, "make sure this module is loaded first\n");
+ result = -EBUSY;
+ goto exit_free_irq;
+ }
+
+ result = hardware_init_port();
+ if (result < 0)
+ goto exit_release_region;
+
+ /* Initialize pulse/space widths */
+ init_timing_params(duty_cycle, freq);
+
+ /* If pin is high, then this must be an active low receiver. */
+ if (sense == -1) {
+ /* wait 1/2 sec for the power supply */
+ msleep(500);
+
+ /*
+ * probe 9 times every 0.04s, collect "votes" for
+ * active high/low
+ */
+ nlow = 0;
+ nhigh = 0;
+ for (i = 0; i < 9; i++) {
+ if (sinp(UART_MSR) & hardware[type].signal_pin)
+ nlow++;
+ else
+ nhigh++;
+ msleep(40);
+ }
+ sense = (nlow >= nhigh ? 1 : 0);
+ dev_info(&dev->dev, "auto-detected active %s receiver\n",
+ sense ? "low" : "high");
+ } else
+ dev_info(&dev->dev, "Manually using active %s receiver\n",
+ sense ? "low" : "high");
+
+ dprintk("Interrupt %d, port %04x obtained\n", irq, io);
+ return 0;
+
+exit_release_region:
+ if (iommap != 0)
+ release_mem_region(iommap, 8 << ioshift);
+ else
+ release_region(io, 8);
+exit_free_irq:
+ free_irq(irq, (void *)&hardware);
+
+ return result;
+}
+
+static int lirc_serial_remove(struct platform_device *dev)
+{
+ free_irq(irq, (void *)&hardware);
+
+ if (iommap != 0)
+ release_mem_region(iommap, 8 << ioshift);
+ else
+ release_region(io, 8);
+
+ return 0;
+}
+
+static int set_use_inc(void *data)
+{
+ unsigned long flags;
+
+ /* initialize timestamp */
+ do_gettimeofday(&lasttv);
+
+ spin_lock_irqsave(&hardware[type].lock, flags);
+
+ /* Set DLAB 0. */
+ soutp(UART_LCR, sinp(UART_LCR) & (~UART_LCR_DLAB));
+
+ soutp(UART_IER, sinp(UART_IER)|UART_IER_MSI);
+
+ spin_unlock_irqrestore(&hardware[type].lock, flags);
+
+ return 0;
+}
+
+static void set_use_dec(void *data)
+{ unsigned long flags;
+
+ spin_lock_irqsave(&hardware[type].lock, flags);
+
+ /* Set DLAB 0. */
+ soutp(UART_LCR, sinp(UART_LCR) & (~UART_LCR_DLAB));
+
+ /* First of all, disable all interrupts */
+ soutp(UART_IER, sinp(UART_IER) &
+ (~(UART_IER_MSI|UART_IER_RLSI|UART_IER_THRI|UART_IER_RDI)));
+ spin_unlock_irqrestore(&hardware[type].lock, flags);
+}
+
+static ssize_t lirc_write(struct file *file, const char __user *buf,
+ size_t n, loff_t *ppos)
+{
+ int i, count;
+ unsigned long flags;
+ long delta = 0;
+ int *wbuf;
+
+ if (!(hardware[type].features & LIRC_CAN_SEND_PULSE))
+ return -EPERM;
+
+ count = n / sizeof(int);
+ if (n % sizeof(int) || count % 2 == 0)
+ return -EINVAL;
+ wbuf = memdup_user(buf, n);
+ if (IS_ERR(wbuf))
+ return PTR_ERR(wbuf);
+ spin_lock_irqsave(&hardware[type].lock, flags);
+ if (type == LIRC_IRDEO) {
+ /* DTR, RTS down */
+ on();
+ }
+ for (i = 0; i < count; i++) {
+ if (i%2)
+ hardware[type].send_space(wbuf[i] - delta);
+ else
+ delta = hardware[type].send_pulse(wbuf[i]);
+ }
+ off();
+ spin_unlock_irqrestore(&hardware[type].lock, flags);
+ kfree(wbuf);
+ return n;
+}
+
+static long lirc_ioctl(struct file *filep, unsigned int cmd, unsigned long arg)
+{
+ int result;
+ u32 __user *uptr = (u32 __user *)arg;
+ u32 value;
+
+ switch (cmd) {
+ case LIRC_GET_SEND_MODE:
+ if (!(hardware[type].features&LIRC_CAN_SEND_MASK))
+ return -ENOIOCTLCMD;
+
+ result = put_user(LIRC_SEND2MODE
+ (hardware[type].features&LIRC_CAN_SEND_MASK),
+ uptr);
+ if (result)
+ return result;
+ break;
+
+ case LIRC_SET_SEND_MODE:
+ if (!(hardware[type].features&LIRC_CAN_SEND_MASK))
+ return -ENOIOCTLCMD;
+
+ result = get_user(value, uptr);
+ if (result)
+ return result;
+ /* only LIRC_MODE_PULSE supported */
+ if (value != LIRC_MODE_PULSE)
+ return -EINVAL;
+ break;
+
+ case LIRC_GET_LENGTH:
+ return -ENOIOCTLCMD;
+ break;
+
+ case LIRC_SET_SEND_DUTY_CYCLE:
+ dprintk("SET_SEND_DUTY_CYCLE\n");
+ if (!(hardware[type].features&LIRC_CAN_SET_SEND_DUTY_CYCLE))
+ return -ENOIOCTLCMD;
+
+ result = get_user(value, uptr);
+ if (result)
+ return result;
+ if (value <= 0 || value > 100)
+ return -EINVAL;
+ return init_timing_params(value, freq);
+ break;
+
+ case LIRC_SET_SEND_CARRIER:
+ dprintk("SET_SEND_CARRIER\n");
+ if (!(hardware[type].features&LIRC_CAN_SET_SEND_CARRIER))
+ return -ENOIOCTLCMD;
+
+ result = get_user(value, uptr);
+ if (result)
+ return result;
+ if (value > 500000 || value < 20000)
+ return -EINVAL;
+ return init_timing_params(duty_cycle, value);
+ break;
+
+ default:
+ return lirc_dev_fop_ioctl(filep, cmd, arg);
+ }
+ return 0;
+}
+
+static const struct file_operations lirc_fops = {
+ .owner = THIS_MODULE,
+ .write = lirc_write,
+ .unlocked_ioctl = lirc_ioctl,
+#ifdef CONFIG_COMPAT
+ .compat_ioctl = lirc_ioctl,
+#endif
+ .read = lirc_dev_fop_read,
+ .poll = lirc_dev_fop_poll,
+ .open = lirc_dev_fop_open,
+ .release = lirc_dev_fop_close,
+ .llseek = no_llseek,
+};
+
+static struct lirc_driver driver = {
+ .name = LIRC_DRIVER_NAME,
+ .minor = -1,
+ .code_length = 1,
+ .sample_rate = 0,
+ .data = NULL,
+ .add_to_buf = NULL,
+ .rbuf = &rbuf,
+ .set_use_inc = set_use_inc,
+ .set_use_dec = set_use_dec,
+ .fops = &lirc_fops,
+ .dev = NULL,
+ .owner = THIS_MODULE,
+};
+
+static struct platform_device *lirc_serial_dev;
+
+static int lirc_serial_suspend(struct platform_device *dev,
+ pm_message_t state)
+{
+ /* Set DLAB 0. */
+ soutp(UART_LCR, sinp(UART_LCR) & (~UART_LCR_DLAB));
+
+ /* Disable all interrupts */
+ soutp(UART_IER, sinp(UART_IER) &
+ (~(UART_IER_MSI|UART_IER_RLSI|UART_IER_THRI|UART_IER_RDI)));
+
+ /* Clear registers. */
+ sinp(UART_LSR);
+ sinp(UART_RX);
+ sinp(UART_IIR);
+ sinp(UART_MSR);
+
+ return 0;
+}
+
+/* twisty maze... need a forward-declaration here... */
+static void lirc_serial_exit(void);
+
+static int lirc_serial_resume(struct platform_device *dev)
+{
+ unsigned long flags;
+ int result;
+
+ result = hardware_init_port();
+ if (result < 0)
+ return result;
+
+ spin_lock_irqsave(&hardware[type].lock, flags);
+ /* Enable Interrupt */
+ do_gettimeofday(&lasttv);
+ soutp(UART_IER, sinp(UART_IER)|UART_IER_MSI);
+ off();
+
+ lirc_buffer_clear(&rbuf);
+
+ spin_unlock_irqrestore(&hardware[type].lock, flags);
+
+ return 0;
+}
+
+static struct platform_driver lirc_serial_driver = {
+ .probe = lirc_serial_probe,
+ .remove = lirc_serial_remove,
+ .suspend = lirc_serial_suspend,
+ .resume = lirc_serial_resume,
+ .driver = {
+ .name = "lirc_serial",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init lirc_serial_init(void)
+{
+ int result;
+
+ /* Init read buffer. */
+ result = lirc_buffer_init(&rbuf, sizeof(int), RBUF_LEN);
+ if (result < 0)
+ return result;
+
+ result = platform_driver_register(&lirc_serial_driver);
+ if (result) {
+ printk("lirc register returned %d\n", result);
+ goto exit_buffer_free;
+ }
+
+ lirc_serial_dev = platform_device_alloc("lirc_serial", 0);
+ if (!lirc_serial_dev) {
+ result = -ENOMEM;
+ goto exit_driver_unregister;
+ }
+
+ result = platform_device_add(lirc_serial_dev);
+ if (result)
+ goto exit_device_put;
+
+ return 0;
+
+exit_device_put:
+ platform_device_put(lirc_serial_dev);
+exit_driver_unregister:
+ platform_driver_unregister(&lirc_serial_driver);
+exit_buffer_free:
+ lirc_buffer_free(&rbuf);
+ return result;
+}
+
+static void lirc_serial_exit(void)
+{
+ platform_device_unregister(lirc_serial_dev);
+ platform_driver_unregister(&lirc_serial_driver);
+ lirc_buffer_free(&rbuf);
+}
+
+static int __init lirc_serial_init_module(void)
+{
+ int result;
+
+ switch (type) {
+ case LIRC_HOMEBREW:
+ case LIRC_IRDEO:
+ case LIRC_IRDEO_REMOTE:
+ case LIRC_ANIMAX:
+ case LIRC_IGOR:
+ /* if nothing specified, use ttyS0/com1 and irq 4 */
+ io = io ? io : 0x3f8;
+ irq = irq ? irq : 4;
+ break;
+ default:
+ return -EINVAL;
+ }
+ if (!softcarrier) {
+ switch (type) {
+ case LIRC_HOMEBREW:
+ case LIRC_IGOR:
+ hardware[type].features &=
+ ~(LIRC_CAN_SET_SEND_DUTY_CYCLE|
+ LIRC_CAN_SET_SEND_CARRIER);
+ break;
+ }
+ }
+
+ /* make sure sense is either -1, 0, or 1 */
+ if (sense != -1)
+ sense = !!sense;
+
+ result = lirc_serial_init();
+ if (result)
+ return result;
+
+ driver.features = hardware[type].features;
+ driver.dev = &lirc_serial_dev->dev;
+ driver.minor = lirc_register_driver(&driver);
+ if (driver.minor < 0) {
+ pr_err("register_chrdev failed!\n");
+ lirc_serial_exit();
+ return driver.minor;
+ }
+ return 0;
+}
+
+static void __exit lirc_serial_exit_module(void)
+{
+ lirc_unregister_driver(driver.minor);
+ lirc_serial_exit();
+ dprintk("cleaned up module\n");
+}
+
+
+module_init(lirc_serial_init_module);
+module_exit(lirc_serial_exit_module);
+
+MODULE_DESCRIPTION("Infra-red receiver driver for serial ports.");
+MODULE_AUTHOR("Ralph Metzler, Trent Piepho, Ben Pfaff, "
+ "Christoph Bartelmus, Andrei Tanas");
+MODULE_LICENSE("GPL");
+
+module_param(type, int, S_IRUGO);
+MODULE_PARM_DESC(type, "Hardware type (0 = home-brew, 1 = IRdeo,"
+ " 2 = IRdeo Remote, 3 = AnimaX, 4 = IgorPlug,"
+ " 5 = NSLU2 RX:CTS2/TX:GreenLED)");
+
+module_param(io, int, S_IRUGO);
+MODULE_PARM_DESC(io, "I/O address base (0x3f8 or 0x2f8)");
+
+/* some architectures (e.g. intel xscale) have memory mapped registers */
+module_param(iommap, bool, S_IRUGO);
+MODULE_PARM_DESC(iommap, "physical base for memory mapped I/O"
+ " (0 = no memory mapped io)");
+
+/*
+ * some architectures (e.g. intel xscale) align the 8bit serial registers
+ * on 32bit word boundaries.
+ * See linux-kernel/drivers/tty/serial/8250/8250.c serial_in()/out()
+ */
+module_param(ioshift, int, S_IRUGO);
+MODULE_PARM_DESC(ioshift, "shift I/O register offset (0 = no shift)");
+
+module_param(irq, int, S_IRUGO);
+MODULE_PARM_DESC(irq, "Interrupt (4 or 3)");
+
+module_param(share_irq, bool, S_IRUGO);
+MODULE_PARM_DESC(share_irq, "Share interrupts (0 = off, 1 = on)");
+
+module_param(sense, int, S_IRUGO);
+MODULE_PARM_DESC(sense, "Override autodetection of IR receiver circuit"
+ " (0 = active high, 1 = active low )");
+
+#ifdef CONFIG_LIRC_SERIAL_TRANSMITTER
+module_param(txsense, bool, S_IRUGO);
+MODULE_PARM_DESC(txsense, "Sense of transmitter circuit"
+ " (0 = active high, 1 = active low )");
+#endif
+
+module_param(softcarrier, bool, S_IRUGO);
+MODULE_PARM_DESC(softcarrier, "Software carrier (0 = off, 1 = on, default on)");
+
+module_param(debug, bool, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(debug, "Enable debugging messages");
diff --git a/drivers/staging/media/lirc/lirc_sir.c b/drivers/staging/media/lirc/lirc_sir.c
new file mode 100644
index 00000000000..e31cbb81f05
--- /dev/null
+++ b/drivers/staging/media/lirc/lirc_sir.c
@@ -0,0 +1,1317 @@
+/*
+ * LIRC SIR driver, (C) 2000 Milan Pikula <www@fornax.sk>
+ *
+ * lirc_sir - Device driver for use with SIR (serial infra red)
+ * mode of IrDA on many notebooks.
+ *
+ * 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
+ *
+ *
+ * 2000/09/16 Frank Przybylski <mail@frankprzybylski.de> :
+ * added timeout and relaxed pulse detection, removed gap bug
+ *
+ * 2000/12/15 Christoph Bartelmus <lirc@bartelmus.de> :
+ * added support for Tekram Irmate 210 (sending does not work yet,
+ * kind of disappointing that nobody was able to implement that
+ * before),
+ * major clean-up
+ *
+ * 2001/02/27 Christoph Bartelmus <lirc@bartelmus.de> :
+ * added support for StrongARM SA1100 embedded microprocessor
+ * parts cut'n'pasted from sa1100_ir.c (C) 2000 Russell King
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/sched.h>
+#include <linux/errno.h>
+#include <linux/signal.h>
+#include <linux/fs.h>
+#include <linux/interrupt.h>
+#include <linux/ioport.h>
+#include <linux/kernel.h>
+#include <linux/serial_reg.h>
+#include <linux/time.h>
+#include <linux/string.h>
+#include <linux/types.h>
+#include <linux/wait.h>
+#include <linux/mm.h>
+#include <linux/delay.h>
+#include <linux/poll.h>
+#include <linux/io.h>
+#include <asm/irq.h>
+#include <linux/fcntl.h>
+#include <linux/platform_device.h>
+#ifdef LIRC_ON_SA1100
+#include <asm/hardware.h>
+#ifdef CONFIG_SA1100_COLLIE
+#include <asm/arch/tc35143.h>
+#include <asm/ucb1200.h>
+#endif
+#endif
+
+#include <linux/timer.h>
+
+#include <media/lirc.h>
+#include <media/lirc_dev.h>
+
+/* SECTION: Definitions */
+
+/*** Tekram dongle ***/
+#ifdef LIRC_SIR_TEKRAM
+/* stolen from kernel source */
+/* definitions for Tekram dongle */
+#define TEKRAM_115200 0x00
+#define TEKRAM_57600 0x01
+#define TEKRAM_38400 0x02
+#define TEKRAM_19200 0x03
+#define TEKRAM_9600 0x04
+#define TEKRAM_2400 0x08
+
+#define TEKRAM_PW 0x10 /* Pulse select bit */
+
+/* 10bit * 1s/115200bit in milliseconds = 87ms*/
+#define TIME_CONST (10000000ul/115200ul)
+
+#endif
+
+#ifdef LIRC_SIR_ACTISYS_ACT200L
+static void init_act200(void);
+#elif defined(LIRC_SIR_ACTISYS_ACT220L)
+static void init_act220(void);
+#endif
+
+/*** SA1100 ***/
+#ifdef LIRC_ON_SA1100
+struct sa1100_ser2_registers {
+ /* HSSP control register */
+ unsigned char hscr0;
+ /* UART registers */
+ unsigned char utcr0;
+ unsigned char utcr1;
+ unsigned char utcr2;
+ unsigned char utcr3;
+ unsigned char utcr4;
+ unsigned char utdr;
+ unsigned char utsr0;
+ unsigned char utsr1;
+} sr;
+
+static int irq = IRQ_Ser2ICP;
+
+#define LIRC_ON_SA1100_TRANSMITTER_LATENCY 0
+
+/* pulse/space ratio of 50/50 */
+static unsigned long pulse_width = (13-LIRC_ON_SA1100_TRANSMITTER_LATENCY);
+/* 1000000/freq-pulse_width */
+static unsigned long space_width = (13-LIRC_ON_SA1100_TRANSMITTER_LATENCY);
+static unsigned int freq = 38000; /* modulation frequency */
+static unsigned int duty_cycle = 50; /* duty cycle of 50% */
+
+#endif
+
+#define RBUF_LEN 1024
+#define WBUF_LEN 1024
+
+#define LIRC_DRIVER_NAME "lirc_sir"
+
+#define PULSE '['
+
+#ifndef LIRC_SIR_TEKRAM
+/* 9bit * 1s/115200bit in milli seconds = 78.125ms*/
+#define TIME_CONST (9000000ul/115200ul)
+#endif
+
+
+/* timeout for sequences in jiffies (=5/100s), must be longer than TIME_CONST */
+#define SIR_TIMEOUT (HZ*5/100)
+
+#ifndef LIRC_ON_SA1100
+#ifndef LIRC_IRQ
+#define LIRC_IRQ 4
+#endif
+#ifndef LIRC_PORT
+/* for external dongles, default to com1 */
+#if defined(LIRC_SIR_ACTISYS_ACT200L) || \
+ defined(LIRC_SIR_ACTISYS_ACT220L) || \
+ defined(LIRC_SIR_TEKRAM)
+#define LIRC_PORT 0x3f8
+#else
+/* onboard sir ports are typically com3 */
+#define LIRC_PORT 0x3e8
+#endif
+#endif
+
+static int io = LIRC_PORT;
+static int irq = LIRC_IRQ;
+static int threshold = 3;
+#endif
+
+static DEFINE_SPINLOCK(timer_lock);
+static struct timer_list timerlist;
+/* time of last signal change detected */
+static struct timeval last_tv = {0, 0};
+/* time of last UART data ready interrupt */
+static struct timeval last_intr_tv = {0, 0};
+static int last_value;
+
+static DECLARE_WAIT_QUEUE_HEAD(lirc_read_queue);
+
+static DEFINE_SPINLOCK(hardware_lock);
+
+static int rx_buf[RBUF_LEN];
+static unsigned int rx_tail, rx_head;
+
+static bool debug;
+#define dprintk(fmt, args...) \
+ do { \
+ if (debug) \
+ printk(KERN_DEBUG LIRC_DRIVER_NAME ": " \
+ fmt, ## args); \
+ } while (0)
+
+/* SECTION: Prototypes */
+
+/* Communication with user-space */
+static unsigned int lirc_poll(struct file *file, poll_table *wait);
+static ssize_t lirc_read(struct file *file, char __user *buf, size_t count,
+ loff_t *ppos);
+static ssize_t lirc_write(struct file *file, const char __user *buf, size_t n,
+ loff_t *pos);
+static long lirc_ioctl(struct file *filep, unsigned int cmd, unsigned long arg);
+static void add_read_queue(int flag, unsigned long val);
+static int init_chrdev(void);
+static void drop_chrdev(void);
+/* Hardware */
+static irqreturn_t sir_interrupt(int irq, void *dev_id);
+static void send_space(unsigned long len);
+static void send_pulse(unsigned long len);
+static int init_hardware(void);
+static void drop_hardware(void);
+/* Initialisation */
+static int init_port(void);
+static void drop_port(void);
+
+#ifdef LIRC_ON_SA1100
+static void on(void)
+{
+ PPSR |= PPC_TXD2;
+}
+
+static void off(void)
+{
+ PPSR &= ~PPC_TXD2;
+}
+#else
+static inline unsigned int sinp(int offset)
+{
+ return inb(io + offset);
+}
+
+static inline void soutp(int offset, int value)
+{
+ outb(value, io + offset);
+}
+#endif
+
+#ifndef MAX_UDELAY_MS
+#define MAX_UDELAY_US 5000
+#else
+#define MAX_UDELAY_US (MAX_UDELAY_MS*1000)
+#endif
+
+static void safe_udelay(unsigned long usecs)
+{
+ while (usecs > MAX_UDELAY_US) {
+ udelay(MAX_UDELAY_US);
+ usecs -= MAX_UDELAY_US;
+ }
+ udelay(usecs);
+}
+
+/* SECTION: Communication with user-space */
+
+static unsigned int lirc_poll(struct file *file, poll_table *wait)
+{
+ poll_wait(file, &lirc_read_queue, wait);
+ if (rx_head != rx_tail)
+ return POLLIN | POLLRDNORM;
+ return 0;
+}
+
+static ssize_t lirc_read(struct file *file, char __user *buf, size_t count,
+ loff_t *ppos)
+{
+ int n = 0;
+ int retval = 0;
+ DECLARE_WAITQUEUE(wait, current);
+
+ if (count % sizeof(int))
+ return -EINVAL;
+
+ add_wait_queue(&lirc_read_queue, &wait);
+ set_current_state(TASK_INTERRUPTIBLE);
+ while (n < count) {
+ if (rx_head != rx_tail) {
+ if (copy_to_user(buf + n,
+ rx_buf + rx_head,
+ sizeof(int))) {
+ retval = -EFAULT;
+ break;
+ }
+ rx_head = (rx_head + 1) & (RBUF_LEN - 1);
+ n += sizeof(int);
+ } else {
+ if (file->f_flags & O_NONBLOCK) {
+ retval = -EAGAIN;
+ break;
+ }
+ if (signal_pending(current)) {
+ retval = -ERESTARTSYS;
+ break;
+ }
+ schedule();
+ set_current_state(TASK_INTERRUPTIBLE);
+ }
+ }
+ remove_wait_queue(&lirc_read_queue, &wait);
+ set_current_state(TASK_RUNNING);
+ return n ? n : retval;
+}
+static ssize_t lirc_write(struct file *file, const char __user *buf, size_t n,
+ loff_t *pos)
+{
+ unsigned long flags;
+ int i, count;
+ int *tx_buf;
+
+ count = n / sizeof(int);
+ if (n % sizeof(int) || count % 2 == 0)
+ return -EINVAL;
+ tx_buf = memdup_user(buf, n);
+ if (IS_ERR(tx_buf))
+ return PTR_ERR(tx_buf);
+ i = 0;
+#ifdef LIRC_ON_SA1100
+ /* disable receiver */
+ Ser2UTCR3 = 0;
+#endif
+ local_irq_save(flags);
+ while (1) {
+ if (i >= count)
+ break;
+ if (tx_buf[i])
+ send_pulse(tx_buf[i]);
+ i++;
+ if (i >= count)
+ break;
+ if (tx_buf[i])
+ send_space(tx_buf[i]);
+ i++;
+ }
+ local_irq_restore(flags);
+#ifdef LIRC_ON_SA1100
+ off();
+ udelay(1000); /* wait 1ms for IR diode to recover */
+ Ser2UTCR3 = 0;
+ /* clear status register to prevent unwanted interrupts */
+ Ser2UTSR0 &= (UTSR0_RID | UTSR0_RBB | UTSR0_REB);
+ /* enable receiver */
+ Ser2UTCR3 = UTCR3_RXE|UTCR3_RIE;
+#endif
+ kfree(tx_buf);
+ return count;
+}
+
+static long lirc_ioctl(struct file *filep, unsigned int cmd, unsigned long arg)
+{
+ u32 __user *uptr = (u32 __user *)arg;
+ int retval = 0;
+ u32 value = 0;
+#ifdef LIRC_ON_SA1100
+
+ if (cmd == LIRC_GET_FEATURES)
+ value = LIRC_CAN_SEND_PULSE |
+ LIRC_CAN_SET_SEND_DUTY_CYCLE |
+ LIRC_CAN_SET_SEND_CARRIER |
+ LIRC_CAN_REC_MODE2;
+ else if (cmd == LIRC_GET_SEND_MODE)
+ value = LIRC_MODE_PULSE;
+ else if (cmd == LIRC_GET_REC_MODE)
+ value = LIRC_MODE_MODE2;
+#else
+ if (cmd == LIRC_GET_FEATURES)
+ value = LIRC_CAN_SEND_PULSE | LIRC_CAN_REC_MODE2;
+ else if (cmd == LIRC_GET_SEND_MODE)
+ value = LIRC_MODE_PULSE;
+ else if (cmd == LIRC_GET_REC_MODE)
+ value = LIRC_MODE_MODE2;
+#endif
+
+ switch (cmd) {
+ case LIRC_GET_FEATURES:
+ case LIRC_GET_SEND_MODE:
+ case LIRC_GET_REC_MODE:
+ retval = put_user(value, uptr);
+ break;
+
+ case LIRC_SET_SEND_MODE:
+ case LIRC_SET_REC_MODE:
+ retval = get_user(value, uptr);
+ break;
+#ifdef LIRC_ON_SA1100
+ case LIRC_SET_SEND_DUTY_CYCLE:
+ retval = get_user(value, uptr);
+ if (retval)
+ return retval;
+ if (value <= 0 || value > 100)
+ return -EINVAL;
+ /* (value/100)*(1000000/freq) */
+ duty_cycle = value;
+ pulse_width = (unsigned long) duty_cycle*10000/freq;
+ space_width = (unsigned long) 1000000L/freq-pulse_width;
+ if (pulse_width >= LIRC_ON_SA1100_TRANSMITTER_LATENCY)
+ pulse_width -= LIRC_ON_SA1100_TRANSMITTER_LATENCY;
+ if (space_width >= LIRC_ON_SA1100_TRANSMITTER_LATENCY)
+ space_width -= LIRC_ON_SA1100_TRANSMITTER_LATENCY;
+ break;
+ case LIRC_SET_SEND_CARRIER:
+ retval = get_user(value, uptr);
+ if (retval)
+ return retval;
+ if (value > 500000 || value < 20000)
+ return -EINVAL;
+ freq = value;
+ pulse_width = (unsigned long) duty_cycle*10000/freq;
+ space_width = (unsigned long) 1000000L/freq-pulse_width;
+ if (pulse_width >= LIRC_ON_SA1100_TRANSMITTER_LATENCY)
+ pulse_width -= LIRC_ON_SA1100_TRANSMITTER_LATENCY;
+ if (space_width >= LIRC_ON_SA1100_TRANSMITTER_LATENCY)
+ space_width -= LIRC_ON_SA1100_TRANSMITTER_LATENCY;
+ break;
+#endif
+ default:
+ retval = -ENOIOCTLCMD;
+
+ }
+
+ if (retval)
+ return retval;
+ if (cmd == LIRC_SET_REC_MODE) {
+ if (value != LIRC_MODE_MODE2)
+ retval = -ENOSYS;
+ } else if (cmd == LIRC_SET_SEND_MODE) {
+ if (value != LIRC_MODE_PULSE)
+ retval = -ENOSYS;
+ }
+
+ return retval;
+}
+
+static void add_read_queue(int flag, unsigned long val)
+{
+ unsigned int new_rx_tail;
+ int newval;
+
+ dprintk("add flag %d with val %lu\n", flag, val);
+
+ newval = val & PULSE_MASK;
+
+ /*
+ * statistically, pulses are ~TIME_CONST/2 too long. we could
+ * maybe make this more exact, but this is good enough
+ */
+ if (flag) {
+ /* pulse */
+ if (newval > TIME_CONST/2)
+ newval -= TIME_CONST/2;
+ else /* should not ever happen */
+ newval = 1;
+ newval |= PULSE_BIT;
+ } else {
+ newval += TIME_CONST/2;
+ }
+ new_rx_tail = (rx_tail + 1) & (RBUF_LEN - 1);
+ if (new_rx_tail == rx_head) {
+ dprintk("Buffer overrun.\n");
+ return;
+ }
+ rx_buf[rx_tail] = newval;
+ rx_tail = new_rx_tail;
+ wake_up_interruptible(&lirc_read_queue);
+}
+
+static const struct file_operations lirc_fops = {
+ .owner = THIS_MODULE,
+ .read = lirc_read,
+ .write = lirc_write,
+ .poll = lirc_poll,
+ .unlocked_ioctl = lirc_ioctl,
+#ifdef CONFIG_COMPAT
+ .compat_ioctl = lirc_ioctl,
+#endif
+ .open = lirc_dev_fop_open,
+ .release = lirc_dev_fop_close,
+ .llseek = no_llseek,
+};
+
+static int set_use_inc(void *data)
+{
+ return 0;
+}
+
+static void set_use_dec(void *data)
+{
+}
+
+static struct lirc_driver driver = {
+ .name = LIRC_DRIVER_NAME,
+ .minor = -1,
+ .code_length = 1,
+ .sample_rate = 0,
+ .data = NULL,
+ .add_to_buf = NULL,
+ .set_use_inc = set_use_inc,
+ .set_use_dec = set_use_dec,
+ .fops = &lirc_fops,
+ .dev = NULL,
+ .owner = THIS_MODULE,
+};
+
+static struct platform_device *lirc_sir_dev;
+
+static int init_chrdev(void)
+{
+ driver.dev = &lirc_sir_dev->dev;
+ driver.minor = lirc_register_driver(&driver);
+ if (driver.minor < 0) {
+ pr_err("init_chrdev() failed.\n");
+ return -EIO;
+ }
+ return 0;
+}
+
+static void drop_chrdev(void)
+{
+ lirc_unregister_driver(driver.minor);
+}
+
+/* SECTION: Hardware */
+static long delta(struct timeval *tv1, struct timeval *tv2)
+{
+ unsigned long deltv;
+
+ deltv = tv2->tv_sec - tv1->tv_sec;
+ if (deltv > 15)
+ deltv = 0xFFFFFF;
+ else
+ deltv = deltv*1000000 +
+ tv2->tv_usec -
+ tv1->tv_usec;
+ return deltv;
+}
+
+static void sir_timeout(unsigned long data)
+{
+ /*
+ * if last received signal was a pulse, but receiving stopped
+ * within the 9 bit frame, we need to finish this pulse and
+ * simulate a signal change to from pulse to space. Otherwise
+ * upper layers will receive two sequences next time.
+ */
+
+ unsigned long flags;
+ unsigned long pulse_end;
+
+ /* avoid interference with interrupt */
+ spin_lock_irqsave(&timer_lock, flags);
+ if (last_value) {
+#ifndef LIRC_ON_SA1100
+ /* clear unread bits in UART and restart */
+ outb(UART_FCR_CLEAR_RCVR, io + UART_FCR);
+#endif
+ /* determine 'virtual' pulse end: */
+ pulse_end = delta(&last_tv, &last_intr_tv);
+ dprintk("timeout add %d for %lu usec\n", last_value, pulse_end);
+ add_read_queue(last_value, pulse_end);
+ last_value = 0;
+ last_tv = last_intr_tv;
+ }
+ spin_unlock_irqrestore(&timer_lock, flags);
+}
+
+static irqreturn_t sir_interrupt(int irq, void *dev_id)
+{
+ unsigned char data;
+ struct timeval curr_tv;
+ static unsigned long deltv;
+#ifdef LIRC_ON_SA1100
+ int status;
+ static int n;
+
+ status = Ser2UTSR0;
+ /*
+ * Deal with any receive errors first. The bytes in error may be
+ * the only bytes in the receive FIFO, so we do this first.
+ */
+ while (status & UTSR0_EIF) {
+ int bstat;
+
+ if (debug) {
+ dprintk("EIF\n");
+ bstat = Ser2UTSR1;
+
+ if (bstat & UTSR1_FRE)
+ dprintk("frame error\n");
+ if (bstat & UTSR1_ROR)
+ dprintk("receive fifo overrun\n");
+ if (bstat & UTSR1_PRE)
+ dprintk("parity error\n");
+ }
+
+ bstat = Ser2UTDR;
+ n++;
+ status = Ser2UTSR0;
+ }
+
+ if (status & (UTSR0_RFS | UTSR0_RID)) {
+ do_gettimeofday(&curr_tv);
+ deltv = delta(&last_tv, &curr_tv);
+ do {
+ data = Ser2UTDR;
+ dprintk("%d data: %u\n", n, (unsigned int) data);
+ n++;
+ } while (status & UTSR0_RID && /* do not empty fifo in order to
+ * get UTSR0_RID in any case */
+ Ser2UTSR1 & UTSR1_RNE); /* data ready */
+
+ if (status&UTSR0_RID) {
+ add_read_queue(0 , deltv - n * TIME_CONST); /*space*/
+ add_read_queue(1, n * TIME_CONST); /*pulse*/
+ n = 0;
+ last_tv = curr_tv;
+ }
+ }
+
+ if (status & UTSR0_TFS)
+ pr_err("transmit fifo not full, shouldn't happen\n");
+
+ /* We must clear certain bits. */
+ status &= (UTSR0_RID | UTSR0_RBB | UTSR0_REB);
+ if (status)
+ Ser2UTSR0 = status;
+#else
+ unsigned long deltintrtv;
+ unsigned long flags;
+ int iir, lsr;
+
+ while ((iir = inb(io + UART_IIR) & UART_IIR_ID)) {
+ switch (iir&UART_IIR_ID) { /* FIXME toto treba preriedit */
+ case UART_IIR_MSI:
+ (void) inb(io + UART_MSR);
+ break;
+ case UART_IIR_RLSI:
+ (void) inb(io + UART_LSR);
+ break;
+ case UART_IIR_THRI:
+#if 0
+ if (lsr & UART_LSR_THRE) /* FIFO is empty */
+ outb(data, io + UART_TX)
+#endif
+ break;
+ case UART_IIR_RDI:
+ /* avoid interference with timer */
+ spin_lock_irqsave(&timer_lock, flags);
+ do {
+ del_timer(&timerlist);
+ data = inb(io + UART_RX);
+ do_gettimeofday(&curr_tv);
+ deltv = delta(&last_tv, &curr_tv);
+ deltintrtv = delta(&last_intr_tv, &curr_tv);
+ dprintk("t %lu, d %d\n", deltintrtv, (int)data);
+ /*
+ * if nothing came in last X cycles,
+ * it was gap
+ */
+ if (deltintrtv > TIME_CONST * threshold) {
+ if (last_value) {
+ dprintk("GAP\n");
+ /* simulate signal change */
+ add_read_queue(last_value,
+ deltv -
+ deltintrtv);
+ last_value = 0;
+ last_tv.tv_sec =
+ last_intr_tv.tv_sec;
+ last_tv.tv_usec =
+ last_intr_tv.tv_usec;
+ deltv = deltintrtv;
+ }
+ }
+ data = 1;
+ if (data ^ last_value) {
+ /*
+ * deltintrtv > 2*TIME_CONST, remember?
+ * the other case is timeout
+ */
+ add_read_queue(last_value,
+ deltv-TIME_CONST);
+ last_value = data;
+ last_tv = curr_tv;
+ if (last_tv.tv_usec >= TIME_CONST) {
+ last_tv.tv_usec -= TIME_CONST;
+ } else {
+ last_tv.tv_sec--;
+ last_tv.tv_usec += 1000000 -
+ TIME_CONST;
+ }
+ }
+ last_intr_tv = curr_tv;
+ if (data) {
+ /*
+ * start timer for end of
+ * sequence detection
+ */
+ timerlist.expires = jiffies +
+ SIR_TIMEOUT;
+ add_timer(&timerlist);
+ }
+
+ lsr = inb(io + UART_LSR);
+ } while (lsr & UART_LSR_DR); /* data ready */
+ spin_unlock_irqrestore(&timer_lock, flags);
+ break;
+ default:
+ break;
+ }
+ }
+#endif
+ return IRQ_RETVAL(IRQ_HANDLED);
+}
+
+#ifdef LIRC_ON_SA1100
+static void send_pulse(unsigned long length)
+{
+ unsigned long k, delay;
+ int flag;
+
+ if (length == 0)
+ return;
+ /*
+ * this won't give us the carrier frequency we really want
+ * due to integer arithmetic, but we can accept this inaccuracy
+ */
+
+ for (k = flag = 0; k < length; k += delay, flag = !flag) {
+ if (flag) {
+ off();
+ delay = space_width;
+ } else {
+ on();
+ delay = pulse_width;
+ }
+ safe_udelay(delay);
+ }
+ off();
+}
+
+static void send_space(unsigned long length)
+{
+ if (length == 0)
+ return;
+ off();
+ safe_udelay(length);
+}
+#else
+static void send_space(unsigned long len)
+{
+ safe_udelay(len);
+}
+
+static void send_pulse(unsigned long len)
+{
+ long bytes_out = len / TIME_CONST;
+
+ if (bytes_out == 0)
+ bytes_out++;
+
+ while (bytes_out--) {
+ outb(PULSE, io + UART_TX);
+ /* FIXME treba seriozne cakanie z char/serial.c */
+ while (!(inb(io + UART_LSR) & UART_LSR_THRE))
+ ;
+ }
+}
+#endif
+
+#ifdef CONFIG_SA1100_COLLIE
+static int sa1100_irda_set_power_collie(int state)
+{
+ if (state) {
+ /*
+ * 0 - off
+ * 1 - short range, lowest power
+ * 2 - medium range, medium power
+ * 3 - maximum range, high power
+ */
+ ucb1200_set_io_direction(TC35143_GPIO_IR_ON,
+ TC35143_IODIR_OUTPUT);
+ ucb1200_set_io(TC35143_GPIO_IR_ON, TC35143_IODAT_LOW);
+ udelay(100);
+ } else {
+ /* OFF */
+ ucb1200_set_io_direction(TC35143_GPIO_IR_ON,
+ TC35143_IODIR_OUTPUT);
+ ucb1200_set_io(TC35143_GPIO_IR_ON, TC35143_IODAT_HIGH);
+ }
+ return 0;
+}
+#endif
+
+static int init_hardware(void)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&hardware_lock, flags);
+ /* reset UART */
+#ifdef LIRC_ON_SA1100
+#ifdef CONFIG_SA1100_COLLIE
+ sa1100_irda_set_power_collie(3); /* power on */
+#endif
+ sr.hscr0 = Ser2HSCR0;
+
+ sr.utcr0 = Ser2UTCR0;
+ sr.utcr1 = Ser2UTCR1;
+ sr.utcr2 = Ser2UTCR2;
+ sr.utcr3 = Ser2UTCR3;
+ sr.utcr4 = Ser2UTCR4;
+
+ sr.utdr = Ser2UTDR;
+ sr.utsr0 = Ser2UTSR0;
+ sr.utsr1 = Ser2UTSR1;
+
+ /* configure GPIO */
+ /* output */
+ PPDR |= PPC_TXD2;
+ PSDR |= PPC_TXD2;
+ /* set output to 0 */
+ off();
+
+ /* Enable HP-SIR modulation, and ensure that the port is disabled. */
+ Ser2UTCR3 = 0;
+ Ser2HSCR0 = sr.hscr0 & (~HSCR0_HSSP);
+
+ /* clear status register to prevent unwanted interrupts */
+ Ser2UTSR0 &= (UTSR0_RID | UTSR0_RBB | UTSR0_REB);
+
+ /* 7N1 */
+ Ser2UTCR0 = UTCR0_1StpBit|UTCR0_7BitData;
+ /* 115200 */
+ Ser2UTCR1 = 0;
+ Ser2UTCR2 = 1;
+ /* use HPSIR, 1.6 usec pulses */
+ Ser2UTCR4 = UTCR4_HPSIR|UTCR4_Z1_6us;
+
+ /* enable receiver, receive fifo interrupt */
+ Ser2UTCR3 = UTCR3_RXE|UTCR3_RIE;
+
+ /* clear status register to prevent unwanted interrupts */
+ Ser2UTSR0 &= (UTSR0_RID | UTSR0_RBB | UTSR0_REB);
+
+#elif defined(LIRC_SIR_TEKRAM)
+ /* disable FIFO */
+ soutp(UART_FCR,
+ UART_FCR_CLEAR_RCVR|
+ UART_FCR_CLEAR_XMIT|
+ UART_FCR_TRIGGER_1);
+
+ /* Set DLAB 0. */
+ soutp(UART_LCR, sinp(UART_LCR) & (~UART_LCR_DLAB));
+
+ /* First of all, disable all interrupts */
+ soutp(UART_IER, sinp(UART_IER) &
+ (~(UART_IER_MSI|UART_IER_RLSI|UART_IER_THRI|UART_IER_RDI)));
+
+ /* Set DLAB 1. */
+ soutp(UART_LCR, sinp(UART_LCR) | UART_LCR_DLAB);
+
+ /* Set divisor to 12 => 9600 Baud */
+ soutp(UART_DLM, 0);
+ soutp(UART_DLL, 12);
+
+ /* Set DLAB 0. */
+ soutp(UART_LCR, sinp(UART_LCR) & (~UART_LCR_DLAB));
+
+ /* power supply */
+ soutp(UART_MCR, UART_MCR_RTS|UART_MCR_DTR|UART_MCR_OUT2);
+ safe_udelay(50*1000);
+
+ /* -DTR low -> reset PIC */
+ soutp(UART_MCR, UART_MCR_RTS|UART_MCR_OUT2);
+ udelay(1*1000);
+
+ soutp(UART_MCR, UART_MCR_RTS|UART_MCR_DTR|UART_MCR_OUT2);
+ udelay(100);
+
+
+ /* -RTS low -> send control byte */
+ soutp(UART_MCR, UART_MCR_DTR|UART_MCR_OUT2);
+ udelay(7);
+ soutp(UART_TX, TEKRAM_115200|TEKRAM_PW);
+
+ /* one byte takes ~1042 usec to transmit at 9600,8N1 */
+ udelay(1500);
+
+ /* back to normal operation */
+ soutp(UART_MCR, UART_MCR_RTS|UART_MCR_DTR|UART_MCR_OUT2);
+ udelay(50);
+
+ udelay(1500);
+
+ /* read previous control byte */
+ pr_info("0x%02x\n", sinp(UART_RX));
+
+ /* Set DLAB 1. */
+ soutp(UART_LCR, sinp(UART_LCR) | UART_LCR_DLAB);
+
+ /* Set divisor to 1 => 115200 Baud */
+ soutp(UART_DLM, 0);
+ soutp(UART_DLL, 1);
+
+ /* Set DLAB 0, 8 Bit */
+ soutp(UART_LCR, UART_LCR_WLEN8);
+ /* enable interrupts */
+ soutp(UART_IER, sinp(UART_IER)|UART_IER_RDI);
+#else
+ outb(0, io + UART_MCR);
+ outb(0, io + UART_IER);
+ /* init UART */
+ /* set DLAB, speed = 115200 */
+ outb(UART_LCR_DLAB | UART_LCR_WLEN7, io + UART_LCR);
+ outb(1, io + UART_DLL); outb(0, io + UART_DLM);
+ /* 7N1+start = 9 bits at 115200 ~ 3 bits at 44000 */
+ outb(UART_LCR_WLEN7, io + UART_LCR);
+ /* FIFO operation */
+ outb(UART_FCR_ENABLE_FIFO, io + UART_FCR);
+ /* interrupts */
+ /* outb(UART_IER_RLSI|UART_IER_RDI|UART_IER_THRI, io + UART_IER); */
+ outb(UART_IER_RDI, io + UART_IER);
+ /* turn on UART */
+ outb(UART_MCR_DTR|UART_MCR_RTS|UART_MCR_OUT2, io + UART_MCR);
+#ifdef LIRC_SIR_ACTISYS_ACT200L
+ init_act200();
+#elif defined(LIRC_SIR_ACTISYS_ACT220L)
+ init_act220();
+#endif
+#endif
+ spin_unlock_irqrestore(&hardware_lock, flags);
+ return 0;
+}
+
+static void drop_hardware(void)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&hardware_lock, flags);
+
+#ifdef LIRC_ON_SA1100
+ Ser2UTCR3 = 0;
+
+ Ser2UTCR0 = sr.utcr0;
+ Ser2UTCR1 = sr.utcr1;
+ Ser2UTCR2 = sr.utcr2;
+ Ser2UTCR4 = sr.utcr4;
+ Ser2UTCR3 = sr.utcr3;
+
+ Ser2HSCR0 = sr.hscr0;
+#ifdef CONFIG_SA1100_COLLIE
+ sa1100_irda_set_power_collie(0); /* power off */
+#endif
+#else
+ /* turn off interrupts */
+ outb(0, io + UART_IER);
+#endif
+ spin_unlock_irqrestore(&hardware_lock, flags);
+}
+
+/* SECTION: Initialisation */
+
+static int init_port(void)
+{
+ int retval;
+
+ /* get I/O port access and IRQ line */
+#ifndef LIRC_ON_SA1100
+ if (request_region(io, 8, LIRC_DRIVER_NAME) == NULL) {
+ pr_err("i/o port 0x%.4x already in use.\n", io);
+ return -EBUSY;
+ }
+#endif
+ retval = request_irq(irq, sir_interrupt, 0,
+ LIRC_DRIVER_NAME, NULL);
+ if (retval < 0) {
+# ifndef LIRC_ON_SA1100
+ release_region(io, 8);
+# endif
+ pr_err("IRQ %d already in use.\n", irq);
+ return retval;
+ }
+#ifndef LIRC_ON_SA1100
+ pr_info("I/O port 0x%.4x, IRQ %d.\n", io, irq);
+#endif
+
+ init_timer(&timerlist);
+ timerlist.function = sir_timeout;
+ timerlist.data = 0xabadcafe;
+
+ return 0;
+}
+
+static void drop_port(void)
+{
+ free_irq(irq, NULL);
+ del_timer_sync(&timerlist);
+#ifndef LIRC_ON_SA1100
+ release_region(io, 8);
+#endif
+}
+
+#ifdef LIRC_SIR_ACTISYS_ACT200L
+/* Crystal/Cirrus CS8130 IR transceiver, used in Actisys Act200L dongle */
+/* some code borrowed from Linux IRDA driver */
+
+/* Register 0: Control register #1 */
+#define ACT200L_REG0 0x00
+#define ACT200L_TXEN 0x01 /* Enable transmitter */
+#define ACT200L_RXEN 0x02 /* Enable receiver */
+#define ACT200L_ECHO 0x08 /* Echo control chars */
+
+/* Register 1: Control register #2 */
+#define ACT200L_REG1 0x10
+#define ACT200L_LODB 0x01 /* Load new baud rate count value */
+#define ACT200L_WIDE 0x04 /* Expand the maximum allowable pulse */
+
+/* Register 3: Transmit mode register #2 */
+#define ACT200L_REG3 0x30
+#define ACT200L_B0 0x01 /* DataBits, 0=6, 1=7, 2=8, 3=9(8P) */
+#define ACT200L_B1 0x02 /* DataBits, 0=6, 1=7, 2=8, 3=9(8P) */
+#define ACT200L_CHSY 0x04 /* StartBit Synced 0=bittime, 1=startbit */
+
+/* Register 4: Output Power register */
+#define ACT200L_REG4 0x40
+#define ACT200L_OP0 0x01 /* Enable LED1C output */
+#define ACT200L_OP1 0x02 /* Enable LED2C output */
+#define ACT200L_BLKR 0x04
+
+/* Register 5: Receive Mode register */
+#define ACT200L_REG5 0x50
+#define ACT200L_RWIDL 0x01 /* fixed 1.6us pulse mode */
+ /*.. other various IRDA bit modes, and TV remote modes..*/
+
+/* Register 6: Receive Sensitivity register #1 */
+#define ACT200L_REG6 0x60
+#define ACT200L_RS0 0x01 /* receive threshold bit 0 */
+#define ACT200L_RS1 0x02 /* receive threshold bit 1 */
+
+/* Register 7: Receive Sensitivity register #2 */
+#define ACT200L_REG7 0x70
+#define ACT200L_ENPOS 0x04 /* Ignore the falling edge */
+
+/* Register 8,9: Baud Rate Divider register #1,#2 */
+#define ACT200L_REG8 0x80
+#define ACT200L_REG9 0x90
+
+#define ACT200L_2400 0x5f
+#define ACT200L_9600 0x17
+#define ACT200L_19200 0x0b
+#define ACT200L_38400 0x05
+#define ACT200L_57600 0x03
+#define ACT200L_115200 0x01
+
+/* Register 13: Control register #3 */
+#define ACT200L_REG13 0xd0
+#define ACT200L_SHDW 0x01 /* Enable access to shadow registers */
+
+/* Register 15: Status register */
+#define ACT200L_REG15 0xf0
+
+/* Register 21: Control register #4 */
+#define ACT200L_REG21 0x50
+#define ACT200L_EXCK 0x02 /* Disable clock output driver */
+#define ACT200L_OSCL 0x04 /* oscillator in low power, medium accuracy mode */
+
+static void init_act200(void)
+{
+ int i;
+ __u8 control[] = {
+ ACT200L_REG15,
+ ACT200L_REG13 | ACT200L_SHDW,
+ ACT200L_REG21 | ACT200L_EXCK | ACT200L_OSCL,
+ ACT200L_REG13,
+ ACT200L_REG7 | ACT200L_ENPOS,
+ ACT200L_REG6 | ACT200L_RS0 | ACT200L_RS1,
+ ACT200L_REG5 | ACT200L_RWIDL,
+ ACT200L_REG4 | ACT200L_OP0 | ACT200L_OP1 | ACT200L_BLKR,
+ ACT200L_REG3 | ACT200L_B0,
+ ACT200L_REG0 | ACT200L_TXEN | ACT200L_RXEN,
+ ACT200L_REG8 | (ACT200L_115200 & 0x0f),
+ ACT200L_REG9 | ((ACT200L_115200 >> 4) & 0x0f),
+ ACT200L_REG1 | ACT200L_LODB | ACT200L_WIDE
+ };
+
+ /* Set DLAB 1. */
+ soutp(UART_LCR, UART_LCR_DLAB | UART_LCR_WLEN8);
+
+ /* Set divisor to 12 => 9600 Baud */
+ soutp(UART_DLM, 0);
+ soutp(UART_DLL, 12);
+
+ /* Set DLAB 0. */
+ soutp(UART_LCR, UART_LCR_WLEN8);
+ /* Set divisor to 12 => 9600 Baud */
+
+ /* power supply */
+ soutp(UART_MCR, UART_MCR_RTS|UART_MCR_DTR|UART_MCR_OUT2);
+ for (i = 0; i < 50; i++)
+ safe_udelay(1000);
+
+ /* Reset the dongle : set RTS low for 25 ms */
+ soutp(UART_MCR, UART_MCR_DTR|UART_MCR_OUT2);
+ for (i = 0; i < 25; i++)
+ udelay(1000);
+
+ soutp(UART_MCR, UART_MCR_RTS|UART_MCR_DTR|UART_MCR_OUT2);
+ udelay(100);
+
+ /* Clear DTR and set RTS to enter command mode */
+ soutp(UART_MCR, UART_MCR_RTS|UART_MCR_OUT2);
+ udelay(7);
+
+ /* send out the control register settings for 115K 7N1 SIR operation */
+ for (i = 0; i < sizeof(control); i++) {
+ soutp(UART_TX, control[i]);
+ /* one byte takes ~1042 usec to transmit at 9600,8N1 */
+ udelay(1500);
+ }
+
+ /* back to normal operation */
+ soutp(UART_MCR, UART_MCR_RTS|UART_MCR_DTR|UART_MCR_OUT2);
+ udelay(50);
+
+ udelay(1500);
+ soutp(UART_LCR, sinp(UART_LCR) | UART_LCR_DLAB);
+
+ /* Set DLAB 1. */
+ soutp(UART_LCR, UART_LCR_DLAB | UART_LCR_WLEN7);
+
+ /* Set divisor to 1 => 115200 Baud */
+ soutp(UART_DLM, 0);
+ soutp(UART_DLL, 1);
+
+ /* Set DLAB 0. */
+ soutp(UART_LCR, sinp(UART_LCR) & (~UART_LCR_DLAB));
+
+ /* Set DLAB 0, 7 Bit */
+ soutp(UART_LCR, UART_LCR_WLEN7);
+
+ /* enable interrupts */
+ soutp(UART_IER, sinp(UART_IER)|UART_IER_RDI);
+}
+#endif
+
+#ifdef LIRC_SIR_ACTISYS_ACT220L
+/*
+ * Derived from linux IrDA driver (net/irda/actisys.c)
+ * Drop me a mail for any kind of comment: maxx@spaceboyz.net
+ */
+
+void init_act220(void)
+{
+ int i;
+
+ /* DLAB 1 */
+ soutp(UART_LCR, UART_LCR_DLAB|UART_LCR_WLEN7);
+
+ /* 9600 baud */
+ soutp(UART_DLM, 0);
+ soutp(UART_DLL, 12);
+
+ /* DLAB 0 */
+ soutp(UART_LCR, UART_LCR_WLEN7);
+
+ /* reset the dongle, set DTR low for 10us */
+ soutp(UART_MCR, UART_MCR_RTS|UART_MCR_OUT2);
+ udelay(10);
+
+ /* back to normal (still 9600) */
+ soutp(UART_MCR, UART_MCR_DTR|UART_MCR_RTS|UART_MCR_OUT2);
+
+ /*
+ * send RTS pulses until we reach 115200
+ * i hope this is really the same for act220l/act220l+
+ */
+ for (i = 0; i < 3; i++) {
+ udelay(10);
+ /* set RTS low for 10 us */
+ soutp(UART_MCR, UART_MCR_DTR|UART_MCR_OUT2);
+ udelay(10);
+ /* set RTS high for 10 us */
+ soutp(UART_MCR, UART_MCR_RTS|UART_MCR_DTR|UART_MCR_OUT2);
+ }
+
+ /* back to normal operation */
+ udelay(1500); /* better safe than sorry ;) */
+
+ /* Set DLAB 1. */
+ soutp(UART_LCR, UART_LCR_DLAB | UART_LCR_WLEN7);
+
+ /* Set divisor to 1 => 115200 Baud */
+ soutp(UART_DLM, 0);
+ soutp(UART_DLL, 1);
+
+ /* Set DLAB 0, 7 Bit */
+ /* The dongle doesn't seem to have any problems with operation at 7N1 */
+ soutp(UART_LCR, UART_LCR_WLEN7);
+
+ /* enable interrupts */
+ soutp(UART_IER, UART_IER_RDI);
+}
+#endif
+
+static int init_lirc_sir(void)
+{
+ int retval;
+
+ init_waitqueue_head(&lirc_read_queue);
+ retval = init_port();
+ if (retval < 0)
+ return retval;
+ init_hardware();
+ pr_info("Installed.\n");
+ return 0;
+}
+
+static int lirc_sir_probe(struct platform_device *dev)
+{
+ return 0;
+}
+
+static int lirc_sir_remove(struct platform_device *dev)
+{
+ return 0;
+}
+
+static struct platform_driver lirc_sir_driver = {
+ .probe = lirc_sir_probe,
+ .remove = lirc_sir_remove,
+ .driver = {
+ .name = "lirc_sir",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init lirc_sir_init(void)
+{
+ int retval;
+
+ retval = platform_driver_register(&lirc_sir_driver);
+ if (retval) {
+ pr_err("Platform driver register failed!\n");
+ return -ENODEV;
+ }
+
+ lirc_sir_dev = platform_device_alloc("lirc_dev", 0);
+ if (!lirc_sir_dev) {
+ pr_err("Platform device alloc failed!\n");
+ retval = -ENOMEM;
+ goto pdev_alloc_fail;
+ }
+
+ retval = platform_device_add(lirc_sir_dev);
+ if (retval) {
+ pr_err("Platform device add failed!\n");
+ retval = -ENODEV;
+ goto pdev_add_fail;
+ }
+
+ retval = init_chrdev();
+ if (retval < 0)
+ goto fail;
+
+ retval = init_lirc_sir();
+ if (retval) {
+ drop_chrdev();
+ goto fail;
+ }
+
+ return 0;
+
+fail:
+ platform_device_del(lirc_sir_dev);
+pdev_add_fail:
+ platform_device_put(lirc_sir_dev);
+pdev_alloc_fail:
+ platform_driver_unregister(&lirc_sir_driver);
+ return retval;
+}
+
+static void __exit lirc_sir_exit(void)
+{
+ drop_hardware();
+ drop_chrdev();
+ drop_port();
+ platform_device_unregister(lirc_sir_dev);
+ platform_driver_unregister(&lirc_sir_driver);
+ pr_info("Uninstalled.\n");
+}
+
+module_init(lirc_sir_init);
+module_exit(lirc_sir_exit);
+
+#ifdef LIRC_SIR_TEKRAM
+MODULE_DESCRIPTION("Infrared receiver driver for Tekram Irmate 210");
+MODULE_AUTHOR("Christoph Bartelmus");
+#elif defined(LIRC_ON_SA1100)
+MODULE_DESCRIPTION("LIRC driver for StrongARM SA1100 embedded microprocessor");
+MODULE_AUTHOR("Christoph Bartelmus");
+#elif defined(LIRC_SIR_ACTISYS_ACT200L)
+MODULE_DESCRIPTION("LIRC driver for Actisys Act200L");
+MODULE_AUTHOR("Karl Bongers");
+#elif defined(LIRC_SIR_ACTISYS_ACT220L)
+MODULE_DESCRIPTION("LIRC driver for Actisys Act220L(+)");
+MODULE_AUTHOR("Jan Roemisch");
+#else
+MODULE_DESCRIPTION("Infrared receiver driver for SIR type serial ports");
+MODULE_AUTHOR("Milan Pikula");
+#endif
+MODULE_LICENSE("GPL");
+
+#ifdef LIRC_ON_SA1100
+module_param(irq, int, S_IRUGO);
+MODULE_PARM_DESC(irq, "Interrupt (16)");
+#else
+module_param(io, int, S_IRUGO);
+MODULE_PARM_DESC(io, "I/O address base (0x3f8 or 0x2f8)");
+
+module_param(irq, int, S_IRUGO);
+MODULE_PARM_DESC(irq, "Interrupt (4 or 3)");
+
+module_param(threshold, int, S_IRUGO);
+MODULE_PARM_DESC(threshold, "space detection threshold (3)");
+#endif
+
+module_param(debug, bool, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(debug, "Enable debugging messages");
diff --git a/drivers/staging/media/lirc/lirc_zilog.c b/drivers/staging/media/lirc/lirc_zilog.c
new file mode 100644
index 00000000000..3259aacfd35
--- /dev/null
+++ b/drivers/staging/media/lirc/lirc_zilog.c
@@ -0,0 +1,1686 @@
+/*
+ * i2c IR lirc driver for devices with zilog IR processors
+ *
+ * Copyright (c) 2000 Gerd Knorr <kraxel@goldbach.in-berlin.de>
+ * modified for PixelView (BT878P+W/FM) by
+ * Michal Kochanowicz <mkochano@pld.org.pl>
+ * Christoph Bartelmus <lirc@bartelmus.de>
+ * modified for KNC ONE TV Station/Anubis Typhoon TView Tuner by
+ * Ulrich Mueller <ulrich.mueller42@web.de>
+ * modified for Asus TV-Box and Creative/VisionTek BreakOut-Box by
+ * Stefan Jahn <stefan@lkcc.org>
+ * modified for inclusion into kernel sources by
+ * Jerome Brock <jbrock@users.sourceforge.net>
+ * modified for Leadtek Winfast PVR2000 by
+ * Thomas Reitmayr (treitmayr@yahoo.com)
+ * modified for Hauppauge PVR-150 IR TX device by
+ * Mark Weaver <mark@npsl.co.uk>
+ * changed name from lirc_pvr150 to lirc_zilog, works on more than pvr-150
+ * Jarod Wilson <jarod@redhat.com>
+ *
+ * parts are cut&pasted from the lirc_i2c.c driver
+ *
+ * Numerous changes updating lirc_zilog.c in kernel 2.6.38 and later are
+ * Copyright (C) 2011 Andy Walls <awalls@md.metrocast.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/module.h>
+#include <linux/kmod.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/fs.h>
+#include <linux/poll.h>
+#include <linux/string.h>
+#include <linux/timer.h>
+#include <linux/delay.h>
+#include <linux/completion.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/i2c.h>
+#include <linux/firmware.h>
+#include <linux/vmalloc.h>
+
+#include <linux/mutex.h>
+#include <linux/kthread.h>
+
+#include <media/lirc_dev.h>
+#include <media/lirc.h>
+
+/* Max transfer size done by I2C transfer functions */
+#define MAX_XFER_SIZE 64
+
+struct IR;
+
+struct IR_rx {
+ struct kref ref;
+ struct IR *ir;
+
+ /* RX device */
+ struct mutex client_lock;
+ struct i2c_client *c;
+
+ /* RX polling thread data */
+ struct task_struct *task;
+
+ /* RX read data */
+ unsigned char b[3];
+ bool hdpvr_data_fmt;
+};
+
+struct IR_tx {
+ struct kref ref;
+ struct IR *ir;
+
+ /* TX device */
+ struct mutex client_lock;
+ struct i2c_client *c;
+
+ /* TX additional actions needed */
+ int need_boot;
+ bool post_tx_ready_poll;
+};
+
+struct IR {
+ struct kref ref;
+ struct list_head list;
+
+ /* FIXME spinlock access to l.features */
+ struct lirc_driver l;
+ struct lirc_buffer rbuf;
+
+ struct mutex ir_lock;
+ atomic_t open_count;
+
+ struct i2c_adapter *adapter;
+
+ spinlock_t rx_ref_lock; /* struct IR_rx kref get()/put() */
+ struct IR_rx *rx;
+
+ spinlock_t tx_ref_lock; /* struct IR_tx kref get()/put() */
+ struct IR_tx *tx;
+};
+
+/* IR transceiver instance object list */
+/*
+ * This lock is used for the following:
+ * a. ir_devices_list access, insertions, deletions
+ * b. struct IR kref get()s and put()s
+ * c. serialization of ir_probe() for the two i2c_clients for a Z8
+ */
+static DEFINE_MUTEX(ir_devices_lock);
+static LIST_HEAD(ir_devices_list);
+
+/* Block size for IR transmitter */
+#define TX_BLOCK_SIZE 99
+
+/* Hauppauge IR transmitter data */
+struct tx_data_struct {
+ /* Boot block */
+ unsigned char *boot_data;
+
+ /* Start of binary data block */
+ unsigned char *datap;
+
+ /* End of binary data block */
+ unsigned char *endp;
+
+ /* Number of installed codesets */
+ unsigned int num_code_sets;
+
+ /* Pointers to codesets */
+ unsigned char **code_sets;
+
+ /* Global fixed data template */
+ int fixed[TX_BLOCK_SIZE];
+};
+
+static struct tx_data_struct *tx_data;
+static struct mutex tx_data_lock;
+
+#define zilog_notify(s, args...) printk(KERN_NOTICE KBUILD_MODNAME ": " s, \
+ ## args)
+#define zilog_error(s, args...) printk(KERN_ERR KBUILD_MODNAME ": " s, ## args)
+#define zilog_info(s, args...) printk(KERN_INFO KBUILD_MODNAME ": " s, ## args)
+
+/* module parameters */
+static bool debug; /* debug output */
+static bool tx_only; /* only handle the IR Tx function */
+static int minor = -1; /* minor number */
+
+#define dprintk(fmt, args...) \
+ do { \
+ if (debug) \
+ printk(KERN_DEBUG KBUILD_MODNAME ": " fmt, \
+ ## args); \
+ } while (0)
+
+
+/* struct IR reference counting */
+static struct IR *get_ir_device(struct IR *ir, bool ir_devices_lock_held)
+{
+ if (ir_devices_lock_held) {
+ kref_get(&ir->ref);
+ } else {
+ mutex_lock(&ir_devices_lock);
+ kref_get(&ir->ref);
+ mutex_unlock(&ir_devices_lock);
+ }
+ return ir;
+}
+
+static void release_ir_device(struct kref *ref)
+{
+ struct IR *ir = container_of(ref, struct IR, ref);
+
+ /*
+ * Things should be in this state by now:
+ * ir->rx set to NULL and deallocated - happens before ir->rx->ir put()
+ * ir->rx->task kthread stopped - happens before ir->rx->ir put()
+ * ir->tx set to NULL and deallocated - happens before ir->tx->ir put()
+ * ir->open_count == 0 - happens on final close()
+ * ir_lock, tx_ref_lock, rx_ref_lock, all released
+ */
+ if (ir->l.minor >= 0 && ir->l.minor < MAX_IRCTL_DEVICES) {
+ lirc_unregister_driver(ir->l.minor);
+ ir->l.minor = MAX_IRCTL_DEVICES;
+ }
+ if (ir->rbuf.fifo_initialized)
+ lirc_buffer_free(&ir->rbuf);
+ list_del(&ir->list);
+ kfree(ir);
+}
+
+static int put_ir_device(struct IR *ir, bool ir_devices_lock_held)
+{
+ int released;
+
+ if (ir_devices_lock_held)
+ return kref_put(&ir->ref, release_ir_device);
+
+ mutex_lock(&ir_devices_lock);
+ released = kref_put(&ir->ref, release_ir_device);
+ mutex_unlock(&ir_devices_lock);
+
+ return released;
+}
+
+/* struct IR_rx reference counting */
+static struct IR_rx *get_ir_rx(struct IR *ir)
+{
+ struct IR_rx *rx;
+
+ spin_lock(&ir->rx_ref_lock);
+ rx = ir->rx;
+ if (rx != NULL)
+ kref_get(&rx->ref);
+ spin_unlock(&ir->rx_ref_lock);
+ return rx;
+}
+
+static void destroy_rx_kthread(struct IR_rx *rx, bool ir_devices_lock_held)
+{
+ /* end up polling thread */
+ if (!IS_ERR_OR_NULL(rx->task)) {
+ kthread_stop(rx->task);
+ rx->task = NULL;
+ /* Put the ir ptr that ir_probe() gave to the rx poll thread */
+ put_ir_device(rx->ir, ir_devices_lock_held);
+ }
+}
+
+static void release_ir_rx(struct kref *ref)
+{
+ struct IR_rx *rx = container_of(ref, struct IR_rx, ref);
+ struct IR *ir = rx->ir;
+
+ /*
+ * This release function can't do all the work, as we want
+ * to keep the rx_ref_lock a spinlock, and killing the poll thread
+ * and releasing the ir reference can cause a sleep. That work is
+ * performed by put_ir_rx()
+ */
+ ir->l.features &= ~LIRC_CAN_REC_LIRCCODE;
+ /* Don't put_ir_device(rx->ir) here; lock can't be freed yet */
+ ir->rx = NULL;
+ /* Don't do the kfree(rx) here; we still need to kill the poll thread */
+ return;
+}
+
+static int put_ir_rx(struct IR_rx *rx, bool ir_devices_lock_held)
+{
+ int released;
+ struct IR *ir = rx->ir;
+
+ spin_lock(&ir->rx_ref_lock);
+ released = kref_put(&rx->ref, release_ir_rx);
+ spin_unlock(&ir->rx_ref_lock);
+ /* Destroy the rx kthread while not holding the spinlock */
+ if (released) {
+ destroy_rx_kthread(rx, ir_devices_lock_held);
+ kfree(rx);
+ /* Make sure we're not still in a poll_table somewhere */
+ wake_up_interruptible(&ir->rbuf.wait_poll);
+ }
+ /* Do a reference put() for the rx->ir reference, if we released rx */
+ if (released)
+ put_ir_device(ir, ir_devices_lock_held);
+ return released;
+}
+
+/* struct IR_tx reference counting */
+static struct IR_tx *get_ir_tx(struct IR *ir)
+{
+ struct IR_tx *tx;
+
+ spin_lock(&ir->tx_ref_lock);
+ tx = ir->tx;
+ if (tx != NULL)
+ kref_get(&tx->ref);
+ spin_unlock(&ir->tx_ref_lock);
+ return tx;
+}
+
+static void release_ir_tx(struct kref *ref)
+{
+ struct IR_tx *tx = container_of(ref, struct IR_tx, ref);
+ struct IR *ir = tx->ir;
+
+ ir->l.features &= ~LIRC_CAN_SEND_PULSE;
+ /* Don't put_ir_device(tx->ir) here, so our lock doesn't get freed */
+ ir->tx = NULL;
+ kfree(tx);
+}
+
+static int put_ir_tx(struct IR_tx *tx, bool ir_devices_lock_held)
+{
+ int released;
+ struct IR *ir = tx->ir;
+
+ spin_lock(&ir->tx_ref_lock);
+ released = kref_put(&tx->ref, release_ir_tx);
+ spin_unlock(&ir->tx_ref_lock);
+ /* Do a reference put() for the tx->ir reference, if we released tx */
+ if (released)
+ put_ir_device(ir, ir_devices_lock_held);
+ return released;
+}
+
+static int add_to_buf(struct IR *ir)
+{
+ __u16 code;
+ unsigned char codes[2];
+ unsigned char keybuf[6];
+ int got_data = 0;
+ int ret;
+ int failures = 0;
+ unsigned char sendbuf[1] = { 0 };
+ struct lirc_buffer *rbuf = ir->l.rbuf;
+ struct IR_rx *rx;
+ struct IR_tx *tx;
+
+ if (lirc_buffer_full(rbuf)) {
+ dprintk("buffer overflow\n");
+ return -EOVERFLOW;
+ }
+
+ rx = get_ir_rx(ir);
+ if (rx == NULL)
+ return -ENXIO;
+
+ /* Ensure our rx->c i2c_client remains valid for the duration */
+ mutex_lock(&rx->client_lock);
+ if (rx->c == NULL) {
+ mutex_unlock(&rx->client_lock);
+ put_ir_rx(rx, false);
+ return -ENXIO;
+ }
+
+ tx = get_ir_tx(ir);
+
+ /*
+ * service the device as long as it is returning
+ * data and we have space
+ */
+ do {
+ if (kthread_should_stop()) {
+ ret = -ENODATA;
+ break;
+ }
+
+ /*
+ * Lock i2c bus for the duration. RX/TX chips interfere so
+ * this is worth it
+ */
+ mutex_lock(&ir->ir_lock);
+
+ if (kthread_should_stop()) {
+ mutex_unlock(&ir->ir_lock);
+ ret = -ENODATA;
+ break;
+ }
+
+ /*
+ * Send random "poll command" (?) Windows driver does this
+ * and it is a good point to detect chip failure.
+ */
+ ret = i2c_master_send(rx->c, sendbuf, 1);
+ if (ret != 1) {
+ zilog_error("i2c_master_send failed with %d\n", ret);
+ if (failures >= 3) {
+ mutex_unlock(&ir->ir_lock);
+ zilog_error("unable to read from the IR chip "
+ "after 3 resets, giving up\n");
+ break;
+ }
+
+ /* Looks like the chip crashed, reset it */
+ zilog_error("polling the IR receiver chip failed, "
+ "trying reset\n");
+
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ if (kthread_should_stop()) {
+ mutex_unlock(&ir->ir_lock);
+ ret = -ENODATA;
+ break;
+ }
+ schedule_timeout((100 * HZ + 999) / 1000);
+ if (tx != NULL)
+ tx->need_boot = 1;
+
+ ++failures;
+ mutex_unlock(&ir->ir_lock);
+ ret = 0;
+ continue;
+ }
+
+ if (kthread_should_stop()) {
+ mutex_unlock(&ir->ir_lock);
+ ret = -ENODATA;
+ break;
+ }
+ ret = i2c_master_recv(rx->c, keybuf, sizeof(keybuf));
+ mutex_unlock(&ir->ir_lock);
+ if (ret != sizeof(keybuf)) {
+ zilog_error("i2c_master_recv failed with %d -- "
+ "keeping last read buffer\n", ret);
+ } else {
+ rx->b[0] = keybuf[3];
+ rx->b[1] = keybuf[4];
+ rx->b[2] = keybuf[5];
+ dprintk("key (0x%02x/0x%02x)\n", rx->b[0], rx->b[1]);
+ }
+
+ /* key pressed ? */
+ if (rx->hdpvr_data_fmt) {
+ if (got_data && (keybuf[0] == 0x80)) {
+ ret = 0;
+ break;
+ } else if (got_data && (keybuf[0] == 0x00)) {
+ ret = -ENODATA;
+ break;
+ }
+ } else if ((rx->b[0] & 0x80) == 0) {
+ ret = got_data ? 0 : -ENODATA;
+ break;
+ }
+
+ /* look what we have */
+ code = (((__u16)rx->b[0] & 0x7f) << 6) | (rx->b[1] >> 2);
+
+ codes[0] = (code >> 8) & 0xff;
+ codes[1] = code & 0xff;
+
+ /* return it */
+ lirc_buffer_write(rbuf, codes);
+ ++got_data;
+ ret = 0;
+ } while (!lirc_buffer_full(rbuf));
+
+ mutex_unlock(&rx->client_lock);
+ if (tx != NULL)
+ put_ir_tx(tx, false);
+ put_ir_rx(rx, false);
+ return ret;
+}
+
+/*
+ * Main function of the polling thread -- from lirc_dev.
+ * We don't fit the LIRC model at all anymore. This is horrible, but
+ * basically we have a single RX/TX device with a nasty failure mode
+ * that needs to be accounted for across the pair. lirc lets us provide
+ * fops, but prevents us from using the internal polling, etc. if we do
+ * so. Hence the replication. Might be neater to extend the LIRC model
+ * to account for this but I'd think it's a very special case of seriously
+ * messed up hardware.
+ */
+static int lirc_thread(void *arg)
+{
+ struct IR *ir = arg;
+ struct lirc_buffer *rbuf = ir->l.rbuf;
+
+ dprintk("poll thread started\n");
+
+ while (!kthread_should_stop()) {
+ set_current_state(TASK_INTERRUPTIBLE);
+
+ /* if device not opened, we can sleep half a second */
+ if (atomic_read(&ir->open_count) == 0) {
+ schedule_timeout(HZ/2);
+ continue;
+ }
+
+ /*
+ * This is ~113*2 + 24 + jitter (2*repeat gap + code length).
+ * We use this interval as the chip resets every time you poll
+ * it (bad!). This is therefore just sufficient to catch all
+ * of the button presses. It makes the remote much more
+ * responsive. You can see the difference by running irw and
+ * holding down a button. With 100ms, the old polling
+ * interval, you'll notice breaks in the repeat sequence
+ * corresponding to lost keypresses.
+ */
+ schedule_timeout((260 * HZ) / 1000);
+ if (kthread_should_stop())
+ break;
+ if (!add_to_buf(ir))
+ wake_up_interruptible(&rbuf->wait_poll);
+ }
+
+ dprintk("poll thread ended\n");
+ return 0;
+}
+
+static int set_use_inc(void *data)
+{
+ return 0;
+}
+
+static void set_use_dec(void *data)
+{
+ return;
+}
+
+/* safe read of a uint32 (always network byte order) */
+static int read_uint32(unsigned char **data,
+ unsigned char *endp, unsigned int *val)
+{
+ if (*data + 4 > endp)
+ return 0;
+ *val = ((*data)[0] << 24) | ((*data)[1] << 16) |
+ ((*data)[2] << 8) | (*data)[3];
+ *data += 4;
+ return 1;
+}
+
+/* safe read of a uint8 */
+static int read_uint8(unsigned char **data,
+ unsigned char *endp, unsigned char *val)
+{
+ if (*data + 1 > endp)
+ return 0;
+ *val = *((*data)++);
+ return 1;
+}
+
+/* safe skipping of N bytes */
+static int skip(unsigned char **data,
+ unsigned char *endp, unsigned int distance)
+{
+ if (*data + distance > endp)
+ return 0;
+ *data += distance;
+ return 1;
+}
+
+/* decompress key data into the given buffer */
+static int get_key_data(unsigned char *buf,
+ unsigned int codeset, unsigned int key)
+{
+ unsigned char *data, *endp, *diffs, *key_block;
+ unsigned char keys, ndiffs, id;
+ unsigned int base, lim, pos, i;
+
+ /* Binary search for the codeset */
+ for (base = 0, lim = tx_data->num_code_sets; lim; lim >>= 1) {
+ pos = base + (lim >> 1);
+ data = tx_data->code_sets[pos];
+
+ if (!read_uint32(&data, tx_data->endp, &i))
+ goto corrupt;
+
+ if (i == codeset)
+ break;
+ else if (codeset > i) {
+ base = pos + 1;
+ --lim;
+ }
+ }
+ /* Not found? */
+ if (!lim)
+ return -EPROTO;
+
+ /* Set end of data block */
+ endp = pos < tx_data->num_code_sets - 1 ?
+ tx_data->code_sets[pos + 1] : tx_data->endp;
+
+ /* Read the block header */
+ if (!read_uint8(&data, endp, &keys) ||
+ !read_uint8(&data, endp, &ndiffs) ||
+ ndiffs > TX_BLOCK_SIZE || keys == 0)
+ goto corrupt;
+
+ /* Save diffs & skip */
+ diffs = data;
+ if (!skip(&data, endp, ndiffs))
+ goto corrupt;
+
+ /* Read the id of the first key */
+ if (!read_uint8(&data, endp, &id))
+ goto corrupt;
+
+ /* Unpack the first key's data */
+ for (i = 0; i < TX_BLOCK_SIZE; ++i) {
+ if (tx_data->fixed[i] == -1) {
+ if (!read_uint8(&data, endp, &buf[i]))
+ goto corrupt;
+ } else {
+ buf[i] = (unsigned char)tx_data->fixed[i];
+ }
+ }
+
+ /* Early out key found/not found */
+ if (key == id)
+ return 0;
+ if (keys == 1)
+ return -EPROTO;
+
+ /* Sanity check */
+ key_block = data;
+ if (!skip(&data, endp, (keys - 1) * (ndiffs + 1)))
+ goto corrupt;
+
+ /* Binary search for the key */
+ for (base = 0, lim = keys - 1; lim; lim >>= 1) {
+ /* Seek to block */
+ unsigned char *key_data;
+ pos = base + (lim >> 1);
+ key_data = key_block + (ndiffs + 1) * pos;
+
+ if (*key_data == key) {
+ /* skip key id */
+ ++key_data;
+
+ /* found, so unpack the diffs */
+ for (i = 0; i < ndiffs; ++i) {
+ unsigned char val;
+ if (!read_uint8(&key_data, endp, &val) ||
+ diffs[i] >= TX_BLOCK_SIZE)
+ goto corrupt;
+ buf[diffs[i]] = val;
+ }
+
+ return 0;
+ } else if (key > *key_data) {
+ base = pos + 1;
+ --lim;
+ }
+ }
+ /* Key not found */
+ return -EPROTO;
+
+corrupt:
+ zilog_error("firmware is corrupt\n");
+ return -EFAULT;
+}
+
+/* send a block of data to the IR TX device */
+static int send_data_block(struct IR_tx *tx, unsigned char *data_block)
+{
+ int i, j, ret;
+ unsigned char buf[5];
+
+ for (i = 0; i < TX_BLOCK_SIZE;) {
+ int tosend = TX_BLOCK_SIZE - i;
+ if (tosend > 4)
+ tosend = 4;
+ buf[0] = (unsigned char)(i + 1);
+ for (j = 0; j < tosend; ++j)
+ buf[1 + j] = data_block[i + j];
+ dprintk("%*ph", 5, buf);
+ ret = i2c_master_send(tx->c, buf, tosend + 1);
+ if (ret != tosend + 1) {
+ zilog_error("i2c_master_send failed with %d\n", ret);
+ return ret < 0 ? ret : -EFAULT;
+ }
+ i += tosend;
+ }
+ return 0;
+}
+
+/* send boot data to the IR TX device */
+static int send_boot_data(struct IR_tx *tx)
+{
+ int ret, i;
+ unsigned char buf[4];
+
+ /* send the boot block */
+ ret = send_data_block(tx, tx_data->boot_data);
+ if (ret != 0)
+ return ret;
+
+ /* Hit the go button to activate the new boot data */
+ buf[0] = 0x00;
+ buf[1] = 0x20;
+ ret = i2c_master_send(tx->c, buf, 2);
+ if (ret != 2) {
+ zilog_error("i2c_master_send failed with %d\n", ret);
+ return ret < 0 ? ret : -EFAULT;
+ }
+
+ /*
+ * Wait for zilog to settle after hitting go post boot block upload.
+ * Without this delay, the HD-PVR and HVR-1950 both return an -EIO
+ * upon attempting to get firmware revision, and tx probe thus fails.
+ */
+ for (i = 0; i < 10; i++) {
+ ret = i2c_master_send(tx->c, buf, 1);
+ if (ret == 1)
+ break;
+ udelay(100);
+ }
+
+ if (ret != 1) {
+ zilog_error("i2c_master_send failed with %d\n", ret);
+ return ret < 0 ? ret : -EFAULT;
+ }
+
+ /* Here comes the firmware version... (hopefully) */
+ ret = i2c_master_recv(tx->c, buf, 4);
+ if (ret != 4) {
+ zilog_error("i2c_master_recv failed with %d\n", ret);
+ return 0;
+ }
+ if ((buf[0] != 0x80) && (buf[0] != 0xa0)) {
+ zilog_error("unexpected IR TX init response: %02x\n", buf[0]);
+ return 0;
+ }
+ zilog_notify("Zilog/Hauppauge IR blaster firmware version "
+ "%d.%d.%d loaded\n", buf[1], buf[2], buf[3]);
+
+ return 0;
+}
+
+/* unload "firmware", lock held */
+static void fw_unload_locked(void)
+{
+ if (tx_data) {
+ if (tx_data->code_sets)
+ vfree(tx_data->code_sets);
+
+ if (tx_data->datap)
+ vfree(tx_data->datap);
+
+ vfree(tx_data);
+ tx_data = NULL;
+ dprintk("successfully unloaded IR blaster firmware\n");
+ }
+}
+
+/* unload "firmware" for the IR TX device */
+static void fw_unload(void)
+{
+ mutex_lock(&tx_data_lock);
+ fw_unload_locked();
+ mutex_unlock(&tx_data_lock);
+}
+
+/* load "firmware" for the IR TX device */
+static int fw_load(struct IR_tx *tx)
+{
+ int ret;
+ unsigned int i;
+ unsigned char *data, version, num_global_fixed;
+ const struct firmware *fw_entry;
+
+ /* Already loaded? */
+ mutex_lock(&tx_data_lock);
+ if (tx_data) {
+ ret = 0;
+ goto out;
+ }
+
+ /* Request codeset data file */
+ ret = request_firmware(&fw_entry, "haup-ir-blaster.bin", tx->ir->l.dev);
+ if (ret != 0) {
+ zilog_error("firmware haup-ir-blaster.bin not available (%d)\n",
+ ret);
+ ret = ret < 0 ? ret : -EFAULT;
+ goto out;
+ }
+ dprintk("firmware of size %zu loaded\n", fw_entry->size);
+
+ /* Parse the file */
+ tx_data = vmalloc(sizeof(*tx_data));
+ if (tx_data == NULL) {
+ zilog_error("out of memory\n");
+ release_firmware(fw_entry);
+ ret = -ENOMEM;
+ goto out;
+ }
+ tx_data->code_sets = NULL;
+
+ /* Copy the data so hotplug doesn't get confused and timeout */
+ tx_data->datap = vmalloc(fw_entry->size);
+ if (tx_data->datap == NULL) {
+ zilog_error("out of memory\n");
+ release_firmware(fw_entry);
+ vfree(tx_data);
+ ret = -ENOMEM;
+ goto out;
+ }
+ memcpy(tx_data->datap, fw_entry->data, fw_entry->size);
+ tx_data->endp = tx_data->datap + fw_entry->size;
+ release_firmware(fw_entry); fw_entry = NULL;
+
+ /* Check version */
+ data = tx_data->datap;
+ if (!read_uint8(&data, tx_data->endp, &version))
+ goto corrupt;
+ if (version != 1) {
+ zilog_error("unsupported code set file version (%u, expected"
+ "1) -- please upgrade to a newer driver",
+ version);
+ fw_unload_locked();
+ ret = -EFAULT;
+ goto out;
+ }
+
+ /* Save boot block for later */
+ tx_data->boot_data = data;
+ if (!skip(&data, tx_data->endp, TX_BLOCK_SIZE))
+ goto corrupt;
+
+ if (!read_uint32(&data, tx_data->endp,
+ &tx_data->num_code_sets))
+ goto corrupt;
+
+ dprintk("%u IR blaster codesets loaded\n", tx_data->num_code_sets);
+
+ tx_data->code_sets = vmalloc(
+ tx_data->num_code_sets * sizeof(char *));
+ if (tx_data->code_sets == NULL) {
+ fw_unload_locked();
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ for (i = 0; i < TX_BLOCK_SIZE; ++i)
+ tx_data->fixed[i] = -1;
+
+ /* Read global fixed data template */
+ if (!read_uint8(&data, tx_data->endp, &num_global_fixed) ||
+ num_global_fixed > TX_BLOCK_SIZE)
+ goto corrupt;
+ for (i = 0; i < num_global_fixed; ++i) {
+ unsigned char pos, val;
+ if (!read_uint8(&data, tx_data->endp, &pos) ||
+ !read_uint8(&data, tx_data->endp, &val) ||
+ pos >= TX_BLOCK_SIZE)
+ goto corrupt;
+ tx_data->fixed[pos] = (int)val;
+ }
+
+ /* Filch out the position of each code set */
+ for (i = 0; i < tx_data->num_code_sets; ++i) {
+ unsigned int id;
+ unsigned char keys;
+ unsigned char ndiffs;
+
+ /* Save the codeset position */
+ tx_data->code_sets[i] = data;
+
+ /* Read header */
+ if (!read_uint32(&data, tx_data->endp, &id) ||
+ !read_uint8(&data, tx_data->endp, &keys) ||
+ !read_uint8(&data, tx_data->endp, &ndiffs) ||
+ ndiffs > TX_BLOCK_SIZE || keys == 0)
+ goto corrupt;
+
+ /* skip diff positions */
+ if (!skip(&data, tx_data->endp, ndiffs))
+ goto corrupt;
+
+ /*
+ * After the diffs we have the first key id + data -
+ * global fixed
+ */
+ if (!skip(&data, tx_data->endp,
+ 1 + TX_BLOCK_SIZE - num_global_fixed))
+ goto corrupt;
+
+ /* Then we have keys-1 blocks of key id+diffs */
+ if (!skip(&data, tx_data->endp,
+ (ndiffs + 1) * (keys - 1)))
+ goto corrupt;
+ }
+ ret = 0;
+ goto out;
+
+corrupt:
+ zilog_error("firmware is corrupt\n");
+ fw_unload_locked();
+ ret = -EFAULT;
+
+out:
+ mutex_unlock(&tx_data_lock);
+ return ret;
+}
+
+/* copied from lirc_dev */
+static ssize_t read(struct file *filep, char __user *outbuf, size_t n,
+ loff_t *ppos)
+{
+ struct IR *ir = filep->private_data;
+ struct IR_rx *rx;
+ struct lirc_buffer *rbuf = ir->l.rbuf;
+ int ret = 0, written = 0, retries = 0;
+ unsigned int m;
+ DECLARE_WAITQUEUE(wait, current);
+
+ dprintk("read called\n");
+ if (n % rbuf->chunk_size) {
+ dprintk("read result = -EINVAL\n");
+ return -EINVAL;
+ }
+
+ rx = get_ir_rx(ir);
+ if (rx == NULL)
+ return -ENXIO;
+
+ /*
+ * we add ourselves to the task queue before buffer check
+ * to avoid losing scan code (in case when queue is awaken somewhere
+ * between while condition checking and scheduling)
+ */
+ add_wait_queue(&rbuf->wait_poll, &wait);
+ set_current_state(TASK_INTERRUPTIBLE);
+
+ /*
+ * while we didn't provide 'length' bytes, device is opened in blocking
+ * mode and 'copy_to_user' is happy, wait for data.
+ */
+ while (written < n && ret == 0) {
+ if (lirc_buffer_empty(rbuf)) {
+ /*
+ * According to the read(2) man page, 'written' can be
+ * returned as less than 'n', instead of blocking
+ * again, returning -EWOULDBLOCK, or returning
+ * -ERESTARTSYS
+ */
+ if (written)
+ break;
+ if (filep->f_flags & O_NONBLOCK) {
+ ret = -EWOULDBLOCK;
+ break;
+ }
+ if (signal_pending(current)) {
+ ret = -ERESTARTSYS;
+ break;
+ }
+ schedule();
+ set_current_state(TASK_INTERRUPTIBLE);
+ } else {
+ unsigned char buf[MAX_XFER_SIZE];
+
+ if (rbuf->chunk_size > sizeof(buf)) {
+ zilog_error("chunk_size is too big (%d)!\n",
+ rbuf->chunk_size);
+ ret = -EINVAL;
+ break;
+ }
+ m = lirc_buffer_read(rbuf, buf);
+ if (m == rbuf->chunk_size) {
+ ret = copy_to_user(outbuf + written, buf,
+ rbuf->chunk_size);
+ written += rbuf->chunk_size;
+ } else {
+ retries++;
+ }
+ if (retries >= 5) {
+ zilog_error("Buffer read failed!\n");
+ ret = -EIO;
+ }
+ }
+ }
+
+ remove_wait_queue(&rbuf->wait_poll, &wait);
+ put_ir_rx(rx, false);
+ set_current_state(TASK_RUNNING);
+
+ dprintk("read result = %d (%s)\n", ret, ret ? "Error" : "OK");
+
+ return ret ? ret : written;
+}
+
+/* send a keypress to the IR TX device */
+static int send_code(struct IR_tx *tx, unsigned int code, unsigned int key)
+{
+ unsigned char data_block[TX_BLOCK_SIZE];
+ unsigned char buf[2];
+ int i, ret;
+
+ /* Get data for the codeset/key */
+ ret = get_key_data(data_block, code, key);
+
+ if (ret == -EPROTO) {
+ zilog_error("failed to get data for code %u, key %u -- check "
+ "lircd.conf entries\n", code, key);
+ return ret;
+ } else if (ret != 0)
+ return ret;
+
+ /* Send the data block */
+ ret = send_data_block(tx, data_block);
+ if (ret != 0)
+ return ret;
+
+ /* Send data block length? */
+ buf[0] = 0x00;
+ buf[1] = 0x40;
+ ret = i2c_master_send(tx->c, buf, 2);
+ if (ret != 2) {
+ zilog_error("i2c_master_send failed with %d\n", ret);
+ return ret < 0 ? ret : -EFAULT;
+ }
+
+ /* Give the z8 a moment to process data block */
+ for (i = 0; i < 10; i++) {
+ ret = i2c_master_send(tx->c, buf, 1);
+ if (ret == 1)
+ break;
+ udelay(100);
+ }
+
+ if (ret != 1) {
+ zilog_error("i2c_master_send failed with %d\n", ret);
+ return ret < 0 ? ret : -EFAULT;
+ }
+
+ /* Send finished download? */
+ ret = i2c_master_recv(tx->c, buf, 1);
+ if (ret != 1) {
+ zilog_error("i2c_master_recv failed with %d\n", ret);
+ return ret < 0 ? ret : -EFAULT;
+ }
+ if (buf[0] != 0xA0) {
+ zilog_error("unexpected IR TX response #1: %02x\n",
+ buf[0]);
+ return -EFAULT;
+ }
+
+ /* Send prepare command? */
+ buf[0] = 0x00;
+ buf[1] = 0x80;
+ ret = i2c_master_send(tx->c, buf, 2);
+ if (ret != 2) {
+ zilog_error("i2c_master_send failed with %d\n", ret);
+ return ret < 0 ? ret : -EFAULT;
+ }
+
+ /*
+ * The sleep bits aren't necessary on the HD PVR, and in fact, the
+ * last i2c_master_recv always fails with a -5, so for now, we're
+ * going to skip this whole mess and say we're done on the HD PVR
+ */
+ if (!tx->post_tx_ready_poll) {
+ dprintk("sent code %u, key %u\n", code, key);
+ return 0;
+ }
+
+ /*
+ * This bit NAKs until the device is ready, so we retry it
+ * sleeping a bit each time. This seems to be what the windows
+ * driver does, approximately.
+ * Try for up to 1s.
+ */
+ for (i = 0; i < 20; ++i) {
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ schedule_timeout((50 * HZ + 999) / 1000);
+ ret = i2c_master_send(tx->c, buf, 1);
+ if (ret == 1)
+ break;
+ dprintk("NAK expected: i2c_master_send "
+ "failed with %d (try %d)\n", ret, i+1);
+ }
+ if (ret != 1) {
+ zilog_error("IR TX chip never got ready: last i2c_master_send "
+ "failed with %d\n", ret);
+ return ret < 0 ? ret : -EFAULT;
+ }
+
+ /* Seems to be an 'ok' response */
+ i = i2c_master_recv(tx->c, buf, 1);
+ if (i != 1) {
+ zilog_error("i2c_master_recv failed with %d\n", ret);
+ return -EFAULT;
+ }
+ if (buf[0] != 0x80) {
+ zilog_error("unexpected IR TX response #2: %02x\n", buf[0]);
+ return -EFAULT;
+ }
+
+ /* Oh good, it worked */
+ dprintk("sent code %u, key %u\n", code, key);
+ return 0;
+}
+
+/*
+ * Write a code to the device. We take in a 32-bit number (an int) and then
+ * decode this to a codeset/key index. The key data is then decompressed and
+ * sent to the device. We have a spin lock as per i2c documentation to prevent
+ * multiple concurrent sends which would probably cause the device to explode.
+ */
+static ssize_t write(struct file *filep, const char __user *buf, size_t n,
+ loff_t *ppos)
+{
+ struct IR *ir = filep->private_data;
+ struct IR_tx *tx;
+ size_t i;
+ int failures = 0;
+
+ /* Validate user parameters */
+ if (n % sizeof(int))
+ return -EINVAL;
+
+ /* Get a struct IR_tx reference */
+ tx = get_ir_tx(ir);
+ if (tx == NULL)
+ return -ENXIO;
+
+ /* Ensure our tx->c i2c_client remains valid for the duration */
+ mutex_lock(&tx->client_lock);
+ if (tx->c == NULL) {
+ mutex_unlock(&tx->client_lock);
+ put_ir_tx(tx, false);
+ return -ENXIO;
+ }
+
+ /* Lock i2c bus for the duration */
+ mutex_lock(&ir->ir_lock);
+
+ /* Send each keypress */
+ for (i = 0; i < n;) {
+ int ret = 0;
+ int command;
+
+ if (copy_from_user(&command, buf + i, sizeof(command))) {
+ mutex_unlock(&ir->ir_lock);
+ mutex_unlock(&tx->client_lock);
+ put_ir_tx(tx, false);
+ return -EFAULT;
+ }
+
+ /* Send boot data first if required */
+ if (tx->need_boot == 1) {
+ /* Make sure we have the 'firmware' loaded, first */
+ ret = fw_load(tx);
+ if (ret != 0) {
+ mutex_unlock(&ir->ir_lock);
+ mutex_unlock(&tx->client_lock);
+ put_ir_tx(tx, false);
+ if (ret != -ENOMEM)
+ ret = -EIO;
+ return ret;
+ }
+ /* Prep the chip for transmitting codes */
+ ret = send_boot_data(tx);
+ if (ret == 0)
+ tx->need_boot = 0;
+ }
+
+ /* Send the code */
+ if (ret == 0) {
+ ret = send_code(tx, (unsigned)command >> 16,
+ (unsigned)command & 0xFFFF);
+ if (ret == -EPROTO) {
+ mutex_unlock(&ir->ir_lock);
+ mutex_unlock(&tx->client_lock);
+ put_ir_tx(tx, false);
+ return ret;
+ }
+ }
+
+ /*
+ * Hmm, a failure. If we've had a few then give up, otherwise
+ * try a reset
+ */
+ if (ret != 0) {
+ /* Looks like the chip crashed, reset it */
+ zilog_error("sending to the IR transmitter chip "
+ "failed, trying reset\n");
+
+ if (failures >= 3) {
+ zilog_error("unable to send to the IR chip "
+ "after 3 resets, giving up\n");
+ mutex_unlock(&ir->ir_lock);
+ mutex_unlock(&tx->client_lock);
+ put_ir_tx(tx, false);
+ return ret;
+ }
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ schedule_timeout((100 * HZ + 999) / 1000);
+ tx->need_boot = 1;
+ ++failures;
+ } else
+ i += sizeof(int);
+ }
+
+ /* Release i2c bus */
+ mutex_unlock(&ir->ir_lock);
+
+ mutex_unlock(&tx->client_lock);
+
+ /* Give back our struct IR_tx reference */
+ put_ir_tx(tx, false);
+
+ /* All looks good */
+ return n;
+}
+
+/* copied from lirc_dev */
+static unsigned int poll(struct file *filep, poll_table *wait)
+{
+ struct IR *ir = filep->private_data;
+ struct IR_rx *rx;
+ struct lirc_buffer *rbuf = ir->l.rbuf;
+ unsigned int ret;
+
+ dprintk("poll called\n");
+
+ rx = get_ir_rx(ir);
+ if (rx == NULL) {
+ /*
+ * Revisit this, if our poll function ever reports writeable
+ * status for Tx
+ */
+ dprintk("poll result = POLLERR\n");
+ return POLLERR;
+ }
+
+ /*
+ * Add our lirc_buffer's wait_queue to the poll_table. A wake up on
+ * that buffer's wait queue indicates we may have a new poll status.
+ */
+ poll_wait(filep, &rbuf->wait_poll, wait);
+
+ /* Indicate what ops could happen immediately without blocking */
+ ret = lirc_buffer_empty(rbuf) ? 0 : (POLLIN|POLLRDNORM);
+
+ dprintk("poll result = %s\n", ret ? "POLLIN|POLLRDNORM" : "none");
+ return ret;
+}
+
+static long ioctl(struct file *filep, unsigned int cmd, unsigned long arg)
+{
+ struct IR *ir = filep->private_data;
+ unsigned long __user *uptr = (unsigned long __user *)arg;
+ int result;
+ unsigned long mode, features;
+
+ features = ir->l.features;
+
+ switch (cmd) {
+ case LIRC_GET_LENGTH:
+ result = put_user(13UL, uptr);
+ break;
+ case LIRC_GET_FEATURES:
+ result = put_user(features, uptr);
+ break;
+ case LIRC_GET_REC_MODE:
+ if (!(features&LIRC_CAN_REC_MASK))
+ return -ENOSYS;
+
+ result = put_user(LIRC_REC2MODE
+ (features&LIRC_CAN_REC_MASK),
+ uptr);
+ break;
+ case LIRC_SET_REC_MODE:
+ if (!(features&LIRC_CAN_REC_MASK))
+ return -ENOSYS;
+
+ result = get_user(mode, uptr);
+ if (!result && !(LIRC_MODE2REC(mode) & features))
+ result = -EINVAL;
+ break;
+ case LIRC_GET_SEND_MODE:
+ if (!(features&LIRC_CAN_SEND_MASK))
+ return -ENOSYS;
+
+ result = put_user(LIRC_MODE_PULSE, uptr);
+ break;
+ case LIRC_SET_SEND_MODE:
+ if (!(features&LIRC_CAN_SEND_MASK))
+ return -ENOSYS;
+
+ result = get_user(mode, uptr);
+ if (!result && mode != LIRC_MODE_PULSE)
+ return -EINVAL;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return result;
+}
+
+static struct IR *get_ir_device_by_minor(unsigned int minor)
+{
+ struct IR *ir;
+ struct IR *ret = NULL;
+
+ mutex_lock(&ir_devices_lock);
+
+ if (!list_empty(&ir_devices_list)) {
+ list_for_each_entry(ir, &ir_devices_list, list) {
+ if (ir->l.minor == minor) {
+ ret = get_ir_device(ir, true);
+ break;
+ }
+ }
+ }
+
+ mutex_unlock(&ir_devices_lock);
+ return ret;
+}
+
+/*
+ * Open the IR device. Get hold of our IR structure and
+ * stash it in private_data for the file
+ */
+static int open(struct inode *node, struct file *filep)
+{
+ struct IR *ir;
+ unsigned int minor = MINOR(node->i_rdev);
+
+ /* find our IR struct */
+ ir = get_ir_device_by_minor(minor);
+
+ if (ir == NULL)
+ return -ENODEV;
+
+ atomic_inc(&ir->open_count);
+
+ /* stash our IR struct */
+ filep->private_data = ir;
+
+ nonseekable_open(node, filep);
+ return 0;
+}
+
+/* Close the IR device */
+static int close(struct inode *node, struct file *filep)
+{
+ /* find our IR struct */
+ struct IR *ir = filep->private_data;
+ if (ir == NULL) {
+ zilog_error("close: no private_data attached to the file!\n");
+ return -ENODEV;
+ }
+
+ atomic_dec(&ir->open_count);
+
+ put_ir_device(ir, false);
+ return 0;
+}
+
+static int ir_remove(struct i2c_client *client);
+static int ir_probe(struct i2c_client *client, const struct i2c_device_id *id);
+
+#define ID_FLAG_TX 0x01
+#define ID_FLAG_HDPVR 0x02
+
+static const struct i2c_device_id ir_transceiver_id[] = {
+ { "ir_tx_z8f0811_haup", ID_FLAG_TX },
+ { "ir_rx_z8f0811_haup", 0 },
+ { "ir_tx_z8f0811_hdpvr", ID_FLAG_HDPVR | ID_FLAG_TX },
+ { "ir_rx_z8f0811_hdpvr", ID_FLAG_HDPVR },
+ { }
+};
+
+static struct i2c_driver driver = {
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "Zilog/Hauppauge i2c IR",
+ },
+ .probe = ir_probe,
+ .remove = ir_remove,
+ .id_table = ir_transceiver_id,
+};
+
+static const struct file_operations lirc_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .read = read,
+ .write = write,
+ .poll = poll,
+ .unlocked_ioctl = ioctl,
+#ifdef CONFIG_COMPAT
+ .compat_ioctl = ioctl,
+#endif
+ .open = open,
+ .release = close
+};
+
+static struct lirc_driver lirc_template = {
+ .name = "lirc_zilog",
+ .minor = -1,
+ .code_length = 13,
+ .buffer_size = BUFLEN / 2,
+ .sample_rate = 0, /* tell lirc_dev to not start its own kthread */
+ .chunk_size = 2,
+ .set_use_inc = set_use_inc,
+ .set_use_dec = set_use_dec,
+ .fops = &lirc_fops,
+ .owner = THIS_MODULE,
+};
+
+static int ir_remove(struct i2c_client *client)
+{
+ if (strncmp("ir_tx_z8", client->name, 8) == 0) {
+ struct IR_tx *tx = i2c_get_clientdata(client);
+ if (tx != NULL) {
+ mutex_lock(&tx->client_lock);
+ tx->c = NULL;
+ mutex_unlock(&tx->client_lock);
+ put_ir_tx(tx, false);
+ }
+ } else if (strncmp("ir_rx_z8", client->name, 8) == 0) {
+ struct IR_rx *rx = i2c_get_clientdata(client);
+ if (rx != NULL) {
+ mutex_lock(&rx->client_lock);
+ rx->c = NULL;
+ mutex_unlock(&rx->client_lock);
+ put_ir_rx(rx, false);
+ }
+ }
+ return 0;
+}
+
+
+/* ir_devices_lock must be held */
+static struct IR *get_ir_device_by_adapter(struct i2c_adapter *adapter)
+{
+ struct IR *ir;
+
+ if (list_empty(&ir_devices_list))
+ return NULL;
+
+ list_for_each_entry(ir, &ir_devices_list, list)
+ if (ir->adapter == adapter) {
+ get_ir_device(ir, true);
+ return ir;
+ }
+
+ return NULL;
+}
+
+static int ir_probe(struct i2c_client *client, const struct i2c_device_id *id)
+{
+ struct IR *ir;
+ struct IR_tx *tx;
+ struct IR_rx *rx;
+ struct i2c_adapter *adap = client->adapter;
+ int ret;
+ bool tx_probe = false;
+
+ dprintk("%s: %s on i2c-%d (%s), client addr=0x%02x\n",
+ __func__, id->name, adap->nr, adap->name, client->addr);
+
+ /*
+ * The IR receiver is at i2c address 0x71.
+ * The IR transmitter is at i2c address 0x70.
+ */
+
+ if (id->driver_data & ID_FLAG_TX)
+ tx_probe = true;
+ else if (tx_only) /* module option */
+ return -ENXIO;
+
+ zilog_info("probing IR %s on %s (i2c-%d)\n",
+ tx_probe ? "Tx" : "Rx", adap->name, adap->nr);
+
+ mutex_lock(&ir_devices_lock);
+
+ /* Use a single struct IR instance for both the Rx and Tx functions */
+ ir = get_ir_device_by_adapter(adap);
+ if (ir == NULL) {
+ ir = kzalloc(sizeof(struct IR), GFP_KERNEL);
+ if (ir == NULL) {
+ ret = -ENOMEM;
+ goto out_no_ir;
+ }
+ kref_init(&ir->ref);
+
+ /* store for use in ir_probe() again, and open() later on */
+ INIT_LIST_HEAD(&ir->list);
+ list_add_tail(&ir->list, &ir_devices_list);
+
+ ir->adapter = adap;
+ mutex_init(&ir->ir_lock);
+ atomic_set(&ir->open_count, 0);
+ spin_lock_init(&ir->tx_ref_lock);
+ spin_lock_init(&ir->rx_ref_lock);
+
+ /* set lirc_dev stuff */
+ memcpy(&ir->l, &lirc_template, sizeof(struct lirc_driver));
+ /*
+ * FIXME this is a pointer reference to us, but no refcount.
+ *
+ * This OK for now, since lirc_dev currently won't touch this
+ * buffer as we provide our own lirc_fops.
+ *
+ * Currently our own lirc_fops rely on this ir->l.rbuf pointer
+ */
+ ir->l.rbuf = &ir->rbuf;
+ ir->l.dev = &adap->dev;
+ ret = lirc_buffer_init(ir->l.rbuf,
+ ir->l.chunk_size, ir->l.buffer_size);
+ if (ret)
+ goto out_put_ir;
+ }
+
+ if (tx_probe) {
+ /* Get the IR_rx instance for later, if already allocated */
+ rx = get_ir_rx(ir);
+
+ /* Set up a struct IR_tx instance */
+ tx = kzalloc(sizeof(struct IR_tx), GFP_KERNEL);
+ if (tx == NULL) {
+ ret = -ENOMEM;
+ goto out_put_xx;
+ }
+ kref_init(&tx->ref);
+ ir->tx = tx;
+
+ ir->l.features |= LIRC_CAN_SEND_PULSE;
+ mutex_init(&tx->client_lock);
+ tx->c = client;
+ tx->need_boot = 1;
+ tx->post_tx_ready_poll =
+ (id->driver_data & ID_FLAG_HDPVR) ? false : true;
+
+ /* An ir ref goes to the struct IR_tx instance */
+ tx->ir = get_ir_device(ir, true);
+
+ /* A tx ref goes to the i2c_client */
+ i2c_set_clientdata(client, get_ir_tx(ir));
+
+ /*
+ * Load the 'firmware'. We do this before registering with
+ * lirc_dev, so the first firmware load attempt does not happen
+ * after a open() or write() call on the device.
+ *
+ * Failure here is not deemed catastrophic, so the receiver will
+ * still be usable. Firmware load will be retried in write(),
+ * if it is needed.
+ */
+ fw_load(tx);
+
+ /* Proceed only if the Rx client is also ready or not needed */
+ if (rx == NULL && !tx_only) {
+ zilog_info("probe of IR Tx on %s (i2c-%d) done. Waiting"
+ " on IR Rx.\n", adap->name, adap->nr);
+ goto out_ok;
+ }
+ } else {
+ /* Get the IR_tx instance for later, if already allocated */
+ tx = get_ir_tx(ir);
+
+ /* Set up a struct IR_rx instance */
+ rx = kzalloc(sizeof(struct IR_rx), GFP_KERNEL);
+ if (rx == NULL) {
+ ret = -ENOMEM;
+ goto out_put_xx;
+ }
+ kref_init(&rx->ref);
+ ir->rx = rx;
+
+ ir->l.features |= LIRC_CAN_REC_LIRCCODE;
+ mutex_init(&rx->client_lock);
+ rx->c = client;
+ rx->hdpvr_data_fmt =
+ (id->driver_data & ID_FLAG_HDPVR) ? true : false;
+
+ /* An ir ref goes to the struct IR_rx instance */
+ rx->ir = get_ir_device(ir, true);
+
+ /* An rx ref goes to the i2c_client */
+ i2c_set_clientdata(client, get_ir_rx(ir));
+
+ /*
+ * Start the polling thread.
+ * It will only perform an empty loop around schedule_timeout()
+ * until we register with lirc_dev and the first user open()
+ */
+ /* An ir ref goes to the new rx polling kthread */
+ rx->task = kthread_run(lirc_thread, get_ir_device(ir, true),
+ "zilog-rx-i2c-%d", adap->nr);
+ if (IS_ERR(rx->task)) {
+ ret = PTR_ERR(rx->task);
+ zilog_error("%s: could not start IR Rx polling thread"
+ "\n", __func__);
+ /* Failed kthread, so put back the ir ref */
+ put_ir_device(ir, true);
+ /* Failure exit, so put back rx ref from i2c_client */
+ i2c_set_clientdata(client, NULL);
+ put_ir_rx(rx, true);
+ ir->l.features &= ~LIRC_CAN_REC_LIRCCODE;
+ goto out_put_xx;
+ }
+
+ /* Proceed only if the Tx client is also ready */
+ if (tx == NULL) {
+ zilog_info("probe of IR Rx on %s (i2c-%d) done. Waiting"
+ " on IR Tx.\n", adap->name, adap->nr);
+ goto out_ok;
+ }
+ }
+
+ /* register with lirc */
+ ir->l.minor = minor; /* module option: user requested minor number */
+ ir->l.minor = lirc_register_driver(&ir->l);
+ if (ir->l.minor < 0 || ir->l.minor >= MAX_IRCTL_DEVICES) {
+ zilog_error("%s: \"minor\" must be between 0 and %d (%d)!\n",
+ __func__, MAX_IRCTL_DEVICES-1, ir->l.minor);
+ ret = -EBADRQC;
+ goto out_put_xx;
+ }
+ zilog_info("IR unit on %s (i2c-%d) registered as lirc%d and ready\n",
+ adap->name, adap->nr, ir->l.minor);
+
+out_ok:
+ if (rx != NULL)
+ put_ir_rx(rx, true);
+ if (tx != NULL)
+ put_ir_tx(tx, true);
+ put_ir_device(ir, true);
+ zilog_info("probe of IR %s on %s (i2c-%d) done\n",
+ tx_probe ? "Tx" : "Rx", adap->name, adap->nr);
+ mutex_unlock(&ir_devices_lock);
+ return 0;
+
+out_put_xx:
+ if (rx != NULL)
+ put_ir_rx(rx, true);
+ if (tx != NULL)
+ put_ir_tx(tx, true);
+out_put_ir:
+ put_ir_device(ir, true);
+out_no_ir:
+ zilog_error("%s: probing IR %s on %s (i2c-%d) failed with %d\n",
+ __func__, tx_probe ? "Tx" : "Rx", adap->name, adap->nr,
+ ret);
+ mutex_unlock(&ir_devices_lock);
+ return ret;
+}
+
+static int __init zilog_init(void)
+{
+ int ret;
+
+ zilog_notify("Zilog/Hauppauge IR driver initializing\n");
+
+ mutex_init(&tx_data_lock);
+
+ request_module("firmware_class");
+
+ ret = i2c_add_driver(&driver);
+ if (ret)
+ zilog_error("initialization failed\n");
+ else
+ zilog_notify("initialization complete\n");
+
+ return ret;
+}
+
+static void __exit zilog_exit(void)
+{
+ i2c_del_driver(&driver);
+ /* if loaded */
+ fw_unload();
+ zilog_notify("Zilog/Hauppauge IR driver unloaded\n");
+}
+
+module_init(zilog_init);
+module_exit(zilog_exit);
+
+MODULE_DESCRIPTION("Zilog/Hauppauge infrared transmitter driver (i2c stack)");
+MODULE_AUTHOR("Gerd Knorr, Michal Kochanowicz, Christoph Bartelmus, "
+ "Ulrich Mueller, Stefan Jahn, Jerome Brock, Mark Weaver, "
+ "Andy Walls");
+MODULE_LICENSE("GPL");
+/* for compat with old name, which isn't all that accurate anymore */
+MODULE_ALIAS("lirc_pvr150");
+
+module_param(minor, int, 0444);
+MODULE_PARM_DESC(minor, "Preferred minor device number");
+
+module_param(debug, bool, 0644);
+MODULE_PARM_DESC(debug, "Enable debugging messages");
+
+module_param(tx_only, bool, 0644);
+MODULE_PARM_DESC(tx_only, "Only handle the IR transmit function");
diff --git a/drivers/staging/media/msi3101/Kconfig b/drivers/staging/media/msi3101/Kconfig
new file mode 100644
index 00000000000..de0b3bba387
--- /dev/null
+++ b/drivers/staging/media/msi3101/Kconfig
@@ -0,0 +1,10 @@
+config USB_MSI3101
+ tristate "Mirics MSi3101 SDR Dongle"
+ depends on USB && VIDEO_DEV && VIDEO_V4L2 && SPI
+ select VIDEOBUF2_CORE
+ select VIDEOBUF2_VMALLOC
+ select MEDIA_TUNER_MSI001
+
+config MEDIA_TUNER_MSI001
+ tristate "Mirics MSi001"
+ depends on VIDEO_V4L2 && SPI
diff --git a/drivers/staging/media/msi3101/Makefile b/drivers/staging/media/msi3101/Makefile
new file mode 100644
index 00000000000..daf4f58d9a5
--- /dev/null
+++ b/drivers/staging/media/msi3101/Makefile
@@ -0,0 +1,2 @@
+obj-$(CONFIG_USB_MSI3101) += sdr-msi3101.o
+obj-$(CONFIG_MEDIA_TUNER_MSI001) += msi001.o
diff --git a/drivers/staging/media/msi3101/msi001.c b/drivers/staging/media/msi3101/msi001.c
new file mode 100644
index 00000000000..bd0b93cb6c5
--- /dev/null
+++ b/drivers/staging/media/msi3101/msi001.c
@@ -0,0 +1,500 @@
+/*
+ * Mirics MSi001 silicon tuner driver
+ *
+ * Copyright (C) 2013 Antti Palosaari <crope@iki.fi>
+ * Copyright (C) 2014 Antti Palosaari <crope@iki.fi>
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/gcd.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-ctrls.h>
+
+static const struct v4l2_frequency_band bands[] = {
+ {
+ .type = V4L2_TUNER_RF,
+ .index = 0,
+ .capability = V4L2_TUNER_CAP_1HZ | V4L2_TUNER_CAP_FREQ_BANDS,
+ .rangelow = 49000000,
+ .rangehigh = 263000000,
+ }, {
+ .type = V4L2_TUNER_RF,
+ .index = 1,
+ .capability = V4L2_TUNER_CAP_1HZ | V4L2_TUNER_CAP_FREQ_BANDS,
+ .rangelow = 390000000,
+ .rangehigh = 960000000,
+ },
+};
+
+struct msi001 {
+ struct spi_device *spi;
+ struct v4l2_subdev sd;
+
+ /* Controls */
+ struct v4l2_ctrl_handler hdl;
+ struct v4l2_ctrl *bandwidth_auto;
+ struct v4l2_ctrl *bandwidth;
+ struct v4l2_ctrl *lna_gain;
+ struct v4l2_ctrl *mixer_gain;
+ struct v4l2_ctrl *if_gain;
+
+ unsigned int f_tuner;
+};
+
+static inline struct msi001 *sd_to_msi001(struct v4l2_subdev *sd)
+{
+ return container_of(sd, struct msi001, sd);
+}
+
+static int msi001_wreg(struct msi001 *s, u32 data)
+{
+ /* Register format: 4 bits addr + 20 bits value */
+ return spi_write(s->spi, &data, 3);
+};
+
+static int msi001_set_gain(struct msi001 *s, int lna_gain, int mixer_gain,
+ int if_gain)
+{
+ int ret;
+ u32 reg;
+ dev_dbg(&s->spi->dev, "%s: lna=%d mixer=%d if=%d\n", __func__,
+ lna_gain, mixer_gain, if_gain);
+
+ reg = 1 << 0;
+ reg |= (59 - if_gain) << 4;
+ reg |= 0 << 10;
+ reg |= (1 - mixer_gain) << 12;
+ reg |= (1 - lna_gain) << 13;
+ reg |= 4 << 14;
+ reg |= 0 << 17;
+ ret = msi001_wreg(s, reg);
+ if (ret)
+ goto err;
+
+ return 0;
+err:
+ dev_dbg(&s->spi->dev, "%s: failed %d\n", __func__, ret);
+ return ret;
+};
+
+static int msi001_set_tuner(struct msi001 *s)
+{
+ int ret, i;
+ unsigned int n, m, thresh, frac, vco_step, tmp, f_if1;
+ u32 reg;
+ u64 f_vco, tmp64;
+ u8 mode, filter_mode, lo_div;
+ static const struct {
+ u32 rf;
+ u8 mode;
+ u8 lo_div;
+ } band_lut[] = {
+ { 50000000, 0xe1, 16}, /* AM_MODE2, antenna 2 */
+ {108000000, 0x42, 32}, /* VHF_MODE */
+ {330000000, 0x44, 16}, /* B3_MODE */
+ {960000000, 0x48, 4}, /* B45_MODE */
+ { ~0U, 0x50, 2}, /* BL_MODE */
+ };
+ static const struct {
+ u32 freq;
+ u8 filter_mode;
+ } if_freq_lut[] = {
+ { 0, 0x03}, /* Zero IF */
+ { 450000, 0x02}, /* 450 kHz IF */
+ {1620000, 0x01}, /* 1.62 MHz IF */
+ {2048000, 0x00}, /* 2.048 MHz IF */
+ };
+ static const struct {
+ u32 freq;
+ u8 val;
+ } bandwidth_lut[] = {
+ { 200000, 0x00}, /* 200 kHz */
+ { 300000, 0x01}, /* 300 kHz */
+ { 600000, 0x02}, /* 600 kHz */
+ {1536000, 0x03}, /* 1.536 MHz */
+ {5000000, 0x04}, /* 5 MHz */
+ {6000000, 0x05}, /* 6 MHz */
+ {7000000, 0x06}, /* 7 MHz */
+ {8000000, 0x07}, /* 8 MHz */
+ };
+
+ unsigned int f_rf = s->f_tuner;
+
+ /*
+ * bandwidth (Hz)
+ * 200000, 300000, 600000, 1536000, 5000000, 6000000, 7000000, 8000000
+ */
+ unsigned int bandwidth;
+
+ /*
+ * intermediate frequency (Hz)
+ * 0, 450000, 1620000, 2048000
+ */
+ unsigned int f_if = 0;
+ #define F_REF 24000000
+ #define R_REF 4
+ #define F_OUT_STEP 1
+
+ dev_dbg(&s->spi->dev,
+ "%s: f_rf=%d f_if=%d\n",
+ __func__, f_rf, f_if);
+
+ for (i = 0; i < ARRAY_SIZE(band_lut); i++) {
+ if (f_rf <= band_lut[i].rf) {
+ mode = band_lut[i].mode;
+ lo_div = band_lut[i].lo_div;
+ break;
+ }
+ }
+
+ if (i == ARRAY_SIZE(band_lut)) {
+ ret = -EINVAL;
+ goto err;
+ }
+
+ /* AM_MODE is upconverted */
+ if ((mode >> 0) & 0x1)
+ f_if1 = 5 * F_REF;
+ else
+ f_if1 = 0;
+
+ for (i = 0; i < ARRAY_SIZE(if_freq_lut); i++) {
+ if (f_if == if_freq_lut[i].freq) {
+ filter_mode = if_freq_lut[i].filter_mode;
+ break;
+ }
+ }
+
+ if (i == ARRAY_SIZE(if_freq_lut)) {
+ ret = -EINVAL;
+ goto err;
+ }
+
+ /* filters */
+ bandwidth = s->bandwidth->val;
+ bandwidth = clamp(bandwidth, 200000U, 8000000U);
+
+ for (i = 0; i < ARRAY_SIZE(bandwidth_lut); i++) {
+ if (bandwidth <= bandwidth_lut[i].freq) {
+ bandwidth = bandwidth_lut[i].val;
+ break;
+ }
+ }
+
+ if (i == ARRAY_SIZE(bandwidth_lut)) {
+ ret = -EINVAL;
+ goto err;
+ }
+
+ s->bandwidth->val = bandwidth_lut[i].freq;
+
+ dev_dbg(&s->spi->dev, "%s: bandwidth selected=%d\n",
+ __func__, bandwidth_lut[i].freq);
+
+ f_vco = (u64) (f_rf + f_if + f_if1) * lo_div;
+ tmp64 = f_vco;
+ m = do_div(tmp64, F_REF * R_REF);
+ n = (unsigned int) tmp64;
+
+ vco_step = F_OUT_STEP * lo_div;
+ thresh = (F_REF * R_REF) / vco_step;
+ frac = 1ul * thresh * m / (F_REF * R_REF);
+
+ /* Find out greatest common divisor and divide to smaller. */
+ tmp = gcd(thresh, frac);
+ thresh /= tmp;
+ frac /= tmp;
+
+ /* Force divide to reg max. Resolution will be reduced. */
+ tmp = DIV_ROUND_UP(thresh, 4095);
+ thresh = DIV_ROUND_CLOSEST(thresh, tmp);
+ frac = DIV_ROUND_CLOSEST(frac, tmp);
+
+ /* calc real RF set */
+ tmp = 1ul * F_REF * R_REF * n;
+ tmp += 1ul * F_REF * R_REF * frac / thresh;
+ tmp /= lo_div;
+
+ dev_dbg(&s->spi->dev,
+ "%s: rf=%u:%u n=%d thresh=%d frac=%d\n",
+ __func__, f_rf, tmp, n, thresh, frac);
+
+ ret = msi001_wreg(s, 0x00000e);
+ if (ret)
+ goto err;
+
+ ret = msi001_wreg(s, 0x000003);
+ if (ret)
+ goto err;
+
+ reg = 0 << 0;
+ reg |= mode << 4;
+ reg |= filter_mode << 12;
+ reg |= bandwidth << 14;
+ reg |= 0x02 << 17;
+ reg |= 0x00 << 20;
+ ret = msi001_wreg(s, reg);
+ if (ret)
+ goto err;
+
+ reg = 5 << 0;
+ reg |= thresh << 4;
+ reg |= 1 << 19;
+ reg |= 1 << 21;
+ ret = msi001_wreg(s, reg);
+ if (ret)
+ goto err;
+
+ reg = 2 << 0;
+ reg |= frac << 4;
+ reg |= n << 16;
+ ret = msi001_wreg(s, reg);
+ if (ret)
+ goto err;
+
+ ret = msi001_set_gain(s, s->lna_gain->cur.val, s->mixer_gain->cur.val,
+ s->if_gain->cur.val);
+ if (ret)
+ goto err;
+
+ reg = 6 << 0;
+ reg |= 63 << 4;
+ reg |= 4095 << 10;
+ ret = msi001_wreg(s, reg);
+ if (ret)
+ goto err;
+
+ return 0;
+err:
+ dev_dbg(&s->spi->dev, "%s: failed %d\n", __func__, ret);
+ return ret;
+};
+
+static int msi001_s_power(struct v4l2_subdev *sd, int on)
+{
+ struct msi001 *s = sd_to_msi001(sd);
+ int ret;
+ dev_dbg(&s->spi->dev, "%s: on=%d\n", __func__, on);
+
+ if (on)
+ ret = 0;
+ else
+ ret = msi001_wreg(s, 0x000000);
+
+ return ret;
+}
+
+static const struct v4l2_subdev_core_ops msi001_core_ops = {
+ .s_power = msi001_s_power,
+};
+
+static int msi001_g_tuner(struct v4l2_subdev *sd, struct v4l2_tuner *v)
+{
+ struct msi001 *s = sd_to_msi001(sd);
+ dev_dbg(&s->spi->dev, "%s: index=%d\n", __func__, v->index);
+
+ strlcpy(v->name, "Mirics MSi001", sizeof(v->name));
+ v->type = V4L2_TUNER_RF;
+ v->capability = V4L2_TUNER_CAP_1HZ | V4L2_TUNER_CAP_FREQ_BANDS;
+ v->rangelow = 49000000;
+ v->rangehigh = 960000000;
+
+ return 0;
+}
+
+static int msi001_s_tuner(struct v4l2_subdev *sd, const struct v4l2_tuner *v)
+{
+ struct msi001 *s = sd_to_msi001(sd);
+ dev_dbg(&s->spi->dev, "%s: index=%d\n", __func__, v->index);
+ return 0;
+}
+
+static int msi001_g_frequency(struct v4l2_subdev *sd, struct v4l2_frequency *f)
+{
+ struct msi001 *s = sd_to_msi001(sd);
+ dev_dbg(&s->spi->dev, "%s: tuner=%d\n", __func__, f->tuner);
+ f->frequency = s->f_tuner;
+ return 0;
+}
+
+static int msi001_s_frequency(struct v4l2_subdev *sd,
+ const struct v4l2_frequency *f)
+{
+ struct msi001 *s = sd_to_msi001(sd);
+ unsigned int band;
+ dev_dbg(&s->spi->dev, "%s: tuner=%d type=%d frequency=%u\n",
+ __func__, f->tuner, f->type, f->frequency);
+
+ if (f->frequency < ((bands[0].rangehigh + bands[1].rangelow) / 2))
+ band = 0;
+ else
+ band = 1;
+ s->f_tuner = clamp_t(unsigned int, f->frequency,
+ bands[band].rangelow, bands[band].rangehigh);
+
+ return msi001_set_tuner(s);
+}
+
+static int msi001_enum_freq_bands(struct v4l2_subdev *sd,
+ struct v4l2_frequency_band *band)
+{
+ struct msi001 *s = sd_to_msi001(sd);
+ dev_dbg(&s->spi->dev, "%s: tuner=%d type=%d index=%d\n",
+ __func__, band->tuner, band->type, band->index);
+
+ if (band->index >= ARRAY_SIZE(bands))
+ return -EINVAL;
+
+ band->capability = bands[band->index].capability;
+ band->rangelow = bands[band->index].rangelow;
+ band->rangehigh = bands[band->index].rangehigh;
+
+ return 0;
+}
+
+static const struct v4l2_subdev_tuner_ops msi001_tuner_ops = {
+ .g_tuner = msi001_g_tuner,
+ .s_tuner = msi001_s_tuner,
+ .g_frequency = msi001_g_frequency,
+ .s_frequency = msi001_s_frequency,
+ .enum_freq_bands = msi001_enum_freq_bands,
+};
+
+static const struct v4l2_subdev_ops msi001_ops = {
+ .core = &msi001_core_ops,
+ .tuner = &msi001_tuner_ops,
+};
+
+static int msi001_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+ struct msi001 *s = container_of(ctrl->handler, struct msi001, hdl);
+
+ int ret;
+ dev_dbg(&s->spi->dev,
+ "%s: id=%d name=%s val=%d min=%d max=%d step=%d\n",
+ __func__, ctrl->id, ctrl->name, ctrl->val,
+ ctrl->minimum, ctrl->maximum, ctrl->step);
+
+ switch (ctrl->id) {
+ case V4L2_CID_RF_TUNER_BANDWIDTH_AUTO:
+ case V4L2_CID_RF_TUNER_BANDWIDTH:
+ ret = msi001_set_tuner(s);
+ break;
+ case V4L2_CID_RF_TUNER_LNA_GAIN:
+ ret = msi001_set_gain(s, s->lna_gain->val,
+ s->mixer_gain->cur.val, s->if_gain->cur.val);
+ break;
+ case V4L2_CID_RF_TUNER_MIXER_GAIN:
+ ret = msi001_set_gain(s, s->lna_gain->cur.val,
+ s->mixer_gain->val, s->if_gain->cur.val);
+ break;
+ case V4L2_CID_RF_TUNER_IF_GAIN:
+ ret = msi001_set_gain(s, s->lna_gain->cur.val,
+ s->mixer_gain->cur.val, s->if_gain->val);
+ break;
+ default:
+ dev_dbg(&s->spi->dev, "%s: unkown control %d\n",
+ __func__, ctrl->id);
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+static const struct v4l2_ctrl_ops msi001_ctrl_ops = {
+ .s_ctrl = msi001_s_ctrl,
+};
+
+static int msi001_probe(struct spi_device *spi)
+{
+ struct msi001 *s;
+ int ret;
+ dev_dbg(&spi->dev, "%s:\n", __func__);
+
+ s = kzalloc(sizeof(struct msi001), GFP_KERNEL);
+ if (s == NULL) {
+ ret = -ENOMEM;
+ dev_dbg(&spi->dev, "Could not allocate memory for msi001\n");
+ goto err_kfree;
+ }
+
+ s->spi = spi;
+ s->f_tuner = bands[0].rangelow;
+ v4l2_spi_subdev_init(&s->sd, spi, &msi001_ops);
+
+ /* Register controls */
+ v4l2_ctrl_handler_init(&s->hdl, 5);
+ s->bandwidth_auto = v4l2_ctrl_new_std(&s->hdl, &msi001_ctrl_ops,
+ V4L2_CID_RF_TUNER_BANDWIDTH_AUTO, 0, 1, 1, 1);
+ s->bandwidth = v4l2_ctrl_new_std(&s->hdl, &msi001_ctrl_ops,
+ V4L2_CID_RF_TUNER_BANDWIDTH, 200000, 8000000, 1, 200000);
+ v4l2_ctrl_auto_cluster(2, &s->bandwidth_auto, 0, false);
+ s->lna_gain = v4l2_ctrl_new_std(&s->hdl, &msi001_ctrl_ops,
+ V4L2_CID_RF_TUNER_LNA_GAIN, 0, 1, 1, 1);
+ s->mixer_gain = v4l2_ctrl_new_std(&s->hdl, &msi001_ctrl_ops,
+ V4L2_CID_RF_TUNER_MIXER_GAIN, 0, 1, 1, 1);
+ s->if_gain = v4l2_ctrl_new_std(&s->hdl, &msi001_ctrl_ops,
+ V4L2_CID_RF_TUNER_IF_GAIN, 0, 59, 1, 0);
+ if (s->hdl.error) {
+ ret = s->hdl.error;
+ dev_err(&s->spi->dev, "Could not initialize controls\n");
+ /* control init failed, free handler */
+ goto err_ctrl_handler_free;
+ }
+
+ s->sd.ctrl_handler = &s->hdl;
+ return 0;
+
+err_ctrl_handler_free:
+ v4l2_ctrl_handler_free(&s->hdl);
+err_kfree:
+ kfree(s);
+ return ret;
+}
+
+static int msi001_remove(struct spi_device *spi)
+{
+ struct v4l2_subdev *sd = spi_get_drvdata(spi);
+ struct msi001 *s = sd_to_msi001(sd);
+ dev_dbg(&spi->dev, "%s:\n", __func__);
+
+ /*
+ * Registered by v4l2_spi_new_subdev() from master driver, but we must
+ * unregister it from here. Weird.
+ */
+ v4l2_device_unregister_subdev(&s->sd);
+ v4l2_ctrl_handler_free(&s->hdl);
+ kfree(s);
+ return 0;
+}
+
+static const struct spi_device_id msi001_id[] = {
+ {"msi001", 0},
+ {}
+};
+MODULE_DEVICE_TABLE(spi, msi001_id);
+
+static struct spi_driver msi001_driver = {
+ .driver = {
+ .name = "msi001",
+ .owner = THIS_MODULE,
+ },
+ .probe = msi001_probe,
+ .remove = msi001_remove,
+ .id_table = msi001_id,
+};
+module_spi_driver(msi001_driver);
+
+MODULE_AUTHOR("Antti Palosaari <crope@iki.fi>");
+MODULE_DESCRIPTION("Mirics MSi001");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/media/msi3101/sdr-msi3101.c b/drivers/staging/media/msi3101/sdr-msi3101.c
new file mode 100644
index 00000000000..08d0d096b88
--- /dev/null
+++ b/drivers/staging/media/msi3101/sdr-msi3101.c
@@ -0,0 +1,1518 @@
+/*
+ * Mirics MSi3101 SDR Dongle driver
+ *
+ * Copyright (C) 2013 Antti Palosaari <crope@iki.fi>
+ *
+ * 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.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * That driver is somehow based of pwc driver:
+ * (C) 1999-2004 Nemosoft Unv.
+ * (C) 2004-2006 Luc Saillard (luc@saillard.org)
+ * (C) 2011 Hans de Goede <hdegoede@redhat.com>
+ */
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <asm/div64.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-event.h>
+#include <linux/usb.h>
+#include <media/videobuf2-vmalloc.h>
+#include <linux/spi/spi.h>
+
+/*
+ * iConfiguration 0
+ * bInterfaceNumber 0
+ * bAlternateSetting 1
+ * bNumEndpoints 1
+ * bEndpointAddress 0x81 EP 1 IN
+ * bmAttributes 1
+ * Transfer Type Isochronous
+ * wMaxPacketSize 0x1400 3x 1024 bytes
+ * bInterval 1
+ */
+#define MAX_ISO_BUFS (8)
+#define ISO_FRAMES_PER_DESC (8)
+#define ISO_MAX_FRAME_SIZE (3 * 1024)
+#define ISO_BUFFER_SIZE (ISO_FRAMES_PER_DESC * ISO_MAX_FRAME_SIZE)
+#define MAX_ISOC_ERRORS 20
+
+/* TODO: These should be moved to V4L2 API */
+#define V4L2_PIX_FMT_SDR_S8 v4l2_fourcc('D', 'S', '0', '8') /* signed 8-bit */
+#define V4L2_PIX_FMT_SDR_S12 v4l2_fourcc('D', 'S', '1', '2') /* signed 12-bit */
+#define V4L2_PIX_FMT_SDR_S14 v4l2_fourcc('D', 'S', '1', '4') /* signed 14-bit */
+#define V4L2_PIX_FMT_SDR_MSI2500_384 v4l2_fourcc('M', '3', '8', '4') /* Mirics MSi2500 format 384 */
+
+static const struct v4l2_frequency_band bands[] = {
+ {
+ .tuner = 0,
+ .type = V4L2_TUNER_ADC,
+ .index = 0,
+ .capability = V4L2_TUNER_CAP_1HZ | V4L2_TUNER_CAP_FREQ_BANDS,
+ .rangelow = 1200000,
+ .rangehigh = 15000000,
+ },
+};
+
+/* stream formats */
+struct msi3101_format {
+ char *name;
+ u32 pixelformat;
+};
+
+/* format descriptions for capture and preview */
+static struct msi3101_format formats[] = {
+ {
+ .name = "IQ U8",
+ .pixelformat = V4L2_SDR_FMT_CU8,
+ }, {
+ .name = "IQ U16LE",
+ .pixelformat = V4L2_SDR_FMT_CU16LE,
+#if 0
+ }, {
+ .name = "8-bit signed",
+ .pixelformat = V4L2_PIX_FMT_SDR_S8,
+ }, {
+ .name = "10+2-bit signed",
+ .pixelformat = V4L2_PIX_FMT_SDR_MSI2500_384,
+ }, {
+ .name = "12-bit signed",
+ .pixelformat = V4L2_PIX_FMT_SDR_S12,
+ }, {
+ .name = "14-bit signed",
+ .pixelformat = V4L2_PIX_FMT_SDR_S14,
+#endif
+ },
+};
+
+static const unsigned int NUM_FORMATS = ARRAY_SIZE(formats);
+
+/* intermediate buffers with raw data from the USB device */
+struct msi3101_frame_buf {
+ struct vb2_buffer vb; /* common v4l buffer stuff -- must be first */
+ struct list_head list;
+};
+
+struct msi3101_state {
+ struct video_device vdev;
+ struct v4l2_device v4l2_dev;
+ struct v4l2_subdev *v4l2_subdev;
+ struct spi_master *master;
+
+ /* videobuf2 queue and queued buffers list */
+ struct vb2_queue vb_queue;
+ struct list_head queued_bufs;
+ spinlock_t queued_bufs_lock; /* Protects queued_bufs */
+
+ /* Note if taking both locks v4l2_lock must always be locked first! */
+ struct mutex v4l2_lock; /* Protects everything else */
+ struct mutex vb_queue_lock; /* Protects vb_queue and capt_file */
+
+ /* Pointer to our usb_device, will be NULL after unplug */
+ struct usb_device *udev; /* Both mutexes most be hold when setting! */
+
+ unsigned int f_adc;
+ u32 pixelformat;
+
+ unsigned int isoc_errors; /* number of contiguous ISOC errors */
+ unsigned int vb_full; /* vb is full and packets dropped */
+
+ struct urb *urbs[MAX_ISO_BUFS];
+ int (*convert_stream)(struct msi3101_state *s, u8 *dst, u8 *src,
+ unsigned int src_len);
+
+ /* Controls */
+ struct v4l2_ctrl_handler hdl;
+
+ u32 next_sample; /* for track lost packets */
+ u32 sample; /* for sample rate calc */
+ unsigned long jiffies_next;
+ unsigned int sample_ctrl_bit[4];
+};
+
+/* Private functions */
+static struct msi3101_frame_buf *msi3101_get_next_fill_buf(
+ struct msi3101_state *s)
+{
+ unsigned long flags = 0;
+ struct msi3101_frame_buf *buf = NULL;
+
+ spin_lock_irqsave(&s->queued_bufs_lock, flags);
+ if (list_empty(&s->queued_bufs))
+ goto leave;
+
+ buf = list_entry(s->queued_bufs.next, struct msi3101_frame_buf, list);
+ list_del(&buf->list);
+leave:
+ spin_unlock_irqrestore(&s->queued_bufs_lock, flags);
+ return buf;
+}
+
+/*
+ * +===========================================================================
+ * | 00-1023 | USB packet type '504'
+ * +===========================================================================
+ * | 00- 03 | sequence number of first sample in that USB packet
+ * +---------------------------------------------------------------------------
+ * | 04- 15 | garbage
+ * +---------------------------------------------------------------------------
+ * | 16-1023 | samples
+ * +---------------------------------------------------------------------------
+ * signed 8-bit sample
+ * 504 * 2 = 1008 samples
+ */
+static int msi3101_convert_stream_504(struct msi3101_state *s, u8 *dst,
+ u8 *src, unsigned int src_len)
+{
+ int i, i_max, dst_len = 0;
+ u32 sample_num[3];
+
+ /* There could be 1-3 1024 bytes URB frames */
+ i_max = src_len / 1024;
+
+ for (i = 0; i < i_max; i++) {
+ sample_num[i] = src[3] << 24 | src[2] << 16 | src[1] << 8 | src[0] << 0;
+ if (i == 0 && s->next_sample != sample_num[0]) {
+ dev_dbg_ratelimited(&s->udev->dev,
+ "%d samples lost, %d %08x:%08x\n",
+ sample_num[0] - s->next_sample,
+ src_len, s->next_sample, sample_num[0]);
+ }
+
+ /*
+ * Dump all unknown 'garbage' data - maybe we will discover
+ * someday if there is something rational...
+ */
+ dev_dbg_ratelimited(&s->udev->dev, "%*ph\n", 12, &src[4]);
+
+ /* 504 x I+Q samples */
+ src += 16;
+ memcpy(dst, src, 1008);
+ src += 1008;
+ dst += 1008;
+ dst_len += 1008;
+ }
+
+ /* calculate samping rate and output it in 10 seconds intervals */
+ if ((s->jiffies_next + msecs_to_jiffies(10000)) <= jiffies) {
+ unsigned long jiffies_now = jiffies;
+ unsigned long msecs = jiffies_to_msecs(jiffies_now) - jiffies_to_msecs(s->jiffies_next);
+ unsigned int samples = sample_num[i_max - 1] - s->sample;
+ s->jiffies_next = jiffies_now;
+ s->sample = sample_num[i_max - 1];
+ dev_dbg(&s->udev->dev,
+ "slen=%d samples=%u msecs=%lu sampling rate=%lu\n",
+ src_len, samples, msecs,
+ samples * 1000UL / msecs);
+ }
+
+ /* next sample (sample = sample + i * 504) */
+ s->next_sample = sample_num[i_max - 1] + 504;
+
+ return dst_len;
+}
+
+static int msi3101_convert_stream_504_u8(struct msi3101_state *s, u8 *dst,
+ u8 *src, unsigned int src_len)
+{
+ int i, j, i_max, dst_len = 0;
+ u32 sample_num[3];
+ s8 *s8src;
+ u8 *u8dst;
+
+ /* There could be 1-3 1024 bytes URB frames */
+ i_max = src_len / 1024;
+ u8dst = (u8 *) dst;
+
+ for (i = 0; i < i_max; i++) {
+ sample_num[i] = src[3] << 24 | src[2] << 16 | src[1] << 8 | src[0] << 0;
+ if (i == 0 && s->next_sample != sample_num[0]) {
+ dev_dbg_ratelimited(&s->udev->dev,
+ "%d samples lost, %d %08x:%08x\n",
+ sample_num[0] - s->next_sample,
+ src_len, s->next_sample, sample_num[0]);
+ }
+
+ /*
+ * Dump all unknown 'garbage' data - maybe we will discover
+ * someday if there is something rational...
+ */
+ dev_dbg_ratelimited(&s->udev->dev, "%*ph\n", 12, &src[4]);
+
+ /* 504 x I+Q samples */
+ src += 16;
+
+ s8src = (s8 *) src;
+ for (j = 0; j < 1008; j++)
+ *u8dst++ = *s8src++ + 128;
+
+ src += 1008;
+ dst += 1008;
+ dst_len += 1008;
+ }
+
+ /* calculate samping rate and output it in 10 seconds intervals */
+ if (unlikely(time_is_before_jiffies(s->jiffies_next))) {
+#define MSECS 10000UL
+ unsigned int samples = sample_num[i_max - 1] - s->sample;
+ s->jiffies_next = jiffies + msecs_to_jiffies(MSECS);
+ s->sample = sample_num[i_max - 1];
+ dev_dbg(&s->udev->dev,
+ "slen=%d samples=%u msecs=%lu sampling rate=%lu\n",
+ src_len, samples, MSECS,
+ samples * 1000UL / MSECS);
+ }
+
+ /* next sample (sample = sample + i * 504) */
+ s->next_sample = sample_num[i_max - 1] + 504;
+
+ return dst_len;
+}
+
+/*
+ * +===========================================================================
+ * | 00-1023 | USB packet type '384'
+ * +===========================================================================
+ * | 00- 03 | sequence number of first sample in that USB packet
+ * +---------------------------------------------------------------------------
+ * | 04- 15 | garbage
+ * +---------------------------------------------------------------------------
+ * | 16- 175 | samples
+ * +---------------------------------------------------------------------------
+ * | 176- 179 | control bits for previous samples
+ * +---------------------------------------------------------------------------
+ * | 180- 339 | samples
+ * +---------------------------------------------------------------------------
+ * | 340- 343 | control bits for previous samples
+ * +---------------------------------------------------------------------------
+ * | 344- 503 | samples
+ * +---------------------------------------------------------------------------
+ * | 504- 507 | control bits for previous samples
+ * +---------------------------------------------------------------------------
+ * | 508- 667 | samples
+ * +---------------------------------------------------------------------------
+ * | 668- 671 | control bits for previous samples
+ * +---------------------------------------------------------------------------
+ * | 672- 831 | samples
+ * +---------------------------------------------------------------------------
+ * | 832- 835 | control bits for previous samples
+ * +---------------------------------------------------------------------------
+ * | 836- 995 | samples
+ * +---------------------------------------------------------------------------
+ * | 996- 999 | control bits for previous samples
+ * +---------------------------------------------------------------------------
+ * | 1000-1023 | garbage
+ * +---------------------------------------------------------------------------
+ *
+ * Bytes 4 - 7 could have some meaning?
+ *
+ * Control bits for previous samples is 32-bit field, containing 16 x 2-bit
+ * numbers. This results one 2-bit number for 8 samples. It is likely used for
+ * for bit shifting sample by given bits, increasing actual sampling resolution.
+ * Number 2 (0b10) was never seen.
+ *
+ * 6 * 16 * 2 * 4 = 768 samples. 768 * 4 = 3072 bytes
+ */
+static int msi3101_convert_stream_384(struct msi3101_state *s, u8 *dst,
+ u8 *src, unsigned int src_len)
+{
+ int i, i_max, dst_len = 0;
+ u32 sample_num[3];
+
+ /* There could be 1-3 1024 bytes URB frames */
+ i_max = src_len / 1024;
+ for (i = 0; i < i_max; i++) {
+ sample_num[i] = src[3] << 24 | src[2] << 16 | src[1] << 8 | src[0] << 0;
+ if (i == 0 && s->next_sample != sample_num[0]) {
+ dev_dbg_ratelimited(&s->udev->dev,
+ "%d samples lost, %d %08x:%08x\n",
+ sample_num[0] - s->next_sample,
+ src_len, s->next_sample, sample_num[0]);
+ }
+
+ /*
+ * Dump all unknown 'garbage' data - maybe we will discover
+ * someday if there is something rational...
+ */
+ dev_dbg_ratelimited(&s->udev->dev,
+ "%*ph %*ph\n", 12, &src[4], 24, &src[1000]);
+
+ /* 384 x I+Q samples */
+ src += 16;
+ memcpy(dst, src, 984);
+ src += 984 + 24;
+ dst += 984;
+ dst_len += 984;
+ }
+
+ /* calculate samping rate and output it in 10 seconds intervals */
+ if ((s->jiffies_next + msecs_to_jiffies(10000)) <= jiffies) {
+ unsigned long jiffies_now = jiffies;
+ unsigned long msecs = jiffies_to_msecs(jiffies_now) - jiffies_to_msecs(s->jiffies_next);
+ unsigned int samples = sample_num[i_max - 1] - s->sample;
+ s->jiffies_next = jiffies_now;
+ s->sample = sample_num[i_max - 1];
+ dev_dbg(&s->udev->dev,
+ "slen=%d samples=%u msecs=%lu sampling rate=%lu bits=%d.%d.%d.%d\n",
+ src_len, samples, msecs,
+ samples * 1000UL / msecs,
+ s->sample_ctrl_bit[0], s->sample_ctrl_bit[1],
+ s->sample_ctrl_bit[2], s->sample_ctrl_bit[3]);
+ }
+
+ /* next sample (sample = sample + i * 384) */
+ s->next_sample = sample_num[i_max - 1] + 384;
+
+ return dst_len;
+}
+
+/*
+ * +===========================================================================
+ * | 00-1023 | USB packet type '336'
+ * +===========================================================================
+ * | 00- 03 | sequence number of first sample in that USB packet
+ * +---------------------------------------------------------------------------
+ * | 04- 15 | garbage
+ * +---------------------------------------------------------------------------
+ * | 16-1023 | samples
+ * +---------------------------------------------------------------------------
+ * signed 12-bit sample
+ */
+static int msi3101_convert_stream_336(struct msi3101_state *s, u8 *dst,
+ u8 *src, unsigned int src_len)
+{
+ int i, i_max, dst_len = 0;
+ u32 sample_num[3];
+
+ /* There could be 1-3 1024 bytes URB frames */
+ i_max = src_len / 1024;
+
+ for (i = 0; i < i_max; i++) {
+ sample_num[i] = src[3] << 24 | src[2] << 16 | src[1] << 8 | src[0] << 0;
+ if (i == 0 && s->next_sample != sample_num[0]) {
+ dev_dbg_ratelimited(&s->udev->dev,
+ "%d samples lost, %d %08x:%08x\n",
+ sample_num[0] - s->next_sample,
+ src_len, s->next_sample, sample_num[0]);
+ }
+
+ /*
+ * Dump all unknown 'garbage' data - maybe we will discover
+ * someday if there is something rational...
+ */
+ dev_dbg_ratelimited(&s->udev->dev, "%*ph\n", 12, &src[4]);
+
+ /* 336 x I+Q samples */
+ src += 16;
+ memcpy(dst, src, 1008);
+ src += 1008;
+ dst += 1008;
+ dst_len += 1008;
+ }
+
+ /* calculate samping rate and output it in 10 seconds intervals */
+ if ((s->jiffies_next + msecs_to_jiffies(10000)) <= jiffies) {
+ unsigned long jiffies_now = jiffies;
+ unsigned long msecs = jiffies_to_msecs(jiffies_now) - jiffies_to_msecs(s->jiffies_next);
+ unsigned int samples = sample_num[i_max - 1] - s->sample;
+ s->jiffies_next = jiffies_now;
+ s->sample = sample_num[i_max - 1];
+ dev_dbg(&s->udev->dev,
+ "slen=%d samples=%u msecs=%lu sampling rate=%lu\n",
+ src_len, samples, msecs,
+ samples * 1000UL / msecs);
+ }
+
+ /* next sample (sample = sample + i * 336) */
+ s->next_sample = sample_num[i_max - 1] + 336;
+
+ return dst_len;
+}
+
+/*
+ * +===========================================================================
+ * | 00-1023 | USB packet type '252'
+ * +===========================================================================
+ * | 00- 03 | sequence number of first sample in that USB packet
+ * +---------------------------------------------------------------------------
+ * | 04- 15 | garbage
+ * +---------------------------------------------------------------------------
+ * | 16-1023 | samples
+ * +---------------------------------------------------------------------------
+ * signed 14-bit sample
+ */
+static int msi3101_convert_stream_252(struct msi3101_state *s, u8 *dst,
+ u8 *src, unsigned int src_len)
+{
+ int i, i_max, dst_len = 0;
+ u32 sample_num[3];
+
+ /* There could be 1-3 1024 bytes URB frames */
+ i_max = src_len / 1024;
+
+ for (i = 0; i < i_max; i++) {
+ sample_num[i] = src[3] << 24 | src[2] << 16 | src[1] << 8 | src[0] << 0;
+ if (i == 0 && s->next_sample != sample_num[0]) {
+ dev_dbg_ratelimited(&s->udev->dev,
+ "%d samples lost, %d %08x:%08x\n",
+ sample_num[0] - s->next_sample,
+ src_len, s->next_sample, sample_num[0]);
+ }
+
+ /*
+ * Dump all unknown 'garbage' data - maybe we will discover
+ * someday if there is something rational...
+ */
+ dev_dbg_ratelimited(&s->udev->dev, "%*ph\n", 12, &src[4]);
+
+ /* 252 x I+Q samples */
+ src += 16;
+ memcpy(dst, src, 1008);
+ src += 1008;
+ dst += 1008;
+ dst_len += 1008;
+ }
+
+ /* calculate samping rate and output it in 10 seconds intervals */
+ if ((s->jiffies_next + msecs_to_jiffies(10000)) <= jiffies) {
+ unsigned long jiffies_now = jiffies;
+ unsigned long msecs = jiffies_to_msecs(jiffies_now) - jiffies_to_msecs(s->jiffies_next);
+ unsigned int samples = sample_num[i_max - 1] - s->sample;
+ s->jiffies_next = jiffies_now;
+ s->sample = sample_num[i_max - 1];
+ dev_dbg(&s->udev->dev,
+ "slen=%d samples=%u msecs=%lu sampling rate=%lu\n",
+ src_len, samples, msecs,
+ samples * 1000UL / msecs);
+ }
+
+ /* next sample (sample = sample + i * 252) */
+ s->next_sample = sample_num[i_max - 1] + 252;
+
+ return dst_len;
+}
+
+static int msi3101_convert_stream_252_u16(struct msi3101_state *s, u8 *dst,
+ u8 *src, unsigned int src_len)
+{
+ int i, j, i_max, dst_len = 0;
+ u32 sample_num[3];
+ u16 *u16dst = (u16 *) dst;
+ struct {signed int x:14;} se;
+
+ /* There could be 1-3 1024 bytes URB frames */
+ i_max = src_len / 1024;
+
+ for (i = 0; i < i_max; i++) {
+ sample_num[i] = src[3] << 24 | src[2] << 16 | src[1] << 8 | src[0] << 0;
+ if (i == 0 && s->next_sample != sample_num[0]) {
+ dev_dbg_ratelimited(&s->udev->dev,
+ "%d samples lost, %d %08x:%08x\n",
+ sample_num[0] - s->next_sample,
+ src_len, s->next_sample, sample_num[0]);
+ }
+
+ /*
+ * Dump all unknown 'garbage' data - maybe we will discover
+ * someday if there is something rational...
+ */
+ dev_dbg_ratelimited(&s->udev->dev, "%*ph\n", 12, &src[4]);
+
+ /* 252 x I+Q samples */
+ src += 16;
+
+ for (j = 0; j < 1008; j += 4) {
+ unsigned int usample[2];
+ int ssample[2];
+
+ usample[0] = src[j + 0] >> 0 | src[j + 1] << 8;
+ usample[1] = src[j + 2] >> 0 | src[j + 3] << 8;
+
+ /* sign extension from 14-bit to signed int */
+ ssample[0] = se.x = usample[0];
+ ssample[1] = se.x = usample[1];
+
+ /* from signed to unsigned */
+ usample[0] = ssample[0] + 8192;
+ usample[1] = ssample[1] + 8192;
+
+ /* from 14-bit to 16-bit */
+ *u16dst++ = (usample[0] << 2) | (usample[0] >> 12);
+ *u16dst++ = (usample[1] << 2) | (usample[1] >> 12);
+ }
+
+ src += 1008;
+ dst += 1008;
+ dst_len += 1008;
+ }
+
+ /* calculate samping rate and output it in 10 seconds intervals */
+ if (unlikely(time_is_before_jiffies(s->jiffies_next))) {
+#define MSECS 10000UL
+ unsigned int samples = sample_num[i_max - 1] - s->sample;
+ s->jiffies_next = jiffies + msecs_to_jiffies(MSECS);
+ s->sample = sample_num[i_max - 1];
+ dev_dbg(&s->udev->dev,
+ "slen=%d samples=%u msecs=%lu sampling rate=%lu\n",
+ src_len, samples, MSECS,
+ samples * 1000UL / MSECS);
+ }
+
+ /* next sample (sample = sample + i * 252) */
+ s->next_sample = sample_num[i_max - 1] + 252;
+
+ return dst_len;
+}
+
+/*
+ * This gets called for the Isochronous pipe (stream). This is done in interrupt
+ * time, so it has to be fast, not crash, and not stall. Neat.
+ */
+static void msi3101_isoc_handler(struct urb *urb)
+{
+ struct msi3101_state *s = (struct msi3101_state *)urb->context;
+ int i, flen, fstatus;
+ unsigned char *iso_buf = NULL;
+ struct msi3101_frame_buf *fbuf;
+
+ if (unlikely(urb->status == -ENOENT || urb->status == -ECONNRESET ||
+ urb->status == -ESHUTDOWN)) {
+ dev_dbg(&s->udev->dev, "URB (%p) unlinked %ssynchronuously\n",
+ urb, urb->status == -ENOENT ? "" : "a");
+ return;
+ }
+
+ if (unlikely(urb->status != 0)) {
+ dev_dbg(&s->udev->dev,
+ "msi3101_isoc_handler() called with status %d\n",
+ urb->status);
+ /* Give up after a number of contiguous errors */
+ if (++s->isoc_errors > MAX_ISOC_ERRORS)
+ dev_dbg(&s->udev->dev,
+ "Too many ISOC errors, bailing out\n");
+ goto handler_end;
+ } else {
+ /* Reset ISOC error counter. We did get here, after all. */
+ s->isoc_errors = 0;
+ }
+
+ /* Compact data */
+ for (i = 0; i < urb->number_of_packets; i++) {
+ void *ptr;
+
+ /* Check frame error */
+ fstatus = urb->iso_frame_desc[i].status;
+ if (unlikely(fstatus)) {
+ dev_dbg_ratelimited(&s->udev->dev,
+ "frame=%d/%d has error %d skipping\n",
+ i, urb->number_of_packets, fstatus);
+ continue;
+ }
+
+ /* Check if that frame contains data */
+ flen = urb->iso_frame_desc[i].actual_length;
+ if (unlikely(flen == 0))
+ continue;
+
+ iso_buf = urb->transfer_buffer + urb->iso_frame_desc[i].offset;
+
+ /* Get free framebuffer */
+ fbuf = msi3101_get_next_fill_buf(s);
+ if (unlikely(fbuf == NULL)) {
+ s->vb_full++;
+ dev_dbg_ratelimited(&s->udev->dev,
+ "videobuf is full, %d packets dropped\n",
+ s->vb_full);
+ continue;
+ }
+
+ /* fill framebuffer */
+ ptr = vb2_plane_vaddr(&fbuf->vb, 0);
+ flen = s->convert_stream(s, ptr, iso_buf, flen);
+ vb2_set_plane_payload(&fbuf->vb, 0, flen);
+ vb2_buffer_done(&fbuf->vb, VB2_BUF_STATE_DONE);
+ }
+
+handler_end:
+ i = usb_submit_urb(urb, GFP_ATOMIC);
+ if (unlikely(i != 0))
+ dev_dbg(&s->udev->dev,
+ "Error (%d) re-submitting urb in msi3101_isoc_handler\n",
+ i);
+}
+
+static void msi3101_iso_stop(struct msi3101_state *s)
+{
+ int i;
+ dev_dbg(&s->udev->dev, "%s:\n", __func__);
+
+ /* Unlinking ISOC buffers one by one */
+ for (i = 0; i < MAX_ISO_BUFS; i++) {
+ if (s->urbs[i]) {
+ dev_dbg(&s->udev->dev, "Unlinking URB %p\n",
+ s->urbs[i]);
+ usb_kill_urb(s->urbs[i]);
+ }
+ }
+}
+
+static void msi3101_iso_free(struct msi3101_state *s)
+{
+ int i;
+ dev_dbg(&s->udev->dev, "%s:\n", __func__);
+
+ /* Freeing ISOC buffers one by one */
+ for (i = 0; i < MAX_ISO_BUFS; i++) {
+ if (s->urbs[i]) {
+ dev_dbg(&s->udev->dev, "Freeing URB\n");
+ if (s->urbs[i]->transfer_buffer) {
+ usb_free_coherent(s->udev,
+ s->urbs[i]->transfer_buffer_length,
+ s->urbs[i]->transfer_buffer,
+ s->urbs[i]->transfer_dma);
+ }
+ usb_free_urb(s->urbs[i]);
+ s->urbs[i] = NULL;
+ }
+ }
+}
+
+/* Both v4l2_lock and vb_queue_lock should be locked when calling this */
+static void msi3101_isoc_cleanup(struct msi3101_state *s)
+{
+ dev_dbg(&s->udev->dev, "%s:\n", __func__);
+
+ msi3101_iso_stop(s);
+ msi3101_iso_free(s);
+}
+
+/* Both v4l2_lock and vb_queue_lock should be locked when calling this */
+static int msi3101_isoc_init(struct msi3101_state *s)
+{
+ struct usb_device *udev;
+ struct urb *urb;
+ int i, j, ret;
+ dev_dbg(&s->udev->dev, "%s:\n", __func__);
+
+ s->isoc_errors = 0;
+ udev = s->udev;
+
+ ret = usb_set_interface(s->udev, 0, 1);
+ if (ret)
+ return ret;
+
+ /* Allocate and init Isochronuous urbs */
+ for (i = 0; i < MAX_ISO_BUFS; i++) {
+ urb = usb_alloc_urb(ISO_FRAMES_PER_DESC, GFP_KERNEL);
+ if (urb == NULL) {
+ dev_err(&s->udev->dev,
+ "Failed to allocate urb %d\n", i);
+ msi3101_isoc_cleanup(s);
+ return -ENOMEM;
+ }
+ s->urbs[i] = urb;
+ dev_dbg(&s->udev->dev, "Allocated URB at 0x%p\n", urb);
+
+ urb->interval = 1;
+ urb->dev = udev;
+ urb->pipe = usb_rcvisocpipe(udev, 0x81);
+ urb->transfer_flags = URB_ISO_ASAP | URB_NO_TRANSFER_DMA_MAP;
+ urb->transfer_buffer = usb_alloc_coherent(udev, ISO_BUFFER_SIZE,
+ GFP_KERNEL, &urb->transfer_dma);
+ if (urb->transfer_buffer == NULL) {
+ dev_err(&s->udev->dev,
+ "Failed to allocate urb buffer %d\n",
+ i);
+ msi3101_isoc_cleanup(s);
+ return -ENOMEM;
+ }
+ urb->transfer_buffer_length = ISO_BUFFER_SIZE;
+ urb->complete = msi3101_isoc_handler;
+ urb->context = s;
+ urb->start_frame = 0;
+ urb->number_of_packets = ISO_FRAMES_PER_DESC;
+ for (j = 0; j < ISO_FRAMES_PER_DESC; j++) {
+ urb->iso_frame_desc[j].offset = j * ISO_MAX_FRAME_SIZE;
+ urb->iso_frame_desc[j].length = ISO_MAX_FRAME_SIZE;
+ }
+ }
+
+ /* link */
+ for (i = 0; i < MAX_ISO_BUFS; i++) {
+ ret = usb_submit_urb(s->urbs[i], GFP_KERNEL);
+ if (ret) {
+ dev_err(&s->udev->dev,
+ "isoc_init() submit_urb %d failed with error %d\n",
+ i, ret);
+ msi3101_isoc_cleanup(s);
+ return ret;
+ }
+ dev_dbg(&s->udev->dev, "URB 0x%p submitted.\n", s->urbs[i]);
+ }
+
+ /* All is done... */
+ return 0;
+}
+
+/* Must be called with vb_queue_lock hold */
+static void msi3101_cleanup_queued_bufs(struct msi3101_state *s)
+{
+ unsigned long flags = 0;
+ dev_dbg(&s->udev->dev, "%s:\n", __func__);
+
+ spin_lock_irqsave(&s->queued_bufs_lock, flags);
+ while (!list_empty(&s->queued_bufs)) {
+ struct msi3101_frame_buf *buf;
+
+ buf = list_entry(s->queued_bufs.next, struct msi3101_frame_buf,
+ list);
+ list_del(&buf->list);
+ vb2_buffer_done(&buf->vb, VB2_BUF_STATE_ERROR);
+ }
+ spin_unlock_irqrestore(&s->queued_bufs_lock, flags);
+}
+
+/* The user yanked out the cable... */
+static void msi3101_disconnect(struct usb_interface *intf)
+{
+ struct v4l2_device *v = usb_get_intfdata(intf);
+ struct msi3101_state *s =
+ container_of(v, struct msi3101_state, v4l2_dev);
+ dev_dbg(&s->udev->dev, "%s:\n", __func__);
+
+ mutex_lock(&s->vb_queue_lock);
+ mutex_lock(&s->v4l2_lock);
+ /* No need to keep the urbs around after disconnection */
+ s->udev = NULL;
+ v4l2_device_disconnect(&s->v4l2_dev);
+ video_unregister_device(&s->vdev);
+ spi_unregister_master(s->master);
+ mutex_unlock(&s->v4l2_lock);
+ mutex_unlock(&s->vb_queue_lock);
+
+ v4l2_device_put(&s->v4l2_dev);
+}
+
+static int msi3101_querycap(struct file *file, void *fh,
+ struct v4l2_capability *cap)
+{
+ struct msi3101_state *s = video_drvdata(file);
+ dev_dbg(&s->udev->dev, "%s:\n", __func__);
+
+ strlcpy(cap->driver, KBUILD_MODNAME, sizeof(cap->driver));
+ strlcpy(cap->card, s->vdev.name, sizeof(cap->card));
+ usb_make_path(s->udev, cap->bus_info, sizeof(cap->bus_info));
+ cap->device_caps = V4L2_CAP_SDR_CAPTURE | V4L2_CAP_STREAMING |
+ V4L2_CAP_READWRITE | V4L2_CAP_TUNER;
+ cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS;
+ return 0;
+}
+
+/* Videobuf2 operations */
+static int msi3101_queue_setup(struct vb2_queue *vq,
+ const struct v4l2_format *fmt, unsigned int *nbuffers,
+ unsigned int *nplanes, unsigned int sizes[], void *alloc_ctxs[])
+{
+ struct msi3101_state *s = vb2_get_drv_priv(vq);
+ dev_dbg(&s->udev->dev, "%s: *nbuffers=%d\n", __func__, *nbuffers);
+
+ /* Absolute min and max number of buffers available for mmap() */
+ *nbuffers = clamp_t(unsigned int, *nbuffers, 8, 32);
+ *nplanes = 1;
+ /*
+ * 3, wMaxPacketSize 3x 1024 bytes
+ * 504, max IQ sample pairs per 1024 frame
+ * 2, two samples, I and Q
+ * 2, 16-bit is enough for single sample
+ */
+ sizes[0] = PAGE_ALIGN(3 * 504 * 2 * 2);
+ dev_dbg(&s->udev->dev, "%s: nbuffers=%d sizes[0]=%d\n",
+ __func__, *nbuffers, sizes[0]);
+ return 0;
+}
+
+static void msi3101_buf_queue(struct vb2_buffer *vb)
+{
+ struct msi3101_state *s = vb2_get_drv_priv(vb->vb2_queue);
+ struct msi3101_frame_buf *buf =
+ container_of(vb, struct msi3101_frame_buf, vb);
+ unsigned long flags = 0;
+
+ /* Check the device has not disconnected between prep and queuing */
+ if (unlikely(!s->udev)) {
+ vb2_buffer_done(&buf->vb, VB2_BUF_STATE_ERROR);
+ return;
+ }
+
+ spin_lock_irqsave(&s->queued_bufs_lock, flags);
+ list_add_tail(&buf->list, &s->queued_bufs);
+ spin_unlock_irqrestore(&s->queued_bufs_lock, flags);
+}
+
+#define CMD_WREG 0x41
+#define CMD_START_STREAMING 0x43
+#define CMD_STOP_STREAMING 0x45
+#define CMD_READ_UNKNOW 0x48
+
+#define msi3101_dbg_usb_control_msg(udev, r, t, v, _i, b, l) { \
+ char *direction; \
+ if (t == (USB_TYPE_VENDOR | USB_DIR_OUT)) \
+ direction = ">>>"; \
+ else \
+ direction = "<<<"; \
+ dev_dbg(&udev->dev, "%s: %02x %02x %02x %02x %02x %02x %02x %02x " \
+ "%s %*ph\n", __func__, t, r, v & 0xff, v >> 8, \
+ _i & 0xff, _i >> 8, l & 0xff, l >> 8, direction, l, b); \
+}
+
+static int msi3101_ctrl_msg(struct msi3101_state *s, u8 cmd, u32 data)
+{
+ int ret;
+ u8 request = cmd;
+ u8 requesttype = USB_DIR_OUT | USB_TYPE_VENDOR;
+ u16 value = (data >> 0) & 0xffff;
+ u16 index = (data >> 16) & 0xffff;
+
+ msi3101_dbg_usb_control_msg(s->udev,
+ request, requesttype, value, index, NULL, 0);
+
+ ret = usb_control_msg(s->udev, usb_sndctrlpipe(s->udev, 0),
+ request, requesttype, value, index, NULL, 0, 2000);
+
+ if (ret)
+ dev_err(&s->udev->dev, "%s: failed %d, cmd %02x, data %04x\n",
+ __func__, ret, cmd, data);
+
+ return ret;
+};
+
+#define F_REF 24000000
+#define DIV_R_IN 2
+static int msi3101_set_usb_adc(struct msi3101_state *s)
+{
+ int ret, div_n, div_m, div_r_out, f_sr, f_vco, fract;
+ u32 reg3, reg4, reg7;
+ struct v4l2_ctrl *bandwidth_auto;
+ struct v4l2_ctrl *bandwidth;
+
+ f_sr = s->f_adc;
+
+ /* set tuner, subdev, filters according to sampling rate */
+ bandwidth_auto = v4l2_ctrl_find(&s->hdl, V4L2_CID_RF_TUNER_BANDWIDTH_AUTO);
+ if (v4l2_ctrl_g_ctrl(bandwidth_auto)) {
+ bandwidth = v4l2_ctrl_find(&s->hdl, V4L2_CID_RF_TUNER_BANDWIDTH);
+ v4l2_ctrl_s_ctrl(bandwidth, s->f_adc);
+ }
+
+ /* select stream format */
+ switch (s->pixelformat) {
+ case V4L2_SDR_FMT_CU8:
+ s->convert_stream = msi3101_convert_stream_504_u8;
+ reg7 = 0x000c9407;
+ break;
+ case V4L2_SDR_FMT_CU16LE:
+ s->convert_stream = msi3101_convert_stream_252_u16;
+ reg7 = 0x00009407;
+ break;
+ case V4L2_PIX_FMT_SDR_S8:
+ s->convert_stream = msi3101_convert_stream_504;
+ reg7 = 0x000c9407;
+ break;
+ case V4L2_PIX_FMT_SDR_MSI2500_384:
+ s->convert_stream = msi3101_convert_stream_384;
+ reg7 = 0x0000a507;
+ break;
+ case V4L2_PIX_FMT_SDR_S12:
+ s->convert_stream = msi3101_convert_stream_336;
+ reg7 = 0x00008507;
+ break;
+ case V4L2_PIX_FMT_SDR_S14:
+ s->convert_stream = msi3101_convert_stream_252;
+ reg7 = 0x00009407;
+ break;
+ default:
+ s->convert_stream = msi3101_convert_stream_504_u8;
+ reg7 = 0x000c9407;
+ break;
+ }
+
+ /*
+ * Synthesizer config is just a educated guess...
+ *
+ * [7:0] 0x03, register address
+ * [8] 1, power control
+ * [9] ?, power control
+ * [12:10] output divider
+ * [13] 0 ?
+ * [14] 0 ?
+ * [15] fractional MSB, bit 20
+ * [16:19] N
+ * [23:20] ?
+ * [24:31] 0x01
+ *
+ * output divider
+ * val div
+ * 0 - (invalid)
+ * 1 4
+ * 2 6
+ * 3 8
+ * 4 10
+ * 5 12
+ * 6 14
+ * 7 16
+ *
+ * VCO 202000000 - 720000000++
+ */
+ reg3 = 0x01000303;
+ reg4 = 0x00000004;
+
+ /* XXX: Filters? AGC? */
+ if (f_sr < 6000000)
+ reg3 |= 0x1 << 20;
+ else if (f_sr < 7000000)
+ reg3 |= 0x5 << 20;
+ else if (f_sr < 8500000)
+ reg3 |= 0x9 << 20;
+ else
+ reg3 |= 0xd << 20;
+
+ for (div_r_out = 4; div_r_out < 16; div_r_out += 2) {
+ f_vco = f_sr * div_r_out * 12;
+ dev_dbg(&s->udev->dev, "%s: div_r_out=%d f_vco=%d\n",
+ __func__, div_r_out, f_vco);
+ if (f_vco >= 202000000)
+ break;
+ }
+
+ div_n = f_vco / (F_REF * DIV_R_IN);
+ div_m = f_vco % (F_REF * DIV_R_IN);
+ fract = 0x200000ul * div_m / (F_REF * DIV_R_IN);
+
+ reg3 |= div_n << 16;
+ reg3 |= (div_r_out / 2 - 1) << 10;
+ reg3 |= ((fract >> 20) & 0x000001) << 15; /* [20] */
+ reg4 |= ((fract >> 0) & 0x0fffff) << 8; /* [19:0] */
+
+ dev_dbg(&s->udev->dev,
+ "%s: f_sr=%d f_vco=%d div_n=%d div_m=%d div_r_out=%d reg3=%08x reg4=%08x\n",
+ __func__, f_sr, f_vco, div_n, div_m, div_r_out, reg3, reg4);
+
+ ret = msi3101_ctrl_msg(s, CMD_WREG, 0x00608008);
+ if (ret)
+ goto err;
+
+ ret = msi3101_ctrl_msg(s, CMD_WREG, 0x00000c05);
+ if (ret)
+ goto err;
+
+ ret = msi3101_ctrl_msg(s, CMD_WREG, 0x00020000);
+ if (ret)
+ goto err;
+
+ ret = msi3101_ctrl_msg(s, CMD_WREG, 0x00480102);
+ if (ret)
+ goto err;
+
+ ret = msi3101_ctrl_msg(s, CMD_WREG, 0x00f38008);
+ if (ret)
+ goto err;
+
+ ret = msi3101_ctrl_msg(s, CMD_WREG, reg7);
+ if (ret)
+ goto err;
+
+ ret = msi3101_ctrl_msg(s, CMD_WREG, reg4);
+ if (ret)
+ goto err;
+
+ ret = msi3101_ctrl_msg(s, CMD_WREG, reg3);
+ if (ret)
+ goto err;
+err:
+ return ret;
+};
+
+static int msi3101_start_streaming(struct vb2_queue *vq, unsigned int count)
+{
+ struct msi3101_state *s = vb2_get_drv_priv(vq);
+ int ret;
+ dev_dbg(&s->udev->dev, "%s:\n", __func__);
+
+ if (!s->udev)
+ return -ENODEV;
+
+ if (mutex_lock_interruptible(&s->v4l2_lock))
+ return -ERESTARTSYS;
+
+ /* wake-up tuner */
+ v4l2_subdev_call(s->v4l2_subdev, core, s_power, 1);
+
+ ret = msi3101_set_usb_adc(s);
+
+ ret = msi3101_isoc_init(s);
+ if (ret)
+ msi3101_cleanup_queued_bufs(s);
+
+ ret = msi3101_ctrl_msg(s, CMD_START_STREAMING, 0);
+
+ mutex_unlock(&s->v4l2_lock);
+
+ return ret;
+}
+
+static void msi3101_stop_streaming(struct vb2_queue *vq)
+{
+ struct msi3101_state *s = vb2_get_drv_priv(vq);
+
+ dev_dbg(&s->udev->dev, "%s:\n", __func__);
+
+ mutex_lock(&s->v4l2_lock);
+
+ if (s->udev)
+ msi3101_isoc_cleanup(s);
+
+ msi3101_cleanup_queued_bufs(s);
+
+ /* according to tests, at least 700us delay is required */
+ msleep(20);
+ if (!msi3101_ctrl_msg(s, CMD_STOP_STREAMING, 0)) {
+ /* sleep USB IF / ADC */
+ msi3101_ctrl_msg(s, CMD_WREG, 0x01000003);
+ }
+
+ /* sleep tuner */
+ v4l2_subdev_call(s->v4l2_subdev, core, s_power, 0);
+
+ mutex_unlock(&s->v4l2_lock);
+}
+
+static struct vb2_ops msi3101_vb2_ops = {
+ .queue_setup = msi3101_queue_setup,
+ .buf_queue = msi3101_buf_queue,
+ .start_streaming = msi3101_start_streaming,
+ .stop_streaming = msi3101_stop_streaming,
+ .wait_prepare = vb2_ops_wait_prepare,
+ .wait_finish = vb2_ops_wait_finish,
+};
+
+static int msi3101_enum_fmt_sdr_cap(struct file *file, void *priv,
+ struct v4l2_fmtdesc *f)
+{
+ struct msi3101_state *s = video_drvdata(file);
+ dev_dbg(&s->udev->dev, "%s: index=%d\n", __func__, f->index);
+
+ if (f->index >= NUM_FORMATS)
+ return -EINVAL;
+
+ strlcpy(f->description, formats[f->index].name, sizeof(f->description));
+ f->pixelformat = formats[f->index].pixelformat;
+
+ return 0;
+}
+
+static int msi3101_g_fmt_sdr_cap(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct msi3101_state *s = video_drvdata(file);
+ dev_dbg(&s->udev->dev, "%s: pixelformat fourcc %4.4s\n", __func__,
+ (char *)&s->pixelformat);
+
+ memset(f->fmt.sdr.reserved, 0, sizeof(f->fmt.sdr.reserved));
+ f->fmt.sdr.pixelformat = s->pixelformat;
+
+ return 0;
+}
+
+static int msi3101_s_fmt_sdr_cap(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct msi3101_state *s = video_drvdata(file);
+ struct vb2_queue *q = &s->vb_queue;
+ int i;
+ dev_dbg(&s->udev->dev, "%s: pixelformat fourcc %4.4s\n", __func__,
+ (char *)&f->fmt.sdr.pixelformat);
+
+ if (vb2_is_busy(q))
+ return -EBUSY;
+
+ memset(f->fmt.sdr.reserved, 0, sizeof(f->fmt.sdr.reserved));
+ for (i = 0; i < NUM_FORMATS; i++) {
+ if (formats[i].pixelformat == f->fmt.sdr.pixelformat) {
+ s->pixelformat = f->fmt.sdr.pixelformat;
+ return 0;
+ }
+ }
+
+ f->fmt.sdr.pixelformat = formats[0].pixelformat;
+ s->pixelformat = formats[0].pixelformat;
+
+ return 0;
+}
+
+static int msi3101_try_fmt_sdr_cap(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct msi3101_state *s = video_drvdata(file);
+ int i;
+ dev_dbg(&s->udev->dev, "%s: pixelformat fourcc %4.4s\n", __func__,
+ (char *)&f->fmt.sdr.pixelformat);
+
+ memset(f->fmt.sdr.reserved, 0, sizeof(f->fmt.sdr.reserved));
+ for (i = 0; i < NUM_FORMATS; i++) {
+ if (formats[i].pixelformat == f->fmt.sdr.pixelformat)
+ return 0;
+ }
+
+ f->fmt.sdr.pixelformat = formats[0].pixelformat;
+
+ return 0;
+}
+
+static int msi3101_s_tuner(struct file *file, void *priv,
+ const struct v4l2_tuner *v)
+{
+ struct msi3101_state *s = video_drvdata(file);
+ int ret;
+ dev_dbg(&s->udev->dev, "%s: index=%d\n", __func__, v->index);
+
+ if (v->index == 0)
+ ret = 0;
+ else if (v->index == 1)
+ ret = v4l2_subdev_call(s->v4l2_subdev, tuner, s_tuner, v);
+ else
+ ret = -EINVAL;
+
+ return ret;
+}
+
+static int msi3101_g_tuner(struct file *file, void *priv, struct v4l2_tuner *v)
+{
+ struct msi3101_state *s = video_drvdata(file);
+ int ret;
+ dev_dbg(&s->udev->dev, "%s: index=%d\n", __func__, v->index);
+
+ if (v->index == 0) {
+ strlcpy(v->name, "Mirics MSi2500", sizeof(v->name));
+ v->type = V4L2_TUNER_ADC;
+ v->capability = V4L2_TUNER_CAP_1HZ | V4L2_TUNER_CAP_FREQ_BANDS;
+ v->rangelow = 1200000;
+ v->rangehigh = 15000000;
+ ret = 0;
+ } else if (v->index == 1) {
+ ret = v4l2_subdev_call(s->v4l2_subdev, tuner, g_tuner, v);
+ } else {
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+static int msi3101_g_frequency(struct file *file, void *priv,
+ struct v4l2_frequency *f)
+{
+ struct msi3101_state *s = video_drvdata(file);
+ int ret = 0;
+ dev_dbg(&s->udev->dev, "%s: tuner=%d type=%d\n",
+ __func__, f->tuner, f->type);
+
+ if (f->tuner == 0) {
+ f->frequency = s->f_adc;
+ ret = 0;
+ } else if (f->tuner == 1) {
+ f->type = V4L2_TUNER_RF;
+ ret = v4l2_subdev_call(s->v4l2_subdev, tuner, g_frequency, f);
+ } else {
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+static int msi3101_s_frequency(struct file *file, void *priv,
+ const struct v4l2_frequency *f)
+{
+ struct msi3101_state *s = video_drvdata(file);
+ int ret;
+ dev_dbg(&s->udev->dev, "%s: tuner=%d type=%d frequency=%u\n",
+ __func__, f->tuner, f->type, f->frequency);
+
+ if (f->tuner == 0) {
+ s->f_adc = clamp_t(unsigned int, f->frequency,
+ bands[0].rangelow,
+ bands[0].rangehigh);
+ dev_dbg(&s->udev->dev, "%s: ADC frequency=%u Hz\n",
+ __func__, s->f_adc);
+ ret = msi3101_set_usb_adc(s);
+ } else if (f->tuner == 1) {
+ ret = v4l2_subdev_call(s->v4l2_subdev, tuner, s_frequency, f);
+ } else {
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+static int msi3101_enum_freq_bands(struct file *file, void *priv,
+ struct v4l2_frequency_band *band)
+{
+ struct msi3101_state *s = video_drvdata(file);
+ int ret;
+ dev_dbg(&s->udev->dev, "%s: tuner=%d type=%d index=%d\n",
+ __func__, band->tuner, band->type, band->index);
+
+ if (band->tuner == 0) {
+ if (band->index >= ARRAY_SIZE(bands)) {
+ ret = -EINVAL;
+ } else {
+ *band = bands[band->index];
+ ret = 0;
+ }
+ } else if (band->tuner == 1) {
+ ret = v4l2_subdev_call(s->v4l2_subdev, tuner,
+ enum_freq_bands, band);
+ } else {
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+static const struct v4l2_ioctl_ops msi3101_ioctl_ops = {
+ .vidioc_querycap = msi3101_querycap,
+
+ .vidioc_enum_fmt_sdr_cap = msi3101_enum_fmt_sdr_cap,
+ .vidioc_g_fmt_sdr_cap = msi3101_g_fmt_sdr_cap,
+ .vidioc_s_fmt_sdr_cap = msi3101_s_fmt_sdr_cap,
+ .vidioc_try_fmt_sdr_cap = msi3101_try_fmt_sdr_cap,
+
+ .vidioc_reqbufs = vb2_ioctl_reqbufs,
+ .vidioc_create_bufs = vb2_ioctl_create_bufs,
+ .vidioc_prepare_buf = vb2_ioctl_prepare_buf,
+ .vidioc_querybuf = vb2_ioctl_querybuf,
+ .vidioc_qbuf = vb2_ioctl_qbuf,
+ .vidioc_dqbuf = vb2_ioctl_dqbuf,
+
+ .vidioc_streamon = vb2_ioctl_streamon,
+ .vidioc_streamoff = vb2_ioctl_streamoff,
+
+ .vidioc_g_tuner = msi3101_g_tuner,
+ .vidioc_s_tuner = msi3101_s_tuner,
+
+ .vidioc_g_frequency = msi3101_g_frequency,
+ .vidioc_s_frequency = msi3101_s_frequency,
+ .vidioc_enum_freq_bands = msi3101_enum_freq_bands,
+
+ .vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
+ .vidioc_unsubscribe_event = v4l2_event_unsubscribe,
+ .vidioc_log_status = v4l2_ctrl_log_status,
+};
+
+static const struct v4l2_file_operations msi3101_fops = {
+ .owner = THIS_MODULE,
+ .open = v4l2_fh_open,
+ .release = vb2_fop_release,
+ .read = vb2_fop_read,
+ .poll = vb2_fop_poll,
+ .mmap = vb2_fop_mmap,
+ .unlocked_ioctl = video_ioctl2,
+};
+
+static struct video_device msi3101_template = {
+ .name = "Mirics MSi3101 SDR Dongle",
+ .release = video_device_release_empty,
+ .fops = &msi3101_fops,
+ .ioctl_ops = &msi3101_ioctl_ops,
+};
+
+static void msi3101_video_release(struct v4l2_device *v)
+{
+ struct msi3101_state *s =
+ container_of(v, struct msi3101_state, v4l2_dev);
+
+ v4l2_ctrl_handler_free(&s->hdl);
+ v4l2_device_unregister(&s->v4l2_dev);
+ kfree(s);
+}
+
+static int msi3101_transfer_one_message(struct spi_master *master,
+ struct spi_message *m)
+{
+ struct msi3101_state *s = spi_master_get_devdata(master);
+ struct spi_transfer *t;
+ int ret = 0;
+ u32 data;
+
+ list_for_each_entry(t, &m->transfers, transfer_list) {
+ dev_dbg(&s->udev->dev, "%s: msg=%*ph\n",
+ __func__, t->len, t->tx_buf);
+ data = 0x09; /* reg 9 is SPI adapter */
+ data |= ((u8 *)t->tx_buf)[0] << 8;
+ data |= ((u8 *)t->tx_buf)[1] << 16;
+ data |= ((u8 *)t->tx_buf)[2] << 24;
+ ret = msi3101_ctrl_msg(s, CMD_WREG, data);
+ }
+
+ m->status = ret;
+ spi_finalize_current_message(master);
+ return ret;
+}
+
+static int msi3101_probe(struct usb_interface *intf,
+ const struct usb_device_id *id)
+{
+ struct usb_device *udev = interface_to_usbdev(intf);
+ struct msi3101_state *s = NULL;
+ struct v4l2_subdev *sd;
+ struct spi_master *master;
+ int ret;
+ static struct spi_board_info board_info = {
+ .modalias = "msi001",
+ .bus_num = 0,
+ .chip_select = 0,
+ .max_speed_hz = 12000000,
+ };
+
+ s = kzalloc(sizeof(struct msi3101_state), GFP_KERNEL);
+ if (s == NULL) {
+ pr_err("Could not allocate memory for msi3101_state\n");
+ return -ENOMEM;
+ }
+
+ mutex_init(&s->v4l2_lock);
+ mutex_init(&s->vb_queue_lock);
+ spin_lock_init(&s->queued_bufs_lock);
+ INIT_LIST_HEAD(&s->queued_bufs);
+ s->udev = udev;
+ s->f_adc = bands[0].rangelow;
+ s->pixelformat = V4L2_SDR_FMT_CU8;
+
+ /* Init videobuf2 queue structure */
+ s->vb_queue.type = V4L2_BUF_TYPE_SDR_CAPTURE;
+ s->vb_queue.io_modes = VB2_MMAP | VB2_USERPTR | VB2_READ;
+ s->vb_queue.drv_priv = s;
+ s->vb_queue.buf_struct_size = sizeof(struct msi3101_frame_buf);
+ s->vb_queue.ops = &msi3101_vb2_ops;
+ s->vb_queue.mem_ops = &vb2_vmalloc_memops;
+ s->vb_queue.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
+ ret = vb2_queue_init(&s->vb_queue);
+ if (ret) {
+ dev_err(&s->udev->dev, "Could not initialize vb2 queue\n");
+ goto err_free_mem;
+ }
+
+ /* Init video_device structure */
+ s->vdev = msi3101_template;
+ s->vdev.queue = &s->vb_queue;
+ s->vdev.queue->lock = &s->vb_queue_lock;
+ set_bit(V4L2_FL_USE_FH_PRIO, &s->vdev.flags);
+ video_set_drvdata(&s->vdev, s);
+
+ /* Register the v4l2_device structure */
+ s->v4l2_dev.release = msi3101_video_release;
+ ret = v4l2_device_register(&intf->dev, &s->v4l2_dev);
+ if (ret) {
+ dev_err(&s->udev->dev,
+ "Failed to register v4l2-device (%d)\n", ret);
+ goto err_free_mem;
+ }
+
+ /* SPI master adapter */
+ master = spi_alloc_master(&s->udev->dev, 0);
+ if (master == NULL) {
+ ret = -ENOMEM;
+ goto err_unregister_v4l2_dev;
+ }
+
+ s->master = master;
+ master->bus_num = 0;
+ master->num_chipselect = 1;
+ master->transfer_one_message = msi3101_transfer_one_message;
+ spi_master_set_devdata(master, s);
+ ret = spi_register_master(master);
+ if (ret) {
+ spi_master_put(master);
+ goto err_unregister_v4l2_dev;
+ }
+
+ /* load v4l2 subdevice */
+ sd = v4l2_spi_new_subdev(&s->v4l2_dev, master, &board_info);
+ s->v4l2_subdev = sd;
+ if (sd == NULL) {
+ dev_err(&s->udev->dev, "cannot get v4l2 subdevice\n");
+ ret = -ENODEV;
+ goto err_unregister_master;
+ }
+
+ /* Register controls */
+ v4l2_ctrl_handler_init(&s->hdl, 0);
+ if (s->hdl.error) {
+ ret = s->hdl.error;
+ dev_err(&s->udev->dev, "Could not initialize controls\n");
+ goto err_free_controls;
+ }
+
+ /* currently all controls are from subdev */
+ v4l2_ctrl_add_handler(&s->hdl, sd->ctrl_handler, NULL);
+
+ s->v4l2_dev.ctrl_handler = &s->hdl;
+ s->vdev.v4l2_dev = &s->v4l2_dev;
+ s->vdev.lock = &s->v4l2_lock;
+
+ ret = video_register_device(&s->vdev, VFL_TYPE_SDR, -1);
+ if (ret) {
+ dev_err(&s->udev->dev,
+ "Failed to register as video device (%d)\n",
+ ret);
+ goto err_unregister_v4l2_dev;
+ }
+ dev_info(&s->udev->dev, "Registered as %s\n",
+ video_device_node_name(&s->vdev));
+
+ return 0;
+
+err_free_controls:
+ v4l2_ctrl_handler_free(&s->hdl);
+err_unregister_master:
+ spi_unregister_master(s->master);
+err_unregister_v4l2_dev:
+ v4l2_device_unregister(&s->v4l2_dev);
+err_free_mem:
+ kfree(s);
+ return ret;
+}
+
+/* USB device ID list */
+static struct usb_device_id msi3101_id_table[] = {
+ { USB_DEVICE(0x1df7, 0x2500) }, /* Mirics MSi3101 SDR Dongle */
+ { USB_DEVICE(0x2040, 0xd300) }, /* Hauppauge WinTV 133559 LF */
+ { }
+};
+MODULE_DEVICE_TABLE(usb, msi3101_id_table);
+
+/* USB subsystem interface */
+static struct usb_driver msi3101_driver = {
+ .name = KBUILD_MODNAME,
+ .probe = msi3101_probe,
+ .disconnect = msi3101_disconnect,
+ .id_table = msi3101_id_table,
+};
+
+module_usb_driver(msi3101_driver);
+
+MODULE_AUTHOR("Antti Palosaari <crope@iki.fi>");
+MODULE_DESCRIPTION("Mirics MSi3101 SDR Dongle");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/media/omap24xx/Kconfig b/drivers/staging/media/omap24xx/Kconfig
new file mode 100644
index 00000000000..82e569a21c4
--- /dev/null
+++ b/drivers/staging/media/omap24xx/Kconfig
@@ -0,0 +1,35 @@
+config VIDEO_V4L2_INT_DEVICE
+ tristate
+
+config VIDEO_OMAP2
+ tristate "OMAP2 Camera Capture Interface driver (DEPRECATED)"
+ depends on VIDEO_DEV && ARCH_OMAP2
+ select VIDEOBUF_DMA_SG
+ select VIDEO_V4L2_INT_DEVICE
+ ---help---
+ This is a v4l2 driver for the TI OMAP2 camera capture interface
+
+ It uses the deprecated int-device API. Since this driver is no
+ longer actively maintained and nobody is interested in converting
+ it to the subdev API, this driver will be removed soon.
+
+ If you do want to keep this driver in the kernel, and are willing
+ to convert it to the subdev API, then please contact the linux-media
+ mailinglist.
+
+config VIDEO_TCM825X
+ tristate "TCM825x camera sensor support (DEPRECATED)"
+ depends on I2C && VIDEO_V4L2
+ depends on MEDIA_CAMERA_SUPPORT
+ select VIDEO_V4L2_INT_DEVICE
+ ---help---
+ This is a driver for the Toshiba TCM825x VGA camera sensor.
+ It is used for example in Nokia N800.
+
+ It uses the deprecated int-device API. Since this driver is no
+ longer actively maintained and nobody is interested in converting
+ it to the subdev API, this driver will be removed soon.
+
+ If you do want to keep this driver in the kernel, and are willing
+ to convert it to the subdev API, then please contact the linux-media
+ mailinglist.
diff --git a/drivers/staging/media/omap24xx/Makefile b/drivers/staging/media/omap24xx/Makefile
new file mode 100644
index 00000000000..c2e7175599c
--- /dev/null
+++ b/drivers/staging/media/omap24xx/Makefile
@@ -0,0 +1,5 @@
+omap2cam-objs := omap24xxcam.o omap24xxcam-dma.o
+
+obj-$(CONFIG_VIDEO_OMAP2) += omap2cam.o
+obj-$(CONFIG_VIDEO_TCM825X) += tcm825x.o
+obj-$(CONFIG_VIDEO_V4L2_INT_DEVICE) += v4l2-int-device.o
diff --git a/drivers/staging/media/omap24xx/omap24xxcam-dma.c b/drivers/staging/media/omap24xx/omap24xxcam-dma.c
new file mode 100644
index 00000000000..9c00776d658
--- /dev/null
+++ b/drivers/staging/media/omap24xx/omap24xxcam-dma.c
@@ -0,0 +1,601 @@
+/*
+ * drivers/media/platform/omap24xxcam-dma.c
+ *
+ * Copyright (C) 2004 MontaVista Software, Inc.
+ * Copyright (C) 2004 Texas Instruments.
+ * Copyright (C) 2007 Nokia Corporation.
+ *
+ * Contact: Sakari Ailus <sakari.ailus@nokia.com>
+ *
+ * Based on code from Andy Lowe <source@mvista.com> and
+ * David Cohen <david.cohen@indt.org.br>.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ */
+
+#include <linux/kernel.h>
+#include <linux/io.h>
+#include <linux/scatterlist.h>
+
+#include "omap24xxcam.h"
+
+/*
+ *
+ * DMA hardware.
+ *
+ */
+
+/* Ack all interrupt on CSR and IRQSTATUS_L0 */
+static void omap24xxcam_dmahw_ack_all(void __iomem *base)
+{
+ u32 csr;
+ int i;
+
+ for (i = 0; i < NUM_CAMDMA_CHANNELS; ++i) {
+ csr = omap24xxcam_reg_in(base, CAMDMA_CSR(i));
+ /* ack interrupt in CSR */
+ omap24xxcam_reg_out(base, CAMDMA_CSR(i), csr);
+ }
+ omap24xxcam_reg_out(base, CAMDMA_IRQSTATUS_L0, 0xf);
+}
+
+/* Ack dmach on CSR and IRQSTATUS_L0 */
+static u32 omap24xxcam_dmahw_ack_ch(void __iomem *base, int dmach)
+{
+ u32 csr;
+
+ csr = omap24xxcam_reg_in(base, CAMDMA_CSR(dmach));
+ /* ack interrupt in CSR */
+ omap24xxcam_reg_out(base, CAMDMA_CSR(dmach), csr);
+ /* ack interrupt in IRQSTATUS */
+ omap24xxcam_reg_out(base, CAMDMA_IRQSTATUS_L0, (1 << dmach));
+
+ return csr;
+}
+
+static int omap24xxcam_dmahw_running(void __iomem *base, int dmach)
+{
+ return omap24xxcam_reg_in(base, CAMDMA_CCR(dmach)) & CAMDMA_CCR_ENABLE;
+}
+
+static void omap24xxcam_dmahw_transfer_setup(void __iomem *base, int dmach,
+ dma_addr_t start, u32 len)
+{
+ omap24xxcam_reg_out(base, CAMDMA_CCR(dmach),
+ CAMDMA_CCR_SEL_SRC_DST_SYNC
+ | CAMDMA_CCR_BS
+ | CAMDMA_CCR_DST_AMODE_POST_INC
+ | CAMDMA_CCR_SRC_AMODE_POST_INC
+ | CAMDMA_CCR_FS
+ | CAMDMA_CCR_WR_ACTIVE
+ | CAMDMA_CCR_RD_ACTIVE
+ | CAMDMA_CCR_SYNCHRO_CAMERA);
+ omap24xxcam_reg_out(base, CAMDMA_CLNK_CTRL(dmach), 0);
+ omap24xxcam_reg_out(base, CAMDMA_CEN(dmach), len);
+ omap24xxcam_reg_out(base, CAMDMA_CFN(dmach), 1);
+ omap24xxcam_reg_out(base, CAMDMA_CSDP(dmach),
+ CAMDMA_CSDP_WRITE_MODE_POSTED
+ | CAMDMA_CSDP_DST_BURST_EN_32
+ | CAMDMA_CSDP_DST_PACKED
+ | CAMDMA_CSDP_SRC_BURST_EN_32
+ | CAMDMA_CSDP_SRC_PACKED
+ | CAMDMA_CSDP_DATA_TYPE_8BITS);
+ omap24xxcam_reg_out(base, CAMDMA_CSSA(dmach), 0);
+ omap24xxcam_reg_out(base, CAMDMA_CDSA(dmach), start);
+ omap24xxcam_reg_out(base, CAMDMA_CSEI(dmach), 0);
+ omap24xxcam_reg_out(base, CAMDMA_CSFI(dmach), DMA_THRESHOLD);
+ omap24xxcam_reg_out(base, CAMDMA_CDEI(dmach), 0);
+ omap24xxcam_reg_out(base, CAMDMA_CDFI(dmach), 0);
+ omap24xxcam_reg_out(base, CAMDMA_CSR(dmach),
+ CAMDMA_CSR_MISALIGNED_ERR
+ | CAMDMA_CSR_SECURE_ERR
+ | CAMDMA_CSR_TRANS_ERR
+ | CAMDMA_CSR_BLOCK
+ | CAMDMA_CSR_DROP);
+ omap24xxcam_reg_out(base, CAMDMA_CICR(dmach),
+ CAMDMA_CICR_MISALIGNED_ERR_IE
+ | CAMDMA_CICR_SECURE_ERR_IE
+ | CAMDMA_CICR_TRANS_ERR_IE
+ | CAMDMA_CICR_BLOCK_IE
+ | CAMDMA_CICR_DROP_IE);
+}
+
+static void omap24xxcam_dmahw_transfer_start(void __iomem *base, int dmach)
+{
+ omap24xxcam_reg_out(base, CAMDMA_CCR(dmach),
+ CAMDMA_CCR_SEL_SRC_DST_SYNC
+ | CAMDMA_CCR_BS
+ | CAMDMA_CCR_DST_AMODE_POST_INC
+ | CAMDMA_CCR_SRC_AMODE_POST_INC
+ | CAMDMA_CCR_ENABLE
+ | CAMDMA_CCR_FS
+ | CAMDMA_CCR_SYNCHRO_CAMERA);
+}
+
+static void omap24xxcam_dmahw_transfer_chain(void __iomem *base, int dmach,
+ int free_dmach)
+{
+ int prev_dmach, ch;
+
+ if (dmach == 0)
+ prev_dmach = NUM_CAMDMA_CHANNELS - 1;
+ else
+ prev_dmach = dmach - 1;
+ omap24xxcam_reg_out(base, CAMDMA_CLNK_CTRL(prev_dmach),
+ CAMDMA_CLNK_CTRL_ENABLE_LNK | dmach);
+ /* Did we chain the DMA transfer before the previous one
+ * finished?
+ */
+ ch = (dmach + free_dmach) % NUM_CAMDMA_CHANNELS;
+ while (!(omap24xxcam_reg_in(base, CAMDMA_CCR(ch))
+ & CAMDMA_CCR_ENABLE)) {
+ if (ch == dmach) {
+ /* The previous transfer has ended and this one
+ * hasn't started, so we must not have chained
+ * to the previous one in time. We'll have to
+ * start it now.
+ */
+ omap24xxcam_dmahw_transfer_start(base, dmach);
+ break;
+ } else
+ ch = (ch + 1) % NUM_CAMDMA_CHANNELS;
+ }
+}
+
+/* Abort all chained DMA transfers. After all transfers have been
+ * aborted and the DMA controller is idle, the completion routines for
+ * any aborted transfers will be called in sequence. The DMA
+ * controller may not be idle after this routine completes, because
+ * the completion routines might start new transfers.
+ */
+static void omap24xxcam_dmahw_abort_ch(void __iomem *base, int dmach)
+{
+ /* mask all interrupts from this channel */
+ omap24xxcam_reg_out(base, CAMDMA_CICR(dmach), 0);
+ /* unlink this channel */
+ omap24xxcam_reg_merge(base, CAMDMA_CLNK_CTRL(dmach), 0,
+ CAMDMA_CLNK_CTRL_ENABLE_LNK);
+ /* disable this channel */
+ omap24xxcam_reg_merge(base, CAMDMA_CCR(dmach), 0, CAMDMA_CCR_ENABLE);
+}
+
+static void omap24xxcam_dmahw_init(void __iomem *base)
+{
+ omap24xxcam_reg_out(base, CAMDMA_OCP_SYSCONFIG,
+ CAMDMA_OCP_SYSCONFIG_MIDLEMODE_FSTANDBY
+ | CAMDMA_OCP_SYSCONFIG_SIDLEMODE_FIDLE
+ | CAMDMA_OCP_SYSCONFIG_AUTOIDLE);
+
+ omap24xxcam_reg_merge(base, CAMDMA_GCR, 0x10,
+ CAMDMA_GCR_MAX_CHANNEL_FIFO_DEPTH);
+
+ omap24xxcam_reg_out(base, CAMDMA_IRQENABLE_L0, 0xf);
+}
+
+/*
+ *
+ * Individual DMA channel handling.
+ *
+ */
+
+/* Start a DMA transfer from the camera to memory.
+ * Returns zero if the transfer was successfully started, or non-zero if all
+ * DMA channels are already in use or starting is currently inhibited.
+ */
+static int omap24xxcam_dma_start(struct omap24xxcam_dma *dma, dma_addr_t start,
+ u32 len, dma_callback_t callback, void *arg)
+{
+ unsigned long flags;
+ int dmach;
+
+ spin_lock_irqsave(&dma->lock, flags);
+
+ if (!dma->free_dmach || atomic_read(&dma->dma_stop)) {
+ spin_unlock_irqrestore(&dma->lock, flags);
+ return -EBUSY;
+ }
+
+ dmach = dma->next_dmach;
+
+ dma->ch_state[dmach].callback = callback;
+ dma->ch_state[dmach].arg = arg;
+
+ omap24xxcam_dmahw_transfer_setup(dma->base, dmach, start, len);
+
+ /* We're ready to start the DMA transfer. */
+
+ if (dma->free_dmach < NUM_CAMDMA_CHANNELS) {
+ /* A transfer is already in progress, so try to chain to it. */
+ omap24xxcam_dmahw_transfer_chain(dma->base, dmach,
+ dma->free_dmach);
+ } else {
+ /* No transfer is in progress, so we'll just start this one
+ * now.
+ */
+ omap24xxcam_dmahw_transfer_start(dma->base, dmach);
+ }
+
+ dma->next_dmach = (dma->next_dmach + 1) % NUM_CAMDMA_CHANNELS;
+ dma->free_dmach--;
+
+ spin_unlock_irqrestore(&dma->lock, flags);
+
+ return 0;
+}
+
+/* Abort all chained DMA transfers. After all transfers have been
+ * aborted and the DMA controller is idle, the completion routines for
+ * any aborted transfers will be called in sequence. The DMA
+ * controller may not be idle after this routine completes, because
+ * the completion routines might start new transfers.
+ */
+static void omap24xxcam_dma_abort(struct omap24xxcam_dma *dma, u32 csr)
+{
+ unsigned long flags;
+ int dmach, i, free_dmach;
+ dma_callback_t callback;
+ void *arg;
+
+ spin_lock_irqsave(&dma->lock, flags);
+
+ /* stop any DMA transfers in progress */
+ dmach = (dma->next_dmach + dma->free_dmach) % NUM_CAMDMA_CHANNELS;
+ for (i = 0; i < NUM_CAMDMA_CHANNELS; i++) {
+ omap24xxcam_dmahw_abort_ch(dma->base, dmach);
+ dmach = (dmach + 1) % NUM_CAMDMA_CHANNELS;
+ }
+
+ /* We have to be careful here because the callback routine
+ * might start a new DMA transfer, and we only want to abort
+ * transfers that were started before this routine was called.
+ */
+ free_dmach = dma->free_dmach;
+ while ((dma->free_dmach < NUM_CAMDMA_CHANNELS) &&
+ (free_dmach < NUM_CAMDMA_CHANNELS)) {
+ dmach = (dma->next_dmach + dma->free_dmach)
+ % NUM_CAMDMA_CHANNELS;
+ callback = dma->ch_state[dmach].callback;
+ arg = dma->ch_state[dmach].arg;
+ dma->free_dmach++;
+ free_dmach++;
+ if (callback) {
+ /* leave interrupts disabled during callback */
+ spin_unlock(&dma->lock);
+ (*callback) (dma, csr, arg);
+ spin_lock(&dma->lock);
+ }
+ }
+
+ spin_unlock_irqrestore(&dma->lock, flags);
+}
+
+/* Abort all chained DMA transfers. After all transfers have been
+ * aborted and the DMA controller is idle, the completion routines for
+ * any aborted transfers will be called in sequence. If the completion
+ * routines attempt to start a new DMA transfer it will fail, so the
+ * DMA controller will be idle after this routine completes.
+ */
+static void omap24xxcam_dma_stop(struct omap24xxcam_dma *dma, u32 csr)
+{
+ atomic_inc(&dma->dma_stop);
+ omap24xxcam_dma_abort(dma, csr);
+ atomic_dec(&dma->dma_stop);
+}
+
+/* Camera DMA interrupt service routine. */
+void omap24xxcam_dma_isr(struct omap24xxcam_dma *dma)
+{
+ int dmach;
+ dma_callback_t callback;
+ void *arg;
+ u32 csr;
+ const u32 csr_error = CAMDMA_CSR_MISALIGNED_ERR
+ | CAMDMA_CSR_SUPERVISOR_ERR | CAMDMA_CSR_SECURE_ERR
+ | CAMDMA_CSR_TRANS_ERR | CAMDMA_CSR_DROP;
+
+ spin_lock(&dma->lock);
+
+ if (dma->free_dmach == NUM_CAMDMA_CHANNELS) {
+ /* A camera DMA interrupt occurred while all channels
+ * are idle, so we'll acknowledge the interrupt in the
+ * IRQSTATUS register and exit.
+ */
+ omap24xxcam_dmahw_ack_all(dma->base);
+ spin_unlock(&dma->lock);
+ return;
+ }
+
+ while (dma->free_dmach < NUM_CAMDMA_CHANNELS) {
+ dmach = (dma->next_dmach + dma->free_dmach)
+ % NUM_CAMDMA_CHANNELS;
+ if (omap24xxcam_dmahw_running(dma->base, dmach)) {
+ /* This buffer hasn't finished yet, so we're done. */
+ break;
+ }
+ csr = omap24xxcam_dmahw_ack_ch(dma->base, dmach);
+ if (csr & csr_error) {
+ /* A DMA error occurred, so stop all DMA
+ * transfers in progress.
+ */
+ spin_unlock(&dma->lock);
+ omap24xxcam_dma_stop(dma, csr);
+ return;
+ } else {
+ callback = dma->ch_state[dmach].callback;
+ arg = dma->ch_state[dmach].arg;
+ dma->free_dmach++;
+ if (callback) {
+ spin_unlock(&dma->lock);
+ (*callback) (dma, csr, arg);
+ spin_lock(&dma->lock);
+ }
+ }
+ }
+
+ spin_unlock(&dma->lock);
+
+ omap24xxcam_sgdma_process(
+ container_of(dma, struct omap24xxcam_sgdma, dma));
+}
+
+void omap24xxcam_dma_hwinit(struct omap24xxcam_dma *dma)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&dma->lock, flags);
+
+ omap24xxcam_dmahw_init(dma->base);
+
+ spin_unlock_irqrestore(&dma->lock, flags);
+}
+
+static void omap24xxcam_dma_init(struct omap24xxcam_dma *dma,
+ void __iomem *base)
+{
+ int ch;
+
+ /* group all channels on DMA IRQ0 and unmask irq */
+ spin_lock_init(&dma->lock);
+ dma->base = base;
+ dma->free_dmach = NUM_CAMDMA_CHANNELS;
+ dma->next_dmach = 0;
+ for (ch = 0; ch < NUM_CAMDMA_CHANNELS; ch++) {
+ dma->ch_state[ch].callback = NULL;
+ dma->ch_state[ch].arg = NULL;
+ }
+}
+
+/*
+ *
+ * Scatter-gather DMA.
+ *
+ * High-level DMA construct for transferring whole picture frames to
+ * memory that is discontinuous.
+ *
+ */
+
+/* DMA completion routine for the scatter-gather DMA fragments. */
+static void omap24xxcam_sgdma_callback(struct omap24xxcam_dma *dma, u32 csr,
+ void *arg)
+{
+ struct omap24xxcam_sgdma *sgdma =
+ container_of(dma, struct omap24xxcam_sgdma, dma);
+ int sgslot = (int)arg;
+ struct sgdma_state *sg_state;
+ const u32 csr_error = CAMDMA_CSR_MISALIGNED_ERR
+ | CAMDMA_CSR_SUPERVISOR_ERR | CAMDMA_CSR_SECURE_ERR
+ | CAMDMA_CSR_TRANS_ERR | CAMDMA_CSR_DROP;
+
+ spin_lock(&sgdma->lock);
+
+ /* We got an interrupt, we can remove the timer */
+ del_timer(&sgdma->reset_timer);
+
+ sg_state = sgdma->sg_state + sgslot;
+ if (!sg_state->queued_sglist) {
+ spin_unlock(&sgdma->lock);
+ printk(KERN_ERR "%s: sgdma completed when none queued!\n",
+ __func__);
+ return;
+ }
+
+ sg_state->csr |= csr;
+ if (!--sg_state->queued_sglist) {
+ /* Queue for this sglist is empty, so check to see if we're
+ * done.
+ */
+ if ((sg_state->next_sglist == sg_state->sglen)
+ || (sg_state->csr & csr_error)) {
+ sgdma_callback_t callback = sg_state->callback;
+ void *arg = sg_state->arg;
+ u32 sg_csr = sg_state->csr;
+ /* All done with this sglist */
+ sgdma->free_sgdma++;
+ if (callback) {
+ spin_unlock(&sgdma->lock);
+ (*callback) (sgdma, sg_csr, arg);
+ return;
+ }
+ }
+ }
+
+ spin_unlock(&sgdma->lock);
+}
+
+/* Start queued scatter-gather DMA transfers. */
+void omap24xxcam_sgdma_process(struct omap24xxcam_sgdma *sgdma)
+{
+ unsigned long flags;
+ int queued_sgdma, sgslot;
+ struct sgdma_state *sg_state;
+ const u32 csr_error = CAMDMA_CSR_MISALIGNED_ERR
+ | CAMDMA_CSR_SUPERVISOR_ERR | CAMDMA_CSR_SECURE_ERR
+ | CAMDMA_CSR_TRANS_ERR | CAMDMA_CSR_DROP;
+
+ spin_lock_irqsave(&sgdma->lock, flags);
+
+ queued_sgdma = NUM_SG_DMA - sgdma->free_sgdma;
+ sgslot = (sgdma->next_sgdma + sgdma->free_sgdma) % NUM_SG_DMA;
+ while (queued_sgdma > 0) {
+ sg_state = sgdma->sg_state + sgslot;
+ while ((sg_state->next_sglist < sg_state->sglen) &&
+ !(sg_state->csr & csr_error)) {
+ const struct scatterlist *sglist;
+ unsigned int len;
+
+ sglist = sg_state->sglist + sg_state->next_sglist;
+ /* try to start the next DMA transfer */
+ if (sg_state->next_sglist + 1 == sg_state->sglen) {
+ /*
+ * On the last sg, we handle the case where
+ * cam->img.pix.sizeimage % PAGE_ALIGN != 0
+ */
+ len = sg_state->len - sg_state->bytes_read;
+ } else {
+ len = sg_dma_len(sglist);
+ }
+
+ if (omap24xxcam_dma_start(&sgdma->dma,
+ sg_dma_address(sglist),
+ len,
+ omap24xxcam_sgdma_callback,
+ (void *)sgslot)) {
+ /* DMA start failed */
+ spin_unlock_irqrestore(&sgdma->lock, flags);
+ return;
+ } else {
+ unsigned long expires;
+ /* DMA start was successful */
+ sg_state->next_sglist++;
+ sg_state->bytes_read += len;
+ sg_state->queued_sglist++;
+
+ /* We start the reset timer */
+ expires = jiffies + HZ;
+ mod_timer(&sgdma->reset_timer, expires);
+ }
+ }
+ queued_sgdma--;
+ sgslot = (sgslot + 1) % NUM_SG_DMA;
+ }
+
+ spin_unlock_irqrestore(&sgdma->lock, flags);
+}
+
+/*
+ * Queue a scatter-gather DMA transfer from the camera to memory.
+ * Returns zero if the transfer was successfully queued, or non-zero
+ * if all of the scatter-gather slots are already in use.
+ */
+int omap24xxcam_sgdma_queue(struct omap24xxcam_sgdma *sgdma,
+ const struct scatterlist *sglist, int sglen,
+ int len, sgdma_callback_t callback, void *arg)
+{
+ unsigned long flags;
+ struct sgdma_state *sg_state;
+
+ if ((sglen < 0) || ((sglen > 0) && !sglist))
+ return -EINVAL;
+
+ spin_lock_irqsave(&sgdma->lock, flags);
+
+ if (!sgdma->free_sgdma) {
+ spin_unlock_irqrestore(&sgdma->lock, flags);
+ return -EBUSY;
+ }
+
+ sg_state = sgdma->sg_state + sgdma->next_sgdma;
+
+ sg_state->sglist = sglist;
+ sg_state->sglen = sglen;
+ sg_state->next_sglist = 0;
+ sg_state->bytes_read = 0;
+ sg_state->len = len;
+ sg_state->queued_sglist = 0;
+ sg_state->csr = 0;
+ sg_state->callback = callback;
+ sg_state->arg = arg;
+
+ sgdma->next_sgdma = (sgdma->next_sgdma + 1) % NUM_SG_DMA;
+ sgdma->free_sgdma--;
+
+ spin_unlock_irqrestore(&sgdma->lock, flags);
+
+ omap24xxcam_sgdma_process(sgdma);
+
+ return 0;
+}
+
+/* Sync scatter-gather DMA by aborting any DMA transfers currently in progress.
+ * Any queued scatter-gather DMA transactions that have not yet been started
+ * will remain queued. The DMA controller will be idle after this routine
+ * completes. When the scatter-gather queue is restarted, the next
+ * scatter-gather DMA transfer will begin at the start of a new transaction.
+ */
+void omap24xxcam_sgdma_sync(struct omap24xxcam_sgdma *sgdma)
+{
+ unsigned long flags;
+ int sgslot;
+ struct sgdma_state *sg_state;
+ u32 csr = CAMDMA_CSR_TRANS_ERR;
+
+ /* stop any DMA transfers in progress */
+ omap24xxcam_dma_stop(&sgdma->dma, csr);
+
+ spin_lock_irqsave(&sgdma->lock, flags);
+
+ if (sgdma->free_sgdma < NUM_SG_DMA) {
+ sgslot = (sgdma->next_sgdma + sgdma->free_sgdma) % NUM_SG_DMA;
+ sg_state = sgdma->sg_state + sgslot;
+ if (sg_state->next_sglist != 0) {
+ /* This DMA transfer was in progress, so abort it. */
+ sgdma_callback_t callback = sg_state->callback;
+ void *arg = sg_state->arg;
+ sgdma->free_sgdma++;
+ if (callback) {
+ /* leave interrupts masked */
+ spin_unlock(&sgdma->lock);
+ (*callback) (sgdma, csr, arg);
+ spin_lock(&sgdma->lock);
+ }
+ }
+ }
+
+ spin_unlock_irqrestore(&sgdma->lock, flags);
+}
+
+void omap24xxcam_sgdma_init(struct omap24xxcam_sgdma *sgdma,
+ void __iomem *base,
+ void (*reset_callback)(unsigned long data),
+ unsigned long reset_callback_data)
+{
+ int sg;
+
+ spin_lock_init(&sgdma->lock);
+ sgdma->free_sgdma = NUM_SG_DMA;
+ sgdma->next_sgdma = 0;
+ for (sg = 0; sg < NUM_SG_DMA; sg++) {
+ sgdma->sg_state[sg].sglen = 0;
+ sgdma->sg_state[sg].next_sglist = 0;
+ sgdma->sg_state[sg].bytes_read = 0;
+ sgdma->sg_state[sg].queued_sglist = 0;
+ sgdma->sg_state[sg].csr = 0;
+ sgdma->sg_state[sg].callback = NULL;
+ sgdma->sg_state[sg].arg = NULL;
+ }
+
+ omap24xxcam_dma_init(&sgdma->dma, base);
+ setup_timer(&sgdma->reset_timer, reset_callback, reset_callback_data);
+}
diff --git a/drivers/staging/media/omap24xx/omap24xxcam.c b/drivers/staging/media/omap24xx/omap24xxcam.c
new file mode 100644
index 00000000000..d2b440c842b
--- /dev/null
+++ b/drivers/staging/media/omap24xx/omap24xxcam.c
@@ -0,0 +1,1888 @@
+/*
+ * drivers/media/platform/omap24xxcam.c
+ *
+ * OMAP 2 camera block driver.
+ *
+ * Copyright (C) 2004 MontaVista Software, Inc.
+ * Copyright (C) 2004 Texas Instruments.
+ * Copyright (C) 2007-2008 Nokia Corporation.
+ *
+ * Contact: Sakari Ailus <sakari.ailus@nokia.com>
+ *
+ * Based on code from Andy Lowe <source@mvista.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ */
+
+#include <linux/delay.h>
+#include <linux/kernel.h>
+#include <linux/interrupt.h>
+#include <linux/videodev2.h>
+#include <linux/pci.h> /* needed for videobufs */
+#include <linux/platform_device.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/slab.h>
+#include <linux/sched.h>
+#include <linux/module.h>
+
+#include <media/v4l2-common.h>
+#include <media/v4l2-ioctl.h>
+
+#include "omap24xxcam.h"
+
+#define OMAP24XXCAM_VERSION "0.0.1"
+
+#define RESET_TIMEOUT_NS 10000
+
+static void omap24xxcam_reset(struct omap24xxcam_device *cam);
+static int omap24xxcam_sensor_if_enable(struct omap24xxcam_device *cam);
+static void omap24xxcam_device_unregister(struct v4l2_int_device *s);
+static int omap24xxcam_remove(struct platform_device *pdev);
+
+/* module parameters */
+static int video_nr = -1; /* video device minor (-1 ==> auto assign) */
+/*
+ * Maximum amount of memory to use for capture buffers.
+ * Default is 4800KB, enough to double-buffer SXGA.
+ */
+static int capture_mem = 1280 * 960 * 2 * 2;
+
+static struct v4l2_int_device omap24xxcam;
+
+/*
+ *
+ * Clocks.
+ *
+ */
+
+static void omap24xxcam_clock_put(struct omap24xxcam_device *cam)
+{
+ if (cam->ick != NULL && !IS_ERR(cam->ick))
+ clk_put(cam->ick);
+ if (cam->fck != NULL && !IS_ERR(cam->fck))
+ clk_put(cam->fck);
+
+ cam->ick = cam->fck = NULL;
+}
+
+static int omap24xxcam_clock_get(struct omap24xxcam_device *cam)
+{
+ int rval = 0;
+
+ cam->fck = clk_get(cam->dev, "fck");
+ if (IS_ERR(cam->fck)) {
+ dev_err(cam->dev, "can't get camera fck");
+ rval = PTR_ERR(cam->fck);
+ omap24xxcam_clock_put(cam);
+ return rval;
+ }
+
+ cam->ick = clk_get(cam->dev, "ick");
+ if (IS_ERR(cam->ick)) {
+ dev_err(cam->dev, "can't get camera ick");
+ rval = PTR_ERR(cam->ick);
+ omap24xxcam_clock_put(cam);
+ }
+
+ return rval;
+}
+
+static void omap24xxcam_clock_on(struct omap24xxcam_device *cam)
+{
+ clk_enable(cam->fck);
+ clk_enable(cam->ick);
+}
+
+static void omap24xxcam_clock_off(struct omap24xxcam_device *cam)
+{
+ clk_disable(cam->fck);
+ clk_disable(cam->ick);
+}
+
+/*
+ *
+ * Camera core
+ *
+ */
+
+/*
+ * Set xclk.
+ *
+ * To disable xclk, use value zero.
+ */
+static void omap24xxcam_core_xclk_set(const struct omap24xxcam_device *cam,
+ u32 xclk)
+{
+ if (xclk) {
+ u32 divisor = CAM_MCLK / xclk;
+
+ if (divisor == 1)
+ omap24xxcam_reg_out(cam->mmio_base + CC_REG_OFFSET,
+ CC_CTRL_XCLK,
+ CC_CTRL_XCLK_DIV_BYPASS);
+ else
+ omap24xxcam_reg_out(cam->mmio_base + CC_REG_OFFSET,
+ CC_CTRL_XCLK, divisor);
+ } else
+ omap24xxcam_reg_out(cam->mmio_base + CC_REG_OFFSET,
+ CC_CTRL_XCLK, CC_CTRL_XCLK_DIV_STABLE_LOW);
+}
+
+static void omap24xxcam_core_hwinit(const struct omap24xxcam_device *cam)
+{
+ /*
+ * Setting the camera core AUTOIDLE bit causes problems with frame
+ * synchronization, so we will clear the AUTOIDLE bit instead.
+ */
+ omap24xxcam_reg_out(cam->mmio_base + CC_REG_OFFSET, CC_SYSCONFIG,
+ CC_SYSCONFIG_AUTOIDLE);
+
+ /* program the camera interface DMA packet size */
+ omap24xxcam_reg_out(cam->mmio_base + CC_REG_OFFSET, CC_CTRL_DMA,
+ CC_CTRL_DMA_EN | (DMA_THRESHOLD / 4 - 1));
+
+ /* enable camera core error interrupts */
+ omap24xxcam_reg_out(cam->mmio_base + CC_REG_OFFSET, CC_IRQENABLE,
+ CC_IRQENABLE_FW_ERR_IRQ
+ | CC_IRQENABLE_FSC_ERR_IRQ
+ | CC_IRQENABLE_SSC_ERR_IRQ
+ | CC_IRQENABLE_FIFO_OF_IRQ);
+}
+
+/*
+ * Enable the camera core.
+ *
+ * Data transfer to the camera DMA starts from next starting frame.
+ */
+static void omap24xxcam_core_enable(const struct omap24xxcam_device *cam)
+{
+
+ omap24xxcam_reg_out(cam->mmio_base + CC_REG_OFFSET, CC_CTRL,
+ cam->cc_ctrl);
+}
+
+/*
+ * Disable camera core.
+ *
+ * The data transfer will be stopped immediately (CC_CTRL_CC_RST). The
+ * core internal state machines will be reset. Use
+ * CC_CTRL_CC_FRAME_TRIG instead if you want to transfer the current
+ * frame completely.
+ */
+static void omap24xxcam_core_disable(const struct omap24xxcam_device *cam)
+{
+ omap24xxcam_reg_out(cam->mmio_base + CC_REG_OFFSET, CC_CTRL,
+ CC_CTRL_CC_RST);
+}
+
+/* Interrupt service routine for camera core interrupts. */
+static void omap24xxcam_core_isr(struct omap24xxcam_device *cam)
+{
+ u32 cc_irqstatus;
+ const u32 cc_irqstatus_err =
+ CC_IRQSTATUS_FW_ERR_IRQ
+ | CC_IRQSTATUS_FSC_ERR_IRQ
+ | CC_IRQSTATUS_SSC_ERR_IRQ
+ | CC_IRQSTATUS_FIFO_UF_IRQ
+ | CC_IRQSTATUS_FIFO_OF_IRQ;
+
+ cc_irqstatus = omap24xxcam_reg_in(cam->mmio_base + CC_REG_OFFSET,
+ CC_IRQSTATUS);
+ omap24xxcam_reg_out(cam->mmio_base + CC_REG_OFFSET, CC_IRQSTATUS,
+ cc_irqstatus);
+
+ if (cc_irqstatus & cc_irqstatus_err
+ && !atomic_read(&cam->in_reset)) {
+ dev_dbg(cam->dev, "resetting camera, cc_irqstatus 0x%x\n",
+ cc_irqstatus);
+ omap24xxcam_reset(cam);
+ }
+}
+
+/*
+ *
+ * videobuf_buffer handling.
+ *
+ * Memory for mmapped videobuf_buffers is not allocated
+ * conventionally, but by several kmalloc allocations and then
+ * creating the scatterlist on our own. User-space buffers are handled
+ * normally.
+ *
+ */
+
+/*
+ * Free the memory-mapped buffer memory allocated for a
+ * videobuf_buffer and the associated scatterlist.
+ */
+static void omap24xxcam_vbq_free_mmap_buffer(struct videobuf_buffer *vb)
+{
+ struct videobuf_dmabuf *dma = videobuf_to_dma(vb);
+ size_t alloc_size;
+ struct page *page;
+ int i;
+
+ if (dma->sglist == NULL)
+ return;
+
+ i = dma->sglen;
+ while (i) {
+ i--;
+ alloc_size = sg_dma_len(&dma->sglist[i]);
+ page = sg_page(&dma->sglist[i]);
+ do {
+ ClearPageReserved(page++);
+ } while (alloc_size -= PAGE_SIZE);
+ __free_pages(sg_page(&dma->sglist[i]),
+ get_order(sg_dma_len(&dma->sglist[i])));
+ }
+
+ kfree(dma->sglist);
+ dma->sglist = NULL;
+}
+
+/* Release all memory related to the videobuf_queue. */
+static void omap24xxcam_vbq_free_mmap_buffers(struct videobuf_queue *vbq)
+{
+ int i;
+
+ mutex_lock(&vbq->vb_lock);
+
+ for (i = 0; i < VIDEO_MAX_FRAME; i++) {
+ if (NULL == vbq->bufs[i])
+ continue;
+ if (V4L2_MEMORY_MMAP != vbq->bufs[i]->memory)
+ continue;
+ vbq->ops->buf_release(vbq, vbq->bufs[i]);
+ omap24xxcam_vbq_free_mmap_buffer(vbq->bufs[i]);
+ kfree(vbq->bufs[i]);
+ vbq->bufs[i] = NULL;
+ }
+
+ mutex_unlock(&vbq->vb_lock);
+
+ videobuf_mmap_free(vbq);
+}
+
+/*
+ * Allocate physically as contiguous as possible buffer for video
+ * frame and allocate and build DMA scatter-gather list for it.
+ */
+static int omap24xxcam_vbq_alloc_mmap_buffer(struct videobuf_buffer *vb)
+{
+ unsigned int order;
+ size_t alloc_size, size = vb->bsize; /* vb->bsize is page aligned */
+ struct page *page;
+ int max_pages, err = 0, i = 0;
+ struct videobuf_dmabuf *dma = videobuf_to_dma(vb);
+
+ /*
+ * allocate maximum size scatter-gather list. Note this is
+ * overhead. We may not use as many entries as we allocate
+ */
+ max_pages = vb->bsize >> PAGE_SHIFT;
+ dma->sglist = kcalloc(max_pages, sizeof(*dma->sglist), GFP_KERNEL);
+ if (dma->sglist == NULL) {
+ err = -ENOMEM;
+ goto out;
+ }
+
+ while (size) {
+ order = get_order(size);
+ /*
+ * do not over-allocate even if we would get larger
+ * contiguous chunk that way
+ */
+ if ((PAGE_SIZE << order) > size)
+ order--;
+
+ /* try to allocate as many contiguous pages as possible */
+ page = alloc_pages(GFP_KERNEL, order);
+ /* if allocation fails, try to allocate smaller amount */
+ while (page == NULL) {
+ order--;
+ page = alloc_pages(GFP_KERNEL, order);
+ if (page == NULL && !order) {
+ err = -ENOMEM;
+ goto out;
+ }
+ }
+ size -= (PAGE_SIZE << order);
+
+ /* append allocated chunk of pages into scatter-gather list */
+ sg_set_page(&dma->sglist[i], page, PAGE_SIZE << order, 0);
+ dma->sglen++;
+ i++;
+
+ alloc_size = (PAGE_SIZE << order);
+
+ /* clear pages before giving them to user space */
+ memset(page_address(page), 0, alloc_size);
+
+ /* mark allocated pages reserved */
+ do {
+ SetPageReserved(page++);
+ } while (alloc_size -= PAGE_SIZE);
+ }
+ /*
+ * REVISIT: not fully correct to assign nr_pages == sglen but
+ * video-buf is passing nr_pages for e.g. unmap_sg calls
+ */
+ dma->nr_pages = dma->sglen;
+ dma->direction = PCI_DMA_FROMDEVICE;
+
+ return 0;
+
+out:
+ omap24xxcam_vbq_free_mmap_buffer(vb);
+ return err;
+}
+
+static int omap24xxcam_vbq_alloc_mmap_buffers(struct videobuf_queue *vbq,
+ unsigned int count)
+{
+ int i, err = 0;
+ struct omap24xxcam_fh *fh =
+ container_of(vbq, struct omap24xxcam_fh, vbq);
+
+ mutex_lock(&vbq->vb_lock);
+
+ for (i = 0; i < count; i++) {
+ err = omap24xxcam_vbq_alloc_mmap_buffer(vbq->bufs[i]);
+ if (err)
+ goto out;
+ dev_dbg(fh->cam->dev, "sglen is %d for buffer %d\n",
+ videobuf_to_dma(vbq->bufs[i])->sglen, i);
+ }
+
+ mutex_unlock(&vbq->vb_lock);
+
+ return 0;
+out:
+ while (i) {
+ i--;
+ omap24xxcam_vbq_free_mmap_buffer(vbq->bufs[i]);
+ }
+
+ mutex_unlock(&vbq->vb_lock);
+
+ return err;
+}
+
+/*
+ * This routine is called from interrupt context when a scatter-gather DMA
+ * transfer of a videobuf_buffer completes.
+ */
+static void omap24xxcam_vbq_complete(struct omap24xxcam_sgdma *sgdma,
+ u32 csr, void *arg)
+{
+ struct omap24xxcam_device *cam =
+ container_of(sgdma, struct omap24xxcam_device, sgdma);
+ struct omap24xxcam_fh *fh = cam->streaming->private_data;
+ struct videobuf_buffer *vb = (struct videobuf_buffer *)arg;
+ const u32 csr_error = CAMDMA_CSR_MISALIGNED_ERR
+ | CAMDMA_CSR_SUPERVISOR_ERR | CAMDMA_CSR_SECURE_ERR
+ | CAMDMA_CSR_TRANS_ERR | CAMDMA_CSR_DROP;
+ unsigned long flags;
+
+ spin_lock_irqsave(&cam->core_enable_disable_lock, flags);
+ if (--cam->sgdma_in_queue == 0)
+ omap24xxcam_core_disable(cam);
+ spin_unlock_irqrestore(&cam->core_enable_disable_lock, flags);
+
+ v4l2_get_timestamp(&vb->ts);
+ vb->field_count = atomic_add_return(2, &fh->field_count);
+ if (csr & csr_error) {
+ vb->state = VIDEOBUF_ERROR;
+ if (!atomic_read(&fh->cam->in_reset)) {
+ dev_dbg(cam->dev, "resetting camera, csr 0x%x\n", csr);
+ omap24xxcam_reset(cam);
+ }
+ } else
+ vb->state = VIDEOBUF_DONE;
+ wake_up(&vb->done);
+}
+
+static void omap24xxcam_vbq_release(struct videobuf_queue *vbq,
+ struct videobuf_buffer *vb)
+{
+ struct videobuf_dmabuf *dma = videobuf_to_dma(vb);
+
+ /* wait for buffer, especially to get out of the sgdma queue */
+ videobuf_waiton(vbq, vb, 0, 0);
+ if (vb->memory == V4L2_MEMORY_MMAP) {
+ dma_unmap_sg(vbq->dev, dma->sglist, dma->sglen,
+ dma->direction);
+ dma->direction = DMA_NONE;
+ } else {
+ videobuf_dma_unmap(vbq->dev, videobuf_to_dma(vb));
+ videobuf_dma_free(videobuf_to_dma(vb));
+ }
+
+ vb->state = VIDEOBUF_NEEDS_INIT;
+}
+
+/*
+ * Limit the number of available kernel image capture buffers based on the
+ * number requested, the currently selected image size, and the maximum
+ * amount of memory permitted for kernel capture buffers.
+ */
+static int omap24xxcam_vbq_setup(struct videobuf_queue *vbq, unsigned int *cnt,
+ unsigned int *size)
+{
+ struct omap24xxcam_fh *fh = vbq->priv_data;
+
+ if (*cnt <= 0)
+ *cnt = VIDEO_MAX_FRAME; /* supply a default number of buffers */
+
+ if (*cnt > VIDEO_MAX_FRAME)
+ *cnt = VIDEO_MAX_FRAME;
+
+ *size = fh->pix.sizeimage;
+
+ /* accessing fh->cam->capture_mem is ok, it's constant */
+ if (*size * *cnt > fh->cam->capture_mem)
+ *cnt = fh->cam->capture_mem / *size;
+
+ return 0;
+}
+
+static int omap24xxcam_dma_iolock(struct videobuf_queue *vbq,
+ struct videobuf_dmabuf *dma)
+{
+ int err = 0;
+
+ dma->direction = PCI_DMA_FROMDEVICE;
+ if (!dma_map_sg(vbq->dev, dma->sglist, dma->sglen, dma->direction)) {
+ kfree(dma->sglist);
+ dma->sglist = NULL;
+ dma->sglen = 0;
+ err = -EIO;
+ }
+
+ return err;
+}
+
+static int omap24xxcam_vbq_prepare(struct videobuf_queue *vbq,
+ struct videobuf_buffer *vb,
+ enum v4l2_field field)
+{
+ struct omap24xxcam_fh *fh = vbq->priv_data;
+ int err = 0;
+
+ /*
+ * Accessing pix here is okay since it's constant while
+ * streaming is on (and we only get called then).
+ */
+ if (vb->baddr) {
+ /* This is a userspace buffer. */
+ if (fh->pix.sizeimage > vb->bsize) {
+ /* The buffer isn't big enough. */
+ err = -EINVAL;
+ } else
+ vb->size = fh->pix.sizeimage;
+ } else {
+ if (vb->state != VIDEOBUF_NEEDS_INIT) {
+ /*
+ * We have a kernel bounce buffer that has
+ * already been allocated.
+ */
+ if (fh->pix.sizeimage > vb->size) {
+ /*
+ * The image size has been changed to
+ * a larger size since this buffer was
+ * allocated, so we need to free and
+ * reallocate it.
+ */
+ omap24xxcam_vbq_release(vbq, vb);
+ vb->size = fh->pix.sizeimage;
+ }
+ } else {
+ /* We need to allocate a new kernel bounce buffer. */
+ vb->size = fh->pix.sizeimage;
+ }
+ }
+
+ if (err)
+ return err;
+
+ vb->width = fh->pix.width;
+ vb->height = fh->pix.height;
+ vb->field = field;
+
+ if (vb->state == VIDEOBUF_NEEDS_INIT) {
+ if (vb->memory == V4L2_MEMORY_MMAP)
+ /*
+ * we have built the scatter-gather list by ourself so
+ * do the scatter-gather mapping as well
+ */
+ err = omap24xxcam_dma_iolock(vbq, videobuf_to_dma(vb));
+ else
+ err = videobuf_iolock(vbq, vb, NULL);
+ }
+
+ if (!err)
+ vb->state = VIDEOBUF_PREPARED;
+ else
+ omap24xxcam_vbq_release(vbq, vb);
+
+ return err;
+}
+
+static void omap24xxcam_vbq_queue(struct videobuf_queue *vbq,
+ struct videobuf_buffer *vb)
+{
+ struct omap24xxcam_fh *fh = vbq->priv_data;
+ struct omap24xxcam_device *cam = fh->cam;
+ enum videobuf_state state = vb->state;
+ unsigned long flags;
+ int err;
+
+ /*
+ * FIXME: We're marking the buffer active since we have no
+ * pretty way of marking it active exactly when the
+ * scatter-gather transfer starts.
+ */
+ vb->state = VIDEOBUF_ACTIVE;
+
+ err = omap24xxcam_sgdma_queue(&fh->cam->sgdma,
+ videobuf_to_dma(vb)->sglist,
+ videobuf_to_dma(vb)->sglen, vb->size,
+ omap24xxcam_vbq_complete, vb);
+
+ if (!err) {
+ spin_lock_irqsave(&cam->core_enable_disable_lock, flags);
+ if (++cam->sgdma_in_queue == 1
+ && !atomic_read(&cam->in_reset))
+ omap24xxcam_core_enable(cam);
+ spin_unlock_irqrestore(&cam->core_enable_disable_lock, flags);
+ } else {
+ /*
+ * Oops. We're not supposed to get any errors here.
+ * The only way we could get an error is if we ran out
+ * of scatter-gather DMA slots, but we are supposed to
+ * have at least as many scatter-gather DMA slots as
+ * video buffers so that can't happen.
+ */
+ dev_err(cam->dev, "failed to queue a video buffer for dma!\n");
+ dev_err(cam->dev, "likely a bug in the driver!\n");
+ vb->state = state;
+ }
+}
+
+static struct videobuf_queue_ops omap24xxcam_vbq_ops = {
+ .buf_setup = omap24xxcam_vbq_setup,
+ .buf_prepare = omap24xxcam_vbq_prepare,
+ .buf_queue = omap24xxcam_vbq_queue,
+ .buf_release = omap24xxcam_vbq_release,
+};
+
+/*
+ *
+ * OMAP main camera system
+ *
+ */
+
+/*
+ * Reset camera block to power-on state.
+ */
+static void omap24xxcam_poweron_reset(struct omap24xxcam_device *cam)
+{
+ int max_loop = RESET_TIMEOUT_NS;
+
+ /* Reset whole camera subsystem */
+ omap24xxcam_reg_out(cam->mmio_base,
+ CAM_SYSCONFIG,
+ CAM_SYSCONFIG_SOFTRESET);
+
+ /* Wait till it's finished */
+ while (!(omap24xxcam_reg_in(cam->mmio_base, CAM_SYSSTATUS)
+ & CAM_SYSSTATUS_RESETDONE)
+ && --max_loop) {
+ ndelay(1);
+ }
+
+ if (!(omap24xxcam_reg_in(cam->mmio_base, CAM_SYSSTATUS)
+ & CAM_SYSSTATUS_RESETDONE))
+ dev_err(cam->dev, "camera soft reset timeout\n");
+}
+
+/*
+ * (Re)initialise the camera block.
+ */
+static void omap24xxcam_hwinit(struct omap24xxcam_device *cam)
+{
+ omap24xxcam_poweron_reset(cam);
+
+ /* set the camera subsystem autoidle bit */
+ omap24xxcam_reg_out(cam->mmio_base, CAM_SYSCONFIG,
+ CAM_SYSCONFIG_AUTOIDLE);
+
+ /* set the camera MMU autoidle bit */
+ omap24xxcam_reg_out(cam->mmio_base,
+ CAMMMU_REG_OFFSET + CAMMMU_SYSCONFIG,
+ CAMMMU_SYSCONFIG_AUTOIDLE);
+
+ omap24xxcam_core_hwinit(cam);
+
+ omap24xxcam_dma_hwinit(&cam->sgdma.dma);
+}
+
+/*
+ * Callback for dma transfer stalling.
+ */
+static void omap24xxcam_stalled_dma_reset(unsigned long data)
+{
+ struct omap24xxcam_device *cam = (struct omap24xxcam_device *)data;
+
+ if (!atomic_read(&cam->in_reset)) {
+ dev_dbg(cam->dev, "dma stalled, resetting camera\n");
+ omap24xxcam_reset(cam);
+ }
+}
+
+/*
+ * Stop capture. Mark we're doing a reset, stop DMA transfers and
+ * core. (No new scatter-gather transfers will be queued whilst
+ * in_reset is non-zero.)
+ *
+ * If omap24xxcam_capture_stop is called from several places at
+ * once, only the first call will have an effect. Similarly, the last
+ * call omap24xxcam_streaming_cont will have effect.
+ *
+ * Serialisation is ensured by using cam->core_enable_disable_lock.
+ */
+static void omap24xxcam_capture_stop(struct omap24xxcam_device *cam)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&cam->core_enable_disable_lock, flags);
+
+ if (atomic_inc_return(&cam->in_reset) != 1) {
+ spin_unlock_irqrestore(&cam->core_enable_disable_lock, flags);
+ return;
+ }
+
+ omap24xxcam_core_disable(cam);
+
+ spin_unlock_irqrestore(&cam->core_enable_disable_lock, flags);
+
+ omap24xxcam_sgdma_sync(&cam->sgdma);
+}
+
+/*
+ * Reset and continue streaming.
+ *
+ * Note: Resetting the camera FIFO via the CC_RST bit in the CC_CTRL
+ * register is supposed to be sufficient to recover from a camera
+ * interface error, but it doesn't seem to be enough. If we only do
+ * that then subsequent image captures are out of sync by either one
+ * or two times DMA_THRESHOLD bytes. Resetting and re-initializing the
+ * entire camera subsystem prevents the problem with frame
+ * synchronization.
+ */
+static void omap24xxcam_capture_cont(struct omap24xxcam_device *cam)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&cam->core_enable_disable_lock, flags);
+
+ if (atomic_read(&cam->in_reset) != 1)
+ goto out;
+
+ omap24xxcam_hwinit(cam);
+
+ omap24xxcam_sensor_if_enable(cam);
+
+ omap24xxcam_sgdma_process(&cam->sgdma);
+
+ if (cam->sgdma_in_queue)
+ omap24xxcam_core_enable(cam);
+
+out:
+ atomic_dec(&cam->in_reset);
+ spin_unlock_irqrestore(&cam->core_enable_disable_lock, flags);
+}
+
+static ssize_t
+omap24xxcam_streaming_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct omap24xxcam_device *cam = dev_get_drvdata(dev);
+
+ return sprintf(buf, "%s\n", cam->streaming ? "active" : "inactive");
+}
+static DEVICE_ATTR(streaming, S_IRUGO, omap24xxcam_streaming_show, NULL);
+
+/*
+ * Stop capture and restart it. I.e. reset the camera during use.
+ */
+static void omap24xxcam_reset(struct omap24xxcam_device *cam)
+{
+ omap24xxcam_capture_stop(cam);
+ omap24xxcam_capture_cont(cam);
+}
+
+/*
+ * The main interrupt handler.
+ */
+static irqreturn_t omap24xxcam_isr(int irq, void *arg)
+{
+ struct omap24xxcam_device *cam = (struct omap24xxcam_device *)arg;
+ u32 irqstatus;
+ unsigned int irqhandled = 0;
+
+ irqstatus = omap24xxcam_reg_in(cam->mmio_base, CAM_IRQSTATUS);
+
+ if (irqstatus &
+ (CAM_IRQSTATUS_DMA_IRQ2 | CAM_IRQSTATUS_DMA_IRQ1
+ | CAM_IRQSTATUS_DMA_IRQ0)) {
+ omap24xxcam_dma_isr(&cam->sgdma.dma);
+ irqhandled = 1;
+ }
+ if (irqstatus & CAM_IRQSTATUS_CC_IRQ) {
+ omap24xxcam_core_isr(cam);
+ irqhandled = 1;
+ }
+ if (irqstatus & CAM_IRQSTATUS_MMU_IRQ)
+ dev_err(cam->dev, "unhandled camera MMU interrupt!\n");
+
+ return IRQ_RETVAL(irqhandled);
+}
+
+/*
+ *
+ * Sensor handling.
+ *
+ */
+
+/*
+ * Enable the external sensor interface. Try to negotiate interface
+ * parameters with the sensor and start using the new ones. The calls
+ * to sensor_if_enable and sensor_if_disable need not to be balanced.
+ */
+static int omap24xxcam_sensor_if_enable(struct omap24xxcam_device *cam)
+{
+ int rval;
+ struct v4l2_ifparm p;
+
+ rval = vidioc_int_g_ifparm(cam->sdev, &p);
+ if (rval) {
+ dev_err(cam->dev, "vidioc_int_g_ifparm failed with %d\n", rval);
+ return rval;
+ }
+
+ cam->if_type = p.if_type;
+
+ cam->cc_ctrl = CC_CTRL_CC_EN;
+
+ switch (p.if_type) {
+ case V4L2_IF_TYPE_BT656:
+ if (p.u.bt656.frame_start_on_rising_vs)
+ cam->cc_ctrl |= CC_CTRL_NOBT_SYNCHRO;
+ if (p.u.bt656.bt_sync_correct)
+ cam->cc_ctrl |= CC_CTRL_BT_CORRECT;
+ if (p.u.bt656.swap)
+ cam->cc_ctrl |= CC_CTRL_PAR_ORDERCAM;
+ if (p.u.bt656.latch_clk_inv)
+ cam->cc_ctrl |= CC_CTRL_PAR_CLK_POL;
+ if (p.u.bt656.nobt_hs_inv)
+ cam->cc_ctrl |= CC_CTRL_NOBT_HS_POL;
+ if (p.u.bt656.nobt_vs_inv)
+ cam->cc_ctrl |= CC_CTRL_NOBT_VS_POL;
+
+ switch (p.u.bt656.mode) {
+ case V4L2_IF_TYPE_BT656_MODE_NOBT_8BIT:
+ cam->cc_ctrl |= CC_CTRL_PAR_MODE_NOBT8;
+ break;
+ case V4L2_IF_TYPE_BT656_MODE_NOBT_10BIT:
+ cam->cc_ctrl |= CC_CTRL_PAR_MODE_NOBT10;
+ break;
+ case V4L2_IF_TYPE_BT656_MODE_NOBT_12BIT:
+ cam->cc_ctrl |= CC_CTRL_PAR_MODE_NOBT12;
+ break;
+ case V4L2_IF_TYPE_BT656_MODE_BT_8BIT:
+ cam->cc_ctrl |= CC_CTRL_PAR_MODE_BT8;
+ break;
+ case V4L2_IF_TYPE_BT656_MODE_BT_10BIT:
+ cam->cc_ctrl |= CC_CTRL_PAR_MODE_BT10;
+ break;
+ default:
+ dev_err(cam->dev,
+ "bt656 interface mode %d not supported\n",
+ p.u.bt656.mode);
+ return -EINVAL;
+ }
+ /*
+ * The clock rate that the sensor wants has changed.
+ * We have to adjust the xclk from OMAP 2 side to
+ * match the sensor's wish as closely as possible.
+ */
+ if (p.u.bt656.clock_curr != cam->if_u.bt656.xclk) {
+ u32 xclk = p.u.bt656.clock_curr;
+ u32 divisor;
+
+ if (xclk == 0)
+ return -EINVAL;
+
+ if (xclk > CAM_MCLK)
+ xclk = CAM_MCLK;
+
+ divisor = CAM_MCLK / xclk;
+ if (divisor * xclk < CAM_MCLK)
+ divisor++;
+ if (CAM_MCLK / divisor < p.u.bt656.clock_min
+ && divisor > 1)
+ divisor--;
+ if (divisor > 30)
+ divisor = 30;
+
+ xclk = CAM_MCLK / divisor;
+
+ if (xclk < p.u.bt656.clock_min
+ || xclk > p.u.bt656.clock_max)
+ return -EINVAL;
+
+ cam->if_u.bt656.xclk = xclk;
+ }
+ omap24xxcam_core_xclk_set(cam, cam->if_u.bt656.xclk);
+ break;
+ default:
+ /* FIXME: how about other interfaces? */
+ dev_err(cam->dev, "interface type %d not supported\n",
+ p.if_type);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static void omap24xxcam_sensor_if_disable(const struct omap24xxcam_device *cam)
+{
+ switch (cam->if_type) {
+ case V4L2_IF_TYPE_BT656:
+ omap24xxcam_core_xclk_set(cam, 0);
+ break;
+ }
+}
+
+/*
+ * Initialise the sensor hardware.
+ */
+static int omap24xxcam_sensor_init(struct omap24xxcam_device *cam)
+{
+ int err = 0;
+ struct v4l2_int_device *sdev = cam->sdev;
+
+ omap24xxcam_clock_on(cam);
+ err = omap24xxcam_sensor_if_enable(cam);
+ if (err) {
+ dev_err(cam->dev, "sensor interface could not be enabled at "
+ "initialisation, %d\n", err);
+ cam->sdev = NULL;
+ goto out;
+ }
+
+ /* power up sensor during sensor initialization */
+ vidioc_int_s_power(sdev, 1);
+
+ err = vidioc_int_dev_init(sdev);
+ if (err) {
+ dev_err(cam->dev, "cannot initialize sensor, error %d\n", err);
+ /* Sensor init failed --- it's nonexistent to us! */
+ cam->sdev = NULL;
+ goto out;
+ }
+
+ dev_info(cam->dev, "sensor is %s\n", sdev->name);
+
+out:
+ omap24xxcam_sensor_if_disable(cam);
+ omap24xxcam_clock_off(cam);
+
+ vidioc_int_s_power(sdev, 0);
+
+ return err;
+}
+
+static void omap24xxcam_sensor_exit(struct omap24xxcam_device *cam)
+{
+ if (cam->sdev)
+ vidioc_int_dev_exit(cam->sdev);
+}
+
+static void omap24xxcam_sensor_disable(struct omap24xxcam_device *cam)
+{
+ omap24xxcam_sensor_if_disable(cam);
+ omap24xxcam_clock_off(cam);
+ vidioc_int_s_power(cam->sdev, 0);
+}
+
+/*
+ * Power-up and configure camera sensor. It's ready for capturing now.
+ */
+static int omap24xxcam_sensor_enable(struct omap24xxcam_device *cam)
+{
+ int rval;
+
+ omap24xxcam_clock_on(cam);
+
+ omap24xxcam_sensor_if_enable(cam);
+
+ rval = vidioc_int_s_power(cam->sdev, 1);
+ if (rval)
+ goto out;
+
+ rval = vidioc_int_init(cam->sdev);
+ if (rval)
+ goto out;
+
+ return 0;
+
+out:
+ omap24xxcam_sensor_disable(cam);
+
+ return rval;
+}
+
+static void omap24xxcam_sensor_reset_work(struct work_struct *work)
+{
+ struct omap24xxcam_device *cam =
+ container_of(work, struct omap24xxcam_device,
+ sensor_reset_work);
+
+ if (atomic_read(&cam->reset_disable))
+ return;
+
+ omap24xxcam_capture_stop(cam);
+
+ if (vidioc_int_reset(cam->sdev) == 0) {
+ vidioc_int_init(cam->sdev);
+ } else {
+ /* Can't reset it by vidioc_int_reset. */
+ omap24xxcam_sensor_disable(cam);
+ omap24xxcam_sensor_enable(cam);
+ }
+
+ omap24xxcam_capture_cont(cam);
+}
+
+/*
+ *
+ * IOCTL interface.
+ *
+ */
+
+static int vidioc_querycap(struct file *file, void *fh,
+ struct v4l2_capability *cap)
+{
+ struct omap24xxcam_fh *ofh = fh;
+ struct omap24xxcam_device *cam = ofh->cam;
+
+ strlcpy(cap->driver, CAM_NAME, sizeof(cap->driver));
+ strlcpy(cap->card, cam->vfd->name, sizeof(cap->card));
+ cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;
+
+ return 0;
+}
+
+static int vidioc_enum_fmt_vid_cap(struct file *file, void *fh,
+ struct v4l2_fmtdesc *f)
+{
+ struct omap24xxcam_fh *ofh = fh;
+ struct omap24xxcam_device *cam = ofh->cam;
+ int rval;
+
+ rval = vidioc_int_enum_fmt_cap(cam->sdev, f);
+
+ return rval;
+}
+
+static int vidioc_g_fmt_vid_cap(struct file *file, void *fh,
+ struct v4l2_format *f)
+{
+ struct omap24xxcam_fh *ofh = fh;
+ struct omap24xxcam_device *cam = ofh->cam;
+ int rval;
+
+ mutex_lock(&cam->mutex);
+ rval = vidioc_int_g_fmt_cap(cam->sdev, f);
+ mutex_unlock(&cam->mutex);
+
+ return rval;
+}
+
+static int vidioc_s_fmt_vid_cap(struct file *file, void *fh,
+ struct v4l2_format *f)
+{
+ struct omap24xxcam_fh *ofh = fh;
+ struct omap24xxcam_device *cam = ofh->cam;
+ int rval;
+
+ mutex_lock(&cam->mutex);
+ if (cam->streaming) {
+ rval = -EBUSY;
+ goto out;
+ }
+
+ rval = vidioc_int_s_fmt_cap(cam->sdev, f);
+
+out:
+ mutex_unlock(&cam->mutex);
+
+ if (!rval) {
+ mutex_lock(&ofh->vbq.vb_lock);
+ ofh->pix = f->fmt.pix;
+ mutex_unlock(&ofh->vbq.vb_lock);
+ }
+
+ memset(f, 0, sizeof(*f));
+ vidioc_g_fmt_vid_cap(file, fh, f);
+
+ return rval;
+}
+
+static int vidioc_try_fmt_vid_cap(struct file *file, void *fh,
+ struct v4l2_format *f)
+{
+ struct omap24xxcam_fh *ofh = fh;
+ struct omap24xxcam_device *cam = ofh->cam;
+ int rval;
+
+ mutex_lock(&cam->mutex);
+ rval = vidioc_int_try_fmt_cap(cam->sdev, f);
+ mutex_unlock(&cam->mutex);
+
+ return rval;
+}
+
+static int vidioc_reqbufs(struct file *file, void *fh,
+ struct v4l2_requestbuffers *b)
+{
+ struct omap24xxcam_fh *ofh = fh;
+ struct omap24xxcam_device *cam = ofh->cam;
+ int rval;
+
+ mutex_lock(&cam->mutex);
+ if (cam->streaming) {
+ mutex_unlock(&cam->mutex);
+ return -EBUSY;
+ }
+
+ omap24xxcam_vbq_free_mmap_buffers(&ofh->vbq);
+ mutex_unlock(&cam->mutex);
+
+ rval = videobuf_reqbufs(&ofh->vbq, b);
+
+ /*
+ * Either videobuf_reqbufs failed or the buffers are not
+ * memory-mapped (which would need special attention).
+ */
+ if (rval < 0 || b->memory != V4L2_MEMORY_MMAP)
+ goto out;
+
+ rval = omap24xxcam_vbq_alloc_mmap_buffers(&ofh->vbq, rval);
+ if (rval)
+ omap24xxcam_vbq_free_mmap_buffers(&ofh->vbq);
+
+out:
+ return rval;
+}
+
+static int vidioc_querybuf(struct file *file, void *fh,
+ struct v4l2_buffer *b)
+{
+ struct omap24xxcam_fh *ofh = fh;
+
+ return videobuf_querybuf(&ofh->vbq, b);
+}
+
+static int vidioc_qbuf(struct file *file, void *fh, struct v4l2_buffer *b)
+{
+ struct omap24xxcam_fh *ofh = fh;
+
+ return videobuf_qbuf(&ofh->vbq, b);
+}
+
+static int vidioc_dqbuf(struct file *file, void *fh, struct v4l2_buffer *b)
+{
+ struct omap24xxcam_fh *ofh = fh;
+ struct omap24xxcam_device *cam = ofh->cam;
+ struct videobuf_buffer *vb;
+ int rval;
+
+videobuf_dqbuf_again:
+ rval = videobuf_dqbuf(&ofh->vbq, b, file->f_flags & O_NONBLOCK);
+ if (rval)
+ goto out;
+
+ vb = ofh->vbq.bufs[b->index];
+
+ mutex_lock(&cam->mutex);
+ /* _needs_reset returns -EIO if reset is required. */
+ rval = vidioc_int_g_needs_reset(cam->sdev, (void *)vb->baddr);
+ mutex_unlock(&cam->mutex);
+ if (rval == -EIO)
+ schedule_work(&cam->sensor_reset_work);
+ else
+ rval = 0;
+
+out:
+ /*
+ * This is a hack. We don't want to show -EIO to the user
+ * space. Requeue the buffer and try again if we're not doing
+ * this in non-blocking mode.
+ */
+ if (rval == -EIO) {
+ videobuf_qbuf(&ofh->vbq, b);
+ if (!(file->f_flags & O_NONBLOCK))
+ goto videobuf_dqbuf_again;
+ /*
+ * We don't have a videobuf_buffer now --- maybe next
+ * time...
+ */
+ rval = -EAGAIN;
+ }
+
+ return rval;
+}
+
+static int vidioc_streamon(struct file *file, void *fh, enum v4l2_buf_type i)
+{
+ struct omap24xxcam_fh *ofh = fh;
+ struct omap24xxcam_device *cam = ofh->cam;
+ int rval;
+
+ mutex_lock(&cam->mutex);
+ if (cam->streaming) {
+ rval = -EBUSY;
+ goto out;
+ }
+
+ rval = omap24xxcam_sensor_if_enable(cam);
+ if (rval) {
+ dev_dbg(cam->dev, "vidioc_int_g_ifparm failed\n");
+ goto out;
+ }
+
+ rval = videobuf_streamon(&ofh->vbq);
+ if (!rval) {
+ cam->streaming = file;
+ sysfs_notify(&cam->dev->kobj, NULL, "streaming");
+ }
+
+out:
+ mutex_unlock(&cam->mutex);
+
+ return rval;
+}
+
+static int vidioc_streamoff(struct file *file, void *fh, enum v4l2_buf_type i)
+{
+ struct omap24xxcam_fh *ofh = fh;
+ struct omap24xxcam_device *cam = ofh->cam;
+ struct videobuf_queue *q = &ofh->vbq;
+ int rval;
+
+ atomic_inc(&cam->reset_disable);
+
+ flush_work(&cam->sensor_reset_work);
+
+ rval = videobuf_streamoff(q);
+ if (!rval) {
+ mutex_lock(&cam->mutex);
+ cam->streaming = NULL;
+ mutex_unlock(&cam->mutex);
+ sysfs_notify(&cam->dev->kobj, NULL, "streaming");
+ }
+
+ atomic_dec(&cam->reset_disable);
+
+ return rval;
+}
+
+static int vidioc_enum_input(struct file *file, void *fh,
+ struct v4l2_input *inp)
+{
+ if (inp->index > 0)
+ return -EINVAL;
+
+ strlcpy(inp->name, "camera", sizeof(inp->name));
+ inp->type = V4L2_INPUT_TYPE_CAMERA;
+
+ return 0;
+}
+
+static int vidioc_g_input(struct file *file, void *fh, unsigned int *i)
+{
+ *i = 0;
+
+ return 0;
+}
+
+static int vidioc_s_input(struct file *file, void *fh, unsigned int i)
+{
+ if (i > 0)
+ return -EINVAL;
+
+ return 0;
+}
+
+static int vidioc_queryctrl(struct file *file, void *fh,
+ struct v4l2_queryctrl *a)
+{
+ struct omap24xxcam_fh *ofh = fh;
+ struct omap24xxcam_device *cam = ofh->cam;
+ int rval;
+
+ rval = vidioc_int_queryctrl(cam->sdev, a);
+
+ return rval;
+}
+
+static int vidioc_g_ctrl(struct file *file, void *fh,
+ struct v4l2_control *a)
+{
+ struct omap24xxcam_fh *ofh = fh;
+ struct omap24xxcam_device *cam = ofh->cam;
+ int rval;
+
+ mutex_lock(&cam->mutex);
+ rval = vidioc_int_g_ctrl(cam->sdev, a);
+ mutex_unlock(&cam->mutex);
+
+ return rval;
+}
+
+static int vidioc_s_ctrl(struct file *file, void *fh,
+ struct v4l2_control *a)
+{
+ struct omap24xxcam_fh *ofh = fh;
+ struct omap24xxcam_device *cam = ofh->cam;
+ int rval;
+
+ mutex_lock(&cam->mutex);
+ rval = vidioc_int_s_ctrl(cam->sdev, a);
+ mutex_unlock(&cam->mutex);
+
+ return rval;
+}
+
+static int vidioc_g_parm(struct file *file, void *fh,
+ struct v4l2_streamparm *a) {
+ struct omap24xxcam_fh *ofh = fh;
+ struct omap24xxcam_device *cam = ofh->cam;
+ int rval;
+
+ mutex_lock(&cam->mutex);
+ rval = vidioc_int_g_parm(cam->sdev, a);
+ mutex_unlock(&cam->mutex);
+
+ return rval;
+}
+
+static int vidioc_s_parm(struct file *file, void *fh,
+ struct v4l2_streamparm *a)
+{
+ struct omap24xxcam_fh *ofh = fh;
+ struct omap24xxcam_device *cam = ofh->cam;
+ struct v4l2_streamparm old_streamparm;
+ int rval;
+
+ mutex_lock(&cam->mutex);
+ if (cam->streaming) {
+ rval = -EBUSY;
+ goto out;
+ }
+
+ old_streamparm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ rval = vidioc_int_g_parm(cam->sdev, &old_streamparm);
+ if (rval)
+ goto out;
+
+ rval = vidioc_int_s_parm(cam->sdev, a);
+ if (rval)
+ goto out;
+
+ rval = omap24xxcam_sensor_if_enable(cam);
+ /*
+ * Revert to old streaming parameters if enabling sensor
+ * interface with the new ones failed.
+ */
+ if (rval)
+ vidioc_int_s_parm(cam->sdev, &old_streamparm);
+
+out:
+ mutex_unlock(&cam->mutex);
+
+ return rval;
+}
+
+/*
+ *
+ * File operations.
+ *
+ */
+
+static unsigned int omap24xxcam_poll(struct file *file,
+ struct poll_table_struct *wait)
+{
+ struct omap24xxcam_fh *fh = file->private_data;
+ struct omap24xxcam_device *cam = fh->cam;
+ struct videobuf_buffer *vb;
+
+ mutex_lock(&cam->mutex);
+ if (cam->streaming != file) {
+ mutex_unlock(&cam->mutex);
+ return POLLERR;
+ }
+ mutex_unlock(&cam->mutex);
+
+ mutex_lock(&fh->vbq.vb_lock);
+ if (list_empty(&fh->vbq.stream)) {
+ mutex_unlock(&fh->vbq.vb_lock);
+ return POLLERR;
+ }
+ vb = list_entry(fh->vbq.stream.next, struct videobuf_buffer, stream);
+ mutex_unlock(&fh->vbq.vb_lock);
+
+ poll_wait(file, &vb->done, wait);
+
+ if (vb->state == VIDEOBUF_DONE || vb->state == VIDEOBUF_ERROR)
+ return POLLIN | POLLRDNORM;
+
+ return 0;
+}
+
+static int omap24xxcam_mmap_buffers(struct file *file,
+ struct vm_area_struct *vma)
+{
+ struct omap24xxcam_fh *fh = file->private_data;
+ struct omap24xxcam_device *cam = fh->cam;
+ struct videobuf_queue *vbq = &fh->vbq;
+ unsigned int first, last, size, i, j;
+ int err = 0;
+
+ mutex_lock(&cam->mutex);
+ if (cam->streaming) {
+ mutex_unlock(&cam->mutex);
+ return -EBUSY;
+ }
+ mutex_unlock(&cam->mutex);
+ mutex_lock(&vbq->vb_lock);
+
+ /* look for first buffer to map */
+ for (first = 0; first < VIDEO_MAX_FRAME; first++) {
+ if (NULL == vbq->bufs[first])
+ continue;
+ if (V4L2_MEMORY_MMAP != vbq->bufs[first]->memory)
+ continue;
+ if (vbq->bufs[first]->boff == (vma->vm_pgoff << PAGE_SHIFT))
+ break;
+ }
+
+ /* look for last buffer to map */
+ for (size = 0, last = first; last < VIDEO_MAX_FRAME; last++) {
+ if (NULL == vbq->bufs[last])
+ continue;
+ if (V4L2_MEMORY_MMAP != vbq->bufs[last]->memory)
+ continue;
+ size += vbq->bufs[last]->bsize;
+ if (size == (vma->vm_end - vma->vm_start))
+ break;
+ }
+
+ size = 0;
+ for (i = first; i <= last && i < VIDEO_MAX_FRAME; i++) {
+ struct videobuf_dmabuf *dma = videobuf_to_dma(vbq->bufs[i]);
+
+ for (j = 0; j < dma->sglen; j++) {
+ err = remap_pfn_range(
+ vma, vma->vm_start + size,
+ page_to_pfn(sg_page(&dma->sglist[j])),
+ sg_dma_len(&dma->sglist[j]), vma->vm_page_prot);
+ if (err)
+ goto out;
+ size += sg_dma_len(&dma->sglist[j]);
+ }
+ }
+
+out:
+ mutex_unlock(&vbq->vb_lock);
+
+ return err;
+}
+
+static int omap24xxcam_mmap(struct file *file, struct vm_area_struct *vma)
+{
+ struct omap24xxcam_fh *fh = file->private_data;
+ int rval;
+
+ /* let the video-buf mapper check arguments and set-up structures */
+ rval = videobuf_mmap_mapper(&fh->vbq, vma);
+ if (rval)
+ return rval;
+
+ vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
+
+ /* do mapping to our allocated buffers */
+ rval = omap24xxcam_mmap_buffers(file, vma);
+ /*
+ * In case of error, free vma->vm_private_data allocated by
+ * videobuf_mmap_mapper.
+ */
+ if (rval)
+ kfree(vma->vm_private_data);
+
+ return rval;
+}
+
+static int omap24xxcam_open(struct file *file)
+{
+ struct omap24xxcam_device *cam = omap24xxcam.priv;
+ struct omap24xxcam_fh *fh;
+ struct v4l2_format format;
+
+ if (!cam || !cam->vfd)
+ return -ENODEV;
+
+ fh = kzalloc(sizeof(*fh), GFP_KERNEL);
+ if (fh == NULL)
+ return -ENOMEM;
+
+ mutex_lock(&cam->mutex);
+ if (cam->sdev == NULL || !try_module_get(cam->sdev->module)) {
+ mutex_unlock(&cam->mutex);
+ goto out_try_module_get;
+ }
+
+ if (atomic_inc_return(&cam->users) == 1) {
+ omap24xxcam_hwinit(cam);
+ if (omap24xxcam_sensor_enable(cam)) {
+ mutex_unlock(&cam->mutex);
+ goto out_omap24xxcam_sensor_enable;
+ }
+ }
+ mutex_unlock(&cam->mutex);
+
+ fh->cam = cam;
+ mutex_lock(&cam->mutex);
+ vidioc_int_g_fmt_cap(cam->sdev, &format);
+ mutex_unlock(&cam->mutex);
+ /* FIXME: how about fh->pix when there are more users? */
+ fh->pix = format.fmt.pix;
+
+ file->private_data = fh;
+
+ spin_lock_init(&fh->vbq_lock);
+
+ videobuf_queue_sg_init(&fh->vbq, &omap24xxcam_vbq_ops, NULL,
+ &fh->vbq_lock, V4L2_BUF_TYPE_VIDEO_CAPTURE,
+ V4L2_FIELD_NONE,
+ sizeof(struct videobuf_buffer), fh, NULL);
+
+ return 0;
+
+out_omap24xxcam_sensor_enable:
+ omap24xxcam_poweron_reset(cam);
+ module_put(cam->sdev->module);
+
+out_try_module_get:
+ kfree(fh);
+
+ return -ENODEV;
+}
+
+static int omap24xxcam_release(struct file *file)
+{
+ struct omap24xxcam_fh *fh = file->private_data;
+ struct omap24xxcam_device *cam = fh->cam;
+
+ atomic_inc(&cam->reset_disable);
+
+ flush_work(&cam->sensor_reset_work);
+
+ /* stop streaming capture */
+ videobuf_streamoff(&fh->vbq);
+
+ mutex_lock(&cam->mutex);
+ if (cam->streaming == file) {
+ cam->streaming = NULL;
+ mutex_unlock(&cam->mutex);
+ sysfs_notify(&cam->dev->kobj, NULL, "streaming");
+ } else {
+ mutex_unlock(&cam->mutex);
+ }
+
+ atomic_dec(&cam->reset_disable);
+
+ omap24xxcam_vbq_free_mmap_buffers(&fh->vbq);
+
+ /*
+ * Make sure the reset work we might have scheduled is not
+ * pending! It may be run *only* if we have users. (And it may
+ * not be scheduled anymore since streaming is already
+ * disabled.)
+ */
+ flush_work(&cam->sensor_reset_work);
+
+ mutex_lock(&cam->mutex);
+ if (atomic_dec_return(&cam->users) == 0) {
+ omap24xxcam_sensor_disable(cam);
+ omap24xxcam_poweron_reset(cam);
+ }
+ mutex_unlock(&cam->mutex);
+
+ file->private_data = NULL;
+
+ module_put(cam->sdev->module);
+ kfree(fh);
+
+ return 0;
+}
+
+static struct v4l2_file_operations omap24xxcam_fops = {
+ .ioctl = video_ioctl2,
+ .poll = omap24xxcam_poll,
+ .mmap = omap24xxcam_mmap,
+ .open = omap24xxcam_open,
+ .release = omap24xxcam_release,
+};
+
+/*
+ *
+ * Power management.
+ *
+ */
+
+#ifdef CONFIG_PM
+static int omap24xxcam_suspend(struct platform_device *pdev, pm_message_t state)
+{
+ struct omap24xxcam_device *cam = platform_get_drvdata(pdev);
+
+ if (atomic_read(&cam->users) == 0)
+ return 0;
+
+ if (!atomic_read(&cam->reset_disable))
+ omap24xxcam_capture_stop(cam);
+
+ omap24xxcam_sensor_disable(cam);
+ omap24xxcam_poweron_reset(cam);
+
+ return 0;
+}
+
+static int omap24xxcam_resume(struct platform_device *pdev)
+{
+ struct omap24xxcam_device *cam = platform_get_drvdata(pdev);
+
+ if (atomic_read(&cam->users) == 0)
+ return 0;
+
+ omap24xxcam_hwinit(cam);
+ omap24xxcam_sensor_enable(cam);
+
+ if (!atomic_read(&cam->reset_disable))
+ omap24xxcam_capture_cont(cam);
+
+ return 0;
+}
+#endif /* CONFIG_PM */
+
+static const struct v4l2_ioctl_ops omap24xxcam_ioctl_fops = {
+ .vidioc_querycap = vidioc_querycap,
+ .vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_vid_cap,
+ .vidioc_g_fmt_vid_cap = vidioc_g_fmt_vid_cap,
+ .vidioc_s_fmt_vid_cap = vidioc_s_fmt_vid_cap,
+ .vidioc_try_fmt_vid_cap = vidioc_try_fmt_vid_cap,
+ .vidioc_reqbufs = vidioc_reqbufs,
+ .vidioc_querybuf = vidioc_querybuf,
+ .vidioc_qbuf = vidioc_qbuf,
+ .vidioc_dqbuf = vidioc_dqbuf,
+ .vidioc_streamon = vidioc_streamon,
+ .vidioc_streamoff = vidioc_streamoff,
+ .vidioc_enum_input = vidioc_enum_input,
+ .vidioc_g_input = vidioc_g_input,
+ .vidioc_s_input = vidioc_s_input,
+ .vidioc_queryctrl = vidioc_queryctrl,
+ .vidioc_g_ctrl = vidioc_g_ctrl,
+ .vidioc_s_ctrl = vidioc_s_ctrl,
+ .vidioc_g_parm = vidioc_g_parm,
+ .vidioc_s_parm = vidioc_s_parm,
+};
+
+/*
+ *
+ * Camera device (i.e. /dev/video).
+ *
+ */
+
+static int omap24xxcam_device_register(struct v4l2_int_device *s)
+{
+ struct omap24xxcam_device *cam = s->u.slave->master->priv;
+ struct video_device *vfd;
+ int rval;
+
+ /* We already have a slave. */
+ if (cam->sdev)
+ return -EBUSY;
+
+ cam->sdev = s;
+
+ if (device_create_file(cam->dev, &dev_attr_streaming) != 0) {
+ dev_err(cam->dev, "could not register sysfs entry\n");
+ rval = -EBUSY;
+ goto err;
+ }
+
+ /* initialize the video_device struct */
+ vfd = cam->vfd = video_device_alloc();
+ if (!vfd) {
+ dev_err(cam->dev, "could not allocate video device struct\n");
+ rval = -ENOMEM;
+ goto err;
+ }
+ vfd->release = video_device_release;
+
+ vfd->v4l2_dev = &cam->v4l2_dev;
+
+ strlcpy(vfd->name, CAM_NAME, sizeof(vfd->name));
+ vfd->fops = &omap24xxcam_fops;
+ vfd->ioctl_ops = &omap24xxcam_ioctl_fops;
+
+ omap24xxcam_hwinit(cam);
+
+ rval = omap24xxcam_sensor_init(cam);
+ if (rval)
+ goto err;
+
+ if (video_register_device(vfd, VFL_TYPE_GRABBER, video_nr) < 0) {
+ dev_err(cam->dev, "could not register V4L device\n");
+ rval = -EBUSY;
+ goto err;
+ }
+
+ omap24xxcam_poweron_reset(cam);
+
+ dev_info(cam->dev, "registered device %s\n",
+ video_device_node_name(vfd));
+
+ return 0;
+
+err:
+ omap24xxcam_device_unregister(s);
+
+ return rval;
+}
+
+static void omap24xxcam_device_unregister(struct v4l2_int_device *s)
+{
+ struct omap24xxcam_device *cam = s->u.slave->master->priv;
+
+ omap24xxcam_sensor_exit(cam);
+
+ if (cam->vfd) {
+ if (!video_is_registered(cam->vfd)) {
+ /*
+ * The device was never registered, so release the
+ * video_device struct directly.
+ */
+ video_device_release(cam->vfd);
+ } else {
+ /*
+ * The unregister function will release the
+ * video_device struct as well as
+ * unregistering it.
+ */
+ video_unregister_device(cam->vfd);
+ }
+ cam->vfd = NULL;
+ }
+
+ device_remove_file(cam->dev, &dev_attr_streaming);
+
+ cam->sdev = NULL;
+}
+
+static struct v4l2_int_master omap24xxcam_master = {
+ .attach = omap24xxcam_device_register,
+ .detach = omap24xxcam_device_unregister,
+};
+
+static struct v4l2_int_device omap24xxcam = {
+ .module = THIS_MODULE,
+ .name = CAM_NAME,
+ .type = v4l2_int_type_master,
+ .u = {
+ .master = &omap24xxcam_master
+ },
+};
+
+/*
+ *
+ * Driver initialisation and deinitialisation.
+ *
+ */
+
+static int omap24xxcam_probe(struct platform_device *pdev)
+{
+ struct omap24xxcam_device *cam;
+ struct resource *mem;
+ int irq;
+
+ cam = kzalloc(sizeof(*cam), GFP_KERNEL);
+ if (!cam) {
+ dev_err(&pdev->dev, "could not allocate memory\n");
+ goto err;
+ }
+
+ platform_set_drvdata(pdev, cam);
+
+ cam->dev = &pdev->dev;
+
+ if (v4l2_device_register(&pdev->dev, &cam->v4l2_dev)) {
+ dev_err(&pdev->dev, "v4l2_device_register failed\n");
+ goto err;
+ }
+
+ /*
+ * Impose a lower limit on the amount of memory allocated for
+ * capture. We require at least enough memory to double-buffer
+ * QVGA (300KB).
+ */
+ if (capture_mem < 320 * 240 * 2 * 2)
+ capture_mem = 320 * 240 * 2 * 2;
+ cam->capture_mem = capture_mem;
+
+ /* request the mem region for the camera registers */
+ mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!mem) {
+ dev_err(cam->dev, "no mem resource?\n");
+ goto err;
+ }
+ if (!request_mem_region(mem->start, resource_size(mem), pdev->name)) {
+ dev_err(cam->dev,
+ "cannot reserve camera register I/O region\n");
+ goto err;
+ }
+ cam->mmio_base_phys = mem->start;
+ cam->mmio_size = resource_size(mem);
+
+ /* map the region */
+ cam->mmio_base = ioremap_nocache(cam->mmio_base_phys, cam->mmio_size);
+ if (!cam->mmio_base) {
+ dev_err(cam->dev, "cannot map camera register I/O region\n");
+ goto err;
+ }
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq <= 0) {
+ dev_err(cam->dev, "no irq for camera?\n");
+ goto err;
+ }
+
+ /* install the interrupt service routine */
+ if (request_irq(irq, omap24xxcam_isr, 0, CAM_NAME, cam)) {
+ dev_err(cam->dev,
+ "could not install interrupt service routine\n");
+ goto err;
+ }
+ cam->irq = irq;
+
+ if (omap24xxcam_clock_get(cam))
+ goto err;
+
+ INIT_WORK(&cam->sensor_reset_work, omap24xxcam_sensor_reset_work);
+
+ mutex_init(&cam->mutex);
+ spin_lock_init(&cam->core_enable_disable_lock);
+
+ omap24xxcam_sgdma_init(&cam->sgdma,
+ cam->mmio_base + CAMDMA_REG_OFFSET,
+ omap24xxcam_stalled_dma_reset,
+ (unsigned long)cam);
+
+ omap24xxcam.priv = cam;
+
+ if (v4l2_int_device_register(&omap24xxcam))
+ goto err;
+
+ return 0;
+
+err:
+ omap24xxcam_remove(pdev);
+ return -ENODEV;
+}
+
+static int omap24xxcam_remove(struct platform_device *pdev)
+{
+ struct omap24xxcam_device *cam = platform_get_drvdata(pdev);
+
+ if (!cam)
+ return 0;
+
+ if (omap24xxcam.priv != NULL)
+ v4l2_int_device_unregister(&omap24xxcam);
+ omap24xxcam.priv = NULL;
+
+ omap24xxcam_clock_put(cam);
+
+ if (cam->irq) {
+ free_irq(cam->irq, cam);
+ cam->irq = 0;
+ }
+
+ if (cam->mmio_base) {
+ iounmap((void *)cam->mmio_base);
+ cam->mmio_base = 0;
+ }
+
+ if (cam->mmio_base_phys) {
+ release_mem_region(cam->mmio_base_phys, cam->mmio_size);
+ cam->mmio_base_phys = 0;
+ }
+
+ v4l2_device_unregister(&cam->v4l2_dev);
+
+ kfree(cam);
+
+ return 0;
+}
+
+static struct platform_driver omap24xxcam_driver = {
+ .probe = omap24xxcam_probe,
+ .remove = omap24xxcam_remove,
+#ifdef CONFIG_PM
+ .suspend = omap24xxcam_suspend,
+ .resume = omap24xxcam_resume,
+#endif
+ .driver = {
+ .name = CAM_NAME,
+ .owner = THIS_MODULE,
+ },
+};
+
+module_platform_driver(omap24xxcam_driver);
+
+MODULE_AUTHOR("Sakari Ailus <sakari.ailus@nokia.com>");
+MODULE_DESCRIPTION("OMAP24xx Video for Linux camera driver");
+MODULE_LICENSE("GPL");
+MODULE_VERSION(OMAP24XXCAM_VERSION);
+module_param(video_nr, int, 0);
+MODULE_PARM_DESC(video_nr,
+ "Minor number for video device (-1 ==> auto assign)");
+module_param(capture_mem, int, 0);
+MODULE_PARM_DESC(capture_mem, "Maximum amount of memory for capture "
+ "buffers (default 4800kiB)");
diff --git a/drivers/staging/media/omap24xx/omap24xxcam.h b/drivers/staging/media/omap24xx/omap24xxcam.h
new file mode 100644
index 00000000000..233bb40cfec
--- /dev/null
+++ b/drivers/staging/media/omap24xx/omap24xxcam.h
@@ -0,0 +1,596 @@
+/*
+ * drivers/media/platform/omap24xxcam.h
+ *
+ * Copyright (C) 2004 MontaVista Software, Inc.
+ * Copyright (C) 2004 Texas Instruments.
+ * Copyright (C) 2007 Nokia Corporation.
+ *
+ * Contact: Sakari Ailus <sakari.ailus@nokia.com>
+ *
+ * Based on code from Andy Lowe <source@mvista.com>.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ */
+
+#ifndef OMAP24XXCAM_H
+#define OMAP24XXCAM_H
+
+#include <media/videobuf-dma-sg.h>
+#include <media/v4l2-device.h>
+#include "v4l2-int-device.h"
+
+/*
+ *
+ * General driver related definitions.
+ *
+ */
+
+#define CAM_NAME "omap24xxcam"
+
+#define CAM_MCLK 96000000
+
+/* number of bytes transferred per DMA request */
+#define DMA_THRESHOLD 32
+
+/*
+ * NUM_CAMDMA_CHANNELS is the number of logical channels provided by
+ * the camera DMA controller.
+ */
+#define NUM_CAMDMA_CHANNELS 4
+
+/*
+ * NUM_SG_DMA is the number of scatter-gather DMA transfers that can
+ * be queued. (We don't have any overlay sglists now.)
+ */
+#define NUM_SG_DMA (VIDEO_MAX_FRAME)
+
+/*
+ *
+ * Register definitions.
+ *
+ */
+
+/* subsystem register block offsets */
+#define CC_REG_OFFSET 0x00000400
+#define CAMDMA_REG_OFFSET 0x00000800
+#define CAMMMU_REG_OFFSET 0x00000C00
+
+/* define camera subsystem register offsets */
+#define CAM_REVISION 0x000
+#define CAM_SYSCONFIG 0x010
+#define CAM_SYSSTATUS 0x014
+#define CAM_IRQSTATUS 0x018
+#define CAM_GPO 0x040
+#define CAM_GPI 0x050
+
+/* define camera core register offsets */
+#define CC_REVISION 0x000
+#define CC_SYSCONFIG 0x010
+#define CC_SYSSTATUS 0x014
+#define CC_IRQSTATUS 0x018
+#define CC_IRQENABLE 0x01C
+#define CC_CTRL 0x040
+#define CC_CTRL_DMA 0x044
+#define CC_CTRL_XCLK 0x048
+#define CC_FIFODATA 0x04C
+#define CC_TEST 0x050
+#define CC_GENPAR 0x054
+#define CC_CCPFSCR 0x058
+#define CC_CCPFECR 0x05C
+#define CC_CCPLSCR 0x060
+#define CC_CCPLECR 0x064
+#define CC_CCPDFR 0x068
+
+/* define camera dma register offsets */
+#define CAMDMA_REVISION 0x000
+#define CAMDMA_IRQSTATUS_L0 0x008
+#define CAMDMA_IRQSTATUS_L1 0x00C
+#define CAMDMA_IRQSTATUS_L2 0x010
+#define CAMDMA_IRQSTATUS_L3 0x014
+#define CAMDMA_IRQENABLE_L0 0x018
+#define CAMDMA_IRQENABLE_L1 0x01C
+#define CAMDMA_IRQENABLE_L2 0x020
+#define CAMDMA_IRQENABLE_L3 0x024
+#define CAMDMA_SYSSTATUS 0x028
+#define CAMDMA_OCP_SYSCONFIG 0x02C
+#define CAMDMA_CAPS_0 0x064
+#define CAMDMA_CAPS_2 0x06C
+#define CAMDMA_CAPS_3 0x070
+#define CAMDMA_CAPS_4 0x074
+#define CAMDMA_GCR 0x078
+#define CAMDMA_CCR(n) (0x080 + (n)*0x60)
+#define CAMDMA_CLNK_CTRL(n) (0x084 + (n)*0x60)
+#define CAMDMA_CICR(n) (0x088 + (n)*0x60)
+#define CAMDMA_CSR(n) (0x08C + (n)*0x60)
+#define CAMDMA_CSDP(n) (0x090 + (n)*0x60)
+#define CAMDMA_CEN(n) (0x094 + (n)*0x60)
+#define CAMDMA_CFN(n) (0x098 + (n)*0x60)
+#define CAMDMA_CSSA(n) (0x09C + (n)*0x60)
+#define CAMDMA_CDSA(n) (0x0A0 + (n)*0x60)
+#define CAMDMA_CSEI(n) (0x0A4 + (n)*0x60)
+#define CAMDMA_CSFI(n) (0x0A8 + (n)*0x60)
+#define CAMDMA_CDEI(n) (0x0AC + (n)*0x60)
+#define CAMDMA_CDFI(n) (0x0B0 + (n)*0x60)
+#define CAMDMA_CSAC(n) (0x0B4 + (n)*0x60)
+#define CAMDMA_CDAC(n) (0x0B8 + (n)*0x60)
+#define CAMDMA_CCEN(n) (0x0BC + (n)*0x60)
+#define CAMDMA_CCFN(n) (0x0C0 + (n)*0x60)
+#define CAMDMA_COLOR(n) (0x0C4 + (n)*0x60)
+
+/* define camera mmu register offsets */
+#define CAMMMU_REVISION 0x000
+#define CAMMMU_SYSCONFIG 0x010
+#define CAMMMU_SYSSTATUS 0x014
+#define CAMMMU_IRQSTATUS 0x018
+#define CAMMMU_IRQENABLE 0x01C
+#define CAMMMU_WALKING_ST 0x040
+#define CAMMMU_CNTL 0x044
+#define CAMMMU_FAULT_AD 0x048
+#define CAMMMU_TTB 0x04C
+#define CAMMMU_LOCK 0x050
+#define CAMMMU_LD_TLB 0x054
+#define CAMMMU_CAM 0x058
+#define CAMMMU_RAM 0x05C
+#define CAMMMU_GFLUSH 0x060
+#define CAMMMU_FLUSH_ENTRY 0x064
+#define CAMMMU_READ_CAM 0x068
+#define CAMMMU_READ_RAM 0x06C
+#define CAMMMU_EMU_FAULT_AD 0x070
+
+/* Define bit fields within selected registers */
+#define CAM_REVISION_MAJOR (15 << 4)
+#define CAM_REVISION_MAJOR_SHIFT 4
+#define CAM_REVISION_MINOR (15 << 0)
+#define CAM_REVISION_MINOR_SHIFT 0
+
+#define CAM_SYSCONFIG_SOFTRESET (1 << 1)
+#define CAM_SYSCONFIG_AUTOIDLE (1 << 0)
+
+#define CAM_SYSSTATUS_RESETDONE (1 << 0)
+
+#define CAM_IRQSTATUS_CC_IRQ (1 << 4)
+#define CAM_IRQSTATUS_MMU_IRQ (1 << 3)
+#define CAM_IRQSTATUS_DMA_IRQ2 (1 << 2)
+#define CAM_IRQSTATUS_DMA_IRQ1 (1 << 1)
+#define CAM_IRQSTATUS_DMA_IRQ0 (1 << 0)
+
+#define CAM_GPO_CAM_S_P_EN (1 << 1)
+#define CAM_GPO_CAM_CCP_MODE (1 << 0)
+
+#define CAM_GPI_CC_DMA_REQ1 (1 << 24)
+#define CAP_GPI_CC_DMA_REQ0 (1 << 23)
+#define CAP_GPI_CAM_MSTANDBY (1 << 21)
+#define CAP_GPI_CAM_WAIT (1 << 20)
+#define CAP_GPI_CAM_S_DATA (1 << 17)
+#define CAP_GPI_CAM_S_CLK (1 << 16)
+#define CAP_GPI_CAM_P_DATA (0xFFF << 3)
+#define CAP_GPI_CAM_P_DATA_SHIFT 3
+#define CAP_GPI_CAM_P_VS (1 << 2)
+#define CAP_GPI_CAM_P_HS (1 << 1)
+#define CAP_GPI_CAM_P_CLK (1 << 0)
+
+#define CC_REVISION_MAJOR (15 << 4)
+#define CC_REVISION_MAJOR_SHIFT 4
+#define CC_REVISION_MINOR (15 << 0)
+#define CC_REVISION_MINOR_SHIFT 0
+
+#define CC_SYSCONFIG_SIDLEMODE (3 << 3)
+#define CC_SYSCONFIG_SIDLEMODE_FIDLE (0 << 3)
+#define CC_SYSCONFIG_SIDLEMODE_NIDLE (1 << 3)
+#define CC_SYSCONFIG_SOFTRESET (1 << 1)
+#define CC_SYSCONFIG_AUTOIDLE (1 << 0)
+
+#define CC_SYSSTATUS_RESETDONE (1 << 0)
+
+#define CC_IRQSTATUS_FS_IRQ (1 << 19)
+#define CC_IRQSTATUS_LE_IRQ (1 << 18)
+#define CC_IRQSTATUS_LS_IRQ (1 << 17)
+#define CC_IRQSTATUS_FE_IRQ (1 << 16)
+#define CC_IRQSTATUS_FW_ERR_IRQ (1 << 10)
+#define CC_IRQSTATUS_FSC_ERR_IRQ (1 << 9)
+#define CC_IRQSTATUS_SSC_ERR_IRQ (1 << 8)
+#define CC_IRQSTATUS_FIFO_NOEMPTY_IRQ (1 << 4)
+#define CC_IRQSTATUS_FIFO_FULL_IRQ (1 << 3)
+#define CC_IRQSTATUS_FIFO_THR_IRQ (1 << 2)
+#define CC_IRQSTATUS_FIFO_OF_IRQ (1 << 1)
+#define CC_IRQSTATUS_FIFO_UF_IRQ (1 << 0)
+
+#define CC_IRQENABLE_FS_IRQ (1 << 19)
+#define CC_IRQENABLE_LE_IRQ (1 << 18)
+#define CC_IRQENABLE_LS_IRQ (1 << 17)
+#define CC_IRQENABLE_FE_IRQ (1 << 16)
+#define CC_IRQENABLE_FW_ERR_IRQ (1 << 10)
+#define CC_IRQENABLE_FSC_ERR_IRQ (1 << 9)
+#define CC_IRQENABLE_SSC_ERR_IRQ (1 << 8)
+#define CC_IRQENABLE_FIFO_NOEMPTY_IRQ (1 << 4)
+#define CC_IRQENABLE_FIFO_FULL_IRQ (1 << 3)
+#define CC_IRQENABLE_FIFO_THR_IRQ (1 << 2)
+#define CC_IRQENABLE_FIFO_OF_IRQ (1 << 1)
+#define CC_IRQENABLE_FIFO_UF_IRQ (1 << 0)
+
+#define CC_CTRL_CC_ONE_SHOT (1 << 20)
+#define CC_CTRL_CC_IF_SYNCHRO (1 << 19)
+#define CC_CTRL_CC_RST (1 << 18)
+#define CC_CTRL_CC_FRAME_TRIG (1 << 17)
+#define CC_CTRL_CC_EN (1 << 16)
+#define CC_CTRL_NOBT_SYNCHRO (1 << 13)
+#define CC_CTRL_BT_CORRECT (1 << 12)
+#define CC_CTRL_PAR_ORDERCAM (1 << 11)
+#define CC_CTRL_PAR_CLK_POL (1 << 10)
+#define CC_CTRL_NOBT_HS_POL (1 << 9)
+#define CC_CTRL_NOBT_VS_POL (1 << 8)
+#define CC_CTRL_PAR_MODE (7 << 1)
+#define CC_CTRL_PAR_MODE_SHIFT 1
+#define CC_CTRL_PAR_MODE_NOBT8 (0 << 1)
+#define CC_CTRL_PAR_MODE_NOBT10 (1 << 1)
+#define CC_CTRL_PAR_MODE_NOBT12 (2 << 1)
+#define CC_CTRL_PAR_MODE_BT8 (4 << 1)
+#define CC_CTRL_PAR_MODE_BT10 (5 << 1)
+#define CC_CTRL_PAR_MODE_FIFOTEST (7 << 1)
+#define CC_CTRL_CCP_MODE (1 << 0)
+
+#define CC_CTRL_DMA_EN (1 << 8)
+#define CC_CTRL_DMA_FIFO_THRESHOLD (0x7F << 0)
+#define CC_CTRL_DMA_FIFO_THRESHOLD_SHIFT 0
+
+#define CC_CTRL_XCLK_DIV (0x1F << 0)
+#define CC_CTRL_XCLK_DIV_SHIFT 0
+#define CC_CTRL_XCLK_DIV_STABLE_LOW (0 << 0)
+#define CC_CTRL_XCLK_DIV_STABLE_HIGH (1 << 0)
+#define CC_CTRL_XCLK_DIV_BYPASS (31 << 0)
+
+#define CC_TEST_FIFO_RD_POINTER (0xFF << 24)
+#define CC_TEST_FIFO_RD_POINTER_SHIFT 24
+#define CC_TEST_FIFO_WR_POINTER (0xFF << 16)
+#define CC_TEST_FIFO_WR_POINTER_SHIFT 16
+#define CC_TEST_FIFO_LEVEL (0xFF << 8)
+#define CC_TEST_FIFO_LEVEL_SHIFT 8
+#define CC_TEST_FIFO_LEVEL_PEAK (0xFF << 0)
+#define CC_TEST_FIFO_LEVEL_PEAK_SHIFT 0
+
+#define CC_GENPAR_FIFO_DEPTH (7 << 0)
+#define CC_GENPAR_FIFO_DEPTH_SHIFT 0
+
+#define CC_CCPDFR_ALPHA (0xFF << 8)
+#define CC_CCPDFR_ALPHA_SHIFT 8
+#define CC_CCPDFR_DATAFORMAT (15 << 0)
+#define CC_CCPDFR_DATAFORMAT_SHIFT 0
+#define CC_CCPDFR_DATAFORMAT_YUV422BE (0 << 0)
+#define CC_CCPDFR_DATAFORMAT_YUV422 (1 << 0)
+#define CC_CCPDFR_DATAFORMAT_YUV420 (2 << 0)
+#define CC_CCPDFR_DATAFORMAT_RGB444 (4 << 0)
+#define CC_CCPDFR_DATAFORMAT_RGB565 (5 << 0)
+#define CC_CCPDFR_DATAFORMAT_RGB888NDE (6 << 0)
+#define CC_CCPDFR_DATAFORMAT_RGB888 (7 << 0)
+#define CC_CCPDFR_DATAFORMAT_RAW8NDE (8 << 0)
+#define CC_CCPDFR_DATAFORMAT_RAW8 (9 << 0)
+#define CC_CCPDFR_DATAFORMAT_RAW10NDE (10 << 0)
+#define CC_CCPDFR_DATAFORMAT_RAW10 (11 << 0)
+#define CC_CCPDFR_DATAFORMAT_RAW12NDE (12 << 0)
+#define CC_CCPDFR_DATAFORMAT_RAW12 (13 << 0)
+#define CC_CCPDFR_DATAFORMAT_JPEG8 (15 << 0)
+
+#define CAMDMA_REVISION_MAJOR (15 << 4)
+#define CAMDMA_REVISION_MAJOR_SHIFT 4
+#define CAMDMA_REVISION_MINOR (15 << 0)
+#define CAMDMA_REVISION_MINOR_SHIFT 0
+
+#define CAMDMA_OCP_SYSCONFIG_MIDLEMODE (3 << 12)
+#define CAMDMA_OCP_SYSCONFIG_MIDLEMODE_FSTANDBY (0 << 12)
+#define CAMDMA_OCP_SYSCONFIG_MIDLEMODE_NSTANDBY (1 << 12)
+#define CAMDMA_OCP_SYSCONFIG_MIDLEMODE_SSTANDBY (2 << 12)
+#define CAMDMA_OCP_SYSCONFIG_FUNC_CLOCK (1 << 9)
+#define CAMDMA_OCP_SYSCONFIG_OCP_CLOCK (1 << 8)
+#define CAMDMA_OCP_SYSCONFIG_EMUFREE (1 << 5)
+#define CAMDMA_OCP_SYSCONFIG_SIDLEMODE (3 << 3)
+#define CAMDMA_OCP_SYSCONFIG_SIDLEMODE_FIDLE (0 << 3)
+#define CAMDMA_OCP_SYSCONFIG_SIDLEMODE_NIDLE (1 << 3)
+#define CAMDMA_OCP_SYSCONFIG_SIDLEMODE_SIDLE (2 << 3)
+#define CAMDMA_OCP_SYSCONFIG_SOFTRESET (1 << 1)
+#define CAMDMA_OCP_SYSCONFIG_AUTOIDLE (1 << 0)
+
+#define CAMDMA_SYSSTATUS_RESETDONE (1 << 0)
+
+#define CAMDMA_GCR_ARBITRATION_RATE (0xFF << 16)
+#define CAMDMA_GCR_ARBITRATION_RATE_SHIFT 16
+#define CAMDMA_GCR_MAX_CHANNEL_FIFO_DEPTH (0xFF << 0)
+#define CAMDMA_GCR_MAX_CHANNEL_FIFO_DEPTH_SHIFT 0
+
+#define CAMDMA_CCR_SEL_SRC_DST_SYNC (1 << 24)
+#define CAMDMA_CCR_PREFETCH (1 << 23)
+#define CAMDMA_CCR_SUPERVISOR (1 << 22)
+#define CAMDMA_CCR_SECURE (1 << 21)
+#define CAMDMA_CCR_BS (1 << 18)
+#define CAMDMA_CCR_TRANSPARENT_COPY_ENABLE (1 << 17)
+#define CAMDMA_CCR_CONSTANT_FILL_ENABLE (1 << 16)
+#define CAMDMA_CCR_DST_AMODE (3 << 14)
+#define CAMDMA_CCR_DST_AMODE_CONST_ADDR (0 << 14)
+#define CAMDMA_CCR_DST_AMODE_POST_INC (1 << 14)
+#define CAMDMA_CCR_DST_AMODE_SGL_IDX (2 << 14)
+#define CAMDMA_CCR_DST_AMODE_DBL_IDX (3 << 14)
+#define CAMDMA_CCR_SRC_AMODE (3 << 12)
+#define CAMDMA_CCR_SRC_AMODE_CONST_ADDR (0 << 12)
+#define CAMDMA_CCR_SRC_AMODE_POST_INC (1 << 12)
+#define CAMDMA_CCR_SRC_AMODE_SGL_IDX (2 << 12)
+#define CAMDMA_CCR_SRC_AMODE_DBL_IDX (3 << 12)
+#define CAMDMA_CCR_WR_ACTIVE (1 << 10)
+#define CAMDMA_CCR_RD_ACTIVE (1 << 9)
+#define CAMDMA_CCR_SUSPEND_SENSITIVE (1 << 8)
+#define CAMDMA_CCR_ENABLE (1 << 7)
+#define CAMDMA_CCR_PRIO (1 << 6)
+#define CAMDMA_CCR_FS (1 << 5)
+#define CAMDMA_CCR_SYNCHRO ((3 << 19) | (31 << 0))
+#define CAMDMA_CCR_SYNCHRO_CAMERA 0x01
+
+#define CAMDMA_CLNK_CTRL_ENABLE_LNK (1 << 15)
+#define CAMDMA_CLNK_CTRL_NEXTLCH_ID (0x1F << 0)
+#define CAMDMA_CLNK_CTRL_NEXTLCH_ID_SHIFT 0
+
+#define CAMDMA_CICR_MISALIGNED_ERR_IE (1 << 11)
+#define CAMDMA_CICR_SUPERVISOR_ERR_IE (1 << 10)
+#define CAMDMA_CICR_SECURE_ERR_IE (1 << 9)
+#define CAMDMA_CICR_TRANS_ERR_IE (1 << 8)
+#define CAMDMA_CICR_PACKET_IE (1 << 7)
+#define CAMDMA_CICR_BLOCK_IE (1 << 5)
+#define CAMDMA_CICR_LAST_IE (1 << 4)
+#define CAMDMA_CICR_FRAME_IE (1 << 3)
+#define CAMDMA_CICR_HALF_IE (1 << 2)
+#define CAMDMA_CICR_DROP_IE (1 << 1)
+
+#define CAMDMA_CSR_MISALIGNED_ERR (1 << 11)
+#define CAMDMA_CSR_SUPERVISOR_ERR (1 << 10)
+#define CAMDMA_CSR_SECURE_ERR (1 << 9)
+#define CAMDMA_CSR_TRANS_ERR (1 << 8)
+#define CAMDMA_CSR_PACKET (1 << 7)
+#define CAMDMA_CSR_SYNC (1 << 6)
+#define CAMDMA_CSR_BLOCK (1 << 5)
+#define CAMDMA_CSR_LAST (1 << 4)
+#define CAMDMA_CSR_FRAME (1 << 3)
+#define CAMDMA_CSR_HALF (1 << 2)
+#define CAMDMA_CSR_DROP (1 << 1)
+
+#define CAMDMA_CSDP_SRC_ENDIANNESS (1 << 21)
+#define CAMDMA_CSDP_SRC_ENDIANNESS_LOCK (1 << 20)
+#define CAMDMA_CSDP_DST_ENDIANNESS (1 << 19)
+#define CAMDMA_CSDP_DST_ENDIANNESS_LOCK (1 << 18)
+#define CAMDMA_CSDP_WRITE_MODE (3 << 16)
+#define CAMDMA_CSDP_WRITE_MODE_WRNP (0 << 16)
+#define CAMDMA_CSDP_WRITE_MODE_POSTED (1 << 16)
+#define CAMDMA_CSDP_WRITE_MODE_POSTED_LAST_WRNP (2 << 16)
+#define CAMDMA_CSDP_DST_BURST_EN (3 << 14)
+#define CAMDMA_CSDP_DST_BURST_EN_1 (0 << 14)
+#define CAMDMA_CSDP_DST_BURST_EN_16 (1 << 14)
+#define CAMDMA_CSDP_DST_BURST_EN_32 (2 << 14)
+#define CAMDMA_CSDP_DST_BURST_EN_64 (3 << 14)
+#define CAMDMA_CSDP_DST_PACKED (1 << 13)
+#define CAMDMA_CSDP_WR_ADD_TRSLT (15 << 9)
+#define CAMDMA_CSDP_WR_ADD_TRSLT_ENABLE_MREQADD (3 << 9)
+#define CAMDMA_CSDP_SRC_BURST_EN (3 << 7)
+#define CAMDMA_CSDP_SRC_BURST_EN_1 (0 << 7)
+#define CAMDMA_CSDP_SRC_BURST_EN_16 (1 << 7)
+#define CAMDMA_CSDP_SRC_BURST_EN_32 (2 << 7)
+#define CAMDMA_CSDP_SRC_BURST_EN_64 (3 << 7)
+#define CAMDMA_CSDP_SRC_PACKED (1 << 6)
+#define CAMDMA_CSDP_RD_ADD_TRSLT (15 << 2)
+#define CAMDMA_CSDP_RD_ADD_TRSLT_ENABLE_MREQADD (3 << 2)
+#define CAMDMA_CSDP_DATA_TYPE (3 << 0)
+#define CAMDMA_CSDP_DATA_TYPE_8BITS (0 << 0)
+#define CAMDMA_CSDP_DATA_TYPE_16BITS (1 << 0)
+#define CAMDMA_CSDP_DATA_TYPE_32BITS (2 << 0)
+
+#define CAMMMU_SYSCONFIG_AUTOIDLE (1 << 0)
+
+/*
+ *
+ * Declarations.
+ *
+ */
+
+/* forward declarations */
+struct omap24xxcam_sgdma;
+struct omap24xxcam_dma;
+
+typedef void (*sgdma_callback_t)(struct omap24xxcam_sgdma *cam,
+ u32 status, void *arg);
+typedef void (*dma_callback_t)(struct omap24xxcam_dma *cam,
+ u32 status, void *arg);
+
+struct channel_state {
+ dma_callback_t callback;
+ void *arg;
+};
+
+/* sgdma state for each of the possible videobuf_buffers + 2 overlays */
+struct sgdma_state {
+ const struct scatterlist *sglist;
+ int sglen; /* number of sglist entries */
+ int next_sglist; /* index of next sglist entry to process */
+ unsigned int bytes_read; /* number of bytes read */
+ unsigned int len; /* total length of sglist (excluding
+ * bytes due to page alignment) */
+ int queued_sglist; /* number of sglist entries queued for DMA */
+ u32 csr; /* DMA return code */
+ sgdma_callback_t callback;
+ void *arg;
+};
+
+/* physical DMA channel management */
+struct omap24xxcam_dma {
+ spinlock_t lock; /* Lock for the whole structure. */
+
+ void __iomem *base; /* base address for dma controller */
+
+ /* While dma_stop!=0, an attempt to start a new DMA transfer will
+ * fail.
+ */
+ atomic_t dma_stop;
+ int free_dmach; /* number of dma channels free */
+ int next_dmach; /* index of next dma channel to use */
+ struct channel_state ch_state[NUM_CAMDMA_CHANNELS];
+};
+
+/* scatter-gather DMA (scatterlist stuff) management */
+struct omap24xxcam_sgdma {
+ struct omap24xxcam_dma dma;
+
+ spinlock_t lock; /* Lock for the fields below. */
+ int free_sgdma; /* number of free sg dma slots */
+ int next_sgdma; /* index of next sg dma slot to use */
+ struct sgdma_state sg_state[NUM_SG_DMA];
+
+ /* Reset timer data */
+ struct timer_list reset_timer;
+};
+
+/* per-device data structure */
+struct omap24xxcam_device {
+ /*** mutex ***/
+ /*
+ * mutex serialises access to this structure. Also camera
+ * opening and releasing is synchronised by this.
+ */
+ struct mutex mutex;
+
+ struct v4l2_device v4l2_dev;
+
+ /*** general driver state information ***/
+ atomic_t users;
+ /*
+ * Lock to serialise core enabling and disabling and access to
+ * sgdma_in_queue.
+ */
+ spinlock_t core_enable_disable_lock;
+ /*
+ * Number or sgdma requests in scatter-gather queue, protected
+ * by the lock above.
+ */
+ int sgdma_in_queue;
+ /*
+ * Sensor interface parameters: interface type, CC_CTRL
+ * register value and interface specific data.
+ */
+ int if_type;
+ union {
+ struct parallel {
+ u32 xclk;
+ } bt656;
+ } if_u;
+ u32 cc_ctrl;
+
+ /*** subsystem structures ***/
+ struct omap24xxcam_sgdma sgdma;
+
+ /*** hardware resources ***/
+ unsigned int irq;
+ void __iomem *mmio_base;
+ unsigned long mmio_base_phys;
+ unsigned long mmio_size;
+
+ /*** interfaces and device ***/
+ struct v4l2_int_device *sdev;
+ struct device *dev;
+ struct video_device *vfd;
+
+ /*** camera and sensor reset related stuff ***/
+ struct work_struct sensor_reset_work;
+ /*
+ * We're in the middle of a reset. Don't enable core if this
+ * is non-zero! This exists to help decisionmaking in a case
+ * where videobuf_qbuf is called while we are in the middle of
+ * a reset.
+ */
+ atomic_t in_reset;
+ /*
+ * Non-zero if we don't want any resets for now. Used to
+ * prevent reset work to run when we're about to stop
+ * streaming.
+ */
+ atomic_t reset_disable;
+
+ /*** video device parameters ***/
+ int capture_mem;
+
+ /*** camera module clocks ***/
+ struct clk *fck;
+ struct clk *ick;
+
+ /*** capture data ***/
+ /* file handle, if streaming is on */
+ struct file *streaming;
+};
+
+/* Per-file handle data. */
+struct omap24xxcam_fh {
+ spinlock_t vbq_lock; /* spinlock for the videobuf queue */
+ struct videobuf_queue vbq;
+ struct v4l2_pix_format pix; /* serialise pix by vbq->lock */
+ atomic_t field_count; /* field counter for videobuf_buffer */
+ /* accessing cam here doesn't need serialisation: it's constant */
+ struct omap24xxcam_device *cam;
+};
+
+/*
+ *
+ * Register I/O functions.
+ *
+ */
+
+static inline u32 omap24xxcam_reg_in(u32 __iomem *base, u32 offset)
+{
+ return readl(base + offset);
+}
+
+static inline u32 omap24xxcam_reg_out(u32 __iomem *base, u32 offset,
+ u32 val)
+{
+ writel(val, base + offset);
+ return val;
+}
+
+static inline u32 omap24xxcam_reg_merge(u32 __iomem *base, u32 offset,
+ u32 val, u32 mask)
+{
+ u32 __iomem *addr = base + offset;
+ u32 new_val = (readl(addr) & ~mask) | (val & mask);
+
+ writel(new_val, addr);
+ return new_val;
+}
+
+/*
+ *
+ * Function prototypes.
+ *
+ */
+
+/* dma prototypes */
+
+void omap24xxcam_dma_hwinit(struct omap24xxcam_dma *dma);
+void omap24xxcam_dma_isr(struct omap24xxcam_dma *dma);
+
+/* sgdma prototypes */
+
+void omap24xxcam_sgdma_process(struct omap24xxcam_sgdma *sgdma);
+int omap24xxcam_sgdma_queue(struct omap24xxcam_sgdma *sgdma,
+ const struct scatterlist *sglist, int sglen,
+ int len, sgdma_callback_t callback, void *arg);
+void omap24xxcam_sgdma_sync(struct omap24xxcam_sgdma *sgdma);
+void omap24xxcam_sgdma_init(struct omap24xxcam_sgdma *sgdma,
+ void __iomem *base,
+ void (*reset_callback)(unsigned long data),
+ unsigned long reset_callback_data);
+void omap24xxcam_sgdma_exit(struct omap24xxcam_sgdma *sgdma);
+
+#endif
diff --git a/drivers/staging/media/omap24xx/tcm825x.c b/drivers/staging/media/omap24xx/tcm825x.c
new file mode 100644
index 00000000000..3367ccd1d1e
--- /dev/null
+++ b/drivers/staging/media/omap24xx/tcm825x.c
@@ -0,0 +1,937 @@
+/*
+ * drivers/media/i2c/tcm825x.c
+ *
+ * TCM825X camera sensor driver.
+ *
+ * Copyright (C) 2007 Nokia Corporation.
+ *
+ * Contact: Sakari Ailus <sakari.ailus@nokia.com>
+ *
+ * Based on code from David Cohen <david.cohen@indt.org.br>
+ *
+ * This driver was based on ov9640 sensor driver from MontaVista
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ */
+
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include "v4l2-int-device.h"
+
+#include "tcm825x.h"
+
+/*
+ * The sensor has two fps modes: the lower one just gives half the fps
+ * at the same xclk than the high one.
+ */
+#define MAX_FPS 30
+#define MIN_FPS 8
+#define MAX_HALF_FPS (MAX_FPS / 2)
+#define HIGH_FPS_MODE_LOWER_LIMIT 14
+#define DEFAULT_FPS MAX_HALF_FPS
+
+struct tcm825x_sensor {
+ const struct tcm825x_platform_data *platform_data;
+ struct v4l2_int_device *v4l2_int_device;
+ struct i2c_client *i2c_client;
+ struct v4l2_pix_format pix;
+ struct v4l2_fract timeperframe;
+};
+
+/* list of image formats supported by TCM825X sensor */
+static const struct v4l2_fmtdesc tcm825x_formats[] = {
+ {
+ .description = "YUYV (YUV 4:2:2), packed",
+ .pixelformat = V4L2_PIX_FMT_UYVY,
+ }, {
+ /* Note: V4L2 defines RGB565 as:
+ *
+ * Byte 0 Byte 1
+ * g2 g1 g0 r4 r3 r2 r1 r0 b4 b3 b2 b1 b0 g5 g4 g3
+ *
+ * We interpret RGB565 as:
+ *
+ * Byte 0 Byte 1
+ * g2 g1 g0 b4 b3 b2 b1 b0 r4 r3 r2 r1 r0 g5 g4 g3
+ */
+ .description = "RGB565, le",
+ .pixelformat = V4L2_PIX_FMT_RGB565,
+ },
+};
+
+#define TCM825X_NUM_CAPTURE_FORMATS ARRAY_SIZE(tcm825x_formats)
+
+/*
+ * TCM825X register configuration for all combinations of pixel format and
+ * image size
+ */
+static const struct tcm825x_reg subqcif = { 0x20, TCM825X_PICSIZ };
+static const struct tcm825x_reg qcif = { 0x18, TCM825X_PICSIZ };
+static const struct tcm825x_reg cif = { 0x14, TCM825X_PICSIZ };
+static const struct tcm825x_reg qqvga = { 0x0c, TCM825X_PICSIZ };
+static const struct tcm825x_reg qvga = { 0x04, TCM825X_PICSIZ };
+static const struct tcm825x_reg vga = { 0x00, TCM825X_PICSIZ };
+
+static const struct tcm825x_reg yuv422 = { 0x00, TCM825X_PICFMT };
+static const struct tcm825x_reg rgb565 = { 0x02, TCM825X_PICFMT };
+
+/* Our own specific controls */
+#define V4L2_CID_ALC V4L2_CID_PRIVATE_BASE
+#define V4L2_CID_H_EDGE_EN (V4L2_CID_PRIVATE_BASE + 1)
+#define V4L2_CID_V_EDGE_EN (V4L2_CID_PRIVATE_BASE + 2)
+#define V4L2_CID_LENS (V4L2_CID_PRIVATE_BASE + 3)
+#define V4L2_CID_MAX_EXPOSURE_TIME (V4L2_CID_PRIVATE_BASE + 4)
+#define V4L2_CID_LAST_PRIV V4L2_CID_MAX_EXPOSURE_TIME
+
+/* Video controls */
+static struct vcontrol {
+ struct v4l2_queryctrl qc;
+ u16 reg;
+ u16 start_bit;
+} video_control[] = {
+ {
+ {
+ .id = V4L2_CID_GAIN,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "Gain",
+ .minimum = 0,
+ .maximum = 63,
+ .step = 1,
+ },
+ .reg = TCM825X_AG,
+ .start_bit = 0,
+ },
+ {
+ {
+ .id = V4L2_CID_RED_BALANCE,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "Red Balance",
+ .minimum = 0,
+ .maximum = 255,
+ .step = 1,
+ },
+ .reg = TCM825X_MRG,
+ .start_bit = 0,
+ },
+ {
+ {
+ .id = V4L2_CID_BLUE_BALANCE,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "Blue Balance",
+ .minimum = 0,
+ .maximum = 255,
+ .step = 1,
+ },
+ .reg = TCM825X_MBG,
+ .start_bit = 0,
+ },
+ {
+ {
+ .id = V4L2_CID_AUTO_WHITE_BALANCE,
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ .name = "Auto White Balance",
+ .minimum = 0,
+ .maximum = 1,
+ .step = 0,
+ },
+ .reg = TCM825X_AWBSW,
+ .start_bit = 7,
+ },
+ {
+ {
+ .id = V4L2_CID_EXPOSURE,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "Exposure Time",
+ .minimum = 0,
+ .maximum = 0x1fff,
+ .step = 1,
+ },
+ .reg = TCM825X_ESRSPD_U,
+ .start_bit = 0,
+ },
+ {
+ {
+ .id = V4L2_CID_HFLIP,
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ .name = "Mirror Image",
+ .minimum = 0,
+ .maximum = 1,
+ .step = 0,
+ },
+ .reg = TCM825X_H_INV,
+ .start_bit = 6,
+ },
+ {
+ {
+ .id = V4L2_CID_VFLIP,
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ .name = "Vertical Flip",
+ .minimum = 0,
+ .maximum = 1,
+ .step = 0,
+ },
+ .reg = TCM825X_V_INV,
+ .start_bit = 7,
+ },
+ /* Private controls */
+ {
+ {
+ .id = V4L2_CID_ALC,
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ .name = "Auto Luminance Control",
+ .minimum = 0,
+ .maximum = 1,
+ .step = 0,
+ },
+ .reg = TCM825X_ALCSW,
+ .start_bit = 7,
+ },
+ {
+ {
+ .id = V4L2_CID_H_EDGE_EN,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "Horizontal Edge Enhancement",
+ .minimum = 0,
+ .maximum = 0xff,
+ .step = 1,
+ },
+ .reg = TCM825X_HDTG,
+ .start_bit = 0,
+ },
+ {
+ {
+ .id = V4L2_CID_V_EDGE_EN,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "Vertical Edge Enhancement",
+ .minimum = 0,
+ .maximum = 0xff,
+ .step = 1,
+ },
+ .reg = TCM825X_VDTG,
+ .start_bit = 0,
+ },
+ {
+ {
+ .id = V4L2_CID_LENS,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "Lens Shading Compensation",
+ .minimum = 0,
+ .maximum = 0x3f,
+ .step = 1,
+ },
+ .reg = TCM825X_LENS,
+ .start_bit = 0,
+ },
+ {
+ {
+ .id = V4L2_CID_MAX_EXPOSURE_TIME,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "Maximum Exposure Time",
+ .minimum = 0,
+ .maximum = 0x3,
+ .step = 1,
+ },
+ .reg = TCM825X_ESRLIM,
+ .start_bit = 5,
+ },
+};
+
+
+static const struct tcm825x_reg *tcm825x_siz_reg[NUM_IMAGE_SIZES] = {
+ &subqcif, &qqvga, &qcif, &qvga, &cif, &vga };
+
+static const struct tcm825x_reg *tcm825x_fmt_reg[NUM_PIXEL_FORMATS] = {
+ &yuv422, &rgb565 };
+
+/*
+ * Read a value from a register in an TCM825X sensor device. The value is
+ * returned in 'val'.
+ * Returns zero if successful, or non-zero otherwise.
+ */
+static int tcm825x_read_reg(struct i2c_client *client, int reg)
+{
+ int err;
+ struct i2c_msg msg[2];
+ u8 reg_buf, data_buf = 0;
+
+ if (!client->adapter)
+ return -ENODEV;
+
+ msg[0].addr = client->addr;
+ msg[0].flags = 0;
+ msg[0].len = 1;
+ msg[0].buf = &reg_buf;
+ msg[1].addr = client->addr;
+ msg[1].flags = I2C_M_RD;
+ msg[1].len = 1;
+ msg[1].buf = &data_buf;
+
+ reg_buf = reg;
+
+ err = i2c_transfer(client->adapter, msg, 2);
+ if (err < 0)
+ return err;
+ return data_buf;
+}
+
+/*
+ * Write a value to a register in an TCM825X sensor device.
+ * Returns zero if successful, or non-zero otherwise.
+ */
+static int tcm825x_write_reg(struct i2c_client *client, u8 reg, u8 val)
+{
+ int err;
+ struct i2c_msg msg[1];
+ unsigned char data[2];
+
+ if (!client->adapter)
+ return -ENODEV;
+
+ msg->addr = client->addr;
+ msg->flags = 0;
+ msg->len = 2;
+ msg->buf = data;
+ data[0] = reg;
+ data[1] = val;
+ err = i2c_transfer(client->adapter, msg, 1);
+ if (err >= 0)
+ return 0;
+ return err;
+}
+
+static int __tcm825x_write_reg_mask(struct i2c_client *client,
+ u8 reg, u8 val, u8 mask)
+{
+ int rc;
+
+ /* need to do read - modify - write */
+ rc = tcm825x_read_reg(client, reg);
+ if (rc < 0)
+ return rc;
+
+ rc &= (~mask); /* Clear the masked bits */
+ val &= mask; /* Enforce mask on value */
+ val |= rc;
+
+ /* write the new value to the register */
+ rc = tcm825x_write_reg(client, reg, val);
+ if (rc)
+ return rc;
+
+ return 0;
+}
+
+#define tcm825x_write_reg_mask(client, regmask, val) \
+ __tcm825x_write_reg_mask(client, TCM825X_ADDR((regmask)), val, \
+ TCM825X_MASK((regmask)))
+
+
+/*
+ * Initialize a list of TCM825X registers.
+ * The list of registers is terminated by the pair of values
+ * { TCM825X_REG_TERM, TCM825X_VAL_TERM }.
+ * Returns zero if successful, or non-zero otherwise.
+ */
+static int tcm825x_write_default_regs(struct i2c_client *client,
+ const struct tcm825x_reg *reglist)
+{
+ int err;
+ const struct tcm825x_reg *next = reglist;
+
+ while (!((next->reg == TCM825X_REG_TERM)
+ && (next->val == TCM825X_VAL_TERM))) {
+ err = tcm825x_write_reg(client, next->reg, next->val);
+ if (err) {
+ dev_err(&client->dev, "register writing failed\n");
+ return err;
+ }
+ next++;
+ }
+
+ return 0;
+}
+
+static struct vcontrol *find_vctrl(int id)
+{
+ int i;
+
+ if (id < V4L2_CID_BASE)
+ return NULL;
+
+ for (i = 0; i < ARRAY_SIZE(video_control); i++)
+ if (video_control[i].qc.id == id)
+ return &video_control[i];
+
+ return NULL;
+}
+
+/*
+ * Find the best match for a requested image capture size. The best match
+ * is chosen as the nearest match that has the same number or fewer pixels
+ * as the requested size, or the smallest image size if the requested size
+ * has fewer pixels than the smallest image.
+ */
+static enum image_size tcm825x_find_size(struct v4l2_int_device *s,
+ unsigned int width,
+ unsigned int height)
+{
+ enum image_size isize;
+ unsigned long pixels = width * height;
+ struct tcm825x_sensor *sensor = s->priv;
+
+ for (isize = subQCIF; isize < VGA; isize++) {
+ if (tcm825x_sizes[isize + 1].height
+ * tcm825x_sizes[isize + 1].width > pixels) {
+ dev_dbg(&sensor->i2c_client->dev, "size %d\n", isize);
+
+ return isize;
+ }
+ }
+
+ dev_dbg(&sensor->i2c_client->dev, "format default VGA\n");
+
+ return VGA;
+}
+
+/*
+ * Configure the TCM825X for current image size, pixel format, and
+ * frame period. fper is the frame period (in seconds) expressed as a
+ * fraction. Returns zero if successful, or non-zero otherwise. The
+ * actual frame period is returned in fper.
+ */
+static int tcm825x_configure(struct v4l2_int_device *s)
+{
+ struct tcm825x_sensor *sensor = s->priv;
+ struct v4l2_pix_format *pix = &sensor->pix;
+ enum image_size isize = tcm825x_find_size(s, pix->width, pix->height);
+ struct v4l2_fract *fper = &sensor->timeperframe;
+ enum pixel_format pfmt;
+ int err;
+ u32 tgt_fps;
+ u8 val;
+
+ /* common register initialization */
+ err = tcm825x_write_default_regs(
+ sensor->i2c_client, sensor->platform_data->default_regs());
+ if (err)
+ return err;
+
+ /* configure image size */
+ val = tcm825x_siz_reg[isize]->val;
+ dev_dbg(&sensor->i2c_client->dev,
+ "configuring image size %d\n", isize);
+ err = tcm825x_write_reg_mask(sensor->i2c_client,
+ tcm825x_siz_reg[isize]->reg, val);
+ if (err)
+ return err;
+
+ /* configure pixel format */
+ switch (pix->pixelformat) {
+ default:
+ case V4L2_PIX_FMT_RGB565:
+ pfmt = RGB565;
+ break;
+ case V4L2_PIX_FMT_UYVY:
+ pfmt = YUV422;
+ break;
+ }
+
+ dev_dbg(&sensor->i2c_client->dev,
+ "configuring pixel format %d\n", pfmt);
+ val = tcm825x_fmt_reg[pfmt]->val;
+
+ err = tcm825x_write_reg_mask(sensor->i2c_client,
+ tcm825x_fmt_reg[pfmt]->reg, val);
+ if (err)
+ return err;
+
+ /*
+ * For frame rate < 15, the FPS reg (addr 0x02, bit 7) must be
+ * set. Frame rate will be halved from the normal.
+ */
+ tgt_fps = fper->denominator / fper->numerator;
+ if (tgt_fps <= HIGH_FPS_MODE_LOWER_LIMIT) {
+ val = tcm825x_read_reg(sensor->i2c_client, 0x02);
+ val |= 0x80;
+ tcm825x_write_reg(sensor->i2c_client, 0x02, val);
+ }
+
+ return 0;
+}
+
+static int ioctl_queryctrl(struct v4l2_int_device *s,
+ struct v4l2_queryctrl *qc)
+{
+ struct vcontrol *control;
+
+ control = find_vctrl(qc->id);
+
+ if (control == NULL)
+ return -EINVAL;
+
+ *qc = control->qc;
+
+ return 0;
+}
+
+static int ioctl_g_ctrl(struct v4l2_int_device *s,
+ struct v4l2_control *vc)
+{
+ struct tcm825x_sensor *sensor = s->priv;
+ struct i2c_client *client = sensor->i2c_client;
+ int val, r;
+ struct vcontrol *lvc;
+
+ /* exposure time is special, spread across 2 registers */
+ if (vc->id == V4L2_CID_EXPOSURE) {
+ int val_lower, val_upper;
+
+ val_upper = tcm825x_read_reg(client,
+ TCM825X_ADDR(TCM825X_ESRSPD_U));
+ if (val_upper < 0)
+ return val_upper;
+ val_lower = tcm825x_read_reg(client,
+ TCM825X_ADDR(TCM825X_ESRSPD_L));
+ if (val_lower < 0)
+ return val_lower;
+
+ vc->value = ((val_upper & 0x1f) << 8) | (val_lower);
+ return 0;
+ }
+
+ lvc = find_vctrl(vc->id);
+ if (lvc == NULL)
+ return -EINVAL;
+
+ r = tcm825x_read_reg(client, TCM825X_ADDR(lvc->reg));
+ if (r < 0)
+ return r;
+ val = r & TCM825X_MASK(lvc->reg);
+ val >>= lvc->start_bit;
+
+ if (val < 0)
+ return val;
+
+ if (vc->id == V4L2_CID_HFLIP || vc->id == V4L2_CID_VFLIP)
+ val ^= sensor->platform_data->is_upside_down();
+
+ vc->value = val;
+ return 0;
+}
+
+static int ioctl_s_ctrl(struct v4l2_int_device *s,
+ struct v4l2_control *vc)
+{
+ struct tcm825x_sensor *sensor = s->priv;
+ struct i2c_client *client = sensor->i2c_client;
+ struct vcontrol *lvc;
+ int val = vc->value;
+
+ /* exposure time is special, spread across 2 registers */
+ if (vc->id == V4L2_CID_EXPOSURE) {
+ int val_lower, val_upper;
+ val_lower = val & TCM825X_MASK(TCM825X_ESRSPD_L);
+ val_upper = (val >> 8) & TCM825X_MASK(TCM825X_ESRSPD_U);
+
+ if (tcm825x_write_reg_mask(client,
+ TCM825X_ESRSPD_U, val_upper))
+ return -EIO;
+
+ if (tcm825x_write_reg_mask(client,
+ TCM825X_ESRSPD_L, val_lower))
+ return -EIO;
+
+ return 0;
+ }
+
+ lvc = find_vctrl(vc->id);
+ if (lvc == NULL)
+ return -EINVAL;
+
+ if (vc->id == V4L2_CID_HFLIP || vc->id == V4L2_CID_VFLIP)
+ val ^= sensor->platform_data->is_upside_down();
+
+ val = val << lvc->start_bit;
+ if (tcm825x_write_reg_mask(client, lvc->reg, val))
+ return -EIO;
+
+ return 0;
+}
+
+static int ioctl_enum_fmt_cap(struct v4l2_int_device *s,
+ struct v4l2_fmtdesc *fmt)
+{
+ int index = fmt->index;
+
+ switch (fmt->type) {
+ case V4L2_BUF_TYPE_VIDEO_CAPTURE:
+ if (index >= TCM825X_NUM_CAPTURE_FORMATS)
+ return -EINVAL;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ fmt->flags = tcm825x_formats[index].flags;
+ strlcpy(fmt->description, tcm825x_formats[index].description,
+ sizeof(fmt->description));
+ fmt->pixelformat = tcm825x_formats[index].pixelformat;
+
+ return 0;
+}
+
+static int ioctl_try_fmt_cap(struct v4l2_int_device *s,
+ struct v4l2_format *f)
+{
+ struct tcm825x_sensor *sensor = s->priv;
+ enum image_size isize;
+ int ifmt;
+ struct v4l2_pix_format *pix = &f->fmt.pix;
+
+ isize = tcm825x_find_size(s, pix->width, pix->height);
+ dev_dbg(&sensor->i2c_client->dev, "isize = %d num_capture = %lu\n",
+ isize, (unsigned long)TCM825X_NUM_CAPTURE_FORMATS);
+
+ pix->width = tcm825x_sizes[isize].width;
+ pix->height = tcm825x_sizes[isize].height;
+
+ for (ifmt = 0; ifmt < TCM825X_NUM_CAPTURE_FORMATS; ifmt++)
+ if (pix->pixelformat == tcm825x_formats[ifmt].pixelformat)
+ break;
+
+ if (ifmt == TCM825X_NUM_CAPTURE_FORMATS)
+ ifmt = 0; /* Default = YUV 4:2:2 */
+
+ pix->pixelformat = tcm825x_formats[ifmt].pixelformat;
+ pix->field = V4L2_FIELD_NONE;
+ pix->bytesperline = pix->width * TCM825X_BYTES_PER_PIXEL;
+ pix->sizeimage = pix->bytesperline * pix->height;
+ pix->priv = 0;
+ dev_dbg(&sensor->i2c_client->dev, "format = 0x%08x\n",
+ pix->pixelformat);
+
+ switch (pix->pixelformat) {
+ case V4L2_PIX_FMT_UYVY:
+ default:
+ pix->colorspace = V4L2_COLORSPACE_JPEG;
+ break;
+ case V4L2_PIX_FMT_RGB565:
+ pix->colorspace = V4L2_COLORSPACE_SRGB;
+ break;
+ }
+
+ return 0;
+}
+
+static int ioctl_s_fmt_cap(struct v4l2_int_device *s,
+ struct v4l2_format *f)
+{
+ struct tcm825x_sensor *sensor = s->priv;
+ struct v4l2_pix_format *pix = &f->fmt.pix;
+ int rval;
+
+ rval = ioctl_try_fmt_cap(s, f);
+ if (rval)
+ return rval;
+
+ rval = tcm825x_configure(s);
+
+ sensor->pix = *pix;
+
+ return rval;
+}
+
+static int ioctl_g_fmt_cap(struct v4l2_int_device *s,
+ struct v4l2_format *f)
+{
+ struct tcm825x_sensor *sensor = s->priv;
+
+ f->fmt.pix = sensor->pix;
+
+ return 0;
+}
+
+static int ioctl_g_parm(struct v4l2_int_device *s,
+ struct v4l2_streamparm *a)
+{
+ struct tcm825x_sensor *sensor = s->priv;
+ struct v4l2_captureparm *cparm = &a->parm.capture;
+
+ if (a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+ return -EINVAL;
+
+ memset(a, 0, sizeof(*a));
+ a->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+
+ cparm->capability = V4L2_CAP_TIMEPERFRAME;
+ cparm->timeperframe = sensor->timeperframe;
+
+ return 0;
+}
+
+static int ioctl_s_parm(struct v4l2_int_device *s,
+ struct v4l2_streamparm *a)
+{
+ struct tcm825x_sensor *sensor = s->priv;
+ struct v4l2_fract *timeperframe = &a->parm.capture.timeperframe;
+ u32 tgt_fps; /* target frames per secound */
+ int rval;
+
+ if (a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+ return -EINVAL;
+
+ if ((timeperframe->numerator == 0)
+ || (timeperframe->denominator == 0)) {
+ timeperframe->denominator = DEFAULT_FPS;
+ timeperframe->numerator = 1;
+ }
+
+ tgt_fps = timeperframe->denominator / timeperframe->numerator;
+
+ if (tgt_fps > MAX_FPS) {
+ timeperframe->denominator = MAX_FPS;
+ timeperframe->numerator = 1;
+ } else if (tgt_fps < MIN_FPS) {
+ timeperframe->denominator = MIN_FPS;
+ timeperframe->numerator = 1;
+ }
+
+ sensor->timeperframe = *timeperframe;
+
+ rval = tcm825x_configure(s);
+
+ return rval;
+}
+
+static int ioctl_s_power(struct v4l2_int_device *s, int on)
+{
+ struct tcm825x_sensor *sensor = s->priv;
+
+ return sensor->platform_data->power_set(on);
+}
+
+/*
+ * Given the image capture format in pix, the nominal frame period in
+ * timeperframe, calculate the required xclk frequency.
+ *
+ * TCM825X input frequency characteristics are:
+ * Minimum 11.9 MHz, Typical 24.57 MHz and maximum 25/27 MHz
+ */
+
+static int ioctl_g_ifparm(struct v4l2_int_device *s, struct v4l2_ifparm *p)
+{
+ struct tcm825x_sensor *sensor = s->priv;
+ struct v4l2_fract *timeperframe = &sensor->timeperframe;
+ u32 tgt_xclk; /* target xclk */
+ u32 tgt_fps; /* target frames per secound */
+ int rval;
+
+ rval = sensor->platform_data->ifparm(p);
+ if (rval)
+ return rval;
+
+ tgt_fps = timeperframe->denominator / timeperframe->numerator;
+
+ tgt_xclk = (tgt_fps <= HIGH_FPS_MODE_LOWER_LIMIT) ?
+ (2457 * tgt_fps) / MAX_HALF_FPS :
+ (2457 * tgt_fps) / MAX_FPS;
+ tgt_xclk *= 10000;
+
+ tgt_xclk = min(tgt_xclk, (u32)TCM825X_XCLK_MAX);
+ tgt_xclk = max(tgt_xclk, (u32)TCM825X_XCLK_MIN);
+
+ p->u.bt656.clock_curr = tgt_xclk;
+
+ return 0;
+}
+
+static int ioctl_g_needs_reset(struct v4l2_int_device *s, void *buf)
+{
+ struct tcm825x_sensor *sensor = s->priv;
+
+ return sensor->platform_data->needs_reset(s, buf, &sensor->pix);
+}
+
+static int ioctl_reset(struct v4l2_int_device *s)
+{
+ return -EBUSY;
+}
+
+static int ioctl_init(struct v4l2_int_device *s)
+{
+ return tcm825x_configure(s);
+}
+
+static int ioctl_dev_exit(struct v4l2_int_device *s)
+{
+ return 0;
+}
+
+static int ioctl_dev_init(struct v4l2_int_device *s)
+{
+ struct tcm825x_sensor *sensor = s->priv;
+ int r;
+
+ r = tcm825x_read_reg(sensor->i2c_client, 0x01);
+ if (r < 0)
+ return r;
+ if (r == 0) {
+ dev_err(&sensor->i2c_client->dev, "device not detected\n");
+ return -EIO;
+ }
+ return 0;
+}
+
+static struct v4l2_int_ioctl_desc tcm825x_ioctl_desc[] = {
+ { vidioc_int_dev_init_num,
+ (v4l2_int_ioctl_func *)ioctl_dev_init },
+ { vidioc_int_dev_exit_num,
+ (v4l2_int_ioctl_func *)ioctl_dev_exit },
+ { vidioc_int_s_power_num,
+ (v4l2_int_ioctl_func *)ioctl_s_power },
+ { vidioc_int_g_ifparm_num,
+ (v4l2_int_ioctl_func *)ioctl_g_ifparm },
+ { vidioc_int_g_needs_reset_num,
+ (v4l2_int_ioctl_func *)ioctl_g_needs_reset },
+ { vidioc_int_reset_num,
+ (v4l2_int_ioctl_func *)ioctl_reset },
+ { vidioc_int_init_num,
+ (v4l2_int_ioctl_func *)ioctl_init },
+ { vidioc_int_enum_fmt_cap_num,
+ (v4l2_int_ioctl_func *)ioctl_enum_fmt_cap },
+ { vidioc_int_try_fmt_cap_num,
+ (v4l2_int_ioctl_func *)ioctl_try_fmt_cap },
+ { vidioc_int_g_fmt_cap_num,
+ (v4l2_int_ioctl_func *)ioctl_g_fmt_cap },
+ { vidioc_int_s_fmt_cap_num,
+ (v4l2_int_ioctl_func *)ioctl_s_fmt_cap },
+ { vidioc_int_g_parm_num,
+ (v4l2_int_ioctl_func *)ioctl_g_parm },
+ { vidioc_int_s_parm_num,
+ (v4l2_int_ioctl_func *)ioctl_s_parm },
+ { vidioc_int_queryctrl_num,
+ (v4l2_int_ioctl_func *)ioctl_queryctrl },
+ { vidioc_int_g_ctrl_num,
+ (v4l2_int_ioctl_func *)ioctl_g_ctrl },
+ { vidioc_int_s_ctrl_num,
+ (v4l2_int_ioctl_func *)ioctl_s_ctrl },
+};
+
+static struct v4l2_int_slave tcm825x_slave = {
+ .ioctls = tcm825x_ioctl_desc,
+ .num_ioctls = ARRAY_SIZE(tcm825x_ioctl_desc),
+};
+
+static struct tcm825x_sensor tcm825x;
+
+static struct v4l2_int_device tcm825x_int_device = {
+ .module = THIS_MODULE,
+ .name = TCM825X_NAME,
+ .priv = &tcm825x,
+ .type = v4l2_int_type_slave,
+ .u = {
+ .slave = &tcm825x_slave,
+ },
+};
+
+static int tcm825x_probe(struct i2c_client *client,
+ const struct i2c_device_id *did)
+{
+ struct tcm825x_sensor *sensor = &tcm825x;
+
+ if (i2c_get_clientdata(client))
+ return -EBUSY;
+
+ sensor->platform_data = client->dev.platform_data;
+
+ if (sensor->platform_data == NULL
+ || !sensor->platform_data->is_okay())
+ return -ENODEV;
+
+ sensor->v4l2_int_device = &tcm825x_int_device;
+
+ sensor->i2c_client = client;
+ i2c_set_clientdata(client, sensor);
+
+ /* Make the default capture format QVGA RGB565 */
+ sensor->pix.width = tcm825x_sizes[QVGA].width;
+ sensor->pix.height = tcm825x_sizes[QVGA].height;
+ sensor->pix.pixelformat = V4L2_PIX_FMT_RGB565;
+
+ return v4l2_int_device_register(sensor->v4l2_int_device);
+}
+
+static int tcm825x_remove(struct i2c_client *client)
+{
+ struct tcm825x_sensor *sensor = i2c_get_clientdata(client);
+
+ if (!client->adapter)
+ return -ENODEV; /* our client isn't attached */
+
+ v4l2_int_device_unregister(sensor->v4l2_int_device);
+
+ return 0;
+}
+
+static const struct i2c_device_id tcm825x_id[] = {
+ { "tcm825x", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, tcm825x_id);
+
+static struct i2c_driver tcm825x_i2c_driver = {
+ .driver = {
+ .name = TCM825X_NAME,
+ },
+ .probe = tcm825x_probe,
+ .remove = tcm825x_remove,
+ .id_table = tcm825x_id,
+};
+
+static struct tcm825x_sensor tcm825x = {
+ .timeperframe = {
+ .numerator = 1,
+ .denominator = DEFAULT_FPS,
+ },
+};
+
+static int __init tcm825x_init(void)
+{
+ int rval;
+
+ rval = i2c_add_driver(&tcm825x_i2c_driver);
+ if (rval)
+ pr_info("%s: failed registering " TCM825X_NAME "\n",
+ __func__);
+
+ return rval;
+}
+
+static void __exit tcm825x_exit(void)
+{
+ i2c_del_driver(&tcm825x_i2c_driver);
+}
+
+/*
+ * FIXME: Menelaus isn't ready (?) at module_init stage, so use
+ * late_initcall for now.
+ */
+late_initcall(tcm825x_init);
+module_exit(tcm825x_exit);
+
+MODULE_AUTHOR("Sakari Ailus <sakari.ailus@nokia.com>");
+MODULE_DESCRIPTION("TCM825x camera sensor driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/media/omap24xx/tcm825x.h b/drivers/staging/media/omap24xx/tcm825x.h
new file mode 100644
index 00000000000..8a29636d1ad
--- /dev/null
+++ b/drivers/staging/media/omap24xx/tcm825x.h
@@ -0,0 +1,200 @@
+/*
+ * drivers/media/i2c/tcm825x.h
+ *
+ * Register definitions for the TCM825X CameraChip.
+ *
+ * Author: David Cohen (david.cohen@indt.org.br)
+ *
+ * This file is licensed under the terms of the GNU General Public License
+ * version 2. This program is licensed "as is" without any warranty of any
+ * kind, whether express or implied.
+ *
+ * This file was based on ov9640.h from MontaVista
+ */
+
+#ifndef TCM825X_H
+#define TCM825X_H
+
+#include <linux/videodev2.h>
+
+#include "v4l2-int-device.h"
+
+#define TCM825X_NAME "tcm825x"
+
+#define TCM825X_MASK(x) (x & 0x00ff)
+#define TCM825X_ADDR(x) ((x & 0xff00) >> 8)
+
+/* The TCM825X I2C sensor chip has a fixed slave address of 0x3d. */
+#define TCM825X_I2C_ADDR 0x3d
+
+/*
+ * define register offsets for the TCM825X sensor chip
+ * OFFSET(8 bits) + MASK(8 bits)
+ * MASK bit 4 and 3 are used when the register uses more than one address
+ */
+#define TCM825X_FPS 0x0280
+#define TCM825X_ACF 0x0240
+#define TCM825X_DOUTBUF 0x020C
+#define TCM825X_DCLKP 0x0202
+#define TCM825X_ACFDET 0x0201
+#define TCM825X_DOUTSW 0x0380
+#define TCM825X_DATAHZ 0x0340
+#define TCM825X_PICSIZ 0x033c
+#define TCM825X_PICFMT 0x0302
+#define TCM825X_V_INV 0x0480
+#define TCM825X_H_INV 0x0440
+#define TCM825X_ESRLSW 0x0430
+#define TCM825X_V_LENGTH 0x040F
+#define TCM825X_ALCSW 0x0580
+#define TCM825X_ESRLIM 0x0560
+#define TCM825X_ESRSPD_U 0x051F
+#define TCM825X_ESRSPD_L 0x06FF
+#define TCM825X_AG 0x07FF
+#define TCM825X_ESRSPD2 0x06FF
+#define TCM825X_ALCMODE 0x0830
+#define TCM825X_ALCH 0x080F
+#define TCM825X_ALCL 0x09FF
+#define TCM825X_AWBSW 0x0A80
+#define TCM825X_MRG 0x0BFF
+#define TCM825X_MBG 0x0CFF
+#define TCM825X_GAMSW 0x0D80
+#define TCM825X_HDTG 0x0EFF
+#define TCM825X_VDTG 0x0FFF
+#define TCM825X_HDTCORE 0x10F0
+#define TCM825X_VDTCORE 0x100F
+#define TCM825X_CONT 0x11FF
+#define TCM825X_BRIGHT 0x12FF
+#define TCM825X_VHUE 0x137F
+#define TCM825X_UHUE 0x147F
+#define TCM825X_VGAIN 0x153F
+#define TCM825X_UGAIN 0x163F
+#define TCM825X_UVCORE 0x170F
+#define TCM825X_SATU 0x187F
+#define TCM825X_MHMODE 0x1980
+#define TCM825X_MHLPFSEL 0x1940
+#define TCM825X_YMODE 0x1930
+#define TCM825X_MIXHG 0x1907
+#define TCM825X_LENS 0x1A3F
+#define TCM825X_AGLIM 0x1BE0
+#define TCM825X_LENSRPOL 0x1B10
+#define TCM825X_LENSRGAIN 0x1B0F
+#define TCM825X_ES100S 0x1CFF
+#define TCM825X_ES120S 0x1DFF
+#define TCM825X_DMASK 0x1EC0
+#define TCM825X_CODESW 0x1E20
+#define TCM825X_CODESEL 0x1E10
+#define TCM825X_TESPIC 0x1E04
+#define TCM825X_PICSEL 0x1E03
+#define TCM825X_HNUM 0x20FF
+#define TCM825X_VOUTPH 0x287F
+#define TCM825X_ESROUT 0x327F
+#define TCM825X_ESROUT2 0x33FF
+#define TCM825X_AGOUT 0x34FF
+#define TCM825X_DGOUT 0x353F
+#define TCM825X_AGSLOW1 0x39C0
+#define TCM825X_FLLSMODE 0x3930
+#define TCM825X_FLLSLIM 0x390F
+#define TCM825X_DETSEL 0x3AF0
+#define TCM825X_ACDETNC 0x3A0F
+#define TCM825X_AGSLOW2 0x3BC0
+#define TCM825X_DG 0x3B3F
+#define TCM825X_REJHLEV 0x3CFF
+#define TCM825X_ALCLOCK 0x3D80
+#define TCM825X_FPSLNKSW 0x3D40
+#define TCM825X_ALCSPD 0x3D30
+#define TCM825X_REJH 0x3D03
+#define TCM825X_SHESRSW 0x3E80
+#define TCM825X_ESLIMSEL 0x3E40
+#define TCM825X_SHESRSPD 0x3E30
+#define TCM825X_ELSTEP 0x3E0C
+#define TCM825X_ELSTART 0x3E03
+#define TCM825X_AGMIN 0x3FFF
+#define TCM825X_PREGRG 0x423F
+#define TCM825X_PREGBG 0x433F
+#define TCM825X_PRERG 0x443F
+#define TCM825X_PREBG 0x453F
+#define TCM825X_MSKBR 0x477F
+#define TCM825X_MSKGR 0x487F
+#define TCM825X_MSKRB 0x497F
+#define TCM825X_MSKGB 0x4A7F
+#define TCM825X_MSKRG 0x4B7F
+#define TCM825X_MSKBG 0x4C7F
+#define TCM825X_HDTCSW 0x4D80
+#define TCM825X_VDTCSW 0x4D40
+#define TCM825X_DTCYL 0x4D3F
+#define TCM825X_HDTPSW 0x4E80
+#define TCM825X_VDTPSW 0x4E40
+#define TCM825X_DTCGAIN 0x4E3F
+#define TCM825X_DTLLIMSW 0x4F10
+#define TCM825X_DTLYLIM 0x4F0F
+#define TCM825X_YLCUTLMSK 0x5080
+#define TCM825X_YLCUTL 0x503F
+#define TCM825X_YLCUTHMSK 0x5180
+#define TCM825X_YLCUTH 0x513F
+#define TCM825X_UVSKNC 0x527F
+#define TCM825X_UVLJ 0x537F
+#define TCM825X_WBGMIN 0x54FF
+#define TCM825X_WBGMAX 0x55FF
+#define TCM825X_WBSPDUP 0x5603
+#define TCM825X_ALLAREA 0x5820
+#define TCM825X_WBLOCK 0x5810
+#define TCM825X_WB2SP 0x580F
+#define TCM825X_KIZUSW 0x5920
+#define TCM825X_PBRSW 0x5910
+#define TCM825X_ABCSW 0x5903
+#define TCM825X_PBDLV 0x5AFF
+#define TCM825X_PBC1LV 0x5BFF
+
+#define TCM825X_NUM_REGS (TCM825X_ADDR(TCM825X_PBC1LV) + 1)
+
+#define TCM825X_BYTES_PER_PIXEL 2
+
+#define TCM825X_REG_TERM 0xff /* terminating list entry for reg */
+#define TCM825X_VAL_TERM 0xff /* terminating list entry for val */
+
+/* define a structure for tcm825x register initialization values */
+struct tcm825x_reg {
+ u8 val;
+ u16 reg;
+};
+
+enum image_size { subQCIF = 0, QQVGA, QCIF, QVGA, CIF, VGA };
+enum pixel_format { YUV422 = 0, RGB565 };
+#define NUM_IMAGE_SIZES 6
+#define NUM_PIXEL_FORMATS 2
+
+#define TCM825X_XCLK_MIN 11900000
+#define TCM825X_XCLK_MAX 25000000
+
+struct capture_size {
+ unsigned long width;
+ unsigned long height;
+};
+
+struct tcm825x_platform_data {
+ /* Is the sensor usable? Doesn't yet mean it's there, but you
+ * can try! */
+ int (*is_okay)(void);
+ /* Set power state, zero is off, non-zero is on. */
+ int (*power_set)(int power);
+ /* Default registers written after power-on or reset. */
+ const struct tcm825x_reg * (*default_regs)(void);
+ int (*needs_reset)(struct v4l2_int_device *s, void *buf,
+ struct v4l2_pix_format *fmt);
+ int (*ifparm)(struct v4l2_ifparm *p);
+ int (*is_upside_down)(void);
+};
+
+/* Array of image sizes supported by TCM825X. These must be ordered from
+ * smallest image size to largest.
+ */
+static const struct capture_size tcm825x_sizes[] = {
+ { 128, 96 }, /* subQCIF */
+ { 160, 120 }, /* QQVGA */
+ { 176, 144 }, /* QCIF */
+ { 320, 240 }, /* QVGA */
+ { 352, 288 }, /* CIF */
+ { 640, 480 }, /* VGA */
+};
+
+#endif /* ifndef TCM825X_H */
diff --git a/drivers/staging/media/omap24xx/v4l2-int-device.c b/drivers/staging/media/omap24xx/v4l2-int-device.c
new file mode 100644
index 00000000000..427a89033a1
--- /dev/null
+++ b/drivers/staging/media/omap24xx/v4l2-int-device.c
@@ -0,0 +1,164 @@
+/*
+ * drivers/media/video/v4l2-int-device.c
+ *
+ * V4L2 internal ioctl interface.
+ *
+ * Copyright (C) 2007 Nokia Corporation.
+ *
+ * Contact: Sakari Ailus <sakari.ailus@nokia.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ */
+
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/sort.h>
+#include <linux/string.h>
+#include <linux/module.h>
+
+#include "v4l2-int-device.h"
+
+static DEFINE_MUTEX(mutex);
+static LIST_HEAD(int_list);
+
+void v4l2_int_device_try_attach_all(void)
+{
+ struct v4l2_int_device *m, *s;
+
+ list_for_each_entry(m, &int_list, head) {
+ if (m->type != v4l2_int_type_master)
+ continue;
+
+ list_for_each_entry(s, &int_list, head) {
+ if (s->type != v4l2_int_type_slave)
+ continue;
+
+ /* Slave is connected? */
+ if (s->u.slave->master)
+ continue;
+
+ /* Slave wants to attach to master? */
+ if (s->u.slave->attach_to[0] != 0
+ && strncmp(m->name, s->u.slave->attach_to,
+ V4L2NAMESIZE))
+ continue;
+
+ if (!try_module_get(m->module))
+ continue;
+
+ s->u.slave->master = m;
+ if (m->u.master->attach(s)) {
+ s->u.slave->master = NULL;
+ module_put(m->module);
+ continue;
+ }
+ }
+ }
+}
+EXPORT_SYMBOL_GPL(v4l2_int_device_try_attach_all);
+
+static int ioctl_sort_cmp(const void *a, const void *b)
+{
+ const struct v4l2_int_ioctl_desc *d1 = a, *d2 = b;
+
+ if (d1->num > d2->num)
+ return 1;
+
+ if (d1->num < d2->num)
+ return -1;
+
+ return 0;
+}
+
+int v4l2_int_device_register(struct v4l2_int_device *d)
+{
+ if (d->type == v4l2_int_type_slave)
+ sort(d->u.slave->ioctls, d->u.slave->num_ioctls,
+ sizeof(struct v4l2_int_ioctl_desc),
+ &ioctl_sort_cmp, NULL);
+ mutex_lock(&mutex);
+ list_add(&d->head, &int_list);
+ v4l2_int_device_try_attach_all();
+ mutex_unlock(&mutex);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(v4l2_int_device_register);
+
+void v4l2_int_device_unregister(struct v4l2_int_device *d)
+{
+ mutex_lock(&mutex);
+ list_del(&d->head);
+ if (d->type == v4l2_int_type_slave
+ && d->u.slave->master != NULL) {
+ d->u.slave->master->u.master->detach(d);
+ module_put(d->u.slave->master->module);
+ d->u.slave->master = NULL;
+ }
+ mutex_unlock(&mutex);
+}
+EXPORT_SYMBOL_GPL(v4l2_int_device_unregister);
+
+/* Adapted from search_extable in extable.c. */
+static v4l2_int_ioctl_func *find_ioctl(struct v4l2_int_slave *slave, int cmd,
+ v4l2_int_ioctl_func *no_such_ioctl)
+{
+ const struct v4l2_int_ioctl_desc *first = slave->ioctls;
+ const struct v4l2_int_ioctl_desc *last =
+ first + slave->num_ioctls - 1;
+
+ while (first <= last) {
+ const struct v4l2_int_ioctl_desc *mid;
+
+ mid = (last - first) / 2 + first;
+
+ if (mid->num < cmd)
+ first = mid + 1;
+ else if (mid->num > cmd)
+ last = mid - 1;
+ else
+ return mid->func;
+ }
+
+ return no_such_ioctl;
+}
+
+static int no_such_ioctl_0(struct v4l2_int_device *d)
+{
+ return -ENOIOCTLCMD;
+}
+
+int v4l2_int_ioctl_0(struct v4l2_int_device *d, int cmd)
+{
+ return ((v4l2_int_ioctl_func_0 *)
+ find_ioctl(d->u.slave, cmd,
+ (v4l2_int_ioctl_func *)no_such_ioctl_0))(d);
+}
+EXPORT_SYMBOL_GPL(v4l2_int_ioctl_0);
+
+static int no_such_ioctl_1(struct v4l2_int_device *d, void *arg)
+{
+ return -ENOIOCTLCMD;
+}
+
+int v4l2_int_ioctl_1(struct v4l2_int_device *d, int cmd, void *arg)
+{
+ return ((v4l2_int_ioctl_func_1 *)
+ find_ioctl(d->u.slave, cmd,
+ (v4l2_int_ioctl_func *)no_such_ioctl_1))(d, arg);
+}
+EXPORT_SYMBOL_GPL(v4l2_int_ioctl_1);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/media/omap24xx/v4l2-int-device.h b/drivers/staging/media/omap24xx/v4l2-int-device.h
new file mode 100644
index 00000000000..0286c95814f
--- /dev/null
+++ b/drivers/staging/media/omap24xx/v4l2-int-device.h
@@ -0,0 +1,305 @@
+/*
+ * include/media/v4l2-int-device.h
+ *
+ * V4L2 internal ioctl interface.
+ *
+ * Copyright (C) 2007 Nokia Corporation.
+ *
+ * Contact: Sakari Ailus <sakari.ailus@nokia.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ */
+
+#ifndef V4L2_INT_DEVICE_H
+#define V4L2_INT_DEVICE_H
+
+#include <media/v4l2-common.h>
+
+#define V4L2NAMESIZE 32
+
+/*
+ *
+ * The internal V4L2 device interface core.
+ *
+ */
+
+enum v4l2_int_type {
+ v4l2_int_type_master = 1,
+ v4l2_int_type_slave
+};
+
+struct module;
+
+struct v4l2_int_device;
+
+struct v4l2_int_master {
+ int (*attach)(struct v4l2_int_device *slave);
+ void (*detach)(struct v4l2_int_device *slave);
+};
+
+typedef int (v4l2_int_ioctl_func)(struct v4l2_int_device *);
+typedef int (v4l2_int_ioctl_func_0)(struct v4l2_int_device *);
+typedef int (v4l2_int_ioctl_func_1)(struct v4l2_int_device *, void *);
+
+struct v4l2_int_ioctl_desc {
+ int num;
+ v4l2_int_ioctl_func *func;
+};
+
+struct v4l2_int_slave {
+ /* Don't touch master. */
+ struct v4l2_int_device *master;
+
+ char attach_to[V4L2NAMESIZE];
+
+ int num_ioctls;
+ struct v4l2_int_ioctl_desc *ioctls;
+};
+
+struct v4l2_int_device {
+ /* Don't touch head. */
+ struct list_head head;
+
+ struct module *module;
+
+ char name[V4L2NAMESIZE];
+
+ enum v4l2_int_type type;
+ union {
+ struct v4l2_int_master *master;
+ struct v4l2_int_slave *slave;
+ } u;
+
+ void *priv;
+};
+
+void v4l2_int_device_try_attach_all(void);
+
+int v4l2_int_device_register(struct v4l2_int_device *d);
+void v4l2_int_device_unregister(struct v4l2_int_device *d);
+
+int v4l2_int_ioctl_0(struct v4l2_int_device *d, int cmd);
+int v4l2_int_ioctl_1(struct v4l2_int_device *d, int cmd, void *arg);
+
+/*
+ *
+ * Types and definitions for IOCTL commands.
+ *
+ */
+
+enum v4l2_power {
+ V4L2_POWER_OFF = 0,
+ V4L2_POWER_ON,
+ V4L2_POWER_STANDBY,
+};
+
+/* Slave interface type. */
+enum v4l2_if_type {
+ /*
+ * Parallel 8-, 10- or 12-bit interface, used by for example
+ * on certain image sensors.
+ */
+ V4L2_IF_TYPE_BT656,
+};
+
+enum v4l2_if_type_bt656_mode {
+ /*
+ * Modes without Bt synchronisation codes. Separate
+ * synchronisation signal lines are used.
+ */
+ V4L2_IF_TYPE_BT656_MODE_NOBT_8BIT,
+ V4L2_IF_TYPE_BT656_MODE_NOBT_10BIT,
+ V4L2_IF_TYPE_BT656_MODE_NOBT_12BIT,
+ /*
+ * Use Bt synchronisation codes. The vertical and horizontal
+ * synchronisation is done based on synchronisation codes.
+ */
+ V4L2_IF_TYPE_BT656_MODE_BT_8BIT,
+ V4L2_IF_TYPE_BT656_MODE_BT_10BIT,
+};
+
+struct v4l2_if_type_bt656 {
+ /*
+ * 0: Frame begins when vsync is high.
+ * 1: Frame begins when vsync changes from low to high.
+ */
+ unsigned frame_start_on_rising_vs:1;
+ /* Use Bt synchronisation codes for sync correction. */
+ unsigned bt_sync_correct:1;
+ /* Swap every two adjacent image data elements. */
+ unsigned swap:1;
+ /* Inverted latch clock polarity from slave. */
+ unsigned latch_clk_inv:1;
+ /* Hs polarity. 0 is active high, 1 active low. */
+ unsigned nobt_hs_inv:1;
+ /* Vs polarity. 0 is active high, 1 active low. */
+ unsigned nobt_vs_inv:1;
+ enum v4l2_if_type_bt656_mode mode;
+ /* Minimum accepted bus clock for slave (in Hz). */
+ u32 clock_min;
+ /* Maximum accepted bus clock for slave. */
+ u32 clock_max;
+ /*
+ * Current wish of the slave. May only change in response to
+ * ioctls that affect image capture.
+ */
+ u32 clock_curr;
+};
+
+struct v4l2_ifparm {
+ enum v4l2_if_type if_type;
+ union {
+ struct v4l2_if_type_bt656 bt656;
+ } u;
+};
+
+/* IOCTL command numbers. */
+enum v4l2_int_ioctl_num {
+ /*
+ *
+ * "Proper" V4L ioctls, as in struct video_device.
+ *
+ */
+ vidioc_int_enum_fmt_cap_num = 1,
+ vidioc_int_g_fmt_cap_num,
+ vidioc_int_s_fmt_cap_num,
+ vidioc_int_try_fmt_cap_num,
+ vidioc_int_queryctrl_num,
+ vidioc_int_g_ctrl_num,
+ vidioc_int_s_ctrl_num,
+ vidioc_int_cropcap_num,
+ vidioc_int_g_crop_num,
+ vidioc_int_s_crop_num,
+ vidioc_int_g_parm_num,
+ vidioc_int_s_parm_num,
+ vidioc_int_querystd_num,
+ vidioc_int_s_std_num,
+ vidioc_int_s_video_routing_num,
+
+ /*
+ *
+ * Strictly internal ioctls.
+ *
+ */
+ /* Initialise the device when slave attaches to the master. */
+ vidioc_int_dev_init_num = 1000,
+ /* Delinitialise the device at slave detach. */
+ vidioc_int_dev_exit_num,
+ /* Set device power state. */
+ vidioc_int_s_power_num,
+ /*
+ * Get slave private data, e.g. platform-specific slave
+ * configuration used by the master.
+ */
+ vidioc_int_g_priv_num,
+ /* Get slave interface parameters. */
+ vidioc_int_g_ifparm_num,
+ /* Does the slave need to be reset after VIDIOC_DQBUF? */
+ vidioc_int_g_needs_reset_num,
+ vidioc_int_enum_framesizes_num,
+ vidioc_int_enum_frameintervals_num,
+
+ /*
+ *
+ * VIDIOC_INT_* ioctls.
+ *
+ */
+ /* VIDIOC_INT_RESET */
+ vidioc_int_reset_num,
+ /* VIDIOC_INT_INIT */
+ vidioc_int_init_num,
+
+ /*
+ *
+ * Start of private ioctls.
+ *
+ */
+ vidioc_int_priv_start_num = 2000,
+};
+
+/*
+ *
+ * IOCTL wrapper functions for better type checking.
+ *
+ */
+
+#define V4L2_INT_WRAPPER_0(name) \
+ static inline int vidioc_int_##name(struct v4l2_int_device *d) \
+ { \
+ return v4l2_int_ioctl_0(d, vidioc_int_##name##_num); \
+ } \
+ \
+ static inline struct v4l2_int_ioctl_desc \
+ vidioc_int_##name##_cb(int (*func) \
+ (struct v4l2_int_device *)) \
+ { \
+ struct v4l2_int_ioctl_desc desc; \
+ \
+ desc.num = vidioc_int_##name##_num; \
+ desc.func = (v4l2_int_ioctl_func *)func; \
+ \
+ return desc; \
+ }
+
+#define V4L2_INT_WRAPPER_1(name, arg_type, asterisk) \
+ static inline int vidioc_int_##name(struct v4l2_int_device *d, \
+ arg_type asterisk arg) \
+ { \
+ return v4l2_int_ioctl_1(d, vidioc_int_##name##_num, \
+ (void *)(unsigned long)arg); \
+ } \
+ \
+ static inline struct v4l2_int_ioctl_desc \
+ vidioc_int_##name##_cb(int (*func) \
+ (struct v4l2_int_device *, \
+ arg_type asterisk)) \
+ { \
+ struct v4l2_int_ioctl_desc desc; \
+ \
+ desc.num = vidioc_int_##name##_num; \
+ desc.func = (v4l2_int_ioctl_func *)func; \
+ \
+ return desc; \
+ }
+
+V4L2_INT_WRAPPER_1(enum_fmt_cap, struct v4l2_fmtdesc, *);
+V4L2_INT_WRAPPER_1(g_fmt_cap, struct v4l2_format, *);
+V4L2_INT_WRAPPER_1(s_fmt_cap, struct v4l2_format, *);
+V4L2_INT_WRAPPER_1(try_fmt_cap, struct v4l2_format, *);
+V4L2_INT_WRAPPER_1(queryctrl, struct v4l2_queryctrl, *);
+V4L2_INT_WRAPPER_1(g_ctrl, struct v4l2_control, *);
+V4L2_INT_WRAPPER_1(s_ctrl, struct v4l2_control, *);
+V4L2_INT_WRAPPER_1(cropcap, struct v4l2_cropcap, *);
+V4L2_INT_WRAPPER_1(g_crop, struct v4l2_crop, *);
+V4L2_INT_WRAPPER_1(s_crop, struct v4l2_crop, *);
+V4L2_INT_WRAPPER_1(g_parm, struct v4l2_streamparm, *);
+V4L2_INT_WRAPPER_1(s_parm, struct v4l2_streamparm, *);
+V4L2_INT_WRAPPER_1(querystd, v4l2_std_id, *);
+V4L2_INT_WRAPPER_1(s_std, v4l2_std_id, *);
+V4L2_INT_WRAPPER_1(s_video_routing, struct v4l2_routing, *);
+
+V4L2_INT_WRAPPER_0(dev_init);
+V4L2_INT_WRAPPER_0(dev_exit);
+V4L2_INT_WRAPPER_1(s_power, enum v4l2_power, );
+V4L2_INT_WRAPPER_1(g_priv, void, *);
+V4L2_INT_WRAPPER_1(g_ifparm, struct v4l2_ifparm, *);
+V4L2_INT_WRAPPER_1(g_needs_reset, void, *);
+V4L2_INT_WRAPPER_1(enum_framesizes, struct v4l2_frmsizeenum, *);
+V4L2_INT_WRAPPER_1(enum_frameintervals, struct v4l2_frmivalenum, *);
+
+V4L2_INT_WRAPPER_0(reset);
+V4L2_INT_WRAPPER_0(init);
+
+#endif
diff --git a/drivers/staging/media/omap4iss/Kconfig b/drivers/staging/media/omap4iss/Kconfig
new file mode 100644
index 00000000000..8afc6fee40c
--- /dev/null
+++ b/drivers/staging/media/omap4iss/Kconfig
@@ -0,0 +1,6 @@
+config VIDEO_OMAP4
+ bool "OMAP 4 Camera support"
+ depends on VIDEO_V4L2=y && VIDEO_V4L2_SUBDEV_API && I2C=y && ARCH_OMAP4
+ select VIDEOBUF2_DMA_CONTIG
+ ---help---
+ Driver for an OMAP 4 ISS controller.
diff --git a/drivers/staging/media/omap4iss/Makefile b/drivers/staging/media/omap4iss/Makefile
new file mode 100644
index 00000000000..a716ce936cf
--- /dev/null
+++ b/drivers/staging/media/omap4iss/Makefile
@@ -0,0 +1,6 @@
+# Makefile for OMAP4 ISS driver
+
+omap4-iss-objs += \
+ iss.o iss_csi2.o iss_csiphy.o iss_ipipeif.o iss_ipipe.o iss_resizer.o iss_video.o
+
+obj-$(CONFIG_VIDEO_OMAP4) += omap4-iss.o
diff --git a/drivers/staging/media/omap4iss/TODO b/drivers/staging/media/omap4iss/TODO
new file mode 100644
index 00000000000..fcde88860a2
--- /dev/null
+++ b/drivers/staging/media/omap4iss/TODO
@@ -0,0 +1,4 @@
+* Make the driver compile as a module
+* Fix FIFO/buffer overflows and underflows
+* Replace dummy resizer code with a real implementation
+* Fix checkpatch errors and warnings
diff --git a/drivers/staging/media/omap4iss/iss.c b/drivers/staging/media/omap4iss/iss.c
new file mode 100644
index 00000000000..2e422dde074
--- /dev/null
+++ b/drivers/staging/media/omap4iss/iss.c
@@ -0,0 +1,1557 @@
+/*
+ * TI OMAP4 ISS V4L2 Driver
+ *
+ * Copyright (C) 2012, Texas Instruments
+ *
+ * Author: Sergio Aguirre <sergio.a.aguirre@gmail.com>
+ *
+ * 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.
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/dma-mapping.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/sched.h>
+#include <linux/vmalloc.h>
+
+#include <media/v4l2-common.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-ctrls.h>
+
+#include "iss.h"
+#include "iss_regs.h"
+
+#define ISS_PRINT_REGISTER(iss, name)\
+ dev_dbg(iss->dev, "###ISS " #name "=0x%08x\n", \
+ iss_reg_read(iss, OMAP4_ISS_MEM_TOP, ISS_##name))
+
+static void iss_print_status(struct iss_device *iss)
+{
+ dev_dbg(iss->dev, "-------------ISS HL Register dump-------------\n");
+
+ ISS_PRINT_REGISTER(iss, HL_REVISION);
+ ISS_PRINT_REGISTER(iss, HL_SYSCONFIG);
+ ISS_PRINT_REGISTER(iss, HL_IRQSTATUS(5));
+ ISS_PRINT_REGISTER(iss, HL_IRQENABLE_SET(5));
+ ISS_PRINT_REGISTER(iss, HL_IRQENABLE_CLR(5));
+ ISS_PRINT_REGISTER(iss, CTRL);
+ ISS_PRINT_REGISTER(iss, CLKCTRL);
+ ISS_PRINT_REGISTER(iss, CLKSTAT);
+
+ dev_dbg(iss->dev, "-----------------------------------------------\n");
+}
+
+/*
+ * omap4iss_flush - Post pending L3 bus writes by doing a register readback
+ * @iss: OMAP4 ISS device
+ *
+ * In order to force posting of pending writes, we need to write and
+ * readback the same register, in this case the revision register.
+ *
+ * See this link for reference:
+ * http://www.mail-archive.com/linux-omap@vger.kernel.org/msg08149.html
+ */
+void omap4iss_flush(struct iss_device *iss)
+{
+ iss_reg_write(iss, OMAP4_ISS_MEM_TOP, ISS_HL_REVISION, 0);
+ iss_reg_read(iss, OMAP4_ISS_MEM_TOP, ISS_HL_REVISION);
+}
+
+/*
+ * iss_isp_enable_interrupts - Enable ISS ISP interrupts.
+ * @iss: OMAP4 ISS device
+ */
+static void omap4iss_isp_enable_interrupts(struct iss_device *iss)
+{
+ static const u32 isp_irq = ISP5_IRQ_OCP_ERR |
+ ISP5_IRQ_RSZ_FIFO_IN_BLK_ERR |
+ ISP5_IRQ_RSZ_FIFO_OVF |
+ ISP5_IRQ_RSZ_INT_DMA |
+ ISP5_IRQ_ISIF_INT(0);
+
+ /* Enable ISP interrupts */
+ iss_reg_write(iss, OMAP4_ISS_MEM_ISP_SYS1, ISP5_IRQSTATUS(0), isp_irq);
+ iss_reg_write(iss, OMAP4_ISS_MEM_ISP_SYS1, ISP5_IRQENABLE_SET(0),
+ isp_irq);
+}
+
+/*
+ * iss_isp_disable_interrupts - Disable ISS interrupts.
+ * @iss: OMAP4 ISS device
+ */
+static void omap4iss_isp_disable_interrupts(struct iss_device *iss)
+{
+ iss_reg_write(iss, OMAP4_ISS_MEM_ISP_SYS1, ISP5_IRQENABLE_CLR(0), ~0);
+}
+
+/*
+ * iss_enable_interrupts - Enable ISS interrupts.
+ * @iss: OMAP4 ISS device
+ */
+static void iss_enable_interrupts(struct iss_device *iss)
+{
+ static const u32 hl_irq = ISS_HL_IRQ_CSIA | ISS_HL_IRQ_CSIB
+ | ISS_HL_IRQ_ISP(0);
+
+ /* Enable HL interrupts */
+ iss_reg_write(iss, OMAP4_ISS_MEM_TOP, ISS_HL_IRQSTATUS(5), hl_irq);
+ iss_reg_write(iss, OMAP4_ISS_MEM_TOP, ISS_HL_IRQENABLE_SET(5), hl_irq);
+
+ if (iss->regs[OMAP4_ISS_MEM_ISP_SYS1])
+ omap4iss_isp_enable_interrupts(iss);
+}
+
+/*
+ * iss_disable_interrupts - Disable ISS interrupts.
+ * @iss: OMAP4 ISS device
+ */
+static void iss_disable_interrupts(struct iss_device *iss)
+{
+ if (iss->regs[OMAP4_ISS_MEM_ISP_SYS1])
+ omap4iss_isp_disable_interrupts(iss);
+
+ iss_reg_write(iss, OMAP4_ISS_MEM_TOP, ISS_HL_IRQENABLE_CLR(5), ~0);
+}
+
+int omap4iss_get_external_info(struct iss_pipeline *pipe,
+ struct media_link *link)
+{
+ struct iss_device *iss =
+ container_of(pipe, struct iss_video, pipe)->iss;
+ struct v4l2_subdev_format fmt;
+ struct v4l2_ctrl *ctrl;
+ int ret;
+
+ if (!pipe->external)
+ return 0;
+
+ if (pipe->external_rate)
+ return 0;
+
+ memset(&fmt, 0, sizeof(fmt));
+
+ fmt.pad = link->source->index;
+ fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE;
+ ret = v4l2_subdev_call(media_entity_to_v4l2_subdev(link->sink->entity),
+ pad, get_fmt, NULL, &fmt);
+ if (ret < 0)
+ return -EPIPE;
+
+ pipe->external_bpp = omap4iss_video_format_info(fmt.format.code)->bpp;
+
+ ctrl = v4l2_ctrl_find(pipe->external->ctrl_handler,
+ V4L2_CID_PIXEL_RATE);
+ if (ctrl == NULL) {
+ dev_warn(iss->dev, "no pixel rate control in subdev %s\n",
+ pipe->external->name);
+ return -EPIPE;
+ }
+
+ pipe->external_rate = v4l2_ctrl_g_ctrl_int64(ctrl);
+
+ return 0;
+}
+
+/*
+ * Configure the bridge. Valid inputs are
+ *
+ * IPIPEIF_INPUT_CSI2A: CSI2a receiver
+ * IPIPEIF_INPUT_CSI2B: CSI2b receiver
+ *
+ * The bridge and lane shifter are configured according to the selected input
+ * and the ISP platform data.
+ */
+void omap4iss_configure_bridge(struct iss_device *iss,
+ enum ipipeif_input_entity input)
+{
+ u32 issctrl_val;
+ u32 isp5ctrl_val;
+
+ issctrl_val = iss_reg_read(iss, OMAP4_ISS_MEM_TOP, ISS_CTRL);
+ issctrl_val &= ~ISS_CTRL_INPUT_SEL_MASK;
+ issctrl_val &= ~ISS_CTRL_CLK_DIV_MASK;
+
+ isp5ctrl_val = iss_reg_read(iss, OMAP4_ISS_MEM_ISP_SYS1, ISP5_CTRL);
+
+ switch (input) {
+ case IPIPEIF_INPUT_CSI2A:
+ issctrl_val |= ISS_CTRL_INPUT_SEL_CSI2A;
+ break;
+
+ case IPIPEIF_INPUT_CSI2B:
+ issctrl_val |= ISS_CTRL_INPUT_SEL_CSI2B;
+ break;
+
+ default:
+ return;
+ }
+
+ issctrl_val |= ISS_CTRL_SYNC_DETECT_VS_RAISING;
+
+ isp5ctrl_val |= ISP5_CTRL_VD_PULSE_EXT | ISP5_CTRL_PSYNC_CLK_SEL |
+ ISP5_CTRL_SYNC_ENABLE;
+
+ iss_reg_write(iss, OMAP4_ISS_MEM_TOP, ISS_CTRL, issctrl_val);
+ iss_reg_write(iss, OMAP4_ISS_MEM_ISP_SYS1, ISP5_CTRL, isp5ctrl_val);
+}
+
+#ifdef ISS_ISR_DEBUG
+static void iss_isr_dbg(struct iss_device *iss, u32 irqstatus)
+{
+ static const char * const name[] = {
+ "ISP_0",
+ "ISP_1",
+ "ISP_2",
+ "ISP_3",
+ "CSIA",
+ "CSIB",
+ "CCP2_0",
+ "CCP2_1",
+ "CCP2_2",
+ "CCP2_3",
+ "CBUFF",
+ "BTE",
+ "SIMCOP_0",
+ "SIMCOP_1",
+ "SIMCOP_2",
+ "SIMCOP_3",
+ "CCP2_8",
+ "HS_VS",
+ "18",
+ "19",
+ "20",
+ "21",
+ "22",
+ "23",
+ "24",
+ "25",
+ "26",
+ "27",
+ "28",
+ "29",
+ "30",
+ "31",
+ };
+ unsigned int i;
+
+ dev_dbg(iss->dev, "ISS IRQ: ");
+
+ for (i = 0; i < ARRAY_SIZE(name); i++) {
+ if ((1 << i) & irqstatus)
+ pr_cont("%s ", name[i]);
+ }
+ pr_cont("\n");
+}
+
+static void iss_isp_isr_dbg(struct iss_device *iss, u32 irqstatus)
+{
+ static const char * const name[] = {
+ "ISIF_0",
+ "ISIF_1",
+ "ISIF_2",
+ "ISIF_3",
+ "IPIPEREQ",
+ "IPIPELAST_PIX",
+ "IPIPEDMA",
+ "IPIPEBSC",
+ "IPIPEHST",
+ "IPIPEIF",
+ "AEW",
+ "AF",
+ "H3A",
+ "RSZ_REG",
+ "RSZ_LAST_PIX",
+ "RSZ_DMA",
+ "RSZ_CYC_RZA",
+ "RSZ_CYC_RZB",
+ "RSZ_FIFO_OVF",
+ "RSZ_FIFO_IN_BLK_ERR",
+ "20",
+ "21",
+ "RSZ_EOF0",
+ "RSZ_EOF1",
+ "H3A_EOF",
+ "IPIPE_EOF",
+ "26",
+ "IPIPE_DPC_INI",
+ "IPIPE_DPC_RNEW0",
+ "IPIPE_DPC_RNEW1",
+ "30",
+ "OCP_ERR",
+ };
+ unsigned int i;
+
+ dev_dbg(iss->dev, "ISP IRQ: ");
+
+ for (i = 0; i < ARRAY_SIZE(name); i++) {
+ if ((1 << i) & irqstatus)
+ pr_cont("%s ", name[i]);
+ }
+ pr_cont("\n");
+}
+#endif
+
+/*
+ * iss_isr - Interrupt Service Routine for ISS module.
+ * @irq: Not used currently.
+ * @_iss: Pointer to the OMAP4 ISS device
+ *
+ * Handles the corresponding callback if plugged in.
+ *
+ * Returns IRQ_HANDLED when IRQ was correctly handled, or IRQ_NONE when the
+ * IRQ wasn't handled.
+ */
+static irqreturn_t iss_isr(int irq, void *_iss)
+{
+ static const u32 ipipeif_events = ISP5_IRQ_IPIPEIF_IRQ |
+ ISP5_IRQ_ISIF_INT(0);
+ static const u32 resizer_events = ISP5_IRQ_RSZ_FIFO_IN_BLK_ERR |
+ ISP5_IRQ_RSZ_FIFO_OVF |
+ ISP5_IRQ_RSZ_INT_DMA;
+ struct iss_device *iss = _iss;
+ u32 irqstatus;
+
+ irqstatus = iss_reg_read(iss, OMAP4_ISS_MEM_TOP, ISS_HL_IRQSTATUS(5));
+ iss_reg_write(iss, OMAP4_ISS_MEM_TOP, ISS_HL_IRQSTATUS(5), irqstatus);
+
+ if (irqstatus & ISS_HL_IRQ_CSIA)
+ omap4iss_csi2_isr(&iss->csi2a);
+
+ if (irqstatus & ISS_HL_IRQ_CSIB)
+ omap4iss_csi2_isr(&iss->csi2b);
+
+ if (irqstatus & ISS_HL_IRQ_ISP(0)) {
+ u32 isp_irqstatus = iss_reg_read(iss, OMAP4_ISS_MEM_ISP_SYS1,
+ ISP5_IRQSTATUS(0));
+ iss_reg_write(iss, OMAP4_ISS_MEM_ISP_SYS1, ISP5_IRQSTATUS(0),
+ isp_irqstatus);
+
+ if (isp_irqstatus & ISP5_IRQ_OCP_ERR)
+ dev_dbg(iss->dev, "ISP5 OCP Error!\n");
+
+ if (isp_irqstatus & ipipeif_events) {
+ omap4iss_ipipeif_isr(&iss->ipipeif,
+ isp_irqstatus & ipipeif_events);
+ }
+
+ if (isp_irqstatus & resizer_events)
+ omap4iss_resizer_isr(&iss->resizer,
+ isp_irqstatus & resizer_events);
+
+#ifdef ISS_ISR_DEBUG
+ iss_isp_isr_dbg(iss, isp_irqstatus);
+#endif
+ }
+
+ omap4iss_flush(iss);
+
+#ifdef ISS_ISR_DEBUG
+ iss_isr_dbg(iss, irqstatus);
+#endif
+
+ return IRQ_HANDLED;
+}
+
+/* -----------------------------------------------------------------------------
+ * Pipeline power management
+ *
+ * Entities must be powered up when part of a pipeline that contains at least
+ * one open video device node.
+ *
+ * To achieve this use the entity use_count field to track the number of users.
+ * For entities corresponding to video device nodes the use_count field stores
+ * the users count of the node. For entities corresponding to subdevs the
+ * use_count field stores the total number of users of all video device nodes
+ * in the pipeline.
+ *
+ * The omap4iss_pipeline_pm_use() function must be called in the open() and
+ * close() handlers of video device nodes. It increments or decrements the use
+ * count of all subdev entities in the pipeline.
+ *
+ * To react to link management on powered pipelines, the link setup notification
+ * callback updates the use count of all entities in the source and sink sides
+ * of the link.
+ */
+
+/*
+ * iss_pipeline_pm_use_count - Count the number of users of a pipeline
+ * @entity: The entity
+ *
+ * Return the total number of users of all video device nodes in the pipeline.
+ */
+static int iss_pipeline_pm_use_count(struct media_entity *entity)
+{
+ struct media_entity_graph graph;
+ int use = 0;
+
+ media_entity_graph_walk_start(&graph, entity);
+
+ while ((entity = media_entity_graph_walk_next(&graph))) {
+ if (media_entity_type(entity) == MEDIA_ENT_T_DEVNODE)
+ use += entity->use_count;
+ }
+
+ return use;
+}
+
+/*
+ * iss_pipeline_pm_power_one - Apply power change to an entity
+ * @entity: The entity
+ * @change: Use count change
+ *
+ * Change the entity use count by @change. If the entity is a subdev update its
+ * power state by calling the core::s_power operation when the use count goes
+ * from 0 to != 0 or from != 0 to 0.
+ *
+ * Return 0 on success or a negative error code on failure.
+ */
+static int iss_pipeline_pm_power_one(struct media_entity *entity, int change)
+{
+ struct v4l2_subdev *subdev;
+
+ subdev = media_entity_type(entity) == MEDIA_ENT_T_V4L2_SUBDEV
+ ? media_entity_to_v4l2_subdev(entity) : NULL;
+
+ if (entity->use_count == 0 && change > 0 && subdev != NULL) {
+ int ret;
+
+ ret = v4l2_subdev_call(subdev, core, s_power, 1);
+ if (ret < 0 && ret != -ENOIOCTLCMD)
+ return ret;
+ }
+
+ entity->use_count += change;
+ WARN_ON(entity->use_count < 0);
+
+ if (entity->use_count == 0 && change < 0 && subdev != NULL)
+ v4l2_subdev_call(subdev, core, s_power, 0);
+
+ return 0;
+}
+
+/*
+ * iss_pipeline_pm_power - Apply power change to all entities in a pipeline
+ * @entity: The entity
+ * @change: Use count change
+ *
+ * Walk the pipeline to update the use count and the power state of all non-node
+ * entities.
+ *
+ * Return 0 on success or a negative error code on failure.
+ */
+static int iss_pipeline_pm_power(struct media_entity *entity, int change)
+{
+ struct media_entity_graph graph;
+ struct media_entity *first = entity;
+ int ret = 0;
+
+ if (!change)
+ return 0;
+
+ media_entity_graph_walk_start(&graph, entity);
+
+ while (!ret && (entity = media_entity_graph_walk_next(&graph)))
+ if (media_entity_type(entity) != MEDIA_ENT_T_DEVNODE)
+ ret = iss_pipeline_pm_power_one(entity, change);
+
+ if (!ret)
+ return 0;
+
+ media_entity_graph_walk_start(&graph, first);
+
+ while ((first = media_entity_graph_walk_next(&graph))
+ && first != entity)
+ if (media_entity_type(first) != MEDIA_ENT_T_DEVNODE)
+ iss_pipeline_pm_power_one(first, -change);
+
+ return ret;
+}
+
+/*
+ * omap4iss_pipeline_pm_use - Update the use count of an entity
+ * @entity: The entity
+ * @use: Use (1) or stop using (0) the entity
+ *
+ * Update the use count of all entities in the pipeline and power entities on or
+ * off accordingly.
+ *
+ * Return 0 on success or a negative error code on failure. Powering entities
+ * off is assumed to never fail. No failure can occur when the use parameter is
+ * set to 0.
+ */
+int omap4iss_pipeline_pm_use(struct media_entity *entity, int use)
+{
+ int change = use ? 1 : -1;
+ int ret;
+
+ mutex_lock(&entity->parent->graph_mutex);
+
+ /* Apply use count to node. */
+ entity->use_count += change;
+ WARN_ON(entity->use_count < 0);
+
+ /* Apply power change to connected non-nodes. */
+ ret = iss_pipeline_pm_power(entity, change);
+ if (ret < 0)
+ entity->use_count -= change;
+
+ mutex_unlock(&entity->parent->graph_mutex);
+
+ return ret;
+}
+
+/*
+ * iss_pipeline_link_notify - Link management notification callback
+ * @link: The link
+ * @flags: New link flags that will be applied
+ *
+ * React to link management on powered pipelines by updating the use count of
+ * all entities in the source and sink sides of the link. Entities are powered
+ * on or off accordingly.
+ *
+ * Return 0 on success or a negative error code on failure. Powering entities
+ * off is assumed to never fail. This function will not fail for disconnection
+ * events.
+ */
+static int iss_pipeline_link_notify(struct media_link *link, u32 flags,
+ unsigned int notification)
+{
+ struct media_entity *source = link->source->entity;
+ struct media_entity *sink = link->sink->entity;
+ int source_use = iss_pipeline_pm_use_count(source);
+ int sink_use = iss_pipeline_pm_use_count(sink);
+ int ret;
+
+ if (notification == MEDIA_DEV_NOTIFY_POST_LINK_CH &&
+ !(link->flags & MEDIA_LNK_FL_ENABLED)) {
+ /* Powering off entities is assumed to never fail. */
+ iss_pipeline_pm_power(source, -sink_use);
+ iss_pipeline_pm_power(sink, -source_use);
+ return 0;
+ }
+
+ if (notification == MEDIA_DEV_NOTIFY_POST_LINK_CH &&
+ (flags & MEDIA_LNK_FL_ENABLED)) {
+ ret = iss_pipeline_pm_power(source, sink_use);
+ if (ret < 0)
+ return ret;
+
+ ret = iss_pipeline_pm_power(sink, source_use);
+ if (ret < 0)
+ iss_pipeline_pm_power(source, -sink_use);
+
+ return ret;
+ }
+
+ return 0;
+}
+
+/* -----------------------------------------------------------------------------
+ * Pipeline stream management
+ */
+
+/*
+ * iss_pipeline_enable - Enable streaming on a pipeline
+ * @pipe: ISS pipeline
+ * @mode: Stream mode (single shot or continuous)
+ *
+ * Walk the entities chain starting at the pipeline output video node and start
+ * all modules in the chain in the given mode.
+ *
+ * Return 0 if successful, or the return value of the failed video::s_stream
+ * operation otherwise.
+ */
+static int iss_pipeline_enable(struct iss_pipeline *pipe,
+ enum iss_pipeline_stream_state mode)
+{
+ struct iss_device *iss = pipe->output->iss;
+ struct media_entity *entity;
+ struct media_pad *pad;
+ struct v4l2_subdev *subdev;
+ unsigned long flags;
+ int ret;
+
+ /* If one of the entities in the pipeline has crashed it will not work
+ * properly. Refuse to start streaming in that case. This check must be
+ * performed before the loop below to avoid starting entities if the
+ * pipeline won't start anyway (those entities would then likely fail to
+ * stop, making the problem worse).
+ */
+ if (pipe->entities & iss->crashed)
+ return -EIO;
+
+ spin_lock_irqsave(&pipe->lock, flags);
+ pipe->state &= ~(ISS_PIPELINE_IDLE_INPUT | ISS_PIPELINE_IDLE_OUTPUT);
+ spin_unlock_irqrestore(&pipe->lock, flags);
+
+ pipe->do_propagation = false;
+
+ entity = &pipe->output->video.entity;
+ while (1) {
+ pad = &entity->pads[0];
+ if (!(pad->flags & MEDIA_PAD_FL_SINK))
+ break;
+
+ pad = media_entity_remote_pad(pad);
+ if (pad == NULL ||
+ media_entity_type(pad->entity) != MEDIA_ENT_T_V4L2_SUBDEV)
+ break;
+
+ entity = pad->entity;
+ subdev = media_entity_to_v4l2_subdev(entity);
+
+ ret = v4l2_subdev_call(subdev, video, s_stream, mode);
+ if (ret < 0 && ret != -ENOIOCTLCMD)
+ return ret;
+ }
+ iss_print_status(pipe->output->iss);
+ return 0;
+}
+
+/*
+ * iss_pipeline_disable - Disable streaming on a pipeline
+ * @pipe: ISS pipeline
+ *
+ * Walk the entities chain starting at the pipeline output video node and stop
+ * all modules in the chain. Wait synchronously for the modules to be stopped if
+ * necessary.
+ */
+static int iss_pipeline_disable(struct iss_pipeline *pipe)
+{
+ struct iss_device *iss = pipe->output->iss;
+ struct media_entity *entity;
+ struct media_pad *pad;
+ struct v4l2_subdev *subdev;
+ int failure = 0;
+ int ret;
+
+ entity = &pipe->output->video.entity;
+ while (1) {
+ pad = &entity->pads[0];
+ if (!(pad->flags & MEDIA_PAD_FL_SINK))
+ break;
+
+ pad = media_entity_remote_pad(pad);
+ if (pad == NULL ||
+ media_entity_type(pad->entity) != MEDIA_ENT_T_V4L2_SUBDEV)
+ break;
+
+ entity = pad->entity;
+ subdev = media_entity_to_v4l2_subdev(entity);
+
+ ret = v4l2_subdev_call(subdev, video, s_stream, 0);
+ if (ret < 0) {
+ dev_dbg(iss->dev, "%s: module stop timeout.\n",
+ subdev->name);
+ /* If the entity failed to stopped, assume it has
+ * crashed. Mark it as such, the ISS will be reset when
+ * applications will release it.
+ */
+ iss->crashed |= 1U << subdev->entity.id;
+ failure = -ETIMEDOUT;
+ }
+ }
+
+ return failure;
+}
+
+/*
+ * omap4iss_pipeline_set_stream - Enable/disable streaming on a pipeline
+ * @pipe: ISS pipeline
+ * @state: Stream state (stopped, single shot or continuous)
+ *
+ * Set the pipeline to the given stream state. Pipelines can be started in
+ * single-shot or continuous mode.
+ *
+ * Return 0 if successful, or the return value of the failed video::s_stream
+ * operation otherwise. The pipeline state is not updated when the operation
+ * fails, except when stopping the pipeline.
+ */
+int omap4iss_pipeline_set_stream(struct iss_pipeline *pipe,
+ enum iss_pipeline_stream_state state)
+{
+ int ret;
+
+ if (state == ISS_PIPELINE_STREAM_STOPPED)
+ ret = iss_pipeline_disable(pipe);
+ else
+ ret = iss_pipeline_enable(pipe, state);
+
+ if (ret == 0 || state == ISS_PIPELINE_STREAM_STOPPED)
+ pipe->stream_state = state;
+
+ return ret;
+}
+
+/*
+ * omap4iss_pipeline_cancel_stream - Cancel stream on a pipeline
+ * @pipe: ISS pipeline
+ *
+ * Cancelling a stream mark all buffers on all video nodes in the pipeline as
+ * erroneous and makes sure no new buffer can be queued. This function is called
+ * when a fatal error that prevents any further operation on the pipeline
+ * occurs.
+ */
+void omap4iss_pipeline_cancel_stream(struct iss_pipeline *pipe)
+{
+ if (pipe->input)
+ omap4iss_video_cancel_stream(pipe->input);
+ if (pipe->output)
+ omap4iss_video_cancel_stream(pipe->output);
+}
+
+/*
+ * iss_pipeline_is_last - Verify if entity has an enabled link to the output
+ * video node
+ * @me: ISS module's media entity
+ *
+ * Returns 1 if the entity has an enabled link to the output video node or 0
+ * otherwise. It's true only while pipeline can have no more than one output
+ * node.
+ */
+static int iss_pipeline_is_last(struct media_entity *me)
+{
+ struct iss_pipeline *pipe;
+ struct media_pad *pad;
+
+ if (!me->pipe)
+ return 0;
+ pipe = to_iss_pipeline(me);
+ if (pipe->stream_state == ISS_PIPELINE_STREAM_STOPPED)
+ return 0;
+ pad = media_entity_remote_pad(&pipe->output->pad);
+ return pad->entity == me;
+}
+
+static int iss_reset(struct iss_device *iss)
+{
+ unsigned int timeout;
+
+ iss_reg_set(iss, OMAP4_ISS_MEM_TOP, ISS_HL_SYSCONFIG,
+ ISS_HL_SYSCONFIG_SOFTRESET);
+
+ timeout = iss_poll_condition_timeout(
+ !(iss_reg_read(iss, OMAP4_ISS_MEM_TOP, ISS_HL_SYSCONFIG) &
+ ISS_HL_SYSCONFIG_SOFTRESET), 1000, 10, 100);
+ if (timeout) {
+ dev_err(iss->dev, "ISS reset timeout\n");
+ return -ETIMEDOUT;
+ }
+
+ iss->crashed = 0;
+ return 0;
+}
+
+static int iss_isp_reset(struct iss_device *iss)
+{
+ unsigned int timeout;
+
+ /* Fist, ensure that the ISP is IDLE (no transactions happening) */
+ iss_reg_update(iss, OMAP4_ISS_MEM_ISP_SYS1, ISP5_SYSCONFIG,
+ ISP5_SYSCONFIG_STANDBYMODE_MASK,
+ ISP5_SYSCONFIG_STANDBYMODE_SMART);
+
+ iss_reg_set(iss, OMAP4_ISS_MEM_ISP_SYS1, ISP5_CTRL, ISP5_CTRL_MSTANDBY);
+
+ timeout = iss_poll_condition_timeout(
+ iss_reg_read(iss, OMAP4_ISS_MEM_ISP_SYS1, ISP5_CTRL) &
+ ISP5_CTRL_MSTANDBY_WAIT, 1000000, 1000, 1500);
+ if (timeout) {
+ dev_err(iss->dev, "ISP5 standby timeout\n");
+ return -ETIMEDOUT;
+ }
+
+ /* Now finally, do the reset */
+ iss_reg_set(iss, OMAP4_ISS_MEM_ISP_SYS1, ISP5_SYSCONFIG,
+ ISP5_SYSCONFIG_SOFTRESET);
+
+ timeout = iss_poll_condition_timeout(
+ !(iss_reg_read(iss, OMAP4_ISS_MEM_ISP_SYS1, ISP5_SYSCONFIG) &
+ ISP5_SYSCONFIG_SOFTRESET), 1000000, 1000, 1500);
+ if (timeout) {
+ dev_err(iss->dev, "ISP5 reset timeout\n");
+ return -ETIMEDOUT;
+ }
+
+ return 0;
+}
+
+/*
+ * iss_module_sync_idle - Helper to sync module with its idle state
+ * @me: ISS submodule's media entity
+ * @wait: ISS submodule's wait queue for streamoff/interrupt synchronization
+ * @stopping: flag which tells module wants to stop
+ *
+ * This function checks if ISS submodule needs to wait for next interrupt. If
+ * yes, makes the caller to sleep while waiting for such event.
+ */
+int omap4iss_module_sync_idle(struct media_entity *me, wait_queue_head_t *wait,
+ atomic_t *stopping)
+{
+ struct iss_pipeline *pipe = to_iss_pipeline(me);
+ struct iss_video *video = pipe->output;
+ unsigned long flags;
+
+ if (pipe->stream_state == ISS_PIPELINE_STREAM_STOPPED ||
+ (pipe->stream_state == ISS_PIPELINE_STREAM_SINGLESHOT &&
+ !iss_pipeline_ready(pipe)))
+ return 0;
+
+ /*
+ * atomic_set() doesn't include memory barrier on ARM platform for SMP
+ * scenario. We'll call it here to avoid race conditions.
+ */
+ atomic_set(stopping, 1);
+ smp_wmb();
+
+ /*
+ * If module is the last one, it's writing to memory. In this case,
+ * it's necessary to check if the module is already paused due to
+ * DMA queue underrun or if it has to wait for next interrupt to be
+ * idle.
+ * If it isn't the last one, the function won't sleep but *stopping
+ * will still be set to warn next submodule caller's interrupt the
+ * module wants to be idle.
+ */
+ if (!iss_pipeline_is_last(me))
+ return 0;
+
+ spin_lock_irqsave(&video->qlock, flags);
+ if (video->dmaqueue_flags & ISS_VIDEO_DMAQUEUE_UNDERRUN) {
+ spin_unlock_irqrestore(&video->qlock, flags);
+ atomic_set(stopping, 0);
+ smp_wmb();
+ return 0;
+ }
+ spin_unlock_irqrestore(&video->qlock, flags);
+ if (!wait_event_timeout(*wait, !atomic_read(stopping),
+ msecs_to_jiffies(1000))) {
+ atomic_set(stopping, 0);
+ smp_wmb();
+ return -ETIMEDOUT;
+ }
+
+ return 0;
+}
+
+/*
+ * omap4iss_module_sync_is_stopped - Helper to verify if module was stopping
+ * @wait: ISS submodule's wait queue for streamoff/interrupt synchronization
+ * @stopping: flag which tells module wants to stop
+ *
+ * This function checks if ISS submodule was stopping. In case of yes, it
+ * notices the caller by setting stopping to 0 and waking up the wait queue.
+ * Returns 1 if it was stopping or 0 otherwise.
+ */
+int omap4iss_module_sync_is_stopping(wait_queue_head_t *wait,
+ atomic_t *stopping)
+{
+ if (atomic_cmpxchg(stopping, 1, 0)) {
+ wake_up(wait);
+ return 1;
+ }
+
+ return 0;
+}
+
+/* --------------------------------------------------------------------------
+ * Clock management
+ */
+
+#define ISS_CLKCTRL_MASK (ISS_CLKCTRL_CSI2_A |\
+ ISS_CLKCTRL_CSI2_B |\
+ ISS_CLKCTRL_ISP)
+
+static int __iss_subclk_update(struct iss_device *iss)
+{
+ u32 clk = 0;
+ int ret = 0, timeout = 1000;
+
+ if (iss->subclk_resources & OMAP4_ISS_SUBCLK_CSI2_A)
+ clk |= ISS_CLKCTRL_CSI2_A;
+
+ if (iss->subclk_resources & OMAP4_ISS_SUBCLK_CSI2_B)
+ clk |= ISS_CLKCTRL_CSI2_B;
+
+ if (iss->subclk_resources & OMAP4_ISS_SUBCLK_ISP)
+ clk |= ISS_CLKCTRL_ISP;
+
+ iss_reg_update(iss, OMAP4_ISS_MEM_TOP, ISS_CLKCTRL,
+ ISS_CLKCTRL_MASK, clk);
+
+ /* Wait for HW assertion */
+ while (--timeout > 0) {
+ udelay(1);
+ if ((iss_reg_read(iss, OMAP4_ISS_MEM_TOP, ISS_CLKSTAT) &
+ ISS_CLKCTRL_MASK) == clk)
+ break;
+ }
+
+ if (!timeout)
+ ret = -EBUSY;
+
+ return ret;
+}
+
+int omap4iss_subclk_enable(struct iss_device *iss,
+ enum iss_subclk_resource res)
+{
+ iss->subclk_resources |= res;
+
+ return __iss_subclk_update(iss);
+}
+
+int omap4iss_subclk_disable(struct iss_device *iss,
+ enum iss_subclk_resource res)
+{
+ iss->subclk_resources &= ~res;
+
+ return __iss_subclk_update(iss);
+}
+
+#define ISS_ISP5_CLKCTRL_MASK (ISP5_CTRL_BL_CLK_ENABLE |\
+ ISP5_CTRL_ISIF_CLK_ENABLE |\
+ ISP5_CTRL_H3A_CLK_ENABLE |\
+ ISP5_CTRL_RSZ_CLK_ENABLE |\
+ ISP5_CTRL_IPIPE_CLK_ENABLE |\
+ ISP5_CTRL_IPIPEIF_CLK_ENABLE)
+
+static void __iss_isp_subclk_update(struct iss_device *iss)
+{
+ u32 clk = 0;
+
+ if (iss->isp_subclk_resources & OMAP4_ISS_ISP_SUBCLK_ISIF)
+ clk |= ISP5_CTRL_ISIF_CLK_ENABLE;
+
+ if (iss->isp_subclk_resources & OMAP4_ISS_ISP_SUBCLK_H3A)
+ clk |= ISP5_CTRL_H3A_CLK_ENABLE;
+
+ if (iss->isp_subclk_resources & OMAP4_ISS_ISP_SUBCLK_RSZ)
+ clk |= ISP5_CTRL_RSZ_CLK_ENABLE;
+
+ if (iss->isp_subclk_resources & OMAP4_ISS_ISP_SUBCLK_IPIPE)
+ clk |= ISP5_CTRL_IPIPE_CLK_ENABLE;
+
+ if (iss->isp_subclk_resources & OMAP4_ISS_ISP_SUBCLK_IPIPEIF)
+ clk |= ISP5_CTRL_IPIPEIF_CLK_ENABLE;
+
+ if (clk)
+ clk |= ISP5_CTRL_BL_CLK_ENABLE;
+
+ iss_reg_update(iss, OMAP4_ISS_MEM_ISP_SYS1, ISP5_CTRL,
+ ISS_ISP5_CLKCTRL_MASK, clk);
+}
+
+void omap4iss_isp_subclk_enable(struct iss_device *iss,
+ enum iss_isp_subclk_resource res)
+{
+ iss->isp_subclk_resources |= res;
+
+ __iss_isp_subclk_update(iss);
+}
+
+void omap4iss_isp_subclk_disable(struct iss_device *iss,
+ enum iss_isp_subclk_resource res)
+{
+ iss->isp_subclk_resources &= ~res;
+
+ __iss_isp_subclk_update(iss);
+}
+
+/*
+ * iss_enable_clocks - Enable ISS clocks
+ * @iss: OMAP4 ISS device
+ *
+ * Return 0 if successful, or clk_enable return value if any of tthem fails.
+ */
+static int iss_enable_clocks(struct iss_device *iss)
+{
+ int ret;
+
+ ret = clk_enable(iss->iss_fck);
+ if (ret) {
+ dev_err(iss->dev, "clk_enable iss_fck failed\n");
+ return ret;
+ }
+
+ ret = clk_enable(iss->iss_ctrlclk);
+ if (ret) {
+ dev_err(iss->dev, "clk_enable iss_ctrlclk failed\n");
+ clk_disable(iss->iss_fck);
+ return ret;
+ }
+
+ return 0;
+}
+
+/*
+ * iss_disable_clocks - Disable ISS clocks
+ * @iss: OMAP4 ISS device
+ */
+static void iss_disable_clocks(struct iss_device *iss)
+{
+ clk_disable(iss->iss_ctrlclk);
+ clk_disable(iss->iss_fck);
+}
+
+static void iss_put_clocks(struct iss_device *iss)
+{
+ if (iss->iss_fck) {
+ clk_put(iss->iss_fck);
+ iss->iss_fck = NULL;
+ }
+
+ if (iss->iss_ctrlclk) {
+ clk_put(iss->iss_ctrlclk);
+ iss->iss_ctrlclk = NULL;
+ }
+}
+
+static int iss_get_clocks(struct iss_device *iss)
+{
+ iss->iss_fck = clk_get(iss->dev, "iss_fck");
+ if (IS_ERR(iss->iss_fck)) {
+ dev_err(iss->dev, "Unable to get iss_fck clock info\n");
+ iss_put_clocks(iss);
+ return PTR_ERR(iss->iss_fck);
+ }
+
+ iss->iss_ctrlclk = clk_get(iss->dev, "iss_ctrlclk");
+ if (IS_ERR(iss->iss_ctrlclk)) {
+ dev_err(iss->dev, "Unable to get iss_ctrlclk clock info\n");
+ iss_put_clocks(iss);
+ return PTR_ERR(iss->iss_fck);
+ }
+
+ return 0;
+}
+
+/*
+ * omap4iss_get - Acquire the ISS resource.
+ *
+ * Initializes the clocks for the first acquire.
+ *
+ * Increment the reference count on the ISS. If the first reference is taken,
+ * enable clocks and power-up all submodules.
+ *
+ * Return a pointer to the ISS device structure, or NULL if an error occurred.
+ */
+struct iss_device *omap4iss_get(struct iss_device *iss)
+{
+ struct iss_device *__iss = iss;
+
+ if (iss == NULL)
+ return NULL;
+
+ mutex_lock(&iss->iss_mutex);
+ if (iss->ref_count > 0)
+ goto out;
+
+ if (iss_enable_clocks(iss) < 0) {
+ __iss = NULL;
+ goto out;
+ }
+
+ iss_enable_interrupts(iss);
+
+out:
+ if (__iss != NULL)
+ iss->ref_count++;
+ mutex_unlock(&iss->iss_mutex);
+
+ return __iss;
+}
+
+/*
+ * omap4iss_put - Release the ISS
+ *
+ * Decrement the reference count on the ISS. If the last reference is released,
+ * power-down all submodules, disable clocks and free temporary buffers.
+ */
+void omap4iss_put(struct iss_device *iss)
+{
+ if (iss == NULL)
+ return;
+
+ mutex_lock(&iss->iss_mutex);
+ BUG_ON(iss->ref_count == 0);
+ if (--iss->ref_count == 0) {
+ iss_disable_interrupts(iss);
+ /* Reset the ISS if an entity has failed to stop. This is the
+ * only way to recover from such conditions, although it would
+ * be worth investigating whether resetting the ISP only can't
+ * fix the problem in some cases.
+ */
+ if (iss->crashed)
+ iss_reset(iss);
+ iss_disable_clocks(iss);
+ }
+ mutex_unlock(&iss->iss_mutex);
+}
+
+static int iss_map_mem_resource(struct platform_device *pdev,
+ struct iss_device *iss,
+ enum iss_mem_resources res)
+{
+ struct resource *mem;
+
+ /* request the mem region for the camera registers */
+
+ mem = platform_get_resource(pdev, IORESOURCE_MEM, res);
+ if (!mem) {
+ dev_err(iss->dev, "no mem resource?\n");
+ return -ENODEV;
+ }
+
+ if (!request_mem_region(mem->start, resource_size(mem), pdev->name)) {
+ dev_err(iss->dev,
+ "cannot reserve camera register I/O region\n");
+ return -ENODEV;
+ }
+ iss->res[res] = mem;
+
+ /* map the region */
+ iss->regs[res] = ioremap_nocache(mem->start, resource_size(mem));
+ if (!iss->regs[res]) {
+ dev_err(iss->dev, "cannot map camera register I/O region\n");
+ return -ENODEV;
+ }
+
+ return 0;
+}
+
+static void iss_unregister_entities(struct iss_device *iss)
+{
+ omap4iss_resizer_unregister_entities(&iss->resizer);
+ omap4iss_ipipe_unregister_entities(&iss->ipipe);
+ omap4iss_ipipeif_unregister_entities(&iss->ipipeif);
+ omap4iss_csi2_unregister_entities(&iss->csi2a);
+ omap4iss_csi2_unregister_entities(&iss->csi2b);
+
+ v4l2_device_unregister(&iss->v4l2_dev);
+ media_device_unregister(&iss->media_dev);
+}
+
+/*
+ * iss_register_subdev_group - Register a group of subdevices
+ * @iss: OMAP4 ISS device
+ * @board_info: I2C subdevs board information array
+ *
+ * Register all I2C subdevices in the board_info array. The array must be
+ * terminated by a NULL entry, and the first entry must be the sensor.
+ *
+ * Return a pointer to the sensor media entity if it has been successfully
+ * registered, or NULL otherwise.
+ */
+static struct v4l2_subdev *
+iss_register_subdev_group(struct iss_device *iss,
+ struct iss_subdev_i2c_board_info *board_info)
+{
+ struct v4l2_subdev *sensor = NULL;
+ unsigned int first;
+
+ if (board_info->board_info == NULL)
+ return NULL;
+
+ for (first = 1; board_info->board_info; ++board_info, first = 0) {
+ struct v4l2_subdev *subdev;
+ struct i2c_adapter *adapter;
+
+ adapter = i2c_get_adapter(board_info->i2c_adapter_id);
+ if (adapter == NULL) {
+ dev_err(iss->dev,
+ "%s: Unable to get I2C adapter %d for device %s\n",
+ __func__, board_info->i2c_adapter_id,
+ board_info->board_info->type);
+ continue;
+ }
+
+ subdev = v4l2_i2c_new_subdev_board(&iss->v4l2_dev, adapter,
+ board_info->board_info, NULL);
+ if (subdev == NULL) {
+ dev_err(iss->dev, "%s: Unable to register subdev %s\n",
+ __func__, board_info->board_info->type);
+ continue;
+ }
+
+ if (first)
+ sensor = subdev;
+ }
+
+ return sensor;
+}
+
+static int iss_register_entities(struct iss_device *iss)
+{
+ struct iss_platform_data *pdata = iss->pdata;
+ struct iss_v4l2_subdevs_group *subdevs;
+ int ret;
+
+ iss->media_dev.dev = iss->dev;
+ strlcpy(iss->media_dev.model, "TI OMAP4 ISS",
+ sizeof(iss->media_dev.model));
+ iss->media_dev.hw_revision = iss->revision;
+ iss->media_dev.link_notify = iss_pipeline_link_notify;
+ ret = media_device_register(&iss->media_dev);
+ if (ret < 0) {
+ dev_err(iss->dev, "%s: Media device registration failed (%d)\n",
+ __func__, ret);
+ return ret;
+ }
+
+ iss->v4l2_dev.mdev = &iss->media_dev;
+ ret = v4l2_device_register(iss->dev, &iss->v4l2_dev);
+ if (ret < 0) {
+ dev_err(iss->dev, "%s: V4L2 device registration failed (%d)\n",
+ __func__, ret);
+ goto done;
+ }
+
+ /* Register internal entities */
+ ret = omap4iss_csi2_register_entities(&iss->csi2a, &iss->v4l2_dev);
+ if (ret < 0)
+ goto done;
+
+ ret = omap4iss_csi2_register_entities(&iss->csi2b, &iss->v4l2_dev);
+ if (ret < 0)
+ goto done;
+
+ ret = omap4iss_ipipeif_register_entities(&iss->ipipeif, &iss->v4l2_dev);
+ if (ret < 0)
+ goto done;
+
+ ret = omap4iss_ipipe_register_entities(&iss->ipipe, &iss->v4l2_dev);
+ if (ret < 0)
+ goto done;
+
+ ret = omap4iss_resizer_register_entities(&iss->resizer, &iss->v4l2_dev);
+ if (ret < 0)
+ goto done;
+
+ /* Register external entities */
+ for (subdevs = pdata->subdevs; subdevs && subdevs->subdevs; ++subdevs) {
+ struct v4l2_subdev *sensor;
+ struct media_entity *input;
+ unsigned int flags;
+ unsigned int pad;
+
+ sensor = iss_register_subdev_group(iss, subdevs->subdevs);
+ if (sensor == NULL)
+ continue;
+
+ sensor->host_priv = subdevs;
+
+ /* Connect the sensor to the correct interface module.
+ * CSI2a receiver through CSIPHY1, or
+ * CSI2b receiver through CSIPHY2
+ */
+ switch (subdevs->interface) {
+ case ISS_INTERFACE_CSI2A_PHY1:
+ input = &iss->csi2a.subdev.entity;
+ pad = CSI2_PAD_SINK;
+ flags = MEDIA_LNK_FL_IMMUTABLE
+ | MEDIA_LNK_FL_ENABLED;
+ break;
+
+ case ISS_INTERFACE_CSI2B_PHY2:
+ input = &iss->csi2b.subdev.entity;
+ pad = CSI2_PAD_SINK;
+ flags = MEDIA_LNK_FL_IMMUTABLE
+ | MEDIA_LNK_FL_ENABLED;
+ break;
+
+ default:
+ dev_err(iss->dev, "%s: invalid interface type %u\n",
+ __func__, subdevs->interface);
+ ret = -EINVAL;
+ goto done;
+ }
+
+ ret = media_entity_create_link(&sensor->entity, 0, input, pad,
+ flags);
+ if (ret < 0)
+ goto done;
+ }
+
+ ret = v4l2_device_register_subdev_nodes(&iss->v4l2_dev);
+
+done:
+ if (ret < 0)
+ iss_unregister_entities(iss);
+
+ return ret;
+}
+
+static void iss_cleanup_modules(struct iss_device *iss)
+{
+ omap4iss_csi2_cleanup(iss);
+ omap4iss_ipipeif_cleanup(iss);
+ omap4iss_ipipe_cleanup(iss);
+ omap4iss_resizer_cleanup(iss);
+}
+
+static int iss_initialize_modules(struct iss_device *iss)
+{
+ int ret;
+
+ ret = omap4iss_csiphy_init(iss);
+ if (ret < 0) {
+ dev_err(iss->dev, "CSI PHY initialization failed\n");
+ goto error_csiphy;
+ }
+
+ ret = omap4iss_csi2_init(iss);
+ if (ret < 0) {
+ dev_err(iss->dev, "CSI2 initialization failed\n");
+ goto error_csi2;
+ }
+
+ ret = omap4iss_ipipeif_init(iss);
+ if (ret < 0) {
+ dev_err(iss->dev, "ISP IPIPEIF initialization failed\n");
+ goto error_ipipeif;
+ }
+
+ ret = omap4iss_ipipe_init(iss);
+ if (ret < 0) {
+ dev_err(iss->dev, "ISP IPIPE initialization failed\n");
+ goto error_ipipe;
+ }
+
+ ret = omap4iss_resizer_init(iss);
+ if (ret < 0) {
+ dev_err(iss->dev, "ISP RESIZER initialization failed\n");
+ goto error_resizer;
+ }
+
+ /* Connect the submodules. */
+ ret = media_entity_create_link(
+ &iss->csi2a.subdev.entity, CSI2_PAD_SOURCE,
+ &iss->ipipeif.subdev.entity, IPIPEIF_PAD_SINK, 0);
+ if (ret < 0)
+ goto error_link;
+
+ ret = media_entity_create_link(
+ &iss->csi2b.subdev.entity, CSI2_PAD_SOURCE,
+ &iss->ipipeif.subdev.entity, IPIPEIF_PAD_SINK, 0);
+ if (ret < 0)
+ goto error_link;
+
+ ret = media_entity_create_link(
+ &iss->ipipeif.subdev.entity, IPIPEIF_PAD_SOURCE_VP,
+ &iss->resizer.subdev.entity, RESIZER_PAD_SINK, 0);
+ if (ret < 0)
+ goto error_link;
+
+ ret = media_entity_create_link(
+ &iss->ipipeif.subdev.entity, IPIPEIF_PAD_SOURCE_VP,
+ &iss->ipipe.subdev.entity, IPIPE_PAD_SINK, 0);
+ if (ret < 0)
+ goto error_link;
+
+ ret = media_entity_create_link(
+ &iss->ipipe.subdev.entity, IPIPE_PAD_SOURCE_VP,
+ &iss->resizer.subdev.entity, RESIZER_PAD_SINK, 0);
+ if (ret < 0)
+ goto error_link;
+
+ return 0;
+
+error_link:
+ omap4iss_resizer_cleanup(iss);
+error_resizer:
+ omap4iss_ipipe_cleanup(iss);
+error_ipipe:
+ omap4iss_ipipeif_cleanup(iss);
+error_ipipeif:
+ omap4iss_csi2_cleanup(iss);
+error_csi2:
+error_csiphy:
+ return ret;
+}
+
+static int iss_probe(struct platform_device *pdev)
+{
+ struct iss_platform_data *pdata = pdev->dev.platform_data;
+ struct iss_device *iss;
+ unsigned int i;
+ int ret;
+
+ if (pdata == NULL)
+ return -EINVAL;
+
+ iss = kzalloc(sizeof(*iss), GFP_KERNEL);
+ if (!iss) {
+ dev_err(&pdev->dev, "Could not allocate memory\n");
+ return -ENOMEM;
+ }
+
+ mutex_init(&iss->iss_mutex);
+
+ iss->dev = &pdev->dev;
+ iss->pdata = pdata;
+
+ iss->raw_dmamask = DMA_BIT_MASK(32);
+ iss->dev->dma_mask = &iss->raw_dmamask;
+ iss->dev->coherent_dma_mask = DMA_BIT_MASK(32);
+
+ platform_set_drvdata(pdev, iss);
+
+ /* Clocks */
+ ret = iss_map_mem_resource(pdev, iss, OMAP4_ISS_MEM_TOP);
+ if (ret < 0)
+ goto error;
+
+ ret = iss_get_clocks(iss);
+ if (ret < 0)
+ goto error;
+
+ if (omap4iss_get(iss) == NULL)
+ goto error;
+
+ ret = iss_reset(iss);
+ if (ret < 0)
+ goto error_iss;
+
+ iss->revision = iss_reg_read(iss, OMAP4_ISS_MEM_TOP, ISS_HL_REVISION);
+ dev_info(iss->dev, "Revision %08x found\n", iss->revision);
+
+ for (i = 1; i < OMAP4_ISS_MEM_LAST; i++) {
+ ret = iss_map_mem_resource(pdev, iss, i);
+ if (ret)
+ goto error_iss;
+ }
+
+ /* Configure BTE BW_LIMITER field to max recommended value (1 GB) */
+ iss_reg_update(iss, OMAP4_ISS_MEM_BTE, BTE_CTRL,
+ BTE_CTRL_BW_LIMITER_MASK,
+ 18 << BTE_CTRL_BW_LIMITER_SHIFT);
+
+ /* Perform ISP reset */
+ ret = omap4iss_subclk_enable(iss, OMAP4_ISS_SUBCLK_ISP);
+ if (ret < 0)
+ goto error_iss;
+
+ ret = iss_isp_reset(iss);
+ if (ret < 0)
+ goto error_iss;
+
+ dev_info(iss->dev, "ISP Revision %08x found\n",
+ iss_reg_read(iss, OMAP4_ISS_MEM_ISP_SYS1, ISP5_REVISION));
+
+ /* Interrupt */
+ iss->irq_num = platform_get_irq(pdev, 0);
+ if (iss->irq_num <= 0) {
+ dev_err(iss->dev, "No IRQ resource\n");
+ ret = -ENODEV;
+ goto error_iss;
+ }
+
+ if (request_irq(iss->irq_num, iss_isr, IRQF_SHARED, "OMAP4 ISS", iss)) {
+ dev_err(iss->dev, "Unable to request IRQ\n");
+ ret = -EINVAL;
+ goto error_iss;
+ }
+
+ /* Entities */
+ ret = iss_initialize_modules(iss);
+ if (ret < 0)
+ goto error_irq;
+
+ ret = iss_register_entities(iss);
+ if (ret < 0)
+ goto error_modules;
+
+ omap4iss_put(iss);
+
+ return 0;
+
+error_modules:
+ iss_cleanup_modules(iss);
+error_irq:
+ free_irq(iss->irq_num, iss);
+error_iss:
+ omap4iss_put(iss);
+error:
+ iss_put_clocks(iss);
+
+ for (i = 0; i < OMAP4_ISS_MEM_LAST; i++) {
+ if (iss->regs[i]) {
+ iounmap(iss->regs[i]);
+ iss->regs[i] = NULL;
+ }
+
+ if (iss->res[i]) {
+ release_mem_region(iss->res[i]->start,
+ resource_size(iss->res[i]));
+ iss->res[i] = NULL;
+ }
+ }
+ platform_set_drvdata(pdev, NULL);
+
+ mutex_destroy(&iss->iss_mutex);
+ kfree(iss);
+
+ return ret;
+}
+
+static int iss_remove(struct platform_device *pdev)
+{
+ struct iss_device *iss = platform_get_drvdata(pdev);
+ unsigned int i;
+
+ iss_unregister_entities(iss);
+ iss_cleanup_modules(iss);
+
+ free_irq(iss->irq_num, iss);
+ iss_put_clocks(iss);
+
+ for (i = 0; i < OMAP4_ISS_MEM_LAST; i++) {
+ if (iss->regs[i]) {
+ iounmap(iss->regs[i]);
+ iss->regs[i] = NULL;
+ }
+
+ if (iss->res[i]) {
+ release_mem_region(iss->res[i]->start,
+ resource_size(iss->res[i]));
+ iss->res[i] = NULL;
+ }
+ }
+
+ kfree(iss);
+
+ return 0;
+}
+
+static struct platform_device_id omap4iss_id_table[] = {
+ { "omap4iss", 0 },
+ { },
+};
+MODULE_DEVICE_TABLE(platform, omap4iss_id_table);
+
+static struct platform_driver iss_driver = {
+ .probe = iss_probe,
+ .remove = iss_remove,
+ .id_table = omap4iss_id_table,
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "omap4iss",
+ },
+};
+
+module_platform_driver(iss_driver);
+
+MODULE_DESCRIPTION("TI OMAP4 ISS driver");
+MODULE_AUTHOR("Sergio Aguirre <sergio.a.aguirre@gmail.com>");
+MODULE_LICENSE("GPL");
+MODULE_VERSION(ISS_VIDEO_DRIVER_VERSION);
diff --git a/drivers/staging/media/omap4iss/iss.h b/drivers/staging/media/omap4iss/iss.h
new file mode 100644
index 00000000000..05cd9bf3b41
--- /dev/null
+++ b/drivers/staging/media/omap4iss/iss.h
@@ -0,0 +1,250 @@
+/*
+ * TI OMAP4 ISS V4L2 Driver
+ *
+ * Copyright (C) 2012 Texas Instruments.
+ *
+ * Author: Sergio Aguirre <sergio.a.aguirre@gmail.com>
+ *
+ * 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.
+ */
+
+#ifndef _OMAP4_ISS_H_
+#define _OMAP4_ISS_H_
+
+#include <media/v4l2-device.h>
+#include <linux/device.h>
+#include <linux/io.h>
+#include <linux/platform_device.h>
+#include <linux/wait.h>
+
+#include <media/omap4iss.h>
+
+#include "iss_regs.h"
+#include "iss_csiphy.h"
+#include "iss_csi2.h"
+#include "iss_ipipeif.h"
+#include "iss_ipipe.h"
+#include "iss_resizer.h"
+
+#define to_iss_device(ptr_module) \
+ container_of(ptr_module, struct iss_device, ptr_module)
+#define to_device(ptr_module) \
+ (to_iss_device(ptr_module)->dev)
+
+enum iss_mem_resources {
+ OMAP4_ISS_MEM_TOP,
+ OMAP4_ISS_MEM_CSI2_A_REGS1,
+ OMAP4_ISS_MEM_CAMERARX_CORE1,
+ OMAP4_ISS_MEM_CSI2_B_REGS1,
+ OMAP4_ISS_MEM_CAMERARX_CORE2,
+ OMAP4_ISS_MEM_BTE,
+ OMAP4_ISS_MEM_ISP_SYS1,
+ OMAP4_ISS_MEM_ISP_RESIZER,
+ OMAP4_ISS_MEM_ISP_IPIPE,
+ OMAP4_ISS_MEM_ISP_ISIF,
+ OMAP4_ISS_MEM_ISP_IPIPEIF,
+ OMAP4_ISS_MEM_LAST,
+};
+
+enum iss_subclk_resource {
+ OMAP4_ISS_SUBCLK_SIMCOP = (1 << 0),
+ OMAP4_ISS_SUBCLK_ISP = (1 << 1),
+ OMAP4_ISS_SUBCLK_CSI2_A = (1 << 2),
+ OMAP4_ISS_SUBCLK_CSI2_B = (1 << 3),
+ OMAP4_ISS_SUBCLK_CCP2 = (1 << 4),
+};
+
+enum iss_isp_subclk_resource {
+ OMAP4_ISS_ISP_SUBCLK_BL = (1 << 0),
+ OMAP4_ISS_ISP_SUBCLK_ISIF = (1 << 1),
+ OMAP4_ISS_ISP_SUBCLK_H3A = (1 << 2),
+ OMAP4_ISS_ISP_SUBCLK_RSZ = (1 << 3),
+ OMAP4_ISS_ISP_SUBCLK_IPIPE = (1 << 4),
+ OMAP4_ISS_ISP_SUBCLK_IPIPEIF = (1 << 5),
+};
+
+/*
+ * struct iss_reg - Structure for ISS register values.
+ * @reg: 32-bit Register address.
+ * @val: 32-bit Register value.
+ */
+struct iss_reg {
+ enum iss_mem_resources mmio_range;
+ u32 reg;
+ u32 val;
+};
+
+/*
+ * struct iss_device - ISS device structure.
+ * @crashed: Bitmask of crashed entities (indexed by entity ID)
+ */
+struct iss_device {
+ struct v4l2_device v4l2_dev;
+ struct media_device media_dev;
+ struct device *dev;
+ u32 revision;
+
+ /* platform HW resources */
+ struct iss_platform_data *pdata;
+ unsigned int irq_num;
+
+ struct resource *res[OMAP4_ISS_MEM_LAST];
+ void __iomem *regs[OMAP4_ISS_MEM_LAST];
+
+ u64 raw_dmamask;
+
+ struct mutex iss_mutex; /* For handling ref_count field */
+ bool crashed;
+ int has_context;
+ int ref_count;
+
+ struct clk *iss_fck;
+ struct clk *iss_ctrlclk;
+
+ /* ISS modules */
+ struct iss_csi2_device csi2a;
+ struct iss_csi2_device csi2b;
+ struct iss_csiphy csiphy1;
+ struct iss_csiphy csiphy2;
+ struct iss_ipipeif_device ipipeif;
+ struct iss_ipipe_device ipipe;
+ struct iss_resizer_device resizer;
+
+ unsigned int subclk_resources;
+ unsigned int isp_subclk_resources;
+};
+
+#define v4l2_dev_to_iss_device(dev) \
+ container_of(dev, struct iss_device, v4l2_dev)
+
+int omap4iss_get_external_info(struct iss_pipeline *pipe,
+ struct media_link *link);
+
+int omap4iss_module_sync_idle(struct media_entity *me, wait_queue_head_t *wait,
+ atomic_t *stopping);
+
+int omap4iss_module_sync_is_stopping(wait_queue_head_t *wait,
+ atomic_t *stopping);
+
+int omap4iss_pipeline_set_stream(struct iss_pipeline *pipe,
+ enum iss_pipeline_stream_state state);
+void omap4iss_pipeline_cancel_stream(struct iss_pipeline *pipe);
+
+void omap4iss_configure_bridge(struct iss_device *iss,
+ enum ipipeif_input_entity input);
+
+struct iss_device *omap4iss_get(struct iss_device *iss);
+void omap4iss_put(struct iss_device *iss);
+int omap4iss_subclk_enable(struct iss_device *iss,
+ enum iss_subclk_resource res);
+int omap4iss_subclk_disable(struct iss_device *iss,
+ enum iss_subclk_resource res);
+void omap4iss_isp_subclk_enable(struct iss_device *iss,
+ enum iss_isp_subclk_resource res);
+void omap4iss_isp_subclk_disable(struct iss_device *iss,
+ enum iss_isp_subclk_resource res);
+
+int omap4iss_pipeline_pm_use(struct media_entity *entity, int use);
+
+int omap4iss_register_entities(struct platform_device *pdev,
+ struct v4l2_device *v4l2_dev);
+void omap4iss_unregister_entities(struct platform_device *pdev);
+
+/*
+ * iss_reg_read - Read the value of an OMAP4 ISS register
+ * @iss: the ISS device
+ * @res: memory resource in which the register is located
+ * @offset: register offset in the memory resource
+ *
+ * Return the register value.
+ */
+static inline
+u32 iss_reg_read(struct iss_device *iss, enum iss_mem_resources res,
+ u32 offset)
+{
+ return readl(iss->regs[res] + offset);
+}
+
+/*
+ * iss_reg_write - Write a value to an OMAP4 ISS register
+ * @iss: the ISS device
+ * @res: memory resource in which the register is located
+ * @offset: register offset in the memory resource
+ * @value: value to be written
+ */
+static inline
+void iss_reg_write(struct iss_device *iss, enum iss_mem_resources res,
+ u32 offset, u32 value)
+{
+ writel(value, iss->regs[res] + offset);
+}
+
+/*
+ * iss_reg_clr - Clear bits in an OMAP4 ISS register
+ * @iss: the ISS device
+ * @res: memory resource in which the register is located
+ * @offset: register offset in the memory resource
+ * @clr: bit mask to be cleared
+ */
+static inline
+void iss_reg_clr(struct iss_device *iss, enum iss_mem_resources res,
+ u32 offset, u32 clr)
+{
+ u32 v = iss_reg_read(iss, res, offset);
+
+ iss_reg_write(iss, res, offset, v & ~clr);
+}
+
+/*
+ * iss_reg_set - Set bits in an OMAP4 ISS register
+ * @iss: the ISS device
+ * @res: memory resource in which the register is located
+ * @offset: register offset in the memory resource
+ * @set: bit mask to be set
+ */
+static inline
+void iss_reg_set(struct iss_device *iss, enum iss_mem_resources res,
+ u32 offset, u32 set)
+{
+ u32 v = iss_reg_read(iss, res, offset);
+
+ iss_reg_write(iss, res, offset, v | set);
+}
+
+/*
+ * iss_reg_update - Clear and set bits in an OMAP4 ISS register
+ * @iss: the ISS device
+ * @res: memory resource in which the register is located
+ * @offset: register offset in the memory resource
+ * @clr: bit mask to be cleared
+ * @set: bit mask to be set
+ *
+ * Clear the clr mask first and then set the set mask.
+ */
+static inline
+void iss_reg_update(struct iss_device *iss, enum iss_mem_resources res,
+ u32 offset, u32 clr, u32 set)
+{
+ u32 v = iss_reg_read(iss, res, offset);
+
+ iss_reg_write(iss, res, offset, (v & ~clr) | set);
+}
+
+#define iss_poll_condition_timeout(cond, timeout, min_ival, max_ival) \
+({ \
+ unsigned long __timeout = jiffies + usecs_to_jiffies(timeout); \
+ unsigned int __min_ival = (min_ival); \
+ unsigned int __max_ival = (max_ival); \
+ bool __cond; \
+ while (!(__cond = (cond))) { \
+ if (time_after(jiffies, __timeout)) \
+ break; \
+ usleep_range(__min_ival, __max_ival); \
+ } \
+ !__cond; \
+})
+
+#endif /* _OMAP4_ISS_H_ */
diff --git a/drivers/staging/media/omap4iss/iss_csi2.c b/drivers/staging/media/omap4iss/iss_csi2.c
new file mode 100644
index 00000000000..bf8a6572610
--- /dev/null
+++ b/drivers/staging/media/omap4iss/iss_csi2.c
@@ -0,0 +1,1326 @@
+/*
+ * TI OMAP4 ISS V4L2 Driver - CSI PHY module
+ *
+ * Copyright (C) 2012 Texas Instruments, Inc.
+ *
+ * Author: Sergio Aguirre <sergio.a.aguirre@gmail.com>
+ *
+ * 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.
+ */
+
+#include <linux/delay.h>
+#include <media/v4l2-common.h>
+#include <linux/v4l2-mediabus.h>
+#include <linux/mm.h>
+
+#include "iss.h"
+#include "iss_regs.h"
+#include "iss_csi2.h"
+
+/*
+ * csi2_if_enable - Enable CSI2 Receiver interface.
+ * @enable: enable flag
+ *
+ */
+static void csi2_if_enable(struct iss_csi2_device *csi2, u8 enable)
+{
+ struct iss_csi2_ctrl_cfg *currctrl = &csi2->ctrl;
+
+ iss_reg_update(csi2->iss, csi2->regs1, CSI2_CTRL, CSI2_CTRL_IF_EN,
+ enable ? CSI2_CTRL_IF_EN : 0);
+
+ currctrl->if_enable = enable;
+}
+
+/*
+ * csi2_recv_config - CSI2 receiver module configuration.
+ * @currctrl: iss_csi2_ctrl_cfg structure
+ *
+ */
+static void csi2_recv_config(struct iss_csi2_device *csi2,
+ struct iss_csi2_ctrl_cfg *currctrl)
+{
+ u32 reg = 0;
+
+ if (currctrl->frame_mode)
+ reg |= CSI2_CTRL_FRAME;
+ else
+ reg &= ~CSI2_CTRL_FRAME;
+
+ if (currctrl->vp_clk_enable)
+ reg |= CSI2_CTRL_VP_CLK_EN;
+ else
+ reg &= ~CSI2_CTRL_VP_CLK_EN;
+
+ if (currctrl->vp_only_enable)
+ reg |= CSI2_CTRL_VP_ONLY_EN;
+ else
+ reg &= ~CSI2_CTRL_VP_ONLY_EN;
+
+ reg &= ~CSI2_CTRL_VP_OUT_CTRL_MASK;
+ reg |= currctrl->vp_out_ctrl << CSI2_CTRL_VP_OUT_CTRL_SHIFT;
+
+ if (currctrl->ecc_enable)
+ reg |= CSI2_CTRL_ECC_EN;
+ else
+ reg &= ~CSI2_CTRL_ECC_EN;
+
+ /*
+ * Set MFlag assertion boundaries to:
+ * Low: 4/8 of FIFO size
+ * High: 6/8 of FIFO size
+ */
+ reg &= ~(CSI2_CTRL_MFLAG_LEVH_MASK | CSI2_CTRL_MFLAG_LEVL_MASK);
+ reg |= (2 << CSI2_CTRL_MFLAG_LEVH_SHIFT) |
+ (4 << CSI2_CTRL_MFLAG_LEVL_SHIFT);
+
+ /* Generation of 16x64-bit bursts (Recommended) */
+ reg |= CSI2_CTRL_BURST_SIZE_EXPAND;
+
+ /* Do Non-Posted writes (Recommended) */
+ reg |= CSI2_CTRL_NON_POSTED_WRITE;
+
+ /*
+ * Enforce Little endian for all formats, including:
+ * YUV4:2:2 8-bit and YUV4:2:0 Legacy
+ */
+ reg |= CSI2_CTRL_ENDIANNESS;
+
+ iss_reg_write(csi2->iss, csi2->regs1, CSI2_CTRL, reg);
+}
+
+static const unsigned int csi2_input_fmts[] = {
+ V4L2_MBUS_FMT_SGRBG10_1X10,
+ V4L2_MBUS_FMT_SGRBG10_DPCM8_1X8,
+ V4L2_MBUS_FMT_SRGGB10_1X10,
+ V4L2_MBUS_FMT_SRGGB10_DPCM8_1X8,
+ V4L2_MBUS_FMT_SBGGR10_1X10,
+ V4L2_MBUS_FMT_SBGGR10_DPCM8_1X8,
+ V4L2_MBUS_FMT_SGBRG10_1X10,
+ V4L2_MBUS_FMT_SGBRG10_DPCM8_1X8,
+ V4L2_MBUS_FMT_SBGGR8_1X8,
+ V4L2_MBUS_FMT_SGBRG8_1X8,
+ V4L2_MBUS_FMT_SGRBG8_1X8,
+ V4L2_MBUS_FMT_SRGGB8_1X8,
+ V4L2_MBUS_FMT_UYVY8_1X16,
+ V4L2_MBUS_FMT_YUYV8_1X16,
+};
+
+/* To set the format on the CSI2 requires a mapping function that takes
+ * the following inputs:
+ * - 3 different formats (at this time)
+ * - 2 destinations (mem, vp+mem) (vp only handled separately)
+ * - 2 decompression options (on, off)
+ * Output should be CSI2 frame format code
+ * Array indices as follows: [format][dest][decompr]
+ * Not all combinations are valid. 0 means invalid.
+ */
+static const u16 __csi2_fmt_map[][2][2] = {
+ /* RAW10 formats */
+ {
+ /* Output to memory */
+ {
+ /* No DPCM decompression */
+ CSI2_PIX_FMT_RAW10_EXP16,
+ /* DPCM decompression */
+ 0,
+ },
+ /* Output to both */
+ {
+ /* No DPCM decompression */
+ CSI2_PIX_FMT_RAW10_EXP16_VP,
+ /* DPCM decompression */
+ 0,
+ },
+ },
+ /* RAW10 DPCM8 formats */
+ {
+ /* Output to memory */
+ {
+ /* No DPCM decompression */
+ CSI2_USERDEF_8BIT_DATA1,
+ /* DPCM decompression */
+ CSI2_USERDEF_8BIT_DATA1_DPCM10,
+ },
+ /* Output to both */
+ {
+ /* No DPCM decompression */
+ CSI2_PIX_FMT_RAW8_VP,
+ /* DPCM decompression */
+ CSI2_USERDEF_8BIT_DATA1_DPCM10_VP,
+ },
+ },
+ /* RAW8 formats */
+ {
+ /* Output to memory */
+ {
+ /* No DPCM decompression */
+ CSI2_PIX_FMT_RAW8,
+ /* DPCM decompression */
+ 0,
+ },
+ /* Output to both */
+ {
+ /* No DPCM decompression */
+ CSI2_PIX_FMT_RAW8_VP,
+ /* DPCM decompression */
+ 0,
+ },
+ },
+ /* YUV422 formats */
+ {
+ /* Output to memory */
+ {
+ /* No DPCM decompression */
+ CSI2_PIX_FMT_YUV422_8BIT,
+ /* DPCM decompression */
+ 0,
+ },
+ /* Output to both */
+ {
+ /* No DPCM decompression */
+ CSI2_PIX_FMT_YUV422_8BIT_VP16,
+ /* DPCM decompression */
+ 0,
+ },
+ },
+};
+
+/*
+ * csi2_ctx_map_format - Map CSI2 sink media bus format to CSI2 format ID
+ * @csi2: ISS CSI2 device
+ *
+ * Returns CSI2 physical format id
+ */
+static u16 csi2_ctx_map_format(struct iss_csi2_device *csi2)
+{
+ const struct v4l2_mbus_framefmt *fmt = &csi2->formats[CSI2_PAD_SINK];
+ int fmtidx, destidx;
+
+ switch (fmt->code) {
+ case V4L2_MBUS_FMT_SGRBG10_1X10:
+ case V4L2_MBUS_FMT_SRGGB10_1X10:
+ case V4L2_MBUS_FMT_SBGGR10_1X10:
+ case V4L2_MBUS_FMT_SGBRG10_1X10:
+ fmtidx = 0;
+ break;
+ case V4L2_MBUS_FMT_SGRBG10_DPCM8_1X8:
+ case V4L2_MBUS_FMT_SRGGB10_DPCM8_1X8:
+ case V4L2_MBUS_FMT_SBGGR10_DPCM8_1X8:
+ case V4L2_MBUS_FMT_SGBRG10_DPCM8_1X8:
+ fmtidx = 1;
+ break;
+ case V4L2_MBUS_FMT_SBGGR8_1X8:
+ case V4L2_MBUS_FMT_SGBRG8_1X8:
+ case V4L2_MBUS_FMT_SGRBG8_1X8:
+ case V4L2_MBUS_FMT_SRGGB8_1X8:
+ fmtidx = 2;
+ break;
+ case V4L2_MBUS_FMT_UYVY8_1X16:
+ case V4L2_MBUS_FMT_YUYV8_1X16:
+ fmtidx = 3;
+ break;
+ default:
+ WARN(1, KERN_ERR "CSI2: pixel format %08x unsupported!\n",
+ fmt->code);
+ return 0;
+ }
+
+ if (!(csi2->output & CSI2_OUTPUT_IPIPEIF) &&
+ !(csi2->output & CSI2_OUTPUT_MEMORY)) {
+ /* Neither output enabled is a valid combination */
+ return CSI2_PIX_FMT_OTHERS;
+ }
+
+ /* If we need to skip frames at the beginning of the stream disable the
+ * video port to avoid sending the skipped frames to the IPIPEIF.
+ */
+ destidx = csi2->frame_skip ? 0 : !!(csi2->output & CSI2_OUTPUT_IPIPEIF);
+
+ return __csi2_fmt_map[fmtidx][destidx][csi2->dpcm_decompress];
+}
+
+/*
+ * csi2_set_outaddr - Set memory address to save output image
+ * @csi2: Pointer to ISS CSI2a device.
+ * @addr: 32-bit memory address aligned on 32 byte boundary.
+ *
+ * Sets the memory address where the output will be saved.
+ *
+ * Returns 0 if successful, or -EINVAL if the address is not in the 32 byte
+ * boundary.
+ */
+static void csi2_set_outaddr(struct iss_csi2_device *csi2, u32 addr)
+{
+ struct iss_csi2_ctx_cfg *ctx = &csi2->contexts[0];
+
+ ctx->ping_addr = addr;
+ ctx->pong_addr = addr;
+ iss_reg_write(csi2->iss, csi2->regs1, CSI2_CTX_PING_ADDR(ctx->ctxnum),
+ ctx->ping_addr);
+ iss_reg_write(csi2->iss, csi2->regs1, CSI2_CTX_PONG_ADDR(ctx->ctxnum),
+ ctx->pong_addr);
+}
+
+/*
+ * is_usr_def_mapping - Checks whether USER_DEF_MAPPING should
+ * be enabled by CSI2.
+ * @format_id: mapped format id
+ *
+ */
+static inline int is_usr_def_mapping(u32 format_id)
+{
+ return (format_id & 0xf0) == 0x40 ? 1 : 0;
+}
+
+/*
+ * csi2_ctx_enable - Enable specified CSI2 context
+ * @ctxnum: Context number, valid between 0 and 7 values.
+ * @enable: enable
+ *
+ */
+static void csi2_ctx_enable(struct iss_csi2_device *csi2, u8 ctxnum, u8 enable)
+{
+ struct iss_csi2_ctx_cfg *ctx = &csi2->contexts[ctxnum];
+ u32 reg;
+
+ reg = iss_reg_read(csi2->iss, csi2->regs1, CSI2_CTX_CTRL1(ctxnum));
+
+ if (enable) {
+ unsigned int skip = 0;
+
+ if (csi2->frame_skip)
+ skip = csi2->frame_skip;
+ else if (csi2->output & CSI2_OUTPUT_MEMORY)
+ skip = 1;
+
+ reg &= ~CSI2_CTX_CTRL1_COUNT_MASK;
+ reg |= CSI2_CTX_CTRL1_COUNT_UNLOCK
+ | (skip << CSI2_CTX_CTRL1_COUNT_SHIFT)
+ | CSI2_CTX_CTRL1_CTX_EN;
+ } else {
+ reg &= ~CSI2_CTX_CTRL1_CTX_EN;
+ }
+
+ iss_reg_write(csi2->iss, csi2->regs1, CSI2_CTX_CTRL1(ctxnum), reg);
+ ctx->enabled = enable;
+}
+
+/*
+ * csi2_ctx_config - CSI2 context configuration.
+ * @ctx: context configuration
+ *
+ */
+static void csi2_ctx_config(struct iss_csi2_device *csi2,
+ struct iss_csi2_ctx_cfg *ctx)
+{
+ u32 reg;
+
+ /* Set up CSI2_CTx_CTRL1 */
+ if (ctx->eof_enabled)
+ reg = CSI2_CTX_CTRL1_EOF_EN;
+
+ if (ctx->eol_enabled)
+ reg |= CSI2_CTX_CTRL1_EOL_EN;
+
+ if (ctx->checksum_enabled)
+ reg |= CSI2_CTX_CTRL1_CS_EN;
+
+ iss_reg_write(csi2->iss, csi2->regs1, CSI2_CTX_CTRL1(ctx->ctxnum), reg);
+
+ /* Set up CSI2_CTx_CTRL2 */
+ reg = ctx->virtual_id << CSI2_CTX_CTRL2_VIRTUAL_ID_SHIFT;
+ reg |= ctx->format_id << CSI2_CTX_CTRL2_FORMAT_SHIFT;
+
+ if (ctx->dpcm_decompress && ctx->dpcm_predictor)
+ reg |= CSI2_CTX_CTRL2_DPCM_PRED;
+
+ if (is_usr_def_mapping(ctx->format_id))
+ reg |= 2 << CSI2_CTX_CTRL2_USER_DEF_MAP_SHIFT;
+
+ iss_reg_write(csi2->iss, csi2->regs1, CSI2_CTX_CTRL2(ctx->ctxnum), reg);
+
+ /* Set up CSI2_CTx_CTRL3 */
+ iss_reg_write(csi2->iss, csi2->regs1, CSI2_CTX_CTRL3(ctx->ctxnum),
+ ctx->alpha << CSI2_CTX_CTRL3_ALPHA_SHIFT);
+
+ /* Set up CSI2_CTx_DAT_OFST */
+ iss_reg_update(csi2->iss, csi2->regs1, CSI2_CTX_DAT_OFST(ctx->ctxnum),
+ CSI2_CTX_DAT_OFST_MASK, ctx->data_offset);
+
+ iss_reg_write(csi2->iss, csi2->regs1, CSI2_CTX_PING_ADDR(ctx->ctxnum),
+ ctx->ping_addr);
+ iss_reg_write(csi2->iss, csi2->regs1, CSI2_CTX_PONG_ADDR(ctx->ctxnum),
+ ctx->pong_addr);
+}
+
+/*
+ * csi2_timing_config - CSI2 timing configuration.
+ * @timing: csi2_timing_cfg structure
+ */
+static void csi2_timing_config(struct iss_csi2_device *csi2,
+ struct iss_csi2_timing_cfg *timing)
+{
+ u32 reg;
+
+ reg = iss_reg_read(csi2->iss, csi2->regs1, CSI2_TIMING);
+
+ if (timing->force_rx_mode)
+ reg |= CSI2_TIMING_FORCE_RX_MODE_IO1;
+ else
+ reg &= ~CSI2_TIMING_FORCE_RX_MODE_IO1;
+
+ if (timing->stop_state_16x)
+ reg |= CSI2_TIMING_STOP_STATE_X16_IO1;
+ else
+ reg &= ~CSI2_TIMING_STOP_STATE_X16_IO1;
+
+ if (timing->stop_state_4x)
+ reg |= CSI2_TIMING_STOP_STATE_X4_IO1;
+ else
+ reg &= ~CSI2_TIMING_STOP_STATE_X4_IO1;
+
+ reg &= ~CSI2_TIMING_STOP_STATE_COUNTER_IO1_MASK;
+ reg |= timing->stop_state_counter <<
+ CSI2_TIMING_STOP_STATE_COUNTER_IO1_SHIFT;
+
+ iss_reg_write(csi2->iss, csi2->regs1, CSI2_TIMING, reg);
+}
+
+/*
+ * csi2_irq_ctx_set - Enables CSI2 Context IRQs.
+ * @enable: Enable/disable CSI2 Context interrupts
+ */
+static void csi2_irq_ctx_set(struct iss_csi2_device *csi2, int enable)
+{
+ u32 reg = CSI2_CTX_IRQ_FE;
+ int i;
+
+ if (csi2->use_fs_irq)
+ reg |= CSI2_CTX_IRQ_FS;
+
+ for (i = 0; i < 8; i++) {
+ iss_reg_write(csi2->iss, csi2->regs1, CSI2_CTX_IRQSTATUS(i),
+ reg);
+ if (enable)
+ iss_reg_set(csi2->iss, csi2->regs1,
+ CSI2_CTX_IRQENABLE(i), reg);
+ else
+ iss_reg_clr(csi2->iss, csi2->regs1,
+ CSI2_CTX_IRQENABLE(i), reg);
+ }
+}
+
+/*
+ * csi2_irq_complexio1_set - Enables CSI2 ComplexIO IRQs.
+ * @enable: Enable/disable CSI2 ComplexIO #1 interrupts
+ */
+static void csi2_irq_complexio1_set(struct iss_csi2_device *csi2, int enable)
+{
+ u32 reg;
+ reg = CSI2_COMPLEXIO_IRQ_STATEALLULPMEXIT |
+ CSI2_COMPLEXIO_IRQ_STATEALLULPMENTER |
+ CSI2_COMPLEXIO_IRQ_STATEULPM5 |
+ CSI2_COMPLEXIO_IRQ_ERRCONTROL5 |
+ CSI2_COMPLEXIO_IRQ_ERRESC5 |
+ CSI2_COMPLEXIO_IRQ_ERRSOTSYNCHS5 |
+ CSI2_COMPLEXIO_IRQ_ERRSOTHS5 |
+ CSI2_COMPLEXIO_IRQ_STATEULPM4 |
+ CSI2_COMPLEXIO_IRQ_ERRCONTROL4 |
+ CSI2_COMPLEXIO_IRQ_ERRESC4 |
+ CSI2_COMPLEXIO_IRQ_ERRSOTSYNCHS4 |
+ CSI2_COMPLEXIO_IRQ_ERRSOTHS4 |
+ CSI2_COMPLEXIO_IRQ_STATEULPM3 |
+ CSI2_COMPLEXIO_IRQ_ERRCONTROL3 |
+ CSI2_COMPLEXIO_IRQ_ERRESC3 |
+ CSI2_COMPLEXIO_IRQ_ERRSOTSYNCHS3 |
+ CSI2_COMPLEXIO_IRQ_ERRSOTHS3 |
+ CSI2_COMPLEXIO_IRQ_STATEULPM2 |
+ CSI2_COMPLEXIO_IRQ_ERRCONTROL2 |
+ CSI2_COMPLEXIO_IRQ_ERRESC2 |
+ CSI2_COMPLEXIO_IRQ_ERRSOTSYNCHS2 |
+ CSI2_COMPLEXIO_IRQ_ERRSOTHS2 |
+ CSI2_COMPLEXIO_IRQ_STATEULPM1 |
+ CSI2_COMPLEXIO_IRQ_ERRCONTROL1 |
+ CSI2_COMPLEXIO_IRQ_ERRESC1 |
+ CSI2_COMPLEXIO_IRQ_ERRSOTSYNCHS1 |
+ CSI2_COMPLEXIO_IRQ_ERRSOTHS1;
+ iss_reg_write(csi2->iss, csi2->regs1, CSI2_COMPLEXIO_IRQSTATUS, reg);
+ if (enable)
+ iss_reg_set(csi2->iss, csi2->regs1, CSI2_COMPLEXIO_IRQENABLE,
+ reg);
+ else
+ iss_reg_write(csi2->iss, csi2->regs1, CSI2_COMPLEXIO_IRQENABLE,
+ 0);
+}
+
+/*
+ * csi2_irq_status_set - Enables CSI2 Status IRQs.
+ * @enable: Enable/disable CSI2 Status interrupts
+ */
+static void csi2_irq_status_set(struct iss_csi2_device *csi2, int enable)
+{
+ u32 reg;
+ reg = CSI2_IRQ_OCP_ERR |
+ CSI2_IRQ_SHORT_PACKET |
+ CSI2_IRQ_ECC_CORRECTION |
+ CSI2_IRQ_ECC_NO_CORRECTION |
+ CSI2_IRQ_COMPLEXIO_ERR |
+ CSI2_IRQ_FIFO_OVF |
+ CSI2_IRQ_CONTEXT0;
+ iss_reg_write(csi2->iss, csi2->regs1, CSI2_IRQSTATUS, reg);
+ if (enable)
+ iss_reg_set(csi2->iss, csi2->regs1, CSI2_IRQENABLE, reg);
+ else
+ iss_reg_write(csi2->iss, csi2->regs1, CSI2_IRQENABLE, 0);
+}
+
+/*
+ * omap4iss_csi2_reset - Resets the CSI2 module.
+ *
+ * Must be called with the phy lock held.
+ *
+ * Returns 0 if successful, or -EBUSY if power command didn't respond.
+ */
+int omap4iss_csi2_reset(struct iss_csi2_device *csi2)
+{
+ unsigned int timeout;
+
+ if (!csi2->available)
+ return -ENODEV;
+
+ if (csi2->phy->phy_in_use)
+ return -EBUSY;
+
+ iss_reg_set(csi2->iss, csi2->regs1, CSI2_SYSCONFIG,
+ CSI2_SYSCONFIG_SOFT_RESET);
+
+ timeout = iss_poll_condition_timeout(
+ iss_reg_read(csi2->iss, csi2->regs1, CSI2_SYSSTATUS) &
+ CSI2_SYSSTATUS_RESET_DONE, 500, 100, 200);
+ if (timeout) {
+ dev_err(csi2->iss->dev, "CSI2: Soft reset timeout!\n");
+ return -EBUSY;
+ }
+
+ iss_reg_set(csi2->iss, csi2->regs1, CSI2_COMPLEXIO_CFG,
+ CSI2_COMPLEXIO_CFG_RESET_CTRL);
+
+ timeout = iss_poll_condition_timeout(
+ iss_reg_read(csi2->iss, csi2->phy->phy_regs, REGISTER1) &
+ REGISTER1_RESET_DONE_CTRLCLK, 10000, 100, 500);
+ if (timeout) {
+ dev_err(csi2->iss->dev, "CSI2: CSI2_96M_FCLK reset timeout!\n");
+ return -EBUSY;
+ }
+
+ iss_reg_update(csi2->iss, csi2->regs1, CSI2_SYSCONFIG,
+ CSI2_SYSCONFIG_MSTANDBY_MODE_MASK |
+ CSI2_SYSCONFIG_AUTO_IDLE,
+ CSI2_SYSCONFIG_MSTANDBY_MODE_NO);
+
+ return 0;
+}
+
+static int csi2_configure(struct iss_csi2_device *csi2)
+{
+ const struct iss_v4l2_subdevs_group *pdata;
+ struct iss_csi2_timing_cfg *timing = &csi2->timing[0];
+ struct v4l2_subdev *sensor;
+ struct media_pad *pad;
+
+ /*
+ * CSI2 fields that can be updated while the context has
+ * been enabled or the interface has been enabled are not
+ * updated dynamically currently. So we do not allow to
+ * reconfigure if either has been enabled
+ */
+ if (csi2->contexts[0].enabled || csi2->ctrl.if_enable)
+ return -EBUSY;
+
+ pad = media_entity_remote_pad(&csi2->pads[CSI2_PAD_SINK]);
+ sensor = media_entity_to_v4l2_subdev(pad->entity);
+ pdata = sensor->host_priv;
+
+ csi2->frame_skip = 0;
+ v4l2_subdev_call(sensor, sensor, g_skip_frames, &csi2->frame_skip);
+
+ csi2->ctrl.vp_out_ctrl = pdata->bus.csi2.vpclk_div;
+ csi2->ctrl.frame_mode = ISS_CSI2_FRAME_IMMEDIATE;
+ csi2->ctrl.ecc_enable = pdata->bus.csi2.crc;
+
+ timing->force_rx_mode = 1;
+ timing->stop_state_16x = 1;
+ timing->stop_state_4x = 1;
+ timing->stop_state_counter = 0x1ff;
+
+ /*
+ * The CSI2 receiver can't do any format conversion except DPCM
+ * decompression, so every set_format call configures both pads
+ * and enables DPCM decompression as a special case:
+ */
+ if (csi2->formats[CSI2_PAD_SINK].code !=
+ csi2->formats[CSI2_PAD_SOURCE].code)
+ csi2->dpcm_decompress = true;
+ else
+ csi2->dpcm_decompress = false;
+
+ csi2->contexts[0].format_id = csi2_ctx_map_format(csi2);
+
+ if (csi2->video_out.bpl_padding == 0)
+ csi2->contexts[0].data_offset = 0;
+ else
+ csi2->contexts[0].data_offset = csi2->video_out.bpl_value;
+
+ /*
+ * Enable end of frame and end of line signals generation for
+ * context 0. These signals are generated from CSI2 receiver to
+ * qualify the last pixel of a frame and the last pixel of a line.
+ * Without enabling the signals CSI2 receiver writes data to memory
+ * beyond buffer size and/or data line offset is not handled correctly.
+ */
+ csi2->contexts[0].eof_enabled = 1;
+ csi2->contexts[0].eol_enabled = 1;
+
+ csi2_irq_complexio1_set(csi2, 1);
+ csi2_irq_ctx_set(csi2, 1);
+ csi2_irq_status_set(csi2, 1);
+
+ /* Set configuration (timings, format and links) */
+ csi2_timing_config(csi2, timing);
+ csi2_recv_config(csi2, &csi2->ctrl);
+ csi2_ctx_config(csi2, &csi2->contexts[0]);
+
+ return 0;
+}
+
+/*
+ * csi2_print_status - Prints CSI2 debug information.
+ */
+#define CSI2_PRINT_REGISTER(iss, regs, name)\
+ dev_dbg(iss->dev, "###CSI2 " #name "=0x%08x\n", \
+ iss_reg_read(iss, regs, CSI2_##name))
+
+static void csi2_print_status(struct iss_csi2_device *csi2)
+{
+ struct iss_device *iss = csi2->iss;
+
+ if (!csi2->available)
+ return;
+
+ dev_dbg(iss->dev, "-------------CSI2 Register dump-------------\n");
+
+ CSI2_PRINT_REGISTER(iss, csi2->regs1, SYSCONFIG);
+ CSI2_PRINT_REGISTER(iss, csi2->regs1, SYSSTATUS);
+ CSI2_PRINT_REGISTER(iss, csi2->regs1, IRQENABLE);
+ CSI2_PRINT_REGISTER(iss, csi2->regs1, IRQSTATUS);
+ CSI2_PRINT_REGISTER(iss, csi2->regs1, CTRL);
+ CSI2_PRINT_REGISTER(iss, csi2->regs1, DBG_H);
+ CSI2_PRINT_REGISTER(iss, csi2->regs1, COMPLEXIO_CFG);
+ CSI2_PRINT_REGISTER(iss, csi2->regs1, COMPLEXIO_IRQSTATUS);
+ CSI2_PRINT_REGISTER(iss, csi2->regs1, SHORT_PACKET);
+ CSI2_PRINT_REGISTER(iss, csi2->regs1, COMPLEXIO_IRQENABLE);
+ CSI2_PRINT_REGISTER(iss, csi2->regs1, DBG_P);
+ CSI2_PRINT_REGISTER(iss, csi2->regs1, TIMING);
+ CSI2_PRINT_REGISTER(iss, csi2->regs1, CTX_CTRL1(0));
+ CSI2_PRINT_REGISTER(iss, csi2->regs1, CTX_CTRL2(0));
+ CSI2_PRINT_REGISTER(iss, csi2->regs1, CTX_DAT_OFST(0));
+ CSI2_PRINT_REGISTER(iss, csi2->regs1, CTX_PING_ADDR(0));
+ CSI2_PRINT_REGISTER(iss, csi2->regs1, CTX_PONG_ADDR(0));
+ CSI2_PRINT_REGISTER(iss, csi2->regs1, CTX_IRQENABLE(0));
+ CSI2_PRINT_REGISTER(iss, csi2->regs1, CTX_IRQSTATUS(0));
+ CSI2_PRINT_REGISTER(iss, csi2->regs1, CTX_CTRL3(0));
+
+ dev_dbg(iss->dev, "--------------------------------------------\n");
+}
+
+/* -----------------------------------------------------------------------------
+ * Interrupt handling
+ */
+
+/*
+ * csi2_isr_buffer - Does buffer handling at end-of-frame
+ * when writing to memory.
+ */
+static void csi2_isr_buffer(struct iss_csi2_device *csi2)
+{
+ struct iss_buffer *buffer;
+
+ csi2_ctx_enable(csi2, 0, 0);
+
+ buffer = omap4iss_video_buffer_next(&csi2->video_out);
+
+ /*
+ * Let video queue operation restart engine if there is an underrun
+ * condition.
+ */
+ if (buffer == NULL)
+ return;
+
+ csi2_set_outaddr(csi2, buffer->iss_addr);
+ csi2_ctx_enable(csi2, 0, 1);
+}
+
+static void csi2_isr_ctx(struct iss_csi2_device *csi2,
+ struct iss_csi2_ctx_cfg *ctx)
+{
+ unsigned int n = ctx->ctxnum;
+ u32 status;
+
+ status = iss_reg_read(csi2->iss, csi2->regs1, CSI2_CTX_IRQSTATUS(n));
+ iss_reg_write(csi2->iss, csi2->regs1, CSI2_CTX_IRQSTATUS(n), status);
+
+ /* Propagate frame number */
+ if (status & CSI2_CTX_IRQ_FS) {
+ struct iss_pipeline *pipe =
+ to_iss_pipeline(&csi2->subdev.entity);
+ if (pipe->do_propagation)
+ atomic_inc(&pipe->frame_number);
+ }
+
+ if (!(status & CSI2_CTX_IRQ_FE))
+ return;
+
+ /* Skip interrupts until we reach the frame skip count. The CSI2 will be
+ * automatically disabled, as the frame skip count has been programmed
+ * in the CSI2_CTx_CTRL1::COUNT field, so reenable it.
+ *
+ * It would have been nice to rely on the FRAME_NUMBER interrupt instead
+ * but it turned out that the interrupt is only generated when the CSI2
+ * writes to memory (the CSI2_CTx_CTRL1::COUNT field is decreased
+ * correctly and reaches 0 when data is forwarded to the video port only
+ * but no interrupt arrives). Maybe a CSI2 hardware bug.
+ */
+ if (csi2->frame_skip) {
+ csi2->frame_skip--;
+ if (csi2->frame_skip == 0) {
+ ctx->format_id = csi2_ctx_map_format(csi2);
+ csi2_ctx_config(csi2, ctx);
+ csi2_ctx_enable(csi2, n, 1);
+ }
+ return;
+ }
+
+ if (csi2->output & CSI2_OUTPUT_MEMORY)
+ csi2_isr_buffer(csi2);
+}
+
+/*
+ * omap4iss_csi2_isr - CSI2 interrupt handling.
+ */
+void omap4iss_csi2_isr(struct iss_csi2_device *csi2)
+{
+ struct iss_pipeline *pipe = to_iss_pipeline(&csi2->subdev.entity);
+ u32 csi2_irqstatus, cpxio1_irqstatus;
+ struct iss_device *iss = csi2->iss;
+
+ if (!csi2->available)
+ return;
+
+ csi2_irqstatus = iss_reg_read(csi2->iss, csi2->regs1, CSI2_IRQSTATUS);
+ iss_reg_write(csi2->iss, csi2->regs1, CSI2_IRQSTATUS, csi2_irqstatus);
+
+ /* Failure Cases */
+ if (csi2_irqstatus & CSI2_IRQ_COMPLEXIO_ERR) {
+ cpxio1_irqstatus = iss_reg_read(csi2->iss, csi2->regs1,
+ CSI2_COMPLEXIO_IRQSTATUS);
+ iss_reg_write(csi2->iss, csi2->regs1, CSI2_COMPLEXIO_IRQSTATUS,
+ cpxio1_irqstatus);
+ dev_dbg(iss->dev, "CSI2: ComplexIO Error IRQ %x\n",
+ cpxio1_irqstatus);
+ pipe->error = true;
+ }
+
+ if (csi2_irqstatus & (CSI2_IRQ_OCP_ERR |
+ CSI2_IRQ_SHORT_PACKET |
+ CSI2_IRQ_ECC_NO_CORRECTION |
+ CSI2_IRQ_COMPLEXIO_ERR |
+ CSI2_IRQ_FIFO_OVF)) {
+ dev_dbg(iss->dev,
+ "CSI2 Err: OCP:%d SHORT:%d ECC:%d CPXIO:%d OVF:%d\n",
+ csi2_irqstatus & CSI2_IRQ_OCP_ERR ? 1 : 0,
+ csi2_irqstatus & CSI2_IRQ_SHORT_PACKET ? 1 : 0,
+ csi2_irqstatus & CSI2_IRQ_ECC_NO_CORRECTION ? 1 : 0,
+ csi2_irqstatus & CSI2_IRQ_COMPLEXIO_ERR ? 1 : 0,
+ csi2_irqstatus & CSI2_IRQ_FIFO_OVF ? 1 : 0);
+ pipe->error = true;
+ }
+
+ if (omap4iss_module_sync_is_stopping(&csi2->wait, &csi2->stopping))
+ return;
+
+ /* Successful cases */
+ if (csi2_irqstatus & CSI2_IRQ_CONTEXT0)
+ csi2_isr_ctx(csi2, &csi2->contexts[0]);
+
+ if (csi2_irqstatus & CSI2_IRQ_ECC_CORRECTION)
+ dev_dbg(iss->dev, "CSI2: ECC correction done\n");
+}
+
+/* -----------------------------------------------------------------------------
+ * ISS video operations
+ */
+
+/*
+ * csi2_queue - Queues the first buffer when using memory output
+ * @video: The video node
+ * @buffer: buffer to queue
+ */
+static int csi2_queue(struct iss_video *video, struct iss_buffer *buffer)
+{
+ struct iss_csi2_device *csi2 = container_of(video,
+ struct iss_csi2_device, video_out);
+
+ csi2_set_outaddr(csi2, buffer->iss_addr);
+
+ /*
+ * If streaming was enabled before there was a buffer queued
+ * or underrun happened in the ISR, the hardware was not enabled
+ * and DMA queue flag ISS_VIDEO_DMAQUEUE_UNDERRUN is still set.
+ * Enable it now.
+ */
+ if (csi2->video_out.dmaqueue_flags & ISS_VIDEO_DMAQUEUE_UNDERRUN) {
+ /* Enable / disable context 0 and IRQs */
+ csi2_if_enable(csi2, 1);
+ csi2_ctx_enable(csi2, 0, 1);
+ iss_video_dmaqueue_flags_clr(&csi2->video_out);
+ }
+
+ return 0;
+}
+
+static const struct iss_video_operations csi2_issvideo_ops = {
+ .queue = csi2_queue,
+};
+
+/* -----------------------------------------------------------------------------
+ * V4L2 subdev operations
+ */
+
+static struct v4l2_mbus_framefmt *
+__csi2_get_format(struct iss_csi2_device *csi2, struct v4l2_subdev_fh *fh,
+ unsigned int pad, enum v4l2_subdev_format_whence which)
+{
+ if (which == V4L2_SUBDEV_FORMAT_TRY)
+ return v4l2_subdev_get_try_format(fh, pad);
+ else
+ return &csi2->formats[pad];
+}
+
+static void
+csi2_try_format(struct iss_csi2_device *csi2, struct v4l2_subdev_fh *fh,
+ unsigned int pad, struct v4l2_mbus_framefmt *fmt,
+ enum v4l2_subdev_format_whence which)
+{
+ enum v4l2_mbus_pixelcode pixelcode;
+ struct v4l2_mbus_framefmt *format;
+ const struct iss_format_info *info;
+ unsigned int i;
+
+ switch (pad) {
+ case CSI2_PAD_SINK:
+ /* Clamp the width and height to valid range (1-8191). */
+ for (i = 0; i < ARRAY_SIZE(csi2_input_fmts); i++) {
+ if (fmt->code == csi2_input_fmts[i])
+ break;
+ }
+
+ /* If not found, use SGRBG10 as default */
+ if (i >= ARRAY_SIZE(csi2_input_fmts))
+ fmt->code = V4L2_MBUS_FMT_SGRBG10_1X10;
+
+ fmt->width = clamp_t(u32, fmt->width, 1, 8191);
+ fmt->height = clamp_t(u32, fmt->height, 1, 8191);
+ break;
+
+ case CSI2_PAD_SOURCE:
+ /* Source format same as sink format, except for DPCM
+ * compression.
+ */
+ pixelcode = fmt->code;
+ format = __csi2_get_format(csi2, fh, CSI2_PAD_SINK, which);
+ memcpy(fmt, format, sizeof(*fmt));
+
+ /*
+ * Only Allow DPCM decompression, and check that the
+ * pattern is preserved
+ */
+ info = omap4iss_video_format_info(fmt->code);
+ if (info->uncompressed == pixelcode)
+ fmt->code = pixelcode;
+ break;
+ }
+
+ /* RGB, non-interlaced */
+ fmt->colorspace = V4L2_COLORSPACE_SRGB;
+ fmt->field = V4L2_FIELD_NONE;
+}
+
+/*
+ * csi2_enum_mbus_code - Handle pixel format enumeration
+ * @sd : pointer to v4l2 subdev structure
+ * @fh : V4L2 subdev file handle
+ * @code : pointer to v4l2_subdev_mbus_code_enum structure
+ * return -EINVAL or zero on success
+ */
+static int csi2_enum_mbus_code(struct v4l2_subdev *sd,
+ struct v4l2_subdev_fh *fh,
+ struct v4l2_subdev_mbus_code_enum *code)
+{
+ struct iss_csi2_device *csi2 = v4l2_get_subdevdata(sd);
+ struct v4l2_mbus_framefmt *format;
+ const struct iss_format_info *info;
+
+ if (code->pad == CSI2_PAD_SINK) {
+ if (code->index >= ARRAY_SIZE(csi2_input_fmts))
+ return -EINVAL;
+
+ code->code = csi2_input_fmts[code->index];
+ } else {
+ format = __csi2_get_format(csi2, fh, CSI2_PAD_SINK,
+ V4L2_SUBDEV_FORMAT_TRY);
+ switch (code->index) {
+ case 0:
+ /* Passthrough sink pad code */
+ code->code = format->code;
+ break;
+ case 1:
+ /* Uncompressed code */
+ info = omap4iss_video_format_info(format->code);
+ if (info->uncompressed == format->code)
+ return -EINVAL;
+
+ code->code = info->uncompressed;
+ break;
+ default:
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+static int csi2_enum_frame_size(struct v4l2_subdev *sd,
+ struct v4l2_subdev_fh *fh,
+ struct v4l2_subdev_frame_size_enum *fse)
+{
+ struct iss_csi2_device *csi2 = v4l2_get_subdevdata(sd);
+ struct v4l2_mbus_framefmt format;
+
+ if (fse->index != 0)
+ return -EINVAL;
+
+ format.code = fse->code;
+ format.width = 1;
+ format.height = 1;
+ csi2_try_format(csi2, fh, fse->pad, &format, V4L2_SUBDEV_FORMAT_TRY);
+ fse->min_width = format.width;
+ fse->min_height = format.height;
+
+ if (format.code != fse->code)
+ return -EINVAL;
+
+ format.code = fse->code;
+ format.width = -1;
+ format.height = -1;
+ csi2_try_format(csi2, fh, fse->pad, &format, V4L2_SUBDEV_FORMAT_TRY);
+ fse->max_width = format.width;
+ fse->max_height = format.height;
+
+ return 0;
+}
+
+/*
+ * csi2_get_format - Handle get format by pads subdev method
+ * @sd : pointer to v4l2 subdev structure
+ * @fh : V4L2 subdev file handle
+ * @fmt: pointer to v4l2 subdev format structure
+ * return -EINVAL or zero on success
+ */
+static int csi2_get_format(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh,
+ struct v4l2_subdev_format *fmt)
+{
+ struct iss_csi2_device *csi2 = v4l2_get_subdevdata(sd);
+ struct v4l2_mbus_framefmt *format;
+
+ format = __csi2_get_format(csi2, fh, fmt->pad, fmt->which);
+ if (format == NULL)
+ return -EINVAL;
+
+ fmt->format = *format;
+ return 0;
+}
+
+/*
+ * csi2_set_format - Handle set format by pads subdev method
+ * @sd : pointer to v4l2 subdev structure
+ * @fh : V4L2 subdev file handle
+ * @fmt: pointer to v4l2 subdev format structure
+ * return -EINVAL or zero on success
+ */
+static int csi2_set_format(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh,
+ struct v4l2_subdev_format *fmt)
+{
+ struct iss_csi2_device *csi2 = v4l2_get_subdevdata(sd);
+ struct v4l2_mbus_framefmt *format;
+
+ format = __csi2_get_format(csi2, fh, fmt->pad, fmt->which);
+ if (format == NULL)
+ return -EINVAL;
+
+ csi2_try_format(csi2, fh, fmt->pad, &fmt->format, fmt->which);
+ *format = fmt->format;
+
+ /* Propagate the format from sink to source */
+ if (fmt->pad == CSI2_PAD_SINK) {
+ format = __csi2_get_format(csi2, fh, CSI2_PAD_SOURCE,
+ fmt->which);
+ *format = fmt->format;
+ csi2_try_format(csi2, fh, CSI2_PAD_SOURCE, format, fmt->which);
+ }
+
+ return 0;
+}
+
+static int csi2_link_validate(struct v4l2_subdev *sd, struct media_link *link,
+ struct v4l2_subdev_format *source_fmt,
+ struct v4l2_subdev_format *sink_fmt)
+{
+ struct iss_csi2_device *csi2 = v4l2_get_subdevdata(sd);
+ struct iss_pipeline *pipe = to_iss_pipeline(&csi2->subdev.entity);
+ int rval;
+
+ pipe->external = media_entity_to_v4l2_subdev(link->source->entity);
+ rval = omap4iss_get_external_info(pipe, link);
+ if (rval < 0)
+ return rval;
+
+ return v4l2_subdev_link_validate_default(sd, link, source_fmt,
+ sink_fmt);
+}
+
+/*
+ * csi2_init_formats - Initialize formats on all pads
+ * @sd: ISS CSI2 V4L2 subdevice
+ * @fh: V4L2 subdev file handle
+ *
+ * Initialize all pad formats with default values. If fh is not NULL, try
+ * formats are initialized on the file handle. Otherwise active formats are
+ * initialized on the device.
+ */
+static int csi2_init_formats(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh)
+{
+ struct v4l2_subdev_format format;
+
+ memset(&format, 0, sizeof(format));
+ format.pad = CSI2_PAD_SINK;
+ format.which = fh ? V4L2_SUBDEV_FORMAT_TRY : V4L2_SUBDEV_FORMAT_ACTIVE;
+ format.format.code = V4L2_MBUS_FMT_SGRBG10_1X10;
+ format.format.width = 4096;
+ format.format.height = 4096;
+ csi2_set_format(sd, fh, &format);
+
+ return 0;
+}
+
+/*
+ * csi2_set_stream - Enable/Disable streaming on the CSI2 module
+ * @sd: ISS CSI2 V4L2 subdevice
+ * @enable: ISS pipeline stream state
+ *
+ * Return 0 on success or a negative error code otherwise.
+ */
+static int csi2_set_stream(struct v4l2_subdev *sd, int enable)
+{
+ struct iss_csi2_device *csi2 = v4l2_get_subdevdata(sd);
+ struct iss_device *iss = csi2->iss;
+ struct iss_pipeline *pipe = to_iss_pipeline(&csi2->subdev.entity);
+ struct iss_video *video_out = &csi2->video_out;
+ int ret = 0;
+
+ if (csi2->state == ISS_PIPELINE_STREAM_STOPPED) {
+ if (enable == ISS_PIPELINE_STREAM_STOPPED)
+ return 0;
+
+ omap4iss_subclk_enable(iss, csi2->subclk);
+ }
+
+ switch (enable) {
+ case ISS_PIPELINE_STREAM_CONTINUOUS: {
+ ret = omap4iss_csiphy_config(iss, sd);
+ if (ret < 0)
+ return ret;
+
+ if (omap4iss_csiphy_acquire(csi2->phy) < 0)
+ return -ENODEV;
+ csi2->use_fs_irq = pipe->do_propagation;
+ csi2_configure(csi2);
+ csi2_print_status(csi2);
+
+ /*
+ * When outputting to memory with no buffer available, let the
+ * buffer queue handler start the hardware. A DMA queue flag
+ * ISS_VIDEO_DMAQUEUE_QUEUED will be set as soon as there is
+ * a buffer available.
+ */
+ if (csi2->output & CSI2_OUTPUT_MEMORY &&
+ !(video_out->dmaqueue_flags & ISS_VIDEO_DMAQUEUE_QUEUED))
+ break;
+ /* Enable context 0 and IRQs */
+ atomic_set(&csi2->stopping, 0);
+ csi2_ctx_enable(csi2, 0, 1);
+ csi2_if_enable(csi2, 1);
+ iss_video_dmaqueue_flags_clr(video_out);
+ break;
+ }
+ case ISS_PIPELINE_STREAM_STOPPED:
+ if (csi2->state == ISS_PIPELINE_STREAM_STOPPED)
+ return 0;
+ if (omap4iss_module_sync_idle(&sd->entity, &csi2->wait,
+ &csi2->stopping))
+ ret = -ETIMEDOUT;
+ csi2_ctx_enable(csi2, 0, 0);
+ csi2_if_enable(csi2, 0);
+ csi2_irq_ctx_set(csi2, 0);
+ omap4iss_csiphy_release(csi2->phy);
+ omap4iss_subclk_disable(iss, csi2->subclk);
+ iss_video_dmaqueue_flags_clr(video_out);
+ break;
+ }
+
+ csi2->state = enable;
+ return ret;
+}
+
+/* subdev video operations */
+static const struct v4l2_subdev_video_ops csi2_video_ops = {
+ .s_stream = csi2_set_stream,
+};
+
+/* subdev pad operations */
+static const struct v4l2_subdev_pad_ops csi2_pad_ops = {
+ .enum_mbus_code = csi2_enum_mbus_code,
+ .enum_frame_size = csi2_enum_frame_size,
+ .get_fmt = csi2_get_format,
+ .set_fmt = csi2_set_format,
+ .link_validate = csi2_link_validate,
+};
+
+/* subdev operations */
+static const struct v4l2_subdev_ops csi2_ops = {
+ .video = &csi2_video_ops,
+ .pad = &csi2_pad_ops,
+};
+
+/* subdev internal operations */
+static const struct v4l2_subdev_internal_ops csi2_internal_ops = {
+ .open = csi2_init_formats,
+};
+
+/* -----------------------------------------------------------------------------
+ * Media entity operations
+ */
+
+/*
+ * csi2_link_setup - Setup CSI2 connections.
+ * @entity : Pointer to media entity structure
+ * @local : Pointer to local pad array
+ * @remote : Pointer to remote pad array
+ * @flags : Link flags
+ * return -EINVAL or zero on success
+ */
+static int csi2_link_setup(struct media_entity *entity,
+ const struct media_pad *local,
+ const struct media_pad *remote, u32 flags)
+{
+ struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(entity);
+ struct iss_csi2_device *csi2 = v4l2_get_subdevdata(sd);
+ struct iss_csi2_ctrl_cfg *ctrl = &csi2->ctrl;
+
+ /*
+ * The ISS core doesn't support pipelines with multiple video outputs.
+ * Revisit this when it will be implemented, and return -EBUSY for now.
+ */
+
+ switch (local->index | media_entity_type(remote->entity)) {
+ case CSI2_PAD_SOURCE | MEDIA_ENT_T_DEVNODE:
+ if (flags & MEDIA_LNK_FL_ENABLED) {
+ if (csi2->output & ~CSI2_OUTPUT_MEMORY)
+ return -EBUSY;
+ csi2->output |= CSI2_OUTPUT_MEMORY;
+ } else {
+ csi2->output &= ~CSI2_OUTPUT_MEMORY;
+ }
+ break;
+
+ case CSI2_PAD_SOURCE | MEDIA_ENT_T_V4L2_SUBDEV:
+ if (flags & MEDIA_LNK_FL_ENABLED) {
+ if (csi2->output & ~CSI2_OUTPUT_IPIPEIF)
+ return -EBUSY;
+ csi2->output |= CSI2_OUTPUT_IPIPEIF;
+ } else {
+ csi2->output &= ~CSI2_OUTPUT_IPIPEIF;
+ }
+ break;
+
+ default:
+ /* Link from camera to CSI2 is fixed... */
+ return -EINVAL;
+ }
+
+ ctrl->vp_only_enable = csi2->output & CSI2_OUTPUT_MEMORY ? false : true;
+ ctrl->vp_clk_enable = !!(csi2->output & CSI2_OUTPUT_IPIPEIF);
+
+ return 0;
+}
+
+/* media operations */
+static const struct media_entity_operations csi2_media_ops = {
+ .link_setup = csi2_link_setup,
+ .link_validate = v4l2_subdev_link_validate,
+};
+
+void omap4iss_csi2_unregister_entities(struct iss_csi2_device *csi2)
+{
+ v4l2_device_unregister_subdev(&csi2->subdev);
+ omap4iss_video_unregister(&csi2->video_out);
+}
+
+int omap4iss_csi2_register_entities(struct iss_csi2_device *csi2,
+ struct v4l2_device *vdev)
+{
+ int ret;
+
+ /* Register the subdev and video nodes. */
+ ret = v4l2_device_register_subdev(vdev, &csi2->subdev);
+ if (ret < 0)
+ goto error;
+
+ ret = omap4iss_video_register(&csi2->video_out, vdev);
+ if (ret < 0)
+ goto error;
+
+ return 0;
+
+error:
+ omap4iss_csi2_unregister_entities(csi2);
+ return ret;
+}
+
+/* -----------------------------------------------------------------------------
+ * ISS CSI2 initialisation and cleanup
+ */
+
+/*
+ * csi2_init_entities - Initialize subdev and media entity.
+ * @csi2: Pointer to csi2 structure.
+ * return -ENOMEM or zero on success
+ */
+static int csi2_init_entities(struct iss_csi2_device *csi2, const char *subname)
+{
+ struct v4l2_subdev *sd = &csi2->subdev;
+ struct media_pad *pads = csi2->pads;
+ struct media_entity *me = &sd->entity;
+ int ret;
+ char name[V4L2_SUBDEV_NAME_SIZE];
+
+ v4l2_subdev_init(sd, &csi2_ops);
+ sd->internal_ops = &csi2_internal_ops;
+ sprintf(name, "CSI2%s", subname);
+ snprintf(sd->name, sizeof(sd->name), "OMAP4 ISS %s", name);
+
+ sd->grp_id = 1 << 16; /* group ID for iss subdevs */
+ v4l2_set_subdevdata(sd, csi2);
+ sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
+
+ pads[CSI2_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
+ pads[CSI2_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
+
+ me->ops = &csi2_media_ops;
+ ret = media_entity_init(me, CSI2_PADS_NUM, pads, 0);
+ if (ret < 0)
+ return ret;
+
+ csi2_init_formats(sd, NULL);
+
+ /* Video device node */
+ csi2->video_out.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ csi2->video_out.ops = &csi2_issvideo_ops;
+ csi2->video_out.bpl_alignment = 32;
+ csi2->video_out.bpl_zero_padding = 1;
+ csi2->video_out.bpl_max = 0x1ffe0;
+ csi2->video_out.iss = csi2->iss;
+ csi2->video_out.capture_mem = PAGE_ALIGN(4096 * 4096) * 3;
+
+ ret = omap4iss_video_init(&csi2->video_out, name);
+ if (ret < 0)
+ goto error_video;
+
+ /* Connect the CSI2 subdev to the video node. */
+ ret = media_entity_create_link(&csi2->subdev.entity, CSI2_PAD_SOURCE,
+ &csi2->video_out.video.entity, 0, 0);
+ if (ret < 0)
+ goto error_link;
+
+ return 0;
+
+error_link:
+ omap4iss_video_cleanup(&csi2->video_out);
+error_video:
+ media_entity_cleanup(&csi2->subdev.entity);
+ return ret;
+}
+
+/*
+ * omap4iss_csi2_init - Routine for module driver init
+ */
+int omap4iss_csi2_init(struct iss_device *iss)
+{
+ struct iss_csi2_device *csi2a = &iss->csi2a;
+ struct iss_csi2_device *csi2b = &iss->csi2b;
+ int ret;
+
+ csi2a->iss = iss;
+ csi2a->available = 1;
+ csi2a->regs1 = OMAP4_ISS_MEM_CSI2_A_REGS1;
+ csi2a->phy = &iss->csiphy1;
+ csi2a->subclk = OMAP4_ISS_SUBCLK_CSI2_A;
+ csi2a->state = ISS_PIPELINE_STREAM_STOPPED;
+ init_waitqueue_head(&csi2a->wait);
+
+ ret = csi2_init_entities(csi2a, "a");
+ if (ret < 0)
+ return ret;
+
+ csi2b->iss = iss;
+ csi2b->available = 1;
+ csi2b->regs1 = OMAP4_ISS_MEM_CSI2_B_REGS1;
+ csi2b->phy = &iss->csiphy2;
+ csi2b->subclk = OMAP4_ISS_SUBCLK_CSI2_B;
+ csi2b->state = ISS_PIPELINE_STREAM_STOPPED;
+ init_waitqueue_head(&csi2b->wait);
+
+ ret = csi2_init_entities(csi2b, "b");
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+/*
+ * omap4iss_csi2_cleanup - Routine for module driver cleanup
+ */
+void omap4iss_csi2_cleanup(struct iss_device *iss)
+{
+ struct iss_csi2_device *csi2a = &iss->csi2a;
+ struct iss_csi2_device *csi2b = &iss->csi2b;
+
+ omap4iss_video_cleanup(&csi2a->video_out);
+ media_entity_cleanup(&csi2a->subdev.entity);
+
+ omap4iss_video_cleanup(&csi2b->video_out);
+ media_entity_cleanup(&csi2b->subdev.entity);
+}
diff --git a/drivers/staging/media/omap4iss/iss_csi2.h b/drivers/staging/media/omap4iss/iss_csi2.h
new file mode 100644
index 00000000000..971aa7b0801
--- /dev/null
+++ b/drivers/staging/media/omap4iss/iss_csi2.h
@@ -0,0 +1,158 @@
+/*
+ * TI OMAP4 ISS V4L2 Driver - CSI2 module
+ *
+ * Copyright (C) 2012 Texas Instruments, Inc.
+ *
+ * Author: Sergio Aguirre <sergio.a.aguirre@gmail.com>
+ *
+ * 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.
+ */
+
+#ifndef OMAP4_ISS_CSI2_H
+#define OMAP4_ISS_CSI2_H
+
+#include <linux/types.h>
+#include <linux/videodev2.h>
+
+#include "iss_video.h"
+
+struct iss_csiphy;
+
+/* This is not an exhaustive list */
+enum iss_csi2_pix_formats {
+ CSI2_PIX_FMT_OTHERS = 0,
+ CSI2_PIX_FMT_YUV422_8BIT = 0x1e,
+ CSI2_PIX_FMT_YUV422_8BIT_VP = 0x9e,
+ CSI2_PIX_FMT_YUV422_8BIT_VP16 = 0xde,
+ CSI2_PIX_FMT_RAW10_EXP16 = 0xab,
+ CSI2_PIX_FMT_RAW10_EXP16_VP = 0x12f,
+ CSI2_PIX_FMT_RAW8 = 0x2a,
+ CSI2_PIX_FMT_RAW8_DPCM10_EXP16 = 0x2aa,
+ CSI2_PIX_FMT_RAW8_DPCM10_VP = 0x32a,
+ CSI2_PIX_FMT_RAW8_VP = 0x12a,
+ CSI2_USERDEF_8BIT_DATA1_DPCM10_VP = 0x340,
+ CSI2_USERDEF_8BIT_DATA1_DPCM10 = 0x2c0,
+ CSI2_USERDEF_8BIT_DATA1 = 0x40,
+};
+
+enum iss_csi2_irqevents {
+ OCP_ERR_IRQ = 0x4000,
+ SHORT_PACKET_IRQ = 0x2000,
+ ECC_CORRECTION_IRQ = 0x1000,
+ ECC_NO_CORRECTION_IRQ = 0x800,
+ COMPLEXIO2_ERR_IRQ = 0x400,
+ COMPLEXIO1_ERR_IRQ = 0x200,
+ FIFO_OVF_IRQ = 0x100,
+ CONTEXT7 = 0x80,
+ CONTEXT6 = 0x40,
+ CONTEXT5 = 0x20,
+ CONTEXT4 = 0x10,
+ CONTEXT3 = 0x8,
+ CONTEXT2 = 0x4,
+ CONTEXT1 = 0x2,
+ CONTEXT0 = 0x1,
+};
+
+enum iss_csi2_ctx_irqevents {
+ CTX_ECC_CORRECTION = 0x100,
+ CTX_LINE_NUMBER = 0x80,
+ CTX_FRAME_NUMBER = 0x40,
+ CTX_CS = 0x20,
+ CTX_LE = 0x8,
+ CTX_LS = 0x4,
+ CTX_FE = 0x2,
+ CTX_FS = 0x1,
+};
+
+enum iss_csi2_frame_mode {
+ ISS_CSI2_FRAME_IMMEDIATE,
+ ISS_CSI2_FRAME_AFTERFEC,
+};
+
+#define ISS_CSI2_MAX_CTX_NUM 7
+
+struct iss_csi2_ctx_cfg {
+ u8 ctxnum; /* context number 0 - 7 */
+ u8 dpcm_decompress;
+
+ /* Fields in CSI2_CTx_CTRL2 - locked by CSI2_CTx_CTRL1.CTX_EN */
+ u8 virtual_id;
+ u16 format_id; /* as in CSI2_CTx_CTRL2[9:0] */
+ u8 dpcm_predictor; /* 1: simple, 0: advanced */
+
+ /* Fields in CSI2_CTx_CTRL1/3 - Shadowed */
+ u16 alpha;
+ u16 data_offset;
+ u32 ping_addr;
+ u32 pong_addr;
+ u8 eof_enabled;
+ u8 eol_enabled;
+ u8 checksum_enabled;
+ u8 enabled;
+};
+
+struct iss_csi2_timing_cfg {
+ u8 ionum; /* IO1 or IO2 as in CSI2_TIMING */
+ unsigned force_rx_mode:1;
+ unsigned stop_state_16x:1;
+ unsigned stop_state_4x:1;
+ u16 stop_state_counter;
+};
+
+struct iss_csi2_ctrl_cfg {
+ bool vp_clk_enable;
+ bool vp_only_enable;
+ u8 vp_out_ctrl;
+ enum iss_csi2_frame_mode frame_mode;
+ bool ecc_enable;
+ bool if_enable;
+};
+
+#define CSI2_PAD_SINK 0
+#define CSI2_PAD_SOURCE 1
+#define CSI2_PADS_NUM 2
+
+#define CSI2_OUTPUT_IPIPEIF (1 << 0)
+#define CSI2_OUTPUT_MEMORY (1 << 1)
+
+struct iss_csi2_device {
+ struct v4l2_subdev subdev;
+ struct media_pad pads[CSI2_PADS_NUM];
+ struct v4l2_mbus_framefmt formats[CSI2_PADS_NUM];
+
+ struct iss_video video_out;
+ struct iss_device *iss;
+
+ u8 available; /* Is the IP present on the silicon? */
+
+ /* memory resources, as defined in enum iss_mem_resources */
+ unsigned int regs1;
+ unsigned int regs2;
+ /* ISP subclock, as defined in enum iss_isp_subclk_resource */
+ unsigned int subclk;
+
+ u32 output; /* output to IPIPEIF, memory or both? */
+ bool dpcm_decompress;
+ unsigned int frame_skip;
+ bool use_fs_irq;
+
+ struct iss_csiphy *phy;
+ struct iss_csi2_ctx_cfg contexts[ISS_CSI2_MAX_CTX_NUM + 1];
+ struct iss_csi2_timing_cfg timing[2];
+ struct iss_csi2_ctrl_cfg ctrl;
+ enum iss_pipeline_stream_state state;
+ wait_queue_head_t wait;
+ atomic_t stopping;
+};
+
+void omap4iss_csi2_isr(struct iss_csi2_device *csi2);
+int omap4iss_csi2_reset(struct iss_csi2_device *csi2);
+int omap4iss_csi2_init(struct iss_device *iss);
+void omap4iss_csi2_cleanup(struct iss_device *iss);
+void omap4iss_csi2_unregister_entities(struct iss_csi2_device *csi2);
+int omap4iss_csi2_register_entities(struct iss_csi2_device *csi2,
+ struct v4l2_device *vdev);
+#endif /* OMAP4_ISS_CSI2_H */
diff --git a/drivers/staging/media/omap4iss/iss_csiphy.c b/drivers/staging/media/omap4iss/iss_csiphy.c
new file mode 100644
index 00000000000..7c3d55d811e
--- /dev/null
+++ b/drivers/staging/media/omap4iss/iss_csiphy.c
@@ -0,0 +1,279 @@
+/*
+ * TI OMAP4 ISS V4L2 Driver - CSI PHY module
+ *
+ * Copyright (C) 2012 Texas Instruments, Inc.
+ *
+ * Author: Sergio Aguirre <sergio.a.aguirre@gmail.com>
+ *
+ * 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.
+ */
+
+#include <linux/delay.h>
+#include <linux/device.h>
+
+#include "../../../../arch/arm/mach-omap2/control.h"
+
+#include "iss.h"
+#include "iss_regs.h"
+#include "iss_csiphy.h"
+
+/*
+ * csiphy_lanes_config - Configuration of CSIPHY lanes.
+ *
+ * Updates HW configuration.
+ * Called with phy->mutex taken.
+ */
+static void csiphy_lanes_config(struct iss_csiphy *phy)
+{
+ unsigned int i;
+ u32 reg;
+
+ reg = iss_reg_read(phy->iss, phy->cfg_regs, CSI2_COMPLEXIO_CFG);
+
+ for (i = 0; i < phy->max_data_lanes; i++) {
+ reg &= ~(CSI2_COMPLEXIO_CFG_DATA_POL(i + 1) |
+ CSI2_COMPLEXIO_CFG_DATA_POSITION_MASK(i + 1));
+ reg |= (phy->lanes.data[i].pol ?
+ CSI2_COMPLEXIO_CFG_DATA_POL(i + 1) : 0);
+ reg |= (phy->lanes.data[i].pos <<
+ CSI2_COMPLEXIO_CFG_DATA_POSITION_SHIFT(i + 1));
+ }
+
+ reg &= ~(CSI2_COMPLEXIO_CFG_CLOCK_POL |
+ CSI2_COMPLEXIO_CFG_CLOCK_POSITION_MASK);
+ reg |= phy->lanes.clk.pol ? CSI2_COMPLEXIO_CFG_CLOCK_POL : 0;
+ reg |= phy->lanes.clk.pos << CSI2_COMPLEXIO_CFG_CLOCK_POSITION_SHIFT;
+
+ iss_reg_write(phy->iss, phy->cfg_regs, CSI2_COMPLEXIO_CFG, reg);
+}
+
+/*
+ * csiphy_set_power
+ * @power: Power state to be set.
+ *
+ * Returns 0 if successful, or -EBUSY if the retry count is exceeded.
+ */
+static int csiphy_set_power(struct iss_csiphy *phy, u32 power)
+{
+ u32 reg;
+ u8 retry_count;
+
+ iss_reg_update(phy->iss, phy->cfg_regs, CSI2_COMPLEXIO_CFG,
+ CSI2_COMPLEXIO_CFG_PWD_CMD_MASK,
+ power | CSI2_COMPLEXIO_CFG_PWR_AUTO);
+
+ retry_count = 0;
+ do {
+ udelay(1);
+ reg = iss_reg_read(phy->iss, phy->cfg_regs, CSI2_COMPLEXIO_CFG)
+ & CSI2_COMPLEXIO_CFG_PWD_STATUS_MASK;
+
+ if (reg != power >> 2)
+ retry_count++;
+
+ } while ((reg != power >> 2) && (retry_count < 250));
+
+ if (retry_count == 250) {
+ dev_err(phy->iss->dev, "CSI2 CIO set power failed!\n");
+ return -EBUSY;
+ }
+
+ return 0;
+}
+
+/*
+ * csiphy_dphy_config - Configure CSI2 D-PHY parameters.
+ *
+ * Called with phy->mutex taken.
+ */
+static void csiphy_dphy_config(struct iss_csiphy *phy)
+{
+ u32 reg;
+
+ /* Set up REGISTER0 */
+ reg = phy->dphy.ths_term << REGISTER0_THS_TERM_SHIFT;
+ reg |= phy->dphy.ths_settle << REGISTER0_THS_SETTLE_SHIFT;
+
+ iss_reg_write(phy->iss, phy->phy_regs, REGISTER0, reg);
+
+ /* Set up REGISTER1 */
+ reg = phy->dphy.tclk_term << REGISTER1_TCLK_TERM_SHIFT;
+ reg |= phy->dphy.tclk_miss << REGISTER1_CTRLCLK_DIV_FACTOR_SHIFT;
+ reg |= phy->dphy.tclk_settle << REGISTER1_TCLK_SETTLE_SHIFT;
+ reg |= 0xb8 << REGISTER1_DPHY_HS_SYNC_PATTERN_SHIFT;
+
+ iss_reg_write(phy->iss, phy->phy_regs, REGISTER1, reg);
+}
+
+/*
+ * TCLK values are OK at their reset values
+ */
+#define TCLK_TERM 0
+#define TCLK_MISS 1
+#define TCLK_SETTLE 14
+
+int omap4iss_csiphy_config(struct iss_device *iss,
+ struct v4l2_subdev *csi2_subdev)
+{
+ struct iss_csi2_device *csi2 = v4l2_get_subdevdata(csi2_subdev);
+ struct iss_pipeline *pipe = to_iss_pipeline(&csi2_subdev->entity);
+ struct iss_v4l2_subdevs_group *subdevs = pipe->external->host_priv;
+ struct iss_csiphy_dphy_cfg csi2phy;
+ int csi2_ddrclk_khz;
+ struct iss_csiphy_lanes_cfg *lanes;
+ unsigned int used_lanes = 0;
+ u32 cam_rx_ctrl;
+ unsigned int i;
+
+ lanes = &subdevs->bus.csi2.lanecfg;
+
+ /*
+ * SCM.CONTROL_CAMERA_RX
+ * - bit [31] : CSIPHY2 lane 2 enable (4460+ only)
+ * - bit [30:29] : CSIPHY2 per-lane enable (1 to 0)
+ * - bit [28:24] : CSIPHY1 per-lane enable (4 to 0)
+ * - bit [21] : CSIPHY2 CTRLCLK enable
+ * - bit [20:19] : CSIPHY2 config: 00 d-phy, 01/10 ccp2
+ * - bit [18] : CSIPHY1 CTRLCLK enable
+ * - bit [17:16] : CSIPHY1 config: 00 d-phy, 01/10 ccp2
+ */
+ cam_rx_ctrl = omap4_ctrl_pad_readl(
+ OMAP4_CTRL_MODULE_PAD_CORE_CONTROL_CAMERA_RX);
+
+
+ if (subdevs->interface == ISS_INTERFACE_CSI2A_PHY1) {
+ cam_rx_ctrl &= ~(OMAP4_CAMERARX_CSI21_LANEENABLE_MASK |
+ OMAP4_CAMERARX_CSI21_CAMMODE_MASK);
+ /* NOTE: Leave CSIPHY1 config to 0x0: D-PHY mode */
+ /* Enable all lanes for now */
+ cam_rx_ctrl |=
+ 0x1f << OMAP4_CAMERARX_CSI21_LANEENABLE_SHIFT;
+ /* Enable CTRLCLK */
+ cam_rx_ctrl |= OMAP4_CAMERARX_CSI21_CTRLCLKEN_MASK;
+ }
+
+ if (subdevs->interface == ISS_INTERFACE_CSI2B_PHY2) {
+ cam_rx_ctrl &= ~(OMAP4_CAMERARX_CSI22_LANEENABLE_MASK |
+ OMAP4_CAMERARX_CSI22_CAMMODE_MASK);
+ /* NOTE: Leave CSIPHY2 config to 0x0: D-PHY mode */
+ /* Enable all lanes for now */
+ cam_rx_ctrl |=
+ 0x3 << OMAP4_CAMERARX_CSI22_LANEENABLE_SHIFT;
+ /* Enable CTRLCLK */
+ cam_rx_ctrl |= OMAP4_CAMERARX_CSI22_CTRLCLKEN_MASK;
+ }
+
+ omap4_ctrl_pad_writel(cam_rx_ctrl,
+ OMAP4_CTRL_MODULE_PAD_CORE_CONTROL_CAMERA_RX);
+
+ /* Reset used lane count */
+ csi2->phy->used_data_lanes = 0;
+
+ /* Clock and data lanes verification */
+ for (i = 0; i < csi2->phy->max_data_lanes; i++) {
+ if (lanes->data[i].pos == 0)
+ continue;
+
+ if (lanes->data[i].pol > 1 ||
+ lanes->data[i].pos > (csi2->phy->max_data_lanes + 1))
+ return -EINVAL;
+
+ if (used_lanes & (1 << lanes->data[i].pos))
+ return -EINVAL;
+
+ used_lanes |= 1 << lanes->data[i].pos;
+ csi2->phy->used_data_lanes++;
+ }
+
+ if (lanes->clk.pol > 1 ||
+ lanes->clk.pos > (csi2->phy->max_data_lanes + 1))
+ return -EINVAL;
+
+ if (lanes->clk.pos == 0 || used_lanes & (1 << lanes->clk.pos))
+ return -EINVAL;
+
+ csi2_ddrclk_khz = pipe->external_rate / 1000
+ / (2 * csi2->phy->used_data_lanes)
+ * pipe->external_bpp;
+
+ /*
+ * THS_TERM: Programmed value = ceil(12.5 ns/DDRClk period) - 1.
+ * THS_SETTLE: Programmed value = ceil(90 ns/DDRClk period) + 3.
+ */
+ csi2phy.ths_term = DIV_ROUND_UP(25 * csi2_ddrclk_khz, 2000000) - 1;
+ csi2phy.ths_settle = DIV_ROUND_UP(90 * csi2_ddrclk_khz, 1000000) + 3;
+ csi2phy.tclk_term = TCLK_TERM;
+ csi2phy.tclk_miss = TCLK_MISS;
+ csi2phy.tclk_settle = TCLK_SETTLE;
+
+ mutex_lock(&csi2->phy->mutex);
+ csi2->phy->dphy = csi2phy;
+ csi2->phy->lanes = *lanes;
+ mutex_unlock(&csi2->phy->mutex);
+
+ return 0;
+}
+
+int omap4iss_csiphy_acquire(struct iss_csiphy *phy)
+{
+ int rval;
+
+ mutex_lock(&phy->mutex);
+
+ rval = omap4iss_csi2_reset(phy->csi2);
+ if (rval)
+ goto done;
+
+ csiphy_dphy_config(phy);
+ csiphy_lanes_config(phy);
+
+ rval = csiphy_set_power(phy, CSI2_COMPLEXIO_CFG_PWD_CMD_ON);
+ if (rval)
+ goto done;
+
+ phy->phy_in_use = 1;
+
+done:
+ mutex_unlock(&phy->mutex);
+ return rval;
+}
+
+void omap4iss_csiphy_release(struct iss_csiphy *phy)
+{
+ mutex_lock(&phy->mutex);
+ if (phy->phy_in_use) {
+ csiphy_set_power(phy, CSI2_COMPLEXIO_CFG_PWD_CMD_OFF);
+ phy->phy_in_use = 0;
+ }
+ mutex_unlock(&phy->mutex);
+}
+
+/*
+ * omap4iss_csiphy_init - Initialize the CSI PHY frontends
+ */
+int omap4iss_csiphy_init(struct iss_device *iss)
+{
+ struct iss_csiphy *phy1 = &iss->csiphy1;
+ struct iss_csiphy *phy2 = &iss->csiphy2;
+
+ phy1->iss = iss;
+ phy1->csi2 = &iss->csi2a;
+ phy1->max_data_lanes = ISS_CSIPHY1_NUM_DATA_LANES;
+ phy1->used_data_lanes = 0;
+ phy1->cfg_regs = OMAP4_ISS_MEM_CSI2_A_REGS1;
+ phy1->phy_regs = OMAP4_ISS_MEM_CAMERARX_CORE1;
+ mutex_init(&phy1->mutex);
+
+ phy2->iss = iss;
+ phy2->csi2 = &iss->csi2b;
+ phy2->max_data_lanes = ISS_CSIPHY2_NUM_DATA_LANES;
+ phy2->used_data_lanes = 0;
+ phy2->cfg_regs = OMAP4_ISS_MEM_CSI2_B_REGS1;
+ phy2->phy_regs = OMAP4_ISS_MEM_CAMERARX_CORE2;
+ mutex_init(&phy2->mutex);
+
+ return 0;
+}
diff --git a/drivers/staging/media/omap4iss/iss_csiphy.h b/drivers/staging/media/omap4iss/iss_csiphy.h
new file mode 100644
index 00000000000..e9ca4395565
--- /dev/null
+++ b/drivers/staging/media/omap4iss/iss_csiphy.h
@@ -0,0 +1,51 @@
+/*
+ * TI OMAP4 ISS V4L2 Driver - CSI PHY module
+ *
+ * Copyright (C) 2012 Texas Instruments, Inc.
+ *
+ * Author: Sergio Aguirre <sergio.a.aguirre@gmail.com>
+ *
+ * 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.
+ */
+
+#ifndef OMAP4_ISS_CSI_PHY_H
+#define OMAP4_ISS_CSI_PHY_H
+
+#include <media/omap4iss.h>
+
+struct iss_csi2_device;
+
+struct iss_csiphy_dphy_cfg {
+ u8 ths_term;
+ u8 ths_settle;
+ u8 tclk_term;
+ unsigned tclk_miss:1;
+ u8 tclk_settle;
+};
+
+struct iss_csiphy {
+ struct iss_device *iss;
+ struct mutex mutex; /* serialize csiphy configuration */
+ u8 phy_in_use;
+ struct iss_csi2_device *csi2;
+
+ /* memory resources, as defined in enum iss_mem_resources */
+ unsigned int cfg_regs;
+ unsigned int phy_regs;
+
+ u8 max_data_lanes; /* number of CSI2 Data Lanes supported */
+ u8 used_data_lanes; /* number of CSI2 Data Lanes used */
+ struct iss_csiphy_lanes_cfg lanes;
+ struct iss_csiphy_dphy_cfg dphy;
+};
+
+int omap4iss_csiphy_config(struct iss_device *iss,
+ struct v4l2_subdev *csi2_subdev);
+int omap4iss_csiphy_acquire(struct iss_csiphy *phy);
+void omap4iss_csiphy_release(struct iss_csiphy *phy);
+int omap4iss_csiphy_init(struct iss_device *iss);
+
+#endif /* OMAP4_ISS_CSI_PHY_H */
diff --git a/drivers/staging/media/omap4iss/iss_ipipe.c b/drivers/staging/media/omap4iss/iss_ipipe.c
new file mode 100644
index 00000000000..6eaafc5e2ee
--- /dev/null
+++ b/drivers/staging/media/omap4iss/iss_ipipe.c
@@ -0,0 +1,570 @@
+/*
+ * TI OMAP4 ISS V4L2 Driver - ISP IPIPE module
+ *
+ * Copyright (C) 2012 Texas Instruments, Inc.
+ *
+ * Author: Sergio Aguirre <sergio.a.aguirre@gmail.com>
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/uaccess.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/dma-mapping.h>
+#include <linux/mm.h>
+#include <linux/sched.h>
+
+#include "iss.h"
+#include "iss_regs.h"
+#include "iss_ipipe.h"
+
+static struct v4l2_mbus_framefmt *
+__ipipe_get_format(struct iss_ipipe_device *ipipe, struct v4l2_subdev_fh *fh,
+ unsigned int pad, enum v4l2_subdev_format_whence which);
+
+static const unsigned int ipipe_fmts[] = {
+ V4L2_MBUS_FMT_SGRBG10_1X10,
+ V4L2_MBUS_FMT_SRGGB10_1X10,
+ V4L2_MBUS_FMT_SBGGR10_1X10,
+ V4L2_MBUS_FMT_SGBRG10_1X10,
+};
+
+/*
+ * ipipe_print_status - Print current IPIPE Module register values.
+ * @ipipe: Pointer to ISS ISP IPIPE device.
+ *
+ * Also prints other debug information stored in the IPIPE module.
+ */
+#define IPIPE_PRINT_REGISTER(iss, name)\
+ dev_dbg(iss->dev, "###IPIPE " #name "=0x%08x\n", \
+ iss_reg_read(iss, OMAP4_ISS_MEM_ISP_IPIPE, IPIPE_##name))
+
+static void ipipe_print_status(struct iss_ipipe_device *ipipe)
+{
+ struct iss_device *iss = to_iss_device(ipipe);
+
+ dev_dbg(iss->dev, "-------------IPIPE Register dump-------------\n");
+
+ IPIPE_PRINT_REGISTER(iss, SRC_EN);
+ IPIPE_PRINT_REGISTER(iss, SRC_MODE);
+ IPIPE_PRINT_REGISTER(iss, SRC_FMT);
+ IPIPE_PRINT_REGISTER(iss, SRC_COL);
+ IPIPE_PRINT_REGISTER(iss, SRC_VPS);
+ IPIPE_PRINT_REGISTER(iss, SRC_VSZ);
+ IPIPE_PRINT_REGISTER(iss, SRC_HPS);
+ IPIPE_PRINT_REGISTER(iss, SRC_HSZ);
+ IPIPE_PRINT_REGISTER(iss, GCK_MMR);
+ IPIPE_PRINT_REGISTER(iss, YUV_PHS);
+
+ dev_dbg(iss->dev, "-----------------------------------------------\n");
+}
+
+/*
+ * ipipe_enable - Enable/Disable IPIPE.
+ * @enable: enable flag
+ *
+ */
+static void ipipe_enable(struct iss_ipipe_device *ipipe, u8 enable)
+{
+ struct iss_device *iss = to_iss_device(ipipe);
+
+ iss_reg_update(iss, OMAP4_ISS_MEM_ISP_IPIPE, IPIPE_SRC_EN,
+ IPIPE_SRC_EN_EN, enable ? IPIPE_SRC_EN_EN : 0);
+}
+
+/* -----------------------------------------------------------------------------
+ * Format- and pipeline-related configuration helpers
+ */
+
+static void ipipe_configure(struct iss_ipipe_device *ipipe)
+{
+ struct iss_device *iss = to_iss_device(ipipe);
+ struct v4l2_mbus_framefmt *format;
+
+ /* IPIPE_PAD_SINK */
+ format = &ipipe->formats[IPIPE_PAD_SINK];
+
+ /* NOTE: Currently just supporting pipeline IN: RGB, OUT: YUV422 */
+ iss_reg_write(iss, OMAP4_ISS_MEM_ISP_IPIPE, IPIPE_SRC_FMT,
+ IPIPE_SRC_FMT_RAW2YUV);
+
+ /* Enable YUV444 -> YUV422 conversion */
+ iss_reg_write(iss, OMAP4_ISS_MEM_ISP_IPIPE, IPIPE_YUV_PHS,
+ IPIPE_YUV_PHS_LPF);
+
+ iss_reg_write(iss, OMAP4_ISS_MEM_ISP_IPIPE, IPIPE_SRC_VPS, 0);
+ iss_reg_write(iss, OMAP4_ISS_MEM_ISP_IPIPE, IPIPE_SRC_HPS, 0);
+ iss_reg_write(iss, OMAP4_ISS_MEM_ISP_IPIPE, IPIPE_SRC_VSZ,
+ (format->height - 2) & IPIPE_SRC_VSZ_MASK);
+ iss_reg_write(iss, OMAP4_ISS_MEM_ISP_IPIPE, IPIPE_SRC_HSZ,
+ (format->width - 1) & IPIPE_SRC_HSZ_MASK);
+
+ /* Ignore ipipeif_wrt signal, and operate on-the-fly. */
+ iss_reg_clr(iss, OMAP4_ISS_MEM_ISP_IPIPE, IPIPE_SRC_MODE,
+ IPIPE_SRC_MODE_WRT | IPIPE_SRC_MODE_OST);
+
+ /* HACK: Values tuned for Ducati SW (OV) */
+ iss_reg_write(iss, OMAP4_ISS_MEM_ISP_IPIPE, IPIPE_SRC_COL,
+ IPIPE_SRC_COL_EE_B | IPIPE_SRC_COL_EO_GB |
+ IPIPE_SRC_COL_OE_GR | IPIPE_SRC_COL_OO_R);
+
+ /* IPIPE_PAD_SOURCE_VP */
+ format = &ipipe->formats[IPIPE_PAD_SOURCE_VP];
+ /* Do nothing? */
+}
+
+/* -----------------------------------------------------------------------------
+ * V4L2 subdev operations
+ */
+
+/*
+ * ipipe_set_stream - Enable/Disable streaming on the IPIPE module
+ * @sd: ISP IPIPE V4L2 subdevice
+ * @enable: Enable/disable stream
+ */
+static int ipipe_set_stream(struct v4l2_subdev *sd, int enable)
+{
+ struct iss_ipipe_device *ipipe = v4l2_get_subdevdata(sd);
+ struct iss_device *iss = to_iss_device(ipipe);
+ int ret = 0;
+
+ if (ipipe->state == ISS_PIPELINE_STREAM_STOPPED) {
+ if (enable == ISS_PIPELINE_STREAM_STOPPED)
+ return 0;
+
+ omap4iss_isp_subclk_enable(iss, OMAP4_ISS_ISP_SUBCLK_IPIPE);
+
+ /* Enable clk_arm_g0 */
+ iss_reg_write(iss, OMAP4_ISS_MEM_ISP_IPIPE, IPIPE_GCK_MMR,
+ IPIPE_GCK_MMR_REG);
+
+ /* Enable clk_pix_g[3:0] */
+ iss_reg_write(iss, OMAP4_ISS_MEM_ISP_IPIPE, IPIPE_GCK_PIX,
+ IPIPE_GCK_PIX_G3 | IPIPE_GCK_PIX_G2 |
+ IPIPE_GCK_PIX_G1 | IPIPE_GCK_PIX_G0);
+ }
+
+ switch (enable) {
+ case ISS_PIPELINE_STREAM_CONTINUOUS:
+
+ ipipe_configure(ipipe);
+ ipipe_print_status(ipipe);
+
+ atomic_set(&ipipe->stopping, 0);
+ ipipe_enable(ipipe, 1);
+ break;
+
+ case ISS_PIPELINE_STREAM_STOPPED:
+ if (ipipe->state == ISS_PIPELINE_STREAM_STOPPED)
+ return 0;
+ if (omap4iss_module_sync_idle(&sd->entity, &ipipe->wait,
+ &ipipe->stopping))
+ ret = -ETIMEDOUT;
+
+ ipipe_enable(ipipe, 0);
+ omap4iss_isp_subclk_disable(iss, OMAP4_ISS_ISP_SUBCLK_IPIPE);
+ break;
+ }
+
+ ipipe->state = enable;
+ return ret;
+}
+
+static struct v4l2_mbus_framefmt *
+__ipipe_get_format(struct iss_ipipe_device *ipipe, struct v4l2_subdev_fh *fh,
+ unsigned int pad, enum v4l2_subdev_format_whence which)
+{
+ if (which == V4L2_SUBDEV_FORMAT_TRY)
+ return v4l2_subdev_get_try_format(fh, pad);
+ else
+ return &ipipe->formats[pad];
+}
+
+/*
+ * ipipe_try_format - Try video format on a pad
+ * @ipipe: ISS IPIPE device
+ * @fh : V4L2 subdev file handle
+ * @pad: Pad number
+ * @fmt: Format
+ */
+static void
+ipipe_try_format(struct iss_ipipe_device *ipipe, struct v4l2_subdev_fh *fh,
+ unsigned int pad, struct v4l2_mbus_framefmt *fmt,
+ enum v4l2_subdev_format_whence which)
+{
+ struct v4l2_mbus_framefmt *format;
+ unsigned int width = fmt->width;
+ unsigned int height = fmt->height;
+ unsigned int i;
+
+ switch (pad) {
+ case IPIPE_PAD_SINK:
+ for (i = 0; i < ARRAY_SIZE(ipipe_fmts); i++) {
+ if (fmt->code == ipipe_fmts[i])
+ break;
+ }
+
+ /* If not found, use SGRBG10 as default */
+ if (i >= ARRAY_SIZE(ipipe_fmts))
+ fmt->code = V4L2_MBUS_FMT_SGRBG10_1X10;
+
+ /* Clamp the input size. */
+ fmt->width = clamp_t(u32, width, 1, 8192);
+ fmt->height = clamp_t(u32, height, 1, 8192);
+ fmt->colorspace = V4L2_COLORSPACE_SRGB;
+ break;
+
+ case IPIPE_PAD_SOURCE_VP:
+ format = __ipipe_get_format(ipipe, fh, IPIPE_PAD_SINK, which);
+ memcpy(fmt, format, sizeof(*fmt));
+
+ fmt->code = V4L2_MBUS_FMT_UYVY8_1X16;
+ fmt->width = clamp_t(u32, width, 32, fmt->width);
+ fmt->height = clamp_t(u32, height, 32, fmt->height);
+ fmt->colorspace = V4L2_COLORSPACE_JPEG;
+ break;
+ }
+
+ fmt->field = V4L2_FIELD_NONE;
+}
+
+/*
+ * ipipe_enum_mbus_code - Handle pixel format enumeration
+ * @sd : pointer to v4l2 subdev structure
+ * @fh : V4L2 subdev file handle
+ * @code : pointer to v4l2_subdev_mbus_code_enum structure
+ * return -EINVAL or zero on success
+ */
+static int ipipe_enum_mbus_code(struct v4l2_subdev *sd,
+ struct v4l2_subdev_fh *fh,
+ struct v4l2_subdev_mbus_code_enum *code)
+{
+ switch (code->pad) {
+ case IPIPE_PAD_SINK:
+ if (code->index >= ARRAY_SIZE(ipipe_fmts))
+ return -EINVAL;
+
+ code->code = ipipe_fmts[code->index];
+ break;
+
+ case IPIPE_PAD_SOURCE_VP:
+ /* FIXME: Forced format conversion inside IPIPE ? */
+ if (code->index != 0)
+ return -EINVAL;
+
+ code->code = V4L2_MBUS_FMT_UYVY8_1X16;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int ipipe_enum_frame_size(struct v4l2_subdev *sd,
+ struct v4l2_subdev_fh *fh,
+ struct v4l2_subdev_frame_size_enum *fse)
+{
+ struct iss_ipipe_device *ipipe = v4l2_get_subdevdata(sd);
+ struct v4l2_mbus_framefmt format;
+
+ if (fse->index != 0)
+ return -EINVAL;
+
+ format.code = fse->code;
+ format.width = 1;
+ format.height = 1;
+ ipipe_try_format(ipipe, fh, fse->pad, &format, V4L2_SUBDEV_FORMAT_TRY);
+ fse->min_width = format.width;
+ fse->min_height = format.height;
+
+ if (format.code != fse->code)
+ return -EINVAL;
+
+ format.code = fse->code;
+ format.width = -1;
+ format.height = -1;
+ ipipe_try_format(ipipe, fh, fse->pad, &format, V4L2_SUBDEV_FORMAT_TRY);
+ fse->max_width = format.width;
+ fse->max_height = format.height;
+
+ return 0;
+}
+
+/*
+ * ipipe_get_format - Retrieve the video format on a pad
+ * @sd : ISP IPIPE V4L2 subdevice
+ * @fh : V4L2 subdev file handle
+ * @fmt: Format
+ *
+ * Return 0 on success or -EINVAL if the pad is invalid or doesn't correspond
+ * to the format type.
+ */
+static int ipipe_get_format(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh,
+ struct v4l2_subdev_format *fmt)
+{
+ struct iss_ipipe_device *ipipe = v4l2_get_subdevdata(sd);
+ struct v4l2_mbus_framefmt *format;
+
+ format = __ipipe_get_format(ipipe, fh, fmt->pad, fmt->which);
+ if (format == NULL)
+ return -EINVAL;
+
+ fmt->format = *format;
+ return 0;
+}
+
+/*
+ * ipipe_set_format - Set the video format on a pad
+ * @sd : ISP IPIPE V4L2 subdevice
+ * @fh : V4L2 subdev file handle
+ * @fmt: Format
+ *
+ * Return 0 on success or -EINVAL if the pad is invalid or doesn't correspond
+ * to the format type.
+ */
+static int ipipe_set_format(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh,
+ struct v4l2_subdev_format *fmt)
+{
+ struct iss_ipipe_device *ipipe = v4l2_get_subdevdata(sd);
+ struct v4l2_mbus_framefmt *format;
+
+ format = __ipipe_get_format(ipipe, fh, fmt->pad, fmt->which);
+ if (format == NULL)
+ return -EINVAL;
+
+ ipipe_try_format(ipipe, fh, fmt->pad, &fmt->format, fmt->which);
+ *format = fmt->format;
+
+ /* Propagate the format from sink to source */
+ if (fmt->pad == IPIPE_PAD_SINK) {
+ format = __ipipe_get_format(ipipe, fh, IPIPE_PAD_SOURCE_VP,
+ fmt->which);
+ *format = fmt->format;
+ ipipe_try_format(ipipe, fh, IPIPE_PAD_SOURCE_VP, format,
+ fmt->which);
+ }
+
+ return 0;
+}
+
+static int ipipe_link_validate(struct v4l2_subdev *sd, struct media_link *link,
+ struct v4l2_subdev_format *source_fmt,
+ struct v4l2_subdev_format *sink_fmt)
+{
+ /* Check if the two ends match */
+ if (source_fmt->format.width != sink_fmt->format.width ||
+ source_fmt->format.height != sink_fmt->format.height)
+ return -EPIPE;
+
+ if (source_fmt->format.code != sink_fmt->format.code)
+ return -EPIPE;
+
+ return 0;
+}
+
+/*
+ * ipipe_init_formats - Initialize formats on all pads
+ * @sd: ISP IPIPE V4L2 subdevice
+ * @fh: V4L2 subdev file handle
+ *
+ * Initialize all pad formats with default values. If fh is not NULL, try
+ * formats are initialized on the file handle. Otherwise active formats are
+ * initialized on the device.
+ */
+static int ipipe_init_formats(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh)
+{
+ struct v4l2_subdev_format format;
+
+ memset(&format, 0, sizeof(format));
+ format.pad = IPIPE_PAD_SINK;
+ format.which = fh ? V4L2_SUBDEV_FORMAT_TRY : V4L2_SUBDEV_FORMAT_ACTIVE;
+ format.format.code = V4L2_MBUS_FMT_SGRBG10_1X10;
+ format.format.width = 4096;
+ format.format.height = 4096;
+ ipipe_set_format(sd, fh, &format);
+
+ return 0;
+}
+
+/* V4L2 subdev video operations */
+static const struct v4l2_subdev_video_ops ipipe_v4l2_video_ops = {
+ .s_stream = ipipe_set_stream,
+};
+
+/* V4L2 subdev pad operations */
+static const struct v4l2_subdev_pad_ops ipipe_v4l2_pad_ops = {
+ .enum_mbus_code = ipipe_enum_mbus_code,
+ .enum_frame_size = ipipe_enum_frame_size,
+ .get_fmt = ipipe_get_format,
+ .set_fmt = ipipe_set_format,
+ .link_validate = ipipe_link_validate,
+};
+
+/* V4L2 subdev operations */
+static const struct v4l2_subdev_ops ipipe_v4l2_ops = {
+ .video = &ipipe_v4l2_video_ops,
+ .pad = &ipipe_v4l2_pad_ops,
+};
+
+/* V4L2 subdev internal operations */
+static const struct v4l2_subdev_internal_ops ipipe_v4l2_internal_ops = {
+ .open = ipipe_init_formats,
+};
+
+/* -----------------------------------------------------------------------------
+ * Media entity operations
+ */
+
+/*
+ * ipipe_link_setup - Setup IPIPE connections
+ * @entity: IPIPE media entity
+ * @local: Pad at the local end of the link
+ * @remote: Pad at the remote end of the link
+ * @flags: Link flags
+ *
+ * return -EINVAL or zero on success
+ */
+static int ipipe_link_setup(struct media_entity *entity,
+ const struct media_pad *local,
+ const struct media_pad *remote, u32 flags)
+{
+ struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(entity);
+ struct iss_ipipe_device *ipipe = v4l2_get_subdevdata(sd);
+ struct iss_device *iss = to_iss_device(ipipe);
+
+ switch (local->index | media_entity_type(remote->entity)) {
+ case IPIPE_PAD_SINK | MEDIA_ENT_T_V4L2_SUBDEV:
+ /* Read from IPIPEIF. */
+ if (!(flags & MEDIA_LNK_FL_ENABLED)) {
+ ipipe->input = IPIPE_INPUT_NONE;
+ break;
+ }
+
+ if (ipipe->input != IPIPE_INPUT_NONE)
+ return -EBUSY;
+
+ if (remote->entity == &iss->ipipeif.subdev.entity)
+ ipipe->input = IPIPE_INPUT_IPIPEIF;
+
+ break;
+
+ case IPIPE_PAD_SOURCE_VP | MEDIA_ENT_T_V4L2_SUBDEV:
+ /* Send to RESIZER */
+ if (flags & MEDIA_LNK_FL_ENABLED) {
+ if (ipipe->output & ~IPIPE_OUTPUT_VP)
+ return -EBUSY;
+ ipipe->output |= IPIPE_OUTPUT_VP;
+ } else {
+ ipipe->output &= ~IPIPE_OUTPUT_VP;
+ }
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/* media operations */
+static const struct media_entity_operations ipipe_media_ops = {
+ .link_setup = ipipe_link_setup,
+ .link_validate = v4l2_subdev_link_validate,
+};
+
+/*
+ * ipipe_init_entities - Initialize V4L2 subdev and media entity
+ * @ipipe: ISS ISP IPIPE module
+ *
+ * Return 0 on success and a negative error code on failure.
+ */
+static int ipipe_init_entities(struct iss_ipipe_device *ipipe)
+{
+ struct v4l2_subdev *sd = &ipipe->subdev;
+ struct media_pad *pads = ipipe->pads;
+ struct media_entity *me = &sd->entity;
+ int ret;
+
+ ipipe->input = IPIPE_INPUT_NONE;
+
+ v4l2_subdev_init(sd, &ipipe_v4l2_ops);
+ sd->internal_ops = &ipipe_v4l2_internal_ops;
+ strlcpy(sd->name, "OMAP4 ISS ISP IPIPE", sizeof(sd->name));
+ sd->grp_id = 1 << 16; /* group ID for iss subdevs */
+ v4l2_set_subdevdata(sd, ipipe);
+ sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
+
+ pads[IPIPE_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
+ pads[IPIPE_PAD_SOURCE_VP].flags = MEDIA_PAD_FL_SOURCE;
+
+ me->ops = &ipipe_media_ops;
+ ret = media_entity_init(me, IPIPE_PADS_NUM, pads, 0);
+ if (ret < 0)
+ return ret;
+
+ ipipe_init_formats(sd, NULL);
+
+ return 0;
+}
+
+void omap4iss_ipipe_unregister_entities(struct iss_ipipe_device *ipipe)
+{
+ media_entity_cleanup(&ipipe->subdev.entity);
+
+ v4l2_device_unregister_subdev(&ipipe->subdev);
+}
+
+int omap4iss_ipipe_register_entities(struct iss_ipipe_device *ipipe,
+ struct v4l2_device *vdev)
+{
+ int ret;
+
+ /* Register the subdev and video node. */
+ ret = v4l2_device_register_subdev(vdev, &ipipe->subdev);
+ if (ret < 0)
+ goto error;
+
+ return 0;
+
+error:
+ omap4iss_ipipe_unregister_entities(ipipe);
+ return ret;
+}
+
+/* -----------------------------------------------------------------------------
+ * ISP IPIPE initialisation and cleanup
+ */
+
+/*
+ * omap4iss_ipipe_init - IPIPE module initialization.
+ * @iss: Device pointer specific to the OMAP4 ISS.
+ *
+ * TODO: Get the initialisation values from platform data.
+ *
+ * Return 0 on success or a negative error code otherwise.
+ */
+int omap4iss_ipipe_init(struct iss_device *iss)
+{
+ struct iss_ipipe_device *ipipe = &iss->ipipe;
+
+ ipipe->state = ISS_PIPELINE_STREAM_STOPPED;
+ init_waitqueue_head(&ipipe->wait);
+
+ return ipipe_init_entities(ipipe);
+}
+
+/*
+ * omap4iss_ipipe_cleanup - IPIPE module cleanup.
+ * @iss: Device pointer specific to the OMAP4 ISS.
+ */
+void omap4iss_ipipe_cleanup(struct iss_device *iss)
+{
+ /* FIXME: are you sure there's nothing to do? */
+}
diff --git a/drivers/staging/media/omap4iss/iss_ipipe.h b/drivers/staging/media/omap4iss/iss_ipipe.h
new file mode 100644
index 00000000000..c22d9041f2a
--- /dev/null
+++ b/drivers/staging/media/omap4iss/iss_ipipe.h
@@ -0,0 +1,67 @@
+/*
+ * TI OMAP4 ISS V4L2 Driver - ISP IPIPE module
+ *
+ * Copyright (C) 2012 Texas Instruments, Inc.
+ *
+ * Author: Sergio Aguirre <sergio.a.aguirre@gmail.com>
+ *
+ * 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.
+ */
+
+#ifndef OMAP4_ISS_IPIPE_H
+#define OMAP4_ISS_IPIPE_H
+
+#include "iss_video.h"
+
+enum ipipe_input_entity {
+ IPIPE_INPUT_NONE,
+ IPIPE_INPUT_IPIPEIF,
+};
+
+#define IPIPE_OUTPUT_VP (1 << 0)
+
+/* Sink and source IPIPE pads */
+#define IPIPE_PAD_SINK 0
+#define IPIPE_PAD_SOURCE_VP 1
+#define IPIPE_PADS_NUM 2
+
+/*
+ * struct iss_ipipe_device - Structure for the IPIPE module to store its own
+ * information
+ * @subdev: V4L2 subdevice
+ * @pads: Sink and source media entity pads
+ * @formats: Active video formats
+ * @input: Active input
+ * @output: Active outputs
+ * @error: A hardware error occurred during capture
+ * @state: Streaming state
+ * @wait: Wait queue used to stop the module
+ * @stopping: Stopping state
+ */
+struct iss_ipipe_device {
+ struct v4l2_subdev subdev;
+ struct media_pad pads[IPIPE_PADS_NUM];
+ struct v4l2_mbus_framefmt formats[IPIPE_PADS_NUM];
+
+ enum ipipe_input_entity input;
+ unsigned int output;
+ unsigned int error;
+
+ enum iss_pipeline_stream_state state;
+ wait_queue_head_t wait;
+ atomic_t stopping;
+};
+
+struct iss_device;
+
+int omap4iss_ipipe_register_entities(struct iss_ipipe_device *ipipe,
+ struct v4l2_device *vdev);
+void omap4iss_ipipe_unregister_entities(struct iss_ipipe_device *ipipe);
+
+int omap4iss_ipipe_init(struct iss_device *iss);
+void omap4iss_ipipe_cleanup(struct iss_device *iss);
+
+#endif /* OMAP4_ISS_IPIPE_H */
diff --git a/drivers/staging/media/omap4iss/iss_ipipeif.c b/drivers/staging/media/omap4iss/iss_ipipeif.c
new file mode 100644
index 00000000000..7bc14576249
--- /dev/null
+++ b/drivers/staging/media/omap4iss/iss_ipipeif.c
@@ -0,0 +1,849 @@
+/*
+ * TI OMAP4 ISS V4L2 Driver - ISP IPIPEIF module
+ *
+ * Copyright (C) 2012 Texas Instruments, Inc.
+ *
+ * Author: Sergio Aguirre <sergio.a.aguirre@gmail.com>
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/uaccess.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/dma-mapping.h>
+#include <linux/mm.h>
+#include <linux/sched.h>
+
+#include "iss.h"
+#include "iss_regs.h"
+#include "iss_ipipeif.h"
+
+static const unsigned int ipipeif_fmts[] = {
+ V4L2_MBUS_FMT_SGRBG10_1X10,
+ V4L2_MBUS_FMT_SRGGB10_1X10,
+ V4L2_MBUS_FMT_SBGGR10_1X10,
+ V4L2_MBUS_FMT_SGBRG10_1X10,
+ V4L2_MBUS_FMT_UYVY8_1X16,
+ V4L2_MBUS_FMT_YUYV8_1X16,
+};
+
+/*
+ * ipipeif_print_status - Print current IPIPEIF Module register values.
+ * @ipipeif: Pointer to ISS ISP IPIPEIF device.
+ *
+ * Also prints other debug information stored in the IPIPEIF module.
+ */
+#define IPIPEIF_PRINT_REGISTER(iss, name)\
+ dev_dbg(iss->dev, "###IPIPEIF " #name "=0x%08x\n", \
+ iss_reg_read(iss, OMAP4_ISS_MEM_ISP_IPIPEIF, IPIPEIF_##name))
+
+#define ISIF_PRINT_REGISTER(iss, name)\
+ dev_dbg(iss->dev, "###ISIF " #name "=0x%08x\n", \
+ iss_reg_read(iss, OMAP4_ISS_MEM_ISP_ISIF, ISIF_##name))
+
+#define ISP5_PRINT_REGISTER(iss, name)\
+ dev_dbg(iss->dev, "###ISP5 " #name "=0x%08x\n", \
+ iss_reg_read(iss, OMAP4_ISS_MEM_ISP_SYS1, ISP5_##name))
+
+static void ipipeif_print_status(struct iss_ipipeif_device *ipipeif)
+{
+ struct iss_device *iss = to_iss_device(ipipeif);
+
+ dev_dbg(iss->dev, "-------------IPIPEIF Register dump-------------\n");
+
+ IPIPEIF_PRINT_REGISTER(iss, CFG1);
+ IPIPEIF_PRINT_REGISTER(iss, CFG2);
+
+ ISIF_PRINT_REGISTER(iss, SYNCEN);
+ ISIF_PRINT_REGISTER(iss, CADU);
+ ISIF_PRINT_REGISTER(iss, CADL);
+ ISIF_PRINT_REGISTER(iss, MODESET);
+ ISIF_PRINT_REGISTER(iss, CCOLP);
+ ISIF_PRINT_REGISTER(iss, SPH);
+ ISIF_PRINT_REGISTER(iss, LNH);
+ ISIF_PRINT_REGISTER(iss, LNV);
+ ISIF_PRINT_REGISTER(iss, VDINT(0));
+ ISIF_PRINT_REGISTER(iss, HSIZE);
+
+ ISP5_PRINT_REGISTER(iss, SYSCONFIG);
+ ISP5_PRINT_REGISTER(iss, CTRL);
+ ISP5_PRINT_REGISTER(iss, IRQSTATUS(0));
+ ISP5_PRINT_REGISTER(iss, IRQENABLE_SET(0));
+ ISP5_PRINT_REGISTER(iss, IRQENABLE_CLR(0));
+
+ dev_dbg(iss->dev, "-----------------------------------------------\n");
+}
+
+static void ipipeif_write_enable(struct iss_ipipeif_device *ipipeif, u8 enable)
+{
+ struct iss_device *iss = to_iss_device(ipipeif);
+
+ iss_reg_update(iss, OMAP4_ISS_MEM_ISP_ISIF, ISIF_SYNCEN,
+ ISIF_SYNCEN_DWEN, enable ? ISIF_SYNCEN_DWEN : 0);
+}
+
+/*
+ * ipipeif_enable - Enable/Disable IPIPEIF.
+ * @enable: enable flag
+ *
+ */
+static void ipipeif_enable(struct iss_ipipeif_device *ipipeif, u8 enable)
+{
+ struct iss_device *iss = to_iss_device(ipipeif);
+
+ iss_reg_update(iss, OMAP4_ISS_MEM_ISP_ISIF, ISIF_SYNCEN,
+ ISIF_SYNCEN_SYEN, enable ? ISIF_SYNCEN_SYEN : 0);
+}
+
+/* -----------------------------------------------------------------------------
+ * Format- and pipeline-related configuration helpers
+ */
+
+/*
+ * ipipeif_set_outaddr - Set memory address to save output image
+ * @ipipeif: Pointer to ISP IPIPEIF device.
+ * @addr: 32-bit memory address aligned on 32 byte boundary.
+ *
+ * Sets the memory address where the output will be saved.
+ */
+static void ipipeif_set_outaddr(struct iss_ipipeif_device *ipipeif, u32 addr)
+{
+ struct iss_device *iss = to_iss_device(ipipeif);
+
+ /* Save address splitted in Base Address H & L */
+ iss_reg_write(iss, OMAP4_ISS_MEM_ISP_ISIF, ISIF_CADU,
+ (addr >> (16 + 5)) & ISIF_CADU_MASK);
+ iss_reg_write(iss, OMAP4_ISS_MEM_ISP_ISIF, ISIF_CADL,
+ (addr >> 5) & ISIF_CADL_MASK);
+}
+
+static void ipipeif_configure(struct iss_ipipeif_device *ipipeif)
+{
+ struct iss_device *iss = to_iss_device(ipipeif);
+ const struct iss_format_info *info;
+ struct v4l2_mbus_framefmt *format;
+ u32 isif_ccolp = 0;
+
+ omap4iss_configure_bridge(iss, ipipeif->input);
+
+ /* IPIPEIF_PAD_SINK */
+ format = &ipipeif->formats[IPIPEIF_PAD_SINK];
+
+ /* IPIPEIF with YUV422 input from ISIF */
+ iss_reg_clr(iss, OMAP4_ISS_MEM_ISP_IPIPEIF, IPIPEIF_CFG1,
+ IPIPEIF_CFG1_INPSRC1_MASK | IPIPEIF_CFG1_INPSRC2_MASK);
+
+ /* Select ISIF/IPIPEIF input format */
+ switch (format->code) {
+ case V4L2_MBUS_FMT_UYVY8_1X16:
+ case V4L2_MBUS_FMT_YUYV8_1X16:
+ iss_reg_update(iss, OMAP4_ISS_MEM_ISP_ISIF, ISIF_MODESET,
+ ISIF_MODESET_CCDMD | ISIF_MODESET_INPMOD_MASK |
+ ISIF_MODESET_CCDW_MASK,
+ ISIF_MODESET_INPMOD_YCBCR16);
+
+ iss_reg_update(iss, OMAP4_ISS_MEM_ISP_IPIPEIF, IPIPEIF_CFG2,
+ IPIPEIF_CFG2_YUV8, IPIPEIF_CFG2_YUV16);
+
+ break;
+ case V4L2_MBUS_FMT_SGRBG10_1X10:
+ isif_ccolp = ISIF_CCOLP_CP0_F0_GR |
+ ISIF_CCOLP_CP1_F0_R |
+ ISIF_CCOLP_CP2_F0_B |
+ ISIF_CCOLP_CP3_F0_GB;
+ goto cont_raw;
+ case V4L2_MBUS_FMT_SRGGB10_1X10:
+ isif_ccolp = ISIF_CCOLP_CP0_F0_R |
+ ISIF_CCOLP_CP1_F0_GR |
+ ISIF_CCOLP_CP2_F0_GB |
+ ISIF_CCOLP_CP3_F0_B;
+ goto cont_raw;
+ case V4L2_MBUS_FMT_SBGGR10_1X10:
+ isif_ccolp = ISIF_CCOLP_CP0_F0_B |
+ ISIF_CCOLP_CP1_F0_GB |
+ ISIF_CCOLP_CP2_F0_GR |
+ ISIF_CCOLP_CP3_F0_R;
+ goto cont_raw;
+ case V4L2_MBUS_FMT_SGBRG10_1X10:
+ isif_ccolp = ISIF_CCOLP_CP0_F0_GB |
+ ISIF_CCOLP_CP1_F0_B |
+ ISIF_CCOLP_CP2_F0_R |
+ ISIF_CCOLP_CP3_F0_GR;
+cont_raw:
+ iss_reg_clr(iss, OMAP4_ISS_MEM_ISP_IPIPEIF, IPIPEIF_CFG2,
+ IPIPEIF_CFG2_YUV16);
+
+ iss_reg_update(iss, OMAP4_ISS_MEM_ISP_ISIF, ISIF_MODESET,
+ ISIF_MODESET_CCDMD | ISIF_MODESET_INPMOD_MASK |
+ ISIF_MODESET_CCDW_MASK, ISIF_MODESET_INPMOD_RAW |
+ ISIF_MODESET_CCDW_2BIT);
+
+ info = omap4iss_video_format_info(format->code);
+ iss_reg_update(iss, OMAP4_ISS_MEM_ISP_ISIF, ISIF_CGAMMAWD,
+ ISIF_CGAMMAWD_GWDI_MASK,
+ ISIF_CGAMMAWD_GWDI(info->bpp));
+
+ /* Set RAW Bayer pattern */
+ iss_reg_write(iss, OMAP4_ISS_MEM_ISP_ISIF, ISIF_CCOLP,
+ isif_ccolp);
+ break;
+ }
+
+ iss_reg_write(iss, OMAP4_ISS_MEM_ISP_ISIF, ISIF_SPH, 0 & ISIF_SPH_MASK);
+ iss_reg_write(iss, OMAP4_ISS_MEM_ISP_ISIF, ISIF_LNH,
+ (format->width - 1) & ISIF_LNH_MASK);
+ iss_reg_write(iss, OMAP4_ISS_MEM_ISP_ISIF, ISIF_LNV,
+ (format->height - 1) & ISIF_LNV_MASK);
+
+ /* Generate ISIF0 on the last line of the image */
+ iss_reg_write(iss, OMAP4_ISS_MEM_ISP_ISIF, ISIF_VDINT(0),
+ format->height - 1);
+
+ /* IPIPEIF_PAD_SOURCE_ISIF_SF */
+ format = &ipipeif->formats[IPIPEIF_PAD_SOURCE_ISIF_SF];
+
+ iss_reg_write(iss, OMAP4_ISS_MEM_ISP_ISIF, ISIF_HSIZE,
+ (ipipeif->video_out.bpl_value >> 5) &
+ ISIF_HSIZE_HSIZE_MASK);
+
+ /* IPIPEIF_PAD_SOURCE_VP */
+ /* Do nothing? */
+}
+
+/* -----------------------------------------------------------------------------
+ * Interrupt handling
+ */
+
+static void ipipeif_isr_buffer(struct iss_ipipeif_device *ipipeif)
+{
+ struct iss_buffer *buffer;
+
+ /* The ISIF generates VD0 interrupts even when writes are disabled.
+ * deal with it anyway). Disabling the ISIF when no buffer is available
+ * is thus not be enough, we need to handle the situation explicitly.
+ */
+ if (list_empty(&ipipeif->video_out.dmaqueue))
+ return;
+
+ ipipeif_write_enable(ipipeif, 0);
+
+ buffer = omap4iss_video_buffer_next(&ipipeif->video_out);
+ if (buffer == NULL)
+ return;
+
+ ipipeif_set_outaddr(ipipeif, buffer->iss_addr);
+
+ ipipeif_write_enable(ipipeif, 1);
+}
+
+/*
+ * ipipeif_isif0_isr - Handle ISIF0 event
+ * @ipipeif: Pointer to ISP IPIPEIF device.
+ *
+ * Executes LSC deferred enablement before next frame starts.
+ */
+static void ipipeif_isif0_isr(struct iss_ipipeif_device *ipipeif)
+{
+ struct iss_pipeline *pipe =
+ to_iss_pipeline(&ipipeif->subdev.entity);
+ if (pipe->do_propagation)
+ atomic_inc(&pipe->frame_number);
+
+ if (ipipeif->output & IPIPEIF_OUTPUT_MEMORY)
+ ipipeif_isr_buffer(ipipeif);
+}
+
+/*
+ * omap4iss_ipipeif_isr - Configure ipipeif during interframe time.
+ * @ipipeif: Pointer to ISP IPIPEIF device.
+ * @events: IPIPEIF events
+ */
+void omap4iss_ipipeif_isr(struct iss_ipipeif_device *ipipeif, u32 events)
+{
+ if (omap4iss_module_sync_is_stopping(&ipipeif->wait,
+ &ipipeif->stopping))
+ return;
+
+ if (events & ISP5_IRQ_ISIF_INT(0))
+ ipipeif_isif0_isr(ipipeif);
+}
+
+/* -----------------------------------------------------------------------------
+ * ISP video operations
+ */
+
+static int ipipeif_video_queue(struct iss_video *video,
+ struct iss_buffer *buffer)
+{
+ struct iss_ipipeif_device *ipipeif = container_of(video,
+ struct iss_ipipeif_device, video_out);
+
+ if (!(ipipeif->output & IPIPEIF_OUTPUT_MEMORY))
+ return -ENODEV;
+
+ ipipeif_set_outaddr(ipipeif, buffer->iss_addr);
+
+ /*
+ * If streaming was enabled before there was a buffer queued
+ * or underrun happened in the ISR, the hardware was not enabled
+ * and DMA queue flag ISS_VIDEO_DMAQUEUE_UNDERRUN is still set.
+ * Enable it now.
+ */
+ if (video->dmaqueue_flags & ISS_VIDEO_DMAQUEUE_UNDERRUN) {
+ if (ipipeif->output & IPIPEIF_OUTPUT_MEMORY)
+ ipipeif_write_enable(ipipeif, 1);
+ ipipeif_enable(ipipeif, 1);
+ iss_video_dmaqueue_flags_clr(video);
+ }
+
+ return 0;
+}
+
+static const struct iss_video_operations ipipeif_video_ops = {
+ .queue = ipipeif_video_queue,
+};
+
+/* -----------------------------------------------------------------------------
+ * V4L2 subdev operations
+ */
+
+#define IPIPEIF_DRV_SUBCLK_MASK (OMAP4_ISS_ISP_SUBCLK_IPIPEIF |\
+ OMAP4_ISS_ISP_SUBCLK_ISIF)
+/*
+ * ipipeif_set_stream - Enable/Disable streaming on the IPIPEIF module
+ * @sd: ISP IPIPEIF V4L2 subdevice
+ * @enable: Enable/disable stream
+ */
+static int ipipeif_set_stream(struct v4l2_subdev *sd, int enable)
+{
+ struct iss_ipipeif_device *ipipeif = v4l2_get_subdevdata(sd);
+ struct iss_device *iss = to_iss_device(ipipeif);
+ struct iss_video *video_out = &ipipeif->video_out;
+ int ret = 0;
+
+ if (ipipeif->state == ISS_PIPELINE_STREAM_STOPPED) {
+ if (enable == ISS_PIPELINE_STREAM_STOPPED)
+ return 0;
+
+ omap4iss_isp_subclk_enable(iss, IPIPEIF_DRV_SUBCLK_MASK);
+ }
+
+ switch (enable) {
+ case ISS_PIPELINE_STREAM_CONTINUOUS:
+
+ ipipeif_configure(ipipeif);
+ ipipeif_print_status(ipipeif);
+
+ /*
+ * When outputting to memory with no buffer available, let the
+ * buffer queue handler start the hardware. A DMA queue flag
+ * ISS_VIDEO_DMAQUEUE_QUEUED will be set as soon as there is
+ * a buffer available.
+ */
+ if (ipipeif->output & IPIPEIF_OUTPUT_MEMORY &&
+ !(video_out->dmaqueue_flags & ISS_VIDEO_DMAQUEUE_QUEUED))
+ break;
+
+ atomic_set(&ipipeif->stopping, 0);
+ if (ipipeif->output & IPIPEIF_OUTPUT_MEMORY)
+ ipipeif_write_enable(ipipeif, 1);
+ ipipeif_enable(ipipeif, 1);
+ iss_video_dmaqueue_flags_clr(video_out);
+ break;
+
+ case ISS_PIPELINE_STREAM_STOPPED:
+ if (ipipeif->state == ISS_PIPELINE_STREAM_STOPPED)
+ return 0;
+ if (omap4iss_module_sync_idle(&sd->entity, &ipipeif->wait,
+ &ipipeif->stopping))
+ ret = -ETIMEDOUT;
+
+ if (ipipeif->output & IPIPEIF_OUTPUT_MEMORY)
+ ipipeif_write_enable(ipipeif, 0);
+ ipipeif_enable(ipipeif, 0);
+ omap4iss_isp_subclk_disable(iss, IPIPEIF_DRV_SUBCLK_MASK);
+ iss_video_dmaqueue_flags_clr(video_out);
+ break;
+ }
+
+ ipipeif->state = enable;
+ return ret;
+}
+
+static struct v4l2_mbus_framefmt *
+__ipipeif_get_format(struct iss_ipipeif_device *ipipeif,
+ struct v4l2_subdev_fh *fh, unsigned int pad,
+ enum v4l2_subdev_format_whence which)
+{
+ if (which == V4L2_SUBDEV_FORMAT_TRY)
+ return v4l2_subdev_get_try_format(fh, pad);
+ else
+ return &ipipeif->formats[pad];
+}
+
+/*
+ * ipipeif_try_format - Try video format on a pad
+ * @ipipeif: ISS IPIPEIF device
+ * @fh : V4L2 subdev file handle
+ * @pad: Pad number
+ * @fmt: Format
+ */
+static void
+ipipeif_try_format(struct iss_ipipeif_device *ipipeif,
+ struct v4l2_subdev_fh *fh, unsigned int pad,
+ struct v4l2_mbus_framefmt *fmt,
+ enum v4l2_subdev_format_whence which)
+{
+ struct v4l2_mbus_framefmt *format;
+ unsigned int width = fmt->width;
+ unsigned int height = fmt->height;
+ unsigned int i;
+
+ switch (pad) {
+ case IPIPEIF_PAD_SINK:
+ /* TODO: If the IPIPEIF output formatter pad is connected
+ * directly to the resizer, only YUV formats can be used.
+ */
+ for (i = 0; i < ARRAY_SIZE(ipipeif_fmts); i++) {
+ if (fmt->code == ipipeif_fmts[i])
+ break;
+ }
+
+ /* If not found, use SGRBG10 as default */
+ if (i >= ARRAY_SIZE(ipipeif_fmts))
+ fmt->code = V4L2_MBUS_FMT_SGRBG10_1X10;
+
+ /* Clamp the input size. */
+ fmt->width = clamp_t(u32, width, 1, 8192);
+ fmt->height = clamp_t(u32, height, 1, 8192);
+ break;
+
+ case IPIPEIF_PAD_SOURCE_ISIF_SF:
+ format = __ipipeif_get_format(ipipeif, fh, IPIPEIF_PAD_SINK,
+ which);
+ memcpy(fmt, format, sizeof(*fmt));
+
+ /* The data formatter truncates the number of horizontal output
+ * pixels to a multiple of 16. To avoid clipping data, allow
+ * callers to request an output size bigger than the input size
+ * up to the nearest multiple of 16.
+ */
+ fmt->width = clamp_t(u32, width, 32, (fmt->width + 15) & ~15);
+ fmt->width &= ~15;
+ fmt->height = clamp_t(u32, height, 32, fmt->height);
+ break;
+
+ case IPIPEIF_PAD_SOURCE_VP:
+ format = __ipipeif_get_format(ipipeif, fh, IPIPEIF_PAD_SINK,
+ which);
+ memcpy(fmt, format, sizeof(*fmt));
+
+ fmt->width = clamp_t(u32, width, 32, fmt->width);
+ fmt->height = clamp_t(u32, height, 32, fmt->height);
+ break;
+ }
+
+ /* Data is written to memory unpacked, each 10-bit or 12-bit pixel is
+ * stored on 2 bytes.
+ */
+ fmt->colorspace = V4L2_COLORSPACE_SRGB;
+ fmt->field = V4L2_FIELD_NONE;
+}
+
+/*
+ * ipipeif_enum_mbus_code - Handle pixel format enumeration
+ * @sd : pointer to v4l2 subdev structure
+ * @fh : V4L2 subdev file handle
+ * @code : pointer to v4l2_subdev_mbus_code_enum structure
+ * return -EINVAL or zero on success
+ */
+static int ipipeif_enum_mbus_code(struct v4l2_subdev *sd,
+ struct v4l2_subdev_fh *fh,
+ struct v4l2_subdev_mbus_code_enum *code)
+{
+ struct iss_ipipeif_device *ipipeif = v4l2_get_subdevdata(sd);
+ struct v4l2_mbus_framefmt *format;
+
+ switch (code->pad) {
+ case IPIPEIF_PAD_SINK:
+ if (code->index >= ARRAY_SIZE(ipipeif_fmts))
+ return -EINVAL;
+
+ code->code = ipipeif_fmts[code->index];
+ break;
+
+ case IPIPEIF_PAD_SOURCE_ISIF_SF:
+ case IPIPEIF_PAD_SOURCE_VP:
+ /* No format conversion inside IPIPEIF */
+ if (code->index != 0)
+ return -EINVAL;
+
+ format = __ipipeif_get_format(ipipeif, fh, IPIPEIF_PAD_SINK,
+ V4L2_SUBDEV_FORMAT_TRY);
+
+ code->code = format->code;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int ipipeif_enum_frame_size(struct v4l2_subdev *sd,
+ struct v4l2_subdev_fh *fh,
+ struct v4l2_subdev_frame_size_enum *fse)
+{
+ struct iss_ipipeif_device *ipipeif = v4l2_get_subdevdata(sd);
+ struct v4l2_mbus_framefmt format;
+
+ if (fse->index != 0)
+ return -EINVAL;
+
+ format.code = fse->code;
+ format.width = 1;
+ format.height = 1;
+ ipipeif_try_format(ipipeif, fh, fse->pad, &format,
+ V4L2_SUBDEV_FORMAT_TRY);
+ fse->min_width = format.width;
+ fse->min_height = format.height;
+
+ if (format.code != fse->code)
+ return -EINVAL;
+
+ format.code = fse->code;
+ format.width = -1;
+ format.height = -1;
+ ipipeif_try_format(ipipeif, fh, fse->pad, &format,
+ V4L2_SUBDEV_FORMAT_TRY);
+ fse->max_width = format.width;
+ fse->max_height = format.height;
+
+ return 0;
+}
+
+/*
+ * ipipeif_get_format - Retrieve the video format on a pad
+ * @sd : ISP IPIPEIF V4L2 subdevice
+ * @fh : V4L2 subdev file handle
+ * @fmt: Format
+ *
+ * Return 0 on success or -EINVAL if the pad is invalid or doesn't correspond
+ * to the format type.
+ */
+static int ipipeif_get_format(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh,
+ struct v4l2_subdev_format *fmt)
+{
+ struct iss_ipipeif_device *ipipeif = v4l2_get_subdevdata(sd);
+ struct v4l2_mbus_framefmt *format;
+
+ format = __ipipeif_get_format(ipipeif, fh, fmt->pad, fmt->which);
+ if (format == NULL)
+ return -EINVAL;
+
+ fmt->format = *format;
+ return 0;
+}
+
+/*
+ * ipipeif_set_format - Set the video format on a pad
+ * @sd : ISP IPIPEIF V4L2 subdevice
+ * @fh : V4L2 subdev file handle
+ * @fmt: Format
+ *
+ * Return 0 on success or -EINVAL if the pad is invalid or doesn't correspond
+ * to the format type.
+ */
+static int ipipeif_set_format(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh,
+ struct v4l2_subdev_format *fmt)
+{
+ struct iss_ipipeif_device *ipipeif = v4l2_get_subdevdata(sd);
+ struct v4l2_mbus_framefmt *format;
+
+ format = __ipipeif_get_format(ipipeif, fh, fmt->pad, fmt->which);
+ if (format == NULL)
+ return -EINVAL;
+
+ ipipeif_try_format(ipipeif, fh, fmt->pad, &fmt->format, fmt->which);
+ *format = fmt->format;
+
+ /* Propagate the format from sink to source */
+ if (fmt->pad == IPIPEIF_PAD_SINK) {
+ format = __ipipeif_get_format(ipipeif, fh,
+ IPIPEIF_PAD_SOURCE_ISIF_SF,
+ fmt->which);
+ *format = fmt->format;
+ ipipeif_try_format(ipipeif, fh, IPIPEIF_PAD_SOURCE_ISIF_SF,
+ format, fmt->which);
+
+ format = __ipipeif_get_format(ipipeif, fh,
+ IPIPEIF_PAD_SOURCE_VP,
+ fmt->which);
+ *format = fmt->format;
+ ipipeif_try_format(ipipeif, fh, IPIPEIF_PAD_SOURCE_VP, format,
+ fmt->which);
+ }
+
+ return 0;
+}
+
+static int ipipeif_link_validate(struct v4l2_subdev *sd,
+ struct media_link *link,
+ struct v4l2_subdev_format *source_fmt,
+ struct v4l2_subdev_format *sink_fmt)
+{
+ /* Check if the two ends match */
+ if (source_fmt->format.width != sink_fmt->format.width ||
+ source_fmt->format.height != sink_fmt->format.height)
+ return -EPIPE;
+
+ if (source_fmt->format.code != sink_fmt->format.code)
+ return -EPIPE;
+
+ return 0;
+}
+
+/*
+ * ipipeif_init_formats - Initialize formats on all pads
+ * @sd: ISP IPIPEIF V4L2 subdevice
+ * @fh: V4L2 subdev file handle
+ *
+ * Initialize all pad formats with default values. If fh is not NULL, try
+ * formats are initialized on the file handle. Otherwise active formats are
+ * initialized on the device.
+ */
+static int ipipeif_init_formats(struct v4l2_subdev *sd,
+ struct v4l2_subdev_fh *fh)
+{
+ struct v4l2_subdev_format format;
+
+ memset(&format, 0, sizeof(format));
+ format.pad = IPIPEIF_PAD_SINK;
+ format.which = fh ? V4L2_SUBDEV_FORMAT_TRY : V4L2_SUBDEV_FORMAT_ACTIVE;
+ format.format.code = V4L2_MBUS_FMT_SGRBG10_1X10;
+ format.format.width = 4096;
+ format.format.height = 4096;
+ ipipeif_set_format(sd, fh, &format);
+
+ return 0;
+}
+
+/* V4L2 subdev video operations */
+static const struct v4l2_subdev_video_ops ipipeif_v4l2_video_ops = {
+ .s_stream = ipipeif_set_stream,
+};
+
+/* V4L2 subdev pad operations */
+static const struct v4l2_subdev_pad_ops ipipeif_v4l2_pad_ops = {
+ .enum_mbus_code = ipipeif_enum_mbus_code,
+ .enum_frame_size = ipipeif_enum_frame_size,
+ .get_fmt = ipipeif_get_format,
+ .set_fmt = ipipeif_set_format,
+ .link_validate = ipipeif_link_validate,
+};
+
+/* V4L2 subdev operations */
+static const struct v4l2_subdev_ops ipipeif_v4l2_ops = {
+ .video = &ipipeif_v4l2_video_ops,
+ .pad = &ipipeif_v4l2_pad_ops,
+};
+
+/* V4L2 subdev internal operations */
+static const struct v4l2_subdev_internal_ops ipipeif_v4l2_internal_ops = {
+ .open = ipipeif_init_formats,
+};
+
+/* -----------------------------------------------------------------------------
+ * Media entity operations
+ */
+
+/*
+ * ipipeif_link_setup - Setup IPIPEIF connections
+ * @entity: IPIPEIF media entity
+ * @local: Pad at the local end of the link
+ * @remote: Pad at the remote end of the link
+ * @flags: Link flags
+ *
+ * return -EINVAL or zero on success
+ */
+static int ipipeif_link_setup(struct media_entity *entity,
+ const struct media_pad *local,
+ const struct media_pad *remote, u32 flags)
+{
+ struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(entity);
+ struct iss_ipipeif_device *ipipeif = v4l2_get_subdevdata(sd);
+ struct iss_device *iss = to_iss_device(ipipeif);
+
+ switch (local->index | media_entity_type(remote->entity)) {
+ case IPIPEIF_PAD_SINK | MEDIA_ENT_T_V4L2_SUBDEV:
+ /* Read from the sensor CSI2a or CSI2b. */
+ if (!(flags & MEDIA_LNK_FL_ENABLED)) {
+ ipipeif->input = IPIPEIF_INPUT_NONE;
+ break;
+ }
+
+ if (ipipeif->input != IPIPEIF_INPUT_NONE)
+ return -EBUSY;
+
+ if (remote->entity == &iss->csi2a.subdev.entity)
+ ipipeif->input = IPIPEIF_INPUT_CSI2A;
+ else if (remote->entity == &iss->csi2b.subdev.entity)
+ ipipeif->input = IPIPEIF_INPUT_CSI2B;
+
+ break;
+
+ case IPIPEIF_PAD_SOURCE_ISIF_SF | MEDIA_ENT_T_DEVNODE:
+ /* Write to memory */
+ if (flags & MEDIA_LNK_FL_ENABLED) {
+ if (ipipeif->output & ~IPIPEIF_OUTPUT_MEMORY)
+ return -EBUSY;
+ ipipeif->output |= IPIPEIF_OUTPUT_MEMORY;
+ } else {
+ ipipeif->output &= ~IPIPEIF_OUTPUT_MEMORY;
+ }
+ break;
+
+ case IPIPEIF_PAD_SOURCE_VP | MEDIA_ENT_T_V4L2_SUBDEV:
+ /* Send to IPIPE/RESIZER */
+ if (flags & MEDIA_LNK_FL_ENABLED) {
+ if (ipipeif->output & ~IPIPEIF_OUTPUT_VP)
+ return -EBUSY;
+ ipipeif->output |= IPIPEIF_OUTPUT_VP;
+ } else {
+ ipipeif->output &= ~IPIPEIF_OUTPUT_VP;
+ }
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/* media operations */
+static const struct media_entity_operations ipipeif_media_ops = {
+ .link_setup = ipipeif_link_setup,
+ .link_validate = v4l2_subdev_link_validate,
+};
+
+/*
+ * ipipeif_init_entities - Initialize V4L2 subdev and media entity
+ * @ipipeif: ISS ISP IPIPEIF module
+ *
+ * Return 0 on success and a negative error code on failure.
+ */
+static int ipipeif_init_entities(struct iss_ipipeif_device *ipipeif)
+{
+ struct v4l2_subdev *sd = &ipipeif->subdev;
+ struct media_pad *pads = ipipeif->pads;
+ struct media_entity *me = &sd->entity;
+ int ret;
+
+ ipipeif->input = IPIPEIF_INPUT_NONE;
+
+ v4l2_subdev_init(sd, &ipipeif_v4l2_ops);
+ sd->internal_ops = &ipipeif_v4l2_internal_ops;
+ strlcpy(sd->name, "OMAP4 ISS ISP IPIPEIF", sizeof(sd->name));
+ sd->grp_id = 1 << 16; /* group ID for iss subdevs */
+ v4l2_set_subdevdata(sd, ipipeif);
+ sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
+
+ pads[IPIPEIF_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
+ pads[IPIPEIF_PAD_SOURCE_ISIF_SF].flags = MEDIA_PAD_FL_SOURCE;
+ pads[IPIPEIF_PAD_SOURCE_VP].flags = MEDIA_PAD_FL_SOURCE;
+
+ me->ops = &ipipeif_media_ops;
+ ret = media_entity_init(me, IPIPEIF_PADS_NUM, pads, 0);
+ if (ret < 0)
+ return ret;
+
+ ipipeif_init_formats(sd, NULL);
+
+ ipipeif->video_out.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ ipipeif->video_out.ops = &ipipeif_video_ops;
+ ipipeif->video_out.iss = to_iss_device(ipipeif);
+ ipipeif->video_out.capture_mem = PAGE_ALIGN(4096 * 4096) * 3;
+ ipipeif->video_out.bpl_alignment = 32;
+ ipipeif->video_out.bpl_zero_padding = 1;
+ ipipeif->video_out.bpl_max = 0x1ffe0;
+
+ ret = omap4iss_video_init(&ipipeif->video_out, "ISP IPIPEIF");
+ if (ret < 0)
+ return ret;
+
+ /* Connect the IPIPEIF subdev to the video node. */
+ ret = media_entity_create_link(&ipipeif->subdev.entity,
+ IPIPEIF_PAD_SOURCE_ISIF_SF,
+ &ipipeif->video_out.video.entity, 0, 0);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+void omap4iss_ipipeif_unregister_entities(struct iss_ipipeif_device *ipipeif)
+{
+ media_entity_cleanup(&ipipeif->subdev.entity);
+
+ v4l2_device_unregister_subdev(&ipipeif->subdev);
+ omap4iss_video_unregister(&ipipeif->video_out);
+}
+
+int omap4iss_ipipeif_register_entities(struct iss_ipipeif_device *ipipeif,
+ struct v4l2_device *vdev)
+{
+ int ret;
+
+ /* Register the subdev and video node. */
+ ret = v4l2_device_register_subdev(vdev, &ipipeif->subdev);
+ if (ret < 0)
+ goto error;
+
+ ret = omap4iss_video_register(&ipipeif->video_out, vdev);
+ if (ret < 0)
+ goto error;
+
+ return 0;
+
+error:
+ omap4iss_ipipeif_unregister_entities(ipipeif);
+ return ret;
+}
+
+/* -----------------------------------------------------------------------------
+ * ISP IPIPEIF initialisation and cleanup
+ */
+
+/*
+ * omap4iss_ipipeif_init - IPIPEIF module initialization.
+ * @iss: Device pointer specific to the OMAP4 ISS.
+ *
+ * TODO: Get the initialisation values from platform data.
+ *
+ * Return 0 on success or a negative error code otherwise.
+ */
+int omap4iss_ipipeif_init(struct iss_device *iss)
+{
+ struct iss_ipipeif_device *ipipeif = &iss->ipipeif;
+
+ ipipeif->state = ISS_PIPELINE_STREAM_STOPPED;
+ init_waitqueue_head(&ipipeif->wait);
+
+ return ipipeif_init_entities(ipipeif);
+}
+
+/*
+ * omap4iss_ipipeif_cleanup - IPIPEIF module cleanup.
+ * @iss: Device pointer specific to the OMAP4 ISS.
+ */
+void omap4iss_ipipeif_cleanup(struct iss_device *iss)
+{
+ /* FIXME: are you sure there's nothing to do? */
+}
diff --git a/drivers/staging/media/omap4iss/iss_ipipeif.h b/drivers/staging/media/omap4iss/iss_ipipeif.h
new file mode 100644
index 00000000000..cbdccb982ee
--- /dev/null
+++ b/drivers/staging/media/omap4iss/iss_ipipeif.h
@@ -0,0 +1,92 @@
+/*
+ * TI OMAP4 ISS V4L2 Driver - ISP IPIPEIF module
+ *
+ * Copyright (C) 2012 Texas Instruments, Inc.
+ *
+ * Author: Sergio Aguirre <sergio.a.aguirre@gmail.com>
+ *
+ * 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.
+ */
+
+#ifndef OMAP4_ISS_IPIPEIF_H
+#define OMAP4_ISS_IPIPEIF_H
+
+#include "iss_video.h"
+
+enum ipipeif_input_entity {
+ IPIPEIF_INPUT_NONE,
+ IPIPEIF_INPUT_CSI2A,
+ IPIPEIF_INPUT_CSI2B
+};
+
+#define IPIPEIF_OUTPUT_MEMORY (1 << 0)
+#define IPIPEIF_OUTPUT_VP (1 << 1)
+
+/* Sink and source IPIPEIF pads */
+#define IPIPEIF_PAD_SINK 0
+#define IPIPEIF_PAD_SOURCE_ISIF_SF 1
+#define IPIPEIF_PAD_SOURCE_VP 2
+#define IPIPEIF_PADS_NUM 3
+
+/*
+ * struct iss_ipipeif_device - Structure for the IPIPEIF module to store its own
+ * information
+ * @subdev: V4L2 subdevice
+ * @pads: Sink and source media entity pads
+ * @formats: Active video formats
+ * @input: Active input
+ * @output: Active outputs
+ * @video_out: Output video node
+ * @error: A hardware error occurred during capture
+ * @alaw: A-law compression enabled (1) or disabled (0)
+ * @lpf: Low pass filter enabled (1) or disabled (0)
+ * @obclamp: Optical-black clamp enabled (1) or disabled (0)
+ * @fpc_en: Faulty pixels correction enabled (1) or disabled (0)
+ * @blcomp: Black level compensation configuration
+ * @clamp: Optical-black or digital clamp configuration
+ * @fpc: Faulty pixels correction configuration
+ * @lsc: Lens shading compensation configuration
+ * @update: Bitmask of controls to update during the next interrupt
+ * @shadow_update: Controls update in progress by userspace
+ * @syncif: Interface synchronization configuration
+ * @vpcfg: Video port configuration
+ * @underrun: A buffer underrun occurred and a new buffer has been queued
+ * @state: Streaming state
+ * @lock: Serializes shadow_update with interrupt handler
+ * @wait: Wait queue used to stop the module
+ * @stopping: Stopping state
+ * @ioctl_lock: Serializes ioctl calls and LSC requests freeing
+ */
+struct iss_ipipeif_device {
+ struct v4l2_subdev subdev;
+ struct media_pad pads[IPIPEIF_PADS_NUM];
+ struct v4l2_mbus_framefmt formats[IPIPEIF_PADS_NUM];
+
+ enum ipipeif_input_entity input;
+ unsigned int output;
+ struct iss_video video_out;
+ unsigned int error;
+
+ enum iss_pipeline_stream_state state;
+ wait_queue_head_t wait;
+ atomic_t stopping;
+};
+
+struct iss_device;
+
+int omap4iss_ipipeif_init(struct iss_device *iss);
+void omap4iss_ipipeif_cleanup(struct iss_device *iss);
+int omap4iss_ipipeif_register_entities(struct iss_ipipeif_device *ipipeif,
+ struct v4l2_device *vdev);
+void omap4iss_ipipeif_unregister_entities(struct iss_ipipeif_device *ipipeif);
+
+int omap4iss_ipipeif_busy(struct iss_ipipeif_device *ipipeif);
+void omap4iss_ipipeif_isr(struct iss_ipipeif_device *ipipeif, u32 events);
+void omap4iss_ipipeif_restore_context(struct iss_device *iss);
+void omap4iss_ipipeif_max_rate(struct iss_ipipeif_device *ipipeif,
+ unsigned int *max_rate);
+
+#endif /* OMAP4_ISS_IPIPEIF_H */
diff --git a/drivers/staging/media/omap4iss/iss_regs.h b/drivers/staging/media/omap4iss/iss_regs.h
new file mode 100644
index 00000000000..efd0291a86f
--- /dev/null
+++ b/drivers/staging/media/omap4iss/iss_regs.h
@@ -0,0 +1,901 @@
+/*
+ * TI OMAP4 ISS V4L2 Driver - Register defines
+ *
+ * Copyright (C) 2012 Texas Instruments.
+ *
+ * Author: Sergio Aguirre <sergio.a.aguirre@gmail.com>
+ *
+ * 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.
+ */
+
+#ifndef _OMAP4_ISS_REGS_H_
+#define _OMAP4_ISS_REGS_H_
+
+/* ISS */
+#define ISS_HL_REVISION 0x0
+
+#define ISS_HL_SYSCONFIG 0x10
+#define ISS_HL_SYSCONFIG_IDLEMODE_SHIFT 2
+#define ISS_HL_SYSCONFIG_IDLEMODE_FORCEIDLE 0x0
+#define ISS_HL_SYSCONFIG_IDLEMODE_NOIDLE 0x1
+#define ISS_HL_SYSCONFIG_IDLEMODE_SMARTIDLE 0x2
+#define ISS_HL_SYSCONFIG_SOFTRESET (1 << 0)
+
+#define ISS_HL_IRQSTATUS_RAW(i) (0x20 + (0x10 * (i)))
+#define ISS_HL_IRQSTATUS(i) (0x24 + (0x10 * (i)))
+#define ISS_HL_IRQENABLE_SET(i) (0x28 + (0x10 * (i)))
+#define ISS_HL_IRQENABLE_CLR(i) (0x2c + (0x10 * (i)))
+
+#define ISS_HL_IRQ_HS_VS (1 << 17)
+#define ISS_HL_IRQ_SIMCOP(i) (1 << (12 + (i)))
+#define ISS_HL_IRQ_BTE (1 << 11)
+#define ISS_HL_IRQ_CBUFF (1 << 10)
+#define ISS_HL_IRQ_CCP2(i) (1 << ((i) > 3 ? 16 : 14 + (i)))
+#define ISS_HL_IRQ_CSIB (1 << 5)
+#define ISS_HL_IRQ_CSIA (1 << 4)
+#define ISS_HL_IRQ_ISP(i) (1 << (i))
+
+#define ISS_CTRL 0x80
+#define ISS_CTRL_CLK_DIV_MASK (3 << 4)
+#define ISS_CTRL_INPUT_SEL_MASK (3 << 2)
+#define ISS_CTRL_INPUT_SEL_CSI2A (0 << 2)
+#define ISS_CTRL_INPUT_SEL_CSI2B (1 << 2)
+#define ISS_CTRL_SYNC_DETECT_VS_RAISING (3 << 0)
+
+#define ISS_CLKCTRL 0x84
+#define ISS_CLKCTRL_VPORT2_CLK (1 << 30)
+#define ISS_CLKCTRL_VPORT1_CLK (1 << 29)
+#define ISS_CLKCTRL_VPORT0_CLK (1 << 28)
+#define ISS_CLKCTRL_CCP2 (1 << 4)
+#define ISS_CLKCTRL_CSI2_B (1 << 3)
+#define ISS_CLKCTRL_CSI2_A (1 << 2)
+#define ISS_CLKCTRL_ISP (1 << 1)
+#define ISS_CLKCTRL_SIMCOP (1 << 0)
+
+#define ISS_CLKSTAT 0x88
+#define ISS_CLKSTAT_VPORT2_CLK (1 << 30)
+#define ISS_CLKSTAT_VPORT1_CLK (1 << 29)
+#define ISS_CLKSTAT_VPORT0_CLK (1 << 28)
+#define ISS_CLKSTAT_CCP2 (1 << 4)
+#define ISS_CLKSTAT_CSI2_B (1 << 3)
+#define ISS_CLKSTAT_CSI2_A (1 << 2)
+#define ISS_CLKSTAT_ISP (1 << 1)
+#define ISS_CLKSTAT_SIMCOP (1 << 0)
+
+#define ISS_PM_STATUS 0x8c
+#define ISS_PM_STATUS_CBUFF_PM_MASK (3 << 12)
+#define ISS_PM_STATUS_BTE_PM_MASK (3 << 10)
+#define ISS_PM_STATUS_SIMCOP_PM_MASK (3 << 8)
+#define ISS_PM_STATUS_ISP_PM_MASK (3 << 6)
+#define ISS_PM_STATUS_CCP2_PM_MASK (3 << 4)
+#define ISS_PM_STATUS_CSI2_B_PM_MASK (3 << 2)
+#define ISS_PM_STATUS_CSI2_A_PM_MASK (3 << 0)
+
+#define REGISTER0 0x0
+#define REGISTER0_HSCLOCKCONFIG (1 << 24)
+#define REGISTER0_THS_TERM_MASK (0xff << 8)
+#define REGISTER0_THS_TERM_SHIFT 8
+#define REGISTER0_THS_SETTLE_MASK (0xff << 0)
+#define REGISTER0_THS_SETTLE_SHIFT 0
+
+#define REGISTER1 0x4
+#define REGISTER1_RESET_DONE_CTRLCLK (1 << 29)
+#define REGISTER1_CLOCK_MISS_DETECTOR_STATUS (1 << 25)
+#define REGISTER1_TCLK_TERM_MASK (0x3f << 18)
+#define REGISTER1_TCLK_TERM_SHIFT 18
+#define REGISTER1_DPHY_HS_SYNC_PATTERN_SHIFT 10
+#define REGISTER1_CTRLCLK_DIV_FACTOR_MASK (0x3 << 8)
+#define REGISTER1_CTRLCLK_DIV_FACTOR_SHIFT 8
+#define REGISTER1_TCLK_SETTLE_MASK (0xff << 0)
+#define REGISTER1_TCLK_SETTLE_SHIFT 0
+
+#define REGISTER2 0x8
+
+#define CSI2_SYSCONFIG 0x10
+#define CSI2_SYSCONFIG_MSTANDBY_MODE_MASK (3 << 12)
+#define CSI2_SYSCONFIG_MSTANDBY_MODE_FORCE (0 << 12)
+#define CSI2_SYSCONFIG_MSTANDBY_MODE_NO (1 << 12)
+#define CSI2_SYSCONFIG_MSTANDBY_MODE_SMART (2 << 12)
+#define CSI2_SYSCONFIG_SOFT_RESET (1 << 1)
+#define CSI2_SYSCONFIG_AUTO_IDLE (1 << 0)
+
+#define CSI2_SYSSTATUS 0x14
+#define CSI2_SYSSTATUS_RESET_DONE (1 << 0)
+
+#define CSI2_IRQSTATUS 0x18
+#define CSI2_IRQENABLE 0x1c
+
+/* Shared bits across CSI2_IRQENABLE and IRQSTATUS */
+
+#define CSI2_IRQ_OCP_ERR (1 << 14)
+#define CSI2_IRQ_SHORT_PACKET (1 << 13)
+#define CSI2_IRQ_ECC_CORRECTION (1 << 12)
+#define CSI2_IRQ_ECC_NO_CORRECTION (1 << 11)
+#define CSI2_IRQ_COMPLEXIO_ERR (1 << 9)
+#define CSI2_IRQ_FIFO_OVF (1 << 8)
+#define CSI2_IRQ_CONTEXT0 (1 << 0)
+
+#define CSI2_CTRL 0x40
+#define CSI2_CTRL_MFLAG_LEVH_MASK (7 << 20)
+#define CSI2_CTRL_MFLAG_LEVH_SHIFT 20
+#define CSI2_CTRL_MFLAG_LEVL_MASK (7 << 17)
+#define CSI2_CTRL_MFLAG_LEVL_SHIFT 17
+#define CSI2_CTRL_BURST_SIZE_EXPAND (1 << 16)
+#define CSI2_CTRL_VP_CLK_EN (1 << 15)
+#define CSI2_CTRL_NON_POSTED_WRITE (1 << 13)
+#define CSI2_CTRL_VP_ONLY_EN (1 << 11)
+#define CSI2_CTRL_VP_OUT_CTRL_MASK (3 << 8)
+#define CSI2_CTRL_VP_OUT_CTRL_SHIFT 8
+#define CSI2_CTRL_DBG_EN (1 << 7)
+#define CSI2_CTRL_BURST_SIZE_MASK (3 << 5)
+#define CSI2_CTRL_ENDIANNESS (1 << 4)
+#define CSI2_CTRL_FRAME (1 << 3)
+#define CSI2_CTRL_ECC_EN (1 << 2)
+#define CSI2_CTRL_IF_EN (1 << 0)
+
+#define CSI2_DBG_H 0x44
+
+#define CSI2_COMPLEXIO_CFG 0x50
+#define CSI2_COMPLEXIO_CFG_RESET_CTRL (1 << 30)
+#define CSI2_COMPLEXIO_CFG_RESET_DONE (1 << 29)
+#define CSI2_COMPLEXIO_CFG_PWD_CMD_MASK (3 << 27)
+#define CSI2_COMPLEXIO_CFG_PWD_CMD_OFF (0 << 27)
+#define CSI2_COMPLEXIO_CFG_PWD_CMD_ON (1 << 27)
+#define CSI2_COMPLEXIO_CFG_PWD_CMD_ULP (2 << 27)
+#define CSI2_COMPLEXIO_CFG_PWD_STATUS_MASK (3 << 25)
+#define CSI2_COMPLEXIO_CFG_PWD_STATUS_OFF (0 << 25)
+#define CSI2_COMPLEXIO_CFG_PWD_STATUS_ON (1 << 25)
+#define CSI2_COMPLEXIO_CFG_PWD_STATUS_ULP (2 << 25)
+#define CSI2_COMPLEXIO_CFG_PWR_AUTO (1 << 24)
+#define CSI2_COMPLEXIO_CFG_DATA_POL(i) (1 << (((i) * 4) + 3))
+#define CSI2_COMPLEXIO_CFG_DATA_POSITION_MASK(i) (7 << ((i) * 4))
+#define CSI2_COMPLEXIO_CFG_DATA_POSITION_SHIFT(i) ((i) * 4)
+#define CSI2_COMPLEXIO_CFG_CLOCK_POL (1 << 3)
+#define CSI2_COMPLEXIO_CFG_CLOCK_POSITION_MASK (7 << 0)
+#define CSI2_COMPLEXIO_CFG_CLOCK_POSITION_SHIFT 0
+
+#define CSI2_COMPLEXIO_IRQSTATUS 0x54
+
+#define CSI2_SHORT_PACKET 0x5c
+
+#define CSI2_COMPLEXIO_IRQENABLE 0x60
+
+/* Shared bits across CSI2_COMPLEXIO_IRQENABLE and IRQSTATUS */
+#define CSI2_COMPLEXIO_IRQ_STATEALLULPMEXIT (1 << 26)
+#define CSI2_COMPLEXIO_IRQ_STATEALLULPMENTER (1 << 25)
+#define CSI2_COMPLEXIO_IRQ_STATEULPM5 (1 << 24)
+#define CSI2_COMPLEXIO_IRQ_STATEULPM4 (1 << 23)
+#define CSI2_COMPLEXIO_IRQ_STATEULPM3 (1 << 22)
+#define CSI2_COMPLEXIO_IRQ_STATEULPM2 (1 << 21)
+#define CSI2_COMPLEXIO_IRQ_STATEULPM1 (1 << 20)
+#define CSI2_COMPLEXIO_IRQ_ERRCONTROL5 (1 << 19)
+#define CSI2_COMPLEXIO_IRQ_ERRCONTROL4 (1 << 18)
+#define CSI2_COMPLEXIO_IRQ_ERRCONTROL3 (1 << 17)
+#define CSI2_COMPLEXIO_IRQ_ERRCONTROL2 (1 << 16)
+#define CSI2_COMPLEXIO_IRQ_ERRCONTROL1 (1 << 15)
+#define CSI2_COMPLEXIO_IRQ_ERRESC5 (1 << 14)
+#define CSI2_COMPLEXIO_IRQ_ERRESC4 (1 << 13)
+#define CSI2_COMPLEXIO_IRQ_ERRESC3 (1 << 12)
+#define CSI2_COMPLEXIO_IRQ_ERRESC2 (1 << 11)
+#define CSI2_COMPLEXIO_IRQ_ERRESC1 (1 << 10)
+#define CSI2_COMPLEXIO_IRQ_ERRSOTSYNCHS5 (1 << 9)
+#define CSI2_COMPLEXIO_IRQ_ERRSOTSYNCHS4 (1 << 8)
+#define CSI2_COMPLEXIO_IRQ_ERRSOTSYNCHS3 (1 << 7)
+#define CSI2_COMPLEXIO_IRQ_ERRSOTSYNCHS2 (1 << 6)
+#define CSI2_COMPLEXIO_IRQ_ERRSOTSYNCHS1 (1 << 5)
+#define CSI2_COMPLEXIO_IRQ_ERRSOTHS5 (1 << 4)
+#define CSI2_COMPLEXIO_IRQ_ERRSOTHS4 (1 << 3)
+#define CSI2_COMPLEXIO_IRQ_ERRSOTHS3 (1 << 2)
+#define CSI2_COMPLEXIO_IRQ_ERRSOTHS2 (1 << 1)
+#define CSI2_COMPLEXIO_IRQ_ERRSOTHS1 (1 << 0)
+
+#define CSI2_DBG_P 0x68
+
+#define CSI2_TIMING 0x6c
+#define CSI2_TIMING_FORCE_RX_MODE_IO1 (1 << 15)
+#define CSI2_TIMING_STOP_STATE_X16_IO1 (1 << 14)
+#define CSI2_TIMING_STOP_STATE_X4_IO1 (1 << 13)
+#define CSI2_TIMING_STOP_STATE_COUNTER_IO1_MASK (0x1fff << 0)
+#define CSI2_TIMING_STOP_STATE_COUNTER_IO1_SHIFT 0
+
+#define CSI2_CTX_CTRL1(i) (0x70 + (0x20 * i))
+#define CSI2_CTX_CTRL1_GENERIC (1 << 30)
+#define CSI2_CTX_CTRL1_TRANSCODE (0xf << 24)
+#define CSI2_CTX_CTRL1_FEC_NUMBER_MASK (0xff << 16)
+#define CSI2_CTX_CTRL1_COUNT_MASK (0xff << 8)
+#define CSI2_CTX_CTRL1_COUNT_SHIFT 8
+#define CSI2_CTX_CTRL1_EOF_EN (1 << 7)
+#define CSI2_CTX_CTRL1_EOL_EN (1 << 6)
+#define CSI2_CTX_CTRL1_CS_EN (1 << 5)
+#define CSI2_CTX_CTRL1_COUNT_UNLOCK (1 << 4)
+#define CSI2_CTX_CTRL1_PING_PONG (1 << 3)
+#define CSI2_CTX_CTRL1_CTX_EN (1 << 0)
+
+#define CSI2_CTX_CTRL2(i) (0x74 + (0x20 * i))
+#define CSI2_CTX_CTRL2_USER_DEF_MAP_SHIFT 13
+#define CSI2_CTX_CTRL2_USER_DEF_MAP_MASK \
+ (0x3 << CSI2_CTX_CTRL2_USER_DEF_MAP_SHIFT)
+#define CSI2_CTX_CTRL2_VIRTUAL_ID_MASK (3 << 11)
+#define CSI2_CTX_CTRL2_VIRTUAL_ID_SHIFT 11
+#define CSI2_CTX_CTRL2_DPCM_PRED (1 << 10)
+#define CSI2_CTX_CTRL2_FORMAT_MASK (0x3ff << 0)
+#define CSI2_CTX_CTRL2_FORMAT_SHIFT 0
+
+#define CSI2_CTX_DAT_OFST(i) (0x78 + (0x20 * i))
+#define CSI2_CTX_DAT_OFST_MASK (0xfff << 5)
+
+#define CSI2_CTX_PING_ADDR(i) (0x7c + (0x20 * i))
+#define CSI2_CTX_PING_ADDR_MASK 0xffffffe0
+
+#define CSI2_CTX_PONG_ADDR(i) (0x80 + (0x20 * i))
+#define CSI2_CTX_PONG_ADDR_MASK CSI2_CTX_PING_ADDR_MASK
+
+#define CSI2_CTX_IRQENABLE(i) (0x84 + (0x20 * i))
+#define CSI2_CTX_IRQSTATUS(i) (0x88 + (0x20 * i))
+
+#define CSI2_CTX_CTRL3(i) (0x8c + (0x20 * i))
+#define CSI2_CTX_CTRL3_ALPHA_SHIFT 5
+#define CSI2_CTX_CTRL3_ALPHA_MASK \
+ (0x3fff << CSI2_CTX_CTRL3_ALPHA_SHIFT)
+
+/* Shared bits across CSI2_CTX_IRQENABLE and IRQSTATUS */
+#define CSI2_CTX_IRQ_ECC_CORRECTION (1 << 8)
+#define CSI2_CTX_IRQ_LINE_NUMBER (1 << 7)
+#define CSI2_CTX_IRQ_FRAME_NUMBER (1 << 6)
+#define CSI2_CTX_IRQ_CS (1 << 5)
+#define CSI2_CTX_IRQ_LE (1 << 3)
+#define CSI2_CTX_IRQ_LS (1 << 2)
+#define CSI2_CTX_IRQ_FE (1 << 1)
+#define CSI2_CTX_IRQ_FS (1 << 0)
+
+/* ISS BTE */
+#define BTE_CTRL (0x0030)
+#define BTE_CTRL_BW_LIMITER_MASK (0x3ff << 22)
+#define BTE_CTRL_BW_LIMITER_SHIFT 22
+
+/* ISS ISP_SYS1 */
+#define ISP5_REVISION (0x0000)
+#define ISP5_SYSCONFIG (0x0010)
+#define ISP5_SYSCONFIG_STANDBYMODE_MASK (3 << 4)
+#define ISP5_SYSCONFIG_STANDBYMODE_FORCE (0 << 4)
+#define ISP5_SYSCONFIG_STANDBYMODE_NO (1 << 4)
+#define ISP5_SYSCONFIG_STANDBYMODE_SMART (2 << 4)
+#define ISP5_SYSCONFIG_SOFTRESET (1 << 1)
+
+#define ISP5_IRQSTATUS(i) (0x0028 + (0x10 * (i)))
+#define ISP5_IRQENABLE_SET(i) (0x002c + (0x10 * (i)))
+#define ISP5_IRQENABLE_CLR(i) (0x0030 + (0x10 * (i)))
+
+/* Bits shared for ISP5_IRQ* registers */
+#define ISP5_IRQ_OCP_ERR (1 << 31)
+#define ISP5_IRQ_IPIPE_INT_DPC_RNEW1 (1 << 29)
+#define ISP5_IRQ_IPIPE_INT_DPC_RNEW0 (1 << 28)
+#define ISP5_IRQ_IPIPE_INT_DPC_INIT (1 << 27)
+#define ISP5_IRQ_IPIPE_INT_EOF (1 << 25)
+#define ISP5_IRQ_H3A_INT_EOF (1 << 24)
+#define ISP5_IRQ_RSZ_INT_EOF1 (1 << 23)
+#define ISP5_IRQ_RSZ_INT_EOF0 (1 << 22)
+#define ISP5_IRQ_RSZ_FIFO_IN_BLK_ERR (1 << 19)
+#define ISP5_IRQ_RSZ_FIFO_OVF (1 << 18)
+#define ISP5_IRQ_RSZ_INT_CYC_RSZB (1 << 17)
+#define ISP5_IRQ_RSZ_INT_CYC_RSZA (1 << 16)
+#define ISP5_IRQ_RSZ_INT_DMA (1 << 15)
+#define ISP5_IRQ_RSZ_INT_LAST_PIX (1 << 14)
+#define ISP5_IRQ_RSZ_INT_REG (1 << 13)
+#define ISP5_IRQ_H3A_INT (1 << 12)
+#define ISP5_IRQ_AF_INT (1 << 11)
+#define ISP5_IRQ_AEW_INT (1 << 10)
+#define ISP5_IRQ_IPIPEIF_IRQ (1 << 9)
+#define ISP5_IRQ_IPIPE_INT_HST (1 << 8)
+#define ISP5_IRQ_IPIPE_INT_BSC (1 << 7)
+#define ISP5_IRQ_IPIPE_INT_DMA (1 << 6)
+#define ISP5_IRQ_IPIPE_INT_LAST_PIX (1 << 5)
+#define ISP5_IRQ_IPIPE_INT_REG (1 << 4)
+#define ISP5_IRQ_ISIF_INT(i) (1 << (i))
+
+#define ISP5_CTRL (0x006c)
+#define ISP5_CTRL_MSTANDBY (1 << 24)
+#define ISP5_CTRL_VD_PULSE_EXT (1 << 23)
+#define ISP5_CTRL_MSTANDBY_WAIT (1 << 20)
+#define ISP5_CTRL_BL_CLK_ENABLE (1 << 15)
+#define ISP5_CTRL_ISIF_CLK_ENABLE (1 << 14)
+#define ISP5_CTRL_H3A_CLK_ENABLE (1 << 13)
+#define ISP5_CTRL_RSZ_CLK_ENABLE (1 << 12)
+#define ISP5_CTRL_IPIPE_CLK_ENABLE (1 << 11)
+#define ISP5_CTRL_IPIPEIF_CLK_ENABLE (1 << 10)
+#define ISP5_CTRL_SYNC_ENABLE (1 << 9)
+#define ISP5_CTRL_PSYNC_CLK_SEL (1 << 8)
+
+/* ISS ISP ISIF register offsets */
+#define ISIF_SYNCEN (0x0000)
+#define ISIF_SYNCEN_DWEN (1 << 1)
+#define ISIF_SYNCEN_SYEN (1 << 0)
+
+#define ISIF_MODESET (0x0004)
+#define ISIF_MODESET_INPMOD_MASK (3 << 12)
+#define ISIF_MODESET_INPMOD_RAW (0 << 12)
+#define ISIF_MODESET_INPMOD_YCBCR16 (1 << 12)
+#define ISIF_MODESET_INPMOD_YCBCR8 (2 << 12)
+#define ISIF_MODESET_CCDW_MASK (7 << 8)
+#define ISIF_MODESET_CCDW_2BIT (2 << 8)
+#define ISIF_MODESET_CCDMD (1 << 7)
+#define ISIF_MODESET_SWEN (1 << 5)
+#define ISIF_MODESET_HDPOL (1 << 3)
+#define ISIF_MODESET_VDPOL (1 << 2)
+
+#define ISIF_SPH (0x0018)
+#define ISIF_SPH_MASK (0x7fff)
+
+#define ISIF_LNH (0x001c)
+#define ISIF_LNH_MASK (0x7fff)
+
+#define ISIF_LNV (0x0028)
+#define ISIF_LNV_MASK (0x7fff)
+
+#define ISIF_HSIZE (0x0034)
+#define ISIF_HSIZE_ADCR (1 << 12)
+#define ISIF_HSIZE_HSIZE_MASK (0xfff)
+
+#define ISIF_CADU (0x003c)
+#define ISIF_CADU_MASK (0x7ff)
+
+#define ISIF_CADL (0x0040)
+#define ISIF_CADL_MASK (0xffff)
+
+#define ISIF_CCOLP (0x004c)
+#define ISIF_CCOLP_CP0_F0_R (0 << 6)
+#define ISIF_CCOLP_CP0_F0_GR (1 << 6)
+#define ISIF_CCOLP_CP0_F0_B (3 << 6)
+#define ISIF_CCOLP_CP0_F0_GB (2 << 6)
+#define ISIF_CCOLP_CP1_F0_R (0 << 4)
+#define ISIF_CCOLP_CP1_F0_GR (1 << 4)
+#define ISIF_CCOLP_CP1_F0_B (3 << 4)
+#define ISIF_CCOLP_CP1_F0_GB (2 << 4)
+#define ISIF_CCOLP_CP2_F0_R (0 << 2)
+#define ISIF_CCOLP_CP2_F0_GR (1 << 2)
+#define ISIF_CCOLP_CP2_F0_B (3 << 2)
+#define ISIF_CCOLP_CP2_F0_GB (2 << 2)
+#define ISIF_CCOLP_CP3_F0_R (0 << 0)
+#define ISIF_CCOLP_CP3_F0_GR (1 << 0)
+#define ISIF_CCOLP_CP3_F0_B (3 << 0)
+#define ISIF_CCOLP_CP3_F0_GB (2 << 0)
+
+#define ISIF_VDINT(i) (0x0070 + (i) * 4)
+#define ISIF_VDINT_MASK (0x7fff)
+
+#define ISIF_CGAMMAWD (0x0080)
+#define ISIF_CGAMMAWD_GWDI_MASK (0xf << 1)
+#define ISIF_CGAMMAWD_GWDI(bpp) ((16 - (bpp)) << 1)
+
+#define ISIF_CCDCFG (0x0088)
+#define ISIF_CCDCFG_Y8POS (1 << 11)
+
+/* ISS ISP IPIPEIF register offsets */
+#define IPIPEIF_ENABLE (0x0000)
+
+#define IPIPEIF_CFG1 (0x0004)
+#define IPIPEIF_CFG1_INPSRC1_MASK (3 << 14)
+#define IPIPEIF_CFG1_INPSRC1_VPORT_RAW (0 << 14)
+#define IPIPEIF_CFG1_INPSRC1_SDRAM_RAW (1 << 14)
+#define IPIPEIF_CFG1_INPSRC1_ISIF_DARKFM (2 << 14)
+#define IPIPEIF_CFG1_INPSRC1_SDRAM_YUV (3 << 14)
+#define IPIPEIF_CFG1_INPSRC2_MASK (3 << 2)
+#define IPIPEIF_CFG1_INPSRC2_ISIF (0 << 2)
+#define IPIPEIF_CFG1_INPSRC2_SDRAM_RAW (1 << 2)
+#define IPIPEIF_CFG1_INPSRC2_ISIF_DARKFM (2 << 2)
+#define IPIPEIF_CFG1_INPSRC2_SDRAM_YUV (3 << 2)
+
+#define IPIPEIF_CFG2 (0x0030)
+#define IPIPEIF_CFG2_YUV8P (1 << 7)
+#define IPIPEIF_CFG2_YUV8 (1 << 6)
+#define IPIPEIF_CFG2_YUV16 (1 << 3)
+#define IPIPEIF_CFG2_VDPOL (1 << 2)
+#define IPIPEIF_CFG2_HDPOL (1 << 1)
+#define IPIPEIF_CFG2_INTSW (1 << 0)
+
+#define IPIPEIF_CLKDIV (0x0040)
+
+/* ISS ISP IPIPE register offsets */
+#define IPIPE_SRC_EN (0x0000)
+#define IPIPE_SRC_EN_EN (1 << 0)
+
+#define IPIPE_SRC_MODE (0x0004)
+#define IPIPE_SRC_MODE_WRT (1 << 1)
+#define IPIPE_SRC_MODE_OST (1 << 0)
+
+#define IPIPE_SRC_FMT (0x0008)
+#define IPIPE_SRC_FMT_RAW2YUV (0 << 0)
+#define IPIPE_SRC_FMT_RAW2RAW (1 << 0)
+#define IPIPE_SRC_FMT_RAW2STATS (2 << 0)
+#define IPIPE_SRC_FMT_YUV2YUV (3 << 0)
+
+#define IPIPE_SRC_COL (0x000c)
+#define IPIPE_SRC_COL_OO_R (0 << 6)
+#define IPIPE_SRC_COL_OO_GR (1 << 6)
+#define IPIPE_SRC_COL_OO_B (3 << 6)
+#define IPIPE_SRC_COL_OO_GB (2 << 6)
+#define IPIPE_SRC_COL_OE_R (0 << 4)
+#define IPIPE_SRC_COL_OE_GR (1 << 4)
+#define IPIPE_SRC_COL_OE_B (3 << 4)
+#define IPIPE_SRC_COL_OE_GB (2 << 4)
+#define IPIPE_SRC_COL_EO_R (0 << 2)
+#define IPIPE_SRC_COL_EO_GR (1 << 2)
+#define IPIPE_SRC_COL_EO_B (3 << 2)
+#define IPIPE_SRC_COL_EO_GB (2 << 2)
+#define IPIPE_SRC_COL_EE_R (0 << 0)
+#define IPIPE_SRC_COL_EE_GR (1 << 0)
+#define IPIPE_SRC_COL_EE_B (3 << 0)
+#define IPIPE_SRC_COL_EE_GB (2 << 0)
+
+#define IPIPE_SRC_VPS (0x0010)
+#define IPIPE_SRC_VPS_MASK (0xffff)
+
+#define IPIPE_SRC_VSZ (0x0014)
+#define IPIPE_SRC_VSZ_MASK (0x1fff)
+
+#define IPIPE_SRC_HPS (0x0018)
+#define IPIPE_SRC_HPS_MASK (0xffff)
+
+#define IPIPE_SRC_HSZ (0x001c)
+#define IPIPE_SRC_HSZ_MASK (0x1ffe)
+
+#define IPIPE_SEL_SBU (0x0020)
+
+#define IPIPE_SRC_STA (0x0024)
+
+#define IPIPE_GCK_MMR (0x0028)
+#define IPIPE_GCK_MMR_REG (1 << 0)
+
+#define IPIPE_GCK_PIX (0x002c)
+#define IPIPE_GCK_PIX_G3 (1 << 3)
+#define IPIPE_GCK_PIX_G2 (1 << 2)
+#define IPIPE_GCK_PIX_G1 (1 << 1)
+#define IPIPE_GCK_PIX_G0 (1 << 0)
+
+#define IPIPE_DPC_LUT_EN (0x0034)
+#define IPIPE_DPC_LUT_SEL (0x0038)
+#define IPIPE_DPC_LUT_ADR (0x003c)
+#define IPIPE_DPC_LUT_SIZ (0x0040)
+
+#define IPIPE_DPC_OTF_EN (0x0044)
+#define IPIPE_DPC_OTF_TYP (0x0048)
+#define IPIPE_DPC_OTF_2_D_THR_R (0x004c)
+#define IPIPE_DPC_OTF_2_D_THR_GR (0x0050)
+#define IPIPE_DPC_OTF_2_D_THR_GB (0x0054)
+#define IPIPE_DPC_OTF_2_D_THR_B (0x0058)
+#define IPIPE_DPC_OTF_2_C_THR_R (0x005c)
+#define IPIPE_DPC_OTF_2_C_THR_GR (0x0060)
+#define IPIPE_DPC_OTF_2_C_THR_GB (0x0064)
+#define IPIPE_DPC_OTF_2_C_THR_B (0x0068)
+#define IPIPE_DPC_OTF_3_SHF (0x006c)
+#define IPIPE_DPC_OTF_3_D_THR (0x0070)
+#define IPIPE_DPC_OTF_3_D_SPL (0x0074)
+#define IPIPE_DPC_OTF_3_D_MIN (0x0078)
+#define IPIPE_DPC_OTF_3_D_MAX (0x007c)
+#define IPIPE_DPC_OTF_3_C_THR (0x0080)
+#define IPIPE_DPC_OTF_3_C_SLP (0x0084)
+#define IPIPE_DPC_OTF_3_C_MIN (0x0088)
+#define IPIPE_DPC_OTF_3_C_MAX (0x008c)
+
+#define IPIPE_LSC_VOFT (0x0090)
+#define IPIPE_LSC_VA2 (0x0094)
+#define IPIPE_LSC_VA1 (0x0098)
+#define IPIPE_LSC_VS (0x009c)
+#define IPIPE_LSC_HOFT (0x00a0)
+#define IPIPE_LSC_HA2 (0x00a4)
+#define IPIPE_LSC_HA1 (0x00a8)
+#define IPIPE_LSC_HS (0x00ac)
+#define IPIPE_LSC_GAN_R (0x00b0)
+#define IPIPE_LSC_GAN_GR (0x00b4)
+#define IPIPE_LSC_GAN_GB (0x00b8)
+#define IPIPE_LSC_GAN_B (0x00bc)
+#define IPIPE_LSC_OFT_R (0x00c0)
+#define IPIPE_LSC_OFT_GR (0x00c4)
+#define IPIPE_LSC_OFT_GB (0x00c8)
+#define IPIPE_LSC_OFT_B (0x00cc)
+#define IPIPE_LSC_SHF (0x00d0)
+#define IPIPE_LSC_MAX (0x00d4)
+
+#define IPIPE_D2F_1ST_EN (0x00d8)
+#define IPIPE_D2F_1ST_TYP (0x00dc)
+#define IPIPE_D2F_1ST_THR_00 (0x00e0)
+#define IPIPE_D2F_1ST_THR_01 (0x00e4)
+#define IPIPE_D2F_1ST_THR_02 (0x00e8)
+#define IPIPE_D2F_1ST_THR_03 (0x00ec)
+#define IPIPE_D2F_1ST_THR_04 (0x00f0)
+#define IPIPE_D2F_1ST_THR_05 (0x00f4)
+#define IPIPE_D2F_1ST_THR_06 (0x00f8)
+#define IPIPE_D2F_1ST_THR_07 (0x00fc)
+#define IPIPE_D2F_1ST_STR_00 (0x0100)
+#define IPIPE_D2F_1ST_STR_01 (0x0104)
+#define IPIPE_D2F_1ST_STR_02 (0x0108)
+#define IPIPE_D2F_1ST_STR_03 (0x010c)
+#define IPIPE_D2F_1ST_STR_04 (0x0110)
+#define IPIPE_D2F_1ST_STR_05 (0x0114)
+#define IPIPE_D2F_1ST_STR_06 (0x0118)
+#define IPIPE_D2F_1ST_STR_07 (0x011c)
+#define IPIPE_D2F_1ST_SPR_00 (0x0120)
+#define IPIPE_D2F_1ST_SPR_01 (0x0124)
+#define IPIPE_D2F_1ST_SPR_02 (0x0128)
+#define IPIPE_D2F_1ST_SPR_03 (0x012c)
+#define IPIPE_D2F_1ST_SPR_04 (0x0130)
+#define IPIPE_D2F_1ST_SPR_05 (0x0134)
+#define IPIPE_D2F_1ST_SPR_06 (0x0138)
+#define IPIPE_D2F_1ST_SPR_07 (0x013c)
+#define IPIPE_D2F_1ST_EDG_MIN (0x0140)
+#define IPIPE_D2F_1ST_EDG_MAX (0x0144)
+#define IPIPE_D2F_2ND_EN (0x0148)
+#define IPIPE_D2F_2ND_TYP (0x014c)
+#define IPIPE_D2F_2ND_THR00 (0x0150)
+#define IPIPE_D2F_2ND_THR01 (0x0154)
+#define IPIPE_D2F_2ND_THR02 (0x0158)
+#define IPIPE_D2F_2ND_THR03 (0x015c)
+#define IPIPE_D2F_2ND_THR04 (0x0160)
+#define IPIPE_D2F_2ND_THR05 (0x0164)
+#define IPIPE_D2F_2ND_THR06 (0x0168)
+#define IPIPE_D2F_2ND_THR07 (0x016c)
+#define IPIPE_D2F_2ND_STR_00 (0x0170)
+#define IPIPE_D2F_2ND_STR_01 (0x0174)
+#define IPIPE_D2F_2ND_STR_02 (0x0178)
+#define IPIPE_D2F_2ND_STR_03 (0x017c)
+#define IPIPE_D2F_2ND_STR_04 (0x0180)
+#define IPIPE_D2F_2ND_STR_05 (0x0184)
+#define IPIPE_D2F_2ND_STR_06 (0x0188)
+#define IPIPE_D2F_2ND_STR_07 (0x018c)
+#define IPIPE_D2F_2ND_SPR_00 (0x0190)
+#define IPIPE_D2F_2ND_SPR_01 (0x0194)
+#define IPIPE_D2F_2ND_SPR_02 (0x0198)
+#define IPIPE_D2F_2ND_SPR_03 (0x019c)
+#define IPIPE_D2F_2ND_SPR_04 (0x01a0)
+#define IPIPE_D2F_2ND_SPR_05 (0x01a4)
+#define IPIPE_D2F_2ND_SPR_06 (0x01a8)
+#define IPIPE_D2F_2ND_SPR_07 (0x01ac)
+#define IPIPE_D2F_2ND_EDG_MIN (0x01b0)
+#define IPIPE_D2F_2ND_EDG_MAX (0x01b4)
+
+#define IPIPE_GIC_EN (0x01b8)
+#define IPIPE_GIC_TYP (0x01bc)
+#define IPIPE_GIC_GAN (0x01c0)
+#define IPIPE_GIC_NFGAIN (0x01c4)
+#define IPIPE_GIC_THR (0x01c8)
+#define IPIPE_GIC_SLP (0x01cc)
+
+#define IPIPE_WB2_OFT_R (0x01d0)
+#define IPIPE_WB2_OFT_GR (0x01d4)
+#define IPIPE_WB2_OFT_GB (0x01d8)
+#define IPIPE_WB2_OFT_B (0x01dc)
+
+#define IPIPE_WB2_WGN_R (0x01e0)
+#define IPIPE_WB2_WGN_GR (0x01e4)
+#define IPIPE_WB2_WGN_GB (0x01e8)
+#define IPIPE_WB2_WGN_B (0x01ec)
+
+#define IPIPE_CFA_MODE (0x01f0)
+#define IPIPE_CFA_2DIR_HPF_THR (0x01f4)
+#define IPIPE_CFA_2DIR_HPF_SLP (0x01f8)
+#define IPIPE_CFA_2DIR_MIX_THR (0x01fc)
+#define IPIPE_CFA_2DIR_MIX_SLP (0x0200)
+#define IPIPE_CFA_2DIR_DIR_TRH (0x0204)
+#define IPIPE_CFA_2DIR_DIR_SLP (0x0208)
+#define IPIPE_CFA_2DIR_NDWT (0x020c)
+#define IPIPE_CFA_MONO_HUE_FRA (0x0210)
+#define IPIPE_CFA_MONO_EDG_THR (0x0214)
+#define IPIPE_CFA_MONO_THR_MIN (0x0218)
+
+#define IPIPE_CFA_MONO_THR_SLP (0x021c)
+#define IPIPE_CFA_MONO_SLP_MIN (0x0220)
+#define IPIPE_CFA_MONO_SLP_SLP (0x0224)
+#define IPIPE_CFA_MONO_LPWT (0x0228)
+
+#define IPIPE_RGB1_MUL_RR (0x022c)
+#define IPIPE_RGB1_MUL_GR (0x0230)
+#define IPIPE_RGB1_MUL_BR (0x0234)
+#define IPIPE_RGB1_MUL_RG (0x0238)
+#define IPIPE_RGB1_MUL_GG (0x023c)
+#define IPIPE_RGB1_MUL_BG (0x0240)
+#define IPIPE_RGB1_MUL_RB (0x0244)
+#define IPIPE_RGB1_MUL_GB (0x0248)
+#define IPIPE_RGB1_MUL_BB (0x024c)
+#define IPIPE_RGB1_OFT_OR (0x0250)
+#define IPIPE_RGB1_OFT_OG (0x0254)
+#define IPIPE_RGB1_OFT_OB (0x0258)
+#define IPIPE_GMM_CFG (0x025c)
+#define IPIPE_RGB2_MUL_RR (0x0260)
+#define IPIPE_RGB2_MUL_GR (0x0264)
+#define IPIPE_RGB2_MUL_BR (0x0268)
+#define IPIPE_RGB2_MUL_RG (0x026c)
+#define IPIPE_RGB2_MUL_GG (0x0270)
+#define IPIPE_RGB2_MUL_BG (0x0274)
+#define IPIPE_RGB2_MUL_RB (0x0278)
+#define IPIPE_RGB2_MUL_GB (0x027c)
+#define IPIPE_RGB2_MUL_BB (0x0280)
+#define IPIPE_RGB2_OFT_OR (0x0284)
+#define IPIPE_RGB2_OFT_OG (0x0288)
+#define IPIPE_RGB2_OFT_OB (0x028c)
+
+#define IPIPE_YUV_ADJ (0x0294)
+#define IPIPE_YUV_MUL_RY (0x0298)
+#define IPIPE_YUV_MUL_GY (0x029c)
+#define IPIPE_YUV_MUL_BY (0x02a0)
+#define IPIPE_YUV_MUL_RCB (0x02a4)
+#define IPIPE_YUV_MUL_GCB (0x02a8)
+#define IPIPE_YUV_MUL_BCB (0x02ac)
+#define IPIPE_YUV_MUL_RCR (0x02b0)
+#define IPIPE_YUV_MUL_GCR (0x02b4)
+#define IPIPE_YUV_MUL_BCR (0x02b8)
+#define IPIPE_YUV_OFT_Y (0x02bc)
+#define IPIPE_YUV_OFT_CB (0x02c0)
+#define IPIPE_YUV_OFT_CR (0x02c4)
+
+#define IPIPE_YUV_PHS (0x02c8)
+#define IPIPE_YUV_PHS_LPF (1 << 1)
+#define IPIPE_YUV_PHS_POS (1 << 0)
+
+#define IPIPE_YEE_EN (0x02d4)
+#define IPIPE_YEE_TYP (0x02d8)
+#define IPIPE_YEE_SHF (0x02dc)
+#define IPIPE_YEE_MUL_00 (0x02e0)
+#define IPIPE_YEE_MUL_01 (0x02e4)
+#define IPIPE_YEE_MUL_02 (0x02e8)
+#define IPIPE_YEE_MUL_10 (0x02ec)
+#define IPIPE_YEE_MUL_11 (0x02f0)
+#define IPIPE_YEE_MUL_12 (0x02f4)
+#define IPIPE_YEE_MUL_20 (0x02f8)
+#define IPIPE_YEE_MUL_21 (0x02fc)
+#define IPIPE_YEE_MUL_22 (0x0300)
+#define IPIPE_YEE_THR (0x0304)
+#define IPIPE_YEE_E_GAN (0x0308)
+#define IPIPE_YEE_E_THR_1 (0x030c)
+#define IPIPE_YEE_E_THR_2 (0x0310)
+#define IPIPE_YEE_G_GAN (0x0314)
+#define IPIPE_YEE_G_OFT (0x0318)
+
+#define IPIPE_CAR_EN (0x031c)
+#define IPIPE_CAR_TYP (0x0320)
+#define IPIPE_CAR_SW (0x0324)
+#define IPIPE_CAR_HPF_TYP (0x0328)
+#define IPIPE_CAR_HPF_SHF (0x032c)
+#define IPIPE_CAR_HPF_THR (0x0330)
+#define IPIPE_CAR_GN1_GAN (0x0334)
+#define IPIPE_CAR_GN1_SHF (0x0338)
+#define IPIPE_CAR_GN1_MIN (0x033c)
+#define IPIPE_CAR_GN2_GAN (0x0340)
+#define IPIPE_CAR_GN2_SHF (0x0344)
+#define IPIPE_CAR_GN2_MIN (0x0348)
+#define IPIPE_CGS_EN (0x034c)
+#define IPIPE_CGS_GN1_L_THR (0x0350)
+#define IPIPE_CGS_GN1_L_GAIN (0x0354)
+#define IPIPE_CGS_GN1_L_SHF (0x0358)
+#define IPIPE_CGS_GN1_L_MIN (0x035c)
+#define IPIPE_CGS_GN1_H_THR (0x0360)
+#define IPIPE_CGS_GN1_H_GAIN (0x0364)
+#define IPIPE_CGS_GN1_H_SHF (0x0368)
+#define IPIPE_CGS_GN1_H_MIN (0x036c)
+#define IPIPE_CGS_GN2_L_THR (0x0370)
+#define IPIPE_CGS_GN2_L_GAIN (0x0374)
+#define IPIPE_CGS_GN2_L_SHF (0x0378)
+#define IPIPE_CGS_GN2_L_MIN (0x037c)
+
+#define IPIPE_BOX_EN (0x0380)
+#define IPIPE_BOX_MODE (0x0384)
+#define IPIPE_BOX_TYP (0x0388)
+#define IPIPE_BOX_SHF (0x038c)
+#define IPIPE_BOX_SDR_SAD_H (0x0390)
+#define IPIPE_BOX_SDR_SAD_L (0x0394)
+
+#define IPIPE_HST_EN (0x039c)
+#define IPIPE_HST_MODE (0x03a0)
+#define IPIPE_HST_SEL (0x03a4)
+#define IPIPE_HST_PARA (0x03a8)
+#define IPIPE_HST_0_VPS (0x03ac)
+#define IPIPE_HST_0_VSZ (0x03b0)
+#define IPIPE_HST_0_HPS (0x03b4)
+#define IPIPE_HST_0_HSZ (0x03b8)
+#define IPIPE_HST_1_VPS (0x03bc)
+#define IPIPE_HST_1_VSZ (0x03c0)
+#define IPIPE_HST_1_HPS (0x03c4)
+#define IPIPE_HST_1_HSZ (0x03c8)
+#define IPIPE_HST_2_VPS (0x03cc)
+#define IPIPE_HST_2_VSZ (0x03d0)
+#define IPIPE_HST_2_HPS (0x03d4)
+#define IPIPE_HST_2_HSZ (0x03d8)
+#define IPIPE_HST_3_VPS (0x03dc)
+#define IPIPE_HST_3_VSZ (0x03e0)
+#define IPIPE_HST_3_HPS (0x03e4)
+#define IPIPE_HST_3_HSZ (0x03e8)
+#define IPIPE_HST_TBL (0x03ec)
+#define IPIPE_HST_MUL_R (0x03f0)
+#define IPIPE_HST_MUL_GR (0x03f4)
+#define IPIPE_HST_MUL_GB (0x03f8)
+#define IPIPE_HST_MUL_B (0x03fc)
+
+#define IPIPE_BSC_EN (0x0400)
+#define IPIPE_BSC_MODE (0x0404)
+#define IPIPE_BSC_TYP (0x0408)
+#define IPIPE_BSC_ROW_VCT (0x040c)
+#define IPIPE_BSC_ROW_SHF (0x0410)
+#define IPIPE_BSC_ROW_VPO (0x0414)
+#define IPIPE_BSC_ROW_VNU (0x0418)
+#define IPIPE_BSC_ROW_VSKIP (0x041c)
+#define IPIPE_BSC_ROW_HPO (0x0420)
+#define IPIPE_BSC_ROW_HNU (0x0424)
+#define IPIPE_BSC_ROW_HSKIP (0x0428)
+#define IPIPE_BSC_COL_VCT (0x042c)
+#define IPIPE_BSC_COL_SHF (0x0430)
+#define IPIPE_BSC_COL_VPO (0x0434)
+#define IPIPE_BSC_COL_VNU (0x0438)
+#define IPIPE_BSC_COL_VSKIP (0x043c)
+#define IPIPE_BSC_COL_HPO (0x0440)
+#define IPIPE_BSC_COL_HNU (0x0444)
+#define IPIPE_BSC_COL_HSKIP (0x0448)
+
+#define IPIPE_BSC_EN (0x0400)
+
+/* ISS ISP Resizer register offsets */
+#define RSZ_REVISION (0x0000)
+#define RSZ_SYSCONFIG (0x0004)
+#define RSZ_SYSCONFIG_RSZB_CLK_EN (1 << 9)
+#define RSZ_SYSCONFIG_RSZA_CLK_EN (1 << 8)
+
+#define RSZ_IN_FIFO_CTRL (0x000c)
+#define RSZ_IN_FIFO_CTRL_THRLD_LOW_MASK (0x1ff << 16)
+#define RSZ_IN_FIFO_CTRL_THRLD_LOW_SHIFT 16
+#define RSZ_IN_FIFO_CTRL_THRLD_HIGH_MASK (0x1ff << 0)
+#define RSZ_IN_FIFO_CTRL_THRLD_HIGH_SHIFT 0
+
+#define RSZ_FRACDIV (0x0008)
+#define RSZ_FRACDIV_MASK (0xffff)
+
+#define RSZ_SRC_EN (0x0020)
+#define RSZ_SRC_EN_SRC_EN (1 << 0)
+
+#define RSZ_SRC_MODE (0x0024)
+#define RSZ_SRC_MODE_OST (1 << 0)
+#define RSZ_SRC_MODE_WRT (1 << 1)
+
+#define RSZ_SRC_FMT0 (0x0028)
+#define RSZ_SRC_FMT0_BYPASS (1 << 1)
+#define RSZ_SRC_FMT0_SEL (1 << 0)
+
+#define RSZ_SRC_FMT1 (0x002c)
+#define RSZ_SRC_FMT1_IN420 (1 << 1)
+
+#define RSZ_SRC_VPS (0x0030)
+#define RSZ_SRC_VSZ (0x0034)
+#define RSZ_SRC_HPS (0x0038)
+#define RSZ_SRC_HSZ (0x003c)
+#define RSZ_DMA_RZA (0x0040)
+#define RSZ_DMA_RZB (0x0044)
+#define RSZ_DMA_STA (0x0048)
+#define RSZ_GCK_MMR (0x004c)
+#define RSZ_GCK_MMR_MMR (1 << 0)
+
+#define RSZ_GCK_SDR (0x0054)
+#define RSZ_GCK_SDR_CORE (1 << 0)
+
+#define RSZ_IRQ_RZA (0x0058)
+#define RSZ_IRQ_RZA_MASK (0x1fff)
+
+#define RSZ_IRQ_RZB (0x005c)
+#define RSZ_IRQ_RZB_MASK (0x1fff)
+
+#define RSZ_YUV_Y_MIN (0x0060)
+#define RSZ_YUV_Y_MAX (0x0064)
+#define RSZ_YUV_C_MIN (0x0068)
+#define RSZ_YUV_C_MAX (0x006c)
+
+#define RSZ_SEQ (0x0074)
+#define RSZ_SEQ_HRVB (1 << 2)
+#define RSZ_SEQ_HRVA (1 << 0)
+
+#define RZA_EN (0x0078)
+#define RZA_MODE (0x007c)
+#define RZA_MODE_ONE_SHOT (1 << 0)
+
+#define RZA_420 (0x0080)
+#define RZA_I_VPS (0x0084)
+#define RZA_I_HPS (0x0088)
+#define RZA_O_VSZ (0x008c)
+#define RZA_O_HSZ (0x0090)
+#define RZA_V_PHS_Y (0x0094)
+#define RZA_V_PHS_C (0x0098)
+#define RZA_V_DIF (0x009c)
+#define RZA_V_TYP (0x00a0)
+#define RZA_V_LPF (0x00a4)
+#define RZA_H_PHS (0x00a8)
+#define RZA_H_DIF (0x00b0)
+#define RZA_H_TYP (0x00b4)
+#define RZA_H_LPF (0x00b8)
+#define RZA_DWN_EN (0x00bc)
+#define RZA_SDR_Y_BAD_H (0x00d0)
+#define RZA_SDR_Y_BAD_L (0x00d4)
+#define RZA_SDR_Y_SAD_H (0x00d8)
+#define RZA_SDR_Y_SAD_L (0x00dc)
+#define RZA_SDR_Y_OFT (0x00e0)
+#define RZA_SDR_Y_PTR_S (0x00e4)
+#define RZA_SDR_Y_PTR_E (0x00e8)
+#define RZA_SDR_C_BAD_H (0x00ec)
+#define RZA_SDR_C_BAD_L (0x00f0)
+#define RZA_SDR_C_SAD_H (0x00f4)
+#define RZA_SDR_C_SAD_L (0x00f8)
+#define RZA_SDR_C_OFT (0x00fc)
+#define RZA_SDR_C_PTR_S (0x0100)
+#define RZA_SDR_C_PTR_E (0x0104)
+
+#define RZB_EN (0x0108)
+#define RZB_MODE (0x010c)
+#define RZB_420 (0x0110)
+#define RZB_I_VPS (0x0114)
+#define RZB_I_HPS (0x0118)
+#define RZB_O_VSZ (0x011c)
+#define RZB_O_HSZ (0x0120)
+
+#define RZB_V_DIF (0x012c)
+#define RZB_V_TYP (0x0130)
+#define RZB_V_LPF (0x0134)
+
+#define RZB_H_DIF (0x0140)
+#define RZB_H_TYP (0x0144)
+#define RZB_H_LPF (0x0148)
+
+#define RZB_SDR_Y_BAD_H (0x0160)
+#define RZB_SDR_Y_BAD_L (0x0164)
+#define RZB_SDR_Y_SAD_H (0x0168)
+#define RZB_SDR_Y_SAD_L (0x016c)
+#define RZB_SDR_Y_OFT (0x0170)
+#define RZB_SDR_Y_PTR_S (0x0174)
+#define RZB_SDR_Y_PTR_E (0x0178)
+#define RZB_SDR_C_BAD_H (0x017c)
+#define RZB_SDR_C_BAD_L (0x0180)
+#define RZB_SDR_C_SAD_H (0x0184)
+#define RZB_SDR_C_SAD_L (0x0188)
+
+#define RZB_SDR_C_PTR_S (0x0190)
+#define RZB_SDR_C_PTR_E (0x0194)
+
+/* Shared Bitmasks between RZA & RZB */
+#define RSZ_EN_EN (1 << 0)
+
+#define RSZ_420_CEN (1 << 1)
+#define RSZ_420_YEN (1 << 0)
+
+#define RSZ_I_VPS_MASK (0x1fff)
+
+#define RSZ_I_HPS_MASK (0x1fff)
+
+#define RSZ_O_VSZ_MASK (0x1fff)
+
+#define RSZ_O_HSZ_MASK (0x1ffe)
+
+#define RSZ_V_PHS_Y_MASK (0x3fff)
+
+#define RSZ_V_PHS_C_MASK (0x3fff)
+
+#define RSZ_V_DIF_MASK (0x3fff)
+
+#define RSZ_V_TYP_C (1 << 1)
+#define RSZ_V_TYP_Y (1 << 0)
+
+#define RSZ_V_LPF_C_MASK (0x3f << 6)
+#define RSZ_V_LPF_C_SHIFT 6
+#define RSZ_V_LPF_Y_MASK (0x3f << 0)
+#define RSZ_V_LPF_Y_SHIFT 0
+
+#define RSZ_H_PHS_MASK (0x3fff)
+
+#define RSZ_H_DIF_MASK (0x3fff)
+
+#define RSZ_H_TYP_C (1 << 1)
+#define RSZ_H_TYP_Y (1 << 0)
+
+#define RSZ_H_LPF_C_MASK (0x3f << 6)
+#define RSZ_H_LPF_C_SHIFT 6
+#define RSZ_H_LPF_Y_MASK (0x3f << 0)
+#define RSZ_H_LPF_Y_SHIFT 0
+
+#define RSZ_DWN_EN_DWN_EN (1 << 0)
+
+#endif /* _OMAP4_ISS_REGS_H_ */
diff --git a/drivers/staging/media/omap4iss/iss_resizer.c b/drivers/staging/media/omap4iss/iss_resizer.c
new file mode 100644
index 00000000000..ae831b8985c
--- /dev/null
+++ b/drivers/staging/media/omap4iss/iss_resizer.c
@@ -0,0 +1,893 @@
+/*
+ * TI OMAP4 ISS V4L2 Driver - ISP RESIZER module
+ *
+ * Copyright (C) 2012 Texas Instruments, Inc.
+ *
+ * Author: Sergio Aguirre <sergio.a.aguirre@gmail.com>
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/uaccess.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/dma-mapping.h>
+#include <linux/mm.h>
+#include <linux/sched.h>
+
+#include "iss.h"
+#include "iss_regs.h"
+#include "iss_resizer.h"
+
+static const unsigned int resizer_fmts[] = {
+ V4L2_MBUS_FMT_UYVY8_1X16,
+ V4L2_MBUS_FMT_YUYV8_1X16,
+};
+
+/*
+ * resizer_print_status - Print current RESIZER Module register values.
+ * @resizer: Pointer to ISS ISP RESIZER device.
+ *
+ * Also prints other debug information stored in the RESIZER module.
+ */
+#define RSZ_PRINT_REGISTER(iss, name)\
+ dev_dbg(iss->dev, "###RSZ " #name "=0x%08x\n", \
+ iss_reg_read(iss, OMAP4_ISS_MEM_ISP_RESIZER, RSZ_##name))
+
+#define RZA_PRINT_REGISTER(iss, name)\
+ dev_dbg(iss->dev, "###RZA " #name "=0x%08x\n", \
+ iss_reg_read(iss, OMAP4_ISS_MEM_ISP_RESIZER, RZA_##name))
+
+static void resizer_print_status(struct iss_resizer_device *resizer)
+{
+ struct iss_device *iss = to_iss_device(resizer);
+
+ dev_dbg(iss->dev, "-------------RESIZER Register dump-------------\n");
+
+ RSZ_PRINT_REGISTER(iss, SYSCONFIG);
+ RSZ_PRINT_REGISTER(iss, IN_FIFO_CTRL);
+ RSZ_PRINT_REGISTER(iss, FRACDIV);
+ RSZ_PRINT_REGISTER(iss, SRC_EN);
+ RSZ_PRINT_REGISTER(iss, SRC_MODE);
+ RSZ_PRINT_REGISTER(iss, SRC_FMT0);
+ RSZ_PRINT_REGISTER(iss, SRC_FMT1);
+ RSZ_PRINT_REGISTER(iss, SRC_VPS);
+ RSZ_PRINT_REGISTER(iss, SRC_VSZ);
+ RSZ_PRINT_REGISTER(iss, SRC_HPS);
+ RSZ_PRINT_REGISTER(iss, SRC_HSZ);
+ RSZ_PRINT_REGISTER(iss, DMA_RZA);
+ RSZ_PRINT_REGISTER(iss, DMA_RZB);
+ RSZ_PRINT_REGISTER(iss, DMA_STA);
+ RSZ_PRINT_REGISTER(iss, GCK_MMR);
+ RSZ_PRINT_REGISTER(iss, GCK_SDR);
+ RSZ_PRINT_REGISTER(iss, IRQ_RZA);
+ RSZ_PRINT_REGISTER(iss, IRQ_RZB);
+ RSZ_PRINT_REGISTER(iss, YUV_Y_MIN);
+ RSZ_PRINT_REGISTER(iss, YUV_Y_MAX);
+ RSZ_PRINT_REGISTER(iss, YUV_C_MIN);
+ RSZ_PRINT_REGISTER(iss, YUV_C_MAX);
+ RSZ_PRINT_REGISTER(iss, SEQ);
+
+ RZA_PRINT_REGISTER(iss, EN);
+ RZA_PRINT_REGISTER(iss, MODE);
+ RZA_PRINT_REGISTER(iss, 420);
+ RZA_PRINT_REGISTER(iss, I_VPS);
+ RZA_PRINT_REGISTER(iss, I_HPS);
+ RZA_PRINT_REGISTER(iss, O_VSZ);
+ RZA_PRINT_REGISTER(iss, O_HSZ);
+ RZA_PRINT_REGISTER(iss, V_PHS_Y);
+ RZA_PRINT_REGISTER(iss, V_PHS_C);
+ RZA_PRINT_REGISTER(iss, V_DIF);
+ RZA_PRINT_REGISTER(iss, V_TYP);
+ RZA_PRINT_REGISTER(iss, V_LPF);
+ RZA_PRINT_REGISTER(iss, H_PHS);
+ RZA_PRINT_REGISTER(iss, H_DIF);
+ RZA_PRINT_REGISTER(iss, H_TYP);
+ RZA_PRINT_REGISTER(iss, H_LPF);
+ RZA_PRINT_REGISTER(iss, DWN_EN);
+ RZA_PRINT_REGISTER(iss, SDR_Y_BAD_H);
+ RZA_PRINT_REGISTER(iss, SDR_Y_BAD_L);
+ RZA_PRINT_REGISTER(iss, SDR_Y_SAD_H);
+ RZA_PRINT_REGISTER(iss, SDR_Y_SAD_L);
+ RZA_PRINT_REGISTER(iss, SDR_Y_OFT);
+ RZA_PRINT_REGISTER(iss, SDR_Y_PTR_S);
+ RZA_PRINT_REGISTER(iss, SDR_Y_PTR_E);
+ RZA_PRINT_REGISTER(iss, SDR_C_BAD_H);
+ RZA_PRINT_REGISTER(iss, SDR_C_BAD_L);
+ RZA_PRINT_REGISTER(iss, SDR_C_SAD_H);
+ RZA_PRINT_REGISTER(iss, SDR_C_SAD_L);
+ RZA_PRINT_REGISTER(iss, SDR_C_OFT);
+ RZA_PRINT_REGISTER(iss, SDR_C_PTR_S);
+ RZA_PRINT_REGISTER(iss, SDR_C_PTR_E);
+
+ dev_dbg(iss->dev, "-----------------------------------------------\n");
+}
+
+/*
+ * resizer_enable - Enable/Disable RESIZER.
+ * @enable: enable flag
+ *
+ */
+static void resizer_enable(struct iss_resizer_device *resizer, u8 enable)
+{
+ struct iss_device *iss = to_iss_device(resizer);
+
+ iss_reg_update(iss, OMAP4_ISS_MEM_ISP_RESIZER, RSZ_SRC_EN,
+ RSZ_SRC_EN_SRC_EN, enable ? RSZ_SRC_EN_SRC_EN : 0);
+
+ /* TODO: Enable RSZB */
+ iss_reg_update(iss, OMAP4_ISS_MEM_ISP_RESIZER, RZA_EN, RSZ_EN_EN,
+ enable ? RSZ_EN_EN : 0);
+}
+
+/* -----------------------------------------------------------------------------
+ * Format- and pipeline-related configuration helpers
+ */
+
+/*
+ * resizer_set_outaddr - Set memory address to save output image
+ * @resizer: Pointer to ISP RESIZER device.
+ * @addr: 32-bit memory address aligned on 32 byte boundary.
+ *
+ * Sets the memory address where the output will be saved.
+ */
+static void resizer_set_outaddr(struct iss_resizer_device *resizer, u32 addr)
+{
+ struct iss_device *iss = to_iss_device(resizer);
+ struct v4l2_mbus_framefmt *informat, *outformat;
+
+ informat = &resizer->formats[RESIZER_PAD_SINK];
+ outformat = &resizer->formats[RESIZER_PAD_SOURCE_MEM];
+
+ /* Save address splitted in Base Address H & L */
+ iss_reg_write(iss, OMAP4_ISS_MEM_ISP_RESIZER, RZA_SDR_Y_BAD_H,
+ (addr >> 16) & 0xffff);
+ iss_reg_write(iss, OMAP4_ISS_MEM_ISP_RESIZER, RZA_SDR_Y_BAD_L,
+ addr & 0xffff);
+
+ /* SAD = BAD */
+ iss_reg_write(iss, OMAP4_ISS_MEM_ISP_RESIZER, RZA_SDR_Y_SAD_H,
+ (addr >> 16) & 0xffff);
+ iss_reg_write(iss, OMAP4_ISS_MEM_ISP_RESIZER, RZA_SDR_Y_SAD_L,
+ addr & 0xffff);
+
+ /* Program UV buffer address... Hardcoded to be contiguous! */
+ if ((informat->code == V4L2_MBUS_FMT_UYVY8_1X16) &&
+ (outformat->code == V4L2_MBUS_FMT_YUYV8_1_5X8)) {
+ u32 c_addr = addr + (resizer->video_out.bpl_value *
+ (outformat->height - 1));
+
+ /* Ensure Y_BAD_L[6:0] = C_BAD_L[6:0]*/
+ if ((c_addr ^ addr) & 0x7f) {
+ c_addr &= ~0x7f;
+ c_addr += 0x80;
+ c_addr |= addr & 0x7f;
+ }
+
+ /* Save address splitted in Base Address H & L */
+ iss_reg_write(iss, OMAP4_ISS_MEM_ISP_RESIZER, RZA_SDR_C_BAD_H,
+ (c_addr >> 16) & 0xffff);
+ iss_reg_write(iss, OMAP4_ISS_MEM_ISP_RESIZER, RZA_SDR_C_BAD_L,
+ c_addr & 0xffff);
+
+ /* SAD = BAD */
+ iss_reg_write(iss, OMAP4_ISS_MEM_ISP_RESIZER, RZA_SDR_C_SAD_H,
+ (c_addr >> 16) & 0xffff);
+ iss_reg_write(iss, OMAP4_ISS_MEM_ISP_RESIZER, RZA_SDR_C_SAD_L,
+ c_addr & 0xffff);
+ }
+}
+
+static void resizer_configure(struct iss_resizer_device *resizer)
+{
+ struct iss_device *iss = to_iss_device(resizer);
+ struct v4l2_mbus_framefmt *informat, *outformat;
+
+ informat = &resizer->formats[RESIZER_PAD_SINK];
+ outformat = &resizer->formats[RESIZER_PAD_SOURCE_MEM];
+
+ /* Disable pass-through more. Despite its name, the BYPASS bit controls
+ * pass-through mode, not bypass mode.
+ */
+ iss_reg_clr(iss, OMAP4_ISS_MEM_ISP_RESIZER, RSZ_SRC_FMT0,
+ RSZ_SRC_FMT0_BYPASS);
+
+ /* Select RSZ input */
+ iss_reg_update(iss, OMAP4_ISS_MEM_ISP_RESIZER, RSZ_SRC_FMT0,
+ RSZ_SRC_FMT0_SEL,
+ resizer->input == RESIZER_INPUT_IPIPEIF ?
+ RSZ_SRC_FMT0_SEL : 0);
+
+ /* RSZ ignores WEN signal from IPIPE/IPIPEIF */
+ iss_reg_clr(iss, OMAP4_ISS_MEM_ISP_RESIZER, RSZ_SRC_MODE,
+ RSZ_SRC_MODE_WRT);
+
+ /* Set Resizer in free-running mode */
+ iss_reg_clr(iss, OMAP4_ISS_MEM_ISP_RESIZER, RSZ_SRC_MODE,
+ RSZ_SRC_MODE_OST);
+
+ /* Init Resizer A */
+ iss_reg_clr(iss, OMAP4_ISS_MEM_ISP_RESIZER, RZA_MODE,
+ RZA_MODE_ONE_SHOT);
+
+ /* Set size related things now */
+ iss_reg_write(iss, OMAP4_ISS_MEM_ISP_RESIZER, RSZ_SRC_VPS, 0);
+ iss_reg_write(iss, OMAP4_ISS_MEM_ISP_RESIZER, RSZ_SRC_HPS, 0);
+ iss_reg_write(iss, OMAP4_ISS_MEM_ISP_RESIZER, RSZ_SRC_VSZ,
+ informat->height - 2);
+ iss_reg_write(iss, OMAP4_ISS_MEM_ISP_RESIZER, RSZ_SRC_HSZ,
+ informat->width - 1);
+
+ iss_reg_write(iss, OMAP4_ISS_MEM_ISP_RESIZER, RZA_I_VPS, 0);
+ iss_reg_write(iss, OMAP4_ISS_MEM_ISP_RESIZER, RZA_I_HPS, 0);
+
+ iss_reg_write(iss, OMAP4_ISS_MEM_ISP_RESIZER, RZA_O_VSZ,
+ outformat->height - 2);
+ iss_reg_write(iss, OMAP4_ISS_MEM_ISP_RESIZER, RZA_O_HSZ,
+ outformat->width - 1);
+
+ iss_reg_write(iss, OMAP4_ISS_MEM_ISP_RESIZER, RZA_V_DIF, 0x100);
+ iss_reg_write(iss, OMAP4_ISS_MEM_ISP_RESIZER, RZA_H_DIF, 0x100);
+
+ /* Buffer output settings */
+ iss_reg_write(iss, OMAP4_ISS_MEM_ISP_RESIZER, RZA_SDR_Y_PTR_S, 0);
+ iss_reg_write(iss, OMAP4_ISS_MEM_ISP_RESIZER, RZA_SDR_Y_PTR_E,
+ outformat->height - 1);
+
+ iss_reg_write(iss, OMAP4_ISS_MEM_ISP_RESIZER, RZA_SDR_Y_OFT,
+ resizer->video_out.bpl_value);
+
+ /* UYVY -> NV12 conversion */
+ if ((informat->code == V4L2_MBUS_FMT_UYVY8_1X16) &&
+ (outformat->code == V4L2_MBUS_FMT_YUYV8_1_5X8)) {
+ iss_reg_write(iss, OMAP4_ISS_MEM_ISP_RESIZER, RZA_420,
+ RSZ_420_CEN | RSZ_420_YEN);
+
+ /* UV Buffer output settings */
+ iss_reg_write(iss, OMAP4_ISS_MEM_ISP_RESIZER, RZA_SDR_C_PTR_S,
+ 0);
+ iss_reg_write(iss, OMAP4_ISS_MEM_ISP_RESIZER, RZA_SDR_C_PTR_E,
+ outformat->height - 1);
+
+ iss_reg_write(iss, OMAP4_ISS_MEM_ISP_RESIZER, RZA_SDR_C_OFT,
+ resizer->video_out.bpl_value);
+ } else {
+ iss_reg_write(iss, OMAP4_ISS_MEM_ISP_RESIZER, RZA_420, 0);
+ }
+}
+
+/* -----------------------------------------------------------------------------
+ * Interrupt handling
+ */
+
+static void resizer_isr_buffer(struct iss_resizer_device *resizer)
+{
+ struct iss_buffer *buffer;
+
+ /* The whole resizer needs to be stopped. Disabling RZA only produces
+ * input FIFO overflows, most probably when the next frame is received.
+ */
+ resizer_enable(resizer, 0);
+
+ buffer = omap4iss_video_buffer_next(&resizer->video_out);
+ if (buffer == NULL)
+ return;
+
+ resizer_set_outaddr(resizer, buffer->iss_addr);
+
+ resizer_enable(resizer, 1);
+}
+
+/*
+ * resizer_isif0_isr - Handle ISIF0 event
+ * @resizer: Pointer to ISP RESIZER device.
+ *
+ * Executes LSC deferred enablement before next frame starts.
+ */
+static void resizer_int_dma_isr(struct iss_resizer_device *resizer)
+{
+ struct iss_pipeline *pipe =
+ to_iss_pipeline(&resizer->subdev.entity);
+ if (pipe->do_propagation)
+ atomic_inc(&pipe->frame_number);
+
+ resizer_isr_buffer(resizer);
+}
+
+/*
+ * omap4iss_resizer_isr - Configure resizer during interframe time.
+ * @resizer: Pointer to ISP RESIZER device.
+ * @events: RESIZER events
+ */
+void omap4iss_resizer_isr(struct iss_resizer_device *resizer, u32 events)
+{
+ struct iss_device *iss = to_iss_device(resizer);
+ struct iss_pipeline *pipe =
+ to_iss_pipeline(&resizer->subdev.entity);
+
+ if (events & (ISP5_IRQ_RSZ_FIFO_IN_BLK_ERR |
+ ISP5_IRQ_RSZ_FIFO_OVF)) {
+ dev_dbg(iss->dev, "RSZ Err: FIFO_IN_BLK:%d, FIFO_OVF:%d\n",
+ events & ISP5_IRQ_RSZ_FIFO_IN_BLK_ERR ? 1 : 0,
+ events & ISP5_IRQ_RSZ_FIFO_OVF ? 1 : 0);
+ omap4iss_pipeline_cancel_stream(pipe);
+ }
+
+ if (omap4iss_module_sync_is_stopping(&resizer->wait,
+ &resizer->stopping))
+ return;
+
+ if (events & ISP5_IRQ_RSZ_INT_DMA)
+ resizer_int_dma_isr(resizer);
+}
+
+/* -----------------------------------------------------------------------------
+ * ISS video operations
+ */
+
+static int resizer_video_queue(struct iss_video *video,
+ struct iss_buffer *buffer)
+{
+ struct iss_resizer_device *resizer = container_of(video,
+ struct iss_resizer_device, video_out);
+
+ if (!(resizer->output & RESIZER_OUTPUT_MEMORY))
+ return -ENODEV;
+
+ resizer_set_outaddr(resizer, buffer->iss_addr);
+
+ /*
+ * If streaming was enabled before there was a buffer queued
+ * or underrun happened in the ISR, the hardware was not enabled
+ * and DMA queue flag ISS_VIDEO_DMAQUEUE_UNDERRUN is still set.
+ * Enable it now.
+ */
+ if (video->dmaqueue_flags & ISS_VIDEO_DMAQUEUE_UNDERRUN) {
+ resizer_enable(resizer, 1);
+ iss_video_dmaqueue_flags_clr(video);
+ }
+
+ return 0;
+}
+
+static const struct iss_video_operations resizer_video_ops = {
+ .queue = resizer_video_queue,
+};
+
+/* -----------------------------------------------------------------------------
+ * V4L2 subdev operations
+ */
+
+/*
+ * resizer_set_stream - Enable/Disable streaming on the RESIZER module
+ * @sd: ISP RESIZER V4L2 subdevice
+ * @enable: Enable/disable stream
+ */
+static int resizer_set_stream(struct v4l2_subdev *sd, int enable)
+{
+ struct iss_resizer_device *resizer = v4l2_get_subdevdata(sd);
+ struct iss_device *iss = to_iss_device(resizer);
+ struct iss_video *video_out = &resizer->video_out;
+ int ret = 0;
+
+ if (resizer->state == ISS_PIPELINE_STREAM_STOPPED) {
+ if (enable == ISS_PIPELINE_STREAM_STOPPED)
+ return 0;
+
+ omap4iss_isp_subclk_enable(iss, OMAP4_ISS_ISP_SUBCLK_RSZ);
+
+ iss_reg_set(iss, OMAP4_ISS_MEM_ISP_RESIZER, RSZ_GCK_MMR,
+ RSZ_GCK_MMR_MMR);
+ iss_reg_set(iss, OMAP4_ISS_MEM_ISP_RESIZER, RSZ_GCK_SDR,
+ RSZ_GCK_SDR_CORE);
+
+ /* FIXME: Enable RSZB also */
+ iss_reg_set(iss, OMAP4_ISS_MEM_ISP_RESIZER, RSZ_SYSCONFIG,
+ RSZ_SYSCONFIG_RSZA_CLK_EN);
+ }
+
+ switch (enable) {
+ case ISS_PIPELINE_STREAM_CONTINUOUS:
+
+ resizer_configure(resizer);
+ resizer_print_status(resizer);
+
+ /*
+ * When outputting to memory with no buffer available, let the
+ * buffer queue handler start the hardware. A DMA queue flag
+ * ISS_VIDEO_DMAQUEUE_QUEUED will be set as soon as there is
+ * a buffer available.
+ */
+ if (resizer->output & RESIZER_OUTPUT_MEMORY &&
+ !(video_out->dmaqueue_flags & ISS_VIDEO_DMAQUEUE_QUEUED))
+ break;
+
+ atomic_set(&resizer->stopping, 0);
+ resizer_enable(resizer, 1);
+ iss_video_dmaqueue_flags_clr(video_out);
+ break;
+
+ case ISS_PIPELINE_STREAM_STOPPED:
+ if (resizer->state == ISS_PIPELINE_STREAM_STOPPED)
+ return 0;
+ if (omap4iss_module_sync_idle(&sd->entity, &resizer->wait,
+ &resizer->stopping))
+ ret = -ETIMEDOUT;
+
+ resizer_enable(resizer, 0);
+ iss_reg_clr(iss, OMAP4_ISS_MEM_ISP_RESIZER, RSZ_SYSCONFIG,
+ RSZ_SYSCONFIG_RSZA_CLK_EN);
+ iss_reg_clr(iss, OMAP4_ISS_MEM_ISP_RESIZER, RSZ_GCK_SDR,
+ RSZ_GCK_SDR_CORE);
+ iss_reg_clr(iss, OMAP4_ISS_MEM_ISP_RESIZER, RSZ_GCK_MMR,
+ RSZ_GCK_MMR_MMR);
+ omap4iss_isp_subclk_disable(iss, OMAP4_ISS_ISP_SUBCLK_RSZ);
+ iss_video_dmaqueue_flags_clr(video_out);
+ break;
+ }
+
+ resizer->state = enable;
+ return ret;
+}
+
+static struct v4l2_mbus_framefmt *
+__resizer_get_format(struct iss_resizer_device *resizer,
+ struct v4l2_subdev_fh *fh, unsigned int pad,
+ enum v4l2_subdev_format_whence which)
+{
+ if (which == V4L2_SUBDEV_FORMAT_TRY)
+ return v4l2_subdev_get_try_format(fh, pad);
+ else
+ return &resizer->formats[pad];
+}
+
+/*
+ * resizer_try_format - Try video format on a pad
+ * @resizer: ISS RESIZER device
+ * @fh : V4L2 subdev file handle
+ * @pad: Pad number
+ * @fmt: Format
+ */
+static void
+resizer_try_format(struct iss_resizer_device *resizer,
+ struct v4l2_subdev_fh *fh, unsigned int pad,
+ struct v4l2_mbus_framefmt *fmt,
+ enum v4l2_subdev_format_whence which)
+{
+ enum v4l2_mbus_pixelcode pixelcode;
+ struct v4l2_mbus_framefmt *format;
+ unsigned int width = fmt->width;
+ unsigned int height = fmt->height;
+ unsigned int i;
+
+ switch (pad) {
+ case RESIZER_PAD_SINK:
+ for (i = 0; i < ARRAY_SIZE(resizer_fmts); i++) {
+ if (fmt->code == resizer_fmts[i])
+ break;
+ }
+
+ /* If not found, use UYVY as default */
+ if (i >= ARRAY_SIZE(resizer_fmts))
+ fmt->code = V4L2_MBUS_FMT_UYVY8_1X16;
+
+ /* Clamp the input size. */
+ fmt->width = clamp_t(u32, width, 1, 8192);
+ fmt->height = clamp_t(u32, height, 1, 8192);
+ break;
+
+ case RESIZER_PAD_SOURCE_MEM:
+ pixelcode = fmt->code;
+ format = __resizer_get_format(resizer, fh, RESIZER_PAD_SINK,
+ which);
+ memcpy(fmt, format, sizeof(*fmt));
+
+ if ((pixelcode == V4L2_MBUS_FMT_YUYV8_1_5X8) &&
+ (fmt->code == V4L2_MBUS_FMT_UYVY8_1X16))
+ fmt->code = pixelcode;
+
+ /* The data formatter truncates the number of horizontal output
+ * pixels to a multiple of 16. To avoid clipping data, allow
+ * callers to request an output size bigger than the input size
+ * up to the nearest multiple of 16.
+ */
+ fmt->width = clamp_t(u32, width, 32, (fmt->width + 15) & ~15);
+ fmt->width &= ~15;
+ fmt->height = clamp_t(u32, height, 32, fmt->height);
+ break;
+
+ }
+
+ fmt->colorspace = V4L2_COLORSPACE_JPEG;
+ fmt->field = V4L2_FIELD_NONE;
+}
+
+/*
+ * resizer_enum_mbus_code - Handle pixel format enumeration
+ * @sd : pointer to v4l2 subdev structure
+ * @fh : V4L2 subdev file handle
+ * @code : pointer to v4l2_subdev_mbus_code_enum structure
+ * return -EINVAL or zero on success
+ */
+static int resizer_enum_mbus_code(struct v4l2_subdev *sd,
+ struct v4l2_subdev_fh *fh,
+ struct v4l2_subdev_mbus_code_enum *code)
+{
+ struct iss_resizer_device *resizer = v4l2_get_subdevdata(sd);
+ struct v4l2_mbus_framefmt *format;
+
+ switch (code->pad) {
+ case RESIZER_PAD_SINK:
+ if (code->index >= ARRAY_SIZE(resizer_fmts))
+ return -EINVAL;
+
+ code->code = resizer_fmts[code->index];
+ break;
+
+ case RESIZER_PAD_SOURCE_MEM:
+ format = __resizer_get_format(resizer, fh, RESIZER_PAD_SINK,
+ V4L2_SUBDEV_FORMAT_TRY);
+
+ if (code->index == 0) {
+ code->code = format->code;
+ break;
+ }
+
+ switch (format->code) {
+ case V4L2_MBUS_FMT_UYVY8_1X16:
+ if (code->index == 1)
+ code->code = V4L2_MBUS_FMT_YUYV8_1_5X8;
+ else
+ return -EINVAL;
+ break;
+ default:
+ if (code->index != 0)
+ return -EINVAL;
+ }
+
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int resizer_enum_frame_size(struct v4l2_subdev *sd,
+ struct v4l2_subdev_fh *fh,
+ struct v4l2_subdev_frame_size_enum *fse)
+{
+ struct iss_resizer_device *resizer = v4l2_get_subdevdata(sd);
+ struct v4l2_mbus_framefmt format;
+
+ if (fse->index != 0)
+ return -EINVAL;
+
+ format.code = fse->code;
+ format.width = 1;
+ format.height = 1;
+ resizer_try_format(resizer, fh, fse->pad, &format,
+ V4L2_SUBDEV_FORMAT_TRY);
+ fse->min_width = format.width;
+ fse->min_height = format.height;
+
+ if (format.code != fse->code)
+ return -EINVAL;
+
+ format.code = fse->code;
+ format.width = -1;
+ format.height = -1;
+ resizer_try_format(resizer, fh, fse->pad, &format,
+ V4L2_SUBDEV_FORMAT_TRY);
+ fse->max_width = format.width;
+ fse->max_height = format.height;
+
+ return 0;
+}
+
+/*
+ * resizer_get_format - Retrieve the video format on a pad
+ * @sd : ISP RESIZER V4L2 subdevice
+ * @fh : V4L2 subdev file handle
+ * @fmt: Format
+ *
+ * Return 0 on success or -EINVAL if the pad is invalid or doesn't correspond
+ * to the format type.
+ */
+static int resizer_get_format(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh,
+ struct v4l2_subdev_format *fmt)
+{
+ struct iss_resizer_device *resizer = v4l2_get_subdevdata(sd);
+ struct v4l2_mbus_framefmt *format;
+
+ format = __resizer_get_format(resizer, fh, fmt->pad, fmt->which);
+ if (format == NULL)
+ return -EINVAL;
+
+ fmt->format = *format;
+ return 0;
+}
+
+/*
+ * resizer_set_format - Set the video format on a pad
+ * @sd : ISP RESIZER V4L2 subdevice
+ * @fh : V4L2 subdev file handle
+ * @fmt: Format
+ *
+ * Return 0 on success or -EINVAL if the pad is invalid or doesn't correspond
+ * to the format type.
+ */
+static int resizer_set_format(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh,
+ struct v4l2_subdev_format *fmt)
+{
+ struct iss_resizer_device *resizer = v4l2_get_subdevdata(sd);
+ struct v4l2_mbus_framefmt *format;
+
+ format = __resizer_get_format(resizer, fh, fmt->pad, fmt->which);
+ if (format == NULL)
+ return -EINVAL;
+
+ resizer_try_format(resizer, fh, fmt->pad, &fmt->format, fmt->which);
+ *format = fmt->format;
+
+ /* Propagate the format from sink to source */
+ if (fmt->pad == RESIZER_PAD_SINK) {
+ format = __resizer_get_format(resizer, fh,
+ RESIZER_PAD_SOURCE_MEM,
+ fmt->which);
+ *format = fmt->format;
+ resizer_try_format(resizer, fh, RESIZER_PAD_SOURCE_MEM, format,
+ fmt->which);
+ }
+
+ return 0;
+}
+
+static int resizer_link_validate(struct v4l2_subdev *sd,
+ struct media_link *link,
+ struct v4l2_subdev_format *source_fmt,
+ struct v4l2_subdev_format *sink_fmt)
+{
+ /* Check if the two ends match */
+ if (source_fmt->format.width != sink_fmt->format.width ||
+ source_fmt->format.height != sink_fmt->format.height)
+ return -EPIPE;
+
+ if (source_fmt->format.code != sink_fmt->format.code)
+ return -EPIPE;
+
+ return 0;
+}
+
+/*
+ * resizer_init_formats - Initialize formats on all pads
+ * @sd: ISP RESIZER V4L2 subdevice
+ * @fh: V4L2 subdev file handle
+ *
+ * Initialize all pad formats with default values. If fh is not NULL, try
+ * formats are initialized on the file handle. Otherwise active formats are
+ * initialized on the device.
+ */
+static int resizer_init_formats(struct v4l2_subdev *sd,
+ struct v4l2_subdev_fh *fh)
+{
+ struct v4l2_subdev_format format;
+
+ memset(&format, 0, sizeof(format));
+ format.pad = RESIZER_PAD_SINK;
+ format.which = fh ? V4L2_SUBDEV_FORMAT_TRY : V4L2_SUBDEV_FORMAT_ACTIVE;
+ format.format.code = V4L2_MBUS_FMT_UYVY8_1X16;
+ format.format.width = 4096;
+ format.format.height = 4096;
+ resizer_set_format(sd, fh, &format);
+
+ return 0;
+}
+
+/* V4L2 subdev video operations */
+static const struct v4l2_subdev_video_ops resizer_v4l2_video_ops = {
+ .s_stream = resizer_set_stream,
+};
+
+/* V4L2 subdev pad operations */
+static const struct v4l2_subdev_pad_ops resizer_v4l2_pad_ops = {
+ .enum_mbus_code = resizer_enum_mbus_code,
+ .enum_frame_size = resizer_enum_frame_size,
+ .get_fmt = resizer_get_format,
+ .set_fmt = resizer_set_format,
+ .link_validate = resizer_link_validate,
+};
+
+/* V4L2 subdev operations */
+static const struct v4l2_subdev_ops resizer_v4l2_ops = {
+ .video = &resizer_v4l2_video_ops,
+ .pad = &resizer_v4l2_pad_ops,
+};
+
+/* V4L2 subdev internal operations */
+static const struct v4l2_subdev_internal_ops resizer_v4l2_internal_ops = {
+ .open = resizer_init_formats,
+};
+
+/* -----------------------------------------------------------------------------
+ * Media entity operations
+ */
+
+/*
+ * resizer_link_setup - Setup RESIZER connections
+ * @entity: RESIZER media entity
+ * @local: Pad at the local end of the link
+ * @remote: Pad at the remote end of the link
+ * @flags: Link flags
+ *
+ * return -EINVAL or zero on success
+ */
+static int resizer_link_setup(struct media_entity *entity,
+ const struct media_pad *local,
+ const struct media_pad *remote, u32 flags)
+{
+ struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(entity);
+ struct iss_resizer_device *resizer = v4l2_get_subdevdata(sd);
+ struct iss_device *iss = to_iss_device(resizer);
+
+ switch (local->index | media_entity_type(remote->entity)) {
+ case RESIZER_PAD_SINK | MEDIA_ENT_T_V4L2_SUBDEV:
+ /* Read from IPIPE or IPIPEIF. */
+ if (!(flags & MEDIA_LNK_FL_ENABLED)) {
+ resizer->input = RESIZER_INPUT_NONE;
+ break;
+ }
+
+ if (resizer->input != RESIZER_INPUT_NONE)
+ return -EBUSY;
+
+ if (remote->entity == &iss->ipipeif.subdev.entity)
+ resizer->input = RESIZER_INPUT_IPIPEIF;
+ else if (remote->entity == &iss->ipipe.subdev.entity)
+ resizer->input = RESIZER_INPUT_IPIPE;
+
+
+ break;
+
+ case RESIZER_PAD_SOURCE_MEM | MEDIA_ENT_T_DEVNODE:
+ /* Write to memory */
+ if (flags & MEDIA_LNK_FL_ENABLED) {
+ if (resizer->output & ~RESIZER_OUTPUT_MEMORY)
+ return -EBUSY;
+ resizer->output |= RESIZER_OUTPUT_MEMORY;
+ } else {
+ resizer->output &= ~RESIZER_OUTPUT_MEMORY;
+ }
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/* media operations */
+static const struct media_entity_operations resizer_media_ops = {
+ .link_setup = resizer_link_setup,
+ .link_validate = v4l2_subdev_link_validate,
+};
+
+/*
+ * resizer_init_entities - Initialize V4L2 subdev and media entity
+ * @resizer: ISS ISP RESIZER module
+ *
+ * Return 0 on success and a negative error code on failure.
+ */
+static int resizer_init_entities(struct iss_resizer_device *resizer)
+{
+ struct v4l2_subdev *sd = &resizer->subdev;
+ struct media_pad *pads = resizer->pads;
+ struct media_entity *me = &sd->entity;
+ int ret;
+
+ resizer->input = RESIZER_INPUT_NONE;
+
+ v4l2_subdev_init(sd, &resizer_v4l2_ops);
+ sd->internal_ops = &resizer_v4l2_internal_ops;
+ strlcpy(sd->name, "OMAP4 ISS ISP resizer", sizeof(sd->name));
+ sd->grp_id = 1 << 16; /* group ID for iss subdevs */
+ v4l2_set_subdevdata(sd, resizer);
+ sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
+
+ pads[RESIZER_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
+ pads[RESIZER_PAD_SOURCE_MEM].flags = MEDIA_PAD_FL_SOURCE;
+
+ me->ops = &resizer_media_ops;
+ ret = media_entity_init(me, RESIZER_PADS_NUM, pads, 0);
+ if (ret < 0)
+ return ret;
+
+ resizer_init_formats(sd, NULL);
+
+ resizer->video_out.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ resizer->video_out.ops = &resizer_video_ops;
+ resizer->video_out.iss = to_iss_device(resizer);
+ resizer->video_out.capture_mem = PAGE_ALIGN(4096 * 4096) * 3;
+ resizer->video_out.bpl_alignment = 32;
+ resizer->video_out.bpl_zero_padding = 1;
+ resizer->video_out.bpl_max = 0x1ffe0;
+
+ ret = omap4iss_video_init(&resizer->video_out, "ISP resizer a");
+ if (ret < 0)
+ return ret;
+
+ /* Connect the RESIZER subdev to the video node. */
+ ret = media_entity_create_link(&resizer->subdev.entity,
+ RESIZER_PAD_SOURCE_MEM,
+ &resizer->video_out.video.entity, 0, 0);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+void omap4iss_resizer_unregister_entities(struct iss_resizer_device *resizer)
+{
+ media_entity_cleanup(&resizer->subdev.entity);
+
+ v4l2_device_unregister_subdev(&resizer->subdev);
+ omap4iss_video_unregister(&resizer->video_out);
+}
+
+int omap4iss_resizer_register_entities(struct iss_resizer_device *resizer,
+ struct v4l2_device *vdev)
+{
+ int ret;
+
+ /* Register the subdev and video node. */
+ ret = v4l2_device_register_subdev(vdev, &resizer->subdev);
+ if (ret < 0)
+ goto error;
+
+ ret = omap4iss_video_register(&resizer->video_out, vdev);
+ if (ret < 0)
+ goto error;
+
+ return 0;
+
+error:
+ omap4iss_resizer_unregister_entities(resizer);
+ return ret;
+}
+
+/* -----------------------------------------------------------------------------
+ * ISP RESIZER initialisation and cleanup
+ */
+
+/*
+ * omap4iss_resizer_init - RESIZER module initialization.
+ * @iss: Device pointer specific to the OMAP4 ISS.
+ *
+ * TODO: Get the initialisation values from platform data.
+ *
+ * Return 0 on success or a negative error code otherwise.
+ */
+int omap4iss_resizer_init(struct iss_device *iss)
+{
+ struct iss_resizer_device *resizer = &iss->resizer;
+
+ resizer->state = ISS_PIPELINE_STREAM_STOPPED;
+ init_waitqueue_head(&resizer->wait);
+
+ return resizer_init_entities(resizer);
+}
+
+/*
+ * omap4iss_resizer_cleanup - RESIZER module cleanup.
+ * @iss: Device pointer specific to the OMAP4 ISS.
+ */
+void omap4iss_resizer_cleanup(struct iss_device *iss)
+{
+ /* FIXME: are you sure there's nothing to do? */
+}
diff --git a/drivers/staging/media/omap4iss/iss_resizer.h b/drivers/staging/media/omap4iss/iss_resizer.h
new file mode 100644
index 00000000000..3727498b06a
--- /dev/null
+++ b/drivers/staging/media/omap4iss/iss_resizer.h
@@ -0,0 +1,75 @@
+/*
+ * TI OMAP4 ISS V4L2 Driver - ISP RESIZER module
+ *
+ * Copyright (C) 2012 Texas Instruments, Inc.
+ *
+ * Author: Sergio Aguirre <sergio.a.aguirre@gmail.com>
+ *
+ * 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.
+ */
+
+#ifndef OMAP4_ISS_RESIZER_H
+#define OMAP4_ISS_RESIZER_H
+
+#include "iss_video.h"
+
+enum resizer_input_entity {
+ RESIZER_INPUT_NONE,
+ RESIZER_INPUT_IPIPE,
+ RESIZER_INPUT_IPIPEIF
+};
+
+#define RESIZER_OUTPUT_MEMORY (1 << 0)
+
+/* Sink and source RESIZER pads */
+#define RESIZER_PAD_SINK 0
+#define RESIZER_PAD_SOURCE_MEM 1
+#define RESIZER_PADS_NUM 2
+
+/*
+ * struct iss_resizer_device - Structure for the RESIZER module to store its own
+ * information
+ * @subdev: V4L2 subdevice
+ * @pads: Sink and source media entity pads
+ * @formats: Active video formats
+ * @input: Active input
+ * @output: Active outputs
+ * @video_out: Output video node
+ * @error: A hardware error occurred during capture
+ * @state: Streaming state
+ * @wait: Wait queue used to stop the module
+ * @stopping: Stopping state
+ */
+struct iss_resizer_device {
+ struct v4l2_subdev subdev;
+ struct media_pad pads[RESIZER_PADS_NUM];
+ struct v4l2_mbus_framefmt formats[RESIZER_PADS_NUM];
+
+ enum resizer_input_entity input;
+ unsigned int output;
+ struct iss_video video_out;
+ unsigned int error;
+
+ enum iss_pipeline_stream_state state;
+ wait_queue_head_t wait;
+ atomic_t stopping;
+};
+
+struct iss_device;
+
+int omap4iss_resizer_init(struct iss_device *iss);
+void omap4iss_resizer_cleanup(struct iss_device *iss);
+int omap4iss_resizer_register_entities(struct iss_resizer_device *resizer,
+ struct v4l2_device *vdev);
+void omap4iss_resizer_unregister_entities(struct iss_resizer_device *resizer);
+
+int omap4iss_resizer_busy(struct iss_resizer_device *resizer);
+void omap4iss_resizer_isr(struct iss_resizer_device *resizer, u32 events);
+void omap4iss_resizer_restore_context(struct iss_device *iss);
+void omap4iss_resizer_max_rate(struct iss_resizer_device *resizer,
+ unsigned int *max_rate);
+
+#endif /* OMAP4_ISS_RESIZER_H */
diff --git a/drivers/staging/media/omap4iss/iss_video.c b/drivers/staging/media/omap4iss/iss_video.c
new file mode 100644
index 00000000000..cbf455d66f7
--- /dev/null
+++ b/drivers/staging/media/omap4iss/iss_video.c
@@ -0,0 +1,1226 @@
+/*
+ * TI OMAP4 ISS V4L2 Driver - Generic video node
+ *
+ * Copyright (C) 2012 Texas Instruments, Inc.
+ *
+ * Author: Sergio Aguirre <sergio.a.aguirre@gmail.com>
+ *
+ * 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.
+ */
+
+#include <asm/cacheflush.h>
+#include <linux/clk.h>
+#include <linux/mm.h>
+#include <linux/pagemap.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/vmalloc.h>
+#include <linux/module.h>
+#include <media/v4l2-dev.h>
+#include <media/v4l2-ioctl.h>
+
+#include "iss_video.h"
+#include "iss.h"
+
+
+/* -----------------------------------------------------------------------------
+ * Helper functions
+ */
+
+static struct iss_format_info formats[] = {
+ { V4L2_MBUS_FMT_Y8_1X8, V4L2_MBUS_FMT_Y8_1X8,
+ V4L2_MBUS_FMT_Y8_1X8, V4L2_MBUS_FMT_Y8_1X8,
+ V4L2_PIX_FMT_GREY, 8, "Greyscale 8 bpp", },
+ { V4L2_MBUS_FMT_Y10_1X10, V4L2_MBUS_FMT_Y10_1X10,
+ V4L2_MBUS_FMT_Y10_1X10, V4L2_MBUS_FMT_Y8_1X8,
+ V4L2_PIX_FMT_Y10, 10, "Greyscale 10 bpp", },
+ { V4L2_MBUS_FMT_Y12_1X12, V4L2_MBUS_FMT_Y10_1X10,
+ V4L2_MBUS_FMT_Y12_1X12, V4L2_MBUS_FMT_Y8_1X8,
+ V4L2_PIX_FMT_Y12, 12, "Greyscale 12 bpp", },
+ { V4L2_MBUS_FMT_SBGGR8_1X8, V4L2_MBUS_FMT_SBGGR8_1X8,
+ V4L2_MBUS_FMT_SBGGR8_1X8, V4L2_MBUS_FMT_SBGGR8_1X8,
+ V4L2_PIX_FMT_SBGGR8, 8, "BGGR Bayer 8 bpp", },
+ { V4L2_MBUS_FMT_SGBRG8_1X8, V4L2_MBUS_FMT_SGBRG8_1X8,
+ V4L2_MBUS_FMT_SGBRG8_1X8, V4L2_MBUS_FMT_SGBRG8_1X8,
+ V4L2_PIX_FMT_SGBRG8, 8, "GBRG Bayer 8 bpp", },
+ { V4L2_MBUS_FMT_SGRBG8_1X8, V4L2_MBUS_FMT_SGRBG8_1X8,
+ V4L2_MBUS_FMT_SGRBG8_1X8, V4L2_MBUS_FMT_SGRBG8_1X8,
+ V4L2_PIX_FMT_SGRBG8, 8, "GRBG Bayer 8 bpp", },
+ { V4L2_MBUS_FMT_SRGGB8_1X8, V4L2_MBUS_FMT_SRGGB8_1X8,
+ V4L2_MBUS_FMT_SRGGB8_1X8, V4L2_MBUS_FMT_SRGGB8_1X8,
+ V4L2_PIX_FMT_SRGGB8, 8, "RGGB Bayer 8 bpp", },
+ { V4L2_MBUS_FMT_SGRBG10_DPCM8_1X8, V4L2_MBUS_FMT_SGRBG10_DPCM8_1X8,
+ V4L2_MBUS_FMT_SGRBG10_1X10, 0,
+ V4L2_PIX_FMT_SGRBG10DPCM8, 8, "GRBG Bayer 10 bpp DPCM8", },
+ { V4L2_MBUS_FMT_SBGGR10_1X10, V4L2_MBUS_FMT_SBGGR10_1X10,
+ V4L2_MBUS_FMT_SBGGR10_1X10, V4L2_MBUS_FMT_SBGGR8_1X8,
+ V4L2_PIX_FMT_SBGGR10, 10, "BGGR Bayer 10 bpp", },
+ { V4L2_MBUS_FMT_SGBRG10_1X10, V4L2_MBUS_FMT_SGBRG10_1X10,
+ V4L2_MBUS_FMT_SGBRG10_1X10, V4L2_MBUS_FMT_SGBRG8_1X8,
+ V4L2_PIX_FMT_SGBRG10, 10, "GBRG Bayer 10 bpp", },
+ { V4L2_MBUS_FMT_SGRBG10_1X10, V4L2_MBUS_FMT_SGRBG10_1X10,
+ V4L2_MBUS_FMT_SGRBG10_1X10, V4L2_MBUS_FMT_SGRBG8_1X8,
+ V4L2_PIX_FMT_SGRBG10, 10, "GRBG Bayer 10 bpp", },
+ { V4L2_MBUS_FMT_SRGGB10_1X10, V4L2_MBUS_FMT_SRGGB10_1X10,
+ V4L2_MBUS_FMT_SRGGB10_1X10, V4L2_MBUS_FMT_SRGGB8_1X8,
+ V4L2_PIX_FMT_SRGGB10, 10, "RGGB Bayer 10 bpp", },
+ { V4L2_MBUS_FMT_SBGGR12_1X12, V4L2_MBUS_FMT_SBGGR10_1X10,
+ V4L2_MBUS_FMT_SBGGR12_1X12, V4L2_MBUS_FMT_SBGGR8_1X8,
+ V4L2_PIX_FMT_SBGGR12, 12, "BGGR Bayer 12 bpp", },
+ { V4L2_MBUS_FMT_SGBRG12_1X12, V4L2_MBUS_FMT_SGBRG10_1X10,
+ V4L2_MBUS_FMT_SGBRG12_1X12, V4L2_MBUS_FMT_SGBRG8_1X8,
+ V4L2_PIX_FMT_SGBRG12, 12, "GBRG Bayer 12 bpp", },
+ { V4L2_MBUS_FMT_SGRBG12_1X12, V4L2_MBUS_FMT_SGRBG10_1X10,
+ V4L2_MBUS_FMT_SGRBG12_1X12, V4L2_MBUS_FMT_SGRBG8_1X8,
+ V4L2_PIX_FMT_SGRBG12, 12, "GRBG Bayer 12 bpp", },
+ { V4L2_MBUS_FMT_SRGGB12_1X12, V4L2_MBUS_FMT_SRGGB10_1X10,
+ V4L2_MBUS_FMT_SRGGB12_1X12, V4L2_MBUS_FMT_SRGGB8_1X8,
+ V4L2_PIX_FMT_SRGGB12, 12, "RGGB Bayer 12 bpp", },
+ { V4L2_MBUS_FMT_UYVY8_1X16, V4L2_MBUS_FMT_UYVY8_1X16,
+ V4L2_MBUS_FMT_UYVY8_1X16, 0,
+ V4L2_PIX_FMT_UYVY, 16, "YUV 4:2:2 (UYVY)", },
+ { V4L2_MBUS_FMT_YUYV8_1X16, V4L2_MBUS_FMT_YUYV8_1X16,
+ V4L2_MBUS_FMT_YUYV8_1X16, 0,
+ V4L2_PIX_FMT_YUYV, 16, "YUV 4:2:2 (YUYV)", },
+ { V4L2_MBUS_FMT_YUYV8_1_5X8, V4L2_MBUS_FMT_YUYV8_1_5X8,
+ V4L2_MBUS_FMT_YUYV8_1_5X8, 0,
+ V4L2_PIX_FMT_NV12, 8, "YUV 4:2:0 (NV12)", },
+};
+
+const struct iss_format_info *
+omap4iss_video_format_info(enum v4l2_mbus_pixelcode code)
+{
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(formats); ++i) {
+ if (formats[i].code == code)
+ return &formats[i];
+ }
+
+ return NULL;
+}
+
+/*
+ * iss_video_mbus_to_pix - Convert v4l2_mbus_framefmt to v4l2_pix_format
+ * @video: ISS video instance
+ * @mbus: v4l2_mbus_framefmt format (input)
+ * @pix: v4l2_pix_format format (output)
+ *
+ * Fill the output pix structure with information from the input mbus format.
+ * The bytesperline and sizeimage fields are computed from the requested bytes
+ * per line value in the pix format and information from the video instance.
+ *
+ * Return the number of padding bytes at end of line.
+ */
+static unsigned int iss_video_mbus_to_pix(const struct iss_video *video,
+ const struct v4l2_mbus_framefmt *mbus,
+ struct v4l2_pix_format *pix)
+{
+ unsigned int bpl = pix->bytesperline;
+ unsigned int min_bpl;
+ unsigned int i;
+
+ memset(pix, 0, sizeof(*pix));
+ pix->width = mbus->width;
+ pix->height = mbus->height;
+
+ /* Skip the last format in the loop so that it will be selected if no
+ * match is found.
+ */
+ for (i = 0; i < ARRAY_SIZE(formats) - 1; ++i) {
+ if (formats[i].code == mbus->code)
+ break;
+ }
+
+ min_bpl = pix->width * ALIGN(formats[i].bpp, 8) / 8;
+
+ /* Clamp the requested bytes per line value. If the maximum bytes per
+ * line value is zero, the module doesn't support user configurable line
+ * sizes. Override the requested value with the minimum in that case.
+ */
+ if (video->bpl_max)
+ bpl = clamp(bpl, min_bpl, video->bpl_max);
+ else
+ bpl = min_bpl;
+
+ if (!video->bpl_zero_padding || bpl != min_bpl)
+ bpl = ALIGN(bpl, video->bpl_alignment);
+
+ pix->pixelformat = formats[i].pixelformat;
+ pix->bytesperline = bpl;
+ pix->sizeimage = pix->bytesperline * pix->height;
+ pix->colorspace = mbus->colorspace;
+ pix->field = mbus->field;
+
+ /* FIXME: Special case for NV12! We should make this nicer... */
+ if (pix->pixelformat == V4L2_PIX_FMT_NV12)
+ pix->sizeimage += (pix->bytesperline * pix->height) / 2;
+
+ return bpl - min_bpl;
+}
+
+static void iss_video_pix_to_mbus(const struct v4l2_pix_format *pix,
+ struct v4l2_mbus_framefmt *mbus)
+{
+ unsigned int i;
+
+ memset(mbus, 0, sizeof(*mbus));
+ mbus->width = pix->width;
+ mbus->height = pix->height;
+
+ for (i = 0; i < ARRAY_SIZE(formats); ++i) {
+ if (formats[i].pixelformat == pix->pixelformat)
+ break;
+ }
+
+ if (WARN_ON(i == ARRAY_SIZE(formats)))
+ return;
+
+ mbus->code = formats[i].code;
+ mbus->colorspace = pix->colorspace;
+ mbus->field = pix->field;
+}
+
+static struct v4l2_subdev *
+iss_video_remote_subdev(struct iss_video *video, u32 *pad)
+{
+ struct media_pad *remote;
+
+ remote = media_entity_remote_pad(&video->pad);
+
+ if (remote == NULL ||
+ media_entity_type(remote->entity) != MEDIA_ENT_T_V4L2_SUBDEV)
+ return NULL;
+
+ if (pad)
+ *pad = remote->index;
+
+ return media_entity_to_v4l2_subdev(remote->entity);
+}
+
+/* Return a pointer to the ISS video instance at the far end of the pipeline. */
+static struct iss_video *
+iss_video_far_end(struct iss_video *video)
+{
+ struct media_entity_graph graph;
+ struct media_entity *entity = &video->video.entity;
+ struct media_device *mdev = entity->parent;
+ struct iss_video *far_end = NULL;
+
+ mutex_lock(&mdev->graph_mutex);
+ media_entity_graph_walk_start(&graph, entity);
+
+ while ((entity = media_entity_graph_walk_next(&graph))) {
+ if (entity == &video->video.entity)
+ continue;
+
+ if (media_entity_type(entity) != MEDIA_ENT_T_DEVNODE)
+ continue;
+
+ far_end = to_iss_video(media_entity_to_video_device(entity));
+ if (far_end->type != video->type)
+ break;
+
+ far_end = NULL;
+ }
+
+ mutex_unlock(&mdev->graph_mutex);
+ return far_end;
+}
+
+static int
+__iss_video_get_format(struct iss_video *video,
+ struct v4l2_mbus_framefmt *format)
+{
+ struct v4l2_subdev_format fmt;
+ struct v4l2_subdev *subdev;
+ u32 pad;
+ int ret;
+
+ subdev = iss_video_remote_subdev(video, &pad);
+ if (subdev == NULL)
+ return -EINVAL;
+
+ memset(&fmt, 0, sizeof(fmt));
+ fmt.pad = pad;
+ fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE;
+
+ mutex_lock(&video->mutex);
+ ret = v4l2_subdev_call(subdev, pad, get_fmt, NULL, &fmt);
+ mutex_unlock(&video->mutex);
+
+ if (ret)
+ return ret;
+
+ *format = fmt.format;
+ return 0;
+}
+
+static int
+iss_video_check_format(struct iss_video *video, struct iss_video_fh *vfh)
+{
+ struct v4l2_mbus_framefmt format;
+ struct v4l2_pix_format pixfmt;
+ int ret;
+
+ ret = __iss_video_get_format(video, &format);
+ if (ret < 0)
+ return ret;
+
+ pixfmt.bytesperline = 0;
+ ret = iss_video_mbus_to_pix(video, &format, &pixfmt);
+
+ if (vfh->format.fmt.pix.pixelformat != pixfmt.pixelformat ||
+ vfh->format.fmt.pix.height != pixfmt.height ||
+ vfh->format.fmt.pix.width != pixfmt.width ||
+ vfh->format.fmt.pix.bytesperline != pixfmt.bytesperline ||
+ vfh->format.fmt.pix.sizeimage != pixfmt.sizeimage)
+ return -EINVAL;
+
+ return ret;
+}
+
+/* -----------------------------------------------------------------------------
+ * Video queue operations
+ */
+
+static int iss_video_queue_setup(struct vb2_queue *vq,
+ const struct v4l2_format *fmt,
+ unsigned int *count, unsigned int *num_planes,
+ unsigned int sizes[], void *alloc_ctxs[])
+{
+ struct iss_video_fh *vfh = vb2_get_drv_priv(vq);
+ struct iss_video *video = vfh->video;
+
+ /* Revisit multi-planar support for NV12 */
+ *num_planes = 1;
+
+ sizes[0] = vfh->format.fmt.pix.sizeimage;
+ if (sizes[0] == 0)
+ return -EINVAL;
+
+ alloc_ctxs[0] = video->alloc_ctx;
+
+ *count = min(*count, video->capture_mem / PAGE_ALIGN(sizes[0]));
+
+ return 0;
+}
+
+static void iss_video_buf_cleanup(struct vb2_buffer *vb)
+{
+ struct iss_buffer *buffer = container_of(vb, struct iss_buffer, vb);
+
+ if (buffer->iss_addr)
+ buffer->iss_addr = 0;
+}
+
+static int iss_video_buf_prepare(struct vb2_buffer *vb)
+{
+ struct iss_video_fh *vfh = vb2_get_drv_priv(vb->vb2_queue);
+ struct iss_buffer *buffer = container_of(vb, struct iss_buffer, vb);
+ struct iss_video *video = vfh->video;
+ unsigned long size = vfh->format.fmt.pix.sizeimage;
+ dma_addr_t addr;
+
+ if (vb2_plane_size(vb, 0) < size)
+ return -ENOBUFS;
+
+ /* Refuse to prepare the buffer is the video node has registered an
+ * error. We don't need to take any lock here as the operation is
+ * inherently racy. The authoritative check will be performed in the
+ * queue handler, which can't return an error, this check is just a best
+ * effort to notify userspace as early as possible.
+ */
+ if (unlikely(video->error))
+ return -EIO;
+
+ addr = vb2_dma_contig_plane_dma_addr(vb, 0);
+ if (!IS_ALIGNED(addr, 32)) {
+ dev_dbg(video->iss->dev,
+ "Buffer address must be aligned to 32 bytes boundary.\n");
+ return -EINVAL;
+ }
+
+ vb2_set_plane_payload(vb, 0, size);
+ buffer->iss_addr = addr;
+ return 0;
+}
+
+static void iss_video_buf_queue(struct vb2_buffer *vb)
+{
+ struct iss_video_fh *vfh = vb2_get_drv_priv(vb->vb2_queue);
+ struct iss_video *video = vfh->video;
+ struct iss_buffer *buffer = container_of(vb, struct iss_buffer, vb);
+ struct iss_pipeline *pipe = to_iss_pipeline(&video->video.entity);
+ unsigned long flags;
+ bool empty;
+
+ spin_lock_irqsave(&video->qlock, flags);
+
+ if (unlikely(video->error)) {
+ vb2_buffer_done(vb, VB2_BUF_STATE_ERROR);
+ spin_unlock_irqrestore(&video->qlock, flags);
+ return;
+ }
+
+ empty = list_empty(&video->dmaqueue);
+ list_add_tail(&buffer->list, &video->dmaqueue);
+
+ spin_unlock_irqrestore(&video->qlock, flags);
+
+ if (empty) {
+ enum iss_pipeline_state state;
+ unsigned int start;
+
+ if (video->type == V4L2_BUF_TYPE_VIDEO_CAPTURE)
+ state = ISS_PIPELINE_QUEUE_OUTPUT;
+ else
+ state = ISS_PIPELINE_QUEUE_INPUT;
+
+ spin_lock_irqsave(&pipe->lock, flags);
+ pipe->state |= state;
+ video->ops->queue(video, buffer);
+ video->dmaqueue_flags |= ISS_VIDEO_DMAQUEUE_QUEUED;
+
+ start = iss_pipeline_ready(pipe);
+ if (start)
+ pipe->state |= ISS_PIPELINE_STREAM;
+ spin_unlock_irqrestore(&pipe->lock, flags);
+
+ if (start)
+ omap4iss_pipeline_set_stream(pipe,
+ ISS_PIPELINE_STREAM_SINGLESHOT);
+ }
+}
+
+static const struct vb2_ops iss_video_vb2ops = {
+ .queue_setup = iss_video_queue_setup,
+ .buf_prepare = iss_video_buf_prepare,
+ .buf_queue = iss_video_buf_queue,
+ .buf_cleanup = iss_video_buf_cleanup,
+};
+
+/*
+ * omap4iss_video_buffer_next - Complete the current buffer and return the next
+ * @video: ISS video object
+ *
+ * Remove the current video buffer from the DMA queue and fill its timestamp,
+ * field count and state fields before waking up its completion handler.
+ *
+ * For capture video nodes, the buffer state is set to VB2_BUF_STATE_DONE if no
+ * error has been flagged in the pipeline, or to VB2_BUF_STATE_ERROR otherwise.
+ *
+ * The DMA queue is expected to contain at least one buffer.
+ *
+ * Return a pointer to the next buffer in the DMA queue, or NULL if the queue is
+ * empty.
+ */
+struct iss_buffer *omap4iss_video_buffer_next(struct iss_video *video)
+{
+ struct iss_pipeline *pipe = to_iss_pipeline(&video->video.entity);
+ enum iss_pipeline_state state;
+ struct iss_buffer *buf;
+ unsigned long flags;
+ struct timespec ts;
+
+ spin_lock_irqsave(&video->qlock, flags);
+ if (WARN_ON(list_empty(&video->dmaqueue))) {
+ spin_unlock_irqrestore(&video->qlock, flags);
+ return NULL;
+ }
+
+ buf = list_first_entry(&video->dmaqueue, struct iss_buffer,
+ list);
+ list_del(&buf->list);
+ spin_unlock_irqrestore(&video->qlock, flags);
+
+ ktime_get_ts(&ts);
+ buf->vb.v4l2_buf.timestamp.tv_sec = ts.tv_sec;
+ buf->vb.v4l2_buf.timestamp.tv_usec = ts.tv_nsec / NSEC_PER_USEC;
+
+ /* Do frame number propagation only if this is the output video node.
+ * Frame number either comes from the CSI receivers or it gets
+ * incremented here if H3A is not active.
+ * Note: There is no guarantee that the output buffer will finish
+ * first, so the input number might lag behind by 1 in some cases.
+ */
+ if (video == pipe->output && !pipe->do_propagation)
+ buf->vb.v4l2_buf.sequence =
+ atomic_inc_return(&pipe->frame_number);
+ else
+ buf->vb.v4l2_buf.sequence = atomic_read(&pipe->frame_number);
+
+ vb2_buffer_done(&buf->vb, pipe->error ?
+ VB2_BUF_STATE_ERROR : VB2_BUF_STATE_DONE);
+ pipe->error = false;
+
+ spin_lock_irqsave(&video->qlock, flags);
+ if (list_empty(&video->dmaqueue)) {
+ spin_unlock_irqrestore(&video->qlock, flags);
+ if (video->type == V4L2_BUF_TYPE_VIDEO_CAPTURE)
+ state = ISS_PIPELINE_QUEUE_OUTPUT
+ | ISS_PIPELINE_STREAM;
+ else
+ state = ISS_PIPELINE_QUEUE_INPUT
+ | ISS_PIPELINE_STREAM;
+
+ spin_lock_irqsave(&pipe->lock, flags);
+ pipe->state &= ~state;
+ if (video->pipe.stream_state == ISS_PIPELINE_STREAM_CONTINUOUS)
+ video->dmaqueue_flags |= ISS_VIDEO_DMAQUEUE_UNDERRUN;
+ spin_unlock_irqrestore(&pipe->lock, flags);
+ return NULL;
+ }
+
+ if (video->type == V4L2_BUF_TYPE_VIDEO_CAPTURE && pipe->input != NULL) {
+ spin_lock(&pipe->lock);
+ pipe->state &= ~ISS_PIPELINE_STREAM;
+ spin_unlock(&pipe->lock);
+ }
+
+ buf = list_first_entry(&video->dmaqueue, struct iss_buffer,
+ list);
+ spin_unlock_irqrestore(&video->qlock, flags);
+ buf->vb.state = VB2_BUF_STATE_ACTIVE;
+ return buf;
+}
+
+/*
+ * omap4iss_video_cancel_stream - Cancel stream on a video node
+ * @video: ISS video object
+ *
+ * Cancelling a stream mark all buffers on the video node as erroneous and makes
+ * sure no new buffer can be queued.
+ */
+void omap4iss_video_cancel_stream(struct iss_video *video)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&video->qlock, flags);
+
+ while (!list_empty(&video->dmaqueue)) {
+ struct iss_buffer *buf;
+
+ buf = list_first_entry(&video->dmaqueue, struct iss_buffer,
+ list);
+ list_del(&buf->list);
+ vb2_buffer_done(&buf->vb, VB2_BUF_STATE_ERROR);
+ }
+
+ video->error = true;
+
+ spin_unlock_irqrestore(&video->qlock, flags);
+}
+
+/* -----------------------------------------------------------------------------
+ * V4L2 ioctls
+ */
+
+static int
+iss_video_querycap(struct file *file, void *fh, struct v4l2_capability *cap)
+{
+ struct iss_video *video = video_drvdata(file);
+
+ strlcpy(cap->driver, ISS_VIDEO_DRIVER_NAME, sizeof(cap->driver));
+ strlcpy(cap->card, video->video.name, sizeof(cap->card));
+ strlcpy(cap->bus_info, "media", sizeof(cap->bus_info));
+
+ if (video->type == V4L2_BUF_TYPE_VIDEO_CAPTURE)
+ cap->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;
+ else
+ cap->device_caps = V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_STREAMING;
+
+ cap->capabilities = V4L2_CAP_DEVICE_CAPS | V4L2_CAP_STREAMING
+ | V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_VIDEO_OUTPUT;
+
+ return 0;
+}
+
+static int
+iss_video_enum_format(struct file *file, void *fh, struct v4l2_fmtdesc *f)
+{
+ struct iss_video *video = video_drvdata(file);
+ struct v4l2_mbus_framefmt format;
+ unsigned int index = f->index;
+ unsigned int i;
+ int ret;
+
+ if (f->type != video->type)
+ return -EINVAL;
+
+ ret = __iss_video_get_format(video, &format);
+ if (ret < 0)
+ return ret;
+
+ for (i = 0; i < ARRAY_SIZE(formats); ++i) {
+ const struct iss_format_info *info = &formats[i];
+
+ if (format.code != info->code)
+ continue;
+
+ if (index == 0) {
+ f->pixelformat = info->pixelformat;
+ strlcpy(f->description, info->description,
+ sizeof(f->description));
+ return 0;
+ }
+
+ index--;
+ }
+
+ return -EINVAL;
+}
+
+static int
+iss_video_get_format(struct file *file, void *fh, struct v4l2_format *format)
+{
+ struct iss_video_fh *vfh = to_iss_video_fh(fh);
+ struct iss_video *video = video_drvdata(file);
+
+ if (format->type != video->type)
+ return -EINVAL;
+
+ mutex_lock(&video->mutex);
+ *format = vfh->format;
+ mutex_unlock(&video->mutex);
+
+ return 0;
+}
+
+static int
+iss_video_set_format(struct file *file, void *fh, struct v4l2_format *format)
+{
+ struct iss_video_fh *vfh = to_iss_video_fh(fh);
+ struct iss_video *video = video_drvdata(file);
+ struct v4l2_mbus_framefmt fmt;
+
+ if (format->type != video->type)
+ return -EINVAL;
+
+ mutex_lock(&video->mutex);
+
+ /* Fill the bytesperline and sizeimage fields by converting to media bus
+ * format and back to pixel format.
+ */
+ iss_video_pix_to_mbus(&format->fmt.pix, &fmt);
+ iss_video_mbus_to_pix(video, &fmt, &format->fmt.pix);
+
+ vfh->format = *format;
+
+ mutex_unlock(&video->mutex);
+ return 0;
+}
+
+static int
+iss_video_try_format(struct file *file, void *fh, struct v4l2_format *format)
+{
+ struct iss_video *video = video_drvdata(file);
+ struct v4l2_subdev_format fmt;
+ struct v4l2_subdev *subdev;
+ u32 pad;
+ int ret;
+
+ if (format->type != video->type)
+ return -EINVAL;
+
+ subdev = iss_video_remote_subdev(video, &pad);
+ if (subdev == NULL)
+ return -EINVAL;
+
+ iss_video_pix_to_mbus(&format->fmt.pix, &fmt.format);
+
+ fmt.pad = pad;
+ fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE;
+ ret = v4l2_subdev_call(subdev, pad, get_fmt, NULL, &fmt);
+ if (ret)
+ return ret;
+
+ iss_video_mbus_to_pix(video, &fmt.format, &format->fmt.pix);
+ return 0;
+}
+
+static int
+iss_video_cropcap(struct file *file, void *fh, struct v4l2_cropcap *cropcap)
+{
+ struct iss_video *video = video_drvdata(file);
+ struct v4l2_subdev *subdev;
+ int ret;
+
+ subdev = iss_video_remote_subdev(video, NULL);
+ if (subdev == NULL)
+ return -EINVAL;
+
+ mutex_lock(&video->mutex);
+ ret = v4l2_subdev_call(subdev, video, cropcap, cropcap);
+ mutex_unlock(&video->mutex);
+
+ return ret == -ENOIOCTLCMD ? -ENOTTY : ret;
+}
+
+static int
+iss_video_get_crop(struct file *file, void *fh, struct v4l2_crop *crop)
+{
+ struct iss_video *video = video_drvdata(file);
+ struct v4l2_subdev_format format;
+ struct v4l2_subdev *subdev;
+ u32 pad;
+ int ret;
+
+ subdev = iss_video_remote_subdev(video, &pad);
+ if (subdev == NULL)
+ return -EINVAL;
+
+ /* Try the get crop operation first and fallback to get format if not
+ * implemented.
+ */
+ ret = v4l2_subdev_call(subdev, video, g_crop, crop);
+ if (ret != -ENOIOCTLCMD)
+ return ret;
+
+ format.pad = pad;
+ format.which = V4L2_SUBDEV_FORMAT_ACTIVE;
+ ret = v4l2_subdev_call(subdev, pad, get_fmt, NULL, &format);
+ if (ret < 0)
+ return ret == -ENOIOCTLCMD ? -ENOTTY : ret;
+
+ crop->c.left = 0;
+ crop->c.top = 0;
+ crop->c.width = format.format.width;
+ crop->c.height = format.format.height;
+
+ return 0;
+}
+
+static int
+iss_video_set_crop(struct file *file, void *fh, const struct v4l2_crop *crop)
+{
+ struct iss_video *video = video_drvdata(file);
+ struct v4l2_subdev *subdev;
+ int ret;
+
+ subdev = iss_video_remote_subdev(video, NULL);
+ if (subdev == NULL)
+ return -EINVAL;
+
+ mutex_lock(&video->mutex);
+ ret = v4l2_subdev_call(subdev, video, s_crop, crop);
+ mutex_unlock(&video->mutex);
+
+ return ret == -ENOIOCTLCMD ? -ENOTTY : ret;
+}
+
+static int
+iss_video_get_param(struct file *file, void *fh, struct v4l2_streamparm *a)
+{
+ struct iss_video_fh *vfh = to_iss_video_fh(fh);
+ struct iss_video *video = video_drvdata(file);
+
+ if (video->type != V4L2_BUF_TYPE_VIDEO_OUTPUT ||
+ video->type != a->type)
+ return -EINVAL;
+
+ memset(a, 0, sizeof(*a));
+ a->type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
+ a->parm.output.capability = V4L2_CAP_TIMEPERFRAME;
+ a->parm.output.timeperframe = vfh->timeperframe;
+
+ return 0;
+}
+
+static int
+iss_video_set_param(struct file *file, void *fh, struct v4l2_streamparm *a)
+{
+ struct iss_video_fh *vfh = to_iss_video_fh(fh);
+ struct iss_video *video = video_drvdata(file);
+
+ if (video->type != V4L2_BUF_TYPE_VIDEO_OUTPUT ||
+ video->type != a->type)
+ return -EINVAL;
+
+ if (a->parm.output.timeperframe.denominator == 0)
+ a->parm.output.timeperframe.denominator = 1;
+
+ vfh->timeperframe = a->parm.output.timeperframe;
+
+ return 0;
+}
+
+static int
+iss_video_reqbufs(struct file *file, void *fh, struct v4l2_requestbuffers *rb)
+{
+ struct iss_video_fh *vfh = to_iss_video_fh(fh);
+
+ return vb2_reqbufs(&vfh->queue, rb);
+}
+
+static int
+iss_video_querybuf(struct file *file, void *fh, struct v4l2_buffer *b)
+{
+ struct iss_video_fh *vfh = to_iss_video_fh(fh);
+
+ return vb2_querybuf(&vfh->queue, b);
+}
+
+static int
+iss_video_qbuf(struct file *file, void *fh, struct v4l2_buffer *b)
+{
+ struct iss_video_fh *vfh = to_iss_video_fh(fh);
+
+ return vb2_qbuf(&vfh->queue, b);
+}
+
+static int
+iss_video_dqbuf(struct file *file, void *fh, struct v4l2_buffer *b)
+{
+ struct iss_video_fh *vfh = to_iss_video_fh(fh);
+
+ return vb2_dqbuf(&vfh->queue, b, file->f_flags & O_NONBLOCK);
+}
+
+/*
+ * Stream management
+ *
+ * Every ISS pipeline has a single input and a single output. The input can be
+ * either a sensor or a video node. The output is always a video node.
+ *
+ * As every pipeline has an output video node, the ISS video objects at the
+ * pipeline output stores the pipeline state. It tracks the streaming state of
+ * both the input and output, as well as the availability of buffers.
+ *
+ * In sensor-to-memory mode, frames are always available at the pipeline input.
+ * Starting the sensor usually requires I2C transfers and must be done in
+ * interruptible context. The pipeline is started and stopped synchronously
+ * to the stream on/off commands. All modules in the pipeline will get their
+ * subdev set stream handler called. The module at the end of the pipeline must
+ * delay starting the hardware until buffers are available at its output.
+ *
+ * In memory-to-memory mode, starting/stopping the stream requires
+ * synchronization between the input and output. ISS modules can't be stopped
+ * in the middle of a frame, and at least some of the modules seem to become
+ * busy as soon as they're started, even if they don't receive a frame start
+ * event. For that reason frames need to be processed in single-shot mode. The
+ * driver needs to wait until a frame is completely processed and written to
+ * memory before restarting the pipeline for the next frame. Pipelined
+ * processing might be possible but requires more testing.
+ *
+ * Stream start must be delayed until buffers are available at both the input
+ * and output. The pipeline must be started in the videobuf queue callback with
+ * the buffers queue spinlock held. The modules subdev set stream operation must
+ * not sleep.
+ */
+static int
+iss_video_streamon(struct file *file, void *fh, enum v4l2_buf_type type)
+{
+ struct iss_video_fh *vfh = to_iss_video_fh(fh);
+ struct iss_video *video = video_drvdata(file);
+ struct media_entity_graph graph;
+ struct media_entity *entity;
+ enum iss_pipeline_state state;
+ struct iss_pipeline *pipe;
+ struct iss_video *far_end;
+ unsigned long flags;
+ int ret;
+
+ if (type != video->type)
+ return -EINVAL;
+
+ mutex_lock(&video->stream_lock);
+
+ /* Start streaming on the pipeline. No link touching an entity in the
+ * pipeline can be activated or deactivated once streaming is started.
+ */
+ pipe = video->video.entity.pipe
+ ? to_iss_pipeline(&video->video.entity) : &video->pipe;
+ pipe->external = NULL;
+ pipe->external_rate = 0;
+ pipe->external_bpp = 0;
+ pipe->entities = 0;
+
+ if (video->iss->pdata->set_constraints)
+ video->iss->pdata->set_constraints(video->iss, true);
+
+ ret = media_entity_pipeline_start(&video->video.entity, &pipe->pipe);
+ if (ret < 0)
+ goto err_media_entity_pipeline_start;
+
+ entity = &video->video.entity;
+ media_entity_graph_walk_start(&graph, entity);
+ while ((entity = media_entity_graph_walk_next(&graph)))
+ pipe->entities |= 1 << entity->id;
+
+ /* Verify that the currently configured format matches the output of
+ * the connected subdev.
+ */
+ ret = iss_video_check_format(video, vfh);
+ if (ret < 0)
+ goto err_iss_video_check_format;
+
+ video->bpl_padding = ret;
+ video->bpl_value = vfh->format.fmt.pix.bytesperline;
+
+ /* Find the ISS video node connected at the far end of the pipeline and
+ * update the pipeline.
+ */
+ far_end = iss_video_far_end(video);
+
+ if (video->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) {
+ state = ISS_PIPELINE_STREAM_OUTPUT | ISS_PIPELINE_IDLE_OUTPUT;
+ pipe->input = far_end;
+ pipe->output = video;
+ } else {
+ if (far_end == NULL) {
+ ret = -EPIPE;
+ goto err_iss_video_check_format;
+ }
+
+ state = ISS_PIPELINE_STREAM_INPUT | ISS_PIPELINE_IDLE_INPUT;
+ pipe->input = video;
+ pipe->output = far_end;
+ }
+
+ spin_lock_irqsave(&pipe->lock, flags);
+ pipe->state &= ~ISS_PIPELINE_STREAM;
+ pipe->state |= state;
+ spin_unlock_irqrestore(&pipe->lock, flags);
+
+ /* Set the maximum time per frame as the value requested by userspace.
+ * This is a soft limit that can be overridden if the hardware doesn't
+ * support the request limit.
+ */
+ if (video->type == V4L2_BUF_TYPE_VIDEO_OUTPUT)
+ pipe->max_timeperframe = vfh->timeperframe;
+
+ video->queue = &vfh->queue;
+ INIT_LIST_HEAD(&video->dmaqueue);
+ spin_lock_init(&video->qlock);
+ video->error = false;
+ atomic_set(&pipe->frame_number, -1);
+
+ ret = vb2_streamon(&vfh->queue, type);
+ if (ret < 0)
+ goto err_iss_video_check_format;
+
+ /* In sensor-to-memory mode, the stream can be started synchronously
+ * to the stream on command. In memory-to-memory mode, it will be
+ * started when buffers are queued on both the input and output.
+ */
+ if (pipe->input == NULL) {
+ unsigned long flags;
+ ret = omap4iss_pipeline_set_stream(pipe,
+ ISS_PIPELINE_STREAM_CONTINUOUS);
+ if (ret < 0)
+ goto err_omap4iss_set_stream;
+ spin_lock_irqsave(&video->qlock, flags);
+ if (list_empty(&video->dmaqueue))
+ video->dmaqueue_flags |= ISS_VIDEO_DMAQUEUE_UNDERRUN;
+ spin_unlock_irqrestore(&video->qlock, flags);
+ }
+
+ mutex_unlock(&video->stream_lock);
+ return 0;
+
+err_omap4iss_set_stream:
+ vb2_streamoff(&vfh->queue, type);
+err_iss_video_check_format:
+ media_entity_pipeline_stop(&video->video.entity);
+err_media_entity_pipeline_start:
+ if (video->iss->pdata->set_constraints)
+ video->iss->pdata->set_constraints(video->iss, false);
+ video->queue = NULL;
+
+ mutex_unlock(&video->stream_lock);
+ return ret;
+}
+
+static int
+iss_video_streamoff(struct file *file, void *fh, enum v4l2_buf_type type)
+{
+ struct iss_video_fh *vfh = to_iss_video_fh(fh);
+ struct iss_video *video = video_drvdata(file);
+ struct iss_pipeline *pipe = to_iss_pipeline(&video->video.entity);
+ enum iss_pipeline_state state;
+ unsigned long flags;
+
+ if (type != video->type)
+ return -EINVAL;
+
+ mutex_lock(&video->stream_lock);
+
+ if (!vb2_is_streaming(&vfh->queue))
+ goto done;
+
+ /* Update the pipeline state. */
+ if (video->type == V4L2_BUF_TYPE_VIDEO_CAPTURE)
+ state = ISS_PIPELINE_STREAM_OUTPUT
+ | ISS_PIPELINE_QUEUE_OUTPUT;
+ else
+ state = ISS_PIPELINE_STREAM_INPUT
+ | ISS_PIPELINE_QUEUE_INPUT;
+
+ spin_lock_irqsave(&pipe->lock, flags);
+ pipe->state &= ~state;
+ spin_unlock_irqrestore(&pipe->lock, flags);
+
+ /* Stop the stream. */
+ omap4iss_pipeline_set_stream(pipe, ISS_PIPELINE_STREAM_STOPPED);
+ vb2_streamoff(&vfh->queue, type);
+ video->queue = NULL;
+
+ if (video->iss->pdata->set_constraints)
+ video->iss->pdata->set_constraints(video->iss, false);
+ media_entity_pipeline_stop(&video->video.entity);
+
+done:
+ mutex_unlock(&video->stream_lock);
+ return 0;
+}
+
+static int
+iss_video_enum_input(struct file *file, void *fh, struct v4l2_input *input)
+{
+ if (input->index > 0)
+ return -EINVAL;
+
+ strlcpy(input->name, "camera", sizeof(input->name));
+ input->type = V4L2_INPUT_TYPE_CAMERA;
+
+ return 0;
+}
+
+static int
+iss_video_g_input(struct file *file, void *fh, unsigned int *input)
+{
+ *input = 0;
+
+ return 0;
+}
+
+static int
+iss_video_s_input(struct file *file, void *fh, unsigned int input)
+{
+ return input == 0 ? 0 : -EINVAL;
+}
+
+static const struct v4l2_ioctl_ops iss_video_ioctl_ops = {
+ .vidioc_querycap = iss_video_querycap,
+ .vidioc_enum_fmt_vid_cap = iss_video_enum_format,
+ .vidioc_g_fmt_vid_cap = iss_video_get_format,
+ .vidioc_s_fmt_vid_cap = iss_video_set_format,
+ .vidioc_try_fmt_vid_cap = iss_video_try_format,
+ .vidioc_g_fmt_vid_out = iss_video_get_format,
+ .vidioc_s_fmt_vid_out = iss_video_set_format,
+ .vidioc_try_fmt_vid_out = iss_video_try_format,
+ .vidioc_cropcap = iss_video_cropcap,
+ .vidioc_g_crop = iss_video_get_crop,
+ .vidioc_s_crop = iss_video_set_crop,
+ .vidioc_g_parm = iss_video_get_param,
+ .vidioc_s_parm = iss_video_set_param,
+ .vidioc_reqbufs = iss_video_reqbufs,
+ .vidioc_querybuf = iss_video_querybuf,
+ .vidioc_qbuf = iss_video_qbuf,
+ .vidioc_dqbuf = iss_video_dqbuf,
+ .vidioc_streamon = iss_video_streamon,
+ .vidioc_streamoff = iss_video_streamoff,
+ .vidioc_enum_input = iss_video_enum_input,
+ .vidioc_g_input = iss_video_g_input,
+ .vidioc_s_input = iss_video_s_input,
+};
+
+/* -----------------------------------------------------------------------------
+ * V4L2 file operations
+ */
+
+static int iss_video_open(struct file *file)
+{
+ struct iss_video *video = video_drvdata(file);
+ struct iss_video_fh *handle;
+ struct vb2_queue *q;
+ int ret = 0;
+
+ handle = kzalloc(sizeof(*handle), GFP_KERNEL);
+ if (handle == NULL)
+ return -ENOMEM;
+
+ v4l2_fh_init(&handle->vfh, &video->video);
+ v4l2_fh_add(&handle->vfh);
+
+ /* If this is the first user, initialise the pipeline. */
+ if (omap4iss_get(video->iss) == NULL) {
+ ret = -EBUSY;
+ goto done;
+ }
+
+ ret = omap4iss_pipeline_pm_use(&video->video.entity, 1);
+ if (ret < 0) {
+ omap4iss_put(video->iss);
+ goto done;
+ }
+
+ video->alloc_ctx = vb2_dma_contig_init_ctx(video->iss->dev);
+ if (IS_ERR(video->alloc_ctx)) {
+ ret = PTR_ERR(video->alloc_ctx);
+ omap4iss_put(video->iss);
+ goto done;
+ }
+
+ q = &handle->queue;
+
+ q->type = video->type;
+ q->io_modes = VB2_MMAP;
+ q->drv_priv = handle;
+ q->ops = &iss_video_vb2ops;
+ q->mem_ops = &vb2_dma_contig_memops;
+ q->buf_struct_size = sizeof(struct iss_buffer);
+ q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
+
+ ret = vb2_queue_init(q);
+ if (ret) {
+ omap4iss_put(video->iss);
+ goto done;
+ }
+
+ memset(&handle->format, 0, sizeof(handle->format));
+ handle->format.type = video->type;
+ handle->timeperframe.denominator = 1;
+
+ handle->video = video;
+ file->private_data = &handle->vfh;
+
+done:
+ if (ret < 0) {
+ v4l2_fh_del(&handle->vfh);
+ kfree(handle);
+ }
+
+ return ret;
+}
+
+static int iss_video_release(struct file *file)
+{
+ struct iss_video *video = video_drvdata(file);
+ struct v4l2_fh *vfh = file->private_data;
+ struct iss_video_fh *handle = to_iss_video_fh(vfh);
+
+ /* Disable streaming and free the buffers queue resources. */
+ iss_video_streamoff(file, vfh, video->type);
+
+ omap4iss_pipeline_pm_use(&video->video.entity, 0);
+
+ /* Release the videobuf2 queue */
+ vb2_queue_release(&handle->queue);
+
+ /* Release the file handle. */
+ v4l2_fh_del(vfh);
+ kfree(handle);
+ file->private_data = NULL;
+
+ omap4iss_put(video->iss);
+
+ return 0;
+}
+
+static unsigned int iss_video_poll(struct file *file, poll_table *wait)
+{
+ struct iss_video_fh *vfh = to_iss_video_fh(file->private_data);
+
+ return vb2_poll(&vfh->queue, file, wait);
+}
+
+static int iss_video_mmap(struct file *file, struct vm_area_struct *vma)
+{
+ struct iss_video_fh *vfh = to_iss_video_fh(file->private_data);
+
+ return vb2_mmap(&vfh->queue, vma);
+}
+
+static struct v4l2_file_operations iss_video_fops = {
+ .owner = THIS_MODULE,
+ .unlocked_ioctl = video_ioctl2,
+ .open = iss_video_open,
+ .release = iss_video_release,
+ .poll = iss_video_poll,
+ .mmap = iss_video_mmap,
+};
+
+/* -----------------------------------------------------------------------------
+ * ISS video core
+ */
+
+static const struct iss_video_operations iss_video_dummy_ops = {
+};
+
+int omap4iss_video_init(struct iss_video *video, const char *name)
+{
+ const char *direction;
+ int ret;
+
+ switch (video->type) {
+ case V4L2_BUF_TYPE_VIDEO_CAPTURE:
+ direction = "output";
+ video->pad.flags = MEDIA_PAD_FL_SINK;
+ break;
+ case V4L2_BUF_TYPE_VIDEO_OUTPUT:
+ direction = "input";
+ video->pad.flags = MEDIA_PAD_FL_SOURCE;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ ret = media_entity_init(&video->video.entity, 1, &video->pad, 0);
+ if (ret < 0)
+ return ret;
+
+ mutex_init(&video->mutex);
+ atomic_set(&video->active, 0);
+
+ spin_lock_init(&video->pipe.lock);
+ mutex_init(&video->stream_lock);
+
+ /* Initialize the video device. */
+ if (video->ops == NULL)
+ video->ops = &iss_video_dummy_ops;
+
+ video->video.fops = &iss_video_fops;
+ snprintf(video->video.name, sizeof(video->video.name),
+ "OMAP4 ISS %s %s", name, direction);
+ video->video.vfl_type = VFL_TYPE_GRABBER;
+ video->video.release = video_device_release_empty;
+ video->video.ioctl_ops = &iss_video_ioctl_ops;
+ video->pipe.stream_state = ISS_PIPELINE_STREAM_STOPPED;
+
+ video_set_drvdata(&video->video, video);
+
+ return 0;
+}
+
+void omap4iss_video_cleanup(struct iss_video *video)
+{
+ media_entity_cleanup(&video->video.entity);
+ mutex_destroy(&video->stream_lock);
+ mutex_destroy(&video->mutex);
+}
+
+int omap4iss_video_register(struct iss_video *video, struct v4l2_device *vdev)
+{
+ int ret;
+
+ video->video.v4l2_dev = vdev;
+
+ ret = video_register_device(&video->video, VFL_TYPE_GRABBER, -1);
+ if (ret < 0)
+ dev_err(video->iss->dev,
+ "%s: could not register video device (%d)\n",
+ __func__, ret);
+
+ return ret;
+}
+
+void omap4iss_video_unregister(struct iss_video *video)
+{
+ video_unregister_device(&video->video);
+}
diff --git a/drivers/staging/media/omap4iss/iss_video.h b/drivers/staging/media/omap4iss/iss_video.h
new file mode 100644
index 00000000000..9dccdb154e1
--- /dev/null
+++ b/drivers/staging/media/omap4iss/iss_video.h
@@ -0,0 +1,204 @@
+/*
+ * TI OMAP4 ISS V4L2 Driver - Generic video node
+ *
+ * Copyright (C) 2012 Texas Instruments, Inc.
+ *
+ * Author: Sergio Aguirre <sergio.a.aguirre@gmail.com>
+ *
+ * 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.
+ */
+
+#ifndef OMAP4_ISS_VIDEO_H
+#define OMAP4_ISS_VIDEO_H
+
+#include <linux/v4l2-mediabus.h>
+#include <media/media-entity.h>
+#include <media/v4l2-dev.h>
+#include <media/v4l2-fh.h>
+#include <media/videobuf2-core.h>
+#include <media/videobuf2-dma-contig.h>
+
+#define ISS_VIDEO_DRIVER_NAME "issvideo"
+#define ISS_VIDEO_DRIVER_VERSION "0.0.2"
+
+struct iss_device;
+struct iss_video;
+struct v4l2_mbus_framefmt;
+struct v4l2_pix_format;
+
+/*
+ * struct iss_format_info - ISS media bus format information
+ * @code: V4L2 media bus format code
+ * @truncated: V4L2 media bus format code for the same format truncated to 10
+ * bits. Identical to @code if the format is 10 bits wide or less.
+ * @uncompressed: V4L2 media bus format code for the corresponding uncompressed
+ * format. Identical to @code if the format is not DPCM compressed.
+ * @flavor: V4L2 media bus format code for the same pixel layout but
+ * shifted to be 8 bits per pixel. =0 if format is not shiftable.
+ * @pixelformat: V4L2 pixel format FCC identifier
+ * @bpp: Bits per pixel
+ * @description: Human-readable format description
+ */
+struct iss_format_info {
+ enum v4l2_mbus_pixelcode code;
+ enum v4l2_mbus_pixelcode truncated;
+ enum v4l2_mbus_pixelcode uncompressed;
+ enum v4l2_mbus_pixelcode flavor;
+ u32 pixelformat;
+ unsigned int bpp;
+ const char *description;
+};
+
+enum iss_pipeline_stream_state {
+ ISS_PIPELINE_STREAM_STOPPED = 0,
+ ISS_PIPELINE_STREAM_CONTINUOUS = 1,
+ ISS_PIPELINE_STREAM_SINGLESHOT = 2,
+};
+
+enum iss_pipeline_state {
+ /* The stream has been started on the input video node. */
+ ISS_PIPELINE_STREAM_INPUT = 1,
+ /* The stream has been started on the output video node. */
+ ISS_PIPELINE_STREAM_OUTPUT = (1 << 1),
+ /* At least one buffer is queued on the input video node. */
+ ISS_PIPELINE_QUEUE_INPUT = (1 << 2),
+ /* At least one buffer is queued on the output video node. */
+ ISS_PIPELINE_QUEUE_OUTPUT = (1 << 3),
+ /* The input entity is idle, ready to be started. */
+ ISS_PIPELINE_IDLE_INPUT = (1 << 4),
+ /* The output entity is idle, ready to be started. */
+ ISS_PIPELINE_IDLE_OUTPUT = (1 << 5),
+ /* The pipeline is currently streaming. */
+ ISS_PIPELINE_STREAM = (1 << 6),
+};
+
+/*
+ * struct iss_pipeline - An OMAP4 ISS hardware pipeline
+ * @entities: Bitmask of entities in the pipeline (indexed by entity ID)
+ * @error: A hardware error occurred during capture
+ */
+struct iss_pipeline {
+ struct media_pipeline pipe;
+ spinlock_t lock; /* Pipeline state and queue flags */
+ unsigned int state;
+ enum iss_pipeline_stream_state stream_state;
+ struct iss_video *input;
+ struct iss_video *output;
+ unsigned int entities;
+ atomic_t frame_number;
+ bool do_propagation; /* of frame number */
+ bool error;
+ struct v4l2_fract max_timeperframe;
+ struct v4l2_subdev *external;
+ unsigned int external_rate;
+ int external_bpp;
+};
+
+#define to_iss_pipeline(__e) \
+ container_of((__e)->pipe, struct iss_pipeline, pipe)
+
+static inline int iss_pipeline_ready(struct iss_pipeline *pipe)
+{
+ return pipe->state == (ISS_PIPELINE_STREAM_INPUT |
+ ISS_PIPELINE_STREAM_OUTPUT |
+ ISS_PIPELINE_QUEUE_INPUT |
+ ISS_PIPELINE_QUEUE_OUTPUT |
+ ISS_PIPELINE_IDLE_INPUT |
+ ISS_PIPELINE_IDLE_OUTPUT);
+}
+
+/*
+ * struct iss_buffer - ISS buffer
+ * @buffer: ISS video buffer
+ * @iss_addr: Physical address of the buffer.
+ */
+struct iss_buffer {
+ /* common v4l buffer stuff -- must be first */
+ struct vb2_buffer vb;
+ struct list_head list;
+ dma_addr_t iss_addr;
+};
+
+#define to_iss_buffer(buf) container_of(buf, struct iss_buffer, buffer)
+
+enum iss_video_dmaqueue_flags {
+ /* Set if DMA queue becomes empty when ISS_PIPELINE_STREAM_CONTINUOUS */
+ ISS_VIDEO_DMAQUEUE_UNDERRUN = (1 << 0),
+ /* Set when queuing buffer to an empty DMA queue */
+ ISS_VIDEO_DMAQUEUE_QUEUED = (1 << 1),
+};
+
+#define iss_video_dmaqueue_flags_clr(video) \
+ ({ (video)->dmaqueue_flags = 0; })
+
+/*
+ * struct iss_video_operations - ISS video operations
+ * @queue: Resume streaming when a buffer is queued. Called on VIDIOC_QBUF
+ * if there was no buffer previously queued.
+ */
+struct iss_video_operations {
+ int (*queue)(struct iss_video *video, struct iss_buffer *buffer);
+};
+
+struct iss_video {
+ struct video_device video;
+ enum v4l2_buf_type type;
+ struct media_pad pad;
+
+ struct mutex mutex; /* format and crop settings */
+ atomic_t active;
+
+ struct iss_device *iss;
+
+ unsigned int capture_mem;
+ unsigned int bpl_alignment; /* alignment value */
+ unsigned int bpl_zero_padding; /* whether the alignment is optional */
+ unsigned int bpl_max; /* maximum bytes per line value */
+ unsigned int bpl_value; /* bytes per line value */
+ unsigned int bpl_padding; /* padding at end of line */
+
+ /* Pipeline state */
+ struct iss_pipeline pipe;
+ struct mutex stream_lock; /* pipeline and stream states */
+ bool error;
+
+ /* Video buffers queue */
+ struct vb2_queue *queue;
+ spinlock_t qlock; /* protects dmaqueue and error */
+ struct list_head dmaqueue;
+ enum iss_video_dmaqueue_flags dmaqueue_flags;
+ struct vb2_alloc_ctx *alloc_ctx;
+
+ const struct iss_video_operations *ops;
+};
+
+#define to_iss_video(vdev) container_of(vdev, struct iss_video, video)
+
+struct iss_video_fh {
+ struct v4l2_fh vfh;
+ struct iss_video *video;
+ struct vb2_queue queue;
+ struct v4l2_format format;
+ struct v4l2_fract timeperframe;
+};
+
+#define to_iss_video_fh(fh) container_of(fh, struct iss_video_fh, vfh)
+#define iss_video_queue_to_iss_video_fh(q) \
+ container_of(q, struct iss_video_fh, queue)
+
+int omap4iss_video_init(struct iss_video *video, const char *name);
+void omap4iss_video_cleanup(struct iss_video *video);
+int omap4iss_video_register(struct iss_video *video,
+ struct v4l2_device *vdev);
+void omap4iss_video_unregister(struct iss_video *video);
+struct iss_buffer *omap4iss_video_buffer_next(struct iss_video *video);
+void omap4iss_video_cancel_stream(struct iss_video *video);
+struct media_pad *omap4iss_video_remote_pad(struct iss_video *video);
+
+const struct iss_format_info *
+omap4iss_video_format_info(enum v4l2_mbus_pixelcode code);
+
+#endif /* OMAP4_ISS_VIDEO_H */
diff --git a/drivers/staging/media/rtl2832u_sdr/Kconfig b/drivers/staging/media/rtl2832u_sdr/Kconfig
new file mode 100644
index 00000000000..3ede5fe8f0a
--- /dev/null
+++ b/drivers/staging/media/rtl2832u_sdr/Kconfig
@@ -0,0 +1,7 @@
+config DVB_RTL2832_SDR
+ tristate "Realtek RTL2832 SDR"
+ depends on USB && DVB_CORE && I2C && VIDEO_V4L2 && DVB_USB_RTL28XXU
+ select DVB_RTL2832
+ select VIDEOBUF2_VMALLOC
+ default m if !MEDIA_SUBDRV_AUTOSELECT
+
diff --git a/drivers/staging/media/rtl2832u_sdr/Makefile b/drivers/staging/media/rtl2832u_sdr/Makefile
new file mode 100644
index 00000000000..7e00a0df463
--- /dev/null
+++ b/drivers/staging/media/rtl2832u_sdr/Makefile
@@ -0,0 +1,6 @@
+obj-$(CONFIG_DVB_RTL2832_SDR) += rtl2832_sdr.o
+
+ccflags-y += -Idrivers/media/dvb-core
+ccflags-y += -Idrivers/media/dvb-frontends
+ccflags-y += -Idrivers/media/tuners
+ccflags-y += -Idrivers/media/usb/dvb-usb-v2
diff --git a/drivers/staging/media/rtl2832u_sdr/rtl2832_sdr.c b/drivers/staging/media/rtl2832u_sdr/rtl2832_sdr.c
new file mode 100644
index 00000000000..093df6b6ae3
--- /dev/null
+++ b/drivers/staging/media/rtl2832u_sdr/rtl2832_sdr.c
@@ -0,0 +1,1497 @@
+/*
+ * Realtek RTL2832U SDR driver
+ *
+ * Copyright (C) 2013 Antti Palosaari <crope@iki.fi>
+ *
+ * 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.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * GNU Radio plugin "gr-kernel" for device usage will be on:
+ * http://git.linuxtv.org/anttip/gr-kernel.git
+ *
+ */
+
+#include "dvb_frontend.h"
+#include "rtl2832_sdr.h"
+#include "dvb_usb.h"
+
+#include <media/v4l2-device.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-event.h>
+#include <media/videobuf2-vmalloc.h>
+
+#include <linux/jiffies.h>
+#include <linux/math64.h>
+
+#define MAX_BULK_BUFS (10)
+#define BULK_BUFFER_SIZE (128 * 512)
+
+static const struct v4l2_frequency_band bands_adc[] = {
+ {
+ .tuner = 0,
+ .type = V4L2_TUNER_ADC,
+ .index = 0,
+ .capability = V4L2_TUNER_CAP_1HZ | V4L2_TUNER_CAP_FREQ_BANDS,
+ .rangelow = 300000,
+ .rangehigh = 300000,
+ },
+ {
+ .tuner = 0,
+ .type = V4L2_TUNER_ADC,
+ .index = 1,
+ .capability = V4L2_TUNER_CAP_1HZ | V4L2_TUNER_CAP_FREQ_BANDS,
+ .rangelow = 900001,
+ .rangehigh = 2800000,
+ },
+ {
+ .tuner = 0,
+ .type = V4L2_TUNER_ADC,
+ .index = 2,
+ .capability = V4L2_TUNER_CAP_1HZ | V4L2_TUNER_CAP_FREQ_BANDS,
+ .rangelow = 3200000,
+ .rangehigh = 3200000,
+ },
+};
+
+static const struct v4l2_frequency_band bands_fm[] = {
+ {
+ .tuner = 1,
+ .type = V4L2_TUNER_RF,
+ .index = 0,
+ .capability = V4L2_TUNER_CAP_1HZ | V4L2_TUNER_CAP_FREQ_BANDS,
+ .rangelow = 50000000,
+ .rangehigh = 2000000000,
+ },
+};
+
+/* stream formats */
+struct rtl2832_sdr_format {
+ char *name;
+ u32 pixelformat;
+};
+
+static struct rtl2832_sdr_format formats[] = {
+ {
+ .name = "IQ U8",
+ .pixelformat = V4L2_SDR_FMT_CU8,
+ }, {
+ .name = "IQ U16LE (emulated)",
+ .pixelformat = V4L2_SDR_FMT_CU16LE,
+ },
+};
+
+static const unsigned int NUM_FORMATS = ARRAY_SIZE(formats);
+
+/* intermediate buffers with raw data from the USB device */
+struct rtl2832_sdr_frame_buf {
+ struct vb2_buffer vb; /* common v4l buffer stuff -- must be first */
+ struct list_head list;
+};
+
+struct rtl2832_sdr_state {
+#define POWER_ON (1 << 1)
+#define URB_BUF (1 << 2)
+ unsigned long flags;
+
+ const struct rtl2832_config *cfg;
+ struct dvb_frontend *fe;
+ struct dvb_usb_device *d;
+ struct i2c_adapter *i2c;
+ u8 bank;
+
+ struct video_device vdev;
+ struct v4l2_device v4l2_dev;
+
+ /* videobuf2 queue and queued buffers list */
+ struct vb2_queue vb_queue;
+ struct list_head queued_bufs;
+ spinlock_t queued_bufs_lock; /* Protects queued_bufs */
+ unsigned sequence; /* buffer sequence counter */
+
+ /* Note if taking both locks v4l2_lock must always be locked first! */
+ struct mutex v4l2_lock; /* Protects everything else */
+ struct mutex vb_queue_lock; /* Protects vb_queue and capt_file */
+
+ /* Pointer to our usb_device, will be NULL after unplug */
+ struct usb_device *udev; /* Both mutexes most be hold when setting! */
+
+ unsigned int vb_full; /* vb is full and packets dropped */
+
+ struct urb *urb_list[MAX_BULK_BUFS];
+ int buf_num;
+ unsigned long buf_size;
+ u8 *buf_list[MAX_BULK_BUFS];
+ dma_addr_t dma_addr[MAX_BULK_BUFS];
+ int urbs_initialized;
+ int urbs_submitted;
+
+ unsigned int f_adc, f_tuner;
+ u32 pixelformat;
+
+ /* Controls */
+ struct v4l2_ctrl_handler hdl;
+ struct v4l2_ctrl *bandwidth_auto;
+ struct v4l2_ctrl *bandwidth;
+
+ /* for sample rate calc */
+ unsigned int sample;
+ unsigned int sample_measured;
+ unsigned long jiffies_next;
+};
+
+/* write multiple hardware registers */
+static int rtl2832_sdr_wr(struct rtl2832_sdr_state *s, u8 reg, const u8 *val,
+ int len)
+{
+ int ret;
+#define MAX_WR_LEN 24
+#define MAX_WR_XFER_LEN (MAX_WR_LEN + 1)
+ u8 buf[MAX_WR_XFER_LEN];
+ struct i2c_msg msg[1] = {
+ {
+ .addr = s->cfg->i2c_addr,
+ .flags = 0,
+ .len = 1 + len,
+ .buf = buf,
+ }
+ };
+
+ if (WARN_ON(len > MAX_WR_LEN))
+ return -EINVAL;
+
+ buf[0] = reg;
+ memcpy(&buf[1], val, len);
+
+ ret = i2c_transfer(s->i2c, msg, 1);
+ if (ret == 1) {
+ ret = 0;
+ } else {
+ dev_err(&s->i2c->dev,
+ "%s: I2C wr failed=%d reg=%02x len=%d\n",
+ KBUILD_MODNAME, ret, reg, len);
+ ret = -EREMOTEIO;
+ }
+ return ret;
+}
+
+/* read multiple hardware registers */
+static int rtl2832_sdr_rd(struct rtl2832_sdr_state *s, u8 reg, u8 *val, int len)
+{
+ int ret;
+ struct i2c_msg msg[2] = {
+ {
+ .addr = s->cfg->i2c_addr,
+ .flags = 0,
+ .len = 1,
+ .buf = &reg,
+ }, {
+ .addr = s->cfg->i2c_addr,
+ .flags = I2C_M_RD,
+ .len = len,
+ .buf = val,
+ }
+ };
+
+ ret = i2c_transfer(s->i2c, msg, 2);
+ if (ret == 2) {
+ ret = 0;
+ } else {
+ dev_err(&s->i2c->dev,
+ "%s: I2C rd failed=%d reg=%02x len=%d\n",
+ KBUILD_MODNAME, ret, reg, len);
+ ret = -EREMOTEIO;
+ }
+ return ret;
+}
+
+/* write multiple registers */
+static int rtl2832_sdr_wr_regs(struct rtl2832_sdr_state *s, u16 reg,
+ const u8 *val, int len)
+{
+ int ret;
+ u8 reg2 = (reg >> 0) & 0xff;
+ u8 bank = (reg >> 8) & 0xff;
+
+ /* switch bank if needed */
+ if (bank != s->bank) {
+ ret = rtl2832_sdr_wr(s, 0x00, &bank, 1);
+ if (ret)
+ return ret;
+
+ s->bank = bank;
+ }
+
+ return rtl2832_sdr_wr(s, reg2, val, len);
+}
+
+/* read multiple registers */
+static int rtl2832_sdr_rd_regs(struct rtl2832_sdr_state *s, u16 reg, u8 *val,
+ int len)
+{
+ int ret;
+ u8 reg2 = (reg >> 0) & 0xff;
+ u8 bank = (reg >> 8) & 0xff;
+
+ /* switch bank if needed */
+ if (bank != s->bank) {
+ ret = rtl2832_sdr_wr(s, 0x00, &bank, 1);
+ if (ret)
+ return ret;
+
+ s->bank = bank;
+ }
+
+ return rtl2832_sdr_rd(s, reg2, val, len);
+}
+
+/* write single register */
+static int rtl2832_sdr_wr_reg(struct rtl2832_sdr_state *s, u16 reg, u8 val)
+{
+ return rtl2832_sdr_wr_regs(s, reg, &val, 1);
+}
+
+#if 0
+/* read single register */
+static int rtl2832_sdr_rd_reg(struct rtl2832_sdr_state *s, u16 reg, u8 *val)
+{
+ return rtl2832_sdr_rd_regs(s, reg, val, 1);
+}
+#endif
+
+/* write single register with mask */
+static int rtl2832_sdr_wr_reg_mask(struct rtl2832_sdr_state *s, u16 reg,
+ u8 val, u8 mask)
+{
+ int ret;
+ u8 tmp;
+
+ /* no need for read if whole reg is written */
+ if (mask != 0xff) {
+ ret = rtl2832_sdr_rd_regs(s, reg, &tmp, 1);
+ if (ret)
+ return ret;
+
+ val &= mask;
+ tmp &= ~mask;
+ val |= tmp;
+ }
+
+ return rtl2832_sdr_wr_regs(s, reg, &val, 1);
+}
+
+#if 0
+/* read single register with mask */
+static int rtl2832_sdr_rd_reg_mask(struct rtl2832_sdr_state *s, u16 reg,
+ u8 *val, u8 mask)
+{
+ int ret, i;
+ u8 tmp;
+
+ ret = rtl2832_sdr_rd_regs(s, reg, &tmp, 1);
+ if (ret)
+ return ret;
+
+ tmp &= mask;
+
+ /* find position of the first bit */
+ for (i = 0; i < 8; i++) {
+ if ((mask >> i) & 0x01)
+ break;
+ }
+ *val = tmp >> i;
+
+ return 0;
+}
+#endif
+
+/* Private functions */
+static struct rtl2832_sdr_frame_buf *rtl2832_sdr_get_next_fill_buf(
+ struct rtl2832_sdr_state *s)
+{
+ unsigned long flags = 0;
+ struct rtl2832_sdr_frame_buf *buf = NULL;
+
+ spin_lock_irqsave(&s->queued_bufs_lock, flags);
+ if (list_empty(&s->queued_bufs))
+ goto leave;
+
+ buf = list_entry(s->queued_bufs.next,
+ struct rtl2832_sdr_frame_buf, list);
+ list_del(&buf->list);
+leave:
+ spin_unlock_irqrestore(&s->queued_bufs_lock, flags);
+ return buf;
+}
+
+static unsigned int rtl2832_sdr_convert_stream(struct rtl2832_sdr_state *s,
+ void *dst, const u8 *src, unsigned int src_len)
+{
+ unsigned int dst_len;
+
+ if (s->pixelformat == V4L2_SDR_FMT_CU8) {
+ /* native stream, no need to convert */
+ memcpy(dst, src, src_len);
+ dst_len = src_len;
+ } else if (s->pixelformat == V4L2_SDR_FMT_CU16LE) {
+ /* convert u8 to u16 */
+ unsigned int i;
+ u16 *u16dst = dst;
+ for (i = 0; i < src_len; i++)
+ *u16dst++ = (src[i] << 8) | (src[i] >> 0);
+ dst_len = 2 * src_len;
+ } else {
+ dst_len = 0;
+ }
+
+ /* calculate samping rate and output it in 10 seconds intervals */
+ if (unlikely(time_is_before_jiffies(s->jiffies_next))) {
+#define MSECS 10000UL
+ unsigned int samples = s->sample - s->sample_measured;
+ s->jiffies_next = jiffies + msecs_to_jiffies(MSECS);
+ s->sample_measured = s->sample;
+ dev_dbg(&s->udev->dev,
+ "slen=%d samples=%u msecs=%lu sampling rate=%lu\n",
+ src_len, samples, MSECS,
+ samples * 1000UL / MSECS);
+ }
+
+ /* total number of I+Q pairs */
+ s->sample += src_len / 2;
+
+ return dst_len;
+}
+
+/*
+ * This gets called for the bulk stream pipe. This is done in interrupt
+ * time, so it has to be fast, not crash, and not stall. Neat.
+ */
+static void rtl2832_sdr_urb_complete(struct urb *urb)
+{
+ struct rtl2832_sdr_state *s = urb->context;
+ struct rtl2832_sdr_frame_buf *fbuf;
+
+ dev_dbg_ratelimited(&s->udev->dev,
+ "%s: status=%d length=%d/%d errors=%d\n",
+ __func__, urb->status, urb->actual_length,
+ urb->transfer_buffer_length, urb->error_count);
+
+ switch (urb->status) {
+ case 0: /* success */
+ case -ETIMEDOUT: /* NAK */
+ break;
+ case -ECONNRESET: /* kill */
+ case -ENOENT:
+ case -ESHUTDOWN:
+ return;
+ default: /* error */
+ dev_err_ratelimited(&s->udev->dev, "urb failed=%d\n",
+ urb->status);
+ break;
+ }
+
+ if (likely(urb->actual_length > 0)) {
+ void *ptr;
+ unsigned int len;
+ /* get free framebuffer */
+ fbuf = rtl2832_sdr_get_next_fill_buf(s);
+ if (unlikely(fbuf == NULL)) {
+ s->vb_full++;
+ dev_notice_ratelimited(&s->udev->dev,
+ "videobuf is full, %d packets dropped\n",
+ s->vb_full);
+ goto skip;
+ }
+
+ /* fill framebuffer */
+ ptr = vb2_plane_vaddr(&fbuf->vb, 0);
+ len = rtl2832_sdr_convert_stream(s, ptr, urb->transfer_buffer,
+ urb->actual_length);
+ vb2_set_plane_payload(&fbuf->vb, 0, len);
+ v4l2_get_timestamp(&fbuf->vb.v4l2_buf.timestamp);
+ fbuf->vb.v4l2_buf.sequence = s->sequence++;
+ vb2_buffer_done(&fbuf->vb, VB2_BUF_STATE_DONE);
+ }
+skip:
+ usb_submit_urb(urb, GFP_ATOMIC);
+}
+
+static int rtl2832_sdr_kill_urbs(struct rtl2832_sdr_state *s)
+{
+ int i;
+
+ for (i = s->urbs_submitted - 1; i >= 0; i--) {
+ dev_dbg(&s->udev->dev, "%s: kill urb=%d\n", __func__, i);
+ /* stop the URB */
+ usb_kill_urb(s->urb_list[i]);
+ }
+ s->urbs_submitted = 0;
+
+ return 0;
+}
+
+static int rtl2832_sdr_submit_urbs(struct rtl2832_sdr_state *s)
+{
+ int i, ret;
+
+ for (i = 0; i < s->urbs_initialized; i++) {
+ dev_dbg(&s->udev->dev, "%s: submit urb=%d\n", __func__, i);
+ ret = usb_submit_urb(s->urb_list[i], GFP_ATOMIC);
+ if (ret) {
+ dev_err(&s->udev->dev,
+ "Could not submit urb no. %d - get them all back\n",
+ i);
+ rtl2832_sdr_kill_urbs(s);
+ return ret;
+ }
+ s->urbs_submitted++;
+ }
+
+ return 0;
+}
+
+static int rtl2832_sdr_free_stream_bufs(struct rtl2832_sdr_state *s)
+{
+ if (s->flags & USB_STATE_URB_BUF) {
+ while (s->buf_num) {
+ s->buf_num--;
+ dev_dbg(&s->udev->dev, "%s: free buf=%d\n",
+ __func__, s->buf_num);
+ usb_free_coherent(s->udev, s->buf_size,
+ s->buf_list[s->buf_num],
+ s->dma_addr[s->buf_num]);
+ }
+ }
+ s->flags &= ~USB_STATE_URB_BUF;
+
+ return 0;
+}
+
+static int rtl2832_sdr_alloc_stream_bufs(struct rtl2832_sdr_state *s)
+{
+ s->buf_num = 0;
+ s->buf_size = BULK_BUFFER_SIZE;
+
+ dev_dbg(&s->udev->dev,
+ "%s: all in all I will use %u bytes for streaming\n",
+ __func__, MAX_BULK_BUFS * BULK_BUFFER_SIZE);
+
+ for (s->buf_num = 0; s->buf_num < MAX_BULK_BUFS; s->buf_num++) {
+ s->buf_list[s->buf_num] = usb_alloc_coherent(s->udev,
+ BULK_BUFFER_SIZE, GFP_ATOMIC,
+ &s->dma_addr[s->buf_num]);
+ if (!s->buf_list[s->buf_num]) {
+ dev_dbg(&s->udev->dev, "%s: alloc buf=%d failed\n",
+ __func__, s->buf_num);
+ rtl2832_sdr_free_stream_bufs(s);
+ return -ENOMEM;
+ }
+
+ dev_dbg(&s->udev->dev, "%s: alloc buf=%d %p (dma %llu)\n",
+ __func__, s->buf_num,
+ s->buf_list[s->buf_num],
+ (long long)s->dma_addr[s->buf_num]);
+ s->flags |= USB_STATE_URB_BUF;
+ }
+
+ return 0;
+}
+
+static int rtl2832_sdr_free_urbs(struct rtl2832_sdr_state *s)
+{
+ int i;
+
+ rtl2832_sdr_kill_urbs(s);
+
+ for (i = s->urbs_initialized - 1; i >= 0; i--) {
+ if (s->urb_list[i]) {
+ dev_dbg(&s->udev->dev, "%s: free urb=%d\n",
+ __func__, i);
+ /* free the URBs */
+ usb_free_urb(s->urb_list[i]);
+ }
+ }
+ s->urbs_initialized = 0;
+
+ return 0;
+}
+
+static int rtl2832_sdr_alloc_urbs(struct rtl2832_sdr_state *s)
+{
+ int i, j;
+
+ /* allocate the URBs */
+ for (i = 0; i < MAX_BULK_BUFS; i++) {
+ dev_dbg(&s->udev->dev, "%s: alloc urb=%d\n", __func__, i);
+ s->urb_list[i] = usb_alloc_urb(0, GFP_ATOMIC);
+ if (!s->urb_list[i]) {
+ dev_dbg(&s->udev->dev, "%s: failed\n", __func__);
+ for (j = 0; j < i; j++)
+ usb_free_urb(s->urb_list[j]);
+ return -ENOMEM;
+ }
+ usb_fill_bulk_urb(s->urb_list[i],
+ s->udev,
+ usb_rcvbulkpipe(s->udev, 0x81),
+ s->buf_list[i],
+ BULK_BUFFER_SIZE,
+ rtl2832_sdr_urb_complete, s);
+
+ s->urb_list[i]->transfer_flags = URB_NO_TRANSFER_DMA_MAP;
+ s->urb_list[i]->transfer_dma = s->dma_addr[i];
+ s->urbs_initialized++;
+ }
+
+ return 0;
+}
+
+/* Must be called with vb_queue_lock hold */
+static void rtl2832_sdr_cleanup_queued_bufs(struct rtl2832_sdr_state *s)
+{
+ unsigned long flags = 0;
+ dev_dbg(&s->udev->dev, "%s:\n", __func__);
+
+ spin_lock_irqsave(&s->queued_bufs_lock, flags);
+ while (!list_empty(&s->queued_bufs)) {
+ struct rtl2832_sdr_frame_buf *buf;
+ buf = list_entry(s->queued_bufs.next,
+ struct rtl2832_sdr_frame_buf, list);
+ list_del(&buf->list);
+ vb2_buffer_done(&buf->vb, VB2_BUF_STATE_ERROR);
+ }
+ spin_unlock_irqrestore(&s->queued_bufs_lock, flags);
+}
+
+/* The user yanked out the cable... */
+static void rtl2832_sdr_release_sec(struct dvb_frontend *fe)
+{
+ struct rtl2832_sdr_state *s = fe->sec_priv;
+ dev_dbg(&s->udev->dev, "%s:\n", __func__);
+
+ mutex_lock(&s->vb_queue_lock);
+ mutex_lock(&s->v4l2_lock);
+ /* No need to keep the urbs around after disconnection */
+ s->udev = NULL;
+
+ v4l2_device_disconnect(&s->v4l2_dev);
+ video_unregister_device(&s->vdev);
+ mutex_unlock(&s->v4l2_lock);
+ mutex_unlock(&s->vb_queue_lock);
+
+ v4l2_device_put(&s->v4l2_dev);
+
+ fe->sec_priv = NULL;
+}
+
+static int rtl2832_sdr_querycap(struct file *file, void *fh,
+ struct v4l2_capability *cap)
+{
+ struct rtl2832_sdr_state *s = video_drvdata(file);
+ dev_dbg(&s->udev->dev, "%s:\n", __func__);
+
+ strlcpy(cap->driver, KBUILD_MODNAME, sizeof(cap->driver));
+ strlcpy(cap->card, s->vdev.name, sizeof(cap->card));
+ usb_make_path(s->udev, cap->bus_info, sizeof(cap->bus_info));
+ cap->device_caps = V4L2_CAP_SDR_CAPTURE | V4L2_CAP_STREAMING |
+ V4L2_CAP_READWRITE | V4L2_CAP_TUNER;
+ cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS;
+ return 0;
+}
+
+/* Videobuf2 operations */
+static int rtl2832_sdr_queue_setup(struct vb2_queue *vq,
+ const struct v4l2_format *fmt, unsigned int *nbuffers,
+ unsigned int *nplanes, unsigned int sizes[], void *alloc_ctxs[])
+{
+ struct rtl2832_sdr_state *s = vb2_get_drv_priv(vq);
+ dev_dbg(&s->udev->dev, "%s: *nbuffers=%d\n", __func__, *nbuffers);
+
+ /* Need at least 8 buffers */
+ if (vq->num_buffers + *nbuffers < 8)
+ *nbuffers = 8 - vq->num_buffers;
+ *nplanes = 1;
+ /* 2 = max 16-bit sample returned */
+ sizes[0] = PAGE_ALIGN(BULK_BUFFER_SIZE * 2);
+ dev_dbg(&s->udev->dev, "%s: nbuffers=%d sizes[0]=%d\n",
+ __func__, *nbuffers, sizes[0]);
+ return 0;
+}
+
+static int rtl2832_sdr_buf_prepare(struct vb2_buffer *vb)
+{
+ struct rtl2832_sdr_state *s = vb2_get_drv_priv(vb->vb2_queue);
+
+ /* Don't allow queing new buffers after device disconnection */
+ if (!s->udev)
+ return -ENODEV;
+
+ return 0;
+}
+
+static void rtl2832_sdr_buf_queue(struct vb2_buffer *vb)
+{
+ struct rtl2832_sdr_state *s = vb2_get_drv_priv(vb->vb2_queue);
+ struct rtl2832_sdr_frame_buf *buf =
+ container_of(vb, struct rtl2832_sdr_frame_buf, vb);
+ unsigned long flags = 0;
+
+ /* Check the device has not disconnected between prep and queuing */
+ if (!s->udev) {
+ vb2_buffer_done(&buf->vb, VB2_BUF_STATE_ERROR);
+ return;
+ }
+
+ spin_lock_irqsave(&s->queued_bufs_lock, flags);
+ list_add_tail(&buf->list, &s->queued_bufs);
+ spin_unlock_irqrestore(&s->queued_bufs_lock, flags);
+}
+
+static int rtl2832_sdr_set_adc(struct rtl2832_sdr_state *s)
+{
+ struct dvb_frontend *fe = s->fe;
+ int ret;
+ unsigned int f_sr, f_if;
+ u8 buf[4], u8tmp1, u8tmp2;
+ u64 u64tmp;
+ u32 u32tmp;
+ dev_dbg(&s->udev->dev, "%s: f_adc=%u\n", __func__, s->f_adc);
+
+ if (!test_bit(POWER_ON, &s->flags))
+ return 0;
+
+ if (s->f_adc == 0)
+ return 0;
+
+ f_sr = s->f_adc;
+
+ ret = rtl2832_sdr_wr_regs(s, 0x13e, "\x00\x00", 2);
+ if (ret)
+ goto err;
+
+ ret = rtl2832_sdr_wr_regs(s, 0x115, "\x00\x00\x00\x00", 4);
+ if (ret)
+ goto err;
+
+ /* get IF from tuner */
+ if (fe->ops.tuner_ops.get_if_frequency)
+ ret = fe->ops.tuner_ops.get_if_frequency(fe, &f_if);
+ else
+ ret = -EINVAL;
+
+ if (ret)
+ goto err;
+
+ /* program IF */
+ u64tmp = f_if % s->cfg->xtal;
+ u64tmp *= 0x400000;
+ u64tmp = div_u64(u64tmp, s->cfg->xtal);
+ u64tmp = -u64tmp;
+ u32tmp = u64tmp & 0x3fffff;
+
+ dev_dbg(&s->udev->dev, "%s: f_if=%u if_ctl=%08x\n",
+ __func__, f_if, u32tmp);
+
+ buf[0] = (u32tmp >> 16) & 0xff;
+ buf[1] = (u32tmp >> 8) & 0xff;
+ buf[2] = (u32tmp >> 0) & 0xff;
+
+ ret = rtl2832_sdr_wr_regs(s, 0x119, buf, 3);
+ if (ret)
+ goto err;
+
+ /* BB / IF mode */
+ /* POR: 0x1b1=0x1f, 0x008=0x0d, 0x006=0x80 */
+ if (f_if) {
+ u8tmp1 = 0x1a; /* disable Zero-IF */
+ u8tmp2 = 0x8d; /* enable ADC I */
+ } else {
+ u8tmp1 = 0x1b; /* enable Zero-IF, DC, IQ */
+ u8tmp2 = 0xcd; /* enable ADC I, ADC Q */
+ }
+
+ ret = rtl2832_sdr_wr_reg(s, 0x1b1, u8tmp1);
+ if (ret)
+ goto err;
+
+ ret = rtl2832_sdr_wr_reg(s, 0x008, u8tmp2);
+ if (ret)
+ goto err;
+
+ ret = rtl2832_sdr_wr_reg(s, 0x006, 0x80);
+ if (ret)
+ goto err;
+
+ /* program sampling rate (resampling down) */
+ u32tmp = div_u64(s->cfg->xtal * 0x400000ULL, f_sr * 4U);
+ u32tmp <<= 2;
+ buf[0] = (u32tmp >> 24) & 0xff;
+ buf[1] = (u32tmp >> 16) & 0xff;
+ buf[2] = (u32tmp >> 8) & 0xff;
+ buf[3] = (u32tmp >> 0) & 0xff;
+ ret = rtl2832_sdr_wr_regs(s, 0x19f, buf, 4);
+ if (ret)
+ goto err;
+
+ /* low-pass filter */
+ ret = rtl2832_sdr_wr_regs(s, 0x11c,
+ "\xca\xdc\xd7\xd8\xe0\xf2\x0e\x35\x06\x50\x9c\x0d\x71\x11\x14\x71\x74\x19\x41\xa5",
+ 20);
+ if (ret)
+ goto err;
+
+ ret = rtl2832_sdr_wr_regs(s, 0x017, "\x11\x10", 2);
+ if (ret)
+ goto err;
+
+ /* mode */
+ ret = rtl2832_sdr_wr_regs(s, 0x019, "\x05", 1);
+ if (ret)
+ goto err;
+
+ ret = rtl2832_sdr_wr_regs(s, 0x01a, "\x1b\x16\x0d\x06\x01\xff", 6);
+ if (ret)
+ goto err;
+
+ /* FSM */
+ ret = rtl2832_sdr_wr_regs(s, 0x192, "\x00\xf0\x0f", 3);
+ if (ret)
+ goto err;
+
+ /* PID filter */
+ ret = rtl2832_sdr_wr_regs(s, 0x061, "\x60", 1);
+ if (ret)
+ goto err;
+
+ /* used RF tuner based settings */
+ switch (s->cfg->tuner) {
+ case RTL2832_TUNER_E4000:
+ ret = rtl2832_sdr_wr_regs(s, 0x112, "\x5a", 1);
+ ret = rtl2832_sdr_wr_regs(s, 0x102, "\x40", 1);
+ ret = rtl2832_sdr_wr_regs(s, 0x103, "\x5a", 1);
+ ret = rtl2832_sdr_wr_regs(s, 0x1c7, "\x30", 1);
+ ret = rtl2832_sdr_wr_regs(s, 0x104, "\xd0", 1);
+ ret = rtl2832_sdr_wr_regs(s, 0x105, "\xbe", 1);
+ ret = rtl2832_sdr_wr_regs(s, 0x1c8, "\x18", 1);
+ ret = rtl2832_sdr_wr_regs(s, 0x106, "\x35", 1);
+ ret = rtl2832_sdr_wr_regs(s, 0x1c9, "\x21", 1);
+ ret = rtl2832_sdr_wr_regs(s, 0x1ca, "\x21", 1);
+ ret = rtl2832_sdr_wr_regs(s, 0x1cb, "\x00", 1);
+ ret = rtl2832_sdr_wr_regs(s, 0x107, "\x40", 1);
+ ret = rtl2832_sdr_wr_regs(s, 0x1cd, "\x10", 1);
+ ret = rtl2832_sdr_wr_regs(s, 0x1ce, "\x10", 1);
+ ret = rtl2832_sdr_wr_regs(s, 0x108, "\x80", 1);
+ ret = rtl2832_sdr_wr_regs(s, 0x109, "\x7f", 1);
+ ret = rtl2832_sdr_wr_regs(s, 0x10a, "\x80", 1);
+ ret = rtl2832_sdr_wr_regs(s, 0x10b, "\x7f", 1);
+ ret = rtl2832_sdr_wr_regs(s, 0x00e, "\xfc", 1);
+ ret = rtl2832_sdr_wr_regs(s, 0x00e, "\xfc", 1);
+ ret = rtl2832_sdr_wr_regs(s, 0x011, "\xd4", 1);
+ ret = rtl2832_sdr_wr_regs(s, 0x1e5, "\xf0", 1);
+ ret = rtl2832_sdr_wr_regs(s, 0x1d9, "\x00", 1);
+ ret = rtl2832_sdr_wr_regs(s, 0x1db, "\x00", 1);
+ ret = rtl2832_sdr_wr_regs(s, 0x1dd, "\x14", 1);
+ ret = rtl2832_sdr_wr_regs(s, 0x1de, "\xec", 1);
+ ret = rtl2832_sdr_wr_regs(s, 0x1d8, "\x0c", 1);
+ ret = rtl2832_sdr_wr_regs(s, 0x1e6, "\x02", 1);
+ ret = rtl2832_sdr_wr_regs(s, 0x1d7, "\x09", 1);
+ ret = rtl2832_sdr_wr_regs(s, 0x00d, "\x83", 1);
+ ret = rtl2832_sdr_wr_regs(s, 0x010, "\x49", 1);
+ ret = rtl2832_sdr_wr_regs(s, 0x00d, "\x87", 1);
+ ret = rtl2832_sdr_wr_regs(s, 0x00d, "\x85", 1);
+ ret = rtl2832_sdr_wr_regs(s, 0x013, "\x02", 1);
+ break;
+ case RTL2832_TUNER_FC0012:
+ case RTL2832_TUNER_FC0013:
+ ret = rtl2832_sdr_wr_regs(s, 0x112, "\x5a", 1);
+ ret = rtl2832_sdr_wr_regs(s, 0x102, "\x40", 1);
+ ret = rtl2832_sdr_wr_regs(s, 0x103, "\x5a", 1);
+ ret = rtl2832_sdr_wr_regs(s, 0x1c7, "\x2c", 1);
+ ret = rtl2832_sdr_wr_regs(s, 0x104, "\xcc", 1);
+ ret = rtl2832_sdr_wr_regs(s, 0x105, "\xbe", 1);
+ ret = rtl2832_sdr_wr_regs(s, 0x1c8, "\x16", 1);
+ ret = rtl2832_sdr_wr_regs(s, 0x106, "\x35", 1);
+ ret = rtl2832_sdr_wr_regs(s, 0x1c9, "\x21", 1);
+ ret = rtl2832_sdr_wr_regs(s, 0x1ca, "\x21", 1);
+ ret = rtl2832_sdr_wr_regs(s, 0x1cb, "\x00", 1);
+ ret = rtl2832_sdr_wr_regs(s, 0x107, "\x40", 1);
+ ret = rtl2832_sdr_wr_regs(s, 0x1cd, "\x10", 1);
+ ret = rtl2832_sdr_wr_regs(s, 0x1ce, "\x10", 1);
+ ret = rtl2832_sdr_wr_regs(s, 0x108, "\x80", 1);
+ ret = rtl2832_sdr_wr_regs(s, 0x109, "\x7f", 1);
+ ret = rtl2832_sdr_wr_regs(s, 0x10a, "\x80", 1);
+ ret = rtl2832_sdr_wr_regs(s, 0x10b, "\x7f", 1);
+ ret = rtl2832_sdr_wr_regs(s, 0x00e, "\xfc", 1);
+ ret = rtl2832_sdr_wr_regs(s, 0x00e, "\xfc", 1);
+ ret = rtl2832_sdr_wr_regs(s, 0x011, "\xe9\xbf", 2);
+ ret = rtl2832_sdr_wr_regs(s, 0x1e5, "\xf0", 1);
+ ret = rtl2832_sdr_wr_regs(s, 0x1d9, "\x00", 1);
+ ret = rtl2832_sdr_wr_regs(s, 0x1db, "\x00", 1);
+ ret = rtl2832_sdr_wr_regs(s, 0x1dd, "\x11", 1);
+ ret = rtl2832_sdr_wr_regs(s, 0x1de, "\xef", 1);
+ ret = rtl2832_sdr_wr_regs(s, 0x1d8, "\x0c", 1);
+ ret = rtl2832_sdr_wr_regs(s, 0x1e6, "\x02", 1);
+ ret = rtl2832_sdr_wr_regs(s, 0x1d7, "\x09", 1);
+ break;
+ case RTL2832_TUNER_R820T:
+ ret = rtl2832_sdr_wr_regs(s, 0x112, "\x5a", 1);
+ ret = rtl2832_sdr_wr_regs(s, 0x102, "\x40", 1);
+ ret = rtl2832_sdr_wr_regs(s, 0x115, "\x01", 1);
+ ret = rtl2832_sdr_wr_regs(s, 0x103, "\x80", 1);
+ ret = rtl2832_sdr_wr_regs(s, 0x1c7, "\x24", 1);
+ ret = rtl2832_sdr_wr_regs(s, 0x104, "\xcc", 1);
+ ret = rtl2832_sdr_wr_regs(s, 0x105, "\xbe", 1);
+ ret = rtl2832_sdr_wr_regs(s, 0x1c8, "\x14", 1);
+ ret = rtl2832_sdr_wr_regs(s, 0x106, "\x35", 1);
+ ret = rtl2832_sdr_wr_regs(s, 0x1c9, "\x21", 1);
+ ret = rtl2832_sdr_wr_regs(s, 0x1ca, "\x21", 1);
+ ret = rtl2832_sdr_wr_regs(s, 0x1cb, "\x00", 1);
+ ret = rtl2832_sdr_wr_regs(s, 0x107, "\x40", 1);
+ ret = rtl2832_sdr_wr_regs(s, 0x1cd, "\x10", 1);
+ ret = rtl2832_sdr_wr_regs(s, 0x1ce, "\x10", 1);
+ ret = rtl2832_sdr_wr_regs(s, 0x108, "\x80", 1);
+ ret = rtl2832_sdr_wr_regs(s, 0x109, "\x7f", 1);
+ ret = rtl2832_sdr_wr_regs(s, 0x10a, "\x80", 1);
+ ret = rtl2832_sdr_wr_regs(s, 0x10b, "\x7f", 1);
+ ret = rtl2832_sdr_wr_regs(s, 0x00e, "\xfc", 1);
+ ret = rtl2832_sdr_wr_regs(s, 0x00e, "\xfc", 1);
+ ret = rtl2832_sdr_wr_regs(s, 0x011, "\xf4", 1);
+ break;
+ default:
+ dev_notice(&s->udev->dev, "Unsupported tuner\n");
+ }
+
+ /* software reset */
+ ret = rtl2832_sdr_wr_reg_mask(s, 0x101, 0x04, 0x04);
+ if (ret)
+ goto err;
+
+ ret = rtl2832_sdr_wr_reg_mask(s, 0x101, 0x00, 0x04);
+ if (ret)
+ goto err;
+err:
+ return ret;
+};
+
+static void rtl2832_sdr_unset_adc(struct rtl2832_sdr_state *s)
+{
+ int ret;
+
+ dev_dbg(&s->udev->dev, "%s:\n", __func__);
+
+ /* PID filter */
+ ret = rtl2832_sdr_wr_regs(s, 0x061, "\xe0", 1);
+ if (ret)
+ goto err;
+
+ /* mode */
+ ret = rtl2832_sdr_wr_regs(s, 0x019, "\x20", 1);
+ if (ret)
+ goto err;
+
+ ret = rtl2832_sdr_wr_regs(s, 0x017, "\x11\x10", 2);
+ if (ret)
+ goto err;
+
+ /* FSM */
+ ret = rtl2832_sdr_wr_regs(s, 0x192, "\x00\x0f\xff", 3);
+ if (ret)
+ goto err;
+
+ ret = rtl2832_sdr_wr_regs(s, 0x13e, "\x40\x00", 2);
+ if (ret)
+ goto err;
+
+ ret = rtl2832_sdr_wr_regs(s, 0x115, "\x06\x3f\xce\xcc", 4);
+ if (ret)
+ goto err;
+err:
+ return;
+};
+
+static int rtl2832_sdr_set_tuner_freq(struct rtl2832_sdr_state *s)
+{
+ struct dvb_frontend *fe = s->fe;
+ struct dtv_frontend_properties *c = &fe->dtv_property_cache;
+ struct v4l2_ctrl *bandwidth_auto;
+ struct v4l2_ctrl *bandwidth;
+
+ /*
+ * tuner RF (Hz)
+ */
+ if (s->f_tuner == 0)
+ return 0;
+
+ /*
+ * bandwidth (Hz)
+ */
+ bandwidth_auto = v4l2_ctrl_find(&s->hdl, V4L2_CID_RF_TUNER_BANDWIDTH_AUTO);
+ bandwidth = v4l2_ctrl_find(&s->hdl, V4L2_CID_RF_TUNER_BANDWIDTH);
+ if (v4l2_ctrl_g_ctrl(bandwidth_auto)) {
+ c->bandwidth_hz = s->f_adc;
+ v4l2_ctrl_s_ctrl(bandwidth, s->f_adc);
+ } else {
+ c->bandwidth_hz = v4l2_ctrl_g_ctrl(bandwidth);
+ }
+
+ c->frequency = s->f_tuner;
+ c->delivery_system = SYS_DVBT;
+
+ dev_dbg(&s->udev->dev, "%s: frequency=%u bandwidth=%d\n",
+ __func__, c->frequency, c->bandwidth_hz);
+
+ if (!test_bit(POWER_ON, &s->flags))
+ return 0;
+
+ if (fe->ops.tuner_ops.set_params)
+ fe->ops.tuner_ops.set_params(fe);
+
+ return 0;
+};
+
+static int rtl2832_sdr_set_tuner(struct rtl2832_sdr_state *s)
+{
+ struct dvb_frontend *fe = s->fe;
+
+ dev_dbg(&s->udev->dev, "%s:\n", __func__);
+
+ if (fe->ops.tuner_ops.init)
+ fe->ops.tuner_ops.init(fe);
+
+ return 0;
+};
+
+static void rtl2832_sdr_unset_tuner(struct rtl2832_sdr_state *s)
+{
+ struct dvb_frontend *fe = s->fe;
+
+ dev_dbg(&s->udev->dev, "%s:\n", __func__);
+
+ if (fe->ops.tuner_ops.sleep)
+ fe->ops.tuner_ops.sleep(fe);
+
+ return;
+};
+
+static int rtl2832_sdr_start_streaming(struct vb2_queue *vq, unsigned int count)
+{
+ struct rtl2832_sdr_state *s = vb2_get_drv_priv(vq);
+ int ret;
+ dev_dbg(&s->udev->dev, "%s:\n", __func__);
+
+ if (!s->udev)
+ return -ENODEV;
+
+ if (mutex_lock_interruptible(&s->v4l2_lock))
+ return -ERESTARTSYS;
+
+ if (s->d->props->power_ctrl)
+ s->d->props->power_ctrl(s->d, 1);
+
+ set_bit(POWER_ON, &s->flags);
+
+ ret = rtl2832_sdr_set_tuner(s);
+ if (ret)
+ goto err;
+
+ ret = rtl2832_sdr_set_tuner_freq(s);
+ if (ret)
+ goto err;
+
+ ret = rtl2832_sdr_set_adc(s);
+ if (ret)
+ goto err;
+
+ ret = rtl2832_sdr_alloc_stream_bufs(s);
+ if (ret)
+ goto err;
+
+ ret = rtl2832_sdr_alloc_urbs(s);
+ if (ret)
+ goto err;
+
+ s->sequence = 0;
+
+ ret = rtl2832_sdr_submit_urbs(s);
+ if (ret)
+ goto err;
+
+err:
+ mutex_unlock(&s->v4l2_lock);
+
+ return ret;
+}
+
+static void rtl2832_sdr_stop_streaming(struct vb2_queue *vq)
+{
+ struct rtl2832_sdr_state *s = vb2_get_drv_priv(vq);
+ dev_dbg(&s->udev->dev, "%s:\n", __func__);
+
+ mutex_lock(&s->v4l2_lock);
+
+ rtl2832_sdr_kill_urbs(s);
+ rtl2832_sdr_free_urbs(s);
+ rtl2832_sdr_free_stream_bufs(s);
+ rtl2832_sdr_cleanup_queued_bufs(s);
+ rtl2832_sdr_unset_adc(s);
+ rtl2832_sdr_unset_tuner(s);
+
+ clear_bit(POWER_ON, &s->flags);
+
+ if (s->d->props->power_ctrl)
+ s->d->props->power_ctrl(s->d, 0);
+
+ mutex_unlock(&s->v4l2_lock);
+}
+
+static struct vb2_ops rtl2832_sdr_vb2_ops = {
+ .queue_setup = rtl2832_sdr_queue_setup,
+ .buf_prepare = rtl2832_sdr_buf_prepare,
+ .buf_queue = rtl2832_sdr_buf_queue,
+ .start_streaming = rtl2832_sdr_start_streaming,
+ .stop_streaming = rtl2832_sdr_stop_streaming,
+ .wait_prepare = vb2_ops_wait_prepare,
+ .wait_finish = vb2_ops_wait_finish,
+};
+
+static int rtl2832_sdr_g_tuner(struct file *file, void *priv,
+ struct v4l2_tuner *v)
+{
+ struct rtl2832_sdr_state *s = video_drvdata(file);
+ dev_dbg(&s->udev->dev, "%s: index=%d type=%d\n",
+ __func__, v->index, v->type);
+
+ if (v->index == 0) {
+ strlcpy(v->name, "ADC: Realtek RTL2832", sizeof(v->name));
+ v->type = V4L2_TUNER_ADC;
+ v->capability = V4L2_TUNER_CAP_1HZ | V4L2_TUNER_CAP_FREQ_BANDS;
+ v->rangelow = 300000;
+ v->rangehigh = 3200000;
+ } else if (v->index == 1) {
+ strlcpy(v->name, "RF: <unknown>", sizeof(v->name));
+ v->type = V4L2_TUNER_RF;
+ v->capability = V4L2_TUNER_CAP_1HZ | V4L2_TUNER_CAP_FREQ_BANDS;
+ v->rangelow = 50000000;
+ v->rangehigh = 2000000000;
+ } else {
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int rtl2832_sdr_s_tuner(struct file *file, void *priv,
+ const struct v4l2_tuner *v)
+{
+ struct rtl2832_sdr_state *s = video_drvdata(file);
+ dev_dbg(&s->udev->dev, "%s:\n", __func__);
+
+ if (v->index > 1)
+ return -EINVAL;
+ return 0;
+}
+
+static int rtl2832_sdr_enum_freq_bands(struct file *file, void *priv,
+ struct v4l2_frequency_band *band)
+{
+ struct rtl2832_sdr_state *s = video_drvdata(file);
+ dev_dbg(&s->udev->dev, "%s: tuner=%d type=%d index=%d\n",
+ __func__, band->tuner, band->type, band->index);
+
+ if (band->tuner == 0) {
+ if (band->index >= ARRAY_SIZE(bands_adc))
+ return -EINVAL;
+
+ *band = bands_adc[band->index];
+ } else if (band->tuner == 1) {
+ if (band->index >= ARRAY_SIZE(bands_fm))
+ return -EINVAL;
+
+ *band = bands_fm[band->index];
+ } else {
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int rtl2832_sdr_g_frequency(struct file *file, void *priv,
+ struct v4l2_frequency *f)
+{
+ struct rtl2832_sdr_state *s = video_drvdata(file);
+ int ret = 0;
+ dev_dbg(&s->udev->dev, "%s: tuner=%d type=%d\n",
+ __func__, f->tuner, f->type);
+
+ if (f->tuner == 0) {
+ f->frequency = s->f_adc;
+ f->type = V4L2_TUNER_ADC;
+ } else if (f->tuner == 1) {
+ f->frequency = s->f_tuner;
+ f->type = V4L2_TUNER_RF;
+ } else {
+ return -EINVAL;
+ }
+
+ return ret;
+}
+
+static int rtl2832_sdr_s_frequency(struct file *file, void *priv,
+ const struct v4l2_frequency *f)
+{
+ struct rtl2832_sdr_state *s = video_drvdata(file);
+ int ret, band;
+
+ dev_dbg(&s->udev->dev, "%s: tuner=%d type=%d frequency=%u\n",
+ __func__, f->tuner, f->type, f->frequency);
+
+ /* ADC band midpoints */
+ #define BAND_ADC_0 ((bands_adc[0].rangehigh + bands_adc[1].rangelow) / 2)
+ #define BAND_ADC_1 ((bands_adc[1].rangehigh + bands_adc[2].rangelow) / 2)
+
+ if (f->tuner == 0 && f->type == V4L2_TUNER_ADC) {
+ if (f->frequency < BAND_ADC_0)
+ band = 0;
+ else if (f->frequency < BAND_ADC_1)
+ band = 1;
+ else
+ band = 2;
+
+ s->f_adc = clamp_t(unsigned int, f->frequency,
+ bands_adc[band].rangelow,
+ bands_adc[band].rangehigh);
+
+ dev_dbg(&s->udev->dev, "%s: ADC frequency=%u Hz\n",
+ __func__, s->f_adc);
+ ret = rtl2832_sdr_set_adc(s);
+ } else if (f->tuner == 1) {
+ s->f_tuner = clamp_t(unsigned int, f->frequency,
+ bands_fm[0].rangelow,
+ bands_fm[0].rangehigh);
+ dev_dbg(&s->udev->dev, "%s: RF frequency=%u Hz\n",
+ __func__, f->frequency);
+
+ ret = rtl2832_sdr_set_tuner_freq(s);
+ } else {
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+static int rtl2832_sdr_enum_fmt_sdr_cap(struct file *file, void *priv,
+ struct v4l2_fmtdesc *f)
+{
+ struct rtl2832_sdr_state *s = video_drvdata(file);
+ dev_dbg(&s->udev->dev, "%s:\n", __func__);
+
+ if (f->index >= NUM_FORMATS)
+ return -EINVAL;
+
+ strlcpy(f->description, formats[f->index].name, sizeof(f->description));
+ f->pixelformat = formats[f->index].pixelformat;
+
+ return 0;
+}
+
+static int rtl2832_sdr_g_fmt_sdr_cap(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct rtl2832_sdr_state *s = video_drvdata(file);
+ dev_dbg(&s->udev->dev, "%s:\n", __func__);
+
+ f->fmt.sdr.pixelformat = s->pixelformat;
+ memset(f->fmt.sdr.reserved, 0, sizeof(f->fmt.sdr.reserved));
+
+ return 0;
+}
+
+static int rtl2832_sdr_s_fmt_sdr_cap(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct rtl2832_sdr_state *s = video_drvdata(file);
+ struct vb2_queue *q = &s->vb_queue;
+ int i;
+ dev_dbg(&s->udev->dev, "%s: pixelformat fourcc %4.4s\n", __func__,
+ (char *)&f->fmt.sdr.pixelformat);
+
+ if (vb2_is_busy(q))
+ return -EBUSY;
+
+ memset(f->fmt.sdr.reserved, 0, sizeof(f->fmt.sdr.reserved));
+ for (i = 0; i < NUM_FORMATS; i++) {
+ if (formats[i].pixelformat == f->fmt.sdr.pixelformat) {
+ s->pixelformat = f->fmt.sdr.pixelformat;
+ return 0;
+ }
+ }
+
+ f->fmt.sdr.pixelformat = formats[0].pixelformat;
+ s->pixelformat = formats[0].pixelformat;
+
+ return 0;
+}
+
+static int rtl2832_sdr_try_fmt_sdr_cap(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct rtl2832_sdr_state *s = video_drvdata(file);
+ int i;
+ dev_dbg(&s->udev->dev, "%s: pixelformat fourcc %4.4s\n", __func__,
+ (char *)&f->fmt.sdr.pixelformat);
+
+ memset(f->fmt.sdr.reserved, 0, sizeof(f->fmt.sdr.reserved));
+ for (i = 0; i < NUM_FORMATS; i++) {
+ if (formats[i].pixelformat == f->fmt.sdr.pixelformat)
+ return 0;
+ }
+
+ f->fmt.sdr.pixelformat = formats[0].pixelformat;
+
+ return 0;
+}
+
+static const struct v4l2_ioctl_ops rtl2832_sdr_ioctl_ops = {
+ .vidioc_querycap = rtl2832_sdr_querycap,
+
+ .vidioc_enum_fmt_sdr_cap = rtl2832_sdr_enum_fmt_sdr_cap,
+ .vidioc_g_fmt_sdr_cap = rtl2832_sdr_g_fmt_sdr_cap,
+ .vidioc_s_fmt_sdr_cap = rtl2832_sdr_s_fmt_sdr_cap,
+ .vidioc_try_fmt_sdr_cap = rtl2832_sdr_try_fmt_sdr_cap,
+
+ .vidioc_reqbufs = vb2_ioctl_reqbufs,
+ .vidioc_create_bufs = vb2_ioctl_create_bufs,
+ .vidioc_prepare_buf = vb2_ioctl_prepare_buf,
+ .vidioc_querybuf = vb2_ioctl_querybuf,
+ .vidioc_qbuf = vb2_ioctl_qbuf,
+ .vidioc_dqbuf = vb2_ioctl_dqbuf,
+
+ .vidioc_streamon = vb2_ioctl_streamon,
+ .vidioc_streamoff = vb2_ioctl_streamoff,
+
+ .vidioc_g_tuner = rtl2832_sdr_g_tuner,
+ .vidioc_s_tuner = rtl2832_sdr_s_tuner,
+
+ .vidioc_enum_freq_bands = rtl2832_sdr_enum_freq_bands,
+ .vidioc_g_frequency = rtl2832_sdr_g_frequency,
+ .vidioc_s_frequency = rtl2832_sdr_s_frequency,
+
+ .vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
+ .vidioc_unsubscribe_event = v4l2_event_unsubscribe,
+ .vidioc_log_status = v4l2_ctrl_log_status,
+};
+
+static const struct v4l2_file_operations rtl2832_sdr_fops = {
+ .owner = THIS_MODULE,
+ .open = v4l2_fh_open,
+ .release = vb2_fop_release,
+ .read = vb2_fop_read,
+ .poll = vb2_fop_poll,
+ .mmap = vb2_fop_mmap,
+ .unlocked_ioctl = video_ioctl2,
+};
+
+static struct video_device rtl2832_sdr_template = {
+ .name = "Realtek RTL2832 SDR",
+ .release = video_device_release_empty,
+ .fops = &rtl2832_sdr_fops,
+ .ioctl_ops = &rtl2832_sdr_ioctl_ops,
+};
+
+static int rtl2832_sdr_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+ struct rtl2832_sdr_state *s =
+ container_of(ctrl->handler, struct rtl2832_sdr_state,
+ hdl);
+ struct dvb_frontend *fe = s->fe;
+ struct dtv_frontend_properties *c = &fe->dtv_property_cache;
+ int ret;
+ dev_dbg(&s->udev->dev,
+ "%s: id=%d name=%s val=%d min=%d max=%d step=%d\n",
+ __func__, ctrl->id, ctrl->name, ctrl->val,
+ ctrl->minimum, ctrl->maximum, ctrl->step);
+
+ switch (ctrl->id) {
+ case V4L2_CID_RF_TUNER_BANDWIDTH_AUTO:
+ case V4L2_CID_RF_TUNER_BANDWIDTH:
+ /* TODO: these controls should be moved to tuner drivers */
+ if (s->bandwidth_auto->val) {
+ /* Round towards the closest legal value */
+ s32 val = s->f_adc + s->bandwidth->step / 2;
+ u32 offset;
+ val = clamp(val, s->bandwidth->minimum, s->bandwidth->maximum);
+ offset = val - s->bandwidth->minimum;
+ offset = s->bandwidth->step * (offset / s->bandwidth->step);
+ s->bandwidth->val = s->bandwidth->minimum + offset;
+ }
+
+ c->bandwidth_hz = s->bandwidth->val;
+
+ if (!test_bit(POWER_ON, &s->flags))
+ return 0;
+
+ if (fe->ops.tuner_ops.set_params)
+ ret = fe->ops.tuner_ops.set_params(fe);
+ else
+ ret = 0;
+ break;
+ default:
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+static const struct v4l2_ctrl_ops rtl2832_sdr_ctrl_ops = {
+ .s_ctrl = rtl2832_sdr_s_ctrl,
+};
+
+static void rtl2832_sdr_video_release(struct v4l2_device *v)
+{
+ struct rtl2832_sdr_state *s =
+ container_of(v, struct rtl2832_sdr_state, v4l2_dev);
+
+ v4l2_ctrl_handler_free(&s->hdl);
+ v4l2_device_unregister(&s->v4l2_dev);
+ kfree(s);
+}
+
+struct dvb_frontend *rtl2832_sdr_attach(struct dvb_frontend *fe,
+ struct i2c_adapter *i2c, const struct rtl2832_config *cfg,
+ struct v4l2_subdev *sd)
+{
+ int ret;
+ struct rtl2832_sdr_state *s;
+ const struct v4l2_ctrl_ops *ops = &rtl2832_sdr_ctrl_ops;
+ struct dvb_usb_device *d = i2c_get_adapdata(i2c);
+
+ s = kzalloc(sizeof(struct rtl2832_sdr_state), GFP_KERNEL);
+ if (s == NULL) {
+ dev_err(&d->udev->dev,
+ "Could not allocate memory for rtl2832_sdr_state\n");
+ return NULL;
+ }
+
+ /* setup the state */
+ s->fe = fe;
+ s->d = d;
+ s->udev = d->udev;
+ s->i2c = i2c;
+ s->cfg = cfg;
+ s->f_adc = bands_adc[0].rangelow;
+ s->f_tuner = bands_fm[0].rangelow;
+ s->pixelformat = V4L2_SDR_FMT_CU8;
+
+ mutex_init(&s->v4l2_lock);
+ mutex_init(&s->vb_queue_lock);
+ spin_lock_init(&s->queued_bufs_lock);
+ INIT_LIST_HEAD(&s->queued_bufs);
+
+ /* Init videobuf2 queue structure */
+ s->vb_queue.type = V4L2_BUF_TYPE_SDR_CAPTURE;
+ s->vb_queue.io_modes = VB2_MMAP | VB2_USERPTR | VB2_READ;
+ s->vb_queue.drv_priv = s;
+ s->vb_queue.buf_struct_size = sizeof(struct rtl2832_sdr_frame_buf);
+ s->vb_queue.ops = &rtl2832_sdr_vb2_ops;
+ s->vb_queue.mem_ops = &vb2_vmalloc_memops;
+ s->vb_queue.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
+ ret = vb2_queue_init(&s->vb_queue);
+ if (ret) {
+ dev_err(&s->udev->dev, "Could not initialize vb2 queue\n");
+ goto err_free_mem;
+ }
+
+ /* Register controls */
+ switch (s->cfg->tuner) {
+ case RTL2832_TUNER_E4000:
+ v4l2_ctrl_handler_init(&s->hdl, 9);
+ if (sd)
+ v4l2_ctrl_add_handler(&s->hdl, sd->ctrl_handler, NULL);
+ break;
+ case RTL2832_TUNER_R820T:
+ v4l2_ctrl_handler_init(&s->hdl, 2);
+ s->bandwidth_auto = v4l2_ctrl_new_std(&s->hdl, ops, V4L2_CID_RF_TUNER_BANDWIDTH_AUTO, 0, 1, 1, 1);
+ s->bandwidth = v4l2_ctrl_new_std(&s->hdl, ops, V4L2_CID_RF_TUNER_BANDWIDTH, 0, 8000000, 100000, 0);
+ v4l2_ctrl_auto_cluster(2, &s->bandwidth_auto, 0, false);
+ break;
+ case RTL2832_TUNER_FC0012:
+ case RTL2832_TUNER_FC0013:
+ v4l2_ctrl_handler_init(&s->hdl, 2);
+ s->bandwidth_auto = v4l2_ctrl_new_std(&s->hdl, ops, V4L2_CID_RF_TUNER_BANDWIDTH_AUTO, 0, 1, 1, 1);
+ s->bandwidth = v4l2_ctrl_new_std(&s->hdl, ops, V4L2_CID_RF_TUNER_BANDWIDTH, 6000000, 8000000, 1000000, 6000000);
+ v4l2_ctrl_auto_cluster(2, &s->bandwidth_auto, 0, false);
+ break;
+ default:
+ v4l2_ctrl_handler_init(&s->hdl, 0);
+ dev_notice(&s->udev->dev, "%s: Unsupported tuner\n",
+ KBUILD_MODNAME);
+ goto err_free_controls;
+ }
+
+ if (s->hdl.error) {
+ ret = s->hdl.error;
+ dev_err(&s->udev->dev, "Could not initialize controls\n");
+ goto err_free_controls;
+ }
+
+ /* Init video_device structure */
+ s->vdev = rtl2832_sdr_template;
+ s->vdev.queue = &s->vb_queue;
+ s->vdev.queue->lock = &s->vb_queue_lock;
+ set_bit(V4L2_FL_USE_FH_PRIO, &s->vdev.flags);
+ video_set_drvdata(&s->vdev, s);
+
+ /* Register the v4l2_device structure */
+ s->v4l2_dev.release = rtl2832_sdr_video_release;
+ ret = v4l2_device_register(&s->udev->dev, &s->v4l2_dev);
+ if (ret) {
+ dev_err(&s->udev->dev,
+ "Failed to register v4l2-device (%d)\n", ret);
+ goto err_free_controls;
+ }
+
+ s->v4l2_dev.ctrl_handler = &s->hdl;
+ s->vdev.v4l2_dev = &s->v4l2_dev;
+ s->vdev.lock = &s->v4l2_lock;
+ s->vdev.vfl_dir = VFL_DIR_RX;
+
+ ret = video_register_device(&s->vdev, VFL_TYPE_SDR, -1);
+ if (ret) {
+ dev_err(&s->udev->dev,
+ "Failed to register as video device (%d)\n",
+ ret);
+ goto err_unregister_v4l2_dev;
+ }
+ dev_info(&s->udev->dev, "Registered as %s\n",
+ video_device_node_name(&s->vdev));
+
+ fe->sec_priv = s;
+ fe->ops.release_sec = rtl2832_sdr_release_sec;
+
+ dev_info(&s->i2c->dev, "%s: Realtek RTL2832 SDR attached\n",
+ KBUILD_MODNAME);
+ return fe;
+
+err_unregister_v4l2_dev:
+ v4l2_device_unregister(&s->v4l2_dev);
+err_free_controls:
+ v4l2_ctrl_handler_free(&s->hdl);
+err_free_mem:
+ kfree(s);
+ return NULL;
+}
+EXPORT_SYMBOL(rtl2832_sdr_attach);
+
+MODULE_AUTHOR("Antti Palosaari <crope@iki.fi>");
+MODULE_DESCRIPTION("Realtek RTL2832 SDR driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/media/rtl2832u_sdr/rtl2832_sdr.h b/drivers/staging/media/rtl2832u_sdr/rtl2832_sdr.h
new file mode 100644
index 00000000000..b865fadf184
--- /dev/null
+++ b/drivers/staging/media/rtl2832u_sdr/rtl2832_sdr.h
@@ -0,0 +1,54 @@
+/*
+ * Realtek RTL2832U SDR driver
+ *
+ * Copyright (C) 2013 Antti Palosaari <crope@iki.fi>
+ *
+ * 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.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * GNU Radio plugin "gr-kernel" for device usage will be on:
+ * http://git.linuxtv.org/anttip/gr-kernel.git
+ *
+ * TODO:
+ * Help is very highly welcome for these + all the others you could imagine:
+ * - move controls to V4L2 API
+ * - use libv4l2 for stream format conversions
+ * - gr-kernel: switch to v4l2_mmap (current read eats a lot of cpu)
+ * - SDRSharp support
+ */
+
+#ifndef RTL2832_SDR_H
+#define RTL2832_SDR_H
+
+#include <linux/kconfig.h>
+#include <media/v4l2-subdev.h>
+
+/* for config struct */
+#include "rtl2832.h"
+
+#if IS_ENABLED(CONFIG_DVB_RTL2832_SDR)
+extern struct dvb_frontend *rtl2832_sdr_attach(struct dvb_frontend *fe,
+ struct i2c_adapter *i2c, const struct rtl2832_config *cfg,
+ struct v4l2_subdev *sd);
+#else
+static inline struct dvb_frontend *rtl2832_sdr_attach(struct dvb_frontend *fe,
+ struct i2c_adapter *i2c, const struct rtl2832_config *cfg,
+ struct v4l2_subdev *sd)
+{
+ dev_warn(&i2c->dev, "%s: driver disabled by Kconfig\n", __func__);
+ return NULL;
+}
+#endif
+
+#endif /* RTL2832_SDR_H */
diff --git a/drivers/staging/media/sn9c102/Kconfig b/drivers/staging/media/sn9c102/Kconfig
new file mode 100644
index 00000000000..c9aba59258d
--- /dev/null
+++ b/drivers/staging/media/sn9c102/Kconfig
@@ -0,0 +1,17 @@
+config USB_SN9C102
+ tristate "USB SN9C1xx PC Camera Controller support (DEPRECATED)"
+ depends on VIDEO_V4L2 && MEDIA_USB_SUPPORT
+ ---help---
+ This driver is DEPRECATED, please use the gspca sonixb and
+ sonixj modules instead.
+
+ Say Y here if you want support for cameras based on SONiX SN9C101,
+ SN9C102, SN9C103, SN9C105 and SN9C120 PC Camera Controllers.
+
+ See <file:drivers/staging/media/sn9c102/sn9c102.txt> for more info.
+
+ If you have webcams that are only supported by this driver and not by
+ the gspca driver, then contact the linux-media mailinglist.
+
+ To compile this driver as a module, choose M here: the
+ module will be called sn9c102.
diff --git a/drivers/staging/media/sn9c102/Makefile b/drivers/staging/media/sn9c102/Makefile
new file mode 100644
index 00000000000..7ecd5a90c7c
--- /dev/null
+++ b/drivers/staging/media/sn9c102/Makefile
@@ -0,0 +1,15 @@
+sn9c102-objs := sn9c102_core.o \
+ sn9c102_hv7131d.o \
+ sn9c102_hv7131r.o \
+ sn9c102_mi0343.o \
+ sn9c102_mi0360.o \
+ sn9c102_mt9v111.o \
+ sn9c102_ov7630.o \
+ sn9c102_ov7660.o \
+ sn9c102_pas106b.o \
+ sn9c102_pas202bcb.o \
+ sn9c102_tas5110c1b.o \
+ sn9c102_tas5110d.o \
+ sn9c102_tas5130d1b.o
+
+obj-$(CONFIG_USB_SN9C102) += sn9c102.o
diff --git a/drivers/staging/media/sn9c102/sn9c102.h b/drivers/staging/media/sn9c102/sn9c102.h
new file mode 100644
index 00000000000..37ca7225fcf
--- /dev/null
+++ b/drivers/staging/media/sn9c102/sn9c102.h
@@ -0,0 +1,214 @@
+/***************************************************************************
+ * V4L2 driver for SN9C1xx PC Camera Controllers *
+ * *
+ * Copyright (C) 2004-2006 by Luca Risolia <luca.risolia@studio.unibo.it> *
+ * *
+ * 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., 675 Mass Ave, Cambridge, MA 02139, USA. *
+ ***************************************************************************/
+
+#ifndef _SN9C102_H_
+#define _SN9C102_H_
+
+#include <linux/usb.h>
+#include <linux/videodev2.h>
+#include <media/v4l2-common.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-device.h>
+#include <linux/device.h>
+#include <linux/list.h>
+#include <linux/spinlock.h>
+#include <linux/time.h>
+#include <linux/wait.h>
+#include <linux/types.h>
+#include <linux/param.h>
+#include <linux/rwsem.h>
+#include <linux/mutex.h>
+#include <linux/string.h>
+#include <linux/stddef.h>
+#include <linux/kref.h>
+
+#include "sn9c102_config.h"
+#include "sn9c102_sensor.h"
+#include "sn9c102_devtable.h"
+
+
+enum sn9c102_frame_state {
+ F_UNUSED,
+ F_QUEUED,
+ F_GRABBING,
+ F_DONE,
+ F_ERROR,
+};
+
+struct sn9c102_frame_t {
+ void *bufmem;
+ struct v4l2_buffer buf;
+ enum sn9c102_frame_state state;
+ struct list_head frame;
+ unsigned long vma_use_count;
+};
+
+enum sn9c102_dev_state {
+ DEV_INITIALIZED = 0x01,
+ DEV_DISCONNECTED = 0x02,
+ DEV_MISCONFIGURED = 0x04,
+};
+
+enum sn9c102_io_method {
+ IO_NONE,
+ IO_READ,
+ IO_MMAP,
+};
+
+enum sn9c102_stream_state {
+ STREAM_OFF,
+ STREAM_INTERRUPT,
+ STREAM_ON,
+};
+
+typedef char sn9c102_sof_header_t[62];
+
+struct sn9c102_sof_t {
+ sn9c102_sof_header_t header;
+ u16 bytesread;
+};
+
+struct sn9c102_sysfs_attr {
+ u16 reg, i2c_reg;
+ sn9c102_sof_header_t frame_header;
+};
+
+struct sn9c102_module_param {
+ u8 force_munmap;
+ u16 frame_timeout;
+};
+
+static DEFINE_MUTEX(sn9c102_sysfs_lock);
+static DECLARE_RWSEM(sn9c102_dev_lock);
+
+struct sn9c102_device {
+ struct video_device *v4ldev;
+
+ struct v4l2_device v4l2_dev;
+
+ enum sn9c102_bridge bridge;
+ struct sn9c102_sensor sensor;
+
+ struct usb_device *usbdev;
+ struct urb *urb[SN9C102_URBS];
+ void *transfer_buffer[SN9C102_URBS];
+ u8 *control_buffer;
+
+ struct sn9c102_frame_t *frame_current, frame[SN9C102_MAX_FRAMES];
+ struct list_head inqueue, outqueue;
+ u32 frame_count, nbuffers, nreadbuffers;
+
+ enum sn9c102_io_method io;
+ enum sn9c102_stream_state stream;
+
+ struct v4l2_jpegcompression compression;
+
+ struct sn9c102_sysfs_attr sysfs;
+ struct sn9c102_sof_t sof;
+ u16 reg[384];
+
+ struct sn9c102_module_param module_param;
+
+ struct kref kref;
+ enum sn9c102_dev_state state;
+ u8 users;
+
+ struct completion probe;
+ struct mutex open_mutex, fileop_mutex;
+ spinlock_t queue_lock;
+ wait_queue_head_t wait_open, wait_frame, wait_stream;
+};
+
+/*****************************************************************************/
+
+struct sn9c102_device*
+sn9c102_match_id(struct sn9c102_device *cam, const struct usb_device_id *id)
+{
+ return usb_match_id(usb_ifnum_to_if(cam->usbdev, 0), id) ? cam : NULL;
+}
+
+
+void
+sn9c102_attach_sensor(struct sn9c102_device *cam,
+ const struct sn9c102_sensor *sensor)
+{
+ memcpy(&cam->sensor, sensor, sizeof(struct sn9c102_sensor));
+}
+
+
+enum sn9c102_bridge
+sn9c102_get_bridge(struct sn9c102_device *cam)
+{
+ return cam->bridge;
+}
+
+
+struct sn9c102_sensor *sn9c102_get_sensor(struct sn9c102_device *cam)
+{
+ return &cam->sensor;
+}
+
+/*****************************************************************************/
+
+#undef DBG
+#undef KDBG
+#ifdef SN9C102_DEBUG
+# define DBG(level, fmt, args...) \
+do { \
+ if (debug >= (level)) { \
+ if ((level) == 1) \
+ dev_err(&cam->usbdev->dev, fmt "\n", ## args); \
+ else if ((level) == 2) \
+ dev_info(&cam->usbdev->dev, fmt "\n", ## args); \
+ else if ((level) >= 3) \
+ dev_info(&cam->usbdev->dev, "[%s:%d] " fmt "\n", \
+ __func__, __LINE__ , ## args); \
+ } \
+} while (0)
+# define V4LDBG(level, name, cmd) \
+do { \
+ if (debug >= (level)) \
+ v4l_printk_ioctl(name, cmd); \
+} while (0)
+# define KDBG(level, fmt, args...) \
+do { \
+ if (debug >= (level)) { \
+ if ((level) == 1 || (level) == 2) \
+ pr_info("sn9c102: " fmt "\n", ## args); \
+ else if ((level) == 3) \
+ pr_debug("sn9c102: [%s:%d] " fmt "\n", \
+ __func__, __LINE__ , ## args); \
+ } \
+} while (0)
+#else
+# define DBG(level, fmt, args...) do { ; } while (0)
+# define V4LDBG(level, name, cmd) do { ; } while (0)
+# define KDBG(level, fmt, args...) do { ; } while (0)
+#endif
+
+#undef PDBG
+#define PDBG(fmt, args...) \
+dev_info(&cam->usbdev->dev, "[%s:%s:%d] " fmt "\n", __FILE__, __func__, \
+ __LINE__ , ## args)
+
+#undef PDBGG
+#define PDBGG(fmt, args...) do { ; } while (0) /* placeholder */
+
+#endif /* _SN9C102_H_ */
diff --git a/drivers/staging/media/sn9c102/sn9c102.txt b/drivers/staging/media/sn9c102/sn9c102.txt
new file mode 100644
index 00000000000..b4f67040403
--- /dev/null
+++ b/drivers/staging/media/sn9c102/sn9c102.txt
@@ -0,0 +1,592 @@
+
+ SN9C1xx PC Camera Controllers
+ Driver for Linux
+ =============================
+
+ - Documentation -
+
+
+Index
+=====
+1. Copyright
+2. Disclaimer
+3. License
+4. Overview and features
+5. Module dependencies
+6. Module loading
+7. Module parameters
+8. Optional device control through "sysfs"
+9. Supported devices
+10. Notes for V4L2 application developers
+11. Video frame formats
+12. Contact information
+13. Credits
+
+
+1. Copyright
+============
+Copyright (C) 2004-2007 by Luca Risolia <luca.risolia@studio.unibo.it>
+
+
+2. Disclaimer
+=============
+SONiX is a trademark of SONiX Technology Company Limited, inc.
+This software is not sponsored or developed by SONiX.
+
+
+3. License
+==========
+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., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+
+4. Overview and features
+========================
+This driver attempts to support the video interface of the devices assembling
+the SONiX SN9C101, SN9C102, SN9C103, SN9C105 and SN9C120 PC Camera Controllers
+("SN9C1xx" from now on).
+
+The driver relies on the Video4Linux2 and USB core modules. It has been
+designed to run properly on SMP systems as well.
+
+The latest version of the SN9C1xx driver can be found at the following URL:
+http://www.linux-projects.org/
+
+Some of the features of the driver are:
+
+- full compliance with the Video4Linux2 API (see also "Notes for V4L2
+ application developers" paragraph);
+- available mmap or read/poll methods for video streaming through isochronous
+ data transfers;
+- automatic detection of image sensor;
+- support for built-in microphone interface;
+- support for any window resolutions and optional panning within the maximum
+ pixel area of image sensor;
+- image downscaling with arbitrary scaling factors from 1, 2 and 4 in both
+ directions (see "Notes for V4L2 application developers" paragraph);
+- two different video formats for uncompressed or compressed data in low or
+ high compression quality (see also "Notes for V4L2 application developers"
+ and "Video frame formats" paragraphs);
+- full support for the capabilities of many of the possible image sensors that
+ can be connected to the SN9C1xx bridges, including, for instance, red, green,
+ blue and global gain adjustments and exposure (see "Supported devices"
+ paragraph for details);
+- use of default color settings for sunlight conditions;
+- dynamic I/O interface for both SN9C1xx and image sensor control and
+ monitoring (see "Optional device control through 'sysfs'" paragraph);
+- dynamic driver control thanks to various module parameters (see "Module
+ parameters" paragraph);
+- up to 64 cameras can be handled at the same time; they can be connected and
+ disconnected from the host many times without turning off the computer, if
+ the system supports hotplugging;
+- no known bugs.
+
+
+5. Module dependencies
+======================
+For it to work properly, the driver needs kernel support for Video4Linux and
+USB.
+
+The following options of the kernel configuration file must be enabled and
+corresponding modules must be compiled:
+
+ # Multimedia devices
+ #
+ CONFIG_VIDEO_DEV=m
+
+To enable advanced debugging functionality on the device through /sysfs:
+
+ # Multimedia devices
+ #
+ CONFIG_VIDEO_ADV_DEBUG=y
+
+ # USB support
+ #
+ CONFIG_USB=m
+
+In addition, depending on the hardware being used, the modules below are
+necessary:
+
+ # USB Host Controller Drivers
+ #
+ CONFIG_USB_EHCI_HCD=m
+ CONFIG_USB_UHCI_HCD=m
+ CONFIG_USB_OHCI_HCD=m
+
+The SN9C103, SN9c105 and SN9C120 controllers also provide a built-in microphone
+interface. It is supported by the USB Audio driver thanks to the ALSA API:
+
+ # Sound
+ #
+ CONFIG_SOUND=y
+
+ # Advanced Linux Sound Architecture
+ #
+ CONFIG_SND=m
+
+ # USB devices
+ #
+ CONFIG_SND_USB_AUDIO=m
+
+And finally:
+
+ # USB Multimedia devices
+ #
+ CONFIG_USB_SN9C102=m
+
+
+6. Module loading
+=================
+To use the driver, it is necessary to load the "sn9c102" module into memory
+after every other module required: "videodev", "v4l2_common", "compat_ioctl32",
+"usbcore" and, depending on the USB host controller you have, "ehci-hcd",
+"uhci-hcd" or "ohci-hcd".
+
+Loading can be done as shown below:
+
+ [root@localhost home]# modprobe sn9c102
+
+Note that the module is called "sn9c102" for historic reasons, although it
+does not just support the SN9C102.
+
+At this point all the devices supported by the driver and connected to the USB
+ports should be recognized. You can invoke "dmesg" to analyze kernel messages
+and verify that the loading process has gone well:
+
+ [user@localhost home]$ dmesg
+
+or, to isolate all the kernel messages generated by the driver:
+
+ [user@localhost home]$ dmesg | grep sn9c102
+
+
+7. Module parameters
+====================
+Module parameters are listed below:
+-------------------------------------------------------------------------------
+Name: video_nr
+Type: short array (min = 0, max = 64)
+Syntax: <-1|n[,...]>
+Description: Specify V4L2 minor mode number:
+ -1 = use next available
+ n = use minor number n
+ You can specify up to 64 cameras this way.
+ For example:
+ video_nr=-1,2,-1 would assign minor number 2 to the second
+ recognized camera and use auto for the first one and for every
+ other camera.
+Default: -1
+-------------------------------------------------------------------------------
+Name: force_munmap
+Type: bool array (min = 0, max = 64)
+Syntax: <0|1[,...]>
+Description: Force the application to unmap previously mapped buffer memory
+ before calling any VIDIOC_S_CROP or VIDIOC_S_FMT ioctl's. Not
+ all the applications support this feature. This parameter is
+ specific for each detected camera.
+ 0 = do not force memory unmapping
+ 1 = force memory unmapping (save memory)
+Default: 0
+-------------------------------------------------------------------------------
+Name: frame_timeout
+Type: uint array (min = 0, max = 64)
+Syntax: <0|n[,...]>
+Description: Timeout for a video frame in seconds before returning an I/O
+ error; 0 for infinity. This parameter is specific for each
+ detected camera and can be changed at runtime thanks to the
+ /sys filesystem interface.
+Default: 2
+-------------------------------------------------------------------------------
+Name: debug
+Type: ushort
+Syntax: <n>
+Description: Debugging information level, from 0 to 3:
+ 0 = none (use carefully)
+ 1 = critical errors
+ 2 = significant information
+ 3 = more verbose messages
+ Level 3 is useful for testing only. It also shows some more
+ information about the hardware being detected.
+ This parameter can be changed at runtime thanks to the /sys
+ filesystem interface.
+Default: 2
+-------------------------------------------------------------------------------
+
+
+8. Optional device control through "sysfs" [1]
+==========================================
+If the kernel has been compiled with the CONFIG_VIDEO_ADV_DEBUG option enabled,
+it is possible to read and write both the SN9C1xx and the image sensor
+registers by using the "sysfs" filesystem interface.
+
+Every time a supported device is recognized, a write-only file named "green" is
+created in the /sys/class/video4linux/videoX directory. You can set the green
+channel's gain by writing the desired value to it. The value may range from 0
+to 15 for the SN9C101 or SN9C102 bridges, from 0 to 127 for the SN9C103,
+SN9C105 and SN9C120 bridges.
+Similarly, only for the SN9C103, SN9C105 and SN9C120 controllers, blue and red
+gain control files are available in the same directory, for which accepted
+values may range from 0 to 127.
+
+There are other four entries in the directory above for each registered camera:
+"reg", "val", "i2c_reg" and "i2c_val". The first two files control the
+SN9C1xx bridge, while the other two control the sensor chip. "reg" and
+"i2c_reg" hold the values of the current register index where the following
+reading/writing operations are addressed at through "val" and "i2c_val". Their
+use is not intended for end-users. Note that "i2c_reg" and "i2c_val" will not
+be created if the sensor does not actually support the standard I2C protocol or
+its registers are not 8-bit long. Also, remember that you must be logged in as
+root before writing to them.
+
+As an example, suppose we were to want to read the value contained in the
+register number 1 of the sensor register table - which is usually the product
+identifier - of the camera registered as "/dev/video0":
+
+ [root@localhost #] cd /sys/class/video4linux/video0
+ [root@localhost #] echo 1 > i2c_reg
+ [root@localhost #] cat i2c_val
+
+Note that "cat" will fail if sensor registers cannot be read.
+
+Now let's set the green gain's register of the SN9C101 or SN9C102 chips to 2:
+
+ [root@localhost #] echo 0x11 > reg
+ [root@localhost #] echo 2 > val
+
+Note that the SN9C1xx always returns 0 when some of its registers are read.
+To avoid race conditions, all the I/O accesses to the above files are
+serialized.
+The sysfs interface also provides the "frame_header" entry, which exports the
+frame header of the most recent requested and captured video frame. The header
+is always 18-bytes long and is appended to every video frame by the SN9C1xx
+controllers. As an example, this additional information can be used by the user
+application for implementing auto-exposure features via software.
+
+The following table describes the frame header exported by the SN9C101 and
+SN9C102:
+
+Byte # Value or bits Description
+------ ------------- -----------
+0x00 0xFF Frame synchronisation pattern
+0x01 0xFF Frame synchronisation pattern
+0x02 0x00 Frame synchronisation pattern
+0x03 0xC4 Frame synchronisation pattern
+0x04 0xC4 Frame synchronisation pattern
+0x05 0x96 Frame synchronisation pattern
+0x06 [3:0] Read channel gain control = (1+R_GAIN/8)
+ [7:4] Blue channel gain control = (1+B_GAIN/8)
+0x07 [ 0 ] Compression mode. 0=No compression, 1=Compression enabled
+ [2:1] Maximum scale factor for compression
+ [ 3 ] 1 = USB fifo(2K bytes) is full
+ [ 4 ] 1 = Digital gain is finish
+ [ 5 ] 1 = Exposure is finish
+ [7:6] Frame index
+0x08 [7:0] Y sum inside Auto-Exposure area (low-byte)
+0x09 [7:0] Y sum inside Auto-Exposure area (high-byte)
+ where Y sum = (R/4 + 5G/16 + B/8) / 32
+0x0A [7:0] Y sum outside Auto-Exposure area (low-byte)
+0x0B [7:0] Y sum outside Auto-Exposure area (high-byte)
+ where Y sum = (R/4 + 5G/16 + B/8) / 128
+0x0C 0xXX Not used
+0x0D 0xXX Not used
+0x0E 0xXX Not used
+0x0F 0xXX Not used
+0x10 0xXX Not used
+0x11 0xXX Not used
+
+The following table describes the frame header exported by the SN9C103:
+
+Byte # Value or bits Description
+------ ------------- -----------
+0x00 0xFF Frame synchronisation pattern
+0x01 0xFF Frame synchronisation pattern
+0x02 0x00 Frame synchronisation pattern
+0x03 0xC4 Frame synchronisation pattern
+0x04 0xC4 Frame synchronisation pattern
+0x05 0x96 Frame synchronisation pattern
+0x06 [6:0] Read channel gain control = (1/2+R_GAIN/64)
+0x07 [6:0] Blue channel gain control = (1/2+B_GAIN/64)
+ [7:4]
+0x08 [ 0 ] Compression mode. 0=No compression, 1=Compression enabled
+ [2:1] Maximum scale factor for compression
+ [ 3 ] 1 = USB fifo(2K bytes) is full
+ [ 4 ] 1 = Digital gain is finish
+ [ 5 ] 1 = Exposure is finish
+ [7:6] Frame index
+0x09 [7:0] Y sum inside Auto-Exposure area (low-byte)
+0x0A [7:0] Y sum inside Auto-Exposure area (high-byte)
+ where Y sum = (R/4 + 5G/16 + B/8) / 32
+0x0B [7:0] Y sum outside Auto-Exposure area (low-byte)
+0x0C [7:0] Y sum outside Auto-Exposure area (high-byte)
+ where Y sum = (R/4 + 5G/16 + B/8) / 128
+0x0D [1:0] Audio frame number
+ [ 2 ] 1 = Audio is recording
+0x0E [7:0] Audio summation (low-byte)
+0x0F [7:0] Audio summation (high-byte)
+0x10 [7:0] Audio sample count
+0x11 [7:0] Audio peak data in audio frame
+
+The AE area (sx, sy, ex, ey) in the active window can be set by programming the
+registers 0x1c, 0x1d, 0x1e and 0x1f of the SN9C1xx controllers, where one unit
+corresponds to 32 pixels.
+
+[1] The frame headers exported by the SN9C105 and SN9C120 are not described.
+
+
+9. Supported devices
+====================
+None of the names of the companies as well as their products will be mentioned
+here. They have never collaborated with the author, so no advertising.
+
+From the point of view of a driver, what unambiguously identify a device are
+its vendor and product USB identifiers. Below is a list of known identifiers of
+devices assembling the SN9C1xx PC camera controllers:
+
+Vendor ID Product ID
+--------- ----------
+0x0458 0x7025
+0x045e 0x00f5
+0x045e 0x00f7
+0x0471 0x0327
+0x0471 0x0328
+0x0c45 0x6001
+0x0c45 0x6005
+0x0c45 0x6007
+0x0c45 0x6009
+0x0c45 0x600d
+0x0c45 0x6011
+0x0c45 0x6019
+0x0c45 0x6024
+0x0c45 0x6025
+0x0c45 0x6028
+0x0c45 0x6029
+0x0c45 0x602a
+0x0c45 0x602b
+0x0c45 0x602c
+0x0c45 0x602d
+0x0c45 0x602e
+0x0c45 0x6030
+0x0c45 0x603f
+0x0c45 0x6080
+0x0c45 0x6082
+0x0c45 0x6083
+0x0c45 0x6088
+0x0c45 0x608a
+0x0c45 0x608b
+0x0c45 0x608c
+0x0c45 0x608e
+0x0c45 0x608f
+0x0c45 0x60a0
+0x0c45 0x60a2
+0x0c45 0x60a3
+0x0c45 0x60a8
+0x0c45 0x60aa
+0x0c45 0x60ab
+0x0c45 0x60ac
+0x0c45 0x60ae
+0x0c45 0x60af
+0x0c45 0x60b0
+0x0c45 0x60b2
+0x0c45 0x60b3
+0x0c45 0x60b8
+0x0c45 0x60ba
+0x0c45 0x60bb
+0x0c45 0x60bc
+0x0c45 0x60be
+0x0c45 0x60c0
+0x0c45 0x60c2
+0x0c45 0x60c8
+0x0c45 0x60cc
+0x0c45 0x60ea
+0x0c45 0x60ec
+0x0c45 0x60ef
+0x0c45 0x60fa
+0x0c45 0x60fb
+0x0c45 0x60fc
+0x0c45 0x60fe
+0x0c45 0x6102
+0x0c45 0x6108
+0x0c45 0x610f
+0x0c45 0x6130
+0x0c45 0x6138
+0x0c45 0x613a
+0x0c45 0x613b
+0x0c45 0x613c
+0x0c45 0x613e
+
+The list above does not imply that all those devices work with this driver: up
+until now only the ones that assemble the following pairs of SN9C1xx bridges
+and image sensors are supported; kernel messages will always tell you whether
+this is the case (see "Module loading" paragraph):
+
+Image sensor / SN9C1xx bridge | SN9C10[12] SN9C103 SN9C105 SN9C120
+-------------------------------------------------------------------------------
+HV7131D Hynix Semiconductor | Yes No No No
+HV7131R Hynix Semiconductor | No Yes Yes Yes
+MI-0343 Micron Technology | Yes No No No
+MI-0360 Micron Technology | No Yes Yes Yes
+OV7630 OmniVision Technologies | Yes Yes Yes Yes
+OV7660 OmniVision Technologies | No No Yes Yes
+PAS106B PixArt Imaging | Yes No No No
+PAS202B PixArt Imaging | Yes Yes No No
+TAS5110C1B Taiwan Advanced Sensor | Yes No No No
+TAS5110D Taiwan Advanced Sensor | Yes No No No
+TAS5130D1B Taiwan Advanced Sensor | Yes No No No
+
+"Yes" means that the pair is supported by the driver, while "No" means that the
+pair does not exist or is not supported by the driver.
+
+Only some of the available control settings of each image sensor are supported
+through the V4L2 interface.
+
+Donations of new models for further testing and support would be much
+appreciated. Non-available hardware will not be supported by the author of this
+driver.
+
+
+10. Notes for V4L2 application developers
+=========================================
+This driver follows the V4L2 API specifications. In particular, it enforces two
+rules:
+
+- exactly one I/O method, either "mmap" or "read", is associated with each
+file descriptor. Once it is selected, the application must close and reopen the
+device to switch to the other I/O method;
+
+- although it is not mandatory, previously mapped buffer memory should always
+be unmapped before calling any "VIDIOC_S_CROP" or "VIDIOC_S_FMT" ioctl's.
+The same number of buffers as before will be allocated again to match the size
+of the new video frames, so you have to map the buffers again before any I/O
+attempts on them.
+
+Consistently with the hardware limits, this driver also supports image
+downscaling with arbitrary scaling factors from 1, 2 and 4 in both directions.
+However, the V4L2 API specifications don't correctly define how the scaling
+factor can be chosen arbitrarily by the "negotiation" of the "source" and
+"target" rectangles. To work around this flaw, we have added the convention
+that, during the negotiation, whenever the "VIDIOC_S_CROP" ioctl is issued, the
+scaling factor is restored to 1.
+
+This driver supports two different video formats: the first one is the "8-bit
+Sequential Bayer" format and can be used to obtain uncompressed video data
+from the device through the current I/O method, while the second one provides
+either "raw" compressed video data (without frame headers not related to the
+compressed data) or standard JPEG (with frame headers). The compression quality
+may vary from 0 to 1 and can be selected or queried thanks to the
+VIDIOC_S_JPEGCOMP and VIDIOC_G_JPEGCOMP V4L2 ioctl's. For maximum flexibility,
+both the default active video format and the default compression quality
+depend on how the image sensor being used is initialized.
+
+
+11. Video frame formats [1]
+=======================
+The SN9C1xx PC Camera Controllers can send images in two possible video
+formats over the USB: either native "Sequential RGB Bayer" or compressed.
+The compression is used to achieve high frame rates. With regard to the
+SN9C101, SN9C102 and SN9C103, the compression is based on the Huffman encoding
+algorithm described below, while with regard to the SN9C105 and SN9C120 the
+compression is based on the JPEG standard.
+The current video format may be selected or queried from the user application
+by calling the VIDIOC_S_FMT or VIDIOC_G_FMT ioctl's, as described in the V4L2
+API specifications.
+
+The name "Sequential Bayer" indicates the organization of the red, green and
+blue pixels in one video frame. Each pixel is associated with a 8-bit long
+value and is disposed in memory according to the pattern shown below:
+
+B[0] G[1] B[2] G[3] ... B[m-2] G[m-1]
+G[m] R[m+1] G[m+2] R[m+2] ... G[2m-2] R[2m-1]
+...
+... B[(n-1)(m-2)] G[(n-1)(m-1)]
+... G[n(m-2)] R[n(m-1)]
+
+The above matrix also represents the sequential or progressive read-out mode of
+the (n, m) Bayer color filter array used in many CCD or CMOS image sensors.
+
+The Huffman compressed video frame consists of a bitstream that encodes for
+every R, G, or B pixel the difference between the value of the pixel itself and
+some reference pixel value. Pixels are organised in the Bayer pattern and the
+Bayer sub-pixels are tracked individually and alternatingly. For example, in
+the first line values for the B and G1 pixels are alternatingly encoded, while
+in the second line values for the G2 and R pixels are alternatingly encoded.
+
+The pixel reference value is calculated as follows:
+- the 4 top left pixels are encoded in raw uncompressed 8-bit format;
+- the value in the top two rows is the value of the pixel left of the current
+ pixel;
+- the value in the left column is the value of the pixel above the current
+ pixel;
+- for all other pixels, the reference value is the average of the value of the
+ pixel on the left and the value of the pixel above the current pixel;
+- there is one code in the bitstream that specifies the value of a pixel
+ directly (in 4-bit resolution);
+- pixel values need to be clamped inside the range [0..255] for proper
+ decoding.
+
+The algorithm purely describes the conversion from compressed Bayer code used
+in the SN9C101, SN9C102 and SN9C103 chips to uncompressed Bayer. Additional
+steps are required to convert this to a color image (i.e. a color interpolation
+algorithm).
+
+The following Huffman codes have been found:
+0: +0 (relative to reference pixel value)
+100: +4
+101: -4?
+1110xxxx: set absolute value to xxxx.0000
+1101: +11
+1111: -11
+11001: +20
+110000: -20
+110001: ??? - these codes are apparently not used
+
+[1] The Huffman compression algorithm has been reverse-engineered and
+ documented by Bertrik Sikken.
+
+
+12. Contact information
+=======================
+The author may be contacted by e-mail at <luca.risolia@studio.unibo.it>.
+
+GPG/PGP encrypted e-mail's are accepted. The GPG key ID of the author is
+'FCE635A4'; the public 1024-bit key should be available at any keyserver;
+the fingerprint is: '88E8 F32F 7244 68BA 3958 5D40 99DA 5D2A FCE6 35A4'.
+
+
+13. Credits
+===========
+Many thanks to following persons for their contribute (listed in alphabetical
+order):
+
+- David Anderson for the donation of a webcam;
+- Luca Capello for the donation of a webcam;
+- Philippe Coval for having helped testing the PAS202BCA image sensor;
+- Joao Rodrigo Fuzaro, Joao Limirio, Claudio Filho and Caio Begotti for the
+ donation of a webcam;
+- Dennis Heitmann for the donation of a webcam;
+- Jon Hollstrom for the donation of a webcam;
+- Nick McGill for the donation of a webcam;
+- Carlos Eduardo Medaglia Dyonisio, who added the support for the PAS202BCB
+ image sensor;
+- Stefano Mozzi, who donated 45 EU;
+- Andrew Pearce for the donation of a webcam;
+- John Pullan for the donation of a webcam;
+- Bertrik Sikken, who reverse-engineered and documented the Huffman compression
+ algorithm used in the SN9C101, SN9C102 and SN9C103 controllers and
+ implemented the first decoder;
+- Ronny Standke for the donation of a webcam;
+- Mizuno Takafumi for the donation of a webcam;
+- an "anonymous" donator (who didn't want his name to be revealed) for the
+ donation of a webcam.
+- an anonymous donator for the donation of four webcams and two boards with ten
+ image sensors.
diff --git a/drivers/staging/media/sn9c102/sn9c102_config.h b/drivers/staging/media/sn9c102/sn9c102_config.h
new file mode 100644
index 00000000000..0f4e0378b07
--- /dev/null
+++ b/drivers/staging/media/sn9c102/sn9c102_config.h
@@ -0,0 +1,86 @@
+/***************************************************************************
+ * Global parameters for the V4L2 driver for SN9C1xx PC Camera Controllers *
+ * *
+ * Copyright (C) 2007 by Luca Risolia <luca.risolia@studio.unibo.it> *
+ * *
+ * 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., 675 Mass Ave, Cambridge, MA 02139, USA. *
+ ***************************************************************************/
+
+#ifndef _SN9C102_CONFIG_H_
+#define _SN9C102_CONFIG_H_
+
+#include <linux/types.h>
+#include <linux/jiffies.h>
+
+#define SN9C102_DEBUG
+#define SN9C102_DEBUG_LEVEL 2
+#define SN9C102_MAX_DEVICES 64
+#define SN9C102_PRESERVE_IMGSCALE 0
+#define SN9C102_FORCE_MUNMAP 0
+#define SN9C102_MAX_FRAMES 32
+#define SN9C102_URBS 2
+#define SN9C102_ISO_PACKETS 7
+#define SN9C102_ALTERNATE_SETTING 8
+#define SN9C102_URB_TIMEOUT msecs_to_jiffies(2 * SN9C102_ISO_PACKETS)
+#define SN9C102_CTRL_TIMEOUT 300
+#define SN9C102_FRAME_TIMEOUT 0
+
+/*****************************************************************************/
+
+static const u8 SN9C102_Y_QTABLE0[64] = {
+ 8, 5, 5, 8, 12, 20, 25, 30,
+ 6, 6, 7, 9, 13, 29, 30, 27,
+ 7, 6, 8, 12, 20, 28, 34, 28,
+ 7, 8, 11, 14, 25, 43, 40, 31,
+ 9, 11, 18, 28, 34, 54, 51, 38,
+ 12, 17, 27, 32, 40, 52, 56, 46,
+ 24, 32, 39, 43, 51, 60, 60, 50,
+ 36, 46, 47, 49, 56, 50, 51, 49
+};
+
+static const u8 SN9C102_UV_QTABLE0[64] = {
+ 8, 9, 12, 23, 49, 49, 49, 49,
+ 9, 10, 13, 33, 49, 49, 49, 49,
+ 12, 13, 28, 49, 49, 49, 49, 49,
+ 23, 33, 49, 49, 49, 49, 49, 49,
+ 49, 49, 49, 49, 49, 49, 49, 49,
+ 49, 49, 49, 49, 49, 49, 49, 49,
+ 49, 49, 49, 49, 49, 49, 49, 49,
+ 49, 49, 49, 49, 49, 49, 49, 49
+};
+
+static const u8 SN9C102_Y_QTABLE1[64] = {
+ 16, 11, 10, 16, 24, 40, 51, 61,
+ 12, 12, 14, 19, 26, 58, 60, 55,
+ 14, 13, 16, 24, 40, 57, 69, 56,
+ 14, 17, 22, 29, 51, 87, 80, 62,
+ 18, 22, 37, 56, 68, 109, 103, 77,
+ 24, 35, 55, 64, 81, 104, 113, 92,
+ 49, 64, 78, 87, 103, 121, 120, 101,
+ 72, 92, 95, 98, 112, 100, 103, 99
+};
+
+static const u8 SN9C102_UV_QTABLE1[64] = {
+ 17, 18, 24, 47, 99, 99, 99, 99,
+ 18, 21, 26, 66, 99, 99, 99, 99,
+ 24, 26, 56, 99, 99, 99, 99, 99,
+ 47, 66, 99, 99, 99, 99, 99, 99,
+ 99, 99, 99, 99, 99, 99, 99, 99,
+ 99, 99, 99, 99, 99, 99, 99, 99,
+ 99, 99, 99, 99, 99, 99, 99, 99,
+ 99, 99, 99, 99, 99, 99, 99, 99
+};
+
+#endif /* _SN9C102_CONFIG_H_ */
diff --git a/drivers/staging/media/sn9c102/sn9c102_core.c b/drivers/staging/media/sn9c102/sn9c102_core.c
new file mode 100644
index 00000000000..98b30579b0a
--- /dev/null
+++ b/drivers/staging/media/sn9c102/sn9c102_core.c
@@ -0,0 +1,3465 @@
+/***************************************************************************
+ * V4L2 driver for SN9C1xx PC Camera Controllers *
+ * *
+ * Copyright (C) 2004-2007 by Luca Risolia <luca.risolia@studio.unibo.it> *
+ * *
+ * 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., 675 Mass Ave, Cambridge, MA 02139, USA. *
+ ***************************************************************************/
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/param.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/device.h>
+#include <linux/fs.h>
+#include <linux/delay.h>
+#include <linux/compiler.h>
+#include <linux/ioctl.h>
+#include <linux/poll.h>
+#include <linux/stat.h>
+#include <linux/mm.h>
+#include <linux/vmalloc.h>
+#include <linux/version.h>
+#include <linux/page-flags.h>
+#include <asm/byteorder.h>
+#include <asm/page.h>
+#include <asm/uaccess.h>
+
+#include "sn9c102.h"
+
+/*****************************************************************************/
+
+#define SN9C102_MODULE_NAME "V4L2 driver for SN9C1xx PC Camera Controllers"
+#define SN9C102_MODULE_ALIAS "sn9c1xx"
+#define SN9C102_MODULE_AUTHOR "(C) 2004-2007 Luca Risolia"
+#define SN9C102_AUTHOR_EMAIL "<luca.risolia@studio.unibo.it>"
+#define SN9C102_MODULE_LICENSE "GPL"
+#define SN9C102_MODULE_VERSION "1:1.48"
+
+/*****************************************************************************/
+
+MODULE_DEVICE_TABLE(usb, sn9c102_id_table);
+
+MODULE_AUTHOR(SN9C102_MODULE_AUTHOR " " SN9C102_AUTHOR_EMAIL);
+MODULE_DESCRIPTION(SN9C102_MODULE_NAME);
+MODULE_ALIAS(SN9C102_MODULE_ALIAS);
+MODULE_VERSION(SN9C102_MODULE_VERSION);
+MODULE_LICENSE(SN9C102_MODULE_LICENSE);
+
+static short video_nr[] = {[0 ... SN9C102_MAX_DEVICES-1] = -1};
+module_param_array(video_nr, short, NULL, 0444);
+MODULE_PARM_DESC(video_nr,
+ " <-1|n[,...]>"
+ "\nSpecify V4L2 minor mode number."
+ "\n-1 = use next available (default)"
+ "\n n = use minor number n (integer >= 0)"
+ "\nYou can specify up to "__MODULE_STRING(SN9C102_MAX_DEVICES)
+ " cameras this way."
+ "\nFor example:"
+ "\nvideo_nr=-1,2,-1 would assign minor number 2 to"
+ "\nthe second camera and use auto for the first"
+ "\none and for every other camera."
+ "\n");
+
+static bool force_munmap[] = {[0 ... SN9C102_MAX_DEVICES-1] =
+ SN9C102_FORCE_MUNMAP};
+module_param_array(force_munmap, bool, NULL, 0444);
+MODULE_PARM_DESC(force_munmap,
+ " <0|1[,...]>"
+ "\nForce the application to unmap previously"
+ "\nmapped buffer memory before calling any VIDIOC_S_CROP or"
+ "\nVIDIOC_S_FMT ioctl's. Not all the applications support"
+ "\nthis feature. This parameter is specific for each"
+ "\ndetected camera."
+ "\n0 = do not force memory unmapping"
+ "\n1 = force memory unmapping (save memory)"
+ "\nDefault value is "__MODULE_STRING(SN9C102_FORCE_MUNMAP)"."
+ "\n");
+
+static unsigned int frame_timeout[] = {[0 ... SN9C102_MAX_DEVICES-1] =
+ SN9C102_FRAME_TIMEOUT};
+module_param_array(frame_timeout, uint, NULL, 0644);
+MODULE_PARM_DESC(frame_timeout,
+ " <0|n[,...]>"
+ "\nTimeout for a video frame in seconds before"
+ "\nreturning an I/O error; 0 for infinity."
+ "\nThis parameter is specific for each detected camera."
+ "\nDefault value is "__MODULE_STRING(SN9C102_FRAME_TIMEOUT)"."
+ "\n");
+
+#ifdef SN9C102_DEBUG
+static unsigned short debug = SN9C102_DEBUG_LEVEL;
+module_param(debug, ushort, 0644);
+MODULE_PARM_DESC(debug,
+ " <n>"
+ "\nDebugging information level, from 0 to 3:"
+ "\n0 = none (use carefully)"
+ "\n1 = critical errors"
+ "\n2 = significant informations"
+ "\n3 = more verbose messages"
+ "\nLevel 3 is useful for testing only."
+ "\nDefault value is "__MODULE_STRING(SN9C102_DEBUG_LEVEL)"."
+ "\n");
+#endif
+
+/*
+ Add the probe entries to this table. Be sure to add the entry in the right
+ place, since, on failure, the next probing routine is called according to
+ the order of the list below, from top to bottom.
+*/
+static int (*sn9c102_sensor_table[])(struct sn9c102_device *) = {
+ &sn9c102_probe_hv7131d, /* strong detection based on SENSOR ids */
+ &sn9c102_probe_hv7131r, /* strong detection based on SENSOR ids */
+ &sn9c102_probe_mi0343, /* strong detection based on SENSOR ids */
+ &sn9c102_probe_mi0360, /* strong detection based on SENSOR ids */
+ &sn9c102_probe_mt9v111, /* strong detection based on SENSOR ids */
+ &sn9c102_probe_pas106b, /* strong detection based on SENSOR ids */
+ &sn9c102_probe_pas202bcb, /* strong detection based on SENSOR ids */
+ &sn9c102_probe_ov7630, /* strong detection based on SENSOR ids */
+ &sn9c102_probe_ov7660, /* strong detection based on SENSOR ids */
+ &sn9c102_probe_tas5110c1b, /* detection based on USB pid/vid */
+ &sn9c102_probe_tas5110d, /* detection based on USB pid/vid */
+ &sn9c102_probe_tas5130d1b, /* detection based on USB pid/vid */
+};
+
+/*****************************************************************************/
+
+static u32
+sn9c102_request_buffers(struct sn9c102_device *cam, u32 count,
+ enum sn9c102_io_method io)
+{
+ struct v4l2_pix_format *p = &(cam->sensor.pix_format);
+ struct v4l2_rect *r = &(cam->sensor.cropcap.bounds);
+ size_t imagesize = cam->module_param.force_munmap || io == IO_READ ?
+ (p->width * p->height * p->priv) / 8 :
+ (r->width * r->height * p->priv) / 8;
+ void *buff = NULL;
+ u32 i;
+
+ if (count > SN9C102_MAX_FRAMES)
+ count = SN9C102_MAX_FRAMES;
+
+ if (cam->bridge == BRIDGE_SN9C105 || cam->bridge == BRIDGE_SN9C120)
+ imagesize += 589 + 2; /* length of JPEG header + EOI marker */
+
+ cam->nbuffers = count;
+ while (cam->nbuffers > 0) {
+ buff = vmalloc_32_user(cam->nbuffers * PAGE_ALIGN(imagesize));
+ if (buff)
+ break;
+ cam->nbuffers--;
+ }
+
+ for (i = 0; i < cam->nbuffers; i++) {
+ cam->frame[i].bufmem = buff + i*PAGE_ALIGN(imagesize);
+ cam->frame[i].buf.index = i;
+ cam->frame[i].buf.m.offset = i*PAGE_ALIGN(imagesize);
+ cam->frame[i].buf.length = imagesize;
+ cam->frame[i].buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ cam->frame[i].buf.sequence = 0;
+ cam->frame[i].buf.field = V4L2_FIELD_NONE;
+ cam->frame[i].buf.memory = V4L2_MEMORY_MMAP;
+ cam->frame[i].buf.flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
+ }
+
+ return cam->nbuffers;
+}
+
+
+static void sn9c102_release_buffers(struct sn9c102_device *cam)
+{
+ if (cam->nbuffers) {
+ vfree(cam->frame[0].bufmem);
+ cam->nbuffers = 0;
+ }
+ cam->frame_current = NULL;
+}
+
+
+static void sn9c102_empty_framequeues(struct sn9c102_device *cam)
+{
+ u32 i;
+
+ INIT_LIST_HEAD(&cam->inqueue);
+ INIT_LIST_HEAD(&cam->outqueue);
+
+ for (i = 0; i < SN9C102_MAX_FRAMES; i++) {
+ cam->frame[i].state = F_UNUSED;
+ cam->frame[i].buf.bytesused = 0;
+ }
+}
+
+
+static void sn9c102_requeue_outqueue(struct sn9c102_device *cam)
+{
+ struct sn9c102_frame_t *i;
+
+ list_for_each_entry(i, &cam->outqueue, frame) {
+ i->state = F_QUEUED;
+ list_add(&i->frame, &cam->inqueue);
+ }
+
+ INIT_LIST_HEAD(&cam->outqueue);
+}
+
+
+static void sn9c102_queue_unusedframes(struct sn9c102_device *cam)
+{
+ unsigned long lock_flags;
+ u32 i;
+
+ for (i = 0; i < cam->nbuffers; i++)
+ if (cam->frame[i].state == F_UNUSED) {
+ cam->frame[i].state = F_QUEUED;
+ spin_lock_irqsave(&cam->queue_lock, lock_flags);
+ list_add_tail(&cam->frame[i].frame, &cam->inqueue);
+ spin_unlock_irqrestore(&cam->queue_lock, lock_flags);
+ }
+}
+
+/*****************************************************************************/
+
+/*
+ Write a sequence of count value/register pairs. Returns -1 after the first
+ failed write, or 0 for no errors.
+*/
+int sn9c102_write_regs(struct sn9c102_device *cam, const u8 valreg[][2],
+ int count)
+{
+ struct usb_device *udev = cam->usbdev;
+ u8 *buff = cam->control_buffer;
+ int i, res;
+
+ for (i = 0; i < count; i++) {
+ u8 index = valreg[i][1];
+
+ /*
+ index is a u8, so it must be <256 and can't be out of range.
+ If we put in a check anyway, gcc annoys us with a warning
+ hat our check is useless. People get all uppity when they
+ see warnings in the kernel compile.
+ */
+
+ *buff = valreg[i][0];
+
+ res = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), 0x08,
+ 0x41, index, 0, buff, 1,
+ SN9C102_CTRL_TIMEOUT);
+
+ if (res < 0) {
+ DBG(3, "Failed to write a register (value 0x%02X, "
+ "index 0x%02X, error %d)", *buff, index, res);
+ return -1;
+ }
+
+ cam->reg[index] = *buff;
+ }
+
+ return 0;
+}
+
+
+int sn9c102_write_reg(struct sn9c102_device *cam, u8 value, u16 index)
+{
+ struct usb_device *udev = cam->usbdev;
+ u8 *buff = cam->control_buffer;
+ int res;
+
+ if (index >= ARRAY_SIZE(cam->reg))
+ return -1;
+
+ *buff = value;
+
+ res = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), 0x08, 0x41,
+ index, 0, buff, 1, SN9C102_CTRL_TIMEOUT);
+ if (res < 0) {
+ DBG(3, "Failed to write a register (value 0x%02X, index "
+ "0x%02X, error %d)", value, index, res);
+ return -1;
+ }
+
+ cam->reg[index] = value;
+
+ return 0;
+}
+
+
+/* NOTE: with the SN9C10[123] reading some registers always returns 0 */
+int sn9c102_read_reg(struct sn9c102_device *cam, u16 index)
+{
+ struct usb_device *udev = cam->usbdev;
+ u8 *buff = cam->control_buffer;
+ int res;
+
+ res = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), 0x00, 0xc1,
+ index, 0, buff, 1, SN9C102_CTRL_TIMEOUT);
+ if (res < 0)
+ DBG(3, "Failed to read a register (index 0x%02X, error %d)",
+ index, res);
+
+ return (res >= 0) ? (int)(*buff) : -1;
+}
+
+
+int sn9c102_pread_reg(struct sn9c102_device *cam, u16 index)
+{
+ if (index >= ARRAY_SIZE(cam->reg))
+ return -1;
+
+ return cam->reg[index];
+}
+
+
+static int
+sn9c102_i2c_wait(struct sn9c102_device *cam,
+ const struct sn9c102_sensor *sensor)
+{
+ int i, r;
+
+ for (i = 1; i <= 5; i++) {
+ r = sn9c102_read_reg(cam, 0x08);
+ if (r < 0)
+ return -EIO;
+ if (r & 0x04)
+ return 0;
+ if (sensor->frequency & SN9C102_I2C_400KHZ)
+ udelay(5*16);
+ else
+ udelay(16*16);
+ }
+ return -EBUSY;
+}
+
+
+static int
+sn9c102_i2c_detect_read_error(struct sn9c102_device *cam,
+ const struct sn9c102_sensor *sensor)
+{
+ int r , err = 0;
+
+ r = sn9c102_read_reg(cam, 0x08);
+ if (r < 0)
+ err += r;
+
+ if (cam->bridge == BRIDGE_SN9C101 || cam->bridge == BRIDGE_SN9C102) {
+ if (!(r & 0x08))
+ err += -1;
+ } else {
+ if (r & 0x08)
+ err += -1;
+ }
+
+ return err ? -EIO : 0;
+}
+
+
+static int
+sn9c102_i2c_detect_write_error(struct sn9c102_device *cam,
+ const struct sn9c102_sensor *sensor)
+{
+ int r;
+
+ r = sn9c102_read_reg(cam, 0x08);
+ return (r < 0 || (r >= 0 && (r & 0x08))) ? -EIO : 0;
+}
+
+
+int
+sn9c102_i2c_try_raw_read(struct sn9c102_device *cam,
+ const struct sn9c102_sensor *sensor, u8 data0,
+ u8 data1, u8 n, u8 buffer[])
+{
+ struct usb_device *udev = cam->usbdev;
+ u8 *data = cam->control_buffer;
+ int i = 0, err = 0, res;
+
+ /* Write cycle */
+ data[0] = ((sensor->interface == SN9C102_I2C_2WIRES) ? 0x80 : 0) |
+ ((sensor->frequency & SN9C102_I2C_400KHZ) ? 0x01 : 0) | 0x10;
+ data[1] = data0; /* I2C slave id */
+ data[2] = data1; /* address */
+ data[7] = 0x10;
+ res = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), 0x08, 0x41,
+ 0x08, 0, data, 8, SN9C102_CTRL_TIMEOUT);
+ if (res < 0)
+ err += res;
+
+ err += sn9c102_i2c_wait(cam, sensor);
+
+ /* Read cycle - n bytes */
+ data[0] = ((sensor->interface == SN9C102_I2C_2WIRES) ? 0x80 : 0) |
+ ((sensor->frequency & SN9C102_I2C_400KHZ) ? 0x01 : 0) |
+ (n << 4) | 0x02;
+ data[1] = data0;
+ data[7] = 0x10;
+ res = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), 0x08, 0x41,
+ 0x08, 0, data, 8, SN9C102_CTRL_TIMEOUT);
+ if (res < 0)
+ err += res;
+
+ err += sn9c102_i2c_wait(cam, sensor);
+
+ /* The first read byte will be placed in data[4] */
+ res = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), 0x00, 0xc1,
+ 0x0a, 0, data, 5, SN9C102_CTRL_TIMEOUT);
+ if (res < 0)
+ err += res;
+
+ err += sn9c102_i2c_detect_read_error(cam, sensor);
+
+ PDBGG("I2C read: address 0x%02X, first read byte: 0x%02X", data1,
+ data[4]);
+
+ if (err) {
+ DBG(3, "I2C read failed for %s image sensor", sensor->name);
+ return -1;
+ }
+
+ if (buffer)
+ for (i = 0; i < n && i < 5; i++)
+ buffer[n-i-1] = data[4-i];
+
+ return (int)data[4];
+}
+
+
+int
+sn9c102_i2c_try_raw_write(struct sn9c102_device *cam,
+ const struct sn9c102_sensor *sensor, u8 n, u8 data0,
+ u8 data1, u8 data2, u8 data3, u8 data4, u8 data5)
+{
+ struct usb_device *udev = cam->usbdev;
+ u8 *data = cam->control_buffer;
+ int err = 0, res;
+
+ /* Write cycle. It usually is address + value */
+ data[0] = ((sensor->interface == SN9C102_I2C_2WIRES) ? 0x80 : 0) |
+ ((sensor->frequency & SN9C102_I2C_400KHZ) ? 0x01 : 0)
+ | ((n - 1) << 4);
+ data[1] = data0;
+ data[2] = data1;
+ data[3] = data2;
+ data[4] = data3;
+ data[5] = data4;
+ data[6] = data5;
+ data[7] = 0x17;
+ res = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), 0x08, 0x41,
+ 0x08, 0, data, 8, SN9C102_CTRL_TIMEOUT);
+ if (res < 0)
+ err += res;
+
+ err += sn9c102_i2c_wait(cam, sensor);
+ err += sn9c102_i2c_detect_write_error(cam, sensor);
+
+ if (err)
+ DBG(3, "I2C write failed for %s image sensor", sensor->name);
+
+ PDBGG("I2C raw write: %u bytes, data0 = 0x%02X, data1 = 0x%02X, "
+ "data2 = 0x%02X, data3 = 0x%02X, data4 = 0x%02X, data5 = 0x%02X",
+ n, data0, data1, data2, data3, data4, data5);
+
+ return err ? -1 : 0;
+}
+
+
+int
+sn9c102_i2c_try_read(struct sn9c102_device *cam,
+ const struct sn9c102_sensor *sensor, u8 address)
+{
+ return sn9c102_i2c_try_raw_read(cam, sensor, sensor->i2c_slave_id,
+ address, 1, NULL);
+}
+
+
+static int sn9c102_i2c_try_write(struct sn9c102_device *cam,
+ const struct sn9c102_sensor *sensor,
+ u8 address, u8 value)
+{
+ return sn9c102_i2c_try_raw_write(cam, sensor, 3,
+ sensor->i2c_slave_id, address,
+ value, 0, 0, 0);
+}
+
+
+int sn9c102_i2c_read(struct sn9c102_device *cam, u8 address)
+{
+ return sn9c102_i2c_try_read(cam, &cam->sensor, address);
+}
+
+
+int sn9c102_i2c_write(struct sn9c102_device *cam, u8 address, u8 value)
+{
+ return sn9c102_i2c_try_write(cam, &cam->sensor, address, value);
+}
+
+/*****************************************************************************/
+
+static size_t sn9c102_sof_length(struct sn9c102_device *cam)
+{
+ switch (cam->bridge) {
+ case BRIDGE_SN9C101:
+ case BRIDGE_SN9C102:
+ return 12;
+ case BRIDGE_SN9C103:
+ return 18;
+ case BRIDGE_SN9C105:
+ case BRIDGE_SN9C120:
+ return 62;
+ }
+
+ return 0;
+}
+
+
+static void*
+sn9c102_find_sof_header(struct sn9c102_device *cam, void *mem, size_t len)
+{
+ static const char marker[6] = {0xff, 0xff, 0x00, 0xc4, 0xc4, 0x96};
+ const char *m = mem;
+ size_t soflen = 0, i, j;
+
+ soflen = sn9c102_sof_length(cam);
+
+ for (i = 0; i < len; i++) {
+ size_t b;
+
+ /* Read the variable part of the header */
+ if (unlikely(cam->sof.bytesread >= sizeof(marker))) {
+ cam->sof.header[cam->sof.bytesread] = *(m+i);
+ if (++cam->sof.bytesread == soflen) {
+ cam->sof.bytesread = 0;
+ return mem + i;
+ }
+ continue;
+ }
+
+ /* Search for the SOF marker (fixed part) in the header */
+ for (j = 0, b = cam->sof.bytesread; j+b < sizeof(marker); j++) {
+ if (unlikely(i+j == len))
+ return NULL;
+ if (*(m+i+j) == marker[cam->sof.bytesread]) {
+ cam->sof.header[cam->sof.bytesread] = *(m+i+j);
+ if (++cam->sof.bytesread == sizeof(marker)) {
+ PDBGG("Bytes to analyze: %zd. SOF "
+ "starts at byte #%zd", len, i);
+ i += j+1;
+ break;
+ }
+ } else {
+ cam->sof.bytesread = 0;
+ break;
+ }
+ }
+ }
+
+ return NULL;
+}
+
+
+static void*
+sn9c102_find_eof_header(struct sn9c102_device *cam, void *mem, size_t len)
+{
+ static const u8 eof_header[4][4] = {
+ {0x00, 0x00, 0x00, 0x00},
+ {0x40, 0x00, 0x00, 0x00},
+ {0x80, 0x00, 0x00, 0x00},
+ {0xc0, 0x00, 0x00, 0x00},
+ };
+ size_t i, j;
+
+ /* The EOF header does not exist in compressed data */
+ if (cam->sensor.pix_format.pixelformat == V4L2_PIX_FMT_SN9C10X ||
+ cam->sensor.pix_format.pixelformat == V4L2_PIX_FMT_JPEG)
+ return NULL;
+
+ /*
+ The EOF header might cross the packet boundary, but this is not a
+ problem, since the end of a frame is determined by checking its size
+ in the first place.
+ */
+ for (i = 0; (len >= 4) && (i <= len - 4); i++)
+ for (j = 0; j < ARRAY_SIZE(eof_header); j++)
+ if (!memcmp(mem + i, eof_header[j], 4))
+ return mem + i;
+
+ return NULL;
+}
+
+
+static void
+sn9c102_write_jpegheader(struct sn9c102_device *cam, struct sn9c102_frame_t *f)
+{
+ static const u8 jpeg_header[589] = {
+ 0xff, 0xd8, 0xff, 0xdb, 0x00, 0x84, 0x00, 0x06, 0x04, 0x05,
+ 0x06, 0x05, 0x04, 0x06, 0x06, 0x05, 0x06, 0x07, 0x07, 0x06,
+ 0x08, 0x0a, 0x10, 0x0a, 0x0a, 0x09, 0x09, 0x0a, 0x14, 0x0e,
+ 0x0f, 0x0c, 0x10, 0x17, 0x14, 0x18, 0x18, 0x17, 0x14, 0x16,
+ 0x16, 0x1a, 0x1d, 0x25, 0x1f, 0x1a, 0x1b, 0x23, 0x1c, 0x16,
+ 0x16, 0x20, 0x2c, 0x20, 0x23, 0x26, 0x27, 0x29, 0x2a, 0x29,
+ 0x19, 0x1f, 0x2d, 0x30, 0x2d, 0x28, 0x30, 0x25, 0x28, 0x29,
+ 0x28, 0x01, 0x07, 0x07, 0x07, 0x0a, 0x08, 0x0a, 0x13, 0x0a,
+ 0x0a, 0x13, 0x28, 0x1a, 0x16, 0x1a, 0x28, 0x28, 0x28, 0x28,
+ 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28,
+ 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28,
+ 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28,
+ 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28,
+ 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 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, 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, 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, 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, 0x01, 0xe0, 0x02, 0x80, 0x03, 0x01, 0x21, 0x00, 0x02,
+ 0x11, 0x01, 0x03, 0x11, 0x01, 0xff, 0xda, 0x00, 0x0c, 0x03,
+ 0x01, 0x00, 0x02, 0x11, 0x03, 0x11, 0x00, 0x3f, 0x00
+ };
+ u8 *pos = f->bufmem;
+
+ memcpy(pos, jpeg_header, sizeof(jpeg_header));
+ *(pos + 6) = 0x00;
+ *(pos + 7 + 64) = 0x01;
+ if (cam->compression.quality == 0) {
+ memcpy(pos + 7, SN9C102_Y_QTABLE0, 64);
+ memcpy(pos + 8 + 64, SN9C102_UV_QTABLE0, 64);
+ } else if (cam->compression.quality == 1) {
+ memcpy(pos + 7, SN9C102_Y_QTABLE1, 64);
+ memcpy(pos + 8 + 64, SN9C102_UV_QTABLE1, 64);
+ }
+ *(pos + 564) = cam->sensor.pix_format.width & 0xFF;
+ *(pos + 563) = (cam->sensor.pix_format.width >> 8) & 0xFF;
+ *(pos + 562) = cam->sensor.pix_format.height & 0xFF;
+ *(pos + 561) = (cam->sensor.pix_format.height >> 8) & 0xFF;
+ *(pos + 567) = 0x21;
+
+ f->buf.bytesused += sizeof(jpeg_header);
+}
+
+
+static void sn9c102_urb_complete(struct urb *urb)
+{
+ struct sn9c102_device *cam = urb->context;
+ struct sn9c102_frame_t **f;
+ size_t imagesize, soflen;
+ u8 i;
+ int err = 0;
+
+ if (urb->status == -ENOENT)
+ return;
+
+ f = &cam->frame_current;
+
+ if (cam->stream == STREAM_INTERRUPT) {
+ cam->stream = STREAM_OFF;
+ if ((*f))
+ (*f)->state = F_QUEUED;
+ cam->sof.bytesread = 0;
+ DBG(3, "Stream interrupted by application");
+ wake_up(&cam->wait_stream);
+ }
+
+ if (cam->state & DEV_DISCONNECTED)
+ return;
+
+ if (cam->state & DEV_MISCONFIGURED) {
+ wake_up_interruptible(&cam->wait_frame);
+ return;
+ }
+
+ if (cam->stream == STREAM_OFF || list_empty(&cam->inqueue))
+ goto resubmit_urb;
+
+ if (!(*f))
+ (*f) = list_entry(cam->inqueue.next, struct sn9c102_frame_t,
+ frame);
+
+ imagesize = (cam->sensor.pix_format.width *
+ cam->sensor.pix_format.height *
+ cam->sensor.pix_format.priv) / 8;
+ if (cam->sensor.pix_format.pixelformat == V4L2_PIX_FMT_JPEG)
+ imagesize += 589; /* length of jpeg header */
+ soflen = sn9c102_sof_length(cam);
+
+ for (i = 0; i < urb->number_of_packets; i++) {
+ unsigned int img, len, status;
+ void *pos, *sof, *eof;
+
+ len = urb->iso_frame_desc[i].actual_length;
+ status = urb->iso_frame_desc[i].status;
+ pos = urb->iso_frame_desc[i].offset + urb->transfer_buffer;
+
+ if (status) {
+ DBG(3, "Error in isochronous frame");
+ (*f)->state = F_ERROR;
+ cam->sof.bytesread = 0;
+ continue;
+ }
+
+ PDBGG("Isochrnous frame: length %u, #%u i", len, i);
+
+redo:
+ sof = sn9c102_find_sof_header(cam, pos, len);
+ if (likely(!sof)) {
+ eof = sn9c102_find_eof_header(cam, pos, len);
+ if ((*f)->state == F_GRABBING) {
+end_of_frame:
+ img = len;
+
+ if (eof)
+ img = (eof > pos) ? eof - pos - 1 : 0;
+
+ if ((*f)->buf.bytesused + img > imagesize) {
+ u32 b;
+ b = (*f)->buf.bytesused + img -
+ imagesize;
+ img = imagesize - (*f)->buf.bytesused;
+ PDBGG("Expected EOF not found: video "
+ "frame cut");
+ if (eof)
+ DBG(3, "Exceeded limit: +%u "
+ "bytes", (unsigned)(b));
+ }
+
+ memcpy((*f)->bufmem + (*f)->buf.bytesused, pos,
+ img);
+
+ if ((*f)->buf.bytesused == 0)
+ v4l2_get_timestamp(
+ &(*f)->buf.timestamp);
+
+ (*f)->buf.bytesused += img;
+
+ if ((*f)->buf.bytesused == imagesize ||
+ ((cam->sensor.pix_format.pixelformat ==
+ V4L2_PIX_FMT_SN9C10X ||
+ cam->sensor.pix_format.pixelformat ==
+ V4L2_PIX_FMT_JPEG) && eof)) {
+ u32 b;
+
+ b = (*f)->buf.bytesused;
+ (*f)->state = F_DONE;
+ (*f)->buf.sequence = ++cam->frame_count;
+
+ spin_lock(&cam->queue_lock);
+ list_move_tail(&(*f)->frame,
+ &cam->outqueue);
+ if (!list_empty(&cam->inqueue))
+ (*f) = list_entry(
+ cam->inqueue.next,
+ struct sn9c102_frame_t,
+ frame);
+ else
+ (*f) = NULL;
+ spin_unlock(&cam->queue_lock);
+
+ memcpy(cam->sysfs.frame_header,
+ cam->sof.header, soflen);
+
+ DBG(3, "Video frame captured: %lu "
+ "bytes", (unsigned long)(b));
+
+ if (!(*f))
+ goto resubmit_urb;
+
+ } else if (eof) {
+ (*f)->state = F_ERROR;
+ DBG(3, "Not expected EOF after %lu "
+ "bytes of image data",
+ (unsigned long)
+ ((*f)->buf.bytesused));
+ }
+
+ if (sof) /* (1) */
+ goto start_of_frame;
+
+ } else if (eof) {
+ DBG(3, "EOF without SOF");
+ continue;
+
+ } else {
+ PDBGG("Ignoring pointless isochronous frame");
+ continue;
+ }
+
+ } else if ((*f)->state == F_QUEUED || (*f)->state == F_ERROR) {
+start_of_frame:
+ (*f)->state = F_GRABBING;
+ (*f)->buf.bytesused = 0;
+ len -= (sof - pos);
+ pos = sof;
+ if (cam->sensor.pix_format.pixelformat ==
+ V4L2_PIX_FMT_JPEG)
+ sn9c102_write_jpegheader(cam, (*f));
+ DBG(3, "SOF detected: new video frame");
+ if (len)
+ goto redo;
+
+ } else if ((*f)->state == F_GRABBING) {
+ eof = sn9c102_find_eof_header(cam, pos, len);
+ if (eof && eof < sof)
+ goto end_of_frame; /* (1) */
+ else {
+ if (cam->sensor.pix_format.pixelformat ==
+ V4L2_PIX_FMT_SN9C10X ||
+ cam->sensor.pix_format.pixelformat ==
+ V4L2_PIX_FMT_JPEG) {
+ if (sof - pos >= soflen) {
+ eof = sof - soflen;
+ } else { /* remove header */
+ eof = pos;
+ (*f)->buf.bytesused -=
+ (soflen - (sof - pos));
+ }
+ goto end_of_frame;
+ } else {
+ DBG(3, "SOF before expected EOF after "
+ "%lu bytes of image data",
+ (unsigned long)
+ ((*f)->buf.bytesused));
+ goto start_of_frame;
+ }
+ }
+ }
+ }
+
+resubmit_urb:
+ urb->dev = cam->usbdev;
+ err = usb_submit_urb(urb, GFP_ATOMIC);
+ if (err < 0 && err != -EPERM) {
+ cam->state |= DEV_MISCONFIGURED;
+ DBG(1, "usb_submit_urb() failed");
+ }
+
+ wake_up_interruptible(&cam->wait_frame);
+}
+
+
+static int sn9c102_start_transfer(struct sn9c102_device *cam)
+{
+ struct usb_device *udev = cam->usbdev;
+ struct urb *urb;
+ struct usb_host_interface *altsetting = usb_altnum_to_altsetting(
+ usb_ifnum_to_if(udev, 0),
+ SN9C102_ALTERNATE_SETTING);
+ const unsigned int psz = le16_to_cpu(altsetting->
+ endpoint[0].desc.wMaxPacketSize);
+ s8 i, j;
+ int err = 0;
+
+ for (i = 0; i < SN9C102_URBS; i++) {
+ cam->transfer_buffer[i] = kzalloc(SN9C102_ISO_PACKETS * psz,
+ GFP_KERNEL);
+ if (!cam->transfer_buffer[i]) {
+ err = -ENOMEM;
+ DBG(1, "Not enough memory");
+ goto free_buffers;
+ }
+ }
+
+ for (i = 0; i < SN9C102_URBS; i++) {
+ urb = usb_alloc_urb(SN9C102_ISO_PACKETS, GFP_KERNEL);
+ cam->urb[i] = urb;
+ if (!urb) {
+ err = -ENOMEM;
+ DBG(1, "usb_alloc_urb() failed");
+ goto free_urbs;
+ }
+ urb->dev = udev;
+ urb->context = cam;
+ urb->pipe = usb_rcvisocpipe(udev, 1);
+ urb->transfer_flags = URB_ISO_ASAP;
+ urb->number_of_packets = SN9C102_ISO_PACKETS;
+ urb->complete = sn9c102_urb_complete;
+ urb->transfer_buffer = cam->transfer_buffer[i];
+ urb->transfer_buffer_length = psz * SN9C102_ISO_PACKETS;
+ urb->interval = 1;
+ for (j = 0; j < SN9C102_ISO_PACKETS; j++) {
+ urb->iso_frame_desc[j].offset = psz * j;
+ urb->iso_frame_desc[j].length = psz;
+ }
+ }
+
+ /* Enable video */
+ if (!(cam->reg[0x01] & 0x04)) {
+ err = sn9c102_write_reg(cam, cam->reg[0x01] | 0x04, 0x01);
+ if (err) {
+ err = -EIO;
+ DBG(1, "I/O hardware error");
+ goto free_urbs;
+ }
+ }
+
+ err = usb_set_interface(udev, 0, SN9C102_ALTERNATE_SETTING);
+ if (err) {
+ DBG(1, "usb_set_interface() failed");
+ goto free_urbs;
+ }
+
+ cam->frame_current = NULL;
+ cam->sof.bytesread = 0;
+
+ for (i = 0; i < SN9C102_URBS; i++) {
+ err = usb_submit_urb(cam->urb[i], GFP_KERNEL);
+ if (err) {
+ for (j = i-1; j >= 0; j--)
+ usb_kill_urb(cam->urb[j]);
+ DBG(1, "usb_submit_urb() failed, error %d", err);
+ goto free_urbs;
+ }
+ }
+
+ return 0;
+
+free_urbs:
+ for (i = 0; (i < SN9C102_URBS) && cam->urb[i]; i++)
+ usb_free_urb(cam->urb[i]);
+
+free_buffers:
+ for (i = 0; (i < SN9C102_URBS) && cam->transfer_buffer[i]; i++)
+ kfree(cam->transfer_buffer[i]);
+
+ return err;
+}
+
+
+static int sn9c102_stop_transfer(struct sn9c102_device *cam)
+{
+ struct usb_device *udev = cam->usbdev;
+ s8 i;
+ int err = 0;
+
+ if (cam->state & DEV_DISCONNECTED)
+ return 0;
+
+ for (i = SN9C102_URBS-1; i >= 0; i--) {
+ usb_kill_urb(cam->urb[i]);
+ usb_free_urb(cam->urb[i]);
+ kfree(cam->transfer_buffer[i]);
+ }
+
+ err = usb_set_interface(udev, 0, 0); /* 0 Mb/s */
+ if (err)
+ DBG(3, "usb_set_interface() failed");
+
+ return err;
+}
+
+
+static int sn9c102_stream_interrupt(struct sn9c102_device *cam)
+{
+ cam->stream = STREAM_INTERRUPT;
+ wait_event_timeout(cam->wait_stream,
+ (cam->stream == STREAM_OFF) ||
+ (cam->state & DEV_DISCONNECTED),
+ SN9C102_URB_TIMEOUT);
+ if (cam->state & DEV_DISCONNECTED)
+ return -ENODEV;
+ else if (cam->stream != STREAM_OFF) {
+ cam->state |= DEV_MISCONFIGURED;
+ DBG(1, "URB timeout reached. The camera is misconfigured. "
+ "To use it, close and open %s again.",
+ video_device_node_name(cam->v4ldev));
+ return -EIO;
+ }
+
+ return 0;
+}
+
+/*****************************************************************************/
+
+#ifdef CONFIG_VIDEO_ADV_DEBUG
+static u16 sn9c102_strtou16(const char *buff, size_t len, ssize_t *count)
+{
+ char str[7];
+ char *endp;
+ unsigned long val;
+
+ if (len < 6) {
+ strncpy(str, buff, len);
+ str[len] = '\0';
+ } else {
+ strncpy(str, buff, 6);
+ str[6] = '\0';
+ }
+
+ val = simple_strtoul(str, &endp, 0);
+
+ *count = 0;
+ if (val <= 0xffff)
+ *count = (ssize_t)(endp - str);
+ if ((*count) && (len == *count+1) && (buff[*count] == '\n'))
+ *count += 1;
+
+ return (u16)val;
+}
+
+/*
+ NOTE 1: being inside one of the following methods implies that the v4l
+ device exists for sure (see kobjects and reference counters)
+ NOTE 2: buffers are PAGE_SIZE long
+*/
+
+static ssize_t sn9c102_show_reg(struct device *cd,
+ struct device_attribute *attr, char *buf)
+{
+ struct sn9c102_device *cam;
+ ssize_t count;
+
+ if (mutex_lock_interruptible(&sn9c102_sysfs_lock))
+ return -ERESTARTSYS;
+
+ cam = video_get_drvdata(container_of(cd, struct video_device, dev));
+ if (!cam) {
+ mutex_unlock(&sn9c102_sysfs_lock);
+ return -ENODEV;
+ }
+
+ count = sprintf(buf, "%u\n", cam->sysfs.reg);
+
+ mutex_unlock(&sn9c102_sysfs_lock);
+
+ return count;
+}
+
+
+static ssize_t
+sn9c102_store_reg(struct device *cd, struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ struct sn9c102_device *cam;
+ u16 index;
+ ssize_t count;
+
+ if (mutex_lock_interruptible(&sn9c102_sysfs_lock))
+ return -ERESTARTSYS;
+
+ cam = video_get_drvdata(container_of(cd, struct video_device, dev));
+ if (!cam) {
+ mutex_unlock(&sn9c102_sysfs_lock);
+ return -ENODEV;
+ }
+
+ index = sn9c102_strtou16(buf, len, &count);
+ if (index >= ARRAY_SIZE(cam->reg) || !count) {
+ mutex_unlock(&sn9c102_sysfs_lock);
+ return -EINVAL;
+ }
+
+ cam->sysfs.reg = index;
+
+ DBG(2, "Moved SN9C1XX register index to 0x%02X", cam->sysfs.reg);
+ DBG(3, "Written bytes: %zd", count);
+
+ mutex_unlock(&sn9c102_sysfs_lock);
+
+ return count;
+}
+
+
+static ssize_t sn9c102_show_val(struct device *cd,
+ struct device_attribute *attr, char *buf)
+{
+ struct sn9c102_device *cam;
+ ssize_t count;
+ int val;
+
+ if (mutex_lock_interruptible(&sn9c102_sysfs_lock))
+ return -ERESTARTSYS;
+
+ cam = video_get_drvdata(container_of(cd, struct video_device, dev));
+ if (!cam) {
+ mutex_unlock(&sn9c102_sysfs_lock);
+ return -ENODEV;
+ }
+
+ val = sn9c102_read_reg(cam, cam->sysfs.reg);
+ if (val < 0) {
+ mutex_unlock(&sn9c102_sysfs_lock);
+ return -EIO;
+ }
+
+ count = sprintf(buf, "%d\n", val);
+
+ DBG(3, "Read bytes: %zd, value: %d", count, val);
+
+ mutex_unlock(&sn9c102_sysfs_lock);
+
+ return count;
+}
+
+
+static ssize_t
+sn9c102_store_val(struct device *cd, struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ struct sn9c102_device *cam;
+ u16 value;
+ ssize_t count;
+ int err;
+
+ if (mutex_lock_interruptible(&sn9c102_sysfs_lock))
+ return -ERESTARTSYS;
+
+ cam = video_get_drvdata(container_of(cd, struct video_device, dev));
+ if (!cam) {
+ mutex_unlock(&sn9c102_sysfs_lock);
+ return -ENODEV;
+ }
+
+ value = sn9c102_strtou16(buf, len, &count);
+ if (!count) {
+ mutex_unlock(&sn9c102_sysfs_lock);
+ return -EINVAL;
+ }
+
+ err = sn9c102_write_reg(cam, value, cam->sysfs.reg);
+ if (err) {
+ mutex_unlock(&sn9c102_sysfs_lock);
+ return -EIO;
+ }
+
+ DBG(2, "Written SN9C1XX reg. 0x%02X, val. 0x%02X",
+ cam->sysfs.reg, value);
+ DBG(3, "Written bytes: %zd", count);
+
+ mutex_unlock(&sn9c102_sysfs_lock);
+
+ return count;
+}
+
+
+static ssize_t sn9c102_show_i2c_reg(struct device *cd,
+ struct device_attribute *attr, char *buf)
+{
+ struct sn9c102_device *cam;
+ ssize_t count;
+
+ if (mutex_lock_interruptible(&sn9c102_sysfs_lock))
+ return -ERESTARTSYS;
+
+ cam = video_get_drvdata(container_of(cd, struct video_device, dev));
+ if (!cam) {
+ mutex_unlock(&sn9c102_sysfs_lock);
+ return -ENODEV;
+ }
+
+ count = sprintf(buf, "%u\n", cam->sysfs.i2c_reg);
+
+ DBG(3, "Read bytes: %zd", count);
+
+ mutex_unlock(&sn9c102_sysfs_lock);
+
+ return count;
+}
+
+
+static ssize_t
+sn9c102_store_i2c_reg(struct device *cd, struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ struct sn9c102_device *cam;
+ u16 index;
+ ssize_t count;
+
+ if (mutex_lock_interruptible(&sn9c102_sysfs_lock))
+ return -ERESTARTSYS;
+
+ cam = video_get_drvdata(container_of(cd, struct video_device, dev));
+ if (!cam) {
+ mutex_unlock(&sn9c102_sysfs_lock);
+ return -ENODEV;
+ }
+
+ index = sn9c102_strtou16(buf, len, &count);
+ if (!count) {
+ mutex_unlock(&sn9c102_sysfs_lock);
+ return -EINVAL;
+ }
+
+ cam->sysfs.i2c_reg = index;
+
+ DBG(2, "Moved sensor register index to 0x%02X", cam->sysfs.i2c_reg);
+ DBG(3, "Written bytes: %zd", count);
+
+ mutex_unlock(&sn9c102_sysfs_lock);
+
+ return count;
+}
+
+
+static ssize_t sn9c102_show_i2c_val(struct device *cd,
+ struct device_attribute *attr, char *buf)
+{
+ struct sn9c102_device *cam;
+ ssize_t count;
+ int val;
+
+ if (mutex_lock_interruptible(&sn9c102_sysfs_lock))
+ return -ERESTARTSYS;
+
+ cam = video_get_drvdata(container_of(cd, struct video_device, dev));
+ if (!cam) {
+ mutex_unlock(&sn9c102_sysfs_lock);
+ return -ENODEV;
+ }
+
+ if (!(cam->sensor.sysfs_ops & SN9C102_I2C_READ)) {
+ mutex_unlock(&sn9c102_sysfs_lock);
+ return -ENOSYS;
+ }
+
+ val = sn9c102_i2c_read(cam, cam->sysfs.i2c_reg);
+ if (val < 0) {
+ mutex_unlock(&sn9c102_sysfs_lock);
+ return -EIO;
+ }
+
+ count = sprintf(buf, "%d\n", val);
+
+ DBG(3, "Read bytes: %zd, value: %d", count, val);
+
+ mutex_unlock(&sn9c102_sysfs_lock);
+
+ return count;
+}
+
+
+static ssize_t
+sn9c102_store_i2c_val(struct device *cd, struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ struct sn9c102_device *cam;
+ u16 value;
+ ssize_t count;
+ int err;
+
+ if (mutex_lock_interruptible(&sn9c102_sysfs_lock))
+ return -ERESTARTSYS;
+
+ cam = video_get_drvdata(container_of(cd, struct video_device, dev));
+ if (!cam) {
+ mutex_unlock(&sn9c102_sysfs_lock);
+ return -ENODEV;
+ }
+
+ if (!(cam->sensor.sysfs_ops & SN9C102_I2C_WRITE)) {
+ mutex_unlock(&sn9c102_sysfs_lock);
+ return -ENOSYS;
+ }
+
+ value = sn9c102_strtou16(buf, len, &count);
+ if (!count) {
+ mutex_unlock(&sn9c102_sysfs_lock);
+ return -EINVAL;
+ }
+
+ err = sn9c102_i2c_write(cam, cam->sysfs.i2c_reg, value);
+ if (err) {
+ mutex_unlock(&sn9c102_sysfs_lock);
+ return -EIO;
+ }
+
+ DBG(2, "Written sensor reg. 0x%02X, val. 0x%02X",
+ cam->sysfs.i2c_reg, value);
+ DBG(3, "Written bytes: %zd", count);
+
+ mutex_unlock(&sn9c102_sysfs_lock);
+
+ return count;
+}
+
+
+static ssize_t
+sn9c102_store_green(struct device *cd, struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ struct sn9c102_device *cam;
+ enum sn9c102_bridge bridge;
+ ssize_t res = 0;
+ u16 value;
+ ssize_t count;
+
+ if (mutex_lock_interruptible(&sn9c102_sysfs_lock))
+ return -ERESTARTSYS;
+
+ cam = video_get_drvdata(container_of(cd, struct video_device, dev));
+ if (!cam) {
+ mutex_unlock(&sn9c102_sysfs_lock);
+ return -ENODEV;
+ }
+
+ bridge = cam->bridge;
+
+ mutex_unlock(&sn9c102_sysfs_lock);
+
+ value = sn9c102_strtou16(buf, len, &count);
+ if (!count)
+ return -EINVAL;
+
+ switch (bridge) {
+ case BRIDGE_SN9C101:
+ case BRIDGE_SN9C102:
+ if (value > 0x0f)
+ return -EINVAL;
+ res = sn9c102_store_reg(cd, attr, "0x11", 4);
+ if (res >= 0)
+ res = sn9c102_store_val(cd, attr, buf, len);
+ break;
+ case BRIDGE_SN9C103:
+ case BRIDGE_SN9C105:
+ case BRIDGE_SN9C120:
+ if (value > 0x7f)
+ return -EINVAL;
+ res = sn9c102_store_reg(cd, attr, "0x07", 4);
+ if (res >= 0)
+ res = sn9c102_store_val(cd, attr, buf, len);
+ break;
+ }
+
+ return res;
+}
+
+
+static ssize_t
+sn9c102_store_blue(struct device *cd, struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ ssize_t res = 0;
+ u16 value;
+ ssize_t count;
+
+ value = sn9c102_strtou16(buf, len, &count);
+ if (!count || value > 0x7f)
+ return -EINVAL;
+
+ res = sn9c102_store_reg(cd, attr, "0x06", 4);
+ if (res >= 0)
+ res = sn9c102_store_val(cd, attr, buf, len);
+
+ return res;
+}
+
+
+static ssize_t
+sn9c102_store_red(struct device *cd, struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ ssize_t res = 0;
+ u16 value;
+ ssize_t count;
+
+ value = sn9c102_strtou16(buf, len, &count);
+ if (!count || value > 0x7f)
+ return -EINVAL;
+ res = sn9c102_store_reg(cd, attr, "0x05", 4);
+ if (res >= 0)
+ res = sn9c102_store_val(cd, attr, buf, len);
+
+ return res;
+}
+
+
+static ssize_t sn9c102_show_frame_header(struct device *cd,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct sn9c102_device *cam;
+ ssize_t count;
+
+ cam = video_get_drvdata(container_of(cd, struct video_device, dev));
+ if (!cam)
+ return -ENODEV;
+
+ count = sizeof(cam->sysfs.frame_header);
+ memcpy(buf, cam->sysfs.frame_header, count);
+
+ DBG(3, "Frame header, read bytes: %zd", count);
+
+ return count;
+}
+
+
+static DEVICE_ATTR(reg, S_IRUGO | S_IWUSR, sn9c102_show_reg, sn9c102_store_reg);
+static DEVICE_ATTR(val, S_IRUGO | S_IWUSR, sn9c102_show_val, sn9c102_store_val);
+static DEVICE_ATTR(i2c_reg, S_IRUGO | S_IWUSR,
+ sn9c102_show_i2c_reg, sn9c102_store_i2c_reg);
+static DEVICE_ATTR(i2c_val, S_IRUGO | S_IWUSR,
+ sn9c102_show_i2c_val, sn9c102_store_i2c_val);
+static DEVICE_ATTR(green, S_IWUSR, NULL, sn9c102_store_green);
+static DEVICE_ATTR(blue, S_IWUSR, NULL, sn9c102_store_blue);
+static DEVICE_ATTR(red, S_IWUSR, NULL, sn9c102_store_red);
+static DEVICE_ATTR(frame_header, S_IRUGO, sn9c102_show_frame_header, NULL);
+
+
+static int sn9c102_create_sysfs(struct sn9c102_device *cam)
+{
+ struct device *dev = &(cam->v4ldev->dev);
+ int err = 0;
+
+ err = device_create_file(dev, &dev_attr_reg);
+ if (err)
+ goto err_out;
+ err = device_create_file(dev, &dev_attr_val);
+ if (err)
+ goto err_reg;
+ err = device_create_file(dev, &dev_attr_frame_header);
+ if (err)
+ goto err_val;
+
+ if (cam->sensor.sysfs_ops) {
+ err = device_create_file(dev, &dev_attr_i2c_reg);
+ if (err)
+ goto err_frame_header;
+ err = device_create_file(dev, &dev_attr_i2c_val);
+ if (err)
+ goto err_i2c_reg;
+ }
+
+ if (cam->bridge == BRIDGE_SN9C101 || cam->bridge == BRIDGE_SN9C102) {
+ err = device_create_file(dev, &dev_attr_green);
+ if (err)
+ goto err_i2c_val;
+ } else {
+ err = device_create_file(dev, &dev_attr_blue);
+ if (err)
+ goto err_i2c_val;
+ err = device_create_file(dev, &dev_attr_red);
+ if (err)
+ goto err_blue;
+ }
+
+ return 0;
+
+err_blue:
+ device_remove_file(dev, &dev_attr_blue);
+err_i2c_val:
+ if (cam->sensor.sysfs_ops)
+ device_remove_file(dev, &dev_attr_i2c_val);
+err_i2c_reg:
+ if (cam->sensor.sysfs_ops)
+ device_remove_file(dev, &dev_attr_i2c_reg);
+err_frame_header:
+ device_remove_file(dev, &dev_attr_frame_header);
+err_val:
+ device_remove_file(dev, &dev_attr_val);
+err_reg:
+ device_remove_file(dev, &dev_attr_reg);
+err_out:
+ return err;
+}
+#endif /* CONFIG_VIDEO_ADV_DEBUG */
+
+/*****************************************************************************/
+
+static int
+sn9c102_set_pix_format(struct sn9c102_device *cam, struct v4l2_pix_format *pix)
+{
+ int err = 0;
+
+ if (pix->pixelformat == V4L2_PIX_FMT_SN9C10X ||
+ pix->pixelformat == V4L2_PIX_FMT_JPEG) {
+ switch (cam->bridge) {
+ case BRIDGE_SN9C101:
+ case BRIDGE_SN9C102:
+ case BRIDGE_SN9C103:
+ err += sn9c102_write_reg(cam, cam->reg[0x18] | 0x80,
+ 0x18);
+ break;
+ case BRIDGE_SN9C105:
+ case BRIDGE_SN9C120:
+ err += sn9c102_write_reg(cam, cam->reg[0x18] & 0x7f,
+ 0x18);
+ break;
+ }
+ } else {
+ switch (cam->bridge) {
+ case BRIDGE_SN9C101:
+ case BRIDGE_SN9C102:
+ case BRIDGE_SN9C103:
+ err += sn9c102_write_reg(cam, cam->reg[0x18] & 0x7f,
+ 0x18);
+ break;
+ case BRIDGE_SN9C105:
+ case BRIDGE_SN9C120:
+ err += sn9c102_write_reg(cam, cam->reg[0x18] | 0x80,
+ 0x18);
+ break;
+ }
+ }
+
+ return err ? -EIO : 0;
+}
+
+
+static int
+sn9c102_set_compression(struct sn9c102_device *cam,
+ struct v4l2_jpegcompression *compression)
+{
+ int i, err = 0;
+
+ switch (cam->bridge) {
+ case BRIDGE_SN9C101:
+ case BRIDGE_SN9C102:
+ case BRIDGE_SN9C103:
+ if (compression->quality == 0)
+ err += sn9c102_write_reg(cam, cam->reg[0x17] | 0x01,
+ 0x17);
+ else if (compression->quality == 1)
+ err += sn9c102_write_reg(cam, cam->reg[0x17] & 0xfe,
+ 0x17);
+ break;
+ case BRIDGE_SN9C105:
+ case BRIDGE_SN9C120:
+ if (compression->quality == 0) {
+ for (i = 0; i <= 63; i++) {
+ err += sn9c102_write_reg(cam,
+ SN9C102_Y_QTABLE1[i],
+ 0x100 + i);
+ err += sn9c102_write_reg(cam,
+ SN9C102_UV_QTABLE1[i],
+ 0x140 + i);
+ }
+ err += sn9c102_write_reg(cam, cam->reg[0x18] & 0xbf,
+ 0x18);
+ } else if (compression->quality == 1) {
+ for (i = 0; i <= 63; i++) {
+ err += sn9c102_write_reg(cam,
+ SN9C102_Y_QTABLE1[i],
+ 0x100 + i);
+ err += sn9c102_write_reg(cam,
+ SN9C102_UV_QTABLE1[i],
+ 0x140 + i);
+ }
+ err += sn9c102_write_reg(cam, cam->reg[0x18] | 0x40,
+ 0x18);
+ }
+ break;
+ }
+
+ return err ? -EIO : 0;
+}
+
+
+static int sn9c102_set_scale(struct sn9c102_device *cam, u8 scale)
+{
+ u8 r = 0;
+ int err = 0;
+
+ if (scale == 1)
+ r = cam->reg[0x18] & 0xcf;
+ else if (scale == 2) {
+ r = cam->reg[0x18] & 0xcf;
+ r |= 0x10;
+ } else if (scale == 4)
+ r = cam->reg[0x18] | 0x20;
+
+ err += sn9c102_write_reg(cam, r, 0x18);
+ if (err)
+ return -EIO;
+
+ PDBGG("Scaling factor: %u", scale);
+
+ return 0;
+}
+
+
+static int sn9c102_set_crop(struct sn9c102_device *cam, struct v4l2_rect *rect)
+{
+ struct sn9c102_sensor *s = &cam->sensor;
+ u8 h_start = (u8)(rect->left - s->cropcap.bounds.left),
+ v_start = (u8)(rect->top - s->cropcap.bounds.top),
+ h_size = (u8)(rect->width / 16),
+ v_size = (u8)(rect->height / 16);
+ int err = 0;
+
+ err += sn9c102_write_reg(cam, h_start, 0x12);
+ err += sn9c102_write_reg(cam, v_start, 0x13);
+ err += sn9c102_write_reg(cam, h_size, 0x15);
+ err += sn9c102_write_reg(cam, v_size, 0x16);
+ if (err)
+ return -EIO;
+
+ PDBGG("h_start, v_start, h_size, v_size, ho_size, vo_size "
+ "%u %u %u %u", h_start, v_start, h_size, v_size);
+
+ return 0;
+}
+
+
+static int sn9c102_init(struct sn9c102_device *cam)
+{
+ struct sn9c102_sensor *s = &cam->sensor;
+ struct v4l2_control ctrl;
+ struct v4l2_queryctrl *qctrl;
+ struct v4l2_rect *rect;
+ u8 i = 0;
+ int err = 0;
+
+ if (!(cam->state & DEV_INITIALIZED)) {
+ mutex_init(&cam->open_mutex);
+ init_waitqueue_head(&cam->wait_open);
+ qctrl = s->qctrl;
+ rect = &(s->cropcap.defrect);
+ } else { /* use current values */
+ qctrl = s->_qctrl;
+ rect = &(s->_rect);
+ }
+
+ err += sn9c102_set_scale(cam, rect->width / s->pix_format.width);
+ err += sn9c102_set_crop(cam, rect);
+ if (err)
+ return err;
+
+ if (s->init) {
+ err = s->init(cam);
+ if (err) {
+ DBG(3, "Sensor initialization failed");
+ return err;
+ }
+ }
+
+ if (!(cam->state & DEV_INITIALIZED))
+ if (cam->bridge == BRIDGE_SN9C101 ||
+ cam->bridge == BRIDGE_SN9C102 ||
+ cam->bridge == BRIDGE_SN9C103) {
+ if (s->pix_format.pixelformat == V4L2_PIX_FMT_JPEG)
+ s->pix_format.pixelformat = V4L2_PIX_FMT_SBGGR8;
+ cam->compression.quality = cam->reg[0x17] & 0x01 ?
+ 0 : 1;
+ } else {
+ if (s->pix_format.pixelformat == V4L2_PIX_FMT_SN9C10X)
+ s->pix_format.pixelformat = V4L2_PIX_FMT_JPEG;
+ cam->compression.quality = cam->reg[0x18] & 0x40 ?
+ 0 : 1;
+ err += sn9c102_set_compression(cam, &cam->compression);
+ }
+ else
+ err += sn9c102_set_compression(cam, &cam->compression);
+ err += sn9c102_set_pix_format(cam, &s->pix_format);
+ if (s->set_pix_format)
+ err += s->set_pix_format(cam, &s->pix_format);
+ if (err)
+ return err;
+
+ if (s->pix_format.pixelformat == V4L2_PIX_FMT_SN9C10X ||
+ s->pix_format.pixelformat == V4L2_PIX_FMT_JPEG)
+ DBG(3, "Compressed video format is active, quality %d",
+ cam->compression.quality);
+ else
+ DBG(3, "Uncompressed video format is active");
+
+ if (s->set_crop) {
+ err = s->set_crop(cam, rect);
+ if (err) {
+ DBG(3, "set_crop() failed");
+ return err;
+ }
+ }
+
+ if (s->set_ctrl) {
+ for (i = 0; i < ARRAY_SIZE(s->qctrl); i++)
+ if (s->qctrl[i].id != 0 &&
+ !(s->qctrl[i].flags & V4L2_CTRL_FLAG_DISABLED)) {
+ ctrl.id = s->qctrl[i].id;
+ ctrl.value = qctrl[i].default_value;
+ err = s->set_ctrl(cam, &ctrl);
+ if (err) {
+ DBG(3, "Set %s control failed",
+ s->qctrl[i].name);
+ return err;
+ }
+ DBG(3, "Image sensor supports '%s' control",
+ s->qctrl[i].name);
+ }
+ }
+
+ if (!(cam->state & DEV_INITIALIZED)) {
+ mutex_init(&cam->fileop_mutex);
+ spin_lock_init(&cam->queue_lock);
+ init_waitqueue_head(&cam->wait_frame);
+ init_waitqueue_head(&cam->wait_stream);
+ cam->nreadbuffers = 2;
+ memcpy(s->_qctrl, s->qctrl, sizeof(s->qctrl));
+ memcpy(&(s->_rect), &(s->cropcap.defrect),
+ sizeof(struct v4l2_rect));
+ cam->state |= DEV_INITIALIZED;
+ }
+
+ DBG(2, "Initialization succeeded");
+ return 0;
+}
+
+/*****************************************************************************/
+
+static void sn9c102_release_resources(struct kref *kref)
+{
+ struct sn9c102_device *cam;
+
+ mutex_lock(&sn9c102_sysfs_lock);
+
+ cam = container_of(kref, struct sn9c102_device, kref);
+
+ DBG(2, "V4L2 device %s deregistered",
+ video_device_node_name(cam->v4ldev));
+ video_set_drvdata(cam->v4ldev, NULL);
+ video_unregister_device(cam->v4ldev);
+ v4l2_device_unregister(&cam->v4l2_dev);
+ usb_put_dev(cam->usbdev);
+ kfree(cam->control_buffer);
+ kfree(cam);
+
+ mutex_unlock(&sn9c102_sysfs_lock);
+
+}
+
+
+static int sn9c102_open(struct file *filp)
+{
+ struct sn9c102_device *cam;
+ int err = 0;
+
+ /*
+ A read_trylock() in open() is the only safe way to prevent race
+ conditions with disconnect(), one close() and multiple (not
+ necessarily simultaneous) attempts to open(). For example, it
+ prevents from waiting for a second access, while the device
+ structure is being deallocated, after a possible disconnect() and
+ during a following close() holding the write lock: given that, after
+ this deallocation, no access will be possible anymore, using the
+ non-trylock version would have let open() gain the access to the
+ device structure improperly.
+ For this reason the lock must also not be per-device.
+ */
+ if (!down_read_trylock(&sn9c102_dev_lock))
+ return -ERESTARTSYS;
+
+ cam = video_drvdata(filp);
+
+ if (wait_for_completion_interruptible(&cam->probe)) {
+ up_read(&sn9c102_dev_lock);
+ return -ERESTARTSYS;
+ }
+
+ kref_get(&cam->kref);
+
+ /*
+ Make sure to isolate all the simultaneous opens.
+ */
+ if (mutex_lock_interruptible(&cam->open_mutex)) {
+ kref_put(&cam->kref, sn9c102_release_resources);
+ up_read(&sn9c102_dev_lock);
+ return -ERESTARTSYS;
+ }
+
+ if (cam->state & DEV_DISCONNECTED) {
+ DBG(1, "Device not present");
+ err = -ENODEV;
+ goto out;
+ }
+
+ if (cam->users) {
+ DBG(2, "Device %s is already in use",
+ video_device_node_name(cam->v4ldev));
+ DBG(3, "Simultaneous opens are not supported");
+ /*
+ open() must follow the open flags and should block
+ eventually while the device is in use.
+ */
+ if ((filp->f_flags & O_NONBLOCK) ||
+ (filp->f_flags & O_NDELAY)) {
+ err = -EWOULDBLOCK;
+ goto out;
+ }
+ DBG(2, "A blocking open() has been requested. Wait for the "
+ "device to be released...");
+ up_read(&sn9c102_dev_lock);
+ /*
+ We will not release the "open_mutex" lock, so that only one
+ process can be in the wait queue below. This way the process
+ will be sleeping while holding the lock, without losing its
+ priority after any wake_up().
+ */
+ err = wait_event_interruptible_exclusive(cam->wait_open,
+ (cam->state & DEV_DISCONNECTED)
+ || !cam->users);
+ down_read(&sn9c102_dev_lock);
+ if (err)
+ goto out;
+ if (cam->state & DEV_DISCONNECTED) {
+ err = -ENODEV;
+ goto out;
+ }
+ }
+
+ if (cam->state & DEV_MISCONFIGURED) {
+ err = sn9c102_init(cam);
+ if (err) {
+ DBG(1, "Initialization failed again. "
+ "I will retry on next open().");
+ goto out;
+ }
+ cam->state &= ~DEV_MISCONFIGURED;
+ }
+
+ err = sn9c102_start_transfer(cam);
+ if (err)
+ goto out;
+
+ filp->private_data = cam;
+ cam->users++;
+ cam->io = IO_NONE;
+ cam->stream = STREAM_OFF;
+ cam->nbuffers = 0;
+ cam->frame_count = 0;
+ sn9c102_empty_framequeues(cam);
+
+ DBG(3, "Video device %s is open", video_device_node_name(cam->v4ldev));
+
+out:
+ mutex_unlock(&cam->open_mutex);
+ if (err)
+ kref_put(&cam->kref, sn9c102_release_resources);
+
+ up_read(&sn9c102_dev_lock);
+ return err;
+}
+
+
+static int sn9c102_release(struct file *filp)
+{
+ struct sn9c102_device *cam;
+
+ down_write(&sn9c102_dev_lock);
+
+ cam = video_drvdata(filp);
+
+ sn9c102_stop_transfer(cam);
+ sn9c102_release_buffers(cam);
+ cam->users--;
+ wake_up_interruptible_nr(&cam->wait_open, 1);
+
+ DBG(3, "Video device %s closed", video_device_node_name(cam->v4ldev));
+
+ kref_put(&cam->kref, sn9c102_release_resources);
+
+ up_write(&sn9c102_dev_lock);
+
+ return 0;
+}
+
+
+static ssize_t
+sn9c102_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
+{
+ struct sn9c102_device *cam = video_drvdata(filp);
+ struct sn9c102_frame_t *f, *i;
+ unsigned long lock_flags;
+ long timeout;
+ int err = 0;
+
+ if (mutex_lock_interruptible(&cam->fileop_mutex))
+ return -ERESTARTSYS;
+
+ if (cam->state & DEV_DISCONNECTED) {
+ DBG(1, "Device not present");
+ mutex_unlock(&cam->fileop_mutex);
+ return -ENODEV;
+ }
+
+ if (cam->state & DEV_MISCONFIGURED) {
+ DBG(1, "The camera is misconfigured. Close and open it "
+ "again.");
+ mutex_unlock(&cam->fileop_mutex);
+ return -EIO;
+ }
+
+ if (cam->io == IO_MMAP) {
+ DBG(3, "Close and open the device again to choose "
+ "the read method");
+ mutex_unlock(&cam->fileop_mutex);
+ return -EBUSY;
+ }
+
+ if (cam->io == IO_NONE) {
+ if (!sn9c102_request_buffers(cam, cam->nreadbuffers, IO_READ)) {
+ DBG(1, "read() failed, not enough memory");
+ mutex_unlock(&cam->fileop_mutex);
+ return -ENOMEM;
+ }
+ cam->io = IO_READ;
+ cam->stream = STREAM_ON;
+ }
+
+ if (list_empty(&cam->inqueue)) {
+ if (!list_empty(&cam->outqueue))
+ sn9c102_empty_framequeues(cam);
+ sn9c102_queue_unusedframes(cam);
+ }
+
+ if (!count) {
+ mutex_unlock(&cam->fileop_mutex);
+ return 0;
+ }
+
+ if (list_empty(&cam->outqueue)) {
+ if (filp->f_flags & O_NONBLOCK) {
+ mutex_unlock(&cam->fileop_mutex);
+ return -EAGAIN;
+ }
+ if (!cam->module_param.frame_timeout) {
+ err = wait_event_interruptible
+ (cam->wait_frame,
+ (!list_empty(&cam->outqueue)) ||
+ (cam->state & DEV_DISCONNECTED) ||
+ (cam->state & DEV_MISCONFIGURED));
+ if (err) {
+ mutex_unlock(&cam->fileop_mutex);
+ return err;
+ }
+ } else {
+ timeout = wait_event_interruptible_timeout
+ (cam->wait_frame,
+ (!list_empty(&cam->outqueue)) ||
+ (cam->state & DEV_DISCONNECTED) ||
+ (cam->state & DEV_MISCONFIGURED),
+ msecs_to_jiffies(
+ cam->module_param.frame_timeout * 1000
+ )
+ );
+ if (timeout < 0) {
+ mutex_unlock(&cam->fileop_mutex);
+ return timeout;
+ } else if (timeout == 0 &&
+ !(cam->state & DEV_DISCONNECTED)) {
+ DBG(1, "Video frame timeout elapsed");
+ mutex_unlock(&cam->fileop_mutex);
+ return -EIO;
+ }
+ }
+ if (cam->state & DEV_DISCONNECTED) {
+ mutex_unlock(&cam->fileop_mutex);
+ return -ENODEV;
+ }
+ if (cam->state & DEV_MISCONFIGURED) {
+ mutex_unlock(&cam->fileop_mutex);
+ return -EIO;
+ }
+ }
+
+ f = list_entry(cam->outqueue.prev, struct sn9c102_frame_t, frame);
+
+ if (count > f->buf.bytesused)
+ count = f->buf.bytesused;
+
+ if (copy_to_user(buf, f->bufmem, count)) {
+ err = -EFAULT;
+ goto exit;
+ }
+ *f_pos += count;
+
+exit:
+ spin_lock_irqsave(&cam->queue_lock, lock_flags);
+ list_for_each_entry(i, &cam->outqueue, frame)
+ i->state = F_UNUSED;
+ INIT_LIST_HEAD(&cam->outqueue);
+ spin_unlock_irqrestore(&cam->queue_lock, lock_flags);
+
+ sn9c102_queue_unusedframes(cam);
+
+ PDBGG("Frame #%lu, bytes read: %zu",
+ (unsigned long)f->buf.index, count);
+
+ mutex_unlock(&cam->fileop_mutex);
+
+ return count;
+}
+
+
+static unsigned int sn9c102_poll(struct file *filp, poll_table *wait)
+{
+ struct sn9c102_device *cam = video_drvdata(filp);
+ struct sn9c102_frame_t *f;
+ unsigned long lock_flags;
+ unsigned int mask = 0;
+
+ if (mutex_lock_interruptible(&cam->fileop_mutex))
+ return POLLERR;
+
+ if (cam->state & DEV_DISCONNECTED) {
+ DBG(1, "Device not present");
+ goto error;
+ }
+
+ if (cam->state & DEV_MISCONFIGURED) {
+ DBG(1, "The camera is misconfigured. Close and open it "
+ "again.");
+ goto error;
+ }
+
+ if (cam->io == IO_NONE) {
+ if (!sn9c102_request_buffers(cam, cam->nreadbuffers,
+ IO_READ)) {
+ DBG(1, "poll() failed, not enough memory");
+ goto error;
+ }
+ cam->io = IO_READ;
+ cam->stream = STREAM_ON;
+ }
+
+ if (cam->io == IO_READ) {
+ spin_lock_irqsave(&cam->queue_lock, lock_flags);
+ list_for_each_entry(f, &cam->outqueue, frame)
+ f->state = F_UNUSED;
+ INIT_LIST_HEAD(&cam->outqueue);
+ spin_unlock_irqrestore(&cam->queue_lock, lock_flags);
+ sn9c102_queue_unusedframes(cam);
+ }
+
+ poll_wait(filp, &cam->wait_frame, wait);
+
+ if (!list_empty(&cam->outqueue))
+ mask |= POLLIN | POLLRDNORM;
+
+ mutex_unlock(&cam->fileop_mutex);
+
+ return mask;
+
+error:
+ mutex_unlock(&cam->fileop_mutex);
+ return POLLERR;
+}
+
+
+static void sn9c102_vm_open(struct vm_area_struct *vma)
+{
+ struct sn9c102_frame_t *f = vma->vm_private_data;
+ f->vma_use_count++;
+}
+
+
+static void sn9c102_vm_close(struct vm_area_struct *vma)
+{
+ /* NOTE: buffers are not freed here */
+ struct sn9c102_frame_t *f = vma->vm_private_data;
+ f->vma_use_count--;
+}
+
+
+static const struct vm_operations_struct sn9c102_vm_ops = {
+ .open = sn9c102_vm_open,
+ .close = sn9c102_vm_close,
+};
+
+
+static int sn9c102_mmap(struct file *filp, struct vm_area_struct *vma)
+{
+ struct sn9c102_device *cam = video_drvdata(filp);
+ unsigned long size = vma->vm_end - vma->vm_start,
+ start = vma->vm_start;
+ void *pos;
+ u32 i;
+
+ if (mutex_lock_interruptible(&cam->fileop_mutex))
+ return -ERESTARTSYS;
+
+ if (cam->state & DEV_DISCONNECTED) {
+ DBG(1, "Device not present");
+ mutex_unlock(&cam->fileop_mutex);
+ return -ENODEV;
+ }
+
+ if (cam->state & DEV_MISCONFIGURED) {
+ DBG(1, "The camera is misconfigured. Close and open it "
+ "again.");
+ mutex_unlock(&cam->fileop_mutex);
+ return -EIO;
+ }
+
+ if (!(vma->vm_flags & (VM_WRITE | VM_READ))) {
+ mutex_unlock(&cam->fileop_mutex);
+ return -EACCES;
+ }
+
+ if (cam->io != IO_MMAP ||
+ size != PAGE_ALIGN(cam->frame[0].buf.length)) {
+ mutex_unlock(&cam->fileop_mutex);
+ return -EINVAL;
+ }
+
+ for (i = 0; i < cam->nbuffers; i++) {
+ if ((cam->frame[i].buf.m.offset>>PAGE_SHIFT) == vma->vm_pgoff)
+ break;
+ }
+ if (i == cam->nbuffers) {
+ mutex_unlock(&cam->fileop_mutex);
+ return -EINVAL;
+ }
+
+ vma->vm_flags |= VM_IO | VM_DONTEXPAND | VM_DONTDUMP;
+
+ pos = cam->frame[i].bufmem;
+ while (size > 0) { /* size is page-aligned */
+ if (vm_insert_page(vma, start, vmalloc_to_page(pos))) {
+ mutex_unlock(&cam->fileop_mutex);
+ return -EAGAIN;
+ }
+ start += PAGE_SIZE;
+ pos += PAGE_SIZE;
+ size -= PAGE_SIZE;
+ }
+
+ vma->vm_ops = &sn9c102_vm_ops;
+ vma->vm_private_data = &cam->frame[i];
+ sn9c102_vm_open(vma);
+
+ mutex_unlock(&cam->fileop_mutex);
+
+ return 0;
+}
+
+/*****************************************************************************/
+
+static int
+sn9c102_vidioc_querycap(struct sn9c102_device *cam, void __user *arg)
+{
+ struct v4l2_capability cap = {
+ .driver = "sn9c102",
+ .version = LINUX_VERSION_CODE,
+ .capabilities = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_READWRITE |
+ V4L2_CAP_STREAMING,
+ };
+
+ strlcpy(cap.card, cam->v4ldev->name, sizeof(cap.card));
+ if (usb_make_path(cam->usbdev, cap.bus_info, sizeof(cap.bus_info)) < 0)
+ strlcpy(cap.bus_info, dev_name(&cam->usbdev->dev),
+ sizeof(cap.bus_info));
+
+ if (copy_to_user(arg, &cap, sizeof(cap)))
+ return -EFAULT;
+
+ return 0;
+}
+
+
+static int
+sn9c102_vidioc_enuminput(struct sn9c102_device *cam, void __user *arg)
+{
+ struct v4l2_input i;
+
+ if (copy_from_user(&i, arg, sizeof(i)))
+ return -EFAULT;
+
+ if (i.index)
+ return -EINVAL;
+
+ memset(&i, 0, sizeof(i));
+ strcpy(i.name, "Camera");
+ i.type = V4L2_INPUT_TYPE_CAMERA;
+ i.capabilities = V4L2_IN_CAP_STD;
+
+ if (copy_to_user(arg, &i, sizeof(i)))
+ return -EFAULT;
+
+ return 0;
+}
+
+
+static int
+sn9c102_vidioc_g_input(struct sn9c102_device *cam, void __user *arg)
+{
+ int index = 0;
+
+ if (copy_to_user(arg, &index, sizeof(index)))
+ return -EFAULT;
+
+ return 0;
+}
+
+
+static int
+sn9c102_vidioc_s_input(struct sn9c102_device *cam, void __user *arg)
+{
+ int index;
+
+ if (copy_from_user(&index, arg, sizeof(index)))
+ return -EFAULT;
+
+ if (index != 0)
+ return -EINVAL;
+
+ return 0;
+}
+
+
+static int
+sn9c102_vidioc_query_ctrl(struct sn9c102_device *cam, void __user *arg)
+{
+ struct sn9c102_sensor *s = &cam->sensor;
+ struct v4l2_queryctrl qc;
+ u8 i;
+
+ if (copy_from_user(&qc, arg, sizeof(qc)))
+ return -EFAULT;
+
+ for (i = 0; i < ARRAY_SIZE(s->qctrl); i++)
+ if (qc.id && qc.id == s->qctrl[i].id) {
+ memcpy(&qc, &(s->qctrl[i]), sizeof(qc));
+ if (copy_to_user(arg, &qc, sizeof(qc)))
+ return -EFAULT;
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+
+static int
+sn9c102_vidioc_g_ctrl(struct sn9c102_device *cam, void __user *arg)
+{
+ struct sn9c102_sensor *s = &cam->sensor;
+ struct v4l2_control ctrl;
+ int err = 0;
+ u8 i;
+
+ if (!s->get_ctrl && !s->set_ctrl)
+ return -EINVAL;
+
+ if (copy_from_user(&ctrl, arg, sizeof(ctrl)))
+ return -EFAULT;
+
+ if (!s->get_ctrl) {
+ for (i = 0; i < ARRAY_SIZE(s->qctrl); i++)
+ if (ctrl.id && ctrl.id == s->qctrl[i].id) {
+ ctrl.value = s->_qctrl[i].default_value;
+ goto exit;
+ }
+ return -EINVAL;
+ } else
+ err = s->get_ctrl(cam, &ctrl);
+
+exit:
+ if (copy_to_user(arg, &ctrl, sizeof(ctrl)))
+ return -EFAULT;
+
+ PDBGG("VIDIOC_G_CTRL: id %lu, value %lu",
+ (unsigned long)ctrl.id, (unsigned long)ctrl.value);
+
+ return err;
+}
+
+
+static int
+sn9c102_vidioc_s_ctrl(struct sn9c102_device *cam, void __user *arg)
+{
+ struct sn9c102_sensor *s = &cam->sensor;
+ struct v4l2_control ctrl;
+ u8 i;
+ int err = 0;
+
+ if (!s->set_ctrl)
+ return -EINVAL;
+
+ if (copy_from_user(&ctrl, arg, sizeof(ctrl)))
+ return -EFAULT;
+
+ for (i = 0; i < ARRAY_SIZE(s->qctrl); i++) {
+ if (ctrl.id == s->qctrl[i].id) {
+ if (s->qctrl[i].flags & V4L2_CTRL_FLAG_DISABLED)
+ return -EINVAL;
+ if (ctrl.value < s->qctrl[i].minimum ||
+ ctrl.value > s->qctrl[i].maximum)
+ return -ERANGE;
+ ctrl.value -= ctrl.value % s->qctrl[i].step;
+ break;
+ }
+ }
+ if (i == ARRAY_SIZE(s->qctrl))
+ return -EINVAL;
+ err = s->set_ctrl(cam, &ctrl);
+ if (err)
+ return err;
+
+ s->_qctrl[i].default_value = ctrl.value;
+
+ PDBGG("VIDIOC_S_CTRL: id %lu, value %lu",
+ (unsigned long)ctrl.id, (unsigned long)ctrl.value);
+
+ return 0;
+}
+
+
+static int
+sn9c102_vidioc_cropcap(struct sn9c102_device *cam, void __user *arg)
+{
+ struct v4l2_cropcap *cc = &(cam->sensor.cropcap);
+
+ cc->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ cc->pixelaspect.numerator = 1;
+ cc->pixelaspect.denominator = 1;
+
+ if (copy_to_user(arg, cc, sizeof(*cc)))
+ return -EFAULT;
+
+ return 0;
+}
+
+
+static int
+sn9c102_vidioc_g_crop(struct sn9c102_device *cam, void __user *arg)
+{
+ struct sn9c102_sensor *s = &cam->sensor;
+ struct v4l2_crop crop = {
+ .type = V4L2_BUF_TYPE_VIDEO_CAPTURE,
+ };
+
+ memcpy(&(crop.c), &(s->_rect), sizeof(struct v4l2_rect));
+
+ if (copy_to_user(arg, &crop, sizeof(crop)))
+ return -EFAULT;
+
+ return 0;
+}
+
+
+static int
+sn9c102_vidioc_s_crop(struct sn9c102_device *cam, void __user *arg)
+{
+ struct sn9c102_sensor *s = &cam->sensor;
+ struct v4l2_crop crop;
+ struct v4l2_rect *rect;
+ struct v4l2_rect *bounds = &(s->cropcap.bounds);
+ struct v4l2_pix_format *pix_format = &(s->pix_format);
+ u8 scale;
+ const enum sn9c102_stream_state stream = cam->stream;
+ const u32 nbuffers = cam->nbuffers;
+ u32 i;
+ int err = 0;
+
+ if (copy_from_user(&crop, arg, sizeof(crop)))
+ return -EFAULT;
+
+ rect = &(crop.c);
+
+ if (crop.type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+ return -EINVAL;
+
+ if (cam->module_param.force_munmap)
+ for (i = 0; i < cam->nbuffers; i++)
+ if (cam->frame[i].vma_use_count) {
+ DBG(3, "VIDIOC_S_CROP failed. "
+ "Unmap the buffers first.");
+ return -EBUSY;
+ }
+
+ /* Preserve R,G or B origin */
+ rect->left = (s->_rect.left & 1L) ? rect->left | 1L : rect->left & ~1L;
+ rect->top = (s->_rect.top & 1L) ? rect->top | 1L : rect->top & ~1L;
+
+ if (rect->width < 16)
+ rect->width = 16;
+ if (rect->height < 16)
+ rect->height = 16;
+ if (rect->width > bounds->width)
+ rect->width = bounds->width;
+ if (rect->height > bounds->height)
+ rect->height = bounds->height;
+ if (rect->left < bounds->left)
+ rect->left = bounds->left;
+ if (rect->top < bounds->top)
+ rect->top = bounds->top;
+ if (rect->left + rect->width > bounds->left + bounds->width)
+ rect->left = bounds->left+bounds->width - rect->width;
+ if (rect->top + rect->height > bounds->top + bounds->height)
+ rect->top = bounds->top+bounds->height - rect->height;
+
+ rect->width &= ~15L;
+ rect->height &= ~15L;
+
+ if (SN9C102_PRESERVE_IMGSCALE) {
+ /* Calculate the actual scaling factor */
+ u32 a, b;
+ a = rect->width * rect->height;
+ b = pix_format->width * pix_format->height;
+ scale = b ? (u8)((a / b) < 4 ? 1 : ((a / b) < 16 ? 2 : 4)) : 1;
+ } else
+ scale = 1;
+
+ if (cam->stream == STREAM_ON) {
+ err = sn9c102_stream_interrupt(cam);
+ if (err)
+ return err;
+ }
+
+ if (copy_to_user(arg, &crop, sizeof(crop))) {
+ cam->stream = stream;
+ return -EFAULT;
+ }
+
+ if (cam->module_param.force_munmap || cam->io == IO_READ)
+ sn9c102_release_buffers(cam);
+
+ err = sn9c102_set_crop(cam, rect);
+ if (s->set_crop)
+ err += s->set_crop(cam, rect);
+ err += sn9c102_set_scale(cam, scale);
+
+ if (err) { /* atomic, no rollback in ioctl() */
+ cam->state |= DEV_MISCONFIGURED;
+ DBG(1, "VIDIOC_S_CROP failed because of hardware problems. To "
+ "use the camera, close and open %s again.",
+ video_device_node_name(cam->v4ldev));
+ return -EIO;
+ }
+
+ s->pix_format.width = rect->width/scale;
+ s->pix_format.height = rect->height/scale;
+ memcpy(&(s->_rect), rect, sizeof(*rect));
+
+ if ((cam->module_param.force_munmap || cam->io == IO_READ) &&
+ nbuffers != sn9c102_request_buffers(cam, nbuffers, cam->io)) {
+ cam->state |= DEV_MISCONFIGURED;
+ DBG(1, "VIDIOC_S_CROP failed because of not enough memory. To "
+ "use the camera, close and open %s again.",
+ video_device_node_name(cam->v4ldev));
+ return -ENOMEM;
+ }
+
+ if (cam->io == IO_READ)
+ sn9c102_empty_framequeues(cam);
+ else if (cam->module_param.force_munmap)
+ sn9c102_requeue_outqueue(cam);
+
+ cam->stream = stream;
+
+ return 0;
+}
+
+
+static int
+sn9c102_vidioc_enum_framesizes(struct sn9c102_device *cam, void __user *arg)
+{
+ struct v4l2_frmsizeenum frmsize;
+
+ if (copy_from_user(&frmsize, arg, sizeof(frmsize)))
+ return -EFAULT;
+
+ if (frmsize.index != 0)
+ return -EINVAL;
+
+ switch (cam->bridge) {
+ case BRIDGE_SN9C101:
+ case BRIDGE_SN9C102:
+ case BRIDGE_SN9C103:
+ if (frmsize.pixel_format != V4L2_PIX_FMT_SN9C10X &&
+ frmsize.pixel_format != V4L2_PIX_FMT_SBGGR8)
+ return -EINVAL;
+ break;
+ case BRIDGE_SN9C105:
+ case BRIDGE_SN9C120:
+ if (frmsize.pixel_format != V4L2_PIX_FMT_JPEG &&
+ frmsize.pixel_format != V4L2_PIX_FMT_SBGGR8)
+ return -EINVAL;
+ break;
+ }
+
+ frmsize.type = V4L2_FRMSIZE_TYPE_STEPWISE;
+ frmsize.stepwise.min_width = frmsize.stepwise.step_width = 16;
+ frmsize.stepwise.min_height = frmsize.stepwise.step_height = 16;
+ frmsize.stepwise.max_width = cam->sensor.cropcap.bounds.width;
+ frmsize.stepwise.max_height = cam->sensor.cropcap.bounds.height;
+ memset(&frmsize.reserved, 0, sizeof(frmsize.reserved));
+
+ if (copy_to_user(arg, &frmsize, sizeof(frmsize)))
+ return -EFAULT;
+
+ return 0;
+}
+
+
+static int
+sn9c102_vidioc_enum_fmt(struct sn9c102_device *cam, void __user *arg)
+{
+ struct v4l2_fmtdesc fmtd;
+
+ if (copy_from_user(&fmtd, arg, sizeof(fmtd)))
+ return -EFAULT;
+
+ if (fmtd.type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+ return -EINVAL;
+
+ if (fmtd.index == 0) {
+ strcpy(fmtd.description, "bayer rgb");
+ fmtd.pixelformat = V4L2_PIX_FMT_SBGGR8;
+ } else if (fmtd.index == 1) {
+ switch (cam->bridge) {
+ case BRIDGE_SN9C101:
+ case BRIDGE_SN9C102:
+ case BRIDGE_SN9C103:
+ strcpy(fmtd.description, "compressed");
+ fmtd.pixelformat = V4L2_PIX_FMT_SN9C10X;
+ break;
+ case BRIDGE_SN9C105:
+ case BRIDGE_SN9C120:
+ strcpy(fmtd.description, "JPEG");
+ fmtd.pixelformat = V4L2_PIX_FMT_JPEG;
+ break;
+ }
+ fmtd.flags = V4L2_FMT_FLAG_COMPRESSED;
+ } else
+ return -EINVAL;
+
+ fmtd.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ memset(&fmtd.reserved, 0, sizeof(fmtd.reserved));
+
+ if (copy_to_user(arg, &fmtd, sizeof(fmtd)))
+ return -EFAULT;
+
+ return 0;
+}
+
+
+static int
+sn9c102_vidioc_g_fmt(struct sn9c102_device *cam, void __user *arg)
+{
+ struct v4l2_format format;
+ struct v4l2_pix_format *pfmt = &(cam->sensor.pix_format);
+
+ if (copy_from_user(&format, arg, sizeof(format)))
+ return -EFAULT;
+
+ if (format.type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+ return -EINVAL;
+
+ pfmt->colorspace = (pfmt->pixelformat == V4L2_PIX_FMT_JPEG) ?
+ V4L2_COLORSPACE_JPEG : V4L2_COLORSPACE_SRGB;
+ pfmt->bytesperline = (pfmt->pixelformat == V4L2_PIX_FMT_SN9C10X ||
+ pfmt->pixelformat == V4L2_PIX_FMT_JPEG)
+ ? 0 : (pfmt->width * pfmt->priv) / 8;
+ pfmt->sizeimage = pfmt->height * ((pfmt->width*pfmt->priv)/8);
+ pfmt->field = V4L2_FIELD_NONE;
+ memcpy(&(format.fmt.pix), pfmt, sizeof(*pfmt));
+
+ if (copy_to_user(arg, &format, sizeof(format)))
+ return -EFAULT;
+
+ return 0;
+}
+
+
+static int
+sn9c102_vidioc_try_s_fmt(struct sn9c102_device *cam, unsigned int cmd,
+ void __user *arg)
+{
+ struct sn9c102_sensor *s = &cam->sensor;
+ struct v4l2_format format;
+ struct v4l2_pix_format *pix;
+ struct v4l2_pix_format *pfmt = &(s->pix_format);
+ struct v4l2_rect *bounds = &(s->cropcap.bounds);
+ struct v4l2_rect rect;
+ u8 scale;
+ const enum sn9c102_stream_state stream = cam->stream;
+ const u32 nbuffers = cam->nbuffers;
+ u32 i;
+ int err = 0;
+
+ if (copy_from_user(&format, arg, sizeof(format)))
+ return -EFAULT;
+
+ pix = &(format.fmt.pix);
+
+ if (format.type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+ return -EINVAL;
+
+ memcpy(&rect, &(s->_rect), sizeof(rect));
+
+ { /* calculate the actual scaling factor */
+ u32 a, b;
+ a = rect.width * rect.height;
+ b = pix->width * pix->height;
+ scale = b ? (u8)((a / b) < 4 ? 1 : ((a / b) < 16 ? 2 : 4)) : 1;
+ }
+
+ rect.width = scale * pix->width;
+ rect.height = scale * pix->height;
+
+ if (rect.width < 16)
+ rect.width = 16;
+ if (rect.height < 16)
+ rect.height = 16;
+ if (rect.width > bounds->left + bounds->width - rect.left)
+ rect.width = bounds->left + bounds->width - rect.left;
+ if (rect.height > bounds->top + bounds->height - rect.top)
+ rect.height = bounds->top + bounds->height - rect.top;
+
+ rect.width &= ~15L;
+ rect.height &= ~15L;
+
+ { /* adjust the scaling factor */
+ u32 a, b;
+ a = rect.width * rect.height;
+ b = pix->width * pix->height;
+ scale = b ? (u8)((a / b) < 4 ? 1 : ((a / b) < 16 ? 2 : 4)) : 1;
+ }
+
+ pix->width = rect.width / scale;
+ pix->height = rect.height / scale;
+
+ switch (cam->bridge) {
+ case BRIDGE_SN9C101:
+ case BRIDGE_SN9C102:
+ case BRIDGE_SN9C103:
+ if (pix->pixelformat != V4L2_PIX_FMT_SN9C10X &&
+ pix->pixelformat != V4L2_PIX_FMT_SBGGR8)
+ pix->pixelformat = pfmt->pixelformat;
+ break;
+ case BRIDGE_SN9C105:
+ case BRIDGE_SN9C120:
+ if (pix->pixelformat != V4L2_PIX_FMT_JPEG &&
+ pix->pixelformat != V4L2_PIX_FMT_SBGGR8)
+ pix->pixelformat = pfmt->pixelformat;
+ break;
+ }
+ pix->priv = pfmt->priv; /* bpp */
+ pix->colorspace = (pix->pixelformat == V4L2_PIX_FMT_JPEG) ?
+ V4L2_COLORSPACE_JPEG : V4L2_COLORSPACE_SRGB;
+ pix->bytesperline = (pix->pixelformat == V4L2_PIX_FMT_SN9C10X ||
+ pix->pixelformat == V4L2_PIX_FMT_JPEG)
+ ? 0 : (pix->width * pix->priv) / 8;
+ pix->sizeimage = pix->height * ((pix->width * pix->priv) / 8);
+ pix->field = V4L2_FIELD_NONE;
+
+ if (cmd == VIDIOC_TRY_FMT) {
+ if (copy_to_user(arg, &format, sizeof(format)))
+ return -EFAULT;
+ return 0;
+ }
+
+ if (cam->module_param.force_munmap)
+ for (i = 0; i < cam->nbuffers; i++)
+ if (cam->frame[i].vma_use_count) {
+ DBG(3, "VIDIOC_S_FMT failed. Unmap the "
+ "buffers first.");
+ return -EBUSY;
+ }
+
+ if (cam->stream == STREAM_ON) {
+ err = sn9c102_stream_interrupt(cam);
+ if (err)
+ return err;
+ }
+
+ if (copy_to_user(arg, &format, sizeof(format))) {
+ cam->stream = stream;
+ return -EFAULT;
+ }
+
+ if (cam->module_param.force_munmap || cam->io == IO_READ)
+ sn9c102_release_buffers(cam);
+
+ err += sn9c102_set_pix_format(cam, pix);
+ err += sn9c102_set_crop(cam, &rect);
+ if (s->set_pix_format)
+ err += s->set_pix_format(cam, pix);
+ if (s->set_crop)
+ err += s->set_crop(cam, &rect);
+ err += sn9c102_set_scale(cam, scale);
+
+ if (err) { /* atomic, no rollback in ioctl() */
+ cam->state |= DEV_MISCONFIGURED;
+ DBG(1, "VIDIOC_S_FMT failed because of hardware problems. To "
+ "use the camera, close and open %s again.",
+ video_device_node_name(cam->v4ldev));
+ return -EIO;
+ }
+
+ memcpy(pfmt, pix, sizeof(*pix));
+ memcpy(&(s->_rect), &rect, sizeof(rect));
+
+ if ((cam->module_param.force_munmap || cam->io == IO_READ) &&
+ nbuffers != sn9c102_request_buffers(cam, nbuffers, cam->io)) {
+ cam->state |= DEV_MISCONFIGURED;
+ DBG(1, "VIDIOC_S_FMT failed because of not enough memory. To "
+ "use the camera, close and open %s again.",
+ video_device_node_name(cam->v4ldev));
+ return -ENOMEM;
+ }
+
+ if (cam->io == IO_READ)
+ sn9c102_empty_framequeues(cam);
+ else if (cam->module_param.force_munmap)
+ sn9c102_requeue_outqueue(cam);
+
+ cam->stream = stream;
+
+ return 0;
+}
+
+
+static int
+sn9c102_vidioc_g_jpegcomp(struct sn9c102_device *cam, void __user *arg)
+{
+ if (copy_to_user(arg, &cam->compression, sizeof(cam->compression)))
+ return -EFAULT;
+
+ return 0;
+}
+
+
+static int
+sn9c102_vidioc_s_jpegcomp(struct sn9c102_device *cam, void __user *arg)
+{
+ struct v4l2_jpegcompression jc;
+ const enum sn9c102_stream_state stream = cam->stream;
+ int err = 0;
+
+ if (copy_from_user(&jc, arg, sizeof(jc)))
+ return -EFAULT;
+
+ if (jc.quality != 0 && jc.quality != 1)
+ return -EINVAL;
+
+ if (cam->stream == STREAM_ON) {
+ err = sn9c102_stream_interrupt(cam);
+ if (err)
+ return err;
+ }
+
+ err += sn9c102_set_compression(cam, &jc);
+ if (err) { /* atomic, no rollback in ioctl() */
+ cam->state |= DEV_MISCONFIGURED;
+ DBG(1, "VIDIOC_S_JPEGCOMP failed because of hardware problems. "
+ "To use the camera, close and open %s again.",
+ video_device_node_name(cam->v4ldev));
+ return -EIO;
+ }
+
+ cam->compression.quality = jc.quality;
+
+ cam->stream = stream;
+
+ return 0;
+}
+
+
+static int
+sn9c102_vidioc_reqbufs(struct sn9c102_device *cam, void __user *arg)
+{
+ struct v4l2_requestbuffers rb;
+ u32 i;
+ int err;
+
+ if (copy_from_user(&rb, arg, sizeof(rb)))
+ return -EFAULT;
+
+ if (rb.type != V4L2_BUF_TYPE_VIDEO_CAPTURE ||
+ rb.memory != V4L2_MEMORY_MMAP)
+ return -EINVAL;
+
+ if (cam->io == IO_READ) {
+ DBG(3, "Close and open the device again to choose the mmap "
+ "I/O method");
+ return -EBUSY;
+ }
+
+ for (i = 0; i < cam->nbuffers; i++)
+ if (cam->frame[i].vma_use_count) {
+ DBG(3, "VIDIOC_REQBUFS failed. Previous buffers are "
+ "still mapped.");
+ return -EBUSY;
+ }
+
+ if (cam->stream == STREAM_ON) {
+ err = sn9c102_stream_interrupt(cam);
+ if (err)
+ return err;
+ }
+
+ sn9c102_empty_framequeues(cam);
+
+ sn9c102_release_buffers(cam);
+ if (rb.count)
+ rb.count = sn9c102_request_buffers(cam, rb.count, IO_MMAP);
+
+ if (copy_to_user(arg, &rb, sizeof(rb))) {
+ sn9c102_release_buffers(cam);
+ cam->io = IO_NONE;
+ return -EFAULT;
+ }
+
+ cam->io = rb.count ? IO_MMAP : IO_NONE;
+
+ return 0;
+}
+
+
+static int
+sn9c102_vidioc_querybuf(struct sn9c102_device *cam, void __user *arg)
+{
+ struct v4l2_buffer b;
+
+ if (copy_from_user(&b, arg, sizeof(b)))
+ return -EFAULT;
+
+ if (b.type != V4L2_BUF_TYPE_VIDEO_CAPTURE ||
+ b.index >= cam->nbuffers || cam->io != IO_MMAP)
+ return -EINVAL;
+
+ b = cam->frame[b.index].buf;
+
+ if (cam->frame[b.index].vma_use_count)
+ b.flags |= V4L2_BUF_FLAG_MAPPED;
+
+ if (cam->frame[b.index].state == F_DONE)
+ b.flags |= V4L2_BUF_FLAG_DONE;
+ else if (cam->frame[b.index].state != F_UNUSED)
+ b.flags |= V4L2_BUF_FLAG_QUEUED;
+
+ if (copy_to_user(arg, &b, sizeof(b)))
+ return -EFAULT;
+
+ return 0;
+}
+
+
+static int
+sn9c102_vidioc_qbuf(struct sn9c102_device *cam, void __user *arg)
+{
+ struct v4l2_buffer b;
+ unsigned long lock_flags;
+
+ if (copy_from_user(&b, arg, sizeof(b)))
+ return -EFAULT;
+
+ if (b.type != V4L2_BUF_TYPE_VIDEO_CAPTURE ||
+ b.index >= cam->nbuffers || cam->io != IO_MMAP)
+ return -EINVAL;
+
+ if (cam->frame[b.index].state != F_UNUSED)
+ return -EINVAL;
+
+ cam->frame[b.index].state = F_QUEUED;
+
+ spin_lock_irqsave(&cam->queue_lock, lock_flags);
+ list_add_tail(&cam->frame[b.index].frame, &cam->inqueue);
+ spin_unlock_irqrestore(&cam->queue_lock, lock_flags);
+
+ PDBGG("Frame #%lu queued", (unsigned long)b.index);
+
+ return 0;
+}
+
+
+static int
+sn9c102_vidioc_dqbuf(struct sn9c102_device *cam, struct file *filp,
+ void __user *arg)
+{
+ struct v4l2_buffer b;
+ struct sn9c102_frame_t *f;
+ unsigned long lock_flags;
+ long timeout;
+ int err = 0;
+
+ if (copy_from_user(&b, arg, sizeof(b)))
+ return -EFAULT;
+
+ if (b.type != V4L2_BUF_TYPE_VIDEO_CAPTURE || cam->io != IO_MMAP)
+ return -EINVAL;
+
+ if (list_empty(&cam->outqueue)) {
+ if (cam->stream == STREAM_OFF)
+ return -EINVAL;
+ if (filp->f_flags & O_NONBLOCK)
+ return -EAGAIN;
+ if (!cam->module_param.frame_timeout) {
+ err = wait_event_interruptible
+ (cam->wait_frame,
+ (!list_empty(&cam->outqueue)) ||
+ (cam->state & DEV_DISCONNECTED) ||
+ (cam->state & DEV_MISCONFIGURED));
+ if (err)
+ return err;
+ } else {
+ timeout = wait_event_interruptible_timeout
+ (cam->wait_frame,
+ (!list_empty(&cam->outqueue)) ||
+ (cam->state & DEV_DISCONNECTED) ||
+ (cam->state & DEV_MISCONFIGURED),
+ cam->module_param.frame_timeout *
+ 1000 * msecs_to_jiffies(1));
+ if (timeout < 0)
+ return timeout;
+ else if (timeout == 0 &&
+ !(cam->state & DEV_DISCONNECTED)) {
+ DBG(1, "Video frame timeout elapsed");
+ return -EIO;
+ }
+ }
+ if (cam->state & DEV_DISCONNECTED)
+ return -ENODEV;
+ if (cam->state & DEV_MISCONFIGURED)
+ return -EIO;
+ }
+
+ spin_lock_irqsave(&cam->queue_lock, lock_flags);
+ f = list_entry(cam->outqueue.next, struct sn9c102_frame_t, frame);
+ list_del(cam->outqueue.next);
+ spin_unlock_irqrestore(&cam->queue_lock, lock_flags);
+
+ f->state = F_UNUSED;
+
+ b = f->buf;
+ if (f->vma_use_count)
+ b.flags |= V4L2_BUF_FLAG_MAPPED;
+
+ if (copy_to_user(arg, &b, sizeof(b)))
+ return -EFAULT;
+
+ PDBGG("Frame #%lu dequeued", (unsigned long)f->buf.index);
+
+ return 0;
+}
+
+
+static int
+sn9c102_vidioc_streamon(struct sn9c102_device *cam, void __user *arg)
+{
+ int type;
+
+ if (copy_from_user(&type, arg, sizeof(type)))
+ return -EFAULT;
+
+ if (type != V4L2_BUF_TYPE_VIDEO_CAPTURE || cam->io != IO_MMAP)
+ return -EINVAL;
+
+ cam->stream = STREAM_ON;
+
+ DBG(3, "Stream on");
+
+ return 0;
+}
+
+
+static int
+sn9c102_vidioc_streamoff(struct sn9c102_device *cam, void __user *arg)
+{
+ int type, err;
+
+ if (copy_from_user(&type, arg, sizeof(type)))
+ return -EFAULT;
+
+ if (type != V4L2_BUF_TYPE_VIDEO_CAPTURE || cam->io != IO_MMAP)
+ return -EINVAL;
+
+ if (cam->stream == STREAM_ON) {
+ err = sn9c102_stream_interrupt(cam);
+ if (err)
+ return err;
+ }
+
+ sn9c102_empty_framequeues(cam);
+
+ DBG(3, "Stream off");
+
+ return 0;
+}
+
+
+static int
+sn9c102_vidioc_g_parm(struct sn9c102_device *cam, void __user *arg)
+{
+ struct v4l2_streamparm sp;
+
+ if (copy_from_user(&sp, arg, sizeof(sp)))
+ return -EFAULT;
+
+ if (sp.type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+ return -EINVAL;
+
+ sp.parm.capture.extendedmode = 0;
+ sp.parm.capture.readbuffers = cam->nreadbuffers;
+
+ if (copy_to_user(arg, &sp, sizeof(sp)))
+ return -EFAULT;
+
+ return 0;
+}
+
+
+static int
+sn9c102_vidioc_s_parm(struct sn9c102_device *cam, void __user *arg)
+{
+ struct v4l2_streamparm sp;
+
+ if (copy_from_user(&sp, arg, sizeof(sp)))
+ return -EFAULT;
+
+ if (sp.type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+ return -EINVAL;
+
+ sp.parm.capture.extendedmode = 0;
+
+ if (sp.parm.capture.readbuffers == 0)
+ sp.parm.capture.readbuffers = cam->nreadbuffers;
+
+ if (sp.parm.capture.readbuffers > SN9C102_MAX_FRAMES)
+ sp.parm.capture.readbuffers = SN9C102_MAX_FRAMES;
+
+ if (copy_to_user(arg, &sp, sizeof(sp)))
+ return -EFAULT;
+
+ cam->nreadbuffers = sp.parm.capture.readbuffers;
+
+ return 0;
+}
+
+
+static int
+sn9c102_vidioc_enumaudio(struct sn9c102_device *cam, void __user *arg)
+{
+ struct v4l2_audio audio;
+
+ if (cam->bridge == BRIDGE_SN9C101 || cam->bridge == BRIDGE_SN9C102)
+ return -EINVAL;
+
+ if (copy_from_user(&audio, arg, sizeof(audio)))
+ return -EFAULT;
+
+ if (audio.index != 0)
+ return -EINVAL;
+
+ strcpy(audio.name, "Microphone");
+ audio.capability = 0;
+ audio.mode = 0;
+
+ if (copy_to_user(arg, &audio, sizeof(audio)))
+ return -EFAULT;
+
+ return 0;
+}
+
+
+static int
+sn9c102_vidioc_g_audio(struct sn9c102_device *cam, void __user *arg)
+{
+ struct v4l2_audio audio;
+
+ if (cam->bridge == BRIDGE_SN9C101 || cam->bridge == BRIDGE_SN9C102)
+ return -EINVAL;
+
+ if (copy_from_user(&audio, arg, sizeof(audio)))
+ return -EFAULT;
+
+ memset(&audio, 0, sizeof(audio));
+ strcpy(audio.name, "Microphone");
+
+ if (copy_to_user(arg, &audio, sizeof(audio)))
+ return -EFAULT;
+
+ return 0;
+}
+
+
+static int
+sn9c102_vidioc_s_audio(struct sn9c102_device *cam, void __user *arg)
+{
+ struct v4l2_audio audio;
+
+ if (cam->bridge == BRIDGE_SN9C101 || cam->bridge == BRIDGE_SN9C102)
+ return -EINVAL;
+
+ if (copy_from_user(&audio, arg, sizeof(audio)))
+ return -EFAULT;
+
+ if (audio.index != 0)
+ return -EINVAL;
+
+ return 0;
+}
+
+
+static long sn9c102_ioctl_v4l2(struct file *filp,
+ unsigned int cmd, void __user *arg)
+{
+ struct sn9c102_device *cam = video_drvdata(filp);
+
+ switch (cmd) {
+
+ case VIDIOC_QUERYCAP:
+ return sn9c102_vidioc_querycap(cam, arg);
+
+ case VIDIOC_ENUMINPUT:
+ return sn9c102_vidioc_enuminput(cam, arg);
+
+ case VIDIOC_G_INPUT:
+ return sn9c102_vidioc_g_input(cam, arg);
+
+ case VIDIOC_S_INPUT:
+ return sn9c102_vidioc_s_input(cam, arg);
+
+ case VIDIOC_QUERYCTRL:
+ return sn9c102_vidioc_query_ctrl(cam, arg);
+
+ case VIDIOC_G_CTRL:
+ return sn9c102_vidioc_g_ctrl(cam, arg);
+
+ case VIDIOC_S_CTRL:
+ return sn9c102_vidioc_s_ctrl(cam, arg);
+
+ case VIDIOC_CROPCAP:
+ return sn9c102_vidioc_cropcap(cam, arg);
+
+ case VIDIOC_G_CROP:
+ return sn9c102_vidioc_g_crop(cam, arg);
+
+ case VIDIOC_S_CROP:
+ return sn9c102_vidioc_s_crop(cam, arg);
+
+ case VIDIOC_ENUM_FRAMESIZES:
+ return sn9c102_vidioc_enum_framesizes(cam, arg);
+
+ case VIDIOC_ENUM_FMT:
+ return sn9c102_vidioc_enum_fmt(cam, arg);
+
+ case VIDIOC_G_FMT:
+ return sn9c102_vidioc_g_fmt(cam, arg);
+
+ case VIDIOC_TRY_FMT:
+ case VIDIOC_S_FMT:
+ return sn9c102_vidioc_try_s_fmt(cam, cmd, arg);
+
+ case VIDIOC_G_JPEGCOMP:
+ return sn9c102_vidioc_g_jpegcomp(cam, arg);
+
+ case VIDIOC_S_JPEGCOMP:
+ return sn9c102_vidioc_s_jpegcomp(cam, arg);
+
+ case VIDIOC_REQBUFS:
+ return sn9c102_vidioc_reqbufs(cam, arg);
+
+ case VIDIOC_QUERYBUF:
+ return sn9c102_vidioc_querybuf(cam, arg);
+
+ case VIDIOC_QBUF:
+ return sn9c102_vidioc_qbuf(cam, arg);
+
+ case VIDIOC_DQBUF:
+ return sn9c102_vidioc_dqbuf(cam, filp, arg);
+
+ case VIDIOC_STREAMON:
+ return sn9c102_vidioc_streamon(cam, arg);
+
+ case VIDIOC_STREAMOFF:
+ return sn9c102_vidioc_streamoff(cam, arg);
+
+ case VIDIOC_G_PARM:
+ return sn9c102_vidioc_g_parm(cam, arg);
+
+ case VIDIOC_S_PARM:
+ return sn9c102_vidioc_s_parm(cam, arg);
+
+ case VIDIOC_ENUMAUDIO:
+ return sn9c102_vidioc_enumaudio(cam, arg);
+
+ case VIDIOC_G_AUDIO:
+ return sn9c102_vidioc_g_audio(cam, arg);
+
+ case VIDIOC_S_AUDIO:
+ return sn9c102_vidioc_s_audio(cam, arg);
+
+ default:
+ return -ENOTTY;
+
+ }
+}
+
+
+static long sn9c102_ioctl(struct file *filp,
+ unsigned int cmd, unsigned long arg)
+{
+ struct sn9c102_device *cam = video_drvdata(filp);
+ int err = 0;
+
+ if (mutex_lock_interruptible(&cam->fileop_mutex))
+ return -ERESTARTSYS;
+
+ if (cam->state & DEV_DISCONNECTED) {
+ DBG(1, "Device not present");
+ mutex_unlock(&cam->fileop_mutex);
+ return -ENODEV;
+ }
+
+ if (cam->state & DEV_MISCONFIGURED) {
+ DBG(1, "The camera is misconfigured. Close and open it "
+ "again.");
+ mutex_unlock(&cam->fileop_mutex);
+ return -EIO;
+ }
+
+ V4LDBG(3, "sn9c102", cmd);
+
+ err = sn9c102_ioctl_v4l2(filp, cmd, (void __user *)arg);
+
+ mutex_unlock(&cam->fileop_mutex);
+
+ return err;
+}
+
+/*****************************************************************************/
+
+static const struct v4l2_file_operations sn9c102_fops = {
+ .owner = THIS_MODULE,
+ .open = sn9c102_open,
+ .release = sn9c102_release,
+ .unlocked_ioctl = sn9c102_ioctl,
+ .read = sn9c102_read,
+ .poll = sn9c102_poll,
+ .mmap = sn9c102_mmap,
+};
+
+/*****************************************************************************/
+
+/* It exists a single interface only. We do not need to validate anything. */
+static int
+sn9c102_usb_probe(struct usb_interface *intf, const struct usb_device_id *id)
+{
+ struct usb_device *udev = interface_to_usbdev(intf);
+ struct sn9c102_device *cam;
+ static unsigned int dev_nr;
+ unsigned int i;
+ int err = 0, r;
+
+ cam = kzalloc(sizeof(struct sn9c102_device), GFP_KERNEL);
+ if (!cam)
+ return -ENOMEM;
+
+ cam->usbdev = udev;
+
+ /* register v4l2_device early so it can be used for printks */
+ if (v4l2_device_register(&intf->dev, &cam->v4l2_dev)) {
+ dev_err(&intf->dev, "v4l2_device_register failed\n");
+ err = -ENOMEM;
+ goto fail;
+ }
+
+ cam->control_buffer = kzalloc(8, GFP_KERNEL);
+ if (!cam->control_buffer) {
+ DBG(1, "kzalloc() failed");
+ err = -ENOMEM;
+ goto fail;
+ }
+
+ cam->v4ldev = video_device_alloc();
+ if (!cam->v4ldev) {
+ DBG(1, "video_device_alloc() failed");
+ err = -ENOMEM;
+ goto fail;
+ }
+
+ r = sn9c102_read_reg(cam, 0x00);
+ if (r < 0 || (r != 0x10 && r != 0x11 && r != 0x12)) {
+ DBG(1, "Sorry, this is not a SN9C1xx-based camera "
+ "(vid:pid 0x%04X:0x%04X)", id->idVendor, id->idProduct);
+ err = -ENODEV;
+ goto fail;
+ }
+
+ cam->bridge = id->driver_info;
+ switch (cam->bridge) {
+ case BRIDGE_SN9C101:
+ case BRIDGE_SN9C102:
+ DBG(2, "SN9C10[12] PC Camera Controller detected "
+ "(vid:pid 0x%04X:0x%04X)", id->idVendor, id->idProduct);
+ break;
+ case BRIDGE_SN9C103:
+ DBG(2, "SN9C103 PC Camera Controller detected "
+ "(vid:pid 0x%04X:0x%04X)", id->idVendor, id->idProduct);
+ break;
+ case BRIDGE_SN9C105:
+ DBG(2, "SN9C105 PC Camera Controller detected "
+ "(vid:pid 0x%04X:0x%04X)", id->idVendor, id->idProduct);
+ break;
+ case BRIDGE_SN9C120:
+ DBG(2, "SN9C120 PC Camera Controller detected "
+ "(vid:pid 0x%04X:0x%04X)", id->idVendor, id->idProduct);
+ break;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(sn9c102_sensor_table); i++) {
+ err = sn9c102_sensor_table[i](cam);
+ if (!err)
+ break;
+ }
+
+ if (!err) {
+ DBG(2, "%s image sensor detected", cam->sensor.name);
+ DBG(3, "Support for %s maintained by %s",
+ cam->sensor.name, cam->sensor.maintainer);
+ } else {
+ DBG(1, "No supported image sensor detected for this bridge");
+ err = -ENODEV;
+ goto fail;
+ }
+
+ if (!(cam->bridge & cam->sensor.supported_bridge)) {
+ DBG(1, "Bridge not supported");
+ err = -ENODEV;
+ goto fail;
+ }
+
+ if (sn9c102_init(cam)) {
+ DBG(1, "Initialization failed. I will retry on open().");
+ cam->state |= DEV_MISCONFIGURED;
+ }
+
+ strcpy(cam->v4ldev->name, "SN9C1xx PC Camera");
+ cam->v4ldev->fops = &sn9c102_fops;
+ cam->v4ldev->release = video_device_release;
+ cam->v4ldev->v4l2_dev = &cam->v4l2_dev;
+
+ init_completion(&cam->probe);
+
+ err = video_register_device(cam->v4ldev, VFL_TYPE_GRABBER,
+ video_nr[dev_nr]);
+ if (err) {
+ DBG(1, "V4L2 device registration failed");
+ if (err == -ENFILE && video_nr[dev_nr] == -1)
+ DBG(1, "Free /dev/videoX node not found");
+ video_nr[dev_nr] = -1;
+ dev_nr = (dev_nr < SN9C102_MAX_DEVICES-1) ? dev_nr+1 : 0;
+ complete_all(&cam->probe);
+ goto fail;
+ }
+
+ DBG(2, "V4L2 device registered as %s",
+ video_device_node_name(cam->v4ldev));
+
+ video_set_drvdata(cam->v4ldev, cam);
+ cam->module_param.force_munmap = force_munmap[dev_nr];
+ cam->module_param.frame_timeout = frame_timeout[dev_nr];
+
+ dev_nr = (dev_nr < SN9C102_MAX_DEVICES-1) ? dev_nr+1 : 0;
+
+#ifdef CONFIG_VIDEO_ADV_DEBUG
+ err = sn9c102_create_sysfs(cam);
+ if (!err)
+ DBG(2, "Optional device control through 'sysfs' "
+ "interface ready");
+ else
+ DBG(2, "Failed to create optional 'sysfs' interface for "
+ "device controlling. Error #%d", err);
+#else
+ DBG(2, "Optional device control through 'sysfs' interface disabled");
+ DBG(3, "Compile the kernel with the 'CONFIG_VIDEO_ADV_DEBUG' "
+ "configuration option to enable it.");
+#endif
+
+ usb_set_intfdata(intf, cam);
+ kref_init(&cam->kref);
+ usb_get_dev(cam->usbdev);
+
+ complete_all(&cam->probe);
+
+ return 0;
+
+fail:
+ if (cam) {
+ kfree(cam->control_buffer);
+ if (cam->v4ldev)
+ video_device_release(cam->v4ldev);
+ v4l2_device_unregister(&cam->v4l2_dev);
+ kfree(cam);
+ }
+ return err;
+}
+
+
+static void sn9c102_usb_disconnect(struct usb_interface *intf)
+{
+ struct sn9c102_device *cam;
+
+ down_write(&sn9c102_dev_lock);
+
+ cam = usb_get_intfdata(intf);
+
+ DBG(2, "Disconnecting %s...", cam->v4ldev->name);
+
+ if (cam->users) {
+ DBG(2, "Device %s is open! Deregistration and memory "
+ "deallocation are deferred.",
+ video_device_node_name(cam->v4ldev));
+ cam->state |= DEV_MISCONFIGURED;
+ sn9c102_stop_transfer(cam);
+ cam->state |= DEV_DISCONNECTED;
+ wake_up_interruptible(&cam->wait_frame);
+ wake_up(&cam->wait_stream);
+ } else
+ cam->state |= DEV_DISCONNECTED;
+
+ wake_up_interruptible_all(&cam->wait_open);
+
+ v4l2_device_disconnect(&cam->v4l2_dev);
+
+ kref_put(&cam->kref, sn9c102_release_resources);
+
+ up_write(&sn9c102_dev_lock);
+}
+
+
+static struct usb_driver sn9c102_usb_driver = {
+ .name = "sn9c102",
+ .id_table = sn9c102_id_table,
+ .probe = sn9c102_usb_probe,
+ .disconnect = sn9c102_usb_disconnect,
+};
+
+module_usb_driver(sn9c102_usb_driver);
diff --git a/drivers/staging/media/sn9c102/sn9c102_devtable.h b/drivers/staging/media/sn9c102/sn9c102_devtable.h
new file mode 100644
index 00000000000..b187a8a304e
--- /dev/null
+++ b/drivers/staging/media/sn9c102/sn9c102_devtable.h
@@ -0,0 +1,145 @@
+/***************************************************************************
+ * Table of device identifiers of the SN9C1xx PC Camera Controllers *
+ * *
+ * Copyright (C) 2007 by Luca Risolia <luca.risolia@studio.unibo.it> *
+ * *
+ * 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., 675 Mass Ave, Cambridge, MA 02139, USA. *
+ ***************************************************************************/
+
+#ifndef _SN9C102_DEVTABLE_H_
+#define _SN9C102_DEVTABLE_H_
+
+#include <linux/usb.h>
+
+struct sn9c102_device;
+
+/*
+ Each SN9C1xx camera has proper PID/VID identifiers.
+ SN9C103, SN9C105, SN9C120 support multiple interfaces, but we only have to
+ handle the video class interface.
+*/
+#define SN9C102_USB_DEVICE(vend, prod, bridge) \
+ .match_flags = USB_DEVICE_ID_MATCH_DEVICE | \
+ USB_DEVICE_ID_MATCH_INT_CLASS, \
+ .idVendor = (vend), \
+ .idProduct = (prod), \
+ .bInterfaceClass = 0xff, \
+ .driver_info = (bridge)
+
+static const struct usb_device_id sn9c102_id_table[] = {
+ /* SN9C101 and SN9C102 */
+#if !defined CONFIG_USB_GSPCA_SONIXB && !defined CONFIG_USB_GSPCA_SONIXB_MODULE
+ { SN9C102_USB_DEVICE(0x0c45, 0x6001, BRIDGE_SN9C102), },
+ { SN9C102_USB_DEVICE(0x0c45, 0x6005, BRIDGE_SN9C102), },
+ { SN9C102_USB_DEVICE(0x0c45, 0x6007, BRIDGE_SN9C102), },
+ { SN9C102_USB_DEVICE(0x0c45, 0x6009, BRIDGE_SN9C102), },
+ { SN9C102_USB_DEVICE(0x0c45, 0x600d, BRIDGE_SN9C102), },
+/* { SN9C102_USB_DEVICE(0x0c45, 0x6011, BRIDGE_SN9C102), }, OV6650 */
+ { SN9C102_USB_DEVICE(0x0c45, 0x6019, BRIDGE_SN9C102), },
+ { SN9C102_USB_DEVICE(0x0c45, 0x6024, BRIDGE_SN9C102), },
+ { SN9C102_USB_DEVICE(0x0c45, 0x6025, BRIDGE_SN9C102), },
+ { SN9C102_USB_DEVICE(0x0c45, 0x6028, BRIDGE_SN9C102), },
+ { SN9C102_USB_DEVICE(0x0c45, 0x6029, BRIDGE_SN9C102), },
+ { SN9C102_USB_DEVICE(0x0c45, 0x602a, BRIDGE_SN9C102), },
+#endif
+ { SN9C102_USB_DEVICE(0x0c45, 0x602b, BRIDGE_SN9C102), }, /* not in sonixb */
+#if !defined CONFIG_USB_GSPCA_SONIXB && !defined CONFIG_USB_GSPCA_SONIXB_MODULE
+ { SN9C102_USB_DEVICE(0x0c45, 0x602c, BRIDGE_SN9C102), },
+/* { SN9C102_USB_DEVICE(0x0c45, 0x602d, BRIDGE_SN9C102), }, HV7131R */
+ { SN9C102_USB_DEVICE(0x0c45, 0x602e, BRIDGE_SN9C102), },
+#endif
+ { SN9C102_USB_DEVICE(0x0c45, 0x6030, BRIDGE_SN9C102), }, /* not in sonixb */
+ /* SN9C103 */
+/* { SN9C102_USB_DEVICE(0x0c45, 0x6080, BRIDGE_SN9C103), }, non existent ? */
+ { SN9C102_USB_DEVICE(0x0c45, 0x6082, BRIDGE_SN9C103), }, /* not in sonixb */
+#if !defined CONFIG_USB_GSPCA_SONIXB && !defined CONFIG_USB_GSPCA_SONIXB_MODULE
+/* { SN9C102_USB_DEVICE(0x0c45, 0x6083, BRIDGE_SN9C103), }, HY7131D/E */
+/* { SN9C102_USB_DEVICE(0x0c45, 0x6088, BRIDGE_SN9C103), }, non existent ? */
+/* { SN9C102_USB_DEVICE(0x0c45, 0x608a, BRIDGE_SN9C103), }, non existent ? */
+/* { SN9C102_USB_DEVICE(0x0c45, 0x608b, BRIDGE_SN9C103), }, non existent ? */
+ { SN9C102_USB_DEVICE(0x0c45, 0x608c, BRIDGE_SN9C103), },
+/* { SN9C102_USB_DEVICE(0x0c45, 0x608e, BRIDGE_SN9C103), }, CISVF10 */
+ { SN9C102_USB_DEVICE(0x0c45, 0x608f, BRIDGE_SN9C103), },
+/* { SN9C102_USB_DEVICE(0x0c45, 0x60a0, BRIDGE_SN9C103), }, non existent ? */
+/* { SN9C102_USB_DEVICE(0x0c45, 0x60a2, BRIDGE_SN9C103), }, non existent ? */
+/* { SN9C102_USB_DEVICE(0x0c45, 0x60a3, BRIDGE_SN9C103), }, non existent ? */
+/* { SN9C102_USB_DEVICE(0x0c45, 0x60a8, BRIDGE_SN9C103), }, PAS106 */
+/* { SN9C102_USB_DEVICE(0x0c45, 0x60aa, BRIDGE_SN9C103), }, TAS5130 */
+/* { SN9C102_USB_DEVICE(0x0c45, 0x60ab, BRIDGE_SN9C103), }, TAS5110, non existent */
+/* { SN9C102_USB_DEVICE(0x0c45, 0x60ac, BRIDGE_SN9C103), }, non existent ? */
+/* { SN9C102_USB_DEVICE(0x0c45, 0x60ae, BRIDGE_SN9C103), }, non existent ? */
+ { SN9C102_USB_DEVICE(0x0c45, 0x60af, BRIDGE_SN9C103), },
+ { SN9C102_USB_DEVICE(0x0c45, 0x60b0, BRIDGE_SN9C103), },
+/* { SN9C102_USB_DEVICE(0x0c45, 0x60b2, BRIDGE_SN9C103), }, non existent ? */
+/* { SN9C102_USB_DEVICE(0x0c45, 0x60b3, BRIDGE_SN9C103), }, non existent ? */
+/* { SN9C102_USB_DEVICE(0x0c45, 0x60b8, BRIDGE_SN9C103), }, non existent ? */
+/* { SN9C102_USB_DEVICE(0x0c45, 0x60ba, BRIDGE_SN9C103), }, non existent ? */
+/* { SN9C102_USB_DEVICE(0x0c45, 0x60bb, BRIDGE_SN9C103), }, non existent ? */
+/* { SN9C102_USB_DEVICE(0x0c45, 0x60bc, BRIDGE_SN9C103), }, non existent ? */
+/* { SN9C102_USB_DEVICE(0x0c45, 0x60be, BRIDGE_SN9C103), }, non existent ? */
+#endif
+ /* SN9C105 */
+#if !defined CONFIG_USB_GSPCA_SONIXJ && !defined CONFIG_USB_GSPCA_SONIXJ_MODULE
+ { SN9C102_USB_DEVICE(0x045e, 0x00f5, BRIDGE_SN9C105), },
+ { SN9C102_USB_DEVICE(0x045e, 0x00f7, BRIDGE_SN9C105), },
+ { SN9C102_USB_DEVICE(0x0471, 0x0327, BRIDGE_SN9C105), },
+ { SN9C102_USB_DEVICE(0x0471, 0x0328, BRIDGE_SN9C105), },
+ { SN9C102_USB_DEVICE(0x0c45, 0x60c0, BRIDGE_SN9C105), },
+/* { SN9C102_USB_DEVICE(0x0c45, 0x60c2, BRIDGE_SN9C105), }, PO1030 */
+/* { SN9C102_USB_DEVICE(0x0c45, 0x60c8, BRIDGE_SN9C105), }, OM6801 */
+/* { SN9C102_USB_DEVICE(0x0c45, 0x60cc, BRIDGE_SN9C105), }, HV7131GP */
+/* { SN9C102_USB_DEVICE(0x0c45, 0x60ea, BRIDGE_SN9C105), }, non existent ? */
+/* { SN9C102_USB_DEVICE(0x0c45, 0x60ec, BRIDGE_SN9C105), }, MO4000 */
+/* { SN9C102_USB_DEVICE(0x0c45, 0x60ef, BRIDGE_SN9C105), }, ICM105C */
+/* { SN9C102_USB_DEVICE(0x0c45, 0x60fa, BRIDGE_SN9C105), }, OV7648 */
+ { SN9C102_USB_DEVICE(0x0c45, 0x60fb, BRIDGE_SN9C105), },
+ { SN9C102_USB_DEVICE(0x0c45, 0x60fc, BRIDGE_SN9C105), },
+ { SN9C102_USB_DEVICE(0x0c45, 0x60fe, BRIDGE_SN9C105), },
+ /* SN9C120 */
+ { SN9C102_USB_DEVICE(0x0458, 0x7025, BRIDGE_SN9C120), },
+/* { SN9C102_USB_DEVICE(0x0c45, 0x6102, BRIDGE_SN9C120), }, po2030 */
+/* { SN9C102_USB_DEVICE(0x0c45, 0x6108, BRIDGE_SN9C120), }, om6801 */
+/* { SN9C102_USB_DEVICE(0x0c45, 0x610f, BRIDGE_SN9C120), }, S5K53BEB */
+ { SN9C102_USB_DEVICE(0x0c45, 0x6130, BRIDGE_SN9C120), },
+/* { SN9C102_USB_DEVICE(0x0c45, 0x6138, BRIDGE_SN9C120), }, MO8000 */
+ { SN9C102_USB_DEVICE(0x0c45, 0x613a, BRIDGE_SN9C120), },
+ { SN9C102_USB_DEVICE(0x0c45, 0x613b, BRIDGE_SN9C120), },
+ { SN9C102_USB_DEVICE(0x0c45, 0x613c, BRIDGE_SN9C120), },
+ { SN9C102_USB_DEVICE(0x0c45, 0x613e, BRIDGE_SN9C120), },
+#endif
+ { }
+};
+
+/*
+ Probing functions: on success, you must attach the sensor to the camera
+ by calling sn9c102_attach_sensor().
+ To enable the I2C communication, you might need to perform a really basic
+ initialization of the SN9C1XX chip.
+ Functions must return 0 on success, the appropriate error otherwise.
+*/
+extern int sn9c102_probe_hv7131d(struct sn9c102_device *cam);
+extern int sn9c102_probe_hv7131r(struct sn9c102_device *cam);
+extern int sn9c102_probe_mi0343(struct sn9c102_device *cam);
+extern int sn9c102_probe_mi0360(struct sn9c102_device *cam);
+extern int sn9c102_probe_mt9v111(struct sn9c102_device *cam);
+extern int sn9c102_probe_ov7630(struct sn9c102_device *cam);
+extern int sn9c102_probe_ov7660(struct sn9c102_device *cam);
+extern int sn9c102_probe_pas106b(struct sn9c102_device *cam);
+extern int sn9c102_probe_pas202bcb(struct sn9c102_device *cam);
+extern int sn9c102_probe_tas5110c1b(struct sn9c102_device *cam);
+extern int sn9c102_probe_tas5110d(struct sn9c102_device *cam);
+extern int sn9c102_probe_tas5130d1b(struct sn9c102_device *cam);
+
+#endif /* _SN9C102_DEVTABLE_H_ */
diff --git a/drivers/staging/media/sn9c102/sn9c102_hv7131d.c b/drivers/staging/media/sn9c102/sn9c102_hv7131d.c
new file mode 100644
index 00000000000..f1d94f0190c
--- /dev/null
+++ b/drivers/staging/media/sn9c102/sn9c102_hv7131d.c
@@ -0,0 +1,269 @@
+/***************************************************************************
+ * Plug-in for HV7131D image sensor connected to the SN9C1xx PC Camera *
+ * Controllers *
+ * *
+ * Copyright (C) 2004-2007 by Luca Risolia <luca.risolia@studio.unibo.it> *
+ * *
+ * 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., 675 Mass Ave, Cambridge, MA 02139, USA. *
+ ***************************************************************************/
+
+#include "sn9c102_sensor.h"
+#include "sn9c102_devtable.h"
+
+
+static int hv7131d_init(struct sn9c102_device *cam)
+{
+ int err;
+
+ err = sn9c102_write_const_regs(cam, {0x00, 0x10}, {0x00, 0x11},
+ {0x00, 0x14}, {0x60, 0x17},
+ {0x0e, 0x18}, {0xf2, 0x19});
+
+ err += sn9c102_i2c_write(cam, 0x01, 0x04);
+ err += sn9c102_i2c_write(cam, 0x02, 0x00);
+ err += sn9c102_i2c_write(cam, 0x28, 0x00);
+
+ return err;
+}
+
+
+static int hv7131d_get_ctrl(struct sn9c102_device *cam,
+ struct v4l2_control *ctrl)
+{
+ switch (ctrl->id) {
+ case V4L2_CID_EXPOSURE:
+ {
+ int r1 = sn9c102_i2c_read(cam, 0x26),
+ r2 = sn9c102_i2c_read(cam, 0x27);
+ if (r1 < 0 || r2 < 0)
+ return -EIO;
+ ctrl->value = (r1 << 8) | (r2 & 0xff);
+ }
+ return 0;
+ case V4L2_CID_RED_BALANCE:
+ ctrl->value = sn9c102_i2c_read(cam, 0x31);
+ if (ctrl->value < 0)
+ return -EIO;
+ ctrl->value = 0x3f - (ctrl->value & 0x3f);
+ return 0;
+ case V4L2_CID_BLUE_BALANCE:
+ ctrl->value = sn9c102_i2c_read(cam, 0x33);
+ if (ctrl->value < 0)
+ return -EIO;
+ ctrl->value = 0x3f - (ctrl->value & 0x3f);
+ return 0;
+ case SN9C102_V4L2_CID_GREEN_BALANCE:
+ ctrl->value = sn9c102_i2c_read(cam, 0x32);
+ if (ctrl->value < 0)
+ return -EIO;
+ ctrl->value = 0x3f - (ctrl->value & 0x3f);
+ return 0;
+ case SN9C102_V4L2_CID_RESET_LEVEL:
+ ctrl->value = sn9c102_i2c_read(cam, 0x30);
+ if (ctrl->value < 0)
+ return -EIO;
+ ctrl->value &= 0x3f;
+ return 0;
+ case SN9C102_V4L2_CID_PIXEL_BIAS_VOLTAGE:
+ ctrl->value = sn9c102_i2c_read(cam, 0x34);
+ if (ctrl->value < 0)
+ return -EIO;
+ ctrl->value &= 0x07;
+ return 0;
+ default:
+ return -EINVAL;
+ }
+}
+
+
+static int hv7131d_set_ctrl(struct sn9c102_device *cam,
+ const struct v4l2_control *ctrl)
+{
+ int err = 0;
+
+ switch (ctrl->id) {
+ case V4L2_CID_EXPOSURE:
+ err += sn9c102_i2c_write(cam, 0x26, ctrl->value >> 8);
+ err += sn9c102_i2c_write(cam, 0x27, ctrl->value & 0xff);
+ break;
+ case V4L2_CID_RED_BALANCE:
+ err += sn9c102_i2c_write(cam, 0x31, 0x3f - ctrl->value);
+ break;
+ case V4L2_CID_BLUE_BALANCE:
+ err += sn9c102_i2c_write(cam, 0x33, 0x3f - ctrl->value);
+ break;
+ case SN9C102_V4L2_CID_GREEN_BALANCE:
+ err += sn9c102_i2c_write(cam, 0x32, 0x3f - ctrl->value);
+ break;
+ case SN9C102_V4L2_CID_RESET_LEVEL:
+ err += sn9c102_i2c_write(cam, 0x30, ctrl->value);
+ break;
+ case SN9C102_V4L2_CID_PIXEL_BIAS_VOLTAGE:
+ err += sn9c102_i2c_write(cam, 0x34, ctrl->value);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return err ? -EIO : 0;
+}
+
+
+static int hv7131d_set_crop(struct sn9c102_device *cam,
+ const struct v4l2_rect *rect)
+{
+ struct sn9c102_sensor *s = sn9c102_get_sensor(cam);
+ int err = 0;
+ u8 h_start = (u8)(rect->left - s->cropcap.bounds.left) + 2,
+ v_start = (u8)(rect->top - s->cropcap.bounds.top) + 2;
+
+ err += sn9c102_write_reg(cam, h_start, 0x12);
+ err += sn9c102_write_reg(cam, v_start, 0x13);
+
+ return err;
+}
+
+
+static int hv7131d_set_pix_format(struct sn9c102_device *cam,
+ const struct v4l2_pix_format *pix)
+{
+ int err = 0;
+
+ if (pix->pixelformat == V4L2_PIX_FMT_SN9C10X)
+ err += sn9c102_write_reg(cam, 0x42, 0x19);
+ else
+ err += sn9c102_write_reg(cam, 0xf2, 0x19);
+
+ return err;
+}
+
+
+static const struct sn9c102_sensor hv7131d = {
+ .name = "HV7131D",
+ .maintainer = "Luca Risolia <luca.risolia@studio.unibo.it>",
+ .supported_bridge = BRIDGE_SN9C101 | BRIDGE_SN9C102,
+ .sysfs_ops = SN9C102_I2C_READ | SN9C102_I2C_WRITE,
+ .frequency = SN9C102_I2C_100KHZ,
+ .interface = SN9C102_I2C_2WIRES,
+ .i2c_slave_id = 0x11,
+ .init = &hv7131d_init,
+ .qctrl = {
+ {
+ .id = V4L2_CID_EXPOSURE,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "exposure",
+ .minimum = 0x0250,
+ .maximum = 0xffff,
+ .step = 0x0001,
+ .default_value = 0x0250,
+ .flags = 0,
+ },
+ {
+ .id = V4L2_CID_RED_BALANCE,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "red balance",
+ .minimum = 0x00,
+ .maximum = 0x3f,
+ .step = 0x01,
+ .default_value = 0x00,
+ .flags = 0,
+ },
+ {
+ .id = V4L2_CID_BLUE_BALANCE,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "blue balance",
+ .minimum = 0x00,
+ .maximum = 0x3f,
+ .step = 0x01,
+ .default_value = 0x20,
+ .flags = 0,
+ },
+ {
+ .id = SN9C102_V4L2_CID_GREEN_BALANCE,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "green balance",
+ .minimum = 0x00,
+ .maximum = 0x3f,
+ .step = 0x01,
+ .default_value = 0x1e,
+ .flags = 0,
+ },
+ {
+ .id = SN9C102_V4L2_CID_RESET_LEVEL,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "reset level",
+ .minimum = 0x19,
+ .maximum = 0x3f,
+ .step = 0x01,
+ .default_value = 0x30,
+ .flags = 0,
+ },
+ {
+ .id = SN9C102_V4L2_CID_PIXEL_BIAS_VOLTAGE,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "pixel bias voltage",
+ .minimum = 0x00,
+ .maximum = 0x07,
+ .step = 0x01,
+ .default_value = 0x02,
+ .flags = 0,
+ },
+ },
+ .get_ctrl = &hv7131d_get_ctrl,
+ .set_ctrl = &hv7131d_set_ctrl,
+ .cropcap = {
+ .bounds = {
+ .left = 0,
+ .top = 0,
+ .width = 640,
+ .height = 480,
+ },
+ .defrect = {
+ .left = 0,
+ .top = 0,
+ .width = 640,
+ .height = 480,
+ },
+ },
+ .set_crop = &hv7131d_set_crop,
+ .pix_format = {
+ .width = 640,
+ .height = 480,
+ .pixelformat = V4L2_PIX_FMT_SBGGR8,
+ .priv = 8,
+ },
+ .set_pix_format = &hv7131d_set_pix_format
+};
+
+
+int sn9c102_probe_hv7131d(struct sn9c102_device *cam)
+{
+ int r0 = 0, r1 = 0, err;
+
+ err = sn9c102_write_const_regs(cam, {0x01, 0x01}, {0x00, 0x01},
+ {0x28, 0x17});
+
+ r0 = sn9c102_i2c_try_read(cam, &hv7131d, 0x00);
+ r1 = sn9c102_i2c_try_read(cam, &hv7131d, 0x01);
+ if (err || r0 < 0 || r1 < 0)
+ return -EIO;
+
+ if ((r0 != 0x00 && r0 != 0x01) || r1 != 0x04)
+ return -ENODEV;
+
+ sn9c102_attach_sensor(cam, &hv7131d);
+
+ return 0;
+}
diff --git a/drivers/staging/media/sn9c102/sn9c102_hv7131r.c b/drivers/staging/media/sn9c102/sn9c102_hv7131r.c
new file mode 100644
index 00000000000..51b24e000e8
--- /dev/null
+++ b/drivers/staging/media/sn9c102/sn9c102_hv7131r.c
@@ -0,0 +1,369 @@
+/***************************************************************************
+ * Plug-in for HV7131R image sensor connected to the SN9C1xx PC Camera *
+ * Controllers *
+ * *
+ * Copyright (C) 2007 by Luca Risolia <luca.risolia@studio.unibo.it> *
+ * *
+ * 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., 675 Mass Ave, Cambridge, MA 02139, USA. *
+ ***************************************************************************/
+
+#include "sn9c102_sensor.h"
+#include "sn9c102_devtable.h"
+
+
+static int hv7131r_init(struct sn9c102_device *cam)
+{
+ int err = 0;
+
+ switch (sn9c102_get_bridge(cam)) {
+ case BRIDGE_SN9C103:
+ err = sn9c102_write_const_regs(cam, {0x00, 0x03}, {0x1a, 0x04},
+ {0x20, 0x05}, {0x20, 0x06},
+ {0x03, 0x10}, {0x00, 0x14},
+ {0x60, 0x17}, {0x0a, 0x18},
+ {0xf0, 0x19}, {0x1d, 0x1a},
+ {0x10, 0x1b}, {0x02, 0x1c},
+ {0x03, 0x1d}, {0x0f, 0x1e},
+ {0x0c, 0x1f}, {0x00, 0x20},
+ {0x10, 0x21}, {0x20, 0x22},
+ {0x30, 0x23}, {0x40, 0x24},
+ {0x50, 0x25}, {0x60, 0x26},
+ {0x70, 0x27}, {0x80, 0x28},
+ {0x90, 0x29}, {0xa0, 0x2a},
+ {0xb0, 0x2b}, {0xc0, 0x2c},
+ {0xd0, 0x2d}, {0xe0, 0x2e},
+ {0xf0, 0x2f}, {0xff, 0x30});
+ break;
+ case BRIDGE_SN9C105:
+ case BRIDGE_SN9C120:
+ err = sn9c102_write_const_regs(cam, {0x44, 0x01}, {0x40, 0x02},
+ {0x00, 0x03}, {0x1a, 0x04},
+ {0x44, 0x05}, {0x3e, 0x06},
+ {0x1a, 0x07}, {0x03, 0x10},
+ {0x08, 0x14}, {0xa3, 0x17},
+ {0x4b, 0x18}, {0x00, 0x19},
+ {0x1d, 0x1a}, {0x10, 0x1b},
+ {0x02, 0x1c}, {0x03, 0x1d},
+ {0x0f, 0x1e}, {0x0c, 0x1f},
+ {0x00, 0x20}, {0x29, 0x21},
+ {0x40, 0x22}, {0x54, 0x23},
+ {0x66, 0x24}, {0x76, 0x25},
+ {0x85, 0x26}, {0x94, 0x27},
+ {0xa1, 0x28}, {0xae, 0x29},
+ {0xbb, 0x2a}, {0xc7, 0x2b},
+ {0xd3, 0x2c}, {0xde, 0x2d},
+ {0xea, 0x2e}, {0xf4, 0x2f},
+ {0xff, 0x30}, {0x00, 0x3F},
+ {0xC7, 0x40}, {0x01, 0x41},
+ {0x44, 0x42}, {0x00, 0x43},
+ {0x44, 0x44}, {0x00, 0x45},
+ {0x44, 0x46}, {0x00, 0x47},
+ {0xC7, 0x48}, {0x01, 0x49},
+ {0xC7, 0x4A}, {0x01, 0x4B},
+ {0xC7, 0x4C}, {0x01, 0x4D},
+ {0x44, 0x4E}, {0x00, 0x4F},
+ {0x44, 0x50}, {0x00, 0x51},
+ {0x44, 0x52}, {0x00, 0x53},
+ {0xC7, 0x54}, {0x01, 0x55},
+ {0xC7, 0x56}, {0x01, 0x57},
+ {0xC7, 0x58}, {0x01, 0x59},
+ {0x44, 0x5A}, {0x00, 0x5B},
+ {0x44, 0x5C}, {0x00, 0x5D},
+ {0x44, 0x5E}, {0x00, 0x5F},
+ {0xC7, 0x60}, {0x01, 0x61},
+ {0xC7, 0x62}, {0x01, 0x63},
+ {0xC7, 0x64}, {0x01, 0x65},
+ {0x44, 0x66}, {0x00, 0x67},
+ {0x44, 0x68}, {0x00, 0x69},
+ {0x44, 0x6A}, {0x00, 0x6B},
+ {0xC7, 0x6C}, {0x01, 0x6D},
+ {0xC7, 0x6E}, {0x01, 0x6F},
+ {0xC7, 0x70}, {0x01, 0x71},
+ {0x44, 0x72}, {0x00, 0x73},
+ {0x44, 0x74}, {0x00, 0x75},
+ {0x44, 0x76}, {0x00, 0x77},
+ {0xC7, 0x78}, {0x01, 0x79},
+ {0xC7, 0x7A}, {0x01, 0x7B},
+ {0xC7, 0x7C}, {0x01, 0x7D},
+ {0x44, 0x7E}, {0x00, 0x7F},
+ {0x14, 0x84}, {0x00, 0x85},
+ {0x27, 0x86}, {0x00, 0x87},
+ {0x07, 0x88}, {0x00, 0x89},
+ {0xEC, 0x8A}, {0x0f, 0x8B},
+ {0xD8, 0x8C}, {0x0f, 0x8D},
+ {0x3D, 0x8E}, {0x00, 0x8F},
+ {0x3D, 0x90}, {0x00, 0x91},
+ {0xCD, 0x92}, {0x0f, 0x93},
+ {0xf7, 0x94}, {0x0f, 0x95},
+ {0x0C, 0x96}, {0x00, 0x97},
+ {0x00, 0x98}, {0x66, 0x99},
+ {0x05, 0x9A}, {0x00, 0x9B},
+ {0x04, 0x9C}, {0x00, 0x9D},
+ {0x08, 0x9E}, {0x00, 0x9F},
+ {0x2D, 0xC0}, {0x2D, 0xC1},
+ {0x3A, 0xC2}, {0x05, 0xC3},
+ {0x04, 0xC4}, {0x3F, 0xC5},
+ {0x00, 0xC6}, {0x00, 0xC7},
+ {0x50, 0xC8}, {0x3C, 0xC9},
+ {0x28, 0xCA}, {0xD8, 0xCB},
+ {0x14, 0xCC}, {0xEC, 0xCD},
+ {0x32, 0xCE}, {0xDD, 0xCF},
+ {0x32, 0xD0}, {0xDD, 0xD1},
+ {0x6A, 0xD2}, {0x50, 0xD3},
+ {0x00, 0xD4}, {0x00, 0xD5},
+ {0x00, 0xD6});
+ break;
+ default:
+ break;
+ }
+
+ err += sn9c102_i2c_write(cam, 0x20, 0x00);
+ err += sn9c102_i2c_write(cam, 0x21, 0xd6);
+ err += sn9c102_i2c_write(cam, 0x25, 0x06);
+
+ return err;
+}
+
+
+static int hv7131r_get_ctrl(struct sn9c102_device *cam,
+ struct v4l2_control *ctrl)
+{
+ switch (ctrl->id) {
+ case V4L2_CID_GAIN:
+ ctrl->value = sn9c102_i2c_read(cam, 0x30);
+ if (ctrl->value < 0)
+ return -EIO;
+ return 0;
+ case V4L2_CID_RED_BALANCE:
+ ctrl->value = sn9c102_i2c_read(cam, 0x31);
+ if (ctrl->value < 0)
+ return -EIO;
+ ctrl->value = ctrl->value & 0x3f;
+ return 0;
+ case V4L2_CID_BLUE_BALANCE:
+ ctrl->value = sn9c102_i2c_read(cam, 0x33);
+ if (ctrl->value < 0)
+ return -EIO;
+ ctrl->value = ctrl->value & 0x3f;
+ return 0;
+ case SN9C102_V4L2_CID_GREEN_BALANCE:
+ ctrl->value = sn9c102_i2c_read(cam, 0x32);
+ if (ctrl->value < 0)
+ return -EIO;
+ ctrl->value = ctrl->value & 0x3f;
+ return 0;
+ case V4L2_CID_BLACK_LEVEL:
+ ctrl->value = sn9c102_i2c_read(cam, 0x01);
+ if (ctrl->value < 0)
+ return -EIO;
+ ctrl->value = (ctrl->value & 0x08) ? 1 : 0;
+ return 0;
+ default:
+ return -EINVAL;
+ }
+}
+
+
+static int hv7131r_set_ctrl(struct sn9c102_device *cam,
+ const struct v4l2_control *ctrl)
+{
+ int err = 0;
+
+ switch (ctrl->id) {
+ case V4L2_CID_GAIN:
+ err += sn9c102_i2c_write(cam, 0x30, ctrl->value);
+ break;
+ case V4L2_CID_RED_BALANCE:
+ err += sn9c102_i2c_write(cam, 0x31, ctrl->value);
+ break;
+ case V4L2_CID_BLUE_BALANCE:
+ err += sn9c102_i2c_write(cam, 0x33, ctrl->value);
+ break;
+ case SN9C102_V4L2_CID_GREEN_BALANCE:
+ err += sn9c102_i2c_write(cam, 0x32, ctrl->value);
+ break;
+ case V4L2_CID_BLACK_LEVEL:
+ {
+ int r = sn9c102_i2c_read(cam, 0x01);
+
+ if (r < 0)
+ return -EIO;
+ err += sn9c102_i2c_write(cam, 0x01,
+ (ctrl->value<<3) | (r&0xf7));
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return err ? -EIO : 0;
+}
+
+
+static int hv7131r_set_crop(struct sn9c102_device *cam,
+ const struct v4l2_rect *rect)
+{
+ struct sn9c102_sensor *s = sn9c102_get_sensor(cam);
+ int err = 0;
+ u8 h_start = (u8)(rect->left - s->cropcap.bounds.left) + 1,
+ v_start = (u8)(rect->top - s->cropcap.bounds.top) + 1;
+
+ err += sn9c102_write_reg(cam, h_start, 0x12);
+ err += sn9c102_write_reg(cam, v_start, 0x13);
+
+ return err;
+}
+
+
+static int hv7131r_set_pix_format(struct sn9c102_device *cam,
+ const struct v4l2_pix_format *pix)
+{
+ int err = 0;
+
+ switch (sn9c102_get_bridge(cam)) {
+ case BRIDGE_SN9C103:
+ if (pix->pixelformat == V4L2_PIX_FMT_SBGGR8) {
+ err += sn9c102_write_reg(cam, 0xa0, 0x19);
+ err += sn9c102_i2c_write(cam, 0x01, 0x04);
+ } else {
+ err += sn9c102_write_reg(cam, 0x30, 0x19);
+ err += sn9c102_i2c_write(cam, 0x01, 0x04);
+ }
+ break;
+ case BRIDGE_SN9C105:
+ case BRIDGE_SN9C120:
+ if (pix->pixelformat == V4L2_PIX_FMT_SBGGR8) {
+ err += sn9c102_write_reg(cam, 0xa5, 0x17);
+ err += sn9c102_i2c_write(cam, 0x01, 0x24);
+ } else {
+ err += sn9c102_write_reg(cam, 0xa3, 0x17);
+ err += sn9c102_i2c_write(cam, 0x01, 0x04);
+ }
+ break;
+ default:
+ break;
+ }
+
+ return err;
+}
+
+
+static const struct sn9c102_sensor hv7131r = {
+ .name = "HV7131R",
+ .maintainer = "Luca Risolia <luca.risolia@studio.unibo.it>",
+ .supported_bridge = BRIDGE_SN9C103 | BRIDGE_SN9C105 | BRIDGE_SN9C120,
+ .sysfs_ops = SN9C102_I2C_READ | SN9C102_I2C_WRITE,
+ .frequency = SN9C102_I2C_100KHZ,
+ .interface = SN9C102_I2C_2WIRES,
+ .i2c_slave_id = 0x11,
+ .init = &hv7131r_init,
+ .qctrl = {
+ {
+ .id = V4L2_CID_GAIN,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "global gain",
+ .minimum = 0x00,
+ .maximum = 0xff,
+ .step = 0x01,
+ .default_value = 0x40,
+ .flags = 0,
+ },
+ {
+ .id = V4L2_CID_RED_BALANCE,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "red balance",
+ .minimum = 0x00,
+ .maximum = 0x3f,
+ .step = 0x01,
+ .default_value = 0x08,
+ .flags = 0,
+ },
+ {
+ .id = V4L2_CID_BLUE_BALANCE,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "blue balance",
+ .minimum = 0x00,
+ .maximum = 0x3f,
+ .step = 0x01,
+ .default_value = 0x1a,
+ .flags = 0,
+ },
+ {
+ .id = SN9C102_V4L2_CID_GREEN_BALANCE,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "green balance",
+ .minimum = 0x00,
+ .maximum = 0x3f,
+ .step = 0x01,
+ .default_value = 0x2f,
+ .flags = 0,
+ },
+ {
+ .id = V4L2_CID_BLACK_LEVEL,
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ .name = "auto black level compensation",
+ .minimum = 0x00,
+ .maximum = 0x01,
+ .step = 0x01,
+ .default_value = 0x00,
+ .flags = 0,
+ },
+ },
+ .get_ctrl = &hv7131r_get_ctrl,
+ .set_ctrl = &hv7131r_set_ctrl,
+ .cropcap = {
+ .bounds = {
+ .left = 0,
+ .top = 0,
+ .width = 640,
+ .height = 480,
+ },
+ .defrect = {
+ .left = 0,
+ .top = 0,
+ .width = 640,
+ .height = 480,
+ },
+ },
+ .set_crop = &hv7131r_set_crop,
+ .pix_format = {
+ .width = 640,
+ .height = 480,
+ .pixelformat = V4L2_PIX_FMT_SBGGR8,
+ .priv = 8,
+ },
+ .set_pix_format = &hv7131r_set_pix_format
+};
+
+
+int sn9c102_probe_hv7131r(struct sn9c102_device *cam)
+{
+ int devid, err;
+
+ err = sn9c102_write_const_regs(cam, {0x09, 0x01}, {0x44, 0x02},
+ {0x34, 0x01}, {0x20, 0x17},
+ {0x34, 0x01}, {0x46, 0x01});
+
+ devid = sn9c102_i2c_try_read(cam, &hv7131r, 0x00);
+ if (err || devid < 0)
+ return -EIO;
+
+ if (devid != 0x02)
+ return -ENODEV;
+
+ sn9c102_attach_sensor(cam, &hv7131r);
+
+ return 0;
+}
diff --git a/drivers/staging/media/sn9c102/sn9c102_mi0343.c b/drivers/staging/media/sn9c102/sn9c102_mi0343.c
new file mode 100644
index 00000000000..b20fdb6541d
--- /dev/null
+++ b/drivers/staging/media/sn9c102/sn9c102_mi0343.c
@@ -0,0 +1,352 @@
+/***************************************************************************
+ * Plug-in for MI-0343 image sensor connected to the SN9C1xx PC Camera *
+ * Controllers *
+ * *
+ * Copyright (C) 2004-2007 by Luca Risolia <luca.risolia@studio.unibo.it> *
+ * *
+ * 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., 675 Mass Ave, Cambridge, MA 02139, USA. *
+ ***************************************************************************/
+
+#include "sn9c102_sensor.h"
+#include "sn9c102_devtable.h"
+
+
+static int mi0343_init(struct sn9c102_device *cam)
+{
+ struct sn9c102_sensor *s = sn9c102_get_sensor(cam);
+ int err = 0;
+
+ err = sn9c102_write_const_regs(cam, {0x00, 0x10}, {0x00, 0x11},
+ {0x0a, 0x14}, {0x40, 0x01},
+ {0x20, 0x17}, {0x07, 0x18},
+ {0xa0, 0x19});
+
+ err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, 0x0d,
+ 0x00, 0x01, 0, 0);
+ err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, 0x0d,
+ 0x00, 0x00, 0, 0);
+ err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, 0x03,
+ 0x01, 0xe1, 0, 0);
+ err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, 0x04,
+ 0x02, 0x81, 0, 0);
+ err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, 0x05,
+ 0x00, 0x17, 0, 0);
+ err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, 0x06,
+ 0x00, 0x11, 0, 0);
+ err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, 0x62,
+ 0x04, 0x9a, 0, 0);
+
+ return err;
+}
+
+
+static int mi0343_get_ctrl(struct sn9c102_device *cam,
+ struct v4l2_control *ctrl)
+{
+ struct sn9c102_sensor *s = sn9c102_get_sensor(cam);
+ u8 data[2];
+
+ switch (ctrl->id) {
+ case V4L2_CID_EXPOSURE:
+ if (sn9c102_i2c_try_raw_read(cam, s, s->i2c_slave_id, 0x09, 2,
+ data) < 0)
+ return -EIO;
+ ctrl->value = data[0];
+ return 0;
+ case V4L2_CID_GAIN:
+ if (sn9c102_i2c_try_raw_read(cam, s, s->i2c_slave_id, 0x35, 2,
+ data) < 0)
+ return -EIO;
+ break;
+ case V4L2_CID_HFLIP:
+ if (sn9c102_i2c_try_raw_read(cam, s, s->i2c_slave_id, 0x20, 2,
+ data) < 0)
+ return -EIO;
+ ctrl->value = data[1] & 0x20 ? 1 : 0;
+ return 0;
+ case V4L2_CID_VFLIP:
+ if (sn9c102_i2c_try_raw_read(cam, s, s->i2c_slave_id, 0x20, 2,
+ data) < 0)
+ return -EIO;
+ ctrl->value = data[1] & 0x80 ? 1 : 0;
+ return 0;
+ case V4L2_CID_RED_BALANCE:
+ if (sn9c102_i2c_try_raw_read(cam, s, s->i2c_slave_id, 0x2d, 2,
+ data) < 0)
+ return -EIO;
+ break;
+ case V4L2_CID_BLUE_BALANCE:
+ if (sn9c102_i2c_try_raw_read(cam, s, s->i2c_slave_id, 0x2c, 2,
+ data) < 0)
+ return -EIO;
+ break;
+ case SN9C102_V4L2_CID_GREEN_BALANCE:
+ if (sn9c102_i2c_try_raw_read(cam, s, s->i2c_slave_id, 0x2e, 2,
+ data) < 0)
+ return -EIO;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (ctrl->id) {
+ case V4L2_CID_GAIN:
+ case V4L2_CID_RED_BALANCE:
+ case V4L2_CID_BLUE_BALANCE:
+ case SN9C102_V4L2_CID_GREEN_BALANCE:
+ ctrl->value = data[1] | (data[0] << 8);
+ if (ctrl->value >= 0x10 && ctrl->value <= 0x3f)
+ ctrl->value -= 0x10;
+ else if (ctrl->value >= 0x60 && ctrl->value <= 0x7f)
+ ctrl->value -= 0x60;
+ else if (ctrl->value >= 0xe0 && ctrl->value <= 0xff)
+ ctrl->value -= 0xe0;
+ }
+
+ return 0;
+}
+
+
+static int mi0343_set_ctrl(struct sn9c102_device *cam,
+ const struct v4l2_control *ctrl)
+{
+ struct sn9c102_sensor *s = sn9c102_get_sensor(cam);
+ u16 reg = 0;
+ int err = 0;
+
+ switch (ctrl->id) {
+ case V4L2_CID_GAIN:
+ case V4L2_CID_RED_BALANCE:
+ case V4L2_CID_BLUE_BALANCE:
+ case SN9C102_V4L2_CID_GREEN_BALANCE:
+ if (ctrl->value <= (0x3f-0x10))
+ reg = 0x10 + ctrl->value;
+ else if (ctrl->value <= ((0x3f-0x10) + (0x7f-0x60)))
+ reg = 0x60 + (ctrl->value - (0x3f-0x10));
+ else
+ reg = 0xe0 + (ctrl->value - (0x3f-0x10) - (0x7f-0x60));
+ break;
+ }
+
+ switch (ctrl->id) {
+ case V4L2_CID_EXPOSURE:
+ err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id,
+ 0x09, ctrl->value, 0x00,
+ 0, 0);
+ break;
+ case V4L2_CID_GAIN:
+ err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id,
+ 0x35, reg >> 8, reg & 0xff,
+ 0, 0);
+ break;
+ case V4L2_CID_HFLIP:
+ err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id,
+ 0x20, ctrl->value ? 0x40:0x00,
+ ctrl->value ? 0x20:0x00,
+ 0, 0);
+ break;
+ case V4L2_CID_VFLIP:
+ err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id,
+ 0x20, ctrl->value ? 0x80:0x00,
+ ctrl->value ? 0x80:0x00,
+ 0, 0);
+ break;
+ case V4L2_CID_RED_BALANCE:
+ err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id,
+ 0x2d, reg >> 8, reg & 0xff,
+ 0, 0);
+ break;
+ case V4L2_CID_BLUE_BALANCE:
+ err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id,
+ 0x2c, reg >> 8, reg & 0xff,
+ 0, 0);
+ break;
+ case SN9C102_V4L2_CID_GREEN_BALANCE:
+ err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id,
+ 0x2b, reg >> 8, reg & 0xff,
+ 0, 0);
+ err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id,
+ 0x2e, reg >> 8, reg & 0xff,
+ 0, 0);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return err ? -EIO : 0;
+}
+
+
+static int mi0343_set_crop(struct sn9c102_device *cam,
+ const struct v4l2_rect *rect)
+{
+ struct sn9c102_sensor *s = sn9c102_get_sensor(cam);
+ int err = 0;
+ u8 h_start = (u8)(rect->left - s->cropcap.bounds.left) + 0,
+ v_start = (u8)(rect->top - s->cropcap.bounds.top) + 2;
+
+ err += sn9c102_write_reg(cam, h_start, 0x12);
+ err += sn9c102_write_reg(cam, v_start, 0x13);
+
+ return err;
+}
+
+
+static int mi0343_set_pix_format(struct sn9c102_device *cam,
+ const struct v4l2_pix_format *pix)
+{
+ struct sn9c102_sensor *s = sn9c102_get_sensor(cam);
+ int err = 0;
+
+ if (pix->pixelformat == V4L2_PIX_FMT_SN9C10X) {
+ err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id,
+ 0x0a, 0x00, 0x03, 0, 0);
+ err += sn9c102_write_reg(cam, 0x20, 0x19);
+ } else {
+ err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id,
+ 0x0a, 0x00, 0x05, 0, 0);
+ err += sn9c102_write_reg(cam, 0xa0, 0x19);
+ }
+
+ return err;
+}
+
+
+static const struct sn9c102_sensor mi0343 = {
+ .name = "MI-0343",
+ .maintainer = "Luca Risolia <luca.risolia@studio.unibo.it>",
+ .supported_bridge = BRIDGE_SN9C101 | BRIDGE_SN9C102,
+ .frequency = SN9C102_I2C_100KHZ,
+ .interface = SN9C102_I2C_2WIRES,
+ .i2c_slave_id = 0x5d,
+ .init = &mi0343_init,
+ .qctrl = {
+ {
+ .id = V4L2_CID_EXPOSURE,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "exposure",
+ .minimum = 0x00,
+ .maximum = 0x0f,
+ .step = 0x01,
+ .default_value = 0x06,
+ .flags = 0,
+ },
+ {
+ .id = V4L2_CID_GAIN,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "global gain",
+ .minimum = 0x00,
+ .maximum = (0x3f-0x10)+(0x7f-0x60)+(0xff-0xe0),/*0x6d*/
+ .step = 0x01,
+ .default_value = 0x00,
+ .flags = 0,
+ },
+ {
+ .id = V4L2_CID_HFLIP,
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ .name = "horizontal mirror",
+ .minimum = 0,
+ .maximum = 1,
+ .step = 1,
+ .default_value = 0,
+ .flags = 0,
+ },
+ {
+ .id = V4L2_CID_VFLIP,
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ .name = "vertical mirror",
+ .minimum = 0,
+ .maximum = 1,
+ .step = 1,
+ .default_value = 0,
+ .flags = 0,
+ },
+ {
+ .id = V4L2_CID_RED_BALANCE,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "red balance",
+ .minimum = 0x00,
+ .maximum = (0x3f-0x10)+(0x7f-0x60)+(0xff-0xe0),
+ .step = 0x01,
+ .default_value = 0x00,
+ .flags = 0,
+ },
+ {
+ .id = V4L2_CID_BLUE_BALANCE,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "blue balance",
+ .minimum = 0x00,
+ .maximum = (0x3f-0x10)+(0x7f-0x60)+(0xff-0xe0),
+ .step = 0x01,
+ .default_value = 0x00,
+ .flags = 0,
+ },
+ {
+ .id = SN9C102_V4L2_CID_GREEN_BALANCE,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "green balance",
+ .minimum = 0x00,
+ .maximum = ((0x3f-0x10)+(0x7f-0x60)+(0xff-0xe0)),
+ .step = 0x01,
+ .default_value = 0x00,
+ .flags = 0,
+ },
+ },
+ .get_ctrl = &mi0343_get_ctrl,
+ .set_ctrl = &mi0343_set_ctrl,
+ .cropcap = {
+ .bounds = {
+ .left = 0,
+ .top = 0,
+ .width = 640,
+ .height = 480,
+ },
+ .defrect = {
+ .left = 0,
+ .top = 0,
+ .width = 640,
+ .height = 480,
+ },
+ },
+ .set_crop = &mi0343_set_crop,
+ .pix_format = {
+ .width = 640,
+ .height = 480,
+ .pixelformat = V4L2_PIX_FMT_SBGGR8,
+ .priv = 8,
+ },
+ .set_pix_format = &mi0343_set_pix_format
+};
+
+
+int sn9c102_probe_mi0343(struct sn9c102_device *cam)
+{
+ u8 data[2];
+
+ if (sn9c102_write_const_regs(cam, {0x01, 0x01}, {0x00, 0x01},
+ {0x28, 0x17}))
+ return -EIO;
+
+ if (sn9c102_i2c_try_raw_read(cam, &mi0343, mi0343.i2c_slave_id, 0x00,
+ 2, data) < 0)
+ return -EIO;
+
+ if (data[1] != 0x42 || data[0] != 0xe3)
+ return -ENODEV;
+
+ sn9c102_attach_sensor(cam, &mi0343);
+
+ return 0;
+}
diff --git a/drivers/staging/media/sn9c102/sn9c102_mi0360.c b/drivers/staging/media/sn9c102/sn9c102_mi0360.c
new file mode 100644
index 00000000000..5f21d1b43e3
--- /dev/null
+++ b/drivers/staging/media/sn9c102/sn9c102_mi0360.c
@@ -0,0 +1,453 @@
+/***************************************************************************
+ * Plug-in for MI-0360 image sensor connected to the SN9C1xx PC Camera *
+ * Controllers *
+ * *
+ * Copyright (C) 2007 by Luca Risolia <luca.risolia@studio.unibo.it> *
+ * *
+ * 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., 675 Mass Ave, Cambridge, MA 02139, USA. *
+ ***************************************************************************/
+
+#include "sn9c102_sensor.h"
+#include "sn9c102_devtable.h"
+
+
+static int mi0360_init(struct sn9c102_device *cam)
+{
+ struct sn9c102_sensor *s = sn9c102_get_sensor(cam);
+ int err = 0;
+
+ switch (sn9c102_get_bridge(cam)) {
+ case BRIDGE_SN9C103:
+ err = sn9c102_write_const_regs(cam, {0x00, 0x10}, {0x00, 0x11},
+ {0x0a, 0x14}, {0x40, 0x01},
+ {0x20, 0x17}, {0x07, 0x18},
+ {0xa0, 0x19}, {0x02, 0x1c},
+ {0x03, 0x1d}, {0x0f, 0x1e},
+ {0x0c, 0x1f}, {0x00, 0x20},
+ {0x10, 0x21}, {0x20, 0x22},
+ {0x30, 0x23}, {0x40, 0x24},
+ {0x50, 0x25}, {0x60, 0x26},
+ {0x70, 0x27}, {0x80, 0x28},
+ {0x90, 0x29}, {0xa0, 0x2a},
+ {0xb0, 0x2b}, {0xc0, 0x2c},
+ {0xd0, 0x2d}, {0xe0, 0x2e},
+ {0xf0, 0x2f}, {0xff, 0x30});
+ break;
+ case BRIDGE_SN9C105:
+ case BRIDGE_SN9C120:
+ err = sn9c102_write_const_regs(cam, {0x44, 0x01}, {0x40, 0x02},
+ {0x00, 0x03}, {0x1a, 0x04},
+ {0x50, 0x05}, {0x20, 0x06},
+ {0x10, 0x07}, {0x03, 0x10},
+ {0x08, 0x14}, {0xa2, 0x17},
+ {0x47, 0x18}, {0x00, 0x19},
+ {0x1d, 0x1a}, {0x10, 0x1b},
+ {0x02, 0x1c}, {0x03, 0x1d},
+ {0x0f, 0x1e}, {0x0c, 0x1f},
+ {0x00, 0x20}, {0x29, 0x21},
+ {0x40, 0x22}, {0x54, 0x23},
+ {0x66, 0x24}, {0x76, 0x25},
+ {0x85, 0x26}, {0x94, 0x27},
+ {0xa1, 0x28}, {0xae, 0x29},
+ {0xbb, 0x2a}, {0xc7, 0x2b},
+ {0xd3, 0x2c}, {0xde, 0x2d},
+ {0xea, 0x2e}, {0xf4, 0x2f},
+ {0xff, 0x30}, {0x00, 0x3F},
+ {0xC7, 0x40}, {0x01, 0x41},
+ {0x44, 0x42}, {0x00, 0x43},
+ {0x44, 0x44}, {0x00, 0x45},
+ {0x44, 0x46}, {0x00, 0x47},
+ {0xC7, 0x48}, {0x01, 0x49},
+ {0xC7, 0x4A}, {0x01, 0x4B},
+ {0xC7, 0x4C}, {0x01, 0x4D},
+ {0x44, 0x4E}, {0x00, 0x4F},
+ {0x44, 0x50}, {0x00, 0x51},
+ {0x44, 0x52}, {0x00, 0x53},
+ {0xC7, 0x54}, {0x01, 0x55},
+ {0xC7, 0x56}, {0x01, 0x57},
+ {0xC7, 0x58}, {0x01, 0x59},
+ {0x44, 0x5A}, {0x00, 0x5B},
+ {0x44, 0x5C}, {0x00, 0x5D},
+ {0x44, 0x5E}, {0x00, 0x5F},
+ {0xC7, 0x60}, {0x01, 0x61},
+ {0xC7, 0x62}, {0x01, 0x63},
+ {0xC7, 0x64}, {0x01, 0x65},
+ {0x44, 0x66}, {0x00, 0x67},
+ {0x44, 0x68}, {0x00, 0x69},
+ {0x44, 0x6A}, {0x00, 0x6B},
+ {0xC7, 0x6C}, {0x01, 0x6D},
+ {0xC7, 0x6E}, {0x01, 0x6F},
+ {0xC7, 0x70}, {0x01, 0x71},
+ {0x44, 0x72}, {0x00, 0x73},
+ {0x44, 0x74}, {0x00, 0x75},
+ {0x44, 0x76}, {0x00, 0x77},
+ {0xC7, 0x78}, {0x01, 0x79},
+ {0xC7, 0x7A}, {0x01, 0x7B},
+ {0xC7, 0x7C}, {0x01, 0x7D},
+ {0x44, 0x7E}, {0x00, 0x7F},
+ {0x14, 0x84}, {0x00, 0x85},
+ {0x27, 0x86}, {0x00, 0x87},
+ {0x07, 0x88}, {0x00, 0x89},
+ {0xEC, 0x8A}, {0x0f, 0x8B},
+ {0xD8, 0x8C}, {0x0f, 0x8D},
+ {0x3D, 0x8E}, {0x00, 0x8F},
+ {0x3D, 0x90}, {0x00, 0x91},
+ {0xCD, 0x92}, {0x0f, 0x93},
+ {0xf7, 0x94}, {0x0f, 0x95},
+ {0x0C, 0x96}, {0x00, 0x97},
+ {0x00, 0x98}, {0x66, 0x99},
+ {0x05, 0x9A}, {0x00, 0x9B},
+ {0x04, 0x9C}, {0x00, 0x9D},
+ {0x08, 0x9E}, {0x00, 0x9F},
+ {0x2D, 0xC0}, {0x2D, 0xC1},
+ {0x3A, 0xC2}, {0x05, 0xC3},
+ {0x04, 0xC4}, {0x3F, 0xC5},
+ {0x00, 0xC6}, {0x00, 0xC7},
+ {0x50, 0xC8}, {0x3C, 0xC9},
+ {0x28, 0xCA}, {0xD8, 0xCB},
+ {0x14, 0xCC}, {0xEC, 0xCD},
+ {0x32, 0xCE}, {0xDD, 0xCF},
+ {0x32, 0xD0}, {0xDD, 0xD1},
+ {0x6A, 0xD2}, {0x50, 0xD3},
+ {0x00, 0xD4}, {0x00, 0xD5},
+ {0x00, 0xD6});
+ break;
+ default:
+ break;
+ }
+
+ err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, 0x0d,
+ 0x00, 0x01, 0, 0);
+ err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, 0x0d,
+ 0x00, 0x00, 0, 0);
+ err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, 0x03,
+ 0x01, 0xe1, 0, 0);
+ err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, 0x04,
+ 0x02, 0x81, 0, 0);
+ err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, 0x05,
+ 0x00, 0x17, 0, 0);
+ err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, 0x06,
+ 0x00, 0x11, 0, 0);
+ err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, 0x62,
+ 0x04, 0x9a, 0, 0);
+
+ return err;
+}
+
+
+static int mi0360_get_ctrl(struct sn9c102_device *cam,
+ struct v4l2_control *ctrl)
+{
+ struct sn9c102_sensor *s = sn9c102_get_sensor(cam);
+ u8 data[2];
+
+ switch (ctrl->id) {
+ case V4L2_CID_EXPOSURE:
+ if (sn9c102_i2c_try_raw_read(cam, s, s->i2c_slave_id, 0x09, 2,
+ data) < 0)
+ return -EIO;
+ ctrl->value = data[0];
+ return 0;
+ case V4L2_CID_GAIN:
+ if (sn9c102_i2c_try_raw_read(cam, s, s->i2c_slave_id, 0x35, 2,
+ data) < 0)
+ return -EIO;
+ ctrl->value = data[1];
+ return 0;
+ case V4L2_CID_RED_BALANCE:
+ if (sn9c102_i2c_try_raw_read(cam, s, s->i2c_slave_id, 0x2c, 2,
+ data) < 0)
+ return -EIO;
+ ctrl->value = data[1];
+ return 0;
+ case V4L2_CID_BLUE_BALANCE:
+ if (sn9c102_i2c_try_raw_read(cam, s, s->i2c_slave_id, 0x2d, 2,
+ data) < 0)
+ return -EIO;
+ ctrl->value = data[1];
+ return 0;
+ case SN9C102_V4L2_CID_GREEN_BALANCE:
+ if (sn9c102_i2c_try_raw_read(cam, s, s->i2c_slave_id, 0x2e, 2,
+ data) < 0)
+ return -EIO;
+ ctrl->value = data[1];
+ return 0;
+ case V4L2_CID_HFLIP:
+ if (sn9c102_i2c_try_raw_read(cam, s, s->i2c_slave_id, 0x20, 2,
+ data) < 0)
+ return -EIO;
+ ctrl->value = data[1] & 0x20 ? 1 : 0;
+ return 0;
+ case V4L2_CID_VFLIP:
+ if (sn9c102_i2c_try_raw_read(cam, s, s->i2c_slave_id, 0x20, 2,
+ data) < 0)
+ return -EIO;
+ ctrl->value = data[1] & 0x80 ? 1 : 0;
+ return 0;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+
+static int mi0360_set_ctrl(struct sn9c102_device *cam,
+ const struct v4l2_control *ctrl)
+{
+ struct sn9c102_sensor *s = sn9c102_get_sensor(cam);
+ int err = 0;
+
+ switch (ctrl->id) {
+ case V4L2_CID_EXPOSURE:
+ err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id,
+ 0x09, ctrl->value, 0x00,
+ 0, 0);
+ break;
+ case V4L2_CID_GAIN:
+ err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id,
+ 0x35, 0x03, ctrl->value,
+ 0, 0);
+ break;
+ case V4L2_CID_RED_BALANCE:
+ err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id,
+ 0x2c, 0x03, ctrl->value,
+ 0, 0);
+ break;
+ case V4L2_CID_BLUE_BALANCE:
+ err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id,
+ 0x2d, 0x03, ctrl->value,
+ 0, 0);
+ break;
+ case SN9C102_V4L2_CID_GREEN_BALANCE:
+ err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id,
+ 0x2b, 0x03, ctrl->value,
+ 0, 0);
+ err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id,
+ 0x2e, 0x03, ctrl->value,
+ 0, 0);
+ break;
+ case V4L2_CID_HFLIP:
+ err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id,
+ 0x20, ctrl->value ? 0x40:0x00,
+ ctrl->value ? 0x20:0x00,
+ 0, 0);
+ break;
+ case V4L2_CID_VFLIP:
+ err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id,
+ 0x20, ctrl->value ? 0x80:0x00,
+ ctrl->value ? 0x80:0x00,
+ 0, 0);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return err ? -EIO : 0;
+}
+
+
+static int mi0360_set_crop(struct sn9c102_device *cam,
+ const struct v4l2_rect *rect)
+{
+ struct sn9c102_sensor *s = sn9c102_get_sensor(cam);
+ int err = 0;
+ u8 h_start = 0, v_start = (u8)(rect->top - s->cropcap.bounds.top) + 1;
+
+ switch (sn9c102_get_bridge(cam)) {
+ case BRIDGE_SN9C103:
+ h_start = (u8)(rect->left - s->cropcap.bounds.left) + 0;
+ break;
+ case BRIDGE_SN9C105:
+ case BRIDGE_SN9C120:
+ h_start = (u8)(rect->left - s->cropcap.bounds.left) + 1;
+ break;
+ default:
+ break;
+ }
+
+ err += sn9c102_write_reg(cam, h_start, 0x12);
+ err += sn9c102_write_reg(cam, v_start, 0x13);
+
+ return err;
+}
+
+
+static int mi0360_set_pix_format(struct sn9c102_device *cam,
+ const struct v4l2_pix_format *pix)
+{
+ struct sn9c102_sensor *s = sn9c102_get_sensor(cam);
+ int err = 0;
+
+ if (pix->pixelformat == V4L2_PIX_FMT_SBGGR8) {
+ err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id,
+ 0x0a, 0x00, 0x05, 0, 0);
+ err += sn9c102_write_reg(cam, 0x60, 0x19);
+ if (sn9c102_get_bridge(cam) == BRIDGE_SN9C105 ||
+ sn9c102_get_bridge(cam) == BRIDGE_SN9C120)
+ err += sn9c102_write_reg(cam, 0xa6, 0x17);
+ } else {
+ err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id,
+ 0x0a, 0x00, 0x02, 0, 0);
+ err += sn9c102_write_reg(cam, 0x20, 0x19);
+ if (sn9c102_get_bridge(cam) == BRIDGE_SN9C105 ||
+ sn9c102_get_bridge(cam) == BRIDGE_SN9C120)
+ err += sn9c102_write_reg(cam, 0xa2, 0x17);
+ }
+
+ return err;
+}
+
+
+static const struct sn9c102_sensor mi0360 = {
+ .name = "MI-0360",
+ .maintainer = "Luca Risolia <luca.risolia@studio.unibo.it>",
+ .supported_bridge = BRIDGE_SN9C103 | BRIDGE_SN9C105 | BRIDGE_SN9C120,
+ .frequency = SN9C102_I2C_100KHZ,
+ .interface = SN9C102_I2C_2WIRES,
+ .i2c_slave_id = 0x5d,
+ .init = &mi0360_init,
+ .qctrl = {
+ {
+ .id = V4L2_CID_EXPOSURE,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "exposure",
+ .minimum = 0x00,
+ .maximum = 0x0f,
+ .step = 0x01,
+ .default_value = 0x05,
+ .flags = 0,
+ },
+ {
+ .id = V4L2_CID_GAIN,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "global gain",
+ .minimum = 0x00,
+ .maximum = 0x7f,
+ .step = 0x01,
+ .default_value = 0x25,
+ .flags = 0,
+ },
+ {
+ .id = V4L2_CID_HFLIP,
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ .name = "horizontal mirror",
+ .minimum = 0,
+ .maximum = 1,
+ .step = 1,
+ .default_value = 0,
+ .flags = 0,
+ },
+ {
+ .id = V4L2_CID_VFLIP,
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ .name = "vertical mirror",
+ .minimum = 0,
+ .maximum = 1,
+ .step = 1,
+ .default_value = 0,
+ .flags = 0,
+ },
+ {
+ .id = V4L2_CID_BLUE_BALANCE,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "blue balance",
+ .minimum = 0x00,
+ .maximum = 0x7f,
+ .step = 0x01,
+ .default_value = 0x0f,
+ .flags = 0,
+ },
+ {
+ .id = V4L2_CID_RED_BALANCE,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "red balance",
+ .minimum = 0x00,
+ .maximum = 0x7f,
+ .step = 0x01,
+ .default_value = 0x32,
+ .flags = 0,
+ },
+ {
+ .id = SN9C102_V4L2_CID_GREEN_BALANCE,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "green balance",
+ .minimum = 0x00,
+ .maximum = 0x7f,
+ .step = 0x01,
+ .default_value = 0x25,
+ .flags = 0,
+ },
+ },
+ .get_ctrl = &mi0360_get_ctrl,
+ .set_ctrl = &mi0360_set_ctrl,
+ .cropcap = {
+ .bounds = {
+ .left = 0,
+ .top = 0,
+ .width = 640,
+ .height = 480,
+ },
+ .defrect = {
+ .left = 0,
+ .top = 0,
+ .width = 640,
+ .height = 480,
+ },
+ },
+ .set_crop = &mi0360_set_crop,
+ .pix_format = {
+ .width = 640,
+ .height = 480,
+ .pixelformat = V4L2_PIX_FMT_SBGGR8,
+ .priv = 8,
+ },
+ .set_pix_format = &mi0360_set_pix_format
+};
+
+
+int sn9c102_probe_mi0360(struct sn9c102_device *cam)
+{
+
+ u8 data[2];
+
+ switch (sn9c102_get_bridge(cam)) {
+ case BRIDGE_SN9C103:
+ if (sn9c102_write_const_regs(cam, {0x01, 0x01}, {0x00, 0x01},
+ {0x28, 0x17}))
+ return -EIO;
+ break;
+ case BRIDGE_SN9C105:
+ case BRIDGE_SN9C120:
+ if (sn9c102_write_const_regs(cam, {0x01, 0xf1}, {0x00, 0xf1},
+ {0x01, 0x01}, {0x00, 0x01},
+ {0x28, 0x17}))
+ return -EIO;
+ break;
+ default:
+ break;
+ }
+
+ if (sn9c102_i2c_try_raw_read(cam, &mi0360, mi0360.i2c_slave_id, 0x00,
+ 2, data) < 0)
+ return -EIO;
+
+ if (data[0] != 0x82 || data[1] != 0x43)
+ return -ENODEV;
+
+ sn9c102_attach_sensor(cam, &mi0360);
+
+ return 0;
+}
diff --git a/drivers/staging/media/sn9c102/sn9c102_mt9v111.c b/drivers/staging/media/sn9c102/sn9c102_mt9v111.c
new file mode 100644
index 00000000000..95986eb492e
--- /dev/null
+++ b/drivers/staging/media/sn9c102/sn9c102_mt9v111.c
@@ -0,0 +1,260 @@
+/***************************************************************************
+ * Plug-in for MT9V111 image sensor connected to the SN9C1xx PC Camera *
+ * Controllers *
+ * *
+ * Copyright (C) 2007 by Luca Risolia <luca.risolia@studio.unibo.it> *
+ * *
+ * 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., 675 Mass Ave, Cambridge, MA 02139, USA. *
+ ***************************************************************************/
+
+#include "sn9c102_sensor.h"
+#include "sn9c102_devtable.h"
+
+
+static int mt9v111_init(struct sn9c102_device *cam)
+{
+ struct sn9c102_sensor *s = sn9c102_get_sensor(cam);
+ int err = 0;
+
+ err = sn9c102_write_const_regs(cam, {0x44, 0x01}, {0x40, 0x02},
+ {0x00, 0x03}, {0x1a, 0x04},
+ {0x1f, 0x05}, {0x20, 0x06},
+ {0x1f, 0x07}, {0x81, 0x08},
+ {0x5c, 0x09}, {0x00, 0x0a},
+ {0x00, 0x0b}, {0x00, 0x0c},
+ {0x00, 0x0d}, {0x00, 0x0e},
+ {0x00, 0x0f}, {0x03, 0x10},
+ {0x00, 0x11}, {0x00, 0x12},
+ {0x02, 0x13}, {0x14, 0x14},
+ {0x28, 0x15}, {0x1e, 0x16},
+ {0xe2, 0x17}, {0x06, 0x18},
+ {0x00, 0x19}, {0x00, 0x1a},
+ {0x00, 0x1b}, {0x08, 0x20},
+ {0x39, 0x21}, {0x51, 0x22},
+ {0x63, 0x23}, {0x73, 0x24},
+ {0x82, 0x25}, {0x8f, 0x26},
+ {0x9b, 0x27}, {0xa7, 0x28},
+ {0xb1, 0x29}, {0xbc, 0x2a},
+ {0xc6, 0x2b}, {0xcf, 0x2c},
+ {0xd8, 0x2d}, {0xe1, 0x2e},
+ {0xea, 0x2f}, {0xf2, 0x30},
+ {0x13, 0x84}, {0x00, 0x85},
+ {0x25, 0x86}, {0x00, 0x87},
+ {0x07, 0x88}, {0x00, 0x89},
+ {0xee, 0x8a}, {0x0f, 0x8b},
+ {0xe5, 0x8c}, {0x0f, 0x8d},
+ {0x2e, 0x8e}, {0x00, 0x8f},
+ {0x30, 0x90}, {0x00, 0x91},
+ {0xd4, 0x92}, {0x0f, 0x93},
+ {0xfc, 0x94}, {0x0f, 0x95},
+ {0x14, 0x96}, {0x00, 0x97},
+ {0x00, 0x98}, {0x60, 0x99},
+ {0x07, 0x9a}, {0x40, 0x9b},
+ {0x20, 0x9c}, {0x00, 0x9d},
+ {0x00, 0x9e}, {0x00, 0x9f},
+ {0x2d, 0xc0}, {0x2d, 0xc1},
+ {0x3a, 0xc2}, {0x05, 0xc3},
+ {0x04, 0xc4}, {0x3f, 0xc5},
+ {0x00, 0xc6}, {0x00, 0xc7},
+ {0x50, 0xc8}, {0x3c, 0xc9},
+ {0x28, 0xca}, {0xd8, 0xcb},
+ {0x14, 0xcc}, {0xec, 0xcd},
+ {0x32, 0xce}, {0xdd, 0xcf},
+ {0x2d, 0xd0}, {0xdd, 0xd1},
+ {0x6a, 0xd2}, {0x50, 0xd3},
+ {0x60, 0xd4}, {0x00, 0xd5},
+ {0x00, 0xd6});
+
+ err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, 0x01,
+ 0x00, 0x01, 0, 0);
+ err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, 0x0d,
+ 0x00, 0x01, 0, 0);
+ err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, 0x0d,
+ 0x00, 0x00, 0, 0);
+ err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, 0x08,
+ 0x04, 0x80, 0, 0);
+ err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, 0x01,
+ 0x00, 0x04, 0, 0);
+ err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, 0x08,
+ 0x00, 0x08, 0, 0);
+ err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, 0x02,
+ 0x00, 0x16, 0, 0);
+ err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, 0x03,
+ 0x01, 0xe7, 0, 0);
+ err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, 0x04,
+ 0x02, 0x87, 0, 0);
+ err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, 0x06,
+ 0x00, 0x40, 0, 0);
+ err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, 0x05,
+ 0x00, 0x09, 0, 0);
+ err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, 0x07,
+ 0x30, 0x02, 0, 0);
+ err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, 0x0c,
+ 0x00, 0x00, 0, 0);
+ err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, 0x12,
+ 0x00, 0xb0, 0, 0);
+ err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, 0x13,
+ 0x00, 0x7c, 0, 0);
+ err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, 0x1e,
+ 0x00, 0x00, 0, 0);
+ err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, 0x20,
+ 0x00, 0x00, 0, 0);
+ err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, 0x20,
+ 0x00, 0x00, 0, 0);
+ err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, 0x01,
+ 0x00, 0x04, 0, 0);
+
+ return err;
+}
+
+static int mt9v111_get_ctrl(struct sn9c102_device *cam,
+ struct v4l2_control *ctrl)
+{
+ struct sn9c102_sensor *s = sn9c102_get_sensor(cam);
+ u8 data[2];
+ int err = 0;
+
+ switch (ctrl->id) {
+ case V4L2_CID_VFLIP:
+ if (sn9c102_i2c_try_raw_read(cam, s, s->i2c_slave_id, 0x20, 2,
+ data) < 0)
+ return -EIO;
+ ctrl->value = data[1] & 0x80 ? 1 : 0;
+ return 0;
+ default:
+ return -EINVAL;
+ }
+
+ return err ? -EIO : 0;
+}
+
+static int mt9v111_set_ctrl(struct sn9c102_device *cam,
+ const struct v4l2_control *ctrl)
+{
+ struct sn9c102_sensor *s = sn9c102_get_sensor(cam);
+ int err = 0;
+
+ switch (ctrl->id) {
+ case V4L2_CID_VFLIP:
+ err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id,
+ 0x20,
+ ctrl->value ? 0x80 : 0x00,
+ ctrl->value ? 0x80 : 0x00, 0,
+ 0);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return err ? -EIO : 0;
+}
+
+static int mt9v111_set_crop(struct sn9c102_device *cam,
+ const struct v4l2_rect *rect)
+{
+ struct sn9c102_sensor *s = sn9c102_get_sensor(cam);
+ int err = 0;
+ u8 v_start = (u8) (rect->top - s->cropcap.bounds.top) + 2;
+
+ err += sn9c102_write_reg(cam, v_start, 0x13);
+
+ return err;
+}
+
+static int mt9v111_set_pix_format(struct sn9c102_device *cam,
+ const struct v4l2_pix_format *pix)
+{
+ int err = 0;
+
+ if (pix->pixelformat == V4L2_PIX_FMT_SBGGR8) {
+ err += sn9c102_write_reg(cam, 0xb4, 0x17);
+ } else {
+ err += sn9c102_write_reg(cam, 0xe2, 0x17);
+ }
+
+ return err;
+}
+
+
+static const struct sn9c102_sensor mt9v111 = {
+ .name = "MT9V111",
+ .maintainer = "Luca Risolia <luca.risolia@studio.unibo.it>",
+ .supported_bridge = BRIDGE_SN9C105 | BRIDGE_SN9C120,
+ .frequency = SN9C102_I2C_100KHZ,
+ .interface = SN9C102_I2C_2WIRES,
+ .i2c_slave_id = 0x5c,
+ .init = &mt9v111_init,
+ .qctrl = {
+ {
+ .id = V4L2_CID_VFLIP,
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ .name = "vertical mirror",
+ .minimum = 0,
+ .maximum = 1,
+ .step = 1,
+ .default_value = 0,
+ .flags = 0,
+ },
+ },
+ .get_ctrl = &mt9v111_get_ctrl,
+ .set_ctrl = &mt9v111_set_ctrl,
+ .cropcap = {
+ .bounds = {
+ .left = 0,
+ .top = 0,
+ .width = 640,
+ .height = 480,
+ },
+ .defrect = {
+ .left = 0,
+ .top = 0,
+ .width = 640,
+ .height = 480,
+ },
+ },
+ .set_crop = &mt9v111_set_crop,
+ .pix_format = {
+ .width = 640,
+ .height = 480,
+ .pixelformat = V4L2_PIX_FMT_SBGGR8,
+ .priv = 8,
+ },
+ .set_pix_format = &mt9v111_set_pix_format
+};
+
+
+int sn9c102_probe_mt9v111(struct sn9c102_device *cam)
+{
+ u8 data[2];
+ int err = 0;
+
+ err += sn9c102_write_const_regs(cam, {0x01, 0xf1}, {0x00, 0xf1},
+ {0x29, 0x01}, {0x42, 0x17},
+ {0x62, 0x17}, {0x08, 0x01});
+ err += sn9c102_i2c_try_raw_write(cam, &mt9v111, 4,
+ mt9v111.i2c_slave_id, 0x01, 0x00,
+ 0x04, 0, 0);
+ if (err || sn9c102_i2c_try_raw_read(cam, &mt9v111,
+ mt9v111.i2c_slave_id, 0x36, 2,
+ data) < 0)
+ return -EIO;
+
+ if (data[0] != 0x82 || data[1] != 0x3a)
+ return -ENODEV;
+
+ sn9c102_attach_sensor(cam, &mt9v111);
+
+ return 0;
+}
diff --git a/drivers/staging/media/sn9c102/sn9c102_ov7630.c b/drivers/staging/media/sn9c102/sn9c102_ov7630.c
new file mode 100644
index 00000000000..9ec304dc470
--- /dev/null
+++ b/drivers/staging/media/sn9c102/sn9c102_ov7630.c
@@ -0,0 +1,634 @@
+/***************************************************************************
+ * Plug-in for OV7630 image sensor connected to the SN9C1xx PC Camera *
+ * Controllers *
+ * *
+ * Copyright (C) 2006-2007 by Luca Risolia <luca.risolia@studio.unibo.it> *
+ * *
+ * 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., 675 Mass Ave, Cambridge, MA 02139, USA. *
+ ***************************************************************************/
+
+#include "sn9c102_sensor.h"
+#include "sn9c102_devtable.h"
+
+
+static int ov7630_init(struct sn9c102_device *cam)
+{
+ int err = 0;
+
+ switch (sn9c102_get_bridge(cam)) {
+ case BRIDGE_SN9C101:
+ case BRIDGE_SN9C102:
+ err = sn9c102_write_const_regs(cam, {0x00, 0x14}, {0x60, 0x17},
+ {0x0f, 0x18}, {0x50, 0x19});
+
+ err += sn9c102_i2c_write(cam, 0x12, 0x8d);
+ err += sn9c102_i2c_write(cam, 0x12, 0x0d);
+ err += sn9c102_i2c_write(cam, 0x11, 0x00);
+ err += sn9c102_i2c_write(cam, 0x15, 0x35);
+ err += sn9c102_i2c_write(cam, 0x16, 0x03);
+ err += sn9c102_i2c_write(cam, 0x17, 0x1c);
+ err += sn9c102_i2c_write(cam, 0x18, 0xbd);
+ err += sn9c102_i2c_write(cam, 0x19, 0x06);
+ err += sn9c102_i2c_write(cam, 0x1a, 0xf6);
+ err += sn9c102_i2c_write(cam, 0x1b, 0x04);
+ err += sn9c102_i2c_write(cam, 0x20, 0x44);
+ err += sn9c102_i2c_write(cam, 0x23, 0xee);
+ err += sn9c102_i2c_write(cam, 0x26, 0xa0);
+ err += sn9c102_i2c_write(cam, 0x27, 0x9a);
+ err += sn9c102_i2c_write(cam, 0x28, 0x20);
+ err += sn9c102_i2c_write(cam, 0x29, 0x30);
+ err += sn9c102_i2c_write(cam, 0x2f, 0x3d);
+ err += sn9c102_i2c_write(cam, 0x30, 0x24);
+ err += sn9c102_i2c_write(cam, 0x32, 0x86);
+ err += sn9c102_i2c_write(cam, 0x60, 0xa9);
+ err += sn9c102_i2c_write(cam, 0x61, 0x42);
+ err += sn9c102_i2c_write(cam, 0x65, 0x00);
+ err += sn9c102_i2c_write(cam, 0x69, 0x38);
+ err += sn9c102_i2c_write(cam, 0x6f, 0x88);
+ err += sn9c102_i2c_write(cam, 0x70, 0x0b);
+ err += sn9c102_i2c_write(cam, 0x71, 0x00);
+ err += sn9c102_i2c_write(cam, 0x74, 0x21);
+ err += sn9c102_i2c_write(cam, 0x7d, 0xf7);
+ break;
+ case BRIDGE_SN9C103:
+ err = sn9c102_write_const_regs(cam, {0x00, 0x02}, {0x00, 0x03},
+ {0x1a, 0x04}, {0x20, 0x05},
+ {0x20, 0x06}, {0x20, 0x07},
+ {0x03, 0x10}, {0x0a, 0x14},
+ {0x60, 0x17}, {0x0f, 0x18},
+ {0x50, 0x19}, {0x1d, 0x1a},
+ {0x10, 0x1b}, {0x02, 0x1c},
+ {0x03, 0x1d}, {0x0f, 0x1e},
+ {0x0c, 0x1f}, {0x00, 0x20},
+ {0x10, 0x21}, {0x20, 0x22},
+ {0x30, 0x23}, {0x40, 0x24},
+ {0x50, 0x25}, {0x60, 0x26},
+ {0x70, 0x27}, {0x80, 0x28},
+ {0x90, 0x29}, {0xa0, 0x2a},
+ {0xb0, 0x2b}, {0xc0, 0x2c},
+ {0xd0, 0x2d}, {0xe0, 0x2e},
+ {0xf0, 0x2f}, {0xff, 0x30});
+
+ err += sn9c102_i2c_write(cam, 0x12, 0x8d);
+ err += sn9c102_i2c_write(cam, 0x12, 0x0d);
+ err += sn9c102_i2c_write(cam, 0x15, 0x34);
+ err += sn9c102_i2c_write(cam, 0x11, 0x01);
+ err += sn9c102_i2c_write(cam, 0x1b, 0x04);
+ err += sn9c102_i2c_write(cam, 0x20, 0x44);
+ err += sn9c102_i2c_write(cam, 0x23, 0xee);
+ err += sn9c102_i2c_write(cam, 0x26, 0xa0);
+ err += sn9c102_i2c_write(cam, 0x27, 0x9a);
+ err += sn9c102_i2c_write(cam, 0x28, 0x20);
+ err += sn9c102_i2c_write(cam, 0x29, 0x30);
+ err += sn9c102_i2c_write(cam, 0x2f, 0x3d);
+ err += sn9c102_i2c_write(cam, 0x30, 0x24);
+ err += sn9c102_i2c_write(cam, 0x32, 0x86);
+ err += sn9c102_i2c_write(cam, 0x60, 0xa9);
+ err += sn9c102_i2c_write(cam, 0x61, 0x42);
+ err += sn9c102_i2c_write(cam, 0x65, 0x00);
+ err += sn9c102_i2c_write(cam, 0x69, 0x38);
+ err += sn9c102_i2c_write(cam, 0x6f, 0x88);
+ err += sn9c102_i2c_write(cam, 0x70, 0x0b);
+ err += sn9c102_i2c_write(cam, 0x71, 0x00);
+ err += sn9c102_i2c_write(cam, 0x74, 0x21);
+ err += sn9c102_i2c_write(cam, 0x7d, 0xf7);
+ break;
+ case BRIDGE_SN9C105:
+ case BRIDGE_SN9C120:
+ err = sn9c102_write_const_regs(cam, {0x40, 0x02}, {0x00, 0x03},
+ {0x1a, 0x04}, {0x03, 0x10},
+ {0x0a, 0x14}, {0xe2, 0x17},
+ {0x0b, 0x18}, {0x00, 0x19},
+ {0x1d, 0x1a}, {0x10, 0x1b},
+ {0x02, 0x1c}, {0x03, 0x1d},
+ {0x0f, 0x1e}, {0x0c, 0x1f},
+ {0x00, 0x20}, {0x24, 0x21},
+ {0x3b, 0x22}, {0x47, 0x23},
+ {0x60, 0x24}, {0x71, 0x25},
+ {0x80, 0x26}, {0x8f, 0x27},
+ {0x9d, 0x28}, {0xaa, 0x29},
+ {0xb8, 0x2a}, {0xc4, 0x2b},
+ {0xd1, 0x2c}, {0xdd, 0x2d},
+ {0xe8, 0x2e}, {0xf4, 0x2f},
+ {0xff, 0x30}, {0x00, 0x3f},
+ {0xc7, 0x40}, {0x01, 0x41},
+ {0x44, 0x42}, {0x00, 0x43},
+ {0x44, 0x44}, {0x00, 0x45},
+ {0x44, 0x46}, {0x00, 0x47},
+ {0xc7, 0x48}, {0x01, 0x49},
+ {0xc7, 0x4a}, {0x01, 0x4b},
+ {0xc7, 0x4c}, {0x01, 0x4d},
+ {0x44, 0x4e}, {0x00, 0x4f},
+ {0x44, 0x50}, {0x00, 0x51},
+ {0x44, 0x52}, {0x00, 0x53},
+ {0xc7, 0x54}, {0x01, 0x55},
+ {0xc7, 0x56}, {0x01, 0x57},
+ {0xc7, 0x58}, {0x01, 0x59},
+ {0x44, 0x5a}, {0x00, 0x5b},
+ {0x44, 0x5c}, {0x00, 0x5d},
+ {0x44, 0x5e}, {0x00, 0x5f},
+ {0xc7, 0x60}, {0x01, 0x61},
+ {0xc7, 0x62}, {0x01, 0x63},
+ {0xc7, 0x64}, {0x01, 0x65},
+ {0x44, 0x66}, {0x00, 0x67},
+ {0x44, 0x68}, {0x00, 0x69},
+ {0x44, 0x6a}, {0x00, 0x6b},
+ {0xc7, 0x6c}, {0x01, 0x6d},
+ {0xc7, 0x6e}, {0x01, 0x6f},
+ {0xc7, 0x70}, {0x01, 0x71},
+ {0x44, 0x72}, {0x00, 0x73},
+ {0x44, 0x74}, {0x00, 0x75},
+ {0x44, 0x76}, {0x00, 0x77},
+ {0xc7, 0x78}, {0x01, 0x79},
+ {0xc7, 0x7a}, {0x01, 0x7b},
+ {0xc7, 0x7c}, {0x01, 0x7d},
+ {0x44, 0x7e}, {0x00, 0x7f},
+ {0x17, 0x84}, {0x00, 0x85},
+ {0x2e, 0x86}, {0x00, 0x87},
+ {0x09, 0x88}, {0x00, 0x89},
+ {0xe8, 0x8a}, {0x0f, 0x8b},
+ {0xda, 0x8c}, {0x0f, 0x8d},
+ {0x40, 0x8e}, {0x00, 0x8f},
+ {0x37, 0x90}, {0x00, 0x91},
+ {0xcf, 0x92}, {0x0f, 0x93},
+ {0xfa, 0x94}, {0x0f, 0x95},
+ {0x00, 0x96}, {0x00, 0x97},
+ {0x00, 0x98}, {0x66, 0x99},
+ {0x00, 0x9a}, {0x40, 0x9b},
+ {0x20, 0x9c}, {0x00, 0x9d},
+ {0x00, 0x9e}, {0x00, 0x9f},
+ {0x2d, 0xc0}, {0x2d, 0xc1},
+ {0x3a, 0xc2}, {0x00, 0xc3},
+ {0x04, 0xc4}, {0x3f, 0xc5},
+ {0x00, 0xc6}, {0x00, 0xc7},
+ {0x50, 0xc8}, {0x3c, 0xc9},
+ {0x28, 0xca}, {0xd8, 0xcb},
+ {0x14, 0xcc}, {0xec, 0xcd},
+ {0x32, 0xce}, {0xdd, 0xcf},
+ {0x32, 0xd0}, {0xdd, 0xd1},
+ {0x6a, 0xd2}, {0x50, 0xd3},
+ {0x60, 0xd4}, {0x00, 0xd5},
+ {0x00, 0xd6});
+
+ err += sn9c102_i2c_write(cam, 0x12, 0x80);
+ err += sn9c102_i2c_write(cam, 0x12, 0x48);
+ err += sn9c102_i2c_write(cam, 0x01, 0x80);
+ err += sn9c102_i2c_write(cam, 0x02, 0x80);
+ err += sn9c102_i2c_write(cam, 0x03, 0x80);
+ err += sn9c102_i2c_write(cam, 0x04, 0x10);
+ err += sn9c102_i2c_write(cam, 0x05, 0x20);
+ err += sn9c102_i2c_write(cam, 0x06, 0x80);
+ err += sn9c102_i2c_write(cam, 0x11, 0x00);
+ err += sn9c102_i2c_write(cam, 0x0c, 0x20);
+ err += sn9c102_i2c_write(cam, 0x0d, 0x20);
+ err += sn9c102_i2c_write(cam, 0x15, 0x80);
+ err += sn9c102_i2c_write(cam, 0x16, 0x03);
+ err += sn9c102_i2c_write(cam, 0x17, 0x1b);
+ err += sn9c102_i2c_write(cam, 0x18, 0xbd);
+ err += sn9c102_i2c_write(cam, 0x19, 0x05);
+ err += sn9c102_i2c_write(cam, 0x1a, 0xf6);
+ err += sn9c102_i2c_write(cam, 0x1b, 0x04);
+ err += sn9c102_i2c_write(cam, 0x21, 0x1b);
+ err += sn9c102_i2c_write(cam, 0x22, 0x00);
+ err += sn9c102_i2c_write(cam, 0x23, 0xde);
+ err += sn9c102_i2c_write(cam, 0x24, 0x10);
+ err += sn9c102_i2c_write(cam, 0x25, 0x8a);
+ err += sn9c102_i2c_write(cam, 0x26, 0xa0);
+ err += sn9c102_i2c_write(cam, 0x27, 0xca);
+ err += sn9c102_i2c_write(cam, 0x28, 0xa2);
+ err += sn9c102_i2c_write(cam, 0x29, 0x74);
+ err += sn9c102_i2c_write(cam, 0x2a, 0x88);
+ err += sn9c102_i2c_write(cam, 0x2b, 0x34);
+ err += sn9c102_i2c_write(cam, 0x2c, 0x88);
+ err += sn9c102_i2c_write(cam, 0x2e, 0x00);
+ err += sn9c102_i2c_write(cam, 0x2f, 0x00);
+ err += sn9c102_i2c_write(cam, 0x30, 0x00);
+ err += sn9c102_i2c_write(cam, 0x32, 0xc2);
+ err += sn9c102_i2c_write(cam, 0x33, 0x08);
+ err += sn9c102_i2c_write(cam, 0x4c, 0x40);
+ err += sn9c102_i2c_write(cam, 0x4d, 0xf3);
+ err += sn9c102_i2c_write(cam, 0x60, 0x05);
+ err += sn9c102_i2c_write(cam, 0x61, 0x40);
+ err += sn9c102_i2c_write(cam, 0x62, 0x12);
+ err += sn9c102_i2c_write(cam, 0x63, 0x57);
+ err += sn9c102_i2c_write(cam, 0x64, 0x73);
+ err += sn9c102_i2c_write(cam, 0x65, 0x00);
+ err += sn9c102_i2c_write(cam, 0x66, 0x55);
+ err += sn9c102_i2c_write(cam, 0x67, 0x01);
+ err += sn9c102_i2c_write(cam, 0x68, 0xac);
+ err += sn9c102_i2c_write(cam, 0x69, 0x38);
+ err += sn9c102_i2c_write(cam, 0x6f, 0x1f);
+ err += sn9c102_i2c_write(cam, 0x70, 0x01);
+ err += sn9c102_i2c_write(cam, 0x71, 0x00);
+ err += sn9c102_i2c_write(cam, 0x72, 0x10);
+ err += sn9c102_i2c_write(cam, 0x73, 0x50);
+ err += sn9c102_i2c_write(cam, 0x74, 0x20);
+ err += sn9c102_i2c_write(cam, 0x76, 0x01);
+ err += sn9c102_i2c_write(cam, 0x77, 0xf3);
+ err += sn9c102_i2c_write(cam, 0x78, 0x90);
+ err += sn9c102_i2c_write(cam, 0x79, 0x98);
+ err += sn9c102_i2c_write(cam, 0x7a, 0x98);
+ err += sn9c102_i2c_write(cam, 0x7b, 0x00);
+ err += sn9c102_i2c_write(cam, 0x7c, 0x38);
+ err += sn9c102_i2c_write(cam, 0x7d, 0xff);
+ break;
+ default:
+ break;
+ }
+
+ return err;
+}
+
+
+static int ov7630_get_ctrl(struct sn9c102_device *cam,
+ struct v4l2_control *ctrl)
+{
+ enum sn9c102_bridge bridge = sn9c102_get_bridge(cam);
+ int err = 0;
+
+ switch (ctrl->id) {
+ case V4L2_CID_EXPOSURE:
+ ctrl->value = sn9c102_i2c_read(cam, 0x10);
+ if (ctrl->value < 0)
+ return -EIO;
+ break;
+ case V4L2_CID_RED_BALANCE:
+ if (bridge == BRIDGE_SN9C105 || bridge == BRIDGE_SN9C120)
+ ctrl->value = sn9c102_pread_reg(cam, 0x05);
+ else
+ ctrl->value = sn9c102_pread_reg(cam, 0x07);
+ break;
+ case V4L2_CID_BLUE_BALANCE:
+ ctrl->value = sn9c102_pread_reg(cam, 0x06);
+ break;
+ case SN9C102_V4L2_CID_GREEN_BALANCE:
+ if (bridge == BRIDGE_SN9C105 || bridge == BRIDGE_SN9C120)
+ ctrl->value = sn9c102_pread_reg(cam, 0x07);
+ else
+ ctrl->value = sn9c102_pread_reg(cam, 0x05);
+ break;
+ break;
+ case V4L2_CID_GAIN:
+ ctrl->value = sn9c102_i2c_read(cam, 0x00);
+ if (ctrl->value < 0)
+ return -EIO;
+ ctrl->value &= 0x3f;
+ break;
+ case V4L2_CID_DO_WHITE_BALANCE:
+ ctrl->value = sn9c102_i2c_read(cam, 0x0c);
+ if (ctrl->value < 0)
+ return -EIO;
+ ctrl->value &= 0x3f;
+ break;
+ case V4L2_CID_WHITENESS:
+ ctrl->value = sn9c102_i2c_read(cam, 0x0d);
+ if (ctrl->value < 0)
+ return -EIO;
+ ctrl->value &= 0x3f;
+ break;
+ case V4L2_CID_AUTOGAIN:
+ ctrl->value = sn9c102_i2c_read(cam, 0x13);
+ if (ctrl->value < 0)
+ return -EIO;
+ ctrl->value &= 0x01;
+ break;
+ case V4L2_CID_VFLIP:
+ ctrl->value = sn9c102_i2c_read(cam, 0x75);
+ if (ctrl->value < 0)
+ return -EIO;
+ ctrl->value = (ctrl->value & 0x80) ? 1 : 0;
+ break;
+ case SN9C102_V4L2_CID_GAMMA:
+ ctrl->value = sn9c102_i2c_read(cam, 0x14);
+ if (ctrl->value < 0)
+ return -EIO;
+ ctrl->value = (ctrl->value & 0x02) ? 1 : 0;
+ break;
+ case SN9C102_V4L2_CID_BAND_FILTER:
+ ctrl->value = sn9c102_i2c_read(cam, 0x2d);
+ if (ctrl->value < 0)
+ return -EIO;
+ ctrl->value = (ctrl->value & 0x02) ? 1 : 0;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return err ? -EIO : 0;
+}
+
+
+static int ov7630_set_ctrl(struct sn9c102_device *cam,
+ const struct v4l2_control *ctrl)
+{
+ enum sn9c102_bridge bridge = sn9c102_get_bridge(cam);
+ int err = 0;
+
+ switch (ctrl->id) {
+ case V4L2_CID_EXPOSURE:
+ err += sn9c102_i2c_write(cam, 0x10, ctrl->value);
+ break;
+ case V4L2_CID_RED_BALANCE:
+ if (bridge == BRIDGE_SN9C105 || bridge == BRIDGE_SN9C120)
+ err += sn9c102_write_reg(cam, ctrl->value, 0x05);
+ else
+ err += sn9c102_write_reg(cam, ctrl->value, 0x07);
+ break;
+ case V4L2_CID_BLUE_BALANCE:
+ err += sn9c102_write_reg(cam, ctrl->value, 0x06);
+ break;
+ case SN9C102_V4L2_CID_GREEN_BALANCE:
+ if (bridge == BRIDGE_SN9C105 || bridge == BRIDGE_SN9C120)
+ err += sn9c102_write_reg(cam, ctrl->value, 0x07);
+ else
+ err += sn9c102_write_reg(cam, ctrl->value, 0x05);
+ break;
+ case V4L2_CID_GAIN:
+ err += sn9c102_i2c_write(cam, 0x00, ctrl->value);
+ break;
+ case V4L2_CID_DO_WHITE_BALANCE:
+ err += sn9c102_i2c_write(cam, 0x0c, ctrl->value);
+ break;
+ case V4L2_CID_WHITENESS:
+ err += sn9c102_i2c_write(cam, 0x0d, ctrl->value);
+ break;
+ case V4L2_CID_AUTOGAIN:
+ err += sn9c102_i2c_write(cam, 0x13, ctrl->value |
+ (ctrl->value << 1));
+ break;
+ case V4L2_CID_VFLIP:
+ err += sn9c102_i2c_write(cam, 0x75, 0x0e | (ctrl->value << 7));
+ break;
+ case SN9C102_V4L2_CID_GAMMA:
+ err += sn9c102_i2c_write(cam, 0x14, ctrl->value << 2);
+ break;
+ case SN9C102_V4L2_CID_BAND_FILTER:
+ err += sn9c102_i2c_write(cam, 0x2d, ctrl->value << 2);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return err ? -EIO : 0;
+}
+
+
+static int ov7630_set_crop(struct sn9c102_device *cam,
+ const struct v4l2_rect *rect)
+{
+ struct sn9c102_sensor *s = sn9c102_get_sensor(cam);
+ int err = 0;
+ u8 h_start = 0, v_start = (u8)(rect->top - s->cropcap.bounds.top) + 1;
+
+ switch (sn9c102_get_bridge(cam)) {
+ case BRIDGE_SN9C101:
+ case BRIDGE_SN9C102:
+ case BRIDGE_SN9C103:
+ h_start = (u8)(rect->left - s->cropcap.bounds.left) + 1;
+ break;
+ case BRIDGE_SN9C105:
+ case BRIDGE_SN9C120:
+ h_start = (u8)(rect->left - s->cropcap.bounds.left) + 4;
+ break;
+ default:
+ break;
+ }
+
+ err += sn9c102_write_reg(cam, h_start, 0x12);
+ err += sn9c102_write_reg(cam, v_start, 0x13);
+
+ return err;
+}
+
+
+static int ov7630_set_pix_format(struct sn9c102_device *cam,
+ const struct v4l2_pix_format *pix)
+{
+ int err = 0;
+
+ switch (sn9c102_get_bridge(cam)) {
+ case BRIDGE_SN9C101:
+ case BRIDGE_SN9C102:
+ case BRIDGE_SN9C103:
+ if (pix->pixelformat == V4L2_PIX_FMT_SBGGR8)
+ err += sn9c102_write_reg(cam, 0x50, 0x19);
+ else
+ err += sn9c102_write_reg(cam, 0x20, 0x19);
+ break;
+ case BRIDGE_SN9C105:
+ case BRIDGE_SN9C120:
+ if (pix->pixelformat == V4L2_PIX_FMT_SBGGR8) {
+ err += sn9c102_write_reg(cam, 0xe5, 0x17);
+ err += sn9c102_i2c_write(cam, 0x11, 0x04);
+ } else {
+ err += sn9c102_write_reg(cam, 0xe2, 0x17);
+ err += sn9c102_i2c_write(cam, 0x11, 0x02);
+ }
+ break;
+ default:
+ break;
+ }
+
+ return err;
+}
+
+
+static const struct sn9c102_sensor ov7630 = {
+ .name = "OV7630",
+ .maintainer = "Luca Risolia <luca.risolia@studio.unibo.it>",
+ .supported_bridge = BRIDGE_SN9C101 | BRIDGE_SN9C102 | BRIDGE_SN9C103 |
+ BRIDGE_SN9C105 | BRIDGE_SN9C120,
+ .sysfs_ops = SN9C102_I2C_READ | SN9C102_I2C_WRITE,
+ .frequency = SN9C102_I2C_100KHZ,
+ .interface = SN9C102_I2C_2WIRES,
+ .i2c_slave_id = 0x21,
+ .init = &ov7630_init,
+ .qctrl = {
+ {
+ .id = V4L2_CID_GAIN,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "global gain",
+ .minimum = 0x00,
+ .maximum = 0x3f,
+ .step = 0x01,
+ .default_value = 0x14,
+ .flags = 0,
+ },
+ {
+ .id = V4L2_CID_EXPOSURE,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "exposure",
+ .minimum = 0x00,
+ .maximum = 0xff,
+ .step = 0x01,
+ .default_value = 0x60,
+ .flags = 0,
+ },
+ {
+ .id = V4L2_CID_WHITENESS,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "white balance background: red",
+ .minimum = 0x00,
+ .maximum = 0x3f,
+ .step = 0x01,
+ .default_value = 0x20,
+ .flags = 0,
+ },
+ {
+ .id = V4L2_CID_DO_WHITE_BALANCE,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "white balance background: blue",
+ .minimum = 0x00,
+ .maximum = 0x3f,
+ .step = 0x01,
+ .default_value = 0x20,
+ .flags = 0,
+ },
+ {
+ .id = V4L2_CID_RED_BALANCE,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "red balance",
+ .minimum = 0x00,
+ .maximum = 0x7f,
+ .step = 0x01,
+ .default_value = 0x20,
+ .flags = 0,
+ },
+ {
+ .id = V4L2_CID_BLUE_BALANCE,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "blue balance",
+ .minimum = 0x00,
+ .maximum = 0x7f,
+ .step = 0x01,
+ .default_value = 0x20,
+ .flags = 0,
+ },
+ {
+ .id = V4L2_CID_AUTOGAIN,
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ .name = "auto adjust",
+ .minimum = 0x00,
+ .maximum = 0x01,
+ .step = 0x01,
+ .default_value = 0x00,
+ .flags = 0,
+ },
+ {
+ .id = V4L2_CID_VFLIP,
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ .name = "vertical flip",
+ .minimum = 0x00,
+ .maximum = 0x01,
+ .step = 0x01,
+ .default_value = 0x01,
+ .flags = 0,
+ },
+ {
+ .id = SN9C102_V4L2_CID_GREEN_BALANCE,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "green balance",
+ .minimum = 0x00,
+ .maximum = 0x7f,
+ .step = 0x01,
+ .default_value = 0x20,
+ .flags = 0,
+ },
+ {
+ .id = SN9C102_V4L2_CID_BAND_FILTER,
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ .name = "band filter",
+ .minimum = 0x00,
+ .maximum = 0x01,
+ .step = 0x01,
+ .default_value = 0x00,
+ .flags = 0,
+ },
+ {
+ .id = SN9C102_V4L2_CID_GAMMA,
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ .name = "rgb gamma",
+ .minimum = 0x00,
+ .maximum = 0x01,
+ .step = 0x01,
+ .default_value = 0x00,
+ .flags = 0,
+ },
+ },
+ .get_ctrl = &ov7630_get_ctrl,
+ .set_ctrl = &ov7630_set_ctrl,
+ .cropcap = {
+ .bounds = {
+ .left = 0,
+ .top = 0,
+ .width = 640,
+ .height = 480,
+ },
+ .defrect = {
+ .left = 0,
+ .top = 0,
+ .width = 640,
+ .height = 480,
+ },
+ },
+ .set_crop = &ov7630_set_crop,
+ .pix_format = {
+ .width = 640,
+ .height = 480,
+ .pixelformat = V4L2_PIX_FMT_SN9C10X,
+ .priv = 8,
+ },
+ .set_pix_format = &ov7630_set_pix_format
+};
+
+
+int sn9c102_probe_ov7630(struct sn9c102_device *cam)
+{
+ int pid, ver, err = 0;
+
+ switch (sn9c102_get_bridge(cam)) {
+ case BRIDGE_SN9C101:
+ case BRIDGE_SN9C102:
+ err = sn9c102_write_const_regs(cam, {0x01, 0x01}, {0x00, 0x01},
+ {0x28, 0x17});
+ break;
+ case BRIDGE_SN9C103: /* do _not_ change anything! */
+ err = sn9c102_write_const_regs(cam, {0x09, 0x01}, {0x42, 0x01},
+ {0x28, 0x17}, {0x44, 0x02});
+ pid = sn9c102_i2c_try_read(cam, &ov7630, 0x0a);
+ if (err || pid < 0) /* try a different initialization */
+ err += sn9c102_write_const_regs(cam, {0x01, 0x01},
+ {0x00, 0x01});
+ break;
+ case BRIDGE_SN9C105:
+ case BRIDGE_SN9C120:
+ err = sn9c102_write_const_regs(cam, {0x01, 0xf1}, {0x00, 0xf1},
+ {0x29, 0x01}, {0x74, 0x02},
+ {0x0e, 0x01}, {0x44, 0x01});
+ break;
+ default:
+ break;
+ }
+
+ pid = sn9c102_i2c_try_read(cam, &ov7630, 0x0a);
+ ver = sn9c102_i2c_try_read(cam, &ov7630, 0x0b);
+ if (err || pid < 0 || ver < 0)
+ return -EIO;
+ if (pid != 0x76 || ver != 0x31)
+ return -ENODEV;
+ sn9c102_attach_sensor(cam, &ov7630);
+
+ return 0;
+}
diff --git a/drivers/staging/media/sn9c102/sn9c102_ov7660.c b/drivers/staging/media/sn9c102/sn9c102_ov7660.c
new file mode 100644
index 00000000000..ac07805d122
--- /dev/null
+++ b/drivers/staging/media/sn9c102/sn9c102_ov7660.c
@@ -0,0 +1,546 @@
+/***************************************************************************
+ * Plug-in for OV7660 image sensor connected to the SN9C1xx PC Camera *
+ * Controllers *
+ * *
+ * Copyright (C) 2007 by Luca Risolia <luca.risolia@studio.unibo.it> *
+ * *
+ * 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., 675 Mass Ave, Cambridge, MA 02139, USA. *
+ ***************************************************************************/
+
+#include "sn9c102_sensor.h"
+#include "sn9c102_devtable.h"
+
+
+static int ov7660_init(struct sn9c102_device *cam)
+{
+ int err = 0;
+
+ err = sn9c102_write_const_regs(cam, {0x40, 0x02}, {0x00, 0x03},
+ {0x1a, 0x04}, {0x03, 0x10},
+ {0x08, 0x14}, {0x20, 0x17},
+ {0x8b, 0x18}, {0x00, 0x19},
+ {0x1d, 0x1a}, {0x10, 0x1b},
+ {0x02, 0x1c}, {0x03, 0x1d},
+ {0x0f, 0x1e}, {0x0c, 0x1f},
+ {0x00, 0x20}, {0x29, 0x21},
+ {0x40, 0x22}, {0x54, 0x23},
+ {0x66, 0x24}, {0x76, 0x25},
+ {0x85, 0x26}, {0x94, 0x27},
+ {0xa1, 0x28}, {0xae, 0x29},
+ {0xbb, 0x2a}, {0xc7, 0x2b},
+ {0xd3, 0x2c}, {0xde, 0x2d},
+ {0xea, 0x2e}, {0xf4, 0x2f},
+ {0xff, 0x30}, {0x00, 0x3f},
+ {0xc7, 0x40}, {0x01, 0x41},
+ {0x44, 0x42}, {0x00, 0x43},
+ {0x44, 0x44}, {0x00, 0x45},
+ {0x44, 0x46}, {0x00, 0x47},
+ {0xc7, 0x48}, {0x01, 0x49},
+ {0xc7, 0x4a}, {0x01, 0x4b},
+ {0xc7, 0x4c}, {0x01, 0x4d},
+ {0x44, 0x4e}, {0x00, 0x4f},
+ {0x44, 0x50}, {0x00, 0x51},
+ {0x44, 0x52}, {0x00, 0x53},
+ {0xc7, 0x54}, {0x01, 0x55},
+ {0xc7, 0x56}, {0x01, 0x57},
+ {0xc7, 0x58}, {0x01, 0x59},
+ {0x44, 0x5a}, {0x00, 0x5b},
+ {0x44, 0x5c}, {0x00, 0x5d},
+ {0x44, 0x5e}, {0x00, 0x5f},
+ {0xc7, 0x60}, {0x01, 0x61},
+ {0xc7, 0x62}, {0x01, 0x63},
+ {0xc7, 0x64}, {0x01, 0x65},
+ {0x44, 0x66}, {0x00, 0x67},
+ {0x44, 0x68}, {0x00, 0x69},
+ {0x44, 0x6a}, {0x00, 0x6b},
+ {0xc7, 0x6c}, {0x01, 0x6d},
+ {0xc7, 0x6e}, {0x01, 0x6f},
+ {0xc7, 0x70}, {0x01, 0x71},
+ {0x44, 0x72}, {0x00, 0x73},
+ {0x44, 0x74}, {0x00, 0x75},
+ {0x44, 0x76}, {0x00, 0x77},
+ {0xc7, 0x78}, {0x01, 0x79},
+ {0xc7, 0x7a}, {0x01, 0x7b},
+ {0xc7, 0x7c}, {0x01, 0x7d},
+ {0x44, 0x7e}, {0x00, 0x7f},
+ {0x14, 0x84}, {0x00, 0x85},
+ {0x27, 0x86}, {0x00, 0x87},
+ {0x07, 0x88}, {0x00, 0x89},
+ {0xec, 0x8a}, {0x0f, 0x8b},
+ {0xd8, 0x8c}, {0x0f, 0x8d},
+ {0x3d, 0x8e}, {0x00, 0x8f},
+ {0x3d, 0x90}, {0x00, 0x91},
+ {0xcd, 0x92}, {0x0f, 0x93},
+ {0xf7, 0x94}, {0x0f, 0x95},
+ {0x0c, 0x96}, {0x00, 0x97},
+ {0x00, 0x98}, {0x66, 0x99},
+ {0x05, 0x9a}, {0x00, 0x9b},
+ {0x04, 0x9c}, {0x00, 0x9d},
+ {0x08, 0x9e}, {0x00, 0x9f},
+ {0x2d, 0xc0}, {0x2d, 0xc1},
+ {0x3a, 0xc2}, {0x05, 0xc3},
+ {0x04, 0xc4}, {0x3f, 0xc5},
+ {0x00, 0xc6}, {0x00, 0xc7},
+ {0x50, 0xc8}, {0x3C, 0xc9},
+ {0x28, 0xca}, {0xd8, 0xcb},
+ {0x14, 0xcc}, {0xec, 0xcd},
+ {0x32, 0xce}, {0xdd, 0xcf},
+ {0x32, 0xd0}, {0xdd, 0xd1},
+ {0x6a, 0xd2}, {0x50, 0xd3},
+ {0x00, 0xd4}, {0x00, 0xd5},
+ {0x00, 0xd6});
+
+ err += sn9c102_i2c_write(cam, 0x12, 0x80);
+ err += sn9c102_i2c_write(cam, 0x11, 0x09);
+ err += sn9c102_i2c_write(cam, 0x00, 0x0A);
+ err += sn9c102_i2c_write(cam, 0x01, 0x80);
+ err += sn9c102_i2c_write(cam, 0x02, 0x80);
+ err += sn9c102_i2c_write(cam, 0x03, 0x00);
+ err += sn9c102_i2c_write(cam, 0x04, 0x00);
+ err += sn9c102_i2c_write(cam, 0x05, 0x08);
+ err += sn9c102_i2c_write(cam, 0x06, 0x0B);
+ err += sn9c102_i2c_write(cam, 0x07, 0x00);
+ err += sn9c102_i2c_write(cam, 0x08, 0x1C);
+ err += sn9c102_i2c_write(cam, 0x09, 0x01);
+ err += sn9c102_i2c_write(cam, 0x0A, 0x76);
+ err += sn9c102_i2c_write(cam, 0x0B, 0x60);
+ err += sn9c102_i2c_write(cam, 0x0C, 0x00);
+ err += sn9c102_i2c_write(cam, 0x0D, 0x08);
+ err += sn9c102_i2c_write(cam, 0x0E, 0x04);
+ err += sn9c102_i2c_write(cam, 0x0F, 0x6F);
+ err += sn9c102_i2c_write(cam, 0x10, 0x20);
+ err += sn9c102_i2c_write(cam, 0x11, 0x03);
+ err += sn9c102_i2c_write(cam, 0x12, 0x05);
+ err += sn9c102_i2c_write(cam, 0x13, 0xC7);
+ err += sn9c102_i2c_write(cam, 0x14, 0x2C);
+ err += sn9c102_i2c_write(cam, 0x15, 0x00);
+ err += sn9c102_i2c_write(cam, 0x16, 0x02);
+ err += sn9c102_i2c_write(cam, 0x17, 0x10);
+ err += sn9c102_i2c_write(cam, 0x18, 0x60);
+ err += sn9c102_i2c_write(cam, 0x19, 0x02);
+ err += sn9c102_i2c_write(cam, 0x1A, 0x7B);
+ err += sn9c102_i2c_write(cam, 0x1B, 0x02);
+ err += sn9c102_i2c_write(cam, 0x1C, 0x7F);
+ err += sn9c102_i2c_write(cam, 0x1D, 0xA2);
+ err += sn9c102_i2c_write(cam, 0x1E, 0x01);
+ err += sn9c102_i2c_write(cam, 0x1F, 0x0E);
+ err += sn9c102_i2c_write(cam, 0x20, 0x05);
+ err += sn9c102_i2c_write(cam, 0x21, 0x05);
+ err += sn9c102_i2c_write(cam, 0x22, 0x05);
+ err += sn9c102_i2c_write(cam, 0x23, 0x05);
+ err += sn9c102_i2c_write(cam, 0x24, 0x68);
+ err += sn9c102_i2c_write(cam, 0x25, 0x58);
+ err += sn9c102_i2c_write(cam, 0x26, 0xD4);
+ err += sn9c102_i2c_write(cam, 0x27, 0x80);
+ err += sn9c102_i2c_write(cam, 0x28, 0x80);
+ err += sn9c102_i2c_write(cam, 0x29, 0x30);
+ err += sn9c102_i2c_write(cam, 0x2A, 0x00);
+ err += sn9c102_i2c_write(cam, 0x2B, 0x00);
+ err += sn9c102_i2c_write(cam, 0x2C, 0x80);
+ err += sn9c102_i2c_write(cam, 0x2D, 0x00);
+ err += sn9c102_i2c_write(cam, 0x2E, 0x00);
+ err += sn9c102_i2c_write(cam, 0x2F, 0x0E);
+ err += sn9c102_i2c_write(cam, 0x30, 0x08);
+ err += sn9c102_i2c_write(cam, 0x31, 0x30);
+ err += sn9c102_i2c_write(cam, 0x32, 0xB4);
+ err += sn9c102_i2c_write(cam, 0x33, 0x00);
+ err += sn9c102_i2c_write(cam, 0x34, 0x07);
+ err += sn9c102_i2c_write(cam, 0x35, 0x84);
+ err += sn9c102_i2c_write(cam, 0x36, 0x00);
+ err += sn9c102_i2c_write(cam, 0x37, 0x0C);
+ err += sn9c102_i2c_write(cam, 0x38, 0x02);
+ err += sn9c102_i2c_write(cam, 0x39, 0x43);
+ err += sn9c102_i2c_write(cam, 0x3A, 0x00);
+ err += sn9c102_i2c_write(cam, 0x3B, 0x0A);
+ err += sn9c102_i2c_write(cam, 0x3C, 0x6C);
+ err += sn9c102_i2c_write(cam, 0x3D, 0x99);
+ err += sn9c102_i2c_write(cam, 0x3E, 0x0E);
+ err += sn9c102_i2c_write(cam, 0x3F, 0x41);
+ err += sn9c102_i2c_write(cam, 0x40, 0xC1);
+ err += sn9c102_i2c_write(cam, 0x41, 0x22);
+ err += sn9c102_i2c_write(cam, 0x42, 0x08);
+ err += sn9c102_i2c_write(cam, 0x43, 0xF0);
+ err += sn9c102_i2c_write(cam, 0x44, 0x10);
+ err += sn9c102_i2c_write(cam, 0x45, 0x78);
+ err += sn9c102_i2c_write(cam, 0x46, 0xA8);
+ err += sn9c102_i2c_write(cam, 0x47, 0x60);
+ err += sn9c102_i2c_write(cam, 0x48, 0x80);
+ err += sn9c102_i2c_write(cam, 0x49, 0x00);
+ err += sn9c102_i2c_write(cam, 0x4A, 0x00);
+ err += sn9c102_i2c_write(cam, 0x4B, 0x00);
+ err += sn9c102_i2c_write(cam, 0x4C, 0x00);
+ err += sn9c102_i2c_write(cam, 0x4D, 0x00);
+ err += sn9c102_i2c_write(cam, 0x4E, 0x00);
+ err += sn9c102_i2c_write(cam, 0x4F, 0x46);
+ err += sn9c102_i2c_write(cam, 0x50, 0x36);
+ err += sn9c102_i2c_write(cam, 0x51, 0x0F);
+ err += sn9c102_i2c_write(cam, 0x52, 0x17);
+ err += sn9c102_i2c_write(cam, 0x53, 0x7F);
+ err += sn9c102_i2c_write(cam, 0x54, 0x96);
+ err += sn9c102_i2c_write(cam, 0x55, 0x40);
+ err += sn9c102_i2c_write(cam, 0x56, 0x40);
+ err += sn9c102_i2c_write(cam, 0x57, 0x40);
+ err += sn9c102_i2c_write(cam, 0x58, 0x0F);
+ err += sn9c102_i2c_write(cam, 0x59, 0xBA);
+ err += sn9c102_i2c_write(cam, 0x5A, 0x9A);
+ err += sn9c102_i2c_write(cam, 0x5B, 0x22);
+ err += sn9c102_i2c_write(cam, 0x5C, 0xB9);
+ err += sn9c102_i2c_write(cam, 0x5D, 0x9B);
+ err += sn9c102_i2c_write(cam, 0x5E, 0x10);
+ err += sn9c102_i2c_write(cam, 0x5F, 0xF0);
+ err += sn9c102_i2c_write(cam, 0x60, 0x05);
+ err += sn9c102_i2c_write(cam, 0x61, 0x60);
+ err += sn9c102_i2c_write(cam, 0x62, 0x00);
+ err += sn9c102_i2c_write(cam, 0x63, 0x00);
+ err += sn9c102_i2c_write(cam, 0x64, 0x50);
+ err += sn9c102_i2c_write(cam, 0x65, 0x30);
+ err += sn9c102_i2c_write(cam, 0x66, 0x00);
+ err += sn9c102_i2c_write(cam, 0x67, 0x80);
+ err += sn9c102_i2c_write(cam, 0x68, 0x7A);
+ err += sn9c102_i2c_write(cam, 0x69, 0x90);
+ err += sn9c102_i2c_write(cam, 0x6A, 0x80);
+ err += sn9c102_i2c_write(cam, 0x6B, 0x0A);
+ err += sn9c102_i2c_write(cam, 0x6C, 0x30);
+ err += sn9c102_i2c_write(cam, 0x6D, 0x48);
+ err += sn9c102_i2c_write(cam, 0x6E, 0x80);
+ err += sn9c102_i2c_write(cam, 0x6F, 0x74);
+ err += sn9c102_i2c_write(cam, 0x70, 0x64);
+ err += sn9c102_i2c_write(cam, 0x71, 0x60);
+ err += sn9c102_i2c_write(cam, 0x72, 0x5C);
+ err += sn9c102_i2c_write(cam, 0x73, 0x58);
+ err += sn9c102_i2c_write(cam, 0x74, 0x54);
+ err += sn9c102_i2c_write(cam, 0x75, 0x4C);
+ err += sn9c102_i2c_write(cam, 0x76, 0x40);
+ err += sn9c102_i2c_write(cam, 0x77, 0x38);
+ err += sn9c102_i2c_write(cam, 0x78, 0x34);
+ err += sn9c102_i2c_write(cam, 0x79, 0x30);
+ err += sn9c102_i2c_write(cam, 0x7A, 0x2F);
+ err += sn9c102_i2c_write(cam, 0x7B, 0x2B);
+ err += sn9c102_i2c_write(cam, 0x7C, 0x03);
+ err += sn9c102_i2c_write(cam, 0x7D, 0x07);
+ err += sn9c102_i2c_write(cam, 0x7E, 0x17);
+ err += sn9c102_i2c_write(cam, 0x7F, 0x34);
+ err += sn9c102_i2c_write(cam, 0x80, 0x41);
+ err += sn9c102_i2c_write(cam, 0x81, 0x4D);
+ err += sn9c102_i2c_write(cam, 0x82, 0x58);
+ err += sn9c102_i2c_write(cam, 0x83, 0x63);
+ err += sn9c102_i2c_write(cam, 0x84, 0x6E);
+ err += sn9c102_i2c_write(cam, 0x85, 0x77);
+ err += sn9c102_i2c_write(cam, 0x86, 0x87);
+ err += sn9c102_i2c_write(cam, 0x87, 0x95);
+ err += sn9c102_i2c_write(cam, 0x88, 0xAF);
+ err += sn9c102_i2c_write(cam, 0x89, 0xC7);
+ err += sn9c102_i2c_write(cam, 0x8A, 0xDF);
+ err += sn9c102_i2c_write(cam, 0x8B, 0x99);
+ err += sn9c102_i2c_write(cam, 0x8C, 0x99);
+ err += sn9c102_i2c_write(cam, 0x8D, 0xCF);
+ err += sn9c102_i2c_write(cam, 0x8E, 0x20);
+ err += sn9c102_i2c_write(cam, 0x8F, 0x26);
+ err += sn9c102_i2c_write(cam, 0x90, 0x10);
+ err += sn9c102_i2c_write(cam, 0x91, 0x0C);
+ err += sn9c102_i2c_write(cam, 0x92, 0x25);
+ err += sn9c102_i2c_write(cam, 0x93, 0x00);
+ err += sn9c102_i2c_write(cam, 0x94, 0x50);
+ err += sn9c102_i2c_write(cam, 0x95, 0x50);
+ err += sn9c102_i2c_write(cam, 0x96, 0x00);
+ err += sn9c102_i2c_write(cam, 0x97, 0x01);
+ err += sn9c102_i2c_write(cam, 0x98, 0x10);
+ err += sn9c102_i2c_write(cam, 0x99, 0x40);
+ err += sn9c102_i2c_write(cam, 0x9A, 0x40);
+ err += sn9c102_i2c_write(cam, 0x9B, 0x20);
+ err += sn9c102_i2c_write(cam, 0x9C, 0x00);
+ err += sn9c102_i2c_write(cam, 0x9D, 0x99);
+ err += sn9c102_i2c_write(cam, 0x9E, 0x7F);
+ err += sn9c102_i2c_write(cam, 0x9F, 0x00);
+ err += sn9c102_i2c_write(cam, 0xA0, 0x00);
+ err += sn9c102_i2c_write(cam, 0xA1, 0x00);
+
+ return err;
+}
+
+
+static int ov7660_get_ctrl(struct sn9c102_device *cam,
+ struct v4l2_control *ctrl)
+{
+ int err = 0;
+
+ switch (ctrl->id) {
+ case V4L2_CID_EXPOSURE:
+ ctrl->value = sn9c102_i2c_read(cam, 0x10);
+ if (ctrl->value < 0)
+ return -EIO;
+ break;
+ case V4L2_CID_DO_WHITE_BALANCE:
+ ctrl->value = sn9c102_read_reg(cam, 0x02);
+ if (ctrl->value < 0)
+ return -EIO;
+ ctrl->value = (ctrl->value & 0x04) ? 1 : 0;
+ break;
+ case V4L2_CID_RED_BALANCE:
+ ctrl->value = sn9c102_read_reg(cam, 0x05);
+ if (ctrl->value < 0)
+ return -EIO;
+ ctrl->value &= 0x7f;
+ break;
+ case V4L2_CID_BLUE_BALANCE:
+ ctrl->value = sn9c102_read_reg(cam, 0x06);
+ if (ctrl->value < 0)
+ return -EIO;
+ ctrl->value &= 0x7f;
+ break;
+ case SN9C102_V4L2_CID_GREEN_BALANCE:
+ ctrl->value = sn9c102_read_reg(cam, 0x07);
+ if (ctrl->value < 0)
+ return -EIO;
+ ctrl->value &= 0x7f;
+ break;
+ case SN9C102_V4L2_CID_BAND_FILTER:
+ ctrl->value = sn9c102_i2c_read(cam, 0x3b);
+ if (ctrl->value < 0)
+ return -EIO;
+ ctrl->value &= 0x08;
+ break;
+ case V4L2_CID_GAIN:
+ ctrl->value = sn9c102_i2c_read(cam, 0x00);
+ if (ctrl->value < 0)
+ return -EIO;
+ ctrl->value &= 0x1f;
+ break;
+ case V4L2_CID_AUTOGAIN:
+ ctrl->value = sn9c102_i2c_read(cam, 0x13);
+ if (ctrl->value < 0)
+ return -EIO;
+ ctrl->value &= 0x01;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return err ? -EIO : 0;
+}
+
+
+static int ov7660_set_ctrl(struct sn9c102_device *cam,
+ const struct v4l2_control *ctrl)
+{
+ int err = 0;
+
+ switch (ctrl->id) {
+ case V4L2_CID_EXPOSURE:
+ err += sn9c102_i2c_write(cam, 0x10, ctrl->value);
+ break;
+ case V4L2_CID_DO_WHITE_BALANCE:
+ err += sn9c102_write_reg(cam, 0x43 | (ctrl->value << 2), 0x02);
+ break;
+ case V4L2_CID_RED_BALANCE:
+ err += sn9c102_write_reg(cam, ctrl->value, 0x05);
+ break;
+ case V4L2_CID_BLUE_BALANCE:
+ err += sn9c102_write_reg(cam, ctrl->value, 0x06);
+ break;
+ case SN9C102_V4L2_CID_GREEN_BALANCE:
+ err += sn9c102_write_reg(cam, ctrl->value, 0x07);
+ break;
+ case SN9C102_V4L2_CID_BAND_FILTER:
+ err += sn9c102_i2c_write(cam, ctrl->value << 3, 0x3b);
+ break;
+ case V4L2_CID_GAIN:
+ err += sn9c102_i2c_write(cam, 0x00, 0x60 + ctrl->value);
+ break;
+ case V4L2_CID_AUTOGAIN:
+ err += sn9c102_i2c_write(cam, 0x13, 0xc0 |
+ (ctrl->value * 0x07));
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return err ? -EIO : 0;
+}
+
+
+static int ov7660_set_crop(struct sn9c102_device *cam,
+ const struct v4l2_rect *rect)
+{
+ struct sn9c102_sensor *s = sn9c102_get_sensor(cam);
+ int err = 0;
+ u8 h_start = (u8)(rect->left - s->cropcap.bounds.left) + 1,
+ v_start = (u8)(rect->top - s->cropcap.bounds.top) + 1;
+
+ err += sn9c102_write_reg(cam, h_start, 0x12);
+ err += sn9c102_write_reg(cam, v_start, 0x13);
+
+ return err;
+}
+
+
+static int ov7660_set_pix_format(struct sn9c102_device *cam,
+ const struct v4l2_pix_format *pix)
+{
+ int r0, err = 0;
+
+ r0 = sn9c102_pread_reg(cam, 0x01);
+
+ if (pix->pixelformat == V4L2_PIX_FMT_JPEG) {
+ err += sn9c102_write_reg(cam, r0 | 0x40, 0x01);
+ err += sn9c102_write_reg(cam, 0xa2, 0x17);
+ err += sn9c102_i2c_write(cam, 0x11, 0x00);
+ } else {
+ err += sn9c102_write_reg(cam, r0 | 0x40, 0x01);
+ err += sn9c102_write_reg(cam, 0xa2, 0x17);
+ err += sn9c102_i2c_write(cam, 0x11, 0x0d);
+ }
+
+ return err;
+}
+
+
+static const struct sn9c102_sensor ov7660 = {
+ .name = "OV7660",
+ .maintainer = "Luca Risolia <luca.risolia@studio.unibo.it>",
+ .supported_bridge = BRIDGE_SN9C105 | BRIDGE_SN9C120,
+ .sysfs_ops = SN9C102_I2C_READ | SN9C102_I2C_WRITE,
+ .frequency = SN9C102_I2C_100KHZ,
+ .interface = SN9C102_I2C_2WIRES,
+ .i2c_slave_id = 0x21,
+ .init = &ov7660_init,
+ .qctrl = {
+ {
+ .id = V4L2_CID_GAIN,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "global gain",
+ .minimum = 0x00,
+ .maximum = 0x1f,
+ .step = 0x01,
+ .default_value = 0x09,
+ .flags = 0,
+ },
+ {
+ .id = V4L2_CID_EXPOSURE,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "exposure",
+ .minimum = 0x00,
+ .maximum = 0xff,
+ .step = 0x01,
+ .default_value = 0x27,
+ .flags = 0,
+ },
+ {
+ .id = V4L2_CID_DO_WHITE_BALANCE,
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ .name = "night mode",
+ .minimum = 0x00,
+ .maximum = 0x01,
+ .step = 0x01,
+ .default_value = 0x00,
+ .flags = 0,
+ },
+ {
+ .id = V4L2_CID_RED_BALANCE,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "red balance",
+ .minimum = 0x00,
+ .maximum = 0x7f,
+ .step = 0x01,
+ .default_value = 0x14,
+ .flags = 0,
+ },
+ {
+ .id = V4L2_CID_BLUE_BALANCE,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "blue balance",
+ .minimum = 0x00,
+ .maximum = 0x7f,
+ .step = 0x01,
+ .default_value = 0x14,
+ .flags = 0,
+ },
+ {
+ .id = V4L2_CID_AUTOGAIN,
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ .name = "auto adjust",
+ .minimum = 0x00,
+ .maximum = 0x01,
+ .step = 0x01,
+ .default_value = 0x01,
+ .flags = 0,
+ },
+ {
+ .id = SN9C102_V4L2_CID_GREEN_BALANCE,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "green balance",
+ .minimum = 0x00,
+ .maximum = 0x7f,
+ .step = 0x01,
+ .default_value = 0x14,
+ .flags = 0,
+ },
+ {
+ .id = SN9C102_V4L2_CID_BAND_FILTER,
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ .name = "band filter",
+ .minimum = 0x00,
+ .maximum = 0x01,
+ .step = 0x01,
+ .default_value = 0x00,
+ .flags = 0,
+ },
+ },
+ .get_ctrl = &ov7660_get_ctrl,
+ .set_ctrl = &ov7660_set_ctrl,
+ .cropcap = {
+ .bounds = {
+ .left = 0,
+ .top = 0,
+ .width = 640,
+ .height = 480,
+ },
+ .defrect = {
+ .left = 0,
+ .top = 0,
+ .width = 640,
+ .height = 480,
+ },
+ },
+ .set_crop = &ov7660_set_crop,
+ .pix_format = {
+ .width = 640,
+ .height = 480,
+ .pixelformat = V4L2_PIX_FMT_JPEG,
+ .priv = 8,
+ },
+ .set_pix_format = &ov7660_set_pix_format
+};
+
+
+int sn9c102_probe_ov7660(struct sn9c102_device *cam)
+{
+ int pid, ver, err;
+
+ err = sn9c102_write_const_regs(cam, {0x01, 0xf1}, {0x00, 0xf1},
+ {0x01, 0x01}, {0x00, 0x01},
+ {0x28, 0x17});
+
+ pid = sn9c102_i2c_try_read(cam, &ov7660, 0x0a);
+ ver = sn9c102_i2c_try_read(cam, &ov7660, 0x0b);
+ if (err || pid < 0 || ver < 0)
+ return -EIO;
+ if (pid != 0x76 || ver != 0x60)
+ return -ENODEV;
+
+ sn9c102_attach_sensor(cam, &ov7660);
+
+ return 0;
+}
diff --git a/drivers/staging/media/sn9c102/sn9c102_pas106b.c b/drivers/staging/media/sn9c102/sn9c102_pas106b.c
new file mode 100644
index 00000000000..895931ecac4
--- /dev/null
+++ b/drivers/staging/media/sn9c102/sn9c102_pas106b.c
@@ -0,0 +1,308 @@
+/***************************************************************************
+ * Plug-in for PAS106B image sensor connected to the SN9C1xx PC Camera *
+ * Controllers *
+ * *
+ * Copyright (C) 2004-2007 by Luca Risolia <luca.risolia@studio.unibo.it> *
+ * *
+ * 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., 675 Mass Ave, Cambridge, MA 02139, USA. *
+ ***************************************************************************/
+
+#include <linux/delay.h>
+#include "sn9c102_sensor.h"
+#include "sn9c102_devtable.h"
+
+
+static int pas106b_init(struct sn9c102_device *cam)
+{
+ int err = 0;
+
+ err = sn9c102_write_const_regs(cam, {0x00, 0x10}, {0x00, 0x11},
+ {0x00, 0x14}, {0x20, 0x17},
+ {0x20, 0x19}, {0x09, 0x18});
+
+ err += sn9c102_i2c_write(cam, 0x02, 0x0c);
+ err += sn9c102_i2c_write(cam, 0x05, 0x5a);
+ err += sn9c102_i2c_write(cam, 0x06, 0x88);
+ err += sn9c102_i2c_write(cam, 0x07, 0x80);
+ err += sn9c102_i2c_write(cam, 0x10, 0x06);
+ err += sn9c102_i2c_write(cam, 0x11, 0x06);
+ err += sn9c102_i2c_write(cam, 0x12, 0x00);
+ err += sn9c102_i2c_write(cam, 0x14, 0x02);
+ err += sn9c102_i2c_write(cam, 0x13, 0x01);
+
+ msleep(400);
+
+ return err;
+}
+
+
+static int pas106b_get_ctrl(struct sn9c102_device *cam,
+ struct v4l2_control *ctrl)
+{
+ switch (ctrl->id) {
+ case V4L2_CID_EXPOSURE:
+ {
+ int r1 = sn9c102_i2c_read(cam, 0x03),
+ r2 = sn9c102_i2c_read(cam, 0x04);
+ if (r1 < 0 || r2 < 0)
+ return -EIO;
+ ctrl->value = (r1 << 4) | (r2 & 0x0f);
+ }
+ return 0;
+ case V4L2_CID_RED_BALANCE:
+ ctrl->value = sn9c102_i2c_read(cam, 0x0c);
+ if (ctrl->value < 0)
+ return -EIO;
+ ctrl->value &= 0x1f;
+ return 0;
+ case V4L2_CID_BLUE_BALANCE:
+ ctrl->value = sn9c102_i2c_read(cam, 0x09);
+ if (ctrl->value < 0)
+ return -EIO;
+ ctrl->value &= 0x1f;
+ return 0;
+ case V4L2_CID_GAIN:
+ ctrl->value = sn9c102_i2c_read(cam, 0x0e);
+ if (ctrl->value < 0)
+ return -EIO;
+ ctrl->value &= 0x1f;
+ return 0;
+ case V4L2_CID_CONTRAST:
+ ctrl->value = sn9c102_i2c_read(cam, 0x0f);
+ if (ctrl->value < 0)
+ return -EIO;
+ ctrl->value &= 0x07;
+ return 0;
+ case SN9C102_V4L2_CID_GREEN_BALANCE:
+ ctrl->value = sn9c102_i2c_read(cam, 0x0a);
+ if (ctrl->value < 0)
+ return -EIO;
+ ctrl->value = (ctrl->value & 0x1f) << 1;
+ return 0;
+ case SN9C102_V4L2_CID_DAC_MAGNITUDE:
+ ctrl->value = sn9c102_i2c_read(cam, 0x08);
+ if (ctrl->value < 0)
+ return -EIO;
+ ctrl->value &= 0xf8;
+ return 0;
+ default:
+ return -EINVAL;
+ }
+}
+
+
+static int pas106b_set_ctrl(struct sn9c102_device *cam,
+ const struct v4l2_control *ctrl)
+{
+ int err = 0;
+
+ switch (ctrl->id) {
+ case V4L2_CID_EXPOSURE:
+ err += sn9c102_i2c_write(cam, 0x03, ctrl->value >> 4);
+ err += sn9c102_i2c_write(cam, 0x04, ctrl->value & 0x0f);
+ break;
+ case V4L2_CID_RED_BALANCE:
+ err += sn9c102_i2c_write(cam, 0x0c, ctrl->value);
+ break;
+ case V4L2_CID_BLUE_BALANCE:
+ err += sn9c102_i2c_write(cam, 0x09, ctrl->value);
+ break;
+ case V4L2_CID_GAIN:
+ err += sn9c102_i2c_write(cam, 0x0e, ctrl->value);
+ break;
+ case V4L2_CID_CONTRAST:
+ err += sn9c102_i2c_write(cam, 0x0f, ctrl->value);
+ break;
+ case SN9C102_V4L2_CID_GREEN_BALANCE:
+ err += sn9c102_i2c_write(cam, 0x0a, ctrl->value >> 1);
+ err += sn9c102_i2c_write(cam, 0x0b, ctrl->value >> 1);
+ break;
+ case SN9C102_V4L2_CID_DAC_MAGNITUDE:
+ err += sn9c102_i2c_write(cam, 0x08, ctrl->value << 3);
+ break;
+ default:
+ return -EINVAL;
+ }
+ err += sn9c102_i2c_write(cam, 0x13, 0x01);
+
+ return err ? -EIO : 0;
+}
+
+
+static int pas106b_set_crop(struct sn9c102_device *cam,
+ const struct v4l2_rect *rect)
+{
+ struct sn9c102_sensor *s = sn9c102_get_sensor(cam);
+ int err = 0;
+ u8 h_start = (u8)(rect->left - s->cropcap.bounds.left) + 4,
+ v_start = (u8)(rect->top - s->cropcap.bounds.top) + 3;
+
+ err += sn9c102_write_reg(cam, h_start, 0x12);
+ err += sn9c102_write_reg(cam, v_start, 0x13);
+
+ return err;
+}
+
+
+static int pas106b_set_pix_format(struct sn9c102_device *cam,
+ const struct v4l2_pix_format *pix)
+{
+ int err = 0;
+
+ if (pix->pixelformat == V4L2_PIX_FMT_SN9C10X)
+ err += sn9c102_write_reg(cam, 0x2c, 0x17);
+ else
+ err += sn9c102_write_reg(cam, 0x20, 0x17);
+
+ return err;
+}
+
+
+static const struct sn9c102_sensor pas106b = {
+ .name = "PAS106B",
+ .maintainer = "Luca Risolia <luca.risolia@studio.unibo.it>",
+ .supported_bridge = BRIDGE_SN9C101 | BRIDGE_SN9C102,
+ .sysfs_ops = SN9C102_I2C_READ | SN9C102_I2C_WRITE,
+ .frequency = SN9C102_I2C_400KHZ | SN9C102_I2C_100KHZ,
+ .interface = SN9C102_I2C_2WIRES,
+ .i2c_slave_id = 0x40,
+ .init = &pas106b_init,
+ .qctrl = {
+ {
+ .id = V4L2_CID_EXPOSURE,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "exposure",
+ .minimum = 0x125,
+ .maximum = 0xfff,
+ .step = 0x001,
+ .default_value = 0x140,
+ .flags = 0,
+ },
+ {
+ .id = V4L2_CID_GAIN,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "global gain",
+ .minimum = 0x00,
+ .maximum = 0x1f,
+ .step = 0x01,
+ .default_value = 0x0d,
+ .flags = 0,
+ },
+ {
+ .id = V4L2_CID_CONTRAST,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "contrast",
+ .minimum = 0x00,
+ .maximum = 0x07,
+ .step = 0x01,
+ .default_value = 0x00, /* 0x00~0x03 have same effect */
+ .flags = 0,
+ },
+ {
+ .id = V4L2_CID_RED_BALANCE,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "red balance",
+ .minimum = 0x00,
+ .maximum = 0x1f,
+ .step = 0x01,
+ .default_value = 0x04,
+ .flags = 0,
+ },
+ {
+ .id = V4L2_CID_BLUE_BALANCE,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "blue balance",
+ .minimum = 0x00,
+ .maximum = 0x1f,
+ .step = 0x01,
+ .default_value = 0x06,
+ .flags = 0,
+ },
+ {
+ .id = SN9C102_V4L2_CID_GREEN_BALANCE,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "green balance",
+ .minimum = 0x00,
+ .maximum = 0x3e,
+ .step = 0x02,
+ .default_value = 0x02,
+ .flags = 0,
+ },
+ {
+ .id = SN9C102_V4L2_CID_DAC_MAGNITUDE,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "DAC magnitude",
+ .minimum = 0x00,
+ .maximum = 0x1f,
+ .step = 0x01,
+ .default_value = 0x01,
+ .flags = 0,
+ },
+ },
+ .get_ctrl = &pas106b_get_ctrl,
+ .set_ctrl = &pas106b_set_ctrl,
+ .cropcap = {
+ .bounds = {
+ .left = 0,
+ .top = 0,
+ .width = 352,
+ .height = 288,
+ },
+ .defrect = {
+ .left = 0,
+ .top = 0,
+ .width = 352,
+ .height = 288,
+ },
+ },
+ .set_crop = &pas106b_set_crop,
+ .pix_format = {
+ .width = 352,
+ .height = 288,
+ .pixelformat = V4L2_PIX_FMT_SBGGR8,
+ .priv = 8, /* we use this field as 'bits per pixel' */
+ },
+ .set_pix_format = &pas106b_set_pix_format
+};
+
+
+int sn9c102_probe_pas106b(struct sn9c102_device *cam)
+{
+ int r0 = 0, r1 = 0;
+ unsigned int pid = 0;
+
+ /*
+ Minimal initialization to enable the I2C communication
+ NOTE: do NOT change the values!
+ */
+ if (sn9c102_write_const_regs(cam,
+ {0x01, 0x01}, /* sensor power down */
+ {0x00, 0x01}, /* sensor power on */
+ {0x28, 0x17})) /* sensor clock at 24 MHz */
+ return -EIO;
+
+ r0 = sn9c102_i2c_try_read(cam, &pas106b, 0x00);
+ r1 = sn9c102_i2c_try_read(cam, &pas106b, 0x01);
+ if (r0 < 0 || r1 < 0)
+ return -EIO;
+
+ pid = (r0 << 11) | ((r1 & 0xf0) >> 4);
+ if (pid != 0x007)
+ return -ENODEV;
+
+ sn9c102_attach_sensor(cam, &pas106b);
+
+ return 0;
+}
diff --git a/drivers/staging/media/sn9c102/sn9c102_pas202bcb.c b/drivers/staging/media/sn9c102/sn9c102_pas202bcb.c
new file mode 100644
index 00000000000..f9e31ae2ad9
--- /dev/null
+++ b/drivers/staging/media/sn9c102/sn9c102_pas202bcb.c
@@ -0,0 +1,340 @@
+/***************************************************************************
+ * Plug-in for PAS202BCB image sensor connected to the SN9C1xx PC Camera *
+ * Controllers *
+ * *
+ * Copyright (C) 2004 by Carlos Eduardo Medaglia Dyonisio *
+ * <medaglia@undl.org.br> *
+ * *
+ * Support for SN9C103, DAC Magnitude, exposure and green gain controls *
+ * added by Luca Risolia <luca.risolia@studio.unibo.it> *
+ * *
+ * 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., 675 Mass Ave, Cambridge, MA 02139, USA. *
+ ***************************************************************************/
+
+#include <linux/delay.h>
+#include "sn9c102_sensor.h"
+#include "sn9c102_devtable.h"
+
+
+static int pas202bcb_init(struct sn9c102_device *cam)
+{
+ int err = 0;
+
+ switch (sn9c102_get_bridge(cam)) {
+ case BRIDGE_SN9C101:
+ case BRIDGE_SN9C102:
+ err = sn9c102_write_const_regs(cam, {0x00, 0x10}, {0x00, 0x11},
+ {0x00, 0x14}, {0x20, 0x17},
+ {0x30, 0x19}, {0x09, 0x18});
+ break;
+ case BRIDGE_SN9C103:
+ err = sn9c102_write_const_regs(cam, {0x00, 0x02}, {0x00, 0x03},
+ {0x1a, 0x04}, {0x20, 0x05},
+ {0x20, 0x06}, {0x20, 0x07},
+ {0x00, 0x10}, {0x00, 0x11},
+ {0x00, 0x14}, {0x20, 0x17},
+ {0x30, 0x19}, {0x09, 0x18},
+ {0x02, 0x1c}, {0x03, 0x1d},
+ {0x0f, 0x1e}, {0x0c, 0x1f},
+ {0x00, 0x20}, {0x10, 0x21},
+ {0x20, 0x22}, {0x30, 0x23},
+ {0x40, 0x24}, {0x50, 0x25},
+ {0x60, 0x26}, {0x70, 0x27},
+ {0x80, 0x28}, {0x90, 0x29},
+ {0xa0, 0x2a}, {0xb0, 0x2b},
+ {0xc0, 0x2c}, {0xd0, 0x2d},
+ {0xe0, 0x2e}, {0xf0, 0x2f},
+ {0xff, 0x30});
+ break;
+ default:
+ break;
+ }
+
+ err += sn9c102_i2c_write(cam, 0x02, 0x14);
+ err += sn9c102_i2c_write(cam, 0x03, 0x40);
+ err += sn9c102_i2c_write(cam, 0x0d, 0x2c);
+ err += sn9c102_i2c_write(cam, 0x0e, 0x01);
+ err += sn9c102_i2c_write(cam, 0x0f, 0xa9);
+ err += sn9c102_i2c_write(cam, 0x10, 0x08);
+ err += sn9c102_i2c_write(cam, 0x13, 0x63);
+ err += sn9c102_i2c_write(cam, 0x15, 0x70);
+ err += sn9c102_i2c_write(cam, 0x11, 0x01);
+
+ msleep(400);
+
+ return err;
+}
+
+
+static int pas202bcb_get_ctrl(struct sn9c102_device *cam,
+ struct v4l2_control *ctrl)
+{
+ switch (ctrl->id) {
+ case V4L2_CID_EXPOSURE:
+ {
+ int r1 = sn9c102_i2c_read(cam, 0x04),
+ r2 = sn9c102_i2c_read(cam, 0x05);
+ if (r1 < 0 || r2 < 0)
+ return -EIO;
+ ctrl->value = (r1 << 6) | (r2 & 0x3f);
+ }
+ return 0;
+ case V4L2_CID_RED_BALANCE:
+ ctrl->value = sn9c102_i2c_read(cam, 0x09);
+ if (ctrl->value < 0)
+ return -EIO;
+ ctrl->value &= 0x0f;
+ return 0;
+ case V4L2_CID_BLUE_BALANCE:
+ ctrl->value = sn9c102_i2c_read(cam, 0x07);
+ if (ctrl->value < 0)
+ return -EIO;
+ ctrl->value &= 0x0f;
+ return 0;
+ case V4L2_CID_GAIN:
+ ctrl->value = sn9c102_i2c_read(cam, 0x10);
+ if (ctrl->value < 0)
+ return -EIO;
+ ctrl->value &= 0x1f;
+ return 0;
+ case SN9C102_V4L2_CID_GREEN_BALANCE:
+ ctrl->value = sn9c102_i2c_read(cam, 0x08);
+ if (ctrl->value < 0)
+ return -EIO;
+ ctrl->value &= 0x0f;
+ return 0;
+ case SN9C102_V4L2_CID_DAC_MAGNITUDE:
+ ctrl->value = sn9c102_i2c_read(cam, 0x0c);
+ if (ctrl->value < 0)
+ return -EIO;
+ return 0;
+ default:
+ return -EINVAL;
+ }
+}
+
+
+static int pas202bcb_set_pix_format(struct sn9c102_device *cam,
+ const struct v4l2_pix_format *pix)
+{
+ int err = 0;
+
+ if (pix->pixelformat == V4L2_PIX_FMT_SN9C10X)
+ err += sn9c102_write_reg(cam, 0x28, 0x17);
+ else
+ err += sn9c102_write_reg(cam, 0x20, 0x17);
+
+ return err;
+}
+
+
+static int pas202bcb_set_ctrl(struct sn9c102_device *cam,
+ const struct v4l2_control *ctrl)
+{
+ int err = 0;
+
+ switch (ctrl->id) {
+ case V4L2_CID_EXPOSURE:
+ err += sn9c102_i2c_write(cam, 0x04, ctrl->value >> 6);
+ err += sn9c102_i2c_write(cam, 0x05, ctrl->value & 0x3f);
+ break;
+ case V4L2_CID_RED_BALANCE:
+ err += sn9c102_i2c_write(cam, 0x09, ctrl->value);
+ break;
+ case V4L2_CID_BLUE_BALANCE:
+ err += sn9c102_i2c_write(cam, 0x07, ctrl->value);
+ break;
+ case V4L2_CID_GAIN:
+ err += sn9c102_i2c_write(cam, 0x10, ctrl->value);
+ break;
+ case SN9C102_V4L2_CID_GREEN_BALANCE:
+ err += sn9c102_i2c_write(cam, 0x08, ctrl->value);
+ break;
+ case SN9C102_V4L2_CID_DAC_MAGNITUDE:
+ err += sn9c102_i2c_write(cam, 0x0c, ctrl->value);
+ break;
+ default:
+ return -EINVAL;
+ }
+ err += sn9c102_i2c_write(cam, 0x11, 0x01);
+
+ return err ? -EIO : 0;
+}
+
+
+static int pas202bcb_set_crop(struct sn9c102_device *cam,
+ const struct v4l2_rect *rect)
+{
+ struct sn9c102_sensor *s = sn9c102_get_sensor(cam);
+ int err = 0;
+ u8 h_start = 0,
+ v_start = (u8)(rect->top - s->cropcap.bounds.top) + 3;
+
+ switch (sn9c102_get_bridge(cam)) {
+ case BRIDGE_SN9C101:
+ case BRIDGE_SN9C102:
+ h_start = (u8)(rect->left - s->cropcap.bounds.left) + 4;
+ break;
+ case BRIDGE_SN9C103:
+ h_start = (u8)(rect->left - s->cropcap.bounds.left) + 3;
+ break;
+ default:
+ break;
+ }
+
+ err += sn9c102_write_reg(cam, h_start, 0x12);
+ err += sn9c102_write_reg(cam, v_start, 0x13);
+
+ return err;
+}
+
+
+static const struct sn9c102_sensor pas202bcb = {
+ .name = "PAS202BCB",
+ .maintainer = "Luca Risolia <luca.risolia@studio.unibo.it>",
+ .supported_bridge = BRIDGE_SN9C101 | BRIDGE_SN9C102 | BRIDGE_SN9C103,
+ .sysfs_ops = SN9C102_I2C_READ | SN9C102_I2C_WRITE,
+ .frequency = SN9C102_I2C_400KHZ | SN9C102_I2C_100KHZ,
+ .interface = SN9C102_I2C_2WIRES,
+ .i2c_slave_id = 0x40,
+ .init = &pas202bcb_init,
+ .qctrl = {
+ {
+ .id = V4L2_CID_EXPOSURE,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "exposure",
+ .minimum = 0x01e5,
+ .maximum = 0x3fff,
+ .step = 0x0001,
+ .default_value = 0x01e5,
+ .flags = 0,
+ },
+ {
+ .id = V4L2_CID_GAIN,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "global gain",
+ .minimum = 0x00,
+ .maximum = 0x1f,
+ .step = 0x01,
+ .default_value = 0x0b,
+ .flags = 0,
+ },
+ {
+ .id = V4L2_CID_RED_BALANCE,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "red balance",
+ .minimum = 0x00,
+ .maximum = 0x0f,
+ .step = 0x01,
+ .default_value = 0x00,
+ .flags = 0,
+ },
+ {
+ .id = V4L2_CID_BLUE_BALANCE,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "blue balance",
+ .minimum = 0x00,
+ .maximum = 0x0f,
+ .step = 0x01,
+ .default_value = 0x05,
+ .flags = 0,
+ },
+ {
+ .id = SN9C102_V4L2_CID_GREEN_BALANCE,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "green balance",
+ .minimum = 0x00,
+ .maximum = 0x0f,
+ .step = 0x01,
+ .default_value = 0x00,
+ .flags = 0,
+ },
+ {
+ .id = SN9C102_V4L2_CID_DAC_MAGNITUDE,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "DAC magnitude",
+ .minimum = 0x00,
+ .maximum = 0xff,
+ .step = 0x01,
+ .default_value = 0x04,
+ .flags = 0,
+ },
+ },
+ .get_ctrl = &pas202bcb_get_ctrl,
+ .set_ctrl = &pas202bcb_set_ctrl,
+ .cropcap = {
+ .bounds = {
+ .left = 0,
+ .top = 0,
+ .width = 640,
+ .height = 480,
+ },
+ .defrect = {
+ .left = 0,
+ .top = 0,
+ .width = 640,
+ .height = 480,
+ },
+ },
+ .set_crop = &pas202bcb_set_crop,
+ .pix_format = {
+ .width = 640,
+ .height = 480,
+ .pixelformat = V4L2_PIX_FMT_SBGGR8,
+ .priv = 8,
+ },
+ .set_pix_format = &pas202bcb_set_pix_format
+};
+
+
+int sn9c102_probe_pas202bcb(struct sn9c102_device *cam)
+{
+ int r0 = 0, r1 = 0, err = 0;
+ unsigned int pid = 0;
+
+ /*
+ * Minimal initialization to enable the I2C communication
+ * NOTE: do NOT change the values!
+ */
+ switch (sn9c102_get_bridge(cam)) {
+ case BRIDGE_SN9C101:
+ case BRIDGE_SN9C102:
+ err = sn9c102_write_const_regs(cam,
+ {0x01, 0x01}, /* power down */
+ {0x40, 0x01}, /* power on */
+ {0x28, 0x17});/* clock 24 MHz */
+ break;
+ case BRIDGE_SN9C103: /* do _not_ change anything! */
+ err = sn9c102_write_const_regs(cam, {0x09, 0x01}, {0x44, 0x01},
+ {0x44, 0x02}, {0x29, 0x17});
+ break;
+ default:
+ break;
+ }
+
+ r0 = sn9c102_i2c_try_read(cam, &pas202bcb, 0x00);
+ r1 = sn9c102_i2c_try_read(cam, &pas202bcb, 0x01);
+
+ if (err || r0 < 0 || r1 < 0)
+ return -EIO;
+
+ pid = (r0 << 4) | ((r1 & 0xf0) >> 4);
+ if (pid != 0x017)
+ return -ENODEV;
+
+ sn9c102_attach_sensor(cam, &pas202bcb);
+
+ return 0;
+}
diff --git a/drivers/staging/media/sn9c102/sn9c102_sensor.h b/drivers/staging/media/sn9c102/sn9c102_sensor.h
new file mode 100644
index 00000000000..9f59c815d48
--- /dev/null
+++ b/drivers/staging/media/sn9c102/sn9c102_sensor.h
@@ -0,0 +1,307 @@
+/***************************************************************************
+ * API for image sensors connected to the SN9C1xx PC Camera Controllers *
+ * *
+ * Copyright (C) 2004-2007 by Luca Risolia <luca.risolia@studio.unibo.it> *
+ * *
+ * 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., 675 Mass Ave, Cambridge, MA 02139, USA. *
+ ***************************************************************************/
+
+#ifndef _SN9C102_SENSOR_H_
+#define _SN9C102_SENSOR_H_
+
+#include <linux/usb.h>
+#include <linux/videodev2.h>
+#include <linux/device.h>
+#include <linux/stddef.h>
+#include <linux/errno.h>
+#include <asm/types.h>
+
+struct sn9c102_device;
+struct sn9c102_sensor;
+
+/*****************************************************************************/
+
+/*
+ OVERVIEW.
+ This is a small interface that allows you to add support for any CCD/CMOS
+ image sensors connected to the SN9C1XX bridges. The entire API is documented
+ below. In the most general case, to support a sensor there are three steps
+ you have to follow:
+ 1) define the main "sn9c102_sensor" structure by setting the basic fields;
+ 2) write a probing function to be called by the core module when the USB
+ camera is recognized, then add both the USB ids and the name of that
+ function to the two corresponding tables in sn9c102_devtable.h;
+ 3) implement the methods that you want/need (and fill the rest of the main
+ structure accordingly).
+ "sn9c102_pas106b.c" is an example of all this stuff. Remember that you do
+ NOT need to touch the source code of the core module for the things to work
+ properly, unless you find bugs or flaws in it. Finally, do not forget to
+ read the V4L2 API for completeness.
+*/
+
+/*****************************************************************************/
+
+enum sn9c102_bridge {
+ BRIDGE_SN9C101 = 0x01,
+ BRIDGE_SN9C102 = 0x02,
+ BRIDGE_SN9C103 = 0x04,
+ BRIDGE_SN9C105 = 0x08,
+ BRIDGE_SN9C120 = 0x10,
+};
+
+/* Return the bridge name */
+enum sn9c102_bridge sn9c102_get_bridge(struct sn9c102_device *cam);
+
+/* Return a pointer the sensor struct attached to the camera */
+struct sn9c102_sensor *sn9c102_get_sensor(struct sn9c102_device *cam);
+
+/* Identify a device */
+extern struct sn9c102_device*
+sn9c102_match_id(struct sn9c102_device *cam, const struct usb_device_id *id);
+
+/* Attach a probed sensor to the camera. */
+extern void
+sn9c102_attach_sensor(struct sn9c102_device *cam,
+ const struct sn9c102_sensor *sensor);
+
+/*
+ Read/write routines: they always return -1 on error, 0 or the read value
+ otherwise. NOTE that a real read operation is not supported by the SN9C1XX
+ chip for some of its registers. To work around this problem, a pseudo-read
+ call is provided instead: it returns the last successfully written value
+ on the register (0 if it has never been written), the usual -1 on error.
+*/
+
+/* The "try" I2C I/O versions are used when probing the sensor */
+extern int sn9c102_i2c_try_read(struct sn9c102_device*,
+ const struct sn9c102_sensor*, u8 address);
+
+/*
+ These must be used if and only if the sensor doesn't implement the standard
+ I2C protocol. There are a number of good reasons why you must use the
+ single-byte versions of these functions: do not abuse. The first function
+ writes n bytes, from data0 to datan, to registers 0x09 - 0x09+n of SN9C1XX
+ chip. The second one programs the registers 0x09 and 0x10 with data0 and
+ data1, and places the n bytes read from the sensor register table in the
+ buffer pointed by 'buffer'. Both the functions return -1 on error; the write
+ version returns 0 on success, while the read version returns the first read
+ byte.
+*/
+extern int sn9c102_i2c_try_raw_write(struct sn9c102_device *cam,
+ const struct sn9c102_sensor *sensor, u8 n,
+ u8 data0, u8 data1, u8 data2, u8 data3,
+ u8 data4, u8 data5);
+extern int sn9c102_i2c_try_raw_read(struct sn9c102_device *cam,
+ const struct sn9c102_sensor *sensor,
+ u8 data0, u8 data1, u8 n, u8 buffer[]);
+
+/* To be used after the sensor struct has been attached to the camera struct */
+extern int sn9c102_i2c_write(struct sn9c102_device*, u8 address, u8 value);
+extern int sn9c102_i2c_read(struct sn9c102_device*, u8 address);
+
+/* I/O on registers in the bridge. Could be used by the sensor methods too */
+extern int sn9c102_read_reg(struct sn9c102_device*, u16 index);
+extern int sn9c102_pread_reg(struct sn9c102_device*, u16 index);
+extern int sn9c102_write_reg(struct sn9c102_device*, u8 value, u16 index);
+extern int sn9c102_write_regs(struct sn9c102_device*, const u8 valreg[][2],
+ int count);
+/*
+ Write multiple registers with constant values. For example:
+ sn9c102_write_const_regs(cam, {0x00, 0x14}, {0x60, 0x17}, {0x0f, 0x18});
+ Register addresses must be < 256.
+*/
+#define sn9c102_write_const_regs(sn9c102_device, data...) \
+ ({ static const u8 _valreg[][2] = {data}; \
+ sn9c102_write_regs(sn9c102_device, _valreg, ARRAY_SIZE(_valreg)); })
+
+/*****************************************************************************/
+
+enum sn9c102_i2c_sysfs_ops {
+ SN9C102_I2C_READ = 0x01,
+ SN9C102_I2C_WRITE = 0x02,
+};
+
+enum sn9c102_i2c_frequency { /* sensors may support both the frequencies */
+ SN9C102_I2C_100KHZ = 0x01,
+ SN9C102_I2C_400KHZ = 0x02,
+};
+
+enum sn9c102_i2c_interface {
+ SN9C102_I2C_2WIRES,
+ SN9C102_I2C_3WIRES,
+};
+
+#define SN9C102_MAX_CTRLS (V4L2_CID_LASTP1-V4L2_CID_BASE+10)
+
+struct sn9c102_sensor {
+ char name[32], /* sensor name */
+ maintainer[64]; /* name of the maintainer <email> */
+
+ enum sn9c102_bridge supported_bridge; /* supported SN9C1xx bridges */
+
+ /* Supported operations through the 'sysfs' interface */
+ enum sn9c102_i2c_sysfs_ops sysfs_ops;
+
+ /*
+ These sensor capabilities must be provided if the SN9C1XX controller
+ needs to communicate through the sensor serial interface by using
+ at least one of the i2c functions available.
+ */
+ enum sn9c102_i2c_frequency frequency;
+ enum sn9c102_i2c_interface interface;
+
+ /*
+ This identifier must be provided if the image sensor implements
+ the standard I2C protocol.
+ */
+ u8 i2c_slave_id; /* reg. 0x09 */
+
+ /*
+ NOTE: Where not noted,most of the functions below are not mandatory.
+ Set to null if you do not implement them. If implemented,
+ they must return 0 on success, the proper error otherwise.
+ */
+
+ int (*init)(struct sn9c102_device *cam);
+ /*
+ This function will be called after the sensor has been attached.
+ It should be used to initialize the sensor only, but may also
+ configure part of the SN9C1XX chip if necessary. You don't need to
+ setup picture settings like brightness, contrast, etc.. here, if
+ the corresponding controls are implemented (see below), since
+ they are adjusted in the core driver by calling the set_ctrl()
+ method after init(), where the arguments are the default values
+ specified in the v4l2_queryctrl list of supported controls;
+ Same suggestions apply for other settings, _if_ the corresponding
+ methods are present; if not, the initialization must configure the
+ sensor according to the default configuration structures below.
+ */
+
+ struct v4l2_queryctrl qctrl[SN9C102_MAX_CTRLS];
+ /*
+ Optional list of default controls, defined as indicated in the
+ V4L2 API. Menu type controls are not handled by this interface.
+ */
+
+ int (*get_ctrl)(struct sn9c102_device *cam, struct v4l2_control *ctrl);
+ int (*set_ctrl)(struct sn9c102_device *cam,
+ const struct v4l2_control *ctrl);
+ /*
+ You must implement at least the set_ctrl method if you have defined
+ the list above. The returned value must follow the V4L2
+ specifications for the VIDIOC_G|C_CTRL ioctls. V4L2_CID_H|VCENTER
+ are not supported by this driver, so do not implement them. Also,
+ you don't have to check whether the passed values are out of bounds,
+ given that this is done by the core module.
+ */
+
+ struct v4l2_cropcap cropcap;
+ /*
+ Think the image sensor as a grid of R,G,B monochromatic pixels
+ disposed according to a particular Bayer pattern, which describes
+ the complete array of pixels, from (0,0) to (xmax, ymax). We will
+ use this coordinate system from now on. It is assumed the sensor
+ chip can be programmed to capture/transmit a subsection of that
+ array of pixels: we will call this subsection "active window".
+ It is not always true that the largest achievable active window can
+ cover the whole array of pixels. The V4L2 API defines another
+ area called "source rectangle", which, in turn, is a subrectangle of
+ the active window. The SN9C1XX chip is always programmed to read the
+ source rectangle.
+ The bounds of both the active window and the source rectangle are
+ specified in the cropcap substructures 'bounds' and 'defrect'.
+ By default, the source rectangle should cover the largest possible
+ area. Again, it is not always true that the largest source rectangle
+ can cover the entire active window, although it is a rare case for
+ the hardware we have. The bounds of the source rectangle _must_ be
+ multiple of 16 and must use the same coordinate system as indicated
+ before; their centers shall align initially.
+ If necessary, the sensor chip must be initialized during init() to
+ set the bounds of the active sensor window; however, by default, it
+ usually covers the largest achievable area (maxwidth x maxheight)
+ of pixels, so no particular initialization is needed, if you have
+ defined the correct default bounds in the structures.
+ See the V4L2 API for further details.
+ NOTE: once you have defined the bounds of the active window
+ (struct cropcap.bounds) you must not change them.anymore.
+ Only 'bounds' and 'defrect' fields are mandatory, other fields
+ will be ignored.
+ */
+
+ int (*set_crop)(struct sn9c102_device *cam,
+ const struct v4l2_rect *rect);
+ /*
+ To be called on VIDIOC_C_SETCROP. The core module always calls a
+ default routine which configures the appropriate SN9C1XX regs (also
+ scaling), but you may need to override/adjust specific stuff.
+ 'rect' contains width and height values that are multiple of 16: in
+ case you override the default function, you always have to program
+ the chip to match those values; on error return the corresponding
+ error code without rolling back.
+ NOTE: in case, you must program the SN9C1XX chip to get rid of
+ blank pixels or blank lines at the _start_ of each line or
+ frame after each HSYNC or VSYNC, so that the image starts with
+ real RGB data (see regs 0x12, 0x13) (having set H_SIZE and,
+ V_SIZE you don't have to care about blank pixels or blank
+ lines at the end of each line or frame).
+ */
+
+ struct v4l2_pix_format pix_format;
+ /*
+ What you have to define here are: 1) initial 'width' and 'height' of
+ the target rectangle 2) the initial 'pixelformat', which can be
+ either V4L2_PIX_FMT_SN9C10X, V4L2_PIX_FMT_JPEG (for ompressed video)
+ or V4L2_PIX_FMT_SBGGR8 3) 'priv', which we'll be used to indicate
+ the number of bits per pixel for uncompressed video, 8 or 9 (despite
+ the current value of 'pixelformat').
+ NOTE 1: both 'width' and 'height' _must_ be either 1/1 or 1/2 or 1/4
+ of cropcap.defrect.width and cropcap.defrect.height. I
+ suggest 1/1.
+ NOTE 2: The initial compression quality is defined by the first bit
+ of reg 0x17 during the initialization of the image sensor.
+ NOTE 3: as said above, you have to program the SN9C1XX chip to get
+ rid of any blank pixels, so that the output of the sensor
+ matches the RGB bayer sequence (i.e. BGBGBG...GRGRGR).
+ */
+
+ int (*set_pix_format)(struct sn9c102_device *cam,
+ const struct v4l2_pix_format *pix);
+ /*
+ To be called on VIDIOC_S_FMT, when switching from the SBGGR8 to
+ SN9C10X pixel format or viceversa. On error return the corresponding
+ error code without rolling back.
+ */
+
+ /*
+ Do NOT write to the data below, it's READ ONLY. It is used by the
+ core module to store successfully updated values of the above
+ settings, for rollbacks..etc..in case of errors during atomic I/O
+ */
+ struct v4l2_queryctrl _qctrl[SN9C102_MAX_CTRLS];
+ struct v4l2_rect _rect;
+};
+
+/*****************************************************************************/
+
+/* Private ioctl's for control settings supported by some image sensors */
+#define SN9C102_V4L2_CID_DAC_MAGNITUDE (V4L2_CID_PRIVATE_BASE + 0)
+#define SN9C102_V4L2_CID_GREEN_BALANCE (V4L2_CID_PRIVATE_BASE + 1)
+#define SN9C102_V4L2_CID_RESET_LEVEL (V4L2_CID_PRIVATE_BASE + 2)
+#define SN9C102_V4L2_CID_PIXEL_BIAS_VOLTAGE (V4L2_CID_PRIVATE_BASE + 3)
+#define SN9C102_V4L2_CID_GAMMA (V4L2_CID_PRIVATE_BASE + 4)
+#define SN9C102_V4L2_CID_BAND_FILTER (V4L2_CID_PRIVATE_BASE + 5)
+#define SN9C102_V4L2_CID_BRIGHT_LEVEL (V4L2_CID_PRIVATE_BASE + 6)
+
+#endif /* _SN9C102_SENSOR_H_ */
diff --git a/drivers/staging/media/sn9c102/sn9c102_tas5110c1b.c b/drivers/staging/media/sn9c102/sn9c102_tas5110c1b.c
new file mode 100644
index 00000000000..6a00b626d34
--- /dev/null
+++ b/drivers/staging/media/sn9c102/sn9c102_tas5110c1b.c
@@ -0,0 +1,154 @@
+/***************************************************************************
+ * Plug-in for TAS5110C1B image sensor connected to the SN9C1xx PC Camera *
+ * Controllers *
+ * *
+ * Copyright (C) 2004-2007 by Luca Risolia <luca.risolia@studio.unibo.it> *
+ * *
+ * 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., 675 Mass Ave, Cambridge, MA 02139, USA. *
+ ***************************************************************************/
+
+#include "sn9c102_sensor.h"
+#include "sn9c102_devtable.h"
+
+
+static int tas5110c1b_init(struct sn9c102_device *cam)
+{
+ int err = 0;
+
+ err = sn9c102_write_const_regs(cam, {0x01, 0x01}, {0x44, 0x01},
+ {0x00, 0x10}, {0x00, 0x11},
+ {0x0a, 0x14}, {0x60, 0x17},
+ {0x06, 0x18}, {0xfb, 0x19});
+
+ err += sn9c102_i2c_write(cam, 0xc0, 0x80);
+
+ return err;
+}
+
+
+static int tas5110c1b_set_ctrl(struct sn9c102_device *cam,
+ const struct v4l2_control *ctrl)
+{
+ int err = 0;
+
+ switch (ctrl->id) {
+ case V4L2_CID_GAIN:
+ err += sn9c102_i2c_write(cam, 0x20, 0xf6 - ctrl->value);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return err ? -EIO : 0;
+}
+
+
+static int tas5110c1b_set_crop(struct sn9c102_device *cam,
+ const struct v4l2_rect *rect)
+{
+ struct sn9c102_sensor *s = sn9c102_get_sensor(cam);
+ int err = 0;
+ u8 h_start = (u8)(rect->left - s->cropcap.bounds.left) + 69,
+ v_start = (u8)(rect->top - s->cropcap.bounds.top) + 9;
+
+ err += sn9c102_write_reg(cam, h_start, 0x12);
+ err += sn9c102_write_reg(cam, v_start, 0x13);
+
+ /* Don't change ! */
+ err += sn9c102_write_reg(cam, 0x14, 0x1a);
+ err += sn9c102_write_reg(cam, 0x0a, 0x1b);
+ err += sn9c102_write_reg(cam, sn9c102_pread_reg(cam, 0x19), 0x19);
+
+ return err;
+}
+
+
+static int tas5110c1b_set_pix_format(struct sn9c102_device *cam,
+ const struct v4l2_pix_format *pix)
+{
+ int err = 0;
+
+ if (pix->pixelformat == V4L2_PIX_FMT_SN9C10X)
+ err += sn9c102_write_reg(cam, 0x2b, 0x19);
+ else
+ err += sn9c102_write_reg(cam, 0xfb, 0x19);
+
+ return err;
+}
+
+
+static const struct sn9c102_sensor tas5110c1b = {
+ .name = "TAS5110C1B",
+ .maintainer = "Luca Risolia <luca.risolia@studio.unibo.it>",
+ .supported_bridge = BRIDGE_SN9C101 | BRIDGE_SN9C102,
+ .sysfs_ops = SN9C102_I2C_WRITE,
+ .frequency = SN9C102_I2C_100KHZ,
+ .interface = SN9C102_I2C_3WIRES,
+ .init = &tas5110c1b_init,
+ .qctrl = {
+ {
+ .id = V4L2_CID_GAIN,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "global gain",
+ .minimum = 0x00,
+ .maximum = 0xf6,
+ .step = 0x01,
+ .default_value = 0x40,
+ .flags = 0,
+ },
+ },
+ .set_ctrl = &tas5110c1b_set_ctrl,
+ .cropcap = {
+ .bounds = {
+ .left = 0,
+ .top = 0,
+ .width = 352,
+ .height = 288,
+ },
+ .defrect = {
+ .left = 0,
+ .top = 0,
+ .width = 352,
+ .height = 288,
+ },
+ },
+ .set_crop = &tas5110c1b_set_crop,
+ .pix_format = {
+ .width = 352,
+ .height = 288,
+ .pixelformat = V4L2_PIX_FMT_SBGGR8,
+ .priv = 8,
+ },
+ .set_pix_format = &tas5110c1b_set_pix_format
+};
+
+
+int sn9c102_probe_tas5110c1b(struct sn9c102_device *cam)
+{
+ const struct usb_device_id tas5110c1b_id_table[] = {
+ { USB_DEVICE(0x0c45, 0x6001), },
+ { USB_DEVICE(0x0c45, 0x6005), },
+ { USB_DEVICE(0x0c45, 0x60ab), },
+ { }
+ };
+
+ /* Sensor detection is based on USB pid/vid */
+ if (!sn9c102_match_id(cam, tas5110c1b_id_table))
+ return -ENODEV;
+
+ sn9c102_attach_sensor(cam, &tas5110c1b);
+
+ return 0;
+}
diff --git a/drivers/staging/media/sn9c102/sn9c102_tas5110d.c b/drivers/staging/media/sn9c102/sn9c102_tas5110d.c
new file mode 100644
index 00000000000..eefbf8670c3
--- /dev/null
+++ b/drivers/staging/media/sn9c102/sn9c102_tas5110d.c
@@ -0,0 +1,119 @@
+/***************************************************************************
+ * Plug-in for TAS5110D image sensor connected to the SN9C1xx PC Camera *
+ * Controllers *
+ * *
+ * Copyright (C) 2007 by Luca Risolia <luca.risolia@studio.unibo.it> *
+ * *
+ * 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., 675 Mass Ave, Cambridge, MA 02139, USA. *
+ ***************************************************************************/
+
+#include "sn9c102_sensor.h"
+#include "sn9c102_devtable.h"
+
+
+static int tas5110d_init(struct sn9c102_device *cam)
+{
+ int err;
+
+ err = sn9c102_write_const_regs(cam, {0x01, 0x01}, {0x04, 0x01},
+ {0x0a, 0x14}, {0x60, 0x17},
+ {0x06, 0x18}, {0xfb, 0x19});
+
+ err += sn9c102_i2c_write(cam, 0x9a, 0xca);
+
+ return err;
+}
+
+
+static int tas5110d_set_crop(struct sn9c102_device *cam,
+ const struct v4l2_rect *rect)
+{
+ struct sn9c102_sensor *s = sn9c102_get_sensor(cam);
+ int err = 0;
+ u8 h_start = (u8)(rect->left - s->cropcap.bounds.left) + 69,
+ v_start = (u8)(rect->top - s->cropcap.bounds.top) + 9;
+
+ err += sn9c102_write_reg(cam, h_start, 0x12);
+ err += sn9c102_write_reg(cam, v_start, 0x13);
+
+ err += sn9c102_write_reg(cam, 0x14, 0x1a);
+ err += sn9c102_write_reg(cam, 0x0a, 0x1b);
+
+ return err;
+}
+
+
+static int tas5110d_set_pix_format(struct sn9c102_device *cam,
+ const struct v4l2_pix_format *pix)
+{
+ int err = 0;
+
+ if (pix->pixelformat == V4L2_PIX_FMT_SN9C10X)
+ err += sn9c102_write_reg(cam, 0x3b, 0x19);
+ else
+ err += sn9c102_write_reg(cam, 0xfb, 0x19);
+
+ return err;
+}
+
+
+static const struct sn9c102_sensor tas5110d = {
+ .name = "TAS5110D",
+ .maintainer = "Luca Risolia <luca.risolia@studio.unibo.it>",
+ .supported_bridge = BRIDGE_SN9C101 | BRIDGE_SN9C102,
+ .sysfs_ops = SN9C102_I2C_WRITE,
+ .frequency = SN9C102_I2C_100KHZ,
+ .interface = SN9C102_I2C_2WIRES,
+ .i2c_slave_id = 0x61,
+ .init = &tas5110d_init,
+ .cropcap = {
+ .bounds = {
+ .left = 0,
+ .top = 0,
+ .width = 352,
+ .height = 288,
+ },
+ .defrect = {
+ .left = 0,
+ .top = 0,
+ .width = 352,
+ .height = 288,
+ },
+ },
+ .set_crop = &tas5110d_set_crop,
+ .pix_format = {
+ .width = 352,
+ .height = 288,
+ .pixelformat = V4L2_PIX_FMT_SBGGR8,
+ .priv = 8,
+ },
+ .set_pix_format = &tas5110d_set_pix_format
+};
+
+
+int sn9c102_probe_tas5110d(struct sn9c102_device *cam)
+{
+ const struct usb_device_id tas5110d_id_table[] = {
+ { USB_DEVICE(0x0c45, 0x6007), },
+ { }
+ };
+
+ if (!sn9c102_match_id(cam, tas5110d_id_table))
+ return -ENODEV;
+
+ sn9c102_attach_sensor(cam, &tas5110d);
+
+ return 0;
+}
diff --git a/drivers/staging/media/sn9c102/sn9c102_tas5130d1b.c b/drivers/staging/media/sn9c102/sn9c102_tas5130d1b.c
new file mode 100644
index 00000000000..725de857de4
--- /dev/null
+++ b/drivers/staging/media/sn9c102/sn9c102_tas5130d1b.c
@@ -0,0 +1,165 @@
+/***************************************************************************
+ * Plug-in for TAS5130D1B image sensor connected to the SN9C1xx PC Camera *
+ * Controllers *
+ * *
+ * Copyright (C) 2004-2007 by Luca Risolia <luca.risolia@studio.unibo.it> *
+ * *
+ * 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., 675 Mass Ave, Cambridge, MA 02139, USA. *
+ ***************************************************************************/
+
+#include "sn9c102_sensor.h"
+#include "sn9c102_devtable.h"
+
+
+static int tas5130d1b_init(struct sn9c102_device *cam)
+{
+ int err;
+
+ err = sn9c102_write_const_regs(cam, {0x01, 0x01}, {0x20, 0x17},
+ {0x04, 0x01}, {0x01, 0x10},
+ {0x00, 0x11}, {0x00, 0x14},
+ {0x60, 0x17}, {0x07, 0x18});
+
+ return err;
+}
+
+
+static int tas5130d1b_set_ctrl(struct sn9c102_device *cam,
+ const struct v4l2_control *ctrl)
+{
+ int err = 0;
+
+ switch (ctrl->id) {
+ case V4L2_CID_GAIN:
+ err += sn9c102_i2c_write(cam, 0x20, 0xf6 - ctrl->value);
+ break;
+ case V4L2_CID_EXPOSURE:
+ err += sn9c102_i2c_write(cam, 0x40, 0x47 - ctrl->value);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return err ? -EIO : 0;
+}
+
+
+static int tas5130d1b_set_crop(struct sn9c102_device *cam,
+ const struct v4l2_rect *rect)
+{
+ struct sn9c102_sensor *s = sn9c102_get_sensor(cam);
+ u8 h_start = (u8)(rect->left - s->cropcap.bounds.left) + 104,
+ v_start = (u8)(rect->top - s->cropcap.bounds.top) + 12;
+ int err = 0;
+
+ err += sn9c102_write_reg(cam, h_start, 0x12);
+ err += sn9c102_write_reg(cam, v_start, 0x13);
+
+ /* Do NOT change! */
+ err += sn9c102_write_reg(cam, 0x1f, 0x1a);
+ err += sn9c102_write_reg(cam, 0x1a, 0x1b);
+ err += sn9c102_write_reg(cam, sn9c102_pread_reg(cam, 0x19), 0x19);
+
+ return err;
+}
+
+
+static int tas5130d1b_set_pix_format(struct sn9c102_device *cam,
+ const struct v4l2_pix_format *pix)
+{
+ int err = 0;
+
+ if (pix->pixelformat == V4L2_PIX_FMT_SN9C10X)
+ err += sn9c102_write_reg(cam, 0x63, 0x19);
+ else
+ err += sn9c102_write_reg(cam, 0xf3, 0x19);
+
+ return err;
+}
+
+
+static const struct sn9c102_sensor tas5130d1b = {
+ .name = "TAS5130D1B",
+ .maintainer = "Luca Risolia <luca.risolia@studio.unibo.it>",
+ .supported_bridge = BRIDGE_SN9C101 | BRIDGE_SN9C102,
+ .sysfs_ops = SN9C102_I2C_WRITE,
+ .frequency = SN9C102_I2C_100KHZ,
+ .interface = SN9C102_I2C_3WIRES,
+ .init = &tas5130d1b_init,
+ .qctrl = {
+ {
+ .id = V4L2_CID_GAIN,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "global gain",
+ .minimum = 0x00,
+ .maximum = 0xf6,
+ .step = 0x02,
+ .default_value = 0x00,
+ .flags = 0,
+ },
+ {
+ .id = V4L2_CID_EXPOSURE,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "exposure",
+ .minimum = 0x00,
+ .maximum = 0x47,
+ .step = 0x01,
+ .default_value = 0x00,
+ .flags = 0,
+ },
+ },
+ .set_ctrl = &tas5130d1b_set_ctrl,
+ .cropcap = {
+ .bounds = {
+ .left = 0,
+ .top = 0,
+ .width = 640,
+ .height = 480,
+ },
+ .defrect = {
+ .left = 0,
+ .top = 0,
+ .width = 640,
+ .height = 480,
+ },
+ },
+ .set_crop = &tas5130d1b_set_crop,
+ .pix_format = {
+ .width = 640,
+ .height = 480,
+ .pixelformat = V4L2_PIX_FMT_SBGGR8,
+ .priv = 8,
+ },
+ .set_pix_format = &tas5130d1b_set_pix_format
+};
+
+
+int sn9c102_probe_tas5130d1b(struct sn9c102_device *cam)
+{
+ const struct usb_device_id tas5130d1b_id_table[] = {
+ { USB_DEVICE(0x0c45, 0x6024), },
+ { USB_DEVICE(0x0c45, 0x6025), },
+ { USB_DEVICE(0x0c45, 0x60aa), },
+ { }
+ };
+
+ /* Sensor detection is based on USB pid/vid */
+ if (!sn9c102_match_id(cam, tas5130d1b_id_table))
+ return -ENODEV;
+
+ sn9c102_attach_sensor(cam, &tas5130d1b);
+
+ return 0;
+}
diff --git a/drivers/staging/media/solo6x10/Kconfig b/drivers/staging/media/solo6x10/Kconfig
new file mode 100644
index 00000000000..6a1906fa111
--- /dev/null
+++ b/drivers/staging/media/solo6x10/Kconfig
@@ -0,0 +1,18 @@
+config SOLO6X10
+ tristate "Bluecherry / Softlogic 6x10 capture cards (MPEG-4/H.264)"
+ depends on PCI && VIDEO_DEV && SND && I2C
+ select FONT_SUPPORT
+ select FONT_8x16
+ select VIDEOBUF2_DMA_SG
+ select VIDEOBUF2_DMA_CONTIG
+ select SND_PCM
+ select FONT_8x16
+ ---help---
+ This driver supports the Bluecherry H.264 and MPEG-4 hardware
+ compression capture cards and other Softlogic-based ones.
+
+ Following cards have been tested:
+ * Bluecherry BC-H16480A (PCIe, 16 port, H.264)
+ * Bluecherry BC-H04120A (PCIe, 4 port, H.264)
+ * Bluecherry BC-H04120A-MPCI (Mini-PCI, 4 port, H.264)
+ * Bluecherry BC-04120A (PCIe, 4 port, MPEG-4)
diff --git a/drivers/staging/media/solo6x10/Makefile b/drivers/staging/media/solo6x10/Makefile
new file mode 100644
index 00000000000..7aae118947b
--- /dev/null
+++ b/drivers/staging/media/solo6x10/Makefile
@@ -0,0 +1,5 @@
+solo6x10-y := solo6x10-core.o solo6x10-i2c.o solo6x10-p2m.o solo6x10-v4l2.o \
+ solo6x10-tw28.o solo6x10-gpio.o solo6x10-disp.o solo6x10-enc.o \
+ solo6x10-v4l2-enc.o solo6x10-g723.o solo6x10-eeprom.o
+
+obj-$(CONFIG_SOLO6X10) += solo6x10.o
diff --git a/drivers/staging/media/solo6x10/TODO b/drivers/staging/media/solo6x10/TODO
new file mode 100644
index 00000000000..7b8db75b1ac
--- /dev/null
+++ b/drivers/staging/media/solo6x10/TODO
@@ -0,0 +1,15 @@
+- batch up desc requests for more efficient use of p2m?
+- encoder on/off controls
+- mpeg cid bitrate mode (vbr/cbr)
+- mpeg cid bitrate/bitrate-peak
+- mpeg encode of user data
+- mpeg decode of user data
+- implement CID controls for mozaic areas
+
+- sound
+ - implement playback via external sound jack
+ - implement loopback of external sound jack with incoming audio?
+ - implement pause/resume (make use of in bc-server)
+
+Please send patches to the linux media list <linux-media@vger.kernel.org> and
+Cc Ismael Luceno <ismael.luceno@corp.bluecherry.net>.
diff --git a/drivers/staging/media/solo6x10/solo6x10-core.c b/drivers/staging/media/solo6x10/solo6x10-core.c
new file mode 100644
index 00000000000..f67046955ef
--- /dev/null
+++ b/drivers/staging/media/solo6x10/solo6x10-core.c
@@ -0,0 +1,709 @@
+/*
+ * Copyright (C) 2010-2013 Bluecherry, LLC <http://www.bluecherrydvr.com>
+ *
+ * Original author:
+ * Ben Collins <bcollins@ubuntu.com>
+ *
+ * Additional work by:
+ * John Brooks <john.brooks@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 <linux/delay.h>
+#include <linux/sysfs.h>
+#include <linux/ktime.h>
+#include <linux/slab.h>
+
+#include "solo6x10.h"
+#include "solo6x10-tw28.h"
+
+MODULE_DESCRIPTION("Softlogic 6x10 MPEG4/H.264/G.723 CODEC V4L2/ALSA Driver");
+MODULE_AUTHOR("Bluecherry <maintainers@bluecherrydvr.com>");
+MODULE_VERSION(SOLO6X10_VERSION);
+MODULE_LICENSE("GPL");
+
+static unsigned video_nr = -1;
+module_param(video_nr, uint, 0644);
+MODULE_PARM_DESC(video_nr, "videoX start number, -1 is autodetect (default)");
+
+static int full_eeprom; /* default is only top 64B */
+module_param(full_eeprom, uint, 0644);
+MODULE_PARM_DESC(full_eeprom, "Allow access to full 128B EEPROM (dangerous)");
+
+
+static void solo_set_time(struct solo_dev *solo_dev)
+{
+ struct timespec ts;
+
+ ktime_get_ts(&ts);
+
+ solo_reg_write(solo_dev, SOLO_TIMER_SEC, ts.tv_sec);
+ solo_reg_write(solo_dev, SOLO_TIMER_USEC, ts.tv_nsec / NSEC_PER_USEC);
+}
+
+static void solo_timer_sync(struct solo_dev *solo_dev)
+{
+ u32 sec, usec;
+ struct timespec ts;
+ long diff;
+
+ if (solo_dev->type != SOLO_DEV_6110)
+ return;
+
+ if (++solo_dev->time_sync < 60)
+ return;
+
+ solo_dev->time_sync = 0;
+
+ sec = solo_reg_read(solo_dev, SOLO_TIMER_SEC);
+ usec = solo_reg_read(solo_dev, SOLO_TIMER_USEC);
+
+ ktime_get_ts(&ts);
+
+ diff = (long)ts.tv_sec - (long)sec;
+ diff = (diff * 1000000)
+ + ((long)(ts.tv_nsec / NSEC_PER_USEC) - (long)usec);
+
+ if (diff > 1000 || diff < -1000) {
+ solo_set_time(solo_dev);
+ } else if (diff) {
+ long usec_lsb = solo_dev->usec_lsb;
+
+ usec_lsb -= diff / 4;
+ if (usec_lsb < 0)
+ usec_lsb = 0;
+ else if (usec_lsb > 255)
+ usec_lsb = 255;
+
+ solo_dev->usec_lsb = usec_lsb;
+ solo_reg_write(solo_dev, SOLO_TIMER_USEC_LSB,
+ solo_dev->usec_lsb);
+ }
+}
+
+static irqreturn_t solo_isr(int irq, void *data)
+{
+ struct solo_dev *solo_dev = data;
+ u32 status;
+ int i;
+
+ status = solo_reg_read(solo_dev, SOLO_IRQ_STAT);
+ if (!status)
+ return IRQ_NONE;
+
+ 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)
+ solo_p2m_error_isr(solo_dev);
+
+ for (i = 0; i < SOLO_NR_P2M; i++)
+ if (status & SOLO_IRQ_P2M(i))
+ solo_p2m_isr(solo_dev, i);
+
+ if (status & SOLO_IRQ_IIC)
+ solo_i2c_isr(solo_dev);
+
+ if (status & SOLO_IRQ_VIDEO_IN) {
+ solo_video_in_isr(solo_dev);
+ solo_timer_sync(solo_dev);
+ }
+
+ if (status & SOLO_IRQ_ENCODER)
+ solo_enc_v4l2_isr(solo_dev);
+
+ if (status & SOLO_IRQ_G723)
+ solo_g723_isr(solo_dev);
+
+ /* Clear all interrupts handled */
+ solo_reg_write(solo_dev, SOLO_IRQ_STAT, status);
+
+ return IRQ_HANDLED;
+}
+
+static void free_solo_dev(struct solo_dev *solo_dev)
+{
+ struct pci_dev *pdev;
+
+ if (!solo_dev)
+ return;
+
+ if (solo_dev->dev.parent)
+ device_unregister(&solo_dev->dev);
+
+ pdev = solo_dev->pdev;
+
+ /* If we never initialized the PCI device, then nothing else
+ * below here needs cleanup */
+ if (!pdev) {
+ kfree(solo_dev);
+ return;
+ }
+
+ if (solo_dev->reg_base) {
+ /* Bring down the sub-devices first */
+ solo_g723_exit(solo_dev);
+ solo_enc_v4l2_exit(solo_dev);
+ solo_enc_exit(solo_dev);
+ solo_v4l2_exit(solo_dev);
+ solo_disp_exit(solo_dev);
+ solo_gpio_exit(solo_dev);
+ solo_p2m_exit(solo_dev);
+ solo_i2c_exit(solo_dev);
+
+ /* Now cleanup the PCI device */
+ solo_irq_off(solo_dev, ~0);
+ pci_iounmap(pdev, solo_dev->reg_base);
+ if (pdev->irq)
+ free_irq(pdev->irq, solo_dev);
+ }
+
+ pci_release_regions(pdev);
+ pci_disable_device(pdev);
+ v4l2_device_unregister(&solo_dev->v4l2_dev);
+ pci_set_drvdata(pdev, NULL);
+
+ kfree(solo_dev);
+}
+
+static ssize_t eeprom_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct solo_dev *solo_dev =
+ container_of(dev, struct solo_dev, dev);
+ unsigned short *p = (unsigned short *)buf;
+ int i;
+
+ if (count & 0x1)
+ dev_warn(dev, "EEPROM Write not aligned (truncating)\n");
+
+ if (!full_eeprom && count > 64) {
+ dev_warn(dev, "EEPROM Write truncated to 64 bytes\n");
+ count = 64;
+ } else if (full_eeprom && count > 128) {
+ dev_warn(dev, "EEPROM Write truncated to 128 bytes\n");
+ count = 128;
+ }
+
+ solo_eeprom_ewen(solo_dev, 1);
+
+ for (i = full_eeprom ? 0 : 32; i < min((int)(full_eeprom ? 64 : 32),
+ (int)(count / 2)); i++)
+ solo_eeprom_write(solo_dev, i, cpu_to_be16(p[i]));
+
+ solo_eeprom_ewen(solo_dev, 0);
+
+ return count;
+}
+
+static ssize_t eeprom_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct solo_dev *solo_dev =
+ container_of(dev, struct solo_dev, dev);
+ unsigned short *p = (unsigned short *)buf;
+ int count = (full_eeprom ? 128 : 64);
+ int i;
+
+ for (i = (full_eeprom ? 0 : 32); i < (count / 2); i++)
+ p[i] = be16_to_cpu(solo_eeprom_read(solo_dev, i));
+
+ return count;
+}
+
+static ssize_t p2m_timeouts_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct solo_dev *solo_dev =
+ container_of(dev, struct solo_dev, dev);
+
+ return sprintf(buf, "%d\n", solo_dev->p2m_timeouts);
+}
+
+static ssize_t sdram_size_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct solo_dev *solo_dev =
+ container_of(dev, struct solo_dev, dev);
+
+ return sprintf(buf, "%dMegs\n", solo_dev->sdram_size >> 20);
+}
+
+static ssize_t tw28xx_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct solo_dev *solo_dev =
+ container_of(dev, struct solo_dev, dev);
+
+ return sprintf(buf, "tw2815[%d] tw2864[%d] tw2865[%d]\n",
+ hweight32(solo_dev->tw2815),
+ hweight32(solo_dev->tw2864),
+ hweight32(solo_dev->tw2865));
+}
+
+static ssize_t input_map_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct solo_dev *solo_dev =
+ container_of(dev, struct solo_dev, dev);
+ unsigned int val;
+ char *out = buf;
+
+ val = solo_reg_read(solo_dev, SOLO_VI_CH_SWITCH_0);
+ out += sprintf(out, "Channel 0 => Input %d\n", val & 0x1f);
+ out += sprintf(out, "Channel 1 => Input %d\n", (val >> 5) & 0x1f);
+ out += sprintf(out, "Channel 2 => Input %d\n", (val >> 10) & 0x1f);
+ out += sprintf(out, "Channel 3 => Input %d\n", (val >> 15) & 0x1f);
+ out += sprintf(out, "Channel 4 => Input %d\n", (val >> 20) & 0x1f);
+ out += sprintf(out, "Channel 5 => Input %d\n", (val >> 25) & 0x1f);
+
+ val = solo_reg_read(solo_dev, SOLO_VI_CH_SWITCH_1);
+ out += sprintf(out, "Channel 6 => Input %d\n", val & 0x1f);
+ out += sprintf(out, "Channel 7 => Input %d\n", (val >> 5) & 0x1f);
+ out += sprintf(out, "Channel 8 => Input %d\n", (val >> 10) & 0x1f);
+ out += sprintf(out, "Channel 9 => Input %d\n", (val >> 15) & 0x1f);
+ out += sprintf(out, "Channel 10 => Input %d\n", (val >> 20) & 0x1f);
+ out += sprintf(out, "Channel 11 => Input %d\n", (val >> 25) & 0x1f);
+
+ val = solo_reg_read(solo_dev, SOLO_VI_CH_SWITCH_2);
+ out += sprintf(out, "Channel 12 => Input %d\n", val & 0x1f);
+ out += sprintf(out, "Channel 13 => Input %d\n", (val >> 5) & 0x1f);
+ out += sprintf(out, "Channel 14 => Input %d\n", (val >> 10) & 0x1f);
+ out += sprintf(out, "Channel 15 => Input %d\n", (val >> 15) & 0x1f);
+ out += sprintf(out, "Spot Output => Input %d\n", (val >> 20) & 0x1f);
+
+ return out - buf;
+}
+
+static ssize_t p2m_timeout_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct solo_dev *solo_dev =
+ container_of(dev, struct solo_dev, dev);
+ unsigned long ms;
+
+ int ret = kstrtoul(buf, 10, &ms);
+ if (ret < 0 || ms > 200)
+ return -EINVAL;
+ solo_dev->p2m_jiffies = msecs_to_jiffies(ms);
+
+ return count;
+}
+
+static ssize_t p2m_timeout_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct solo_dev *solo_dev =
+ container_of(dev, struct solo_dev, dev);
+
+ return sprintf(buf, "%ums\n", jiffies_to_msecs(solo_dev->p2m_jiffies));
+}
+
+static ssize_t intervals_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct solo_dev *solo_dev =
+ container_of(dev, struct solo_dev, dev);
+ char *out = buf;
+ int fps = solo_dev->fps;
+ int i;
+
+ for (i = 0; i < solo_dev->nr_chans; i++) {
+ out += sprintf(out, "Channel %d: %d/%d (0x%08x)\n",
+ i, solo_dev->v4l2_enc[i]->interval, fps,
+ solo_reg_read(solo_dev, SOLO_CAP_CH_INTV(i)));
+ }
+
+ return out - buf;
+}
+
+static ssize_t sdram_offsets_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct solo_dev *solo_dev =
+ container_of(dev, struct solo_dev, dev);
+ char *out = buf;
+
+ out += sprintf(out, "DISP: 0x%08x @ 0x%08x\n",
+ SOLO_DISP_EXT_ADDR,
+ SOLO_DISP_EXT_SIZE);
+
+ out += sprintf(out, "EOSD: 0x%08x @ 0x%08x (0x%08x * %d)\n",
+ SOLO_EOSD_EXT_ADDR,
+ SOLO_EOSD_EXT_AREA(solo_dev),
+ SOLO_EOSD_EXT_SIZE(solo_dev),
+ SOLO_EOSD_EXT_AREA(solo_dev) /
+ SOLO_EOSD_EXT_SIZE(solo_dev));
+
+ out += sprintf(out, "MOTI: 0x%08x @ 0x%08x\n",
+ SOLO_MOTION_EXT_ADDR(solo_dev),
+ SOLO_MOTION_EXT_SIZE);
+
+ out += sprintf(out, "G723: 0x%08x @ 0x%08x\n",
+ SOLO_G723_EXT_ADDR(solo_dev),
+ SOLO_G723_EXT_SIZE);
+
+ out += sprintf(out, "CAPT: 0x%08x @ 0x%08x (0x%08x * %d)\n",
+ SOLO_CAP_EXT_ADDR(solo_dev),
+ SOLO_CAP_EXT_SIZE(solo_dev),
+ SOLO_CAP_PAGE_SIZE,
+ SOLO_CAP_EXT_SIZE(solo_dev) / SOLO_CAP_PAGE_SIZE);
+
+ out += sprintf(out, "EREF: 0x%08x @ 0x%08x (0x%08x * %d)\n",
+ SOLO_EREF_EXT_ADDR(solo_dev),
+ SOLO_EREF_EXT_AREA(solo_dev),
+ SOLO_EREF_EXT_SIZE,
+ SOLO_EREF_EXT_AREA(solo_dev) / SOLO_EREF_EXT_SIZE);
+
+ out += sprintf(out, "MPEG: 0x%08x @ 0x%08x\n",
+ SOLO_MP4E_EXT_ADDR(solo_dev),
+ SOLO_MP4E_EXT_SIZE(solo_dev));
+
+ out += sprintf(out, "JPEG: 0x%08x @ 0x%08x\n",
+ SOLO_JPEG_EXT_ADDR(solo_dev),
+ SOLO_JPEG_EXT_SIZE(solo_dev));
+
+ return out - buf;
+}
+
+static ssize_t sdram_show(struct file *file, struct kobject *kobj,
+ struct bin_attribute *a, char *buf,
+ loff_t off, size_t count)
+{
+ struct device *dev = container_of(kobj, struct device, kobj);
+ struct solo_dev *solo_dev =
+ container_of(dev, struct solo_dev, dev);
+ const int size = solo_dev->sdram_size;
+
+ if (off >= size)
+ return 0;
+
+ if (off + count > size)
+ count = size - off;
+
+ if (solo_p2m_dma(solo_dev, 0, buf, off, count, 0, 0))
+ return -EIO;
+
+ return count;
+}
+
+static const struct device_attribute solo_dev_attrs[] = {
+ __ATTR(eeprom, 0640, eeprom_show, eeprom_store),
+ __ATTR(p2m_timeout, 0644, p2m_timeout_show, p2m_timeout_store),
+ __ATTR_RO(p2m_timeouts),
+ __ATTR_RO(sdram_size),
+ __ATTR_RO(tw28xx),
+ __ATTR_RO(input_map),
+ __ATTR_RO(intervals),
+ __ATTR_RO(sdram_offsets),
+};
+
+static void solo_device_release(struct device *dev)
+{
+ /* Do nothing */
+}
+
+static int solo_sysfs_init(struct solo_dev *solo_dev)
+{
+ struct bin_attribute *sdram_attr = &solo_dev->sdram_attr;
+ struct device *dev = &solo_dev->dev;
+ const char *driver;
+ int i;
+
+ if (solo_dev->type == SOLO_DEV_6110)
+ driver = "solo6110";
+ else
+ driver = "solo6010";
+
+ dev->release = solo_device_release;
+ dev->parent = &solo_dev->pdev->dev;
+ set_dev_node(dev, dev_to_node(&solo_dev->pdev->dev));
+ dev_set_name(dev, "%s-%d-%d", driver, solo_dev->vfd->num,
+ solo_dev->nr_chans);
+
+ if (device_register(dev)) {
+ dev->parent = NULL;
+ return -ENOMEM;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(solo_dev_attrs); i++) {
+ if (device_create_file(dev, &solo_dev_attrs[i])) {
+ device_unregister(dev);
+ return -ENOMEM;
+ }
+ }
+
+ sysfs_attr_init(&sdram_attr->attr);
+ sdram_attr->attr.name = "sdram";
+ sdram_attr->attr.mode = 0440;
+ sdram_attr->read = sdram_show;
+ sdram_attr->size = solo_dev->sdram_size;
+
+ if (device_create_bin_file(dev, sdram_attr)) {
+ device_unregister(dev);
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+
+static int solo_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
+{
+ struct solo_dev *solo_dev;
+ int ret;
+ u8 chip_id;
+
+ solo_dev = kzalloc(sizeof(*solo_dev), GFP_KERNEL);
+ if (solo_dev == NULL)
+ return -ENOMEM;
+
+ if (id->driver_data == SOLO_DEV_6010)
+ dev_info(&pdev->dev, "Probing Softlogic 6010\n");
+ else
+ dev_info(&pdev->dev, "Probing Softlogic 6110\n");
+
+ solo_dev->type = id->driver_data;
+ solo_dev->pdev = pdev;
+ spin_lock_init(&solo_dev->reg_io_lock);
+ ret = v4l2_device_register(&pdev->dev, &solo_dev->v4l2_dev);
+ if (ret)
+ goto fail_probe;
+
+ /* Only for during init */
+ solo_dev->p2m_jiffies = msecs_to_jiffies(100);
+
+ ret = pci_enable_device(pdev);
+ if (ret)
+ goto fail_probe;
+
+ pci_set_master(pdev);
+
+ /* RETRY/TRDY Timeout disabled */
+ pci_write_config_byte(pdev, 0x40, 0x00);
+ pci_write_config_byte(pdev, 0x41, 0x00);
+
+ ret = pci_request_regions(pdev, SOLO6X10_NAME);
+ if (ret)
+ goto fail_probe;
+
+ solo_dev->reg_base = pci_ioremap_bar(pdev, 0);
+ if (solo_dev->reg_base == NULL) {
+ ret = -ENOMEM;
+ goto fail_probe;
+ }
+
+ chip_id = solo_reg_read(solo_dev, SOLO_CHIP_OPTION) &
+ SOLO_CHIP_ID_MASK;
+ switch (chip_id) {
+ case 7:
+ solo_dev->nr_chans = 16;
+ solo_dev->nr_ext = 5;
+ break;
+ case 6:
+ solo_dev->nr_chans = 8;
+ solo_dev->nr_ext = 2;
+ break;
+ default:
+ dev_warn(&pdev->dev, "Invalid chip_id 0x%02x, assuming 4 ch\n",
+ chip_id);
+ case 5:
+ solo_dev->nr_chans = 4;
+ solo_dev->nr_ext = 1;
+ }
+
+ /* Disable all interrupts to start */
+ solo_irq_off(solo_dev, ~0);
+
+ /* Initial global settings */
+ if (solo_dev->type == SOLO_DEV_6010) {
+ solo_dev->clock_mhz = 108;
+ solo_dev->sys_config = SOLO_SYS_CFG_SDRAM64BIT
+ | SOLO_SYS_CFG_INPUTDIV(25)
+ | SOLO_SYS_CFG_FEEDBACKDIV(solo_dev->clock_mhz * 2 - 2)
+ | SOLO_SYS_CFG_OUTDIV(3);
+ solo_reg_write(solo_dev, SOLO_SYS_CFG, solo_dev->sys_config);
+ } else {
+ u32 divq, divf;
+
+ solo_dev->clock_mhz = 135;
+
+ if (solo_dev->clock_mhz < 125) {
+ divq = 3;
+ divf = (solo_dev->clock_mhz * 4) / 3 - 1;
+ } else {
+ divq = 2;
+ divf = (solo_dev->clock_mhz * 2) / 3 - 1;
+ }
+
+ solo_reg_write(solo_dev, SOLO_PLL_CONFIG,
+ (1 << 20) | /* PLL_RANGE */
+ (8 << 15) | /* PLL_DIVR */
+ (divq << 12) |
+ (divf << 4) |
+ (1 << 1) /* PLL_FSEN */);
+
+ solo_dev->sys_config = SOLO_SYS_CFG_SDRAM64BIT;
+ }
+
+ solo_reg_write(solo_dev, SOLO_SYS_CFG, solo_dev->sys_config);
+ solo_reg_write(solo_dev, SOLO_TIMER_CLOCK_NUM,
+ solo_dev->clock_mhz - 1);
+
+ /* PLL locking time of 1ms */
+ mdelay(1);
+
+ ret = request_irq(pdev->irq, solo_isr, IRQF_SHARED, SOLO6X10_NAME,
+ solo_dev);
+ if (ret)
+ goto fail_probe;
+
+ /* Handle this from the start */
+ solo_irq_on(solo_dev, SOLO_IRQ_PCI_ERR);
+
+ ret = solo_i2c_init(solo_dev);
+ if (ret)
+ goto fail_probe;
+
+ /* Setup the DMA engine */
+ solo_reg_write(solo_dev, SOLO_DMA_CTRL,
+ SOLO_DMA_CTRL_REFRESH_CYCLE(1) |
+ SOLO_DMA_CTRL_SDRAM_SIZE(2) |
+ SOLO_DMA_CTRL_SDRAM_CLK_INVERT |
+ SOLO_DMA_CTRL_READ_CLK_SELECT |
+ SOLO_DMA_CTRL_LATENCY(1));
+
+ /* Undocumented crap */
+ solo_reg_write(solo_dev, SOLO_DMA_CTRL1,
+ solo_dev->type == SOLO_DEV_6010 ? 0x100 : 0x300);
+
+ if (solo_dev->type != SOLO_DEV_6010) {
+ solo_dev->usec_lsb = 0x3f;
+ solo_set_time(solo_dev);
+ }
+
+ /* Disable watchdog */
+ solo_reg_write(solo_dev, SOLO_WATCHDOG, 0);
+
+ /* Initialize sub components */
+
+ ret = solo_p2m_init(solo_dev);
+ if (ret)
+ goto fail_probe;
+
+ ret = solo_disp_init(solo_dev);
+ if (ret)
+ goto fail_probe;
+
+ ret = solo_gpio_init(solo_dev);
+ if (ret)
+ goto fail_probe;
+
+ ret = solo_tw28_init(solo_dev);
+ if (ret)
+ goto fail_probe;
+
+ ret = solo_v4l2_init(solo_dev, video_nr);
+ if (ret)
+ goto fail_probe;
+
+ ret = solo_enc_init(solo_dev);
+ if (ret)
+ goto fail_probe;
+
+ ret = solo_enc_v4l2_init(solo_dev, video_nr);
+ if (ret)
+ goto fail_probe;
+
+ ret = solo_g723_init(solo_dev);
+ if (ret)
+ goto fail_probe;
+
+ ret = solo_sysfs_init(solo_dev);
+ if (ret)
+ goto fail_probe;
+
+ /* Now that init is over, set this lower */
+ solo_dev->p2m_jiffies = msecs_to_jiffies(20);
+
+ return 0;
+
+fail_probe:
+ free_solo_dev(solo_dev);
+ return ret;
+}
+
+static void solo_pci_remove(struct pci_dev *pdev)
+{
+ struct v4l2_device *v4l2_dev = pci_get_drvdata(pdev);
+ struct solo_dev *solo_dev = container_of(v4l2_dev, struct solo_dev, v4l2_dev);
+
+ free_solo_dev(solo_dev);
+}
+
+static const struct pci_device_id solo_id_table[] = {
+ /* 6010 based cards */
+ { PCI_DEVICE(PCI_VENDOR_ID_SOFTLOGIC, PCI_DEVICE_ID_SOLO6010),
+ .driver_data = SOLO_DEV_6010 },
+ { PCI_DEVICE(PCI_VENDOR_ID_BLUECHERRY, PCI_DEVICE_ID_NEUSOLO_4),
+ .driver_data = SOLO_DEV_6010 },
+ { PCI_DEVICE(PCI_VENDOR_ID_BLUECHERRY, PCI_DEVICE_ID_NEUSOLO_9),
+ .driver_data = SOLO_DEV_6010 },
+ { PCI_DEVICE(PCI_VENDOR_ID_BLUECHERRY, PCI_DEVICE_ID_NEUSOLO_16),
+ .driver_data = SOLO_DEV_6010 },
+ { PCI_DEVICE(PCI_VENDOR_ID_BLUECHERRY, PCI_DEVICE_ID_BC_SOLO_4),
+ .driver_data = SOLO_DEV_6010 },
+ { PCI_DEVICE(PCI_VENDOR_ID_BLUECHERRY, PCI_DEVICE_ID_BC_SOLO_9),
+ .driver_data = SOLO_DEV_6010 },
+ { PCI_DEVICE(PCI_VENDOR_ID_BLUECHERRY, PCI_DEVICE_ID_BC_SOLO_16),
+ .driver_data = SOLO_DEV_6010 },
+ /* 6110 based cards */
+ { PCI_DEVICE(PCI_VENDOR_ID_SOFTLOGIC, PCI_DEVICE_ID_SOLO6110),
+ .driver_data = SOLO_DEV_6110 },
+ { PCI_DEVICE(PCI_VENDOR_ID_BLUECHERRY, PCI_DEVICE_ID_BC_6110_4),
+ .driver_data = SOLO_DEV_6110 },
+ { PCI_DEVICE(PCI_VENDOR_ID_BLUECHERRY, PCI_DEVICE_ID_BC_6110_8),
+ .driver_data = SOLO_DEV_6110 },
+ { PCI_DEVICE(PCI_VENDOR_ID_BLUECHERRY, PCI_DEVICE_ID_BC_6110_16),
+ .driver_data = SOLO_DEV_6110 },
+ {0,}
+};
+
+MODULE_DEVICE_TABLE(pci, solo_id_table);
+
+static struct pci_driver solo_pci_driver = {
+ .name = SOLO6X10_NAME,
+ .id_table = solo_id_table,
+ .probe = solo_pci_probe,
+ .remove = solo_pci_remove,
+};
+
+module_pci_driver(solo_pci_driver);
diff --git a/drivers/staging/media/solo6x10/solo6x10-disp.c b/drivers/staging/media/solo6x10/solo6x10-disp.c
new file mode 100644
index 00000000000..145295a5db7
--- /dev/null
+++ b/drivers/staging/media/solo6x10/solo6x10-disp.c
@@ -0,0 +1,322 @@
+/*
+ * Copyright (C) 2010-2013 Bluecherry, LLC <http://www.bluecherrydvr.com>
+ *
+ * Original author:
+ * Ben Collins <bcollins@ubuntu.com>
+ *
+ * Additional work by:
+ * John Brooks <john.brooks@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 "solo6x10.h"
+
+#define SOLO_VCLK_DELAY 3
+#define SOLO_PROGRESSIVE_VSIZE 1024
+
+#define SOLO_MOT_THRESH_W 64
+#define SOLO_MOT_THRESH_H 64
+#define SOLO_MOT_THRESH_SIZE 8192
+#define SOLO_MOT_THRESH_REAL (SOLO_MOT_THRESH_W * SOLO_MOT_THRESH_H)
+#define SOLO_MOT_FLAG_SIZE 1024
+#define SOLO_MOT_FLAG_AREA (SOLO_MOT_FLAG_SIZE * 16)
+
+static void solo_vin_config(struct solo_dev *solo_dev)
+{
+ solo_dev->vin_hstart = 8;
+ solo_dev->vin_vstart = 2;
+
+ solo_reg_write(solo_dev, SOLO_SYS_VCLK,
+ SOLO_VCLK_SELECT(2) |
+ SOLO_VCLK_VIN1415_DELAY(SOLO_VCLK_DELAY) |
+ SOLO_VCLK_VIN1213_DELAY(SOLO_VCLK_DELAY) |
+ SOLO_VCLK_VIN1011_DELAY(SOLO_VCLK_DELAY) |
+ SOLO_VCLK_VIN0809_DELAY(SOLO_VCLK_DELAY) |
+ SOLO_VCLK_VIN0607_DELAY(SOLO_VCLK_DELAY) |
+ SOLO_VCLK_VIN0405_DELAY(SOLO_VCLK_DELAY) |
+ SOLO_VCLK_VIN0203_DELAY(SOLO_VCLK_DELAY) |
+ SOLO_VCLK_VIN0001_DELAY(SOLO_VCLK_DELAY));
+
+ solo_reg_write(solo_dev, SOLO_VI_ACT_I_P,
+ SOLO_VI_H_START(solo_dev->vin_hstart) |
+ SOLO_VI_V_START(solo_dev->vin_vstart) |
+ SOLO_VI_V_STOP(solo_dev->vin_vstart +
+ solo_dev->video_vsize));
+
+ solo_reg_write(solo_dev, SOLO_VI_ACT_I_S,
+ SOLO_VI_H_START(solo_dev->vout_hstart) |
+ SOLO_VI_V_START(solo_dev->vout_vstart) |
+ SOLO_VI_V_STOP(solo_dev->vout_vstart +
+ solo_dev->video_vsize));
+
+ solo_reg_write(solo_dev, SOLO_VI_ACT_P,
+ SOLO_VI_H_START(0) |
+ SOLO_VI_V_START(1) |
+ SOLO_VI_V_STOP(SOLO_PROGRESSIVE_VSIZE));
+
+ solo_reg_write(solo_dev, SOLO_VI_CH_FORMAT,
+ SOLO_VI_FD_SEL_MASK(0) | SOLO_VI_PROG_MASK(0));
+
+ /* On 6110, initialize mozaic darkness stength */
+ if (solo_dev->type == SOLO_DEV_6010)
+ solo_reg_write(solo_dev, SOLO_VI_FMT_CFG, 0);
+ else
+ solo_reg_write(solo_dev, SOLO_VI_FMT_CFG, 16 << 22);
+
+ solo_reg_write(solo_dev, SOLO_VI_PAGE_SW, 2);
+
+ if (solo_dev->video_type == SOLO_VO_FMT_TYPE_NTSC) {
+ solo_reg_write(solo_dev, SOLO_VI_PB_CONFIG,
+ SOLO_VI_PB_USER_MODE);
+ solo_reg_write(solo_dev, SOLO_VI_PB_RANGE_HV,
+ SOLO_VI_PB_HSIZE(858) | SOLO_VI_PB_VSIZE(246));
+ solo_reg_write(solo_dev, SOLO_VI_PB_ACT_V,
+ SOLO_VI_PB_VSTART(4) |
+ SOLO_VI_PB_VSTOP(4 + 240));
+ } else {
+ solo_reg_write(solo_dev, SOLO_VI_PB_CONFIG,
+ SOLO_VI_PB_USER_MODE | SOLO_VI_PB_PAL);
+ solo_reg_write(solo_dev, SOLO_VI_PB_RANGE_HV,
+ SOLO_VI_PB_HSIZE(864) | SOLO_VI_PB_VSIZE(294));
+ solo_reg_write(solo_dev, SOLO_VI_PB_ACT_V,
+ SOLO_VI_PB_VSTART(4) |
+ SOLO_VI_PB_VSTOP(4 + 288));
+ }
+ solo_reg_write(solo_dev, SOLO_VI_PB_ACT_H, SOLO_VI_PB_HSTART(16) |
+ SOLO_VI_PB_HSTOP(16 + 720));
+}
+
+static void solo_vout_config_cursor(struct solo_dev *dev)
+{
+ int i;
+
+ /* Load (blank) cursor bitmap mask (2bpp) */
+ for (i = 0; i < 20; i++)
+ solo_reg_write(dev, SOLO_VO_CURSOR_MASK(i), 0);
+
+ solo_reg_write(dev, SOLO_VO_CURSOR_POS, 0);
+
+ solo_reg_write(dev, SOLO_VO_CURSOR_CLR,
+ (0x80 << 24) | (0x80 << 16) | (0x10 << 8) | 0x80);
+ solo_reg_write(dev, SOLO_VO_CURSOR_CLR2, (0xe0 << 8) | 0x80);
+}
+
+static void solo_vout_config(struct solo_dev *solo_dev)
+{
+ solo_dev->vout_hstart = 6;
+ solo_dev->vout_vstart = 8;
+
+ solo_reg_write(solo_dev, SOLO_VO_FMT_ENC,
+ solo_dev->video_type |
+ SOLO_VO_USER_COLOR_SET_NAV |
+ SOLO_VO_USER_COLOR_SET_NAH |
+ SOLO_VO_NA_COLOR_Y(0) |
+ SOLO_VO_NA_COLOR_CB(0) |
+ SOLO_VO_NA_COLOR_CR(0));
+
+ solo_reg_write(solo_dev, SOLO_VO_ACT_H,
+ SOLO_VO_H_START(solo_dev->vout_hstart) |
+ SOLO_VO_H_STOP(solo_dev->vout_hstart +
+ solo_dev->video_hsize));
+
+ solo_reg_write(solo_dev, SOLO_VO_ACT_V,
+ SOLO_VO_V_START(solo_dev->vout_vstart) |
+ SOLO_VO_V_STOP(solo_dev->vout_vstart +
+ solo_dev->video_vsize));
+
+ solo_reg_write(solo_dev, SOLO_VO_RANGE_HV,
+ SOLO_VO_H_LEN(solo_dev->video_hsize) |
+ SOLO_VO_V_LEN(solo_dev->video_vsize));
+
+ /* Border & background colors */
+ solo_reg_write(solo_dev, SOLO_VO_BORDER_LINE_COLOR,
+ (0xa0 << 24) | (0x88 << 16) | (0xa0 << 8) | 0x88);
+ solo_reg_write(solo_dev, SOLO_VO_BORDER_FILL_COLOR,
+ (0x10 << 24) | (0x8f << 16) | (0x10 << 8) | 0x8f);
+ solo_reg_write(solo_dev, SOLO_VO_BKG_COLOR,
+ (16 << 24) | (128 << 16) | (16 << 8) | 128);
+
+ solo_reg_write(solo_dev, SOLO_VO_DISP_ERASE, SOLO_VO_DISP_ERASE_ON);
+
+ solo_reg_write(solo_dev, SOLO_VI_WIN_SW, 0);
+
+ solo_reg_write(solo_dev, SOLO_VO_ZOOM_CTRL, 0);
+ solo_reg_write(solo_dev, SOLO_VO_FREEZE_CTRL, 0);
+
+ solo_reg_write(solo_dev, SOLO_VO_DISP_CTRL, SOLO_VO_DISP_ON |
+ SOLO_VO_DISP_ERASE_COUNT(8) |
+ SOLO_VO_DISP_BASE(SOLO_DISP_EXT_ADDR));
+
+
+ solo_vout_config_cursor(solo_dev);
+
+ /* Enable channels we support */
+ solo_reg_write(solo_dev, SOLO_VI_CH_ENA,
+ (1 << solo_dev->nr_chans) - 1);
+}
+
+static int solo_dma_vin_region(struct solo_dev *solo_dev, u32 off,
+ u16 val, int reg_size)
+{
+ u16 *buf;
+ const int n = 64, size = n * sizeof(*buf);
+ int i, ret = 0;
+
+ buf = kmalloc(size, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ for (i = 0; i < n; i++)
+ buf[i] = cpu_to_le16(val);
+
+ for (i = 0; i < reg_size; i += size) {
+ ret = solo_p2m_dma(solo_dev, 1, buf,
+ SOLO_MOTION_EXT_ADDR(solo_dev) + off + i,
+ size, 0, 0);
+
+ if (ret)
+ break;
+ }
+
+ kfree(buf);
+ return ret;
+}
+
+int solo_set_motion_threshold(struct solo_dev *solo_dev, u8 ch, u16 val)
+{
+ if (ch > solo_dev->nr_chans)
+ return -EINVAL;
+
+ return solo_dma_vin_region(solo_dev, SOLO_MOT_FLAG_AREA +
+ (ch * SOLO_MOT_THRESH_SIZE * 2),
+ val, SOLO_MOT_THRESH_SIZE);
+}
+
+int solo_set_motion_block(struct solo_dev *solo_dev, u8 ch,
+ const struct solo_motion_thresholds *thresholds)
+{
+ u32 off = SOLO_MOT_FLAG_AREA + ch * SOLO_MOT_THRESH_SIZE * 2;
+ u16 buf[64];
+ int x, y;
+ int ret = 0;
+
+ memset(buf, 0, sizeof(buf));
+ for (y = 0; y < SOLO_MOTION_SZ; y++) {
+ for (x = 0; x < SOLO_MOTION_SZ; x++)
+ buf[x] = cpu_to_le16(thresholds->thresholds[y][x]);
+ ret |= solo_p2m_dma(solo_dev, 1, buf,
+ SOLO_MOTION_EXT_ADDR(solo_dev) + off + y * sizeof(buf),
+ sizeof(buf), 0, 0);
+ }
+ return ret;
+}
+
+/* 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 (taken from rdk) has some mystery 8k
+ * block right after the flag area, before the first thresh table. */
+static void solo_motion_config(struct solo_dev *solo_dev)
+{
+ int i;
+
+ for (i = 0; i < solo_dev->nr_chans; i++) {
+ /* Clear motion flag area */
+ solo_dma_vin_region(solo_dev, i * SOLO_MOT_FLAG_SIZE, 0x0000,
+ SOLO_MOT_FLAG_SIZE);
+
+ /* Clear working cache table */
+ solo_dma_vin_region(solo_dev, SOLO_MOT_FLAG_AREA +
+ (i * SOLO_MOT_THRESH_SIZE * 2) +
+ SOLO_MOT_THRESH_SIZE, 0x0000,
+ SOLO_MOT_THRESH_SIZE);
+
+ /* Set default threshold table */
+ solo_set_motion_threshold(solo_dev, i, SOLO_DEF_MOT_THRESH);
+ }
+
+ /* Default motion settings */
+ solo_reg_write(solo_dev, SOLO_VI_MOT_ADR, SOLO_VI_MOTION_EN(0) |
+ (SOLO_MOTION_EXT_ADDR(solo_dev) >> 16));
+ solo_reg_write(solo_dev, SOLO_VI_MOT_CTRL,
+ SOLO_VI_MOTION_FRAME_COUNT(3) |
+ SOLO_VI_MOTION_SAMPLE_LENGTH(solo_dev->video_hsize / 16)
+ /* | SOLO_VI_MOTION_INTR_START_STOP */
+ | SOLO_VI_MOTION_SAMPLE_COUNT(10));
+
+ solo_reg_write(solo_dev, SOLO_VI_MOTION_BORDER, 0);
+ solo_reg_write(solo_dev, SOLO_VI_MOTION_BAR, 0);
+}
+
+int solo_disp_init(struct solo_dev *solo_dev)
+{
+ int i;
+
+ solo_dev->video_hsize = 704;
+ if (solo_dev->video_type == SOLO_VO_FMT_TYPE_NTSC) {
+ solo_dev->video_vsize = 240;
+ solo_dev->fps = 30;
+ } else {
+ solo_dev->video_vsize = 288;
+ solo_dev->fps = 25;
+ }
+
+ solo_vin_config(solo_dev);
+ solo_motion_config(solo_dev);
+ solo_vout_config(solo_dev);
+
+ for (i = 0; i < solo_dev->nr_chans; i++)
+ solo_reg_write(solo_dev, SOLO_VI_WIN_ON(i), 1);
+
+ return 0;
+}
+
+void solo_disp_exit(struct solo_dev *solo_dev)
+{
+ int i;
+
+ solo_reg_write(solo_dev, SOLO_VO_DISP_CTRL, 0);
+ solo_reg_write(solo_dev, SOLO_VO_ZOOM_CTRL, 0);
+ solo_reg_write(solo_dev, SOLO_VO_FREEZE_CTRL, 0);
+
+ for (i = 0; i < solo_dev->nr_chans; i++) {
+ solo_reg_write(solo_dev, SOLO_VI_WIN_CTRL0(i), 0);
+ solo_reg_write(solo_dev, SOLO_VI_WIN_CTRL1(i), 0);
+ solo_reg_write(solo_dev, SOLO_VI_WIN_ON(i), 0);
+ }
+
+ /* Set default border */
+ for (i = 0; i < 5; i++)
+ solo_reg_write(solo_dev, SOLO_VO_BORDER_X(i), 0);
+
+ for (i = 0; i < 5; i++)
+ solo_reg_write(solo_dev, SOLO_VO_BORDER_Y(i), 0);
+
+ solo_reg_write(solo_dev, SOLO_VO_BORDER_LINE_MASK, 0);
+ solo_reg_write(solo_dev, SOLO_VO_BORDER_FILL_MASK, 0);
+
+ solo_reg_write(solo_dev, SOLO_VO_RECTANGLE_CTRL(0), 0);
+ solo_reg_write(solo_dev, SOLO_VO_RECTANGLE_START(0), 0);
+ solo_reg_write(solo_dev, SOLO_VO_RECTANGLE_STOP(0), 0);
+
+ solo_reg_write(solo_dev, SOLO_VO_RECTANGLE_CTRL(1), 0);
+ solo_reg_write(solo_dev, SOLO_VO_RECTANGLE_START(1), 0);
+ solo_reg_write(solo_dev, SOLO_VO_RECTANGLE_STOP(1), 0);
+}
diff --git a/drivers/staging/media/solo6x10/solo6x10-eeprom.c b/drivers/staging/media/solo6x10/solo6x10-eeprom.c
new file mode 100644
index 00000000000..9d1c9bb53d6
--- /dev/null
+++ b/drivers/staging/media/solo6x10/solo6x10-eeprom.c
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2010-2013 Bluecherry, LLC <http://www.bluecherrydvr.com>
+ *
+ * Original author:
+ * Ben Collins <bcollins@ubuntu.com>
+ *
+ * Additional work by:
+ * John Brooks <john.brooks@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/delay.h>
+
+#include "solo6x10.h"
+
+/* Control */
+#define EE_SHIFT_CLK 0x04
+#define EE_CS 0x08
+#define EE_DATA_WRITE 0x02
+#define EE_DATA_READ 0x01
+#define EE_ENB (0x80 | EE_CS)
+
+#define eeprom_delay() udelay(100)
+#if 0
+#define eeprom_delay() solo_reg_read(solo_dev, SOLO_EEPROM_CTRL)
+#define eeprom_delay() ({ \
+ int i, ret; \
+ udelay(100); \
+ for (i = ret = 0; i < 1000 && !ret; i++) \
+ ret = solo_eeprom_reg_read(solo_dev); \
+})
+#endif
+#define ADDR_LEN 6
+
+/* Commands */
+#define EE_EWEN_CMD 4
+#define EE_EWDS_CMD 4
+#define EE_WRITE_CMD 5
+#define EE_READ_CMD 6
+#define EE_ERASE_CMD 7
+
+static unsigned int solo_eeprom_reg_read(struct solo_dev *solo_dev)
+{
+ return solo_reg_read(solo_dev, SOLO_EEPROM_CTRL) & EE_DATA_READ;
+}
+
+static void solo_eeprom_reg_write(struct solo_dev *solo_dev, u32 data)
+{
+ solo_reg_write(solo_dev, SOLO_EEPROM_CTRL, data);
+ eeprom_delay();
+}
+
+static void solo_eeprom_cmd(struct solo_dev *solo_dev, int cmd)
+{
+ int i;
+
+ solo_eeprom_reg_write(solo_dev, SOLO_EEPROM_ACCESS_EN);
+ solo_eeprom_reg_write(solo_dev, SOLO_EEPROM_ENABLE);
+
+ for (i = 4 + ADDR_LEN; i >= 0; i--) {
+ int dataval = (cmd & (1 << i)) ? EE_DATA_WRITE : 0;
+
+ solo_eeprom_reg_write(solo_dev, SOLO_EEPROM_ENABLE | dataval);
+ solo_eeprom_reg_write(solo_dev, SOLO_EEPROM_ENABLE |
+ EE_SHIFT_CLK | dataval);
+ }
+
+ solo_eeprom_reg_write(solo_dev, SOLO_EEPROM_ENABLE);
+}
+
+unsigned int solo_eeprom_ewen(struct solo_dev *solo_dev, int w_en)
+{
+ int ewen_cmd = (w_en ? 0x3f : 0) | (EE_EWEN_CMD << ADDR_LEN);
+ unsigned int retval = 0;
+ int i;
+
+ solo_eeprom_cmd(solo_dev, ewen_cmd);
+
+ for (i = 0; i < 16; i++) {
+ solo_eeprom_reg_write(solo_dev, SOLO_EEPROM_ENABLE |
+ EE_SHIFT_CLK);
+ retval = (retval << 1) | solo_eeprom_reg_read(solo_dev);
+ solo_eeprom_reg_write(solo_dev, SOLO_EEPROM_ENABLE);
+ retval = (retval << 1) | solo_eeprom_reg_read(solo_dev);
+ }
+
+ solo_eeprom_reg_write(solo_dev, ~EE_CS);
+ retval = (retval << 1) | solo_eeprom_reg_read(solo_dev);
+
+ return retval;
+}
+
+unsigned short solo_eeprom_read(struct solo_dev *solo_dev, int loc)
+{
+ int read_cmd = loc | (EE_READ_CMD << ADDR_LEN);
+ unsigned short retval = 0;
+ int i;
+
+ solo_eeprom_cmd(solo_dev, read_cmd);
+
+ for (i = 0; i < 16; i++) {
+ solo_eeprom_reg_write(solo_dev, SOLO_EEPROM_ENABLE |
+ EE_SHIFT_CLK);
+ retval = (retval << 1) | solo_eeprom_reg_read(solo_dev);
+ solo_eeprom_reg_write(solo_dev, SOLO_EEPROM_ENABLE);
+ }
+
+ solo_eeprom_reg_write(solo_dev, ~EE_CS);
+
+ return retval;
+}
+
+int solo_eeprom_write(struct solo_dev *solo_dev, int loc,
+ unsigned short data)
+{
+ int write_cmd = loc | (EE_WRITE_CMD << ADDR_LEN);
+ unsigned int retval;
+ int i;
+
+ solo_eeprom_cmd(solo_dev, write_cmd);
+
+ for (i = 15; i >= 0; i--) {
+ unsigned int dataval = (data >> i) & 1;
+
+ solo_eeprom_reg_write(solo_dev, EE_ENB);
+ solo_eeprom_reg_write(solo_dev,
+ EE_ENB | (dataval << 1) | EE_SHIFT_CLK);
+ }
+
+ solo_eeprom_reg_write(solo_dev, EE_ENB);
+ solo_eeprom_reg_write(solo_dev, ~EE_CS);
+ solo_eeprom_reg_write(solo_dev, EE_ENB);
+
+ for (i = retval = 0; i < 10000 && !retval; i++)
+ retval = solo_eeprom_reg_read(solo_dev);
+
+ solo_eeprom_reg_write(solo_dev, ~EE_CS);
+
+ return !retval;
+}
diff --git a/drivers/staging/media/solo6x10/solo6x10-enc.c b/drivers/staging/media/solo6x10/solo6x10-enc.c
new file mode 100644
index 00000000000..2db53b68c62
--- /dev/null
+++ b/drivers/staging/media/solo6x10/solo6x10-enc.c
@@ -0,0 +1,344 @@
+/*
+ * Copyright (C) 2010-2013 Bluecherry, LLC <http://www.bluecherrydvr.com>
+ *
+ * Original author:
+ * Ben Collins <bcollins@ubuntu.com>
+ *
+ * Additional work by:
+ * John Brooks <john.brooks@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/font.h>
+#include <linux/bitrev.h>
+#include <linux/slab.h>
+
+#include "solo6x10.h"
+
+#define VI_PROG_HSIZE (1280 - 16)
+#define VI_PROG_VSIZE (1024 - 16)
+
+#define IRQ_LEVEL 2
+
+static void solo_capture_config(struct solo_dev *solo_dev)
+{
+ unsigned long height;
+ unsigned long width;
+ void *buf;
+ int i;
+
+ solo_reg_write(solo_dev, SOLO_CAP_BASE,
+ SOLO_CAP_MAX_PAGE((SOLO_CAP_EXT_SIZE(solo_dev)
+ - SOLO_CAP_PAGE_SIZE) >> 16)
+ | SOLO_CAP_BASE_ADDR(SOLO_CAP_EXT_ADDR(solo_dev) >> 16));
+
+ /* XXX: Undocumented bits at b17 and b24 */
+ if (solo_dev->type == SOLO_DEV_6110) {
+ /* NOTE: Ref driver has (62 << 24) here as well, but it causes
+ * wacked out frame timing on 4-port 6110. */
+ solo_reg_write(solo_dev, SOLO_CAP_BTW,
+ (1 << 17) | SOLO_CAP_PROG_BANDWIDTH(2) |
+ SOLO_CAP_MAX_BANDWIDTH(36));
+ } else {
+ solo_reg_write(solo_dev, SOLO_CAP_BTW,
+ (1 << 17) | SOLO_CAP_PROG_BANDWIDTH(2) |
+ SOLO_CAP_MAX_BANDWIDTH(32));
+ }
+
+ /* Set scale 1, 9 dimension */
+ width = solo_dev->video_hsize;
+ height = solo_dev->video_vsize;
+ solo_reg_write(solo_dev, SOLO_DIM_SCALE1,
+ SOLO_DIM_H_MB_NUM(width / 16) |
+ SOLO_DIM_V_MB_NUM_FRAME(height / 8) |
+ SOLO_DIM_V_MB_NUM_FIELD(height / 16));
+
+ /* Set scale 2, 10 dimension */
+ width = solo_dev->video_hsize / 2;
+ height = solo_dev->video_vsize;
+ solo_reg_write(solo_dev, SOLO_DIM_SCALE2,
+ SOLO_DIM_H_MB_NUM(width / 16) |
+ SOLO_DIM_V_MB_NUM_FRAME(height / 8) |
+ SOLO_DIM_V_MB_NUM_FIELD(height / 16));
+
+ /* Set scale 3, 11 dimension */
+ width = solo_dev->video_hsize / 2;
+ height = solo_dev->video_vsize / 2;
+ solo_reg_write(solo_dev, SOLO_DIM_SCALE3,
+ SOLO_DIM_H_MB_NUM(width / 16) |
+ SOLO_DIM_V_MB_NUM_FRAME(height / 8) |
+ SOLO_DIM_V_MB_NUM_FIELD(height / 16));
+
+ /* Set scale 4, 12 dimension */
+ width = solo_dev->video_hsize / 3;
+ height = solo_dev->video_vsize / 3;
+ solo_reg_write(solo_dev, SOLO_DIM_SCALE4,
+ SOLO_DIM_H_MB_NUM(width / 16) |
+ SOLO_DIM_V_MB_NUM_FRAME(height / 8) |
+ SOLO_DIM_V_MB_NUM_FIELD(height / 16));
+
+ /* Set scale 5, 13 dimension */
+ width = solo_dev->video_hsize / 4;
+ height = solo_dev->video_vsize / 2;
+ solo_reg_write(solo_dev, SOLO_DIM_SCALE5,
+ SOLO_DIM_H_MB_NUM(width / 16) |
+ SOLO_DIM_V_MB_NUM_FRAME(height / 8) |
+ SOLO_DIM_V_MB_NUM_FIELD(height / 16));
+
+ /* Progressive */
+ width = VI_PROG_HSIZE;
+ height = VI_PROG_VSIZE;
+ solo_reg_write(solo_dev, SOLO_DIM_PROG,
+ SOLO_DIM_H_MB_NUM(width / 16) |
+ SOLO_DIM_V_MB_NUM_FRAME(height / 16) |
+ SOLO_DIM_V_MB_NUM_FIELD(height / 16));
+
+ /* Clear OSD */
+ solo_reg_write(solo_dev, SOLO_VE_OSD_CH, 0);
+ solo_reg_write(solo_dev, SOLO_VE_OSD_BASE, SOLO_EOSD_EXT_ADDR >> 16);
+ solo_reg_write(solo_dev, SOLO_VE_OSD_CLR,
+ 0xF0 << 16 | 0x80 << 8 | 0x80);
+
+ if (solo_dev->type == SOLO_DEV_6010)
+ solo_reg_write(solo_dev, SOLO_VE_OSD_OPT,
+ SOLO_VE_OSD_H_SHADOW | SOLO_VE_OSD_V_SHADOW);
+ else
+ solo_reg_write(solo_dev, SOLO_VE_OSD_OPT, SOLO_VE_OSD_V_DOUBLE
+ | SOLO_VE_OSD_H_SHADOW | SOLO_VE_OSD_V_SHADOW);
+
+ /* Clear OSG buffer */
+ buf = kzalloc(SOLO_EOSD_EXT_SIZE(solo_dev), GFP_KERNEL);
+ if (!buf)
+ return;
+
+ for (i = 0; i < solo_dev->nr_chans; i++) {
+ solo_p2m_dma(solo_dev, 1, buf,
+ SOLO_EOSD_EXT_ADDR +
+ (SOLO_EOSD_EXT_SIZE(solo_dev) * i),
+ SOLO_EOSD_EXT_SIZE(solo_dev), 0, 0);
+ }
+ kfree(buf);
+}
+
+#define SOLO_OSD_WRITE_SIZE (16 * OSD_TEXT_MAX)
+
+/* Should be called with enable_lock held */
+int solo_osd_print(struct solo_enc_dev *solo_enc)
+{
+ struct solo_dev *solo_dev = solo_enc->solo_dev;
+ unsigned char *str = solo_enc->osd_text;
+ u8 *buf = solo_enc->osd_buf;
+ u32 reg;
+ const struct font_desc *vga = find_font("VGA8x16");
+ const unsigned char *vga_data;
+ int i, j;
+
+ if (WARN_ON_ONCE(!vga))
+ return -ENODEV;
+
+ reg = solo_reg_read(solo_dev, SOLO_VE_OSD_CH);
+ if (!*str) {
+ /* Disable OSD on this channel */
+ reg &= ~(1 << solo_enc->ch);
+ goto out;
+ }
+
+ memset(buf, 0, SOLO_OSD_WRITE_SIZE);
+ vga_data = (const unsigned char *)vga->data;
+
+ for (i = 0; *str; i++, str++) {
+ for (j = 0; j < 16; j++) {
+ buf[(j << 1) | (i & 1) | ((i & ~1) << 4)] =
+ bitrev8(vga_data[(*str << 4) | j]);
+ }
+ }
+
+ solo_p2m_dma(solo_dev, 1, buf,
+ SOLO_EOSD_EXT_ADDR_CHAN(solo_dev, solo_enc->ch),
+ SOLO_OSD_WRITE_SIZE, 0, 0);
+
+ /* Enable OSD on this channel */
+ reg |= (1 << solo_enc->ch);
+
+out:
+ solo_reg_write(solo_dev, SOLO_VE_OSD_CH, reg);
+ return 0;
+}
+
+/**
+ * Set channel Quality Profile (0-3).
+ */
+void solo_s_jpeg_qp(struct solo_dev *solo_dev, unsigned int ch,
+ unsigned int qp)
+{
+ unsigned long flags;
+ unsigned int idx, reg;
+
+ if ((ch > 31) || (qp > 3))
+ return;
+
+ if (solo_dev->type == SOLO_DEV_6010)
+ return;
+
+ if (ch < 16) {
+ idx = 0;
+ reg = SOLO_VE_JPEG_QP_CH_L;
+ } else {
+ ch -= 16;
+ idx = 1;
+ reg = SOLO_VE_JPEG_QP_CH_H;
+ }
+ ch *= 2;
+
+ spin_lock_irqsave(&solo_dev->jpeg_qp_lock, flags);
+
+ solo_dev->jpeg_qp[idx] &= ~(3 << ch);
+ solo_dev->jpeg_qp[idx] |= (qp & 3) << ch;
+
+ solo_reg_write(solo_dev, reg, solo_dev->jpeg_qp[idx]);
+
+ spin_unlock_irqrestore(&solo_dev->jpeg_qp_lock, flags);
+}
+
+int solo_g_jpeg_qp(struct solo_dev *solo_dev, unsigned int ch)
+{
+ int idx;
+
+ if (solo_dev->type == SOLO_DEV_6010)
+ return 2;
+
+ if (WARN_ON_ONCE(ch > 31))
+ return 2;
+
+ if (ch < 16) {
+ idx = 0;
+ } else {
+ ch -= 16;
+ idx = 1;
+ }
+ ch *= 2;
+
+ return (solo_dev->jpeg_qp[idx] >> ch) & 3;
+}
+
+#define SOLO_QP_INIT 0xaaaaaaaa
+
+static void solo_jpeg_config(struct solo_dev *solo_dev)
+{
+ if (solo_dev->type == SOLO_DEV_6010) {
+ solo_reg_write(solo_dev, SOLO_VE_JPEG_QP_TBL,
+ (2 << 24) | (2 << 16) | (2 << 8) | 2);
+ } else {
+ solo_reg_write(solo_dev, SOLO_VE_JPEG_QP_TBL,
+ (4 << 24) | (3 << 16) | (2 << 8) | 1);
+ }
+
+ spin_lock_init(&solo_dev->jpeg_qp_lock);
+
+ /* Initialize Quality Profile for all channels */
+ solo_dev->jpeg_qp[0] = solo_dev->jpeg_qp[1] = SOLO_QP_INIT;
+ solo_reg_write(solo_dev, SOLO_VE_JPEG_QP_CH_L, SOLO_QP_INIT);
+ solo_reg_write(solo_dev, SOLO_VE_JPEG_QP_CH_H, SOLO_QP_INIT);
+
+ solo_reg_write(solo_dev, SOLO_VE_JPEG_CFG,
+ (SOLO_JPEG_EXT_SIZE(solo_dev) & 0xffff0000) |
+ ((SOLO_JPEG_EXT_ADDR(solo_dev) >> 16) & 0x0000ffff));
+ solo_reg_write(solo_dev, SOLO_VE_JPEG_CTRL, 0xffffffff);
+ if (solo_dev->type == SOLO_DEV_6110) {
+ solo_reg_write(solo_dev, SOLO_VE_JPEG_CFG1,
+ (0 << 16) | (30 << 8) | 60);
+ }
+}
+
+static void solo_mp4e_config(struct solo_dev *solo_dev)
+{
+ int i;
+ u32 cfg;
+
+ solo_reg_write(solo_dev, SOLO_VE_CFG0,
+ SOLO_VE_INTR_CTRL(IRQ_LEVEL) |
+ SOLO_VE_BLOCK_SIZE(SOLO_MP4E_EXT_SIZE(solo_dev) >> 16) |
+ SOLO_VE_BLOCK_BASE(SOLO_MP4E_EXT_ADDR(solo_dev) >> 16));
+
+
+ cfg = SOLO_VE_BYTE_ALIGN(2) | SOLO_VE_INSERT_INDEX
+ | SOLO_VE_MOTION_MODE(0);
+ if (solo_dev->type != SOLO_DEV_6010) {
+ cfg |= SOLO_VE_MPEG_SIZE_H(
+ (SOLO_MP4E_EXT_SIZE(solo_dev) >> 24) & 0x0f);
+ cfg |= SOLO_VE_JPEG_SIZE_H(
+ (SOLO_JPEG_EXT_SIZE(solo_dev) >> 24) & 0x0f);
+ }
+ solo_reg_write(solo_dev, SOLO_VE_CFG1, cfg);
+
+ solo_reg_write(solo_dev, SOLO_VE_WMRK_POLY, 0);
+ solo_reg_write(solo_dev, SOLO_VE_VMRK_INIT_KEY, 0);
+ solo_reg_write(solo_dev, SOLO_VE_WMRK_STRL, 0);
+ if (solo_dev->type == SOLO_DEV_6110)
+ solo_reg_write(solo_dev, SOLO_VE_WMRK_ENABLE, 0);
+ solo_reg_write(solo_dev, SOLO_VE_ENCRYP_POLY, 0);
+ solo_reg_write(solo_dev, SOLO_VE_ENCRYP_INIT, 0);
+
+ solo_reg_write(solo_dev, SOLO_VE_ATTR,
+ SOLO_VE_LITTLE_ENDIAN |
+ SOLO_COMP_ATTR_FCODE(1) |
+ SOLO_COMP_TIME_INC(0) |
+ SOLO_COMP_TIME_WIDTH(15) |
+ SOLO_DCT_INTERVAL(solo_dev->type == SOLO_DEV_6010 ? 9 : 10));
+
+ for (i = 0; i < solo_dev->nr_chans; i++) {
+ solo_reg_write(solo_dev, SOLO_VE_CH_REF_BASE(i),
+ (SOLO_EREF_EXT_ADDR(solo_dev) +
+ (i * SOLO_EREF_EXT_SIZE)) >> 16);
+ solo_reg_write(solo_dev, SOLO_VE_CH_REF_BASE_E(i),
+ (SOLO_EREF_EXT_ADDR(solo_dev) +
+ ((i + 16) * SOLO_EREF_EXT_SIZE)) >> 16);
+ }
+
+ if (solo_dev->type == SOLO_DEV_6110) {
+ solo_reg_write(solo_dev, SOLO_VE_COMPT_MOT, 0x00040008);
+ } else {
+ for (i = 0; i < solo_dev->nr_chans; i++)
+ solo_reg_write(solo_dev, SOLO_VE_CH_MOT(i), 0x100);
+ }
+}
+
+int solo_enc_init(struct solo_dev *solo_dev)
+{
+ int i;
+
+ solo_capture_config(solo_dev);
+ solo_mp4e_config(solo_dev);
+ solo_jpeg_config(solo_dev);
+
+ for (i = 0; i < solo_dev->nr_chans; i++) {
+ solo_reg_write(solo_dev, SOLO_CAP_CH_SCALE(i), 0);
+ solo_reg_write(solo_dev, SOLO_CAP_CH_COMP_ENA_E(i), 0);
+ }
+
+ return 0;
+}
+
+void solo_enc_exit(struct solo_dev *solo_dev)
+{
+ int i;
+
+ for (i = 0; i < solo_dev->nr_chans; i++) {
+ solo_reg_write(solo_dev, SOLO_CAP_CH_SCALE(i), 0);
+ solo_reg_write(solo_dev, SOLO_CAP_CH_COMP_ENA_E(i), 0);
+ }
+}
diff --git a/drivers/staging/media/solo6x10/solo6x10-g723.c b/drivers/staging/media/solo6x10/solo6x10-g723.c
new file mode 100644
index 00000000000..74f037b6166
--- /dev/null
+++ b/drivers/staging/media/solo6x10/solo6x10-g723.c
@@ -0,0 +1,424 @@
+/*
+ * Copyright (C) 2010-2013 Bluecherry, LLC <http://www.bluecherrydvr.com>
+ *
+ * Original author:
+ * Ben Collins <bcollins@ubuntu.com>
+ *
+ * Additional work by:
+ * John Brooks <john.brooks@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 <linux/module.h>
+#include <linux/slab.h>
+
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/pcm.h>
+#include <sound/control.h>
+
+#include "solo6x10.h"
+#include "solo6x10-tw28.h"
+
+#define G723_FDMA_PAGES 32
+#define G723_PERIOD_BYTES 48
+#define G723_PERIOD_BLOCK 1024
+#define G723_FRAMES_PER_PAGE 48
+
+/* Sets up channels 16-19 for decoding and 0-15 for encoding */
+#define OUTMODE_MASK 0x300
+
+#define SAMPLERATE 8000
+#define BITRATE 25
+
+/* The solo writes to 1k byte pages, 32 pages, in the dma. Each 1k page
+ * is broken down to 20 * 48 byte regions (one for each channel possible)
+ * with the rest of the page being dummy data. */
+#define G723_MAX_BUFFER (G723_PERIOD_BYTES * PERIODS_MAX)
+#define G723_INTR_ORDER 4 /* 0 - 4 */
+#define PERIODS_MIN (1 << G723_INTR_ORDER)
+#define PERIODS_MAX G723_FDMA_PAGES
+
+struct solo_snd_pcm {
+ int on;
+ spinlock_t lock;
+ struct solo_dev *solo_dev;
+ unsigned char *g723_buf;
+ dma_addr_t g723_dma;
+};
+
+static void solo_g723_config(struct solo_dev *solo_dev)
+{
+ int clk_div;
+
+ clk_div = (solo_dev->clock_mhz * 1000000)
+ / (SAMPLERATE * (BITRATE * 2) * 2);
+
+ solo_reg_write(solo_dev, SOLO_AUDIO_SAMPLE,
+ SOLO_AUDIO_BITRATE(BITRATE)
+ | SOLO_AUDIO_CLK_DIV(clk_div));
+
+ solo_reg_write(solo_dev, SOLO_AUDIO_FDMA_INTR,
+ SOLO_AUDIO_FDMA_INTERVAL(1)
+ | SOLO_AUDIO_INTR_ORDER(G723_INTR_ORDER)
+ | SOLO_AUDIO_FDMA_BASE(SOLO_G723_EXT_ADDR(solo_dev) >> 16));
+
+ solo_reg_write(solo_dev, SOLO_AUDIO_CONTROL,
+ SOLO_AUDIO_ENABLE
+ | SOLO_AUDIO_I2S_MODE
+ | SOLO_AUDIO_I2S_MULTI(3)
+ | SOLO_AUDIO_MODE(OUTMODE_MASK));
+}
+
+void solo_g723_isr(struct solo_dev *solo_dev)
+{
+ struct snd_pcm_str *pstr =
+ &solo_dev->snd_pcm->streams[SNDRV_PCM_STREAM_CAPTURE];
+ struct snd_pcm_substream *ss;
+ struct solo_snd_pcm *solo_pcm;
+
+ for (ss = pstr->substream; ss != NULL; ss = ss->next) {
+ if (snd_pcm_substream_chip(ss) == NULL)
+ continue;
+
+ /* This means open() hasn't been called on this one */
+ if (snd_pcm_substream_chip(ss) == solo_dev)
+ continue;
+
+ /* Haven't triggered a start yet */
+ solo_pcm = snd_pcm_substream_chip(ss);
+ if (!solo_pcm->on)
+ continue;
+
+ snd_pcm_period_elapsed(ss);
+ }
+}
+
+static 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 const struct snd_pcm_hardware snd_solo_pcm_hw = {
+ .info = (SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_MMAP_VALID),
+ .formats = SNDRV_PCM_FMTBIT_U8,
+ .rates = SNDRV_PCM_RATE_8000,
+ .rate_min = SAMPLERATE,
+ .rate_max = SAMPLERATE,
+ .channels_min = 1,
+ .channels_max = 1,
+ .buffer_bytes_max = G723_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 solo_dev *solo_dev = snd_pcm_substream_chip(ss);
+ struct solo_snd_pcm *solo_pcm;
+
+ solo_pcm = kzalloc(sizeof(*solo_pcm), GFP_KERNEL);
+ if (solo_pcm == NULL)
+ goto oom;
+
+ solo_pcm->g723_buf = pci_alloc_consistent(solo_dev->pdev,
+ G723_PERIOD_BYTES,
+ &solo_pcm->g723_dma);
+ if (solo_pcm->g723_buf == NULL)
+ goto oom;
+
+ spin_lock_init(&solo_pcm->lock);
+ solo_pcm->solo_dev = solo_dev;
+ ss->runtime->hw = snd_solo_pcm_hw;
+
+ snd_pcm_substream_chip(ss) = solo_pcm;
+
+ return 0;
+
+oom:
+ kfree(solo_pcm);
+ return -ENOMEM;
+}
+
+static int snd_solo_pcm_close(struct snd_pcm_substream *ss)
+{
+ struct solo_snd_pcm *solo_pcm = snd_pcm_substream_chip(ss);
+
+ snd_pcm_substream_chip(ss) = solo_pcm->solo_dev;
+ pci_free_consistent(solo_pcm->solo_dev->pdev, G723_PERIOD_BYTES,
+ solo_pcm->g723_buf, solo_pcm->g723_dma);
+ kfree(solo_pcm);
+
+ return 0;
+}
+
+static int snd_solo_pcm_trigger(struct snd_pcm_substream *ss, int cmd)
+{
+ struct solo_snd_pcm *solo_pcm = snd_pcm_substream_chip(ss);
+ struct solo_dev *solo_dev = solo_pcm->solo_dev;
+ int ret = 0;
+
+ spin_lock(&solo_pcm->lock);
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ if (solo_pcm->on == 0) {
+ /* If this is the first user, switch on interrupts */
+ if (atomic_inc_return(&solo_dev->snd_users) == 1)
+ solo_irq_on(solo_dev, SOLO_IRQ_G723);
+ solo_pcm->on = 1;
+ }
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ if (solo_pcm->on) {
+ /* If this was our last user, switch them off */
+ if (atomic_dec_return(&solo_dev->snd_users) == 0)
+ solo_irq_off(solo_dev, SOLO_IRQ_G723);
+ solo_pcm->on = 0;
+ }
+ break;
+ default:
+ ret = -EINVAL;
+ }
+
+ spin_unlock(&solo_pcm->lock);
+
+ return ret;
+}
+
+static int snd_solo_pcm_prepare(struct snd_pcm_substream *ss)
+{
+ return 0;
+}
+
+static snd_pcm_uframes_t snd_solo_pcm_pointer(struct snd_pcm_substream *ss)
+{
+ struct solo_snd_pcm *solo_pcm = snd_pcm_substream_chip(ss);
+ struct solo_dev *solo_dev = solo_pcm->solo_dev;
+ snd_pcm_uframes_t idx = solo_reg_read(solo_dev, SOLO_AUDIO_STA) & 0x1f;
+
+ return idx * G723_FRAMES_PER_PAGE;
+}
+
+static int snd_solo_pcm_copy(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 solo_dev *solo_dev = solo_pcm->solo_dev;
+ int err, i;
+
+ for (i = 0; i < (count / G723_FRAMES_PER_PAGE); i++) {
+ int page = (pos / G723_FRAMES_PER_PAGE) + i;
+
+ err = solo_p2m_dma_t(solo_dev, 0, solo_pcm->g723_dma,
+ SOLO_G723_EXT_ADDR(solo_dev) +
+ (page * G723_PERIOD_BLOCK) +
+ (ss->number * G723_PERIOD_BYTES),
+ G723_PERIOD_BYTES, 0, 0);
+ if (err)
+ return err;
+
+ err = copy_to_user(dst + (i * G723_PERIOD_BYTES),
+ solo_pcm->g723_buf, G723_PERIOD_BYTES);
+
+ if (err)
+ return -EFAULT;
+ }
+
+ 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 solo_dev *solo_dev = snd_kcontrol_chip(kcontrol);
+ u8 ch = value->id.numid - 1;
+
+ value->value.integer.value[0] = tw28_get_audio_gain(solo_dev, ch);
+
+ return 0;
+}
+
+static int snd_solo_capture_volume_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *value)
+{
+ struct solo_dev *solo_dev = snd_kcontrol_chip(kcontrol);
+ u8 ch = value->id.numid - 1;
+ u8 old_val;
+
+ old_val = tw28_get_audio_gain(solo_dev, ch);
+ if (old_val == value->value.integer.value[0])
+ return 0;
+
+ tw28_set_audio_gain(solo_dev, ch, value->value.integer.value[0]);
+
+ return 1;
+}
+
+static struct snd_kcontrol_new snd_solo_capture_volume = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Capture Volume",
+ .info = snd_solo_capture_volume_info,
+ .get = snd_solo_capture_volume_get,
+ .put = snd_solo_capture_volume_put,
+};
+
+static int solo_snd_pcm_init(struct solo_dev *solo_dev)
+{
+ struct snd_card *card = solo_dev->snd_card;
+ struct snd_pcm *pcm;
+ struct snd_pcm_substream *ss;
+ int ret;
+ int i;
+
+ ret = snd_pcm_new(card, card->driver, 0, 0, solo_dev->nr_chans,
+ &pcm);
+ if (ret < 0)
+ return ret;
+
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE,
+ &snd_solo_pcm_ops);
+
+ snd_pcm_chip(pcm) = solo_dev;
+ pcm->info_flags = 0;
+ 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),
+ G723_MAX_BUFFER, G723_MAX_BUFFER);
+ if (ret < 0)
+ return ret;
+
+ solo_dev->snd_pcm = pcm;
+
+ return 0;
+}
+
+int solo_g723_init(struct solo_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_new(&solo_dev->pdev->dev,
+ SNDRV_DEFAULT_IDX1, name, THIS_MODULE, 0,
+ &solo_dev->snd_card);
+ if (ret < 0)
+ return ret;
+
+ card = solo_dev->snd_card;
+
+ strcpy(card->driver, SOLO6X10_NAME);
+ strcpy(card->shortname, "SOLO-6x10 Audio");
+ sprintf(card->longname, "%s on %s IRQ %d", card->shortname,
+ pci_name(solo_dev->pdev), solo_dev->pdev->irq);
+
+ ret = snd_device_new(card, SNDRV_DEV_LOWLEVEL, solo_dev, &ops);
+ if (ret < 0)
+ goto snd_error;
+
+ /* Mixer controls */
+ strcpy(card->mixername, "SOLO-6x10");
+ 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;
+
+ ret = solo_snd_pcm_init(solo_dev);
+ if (ret < 0)
+ goto snd_error;
+
+ ret = snd_card_register(card);
+ if (ret < 0)
+ goto snd_error;
+
+ solo_g723_config(solo_dev);
+
+ dev_info(&solo_dev->pdev->dev, "Alsa sound card as %s\n", name);
+
+ return 0;
+
+snd_error:
+ snd_card_free(card);
+ return ret;
+}
+
+void solo_g723_exit(struct solo_dev *solo_dev)
+{
+ if (!solo_dev->snd_card)
+ return;
+
+ solo_reg_write(solo_dev, SOLO_AUDIO_CONTROL, 0);
+ solo_irq_off(solo_dev, SOLO_IRQ_G723);
+
+ snd_card_free(solo_dev->snd_card);
+ solo_dev->snd_card = NULL;
+}
diff --git a/drivers/staging/media/solo6x10/solo6x10-gpio.c b/drivers/staging/media/solo6x10/solo6x10-gpio.c
new file mode 100644
index 00000000000..73276dc9287
--- /dev/null
+++ b/drivers/staging/media/solo6x10/solo6x10-gpio.c
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2010-2013 Bluecherry, LLC <http://www.bluecherrydvr.com>
+ *
+ * Original author:
+ * Ben Collins <bcollins@ubuntu.com>
+ *
+ * Additional work by:
+ * John Brooks <john.brooks@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 <linux/delay.h>
+#include <linux/uaccess.h>
+
+#include "solo6x10.h"
+
+static void solo_gpio_mode(struct solo_dev *solo_dev,
+ unsigned int port_mask, unsigned int mode)
+{
+ int port;
+ unsigned int ret;
+
+ ret = solo_reg_read(solo_dev, SOLO_GPIO_CONFIG_0);
+
+ /* To set gpio */
+ for (port = 0; port < 16; port++) {
+ if (!((1 << port) & port_mask))
+ continue;
+
+ ret &= (~(3 << (port << 1)));
+ ret |= ((mode & 3) << (port << 1));
+ }
+
+ solo_reg_write(solo_dev, SOLO_GPIO_CONFIG_0, ret);
+
+ /* To set extended gpio - sensor */
+ ret = solo_reg_read(solo_dev, SOLO_GPIO_CONFIG_1);
+
+ for (port = 0; port < 16; port++) {
+ if (!((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 solo_dev *solo_dev, unsigned int value)
+{
+ solo_reg_write(solo_dev, SOLO_GPIO_DATA_OUT,
+ solo_reg_read(solo_dev, SOLO_GPIO_DATA_OUT) | value);
+}
+
+static void solo_gpio_clear(struct solo_dev *solo_dev, unsigned int value)
+{
+ solo_reg_write(solo_dev, SOLO_GPIO_DATA_OUT,
+ solo_reg_read(solo_dev, SOLO_GPIO_DATA_OUT) & ~value);
+}
+
+static void solo_gpio_config(struct solo_dev *solo_dev)
+{
+ /* Video reset */
+ solo_gpio_mode(solo_dev, 0x30, 1);
+ solo_gpio_clear(solo_dev, 0x30);
+ udelay(100);
+ solo_gpio_set(solo_dev, 0x30);
+ udelay(100);
+
+ /* Warning: Don't touch the next line unless you're sure of what
+ * you're doing: first four gpio [0-3] are used for video. */
+ solo_gpio_mode(solo_dev, 0x0f, 2);
+
+ /* We use bit 8-15 of SOLO_GPIO_CONFIG_0 for relay purposes */
+ solo_gpio_mode(solo_dev, 0xff00, 1);
+
+ /* Initially set relay status to 0 */
+ solo_gpio_clear(solo_dev, 0xff00);
+}
+
+int solo_gpio_init(struct solo_dev *solo_dev)
+{
+ solo_gpio_config(solo_dev);
+ return 0;
+}
+
+void solo_gpio_exit(struct solo_dev *solo_dev)
+{
+ solo_gpio_clear(solo_dev, 0x30);
+ solo_gpio_config(solo_dev);
+}
diff --git a/drivers/staging/media/solo6x10/solo6x10-i2c.c b/drivers/staging/media/solo6x10/solo6x10-i2c.c
new file mode 100644
index 00000000000..01aa417c925
--- /dev/null
+++ b/drivers/staging/media/solo6x10/solo6x10-i2c.c
@@ -0,0 +1,334 @@
+/*
+ * Copyright (C) 2010-2013 Bluecherry, LLC <http://www.bluecherrydvr.com>
+ *
+ * Original author:
+ * Ben Collins <bcollins@ubuntu.com>
+ *
+ * Additional work by:
+ * John Brooks <john.brooks@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 SOLO6x10 i2c does not have separate interrupts for each i2c
+ * channel. The bus can only handle one i2c event at a time. The below handles
+ * this all wrong. We should be using the status registers to see if the bus
+ * is in use, and have a global lock to check the status register. Also,
+ * the bulk of the work should be handled out-of-interrupt. The ugly loops
+ * that occur during interrupt scare me. The ISR should merely signal
+ * thread context, ACK the interrupt, and move on. -- BenC */
+
+#include <linux/kernel.h>
+
+#include "solo6x10.h"
+
+u8 solo_i2c_readbyte(struct solo_dev *solo_dev, int id, u8 addr, u8 off)
+{
+ struct i2c_msg msgs[2];
+ u8 data;
+
+ msgs[0].flags = 0;
+ msgs[0].addr = addr;
+ msgs[0].len = 1;
+ msgs[0].buf = &off;
+
+ msgs[1].flags = I2C_M_RD;
+ msgs[1].addr = addr;
+ msgs[1].len = 1;
+ msgs[1].buf = &data;
+
+ i2c_transfer(&solo_dev->i2c_adap[id], msgs, 2);
+
+ return data;
+}
+
+void solo_i2c_writebyte(struct solo_dev *solo_dev, int id, u8 addr,
+ u8 off, u8 data)
+{
+ struct i2c_msg msgs;
+ u8 buf[2];
+
+ buf[0] = off;
+ buf[1] = data;
+ msgs.flags = 0;
+ msgs.addr = addr;
+ msgs.len = 2;
+ msgs.buf = buf;
+
+ i2c_transfer(&solo_dev->i2c_adap[id], &msgs, 1);
+}
+
+static void solo_i2c_flush(struct solo_dev *solo_dev, int wr)
+{
+ u32 ctrl;
+
+ ctrl = SOLO_IIC_CH_SET(solo_dev->i2c_id);
+
+ if (solo_dev->i2c_state == IIC_STATE_START)
+ ctrl |= SOLO_IIC_START;
+
+ if (wr) {
+ ctrl |= SOLO_IIC_WRITE;
+ } else {
+ ctrl |= SOLO_IIC_READ;
+ if (!(solo_dev->i2c_msg->flags & I2C_M_NO_RD_ACK))
+ ctrl |= SOLO_IIC_ACK_EN;
+ }
+
+ if (solo_dev->i2c_msg_ptr == solo_dev->i2c_msg->len)
+ ctrl |= SOLO_IIC_STOP;
+
+ solo_reg_write(solo_dev, SOLO_IIC_CTRL, ctrl);
+}
+
+static void solo_i2c_start(struct solo_dev *solo_dev)
+{
+ u32 addr = solo_dev->i2c_msg->addr << 1;
+
+ if (solo_dev->i2c_msg->flags & I2C_M_RD)
+ addr |= 1;
+
+ solo_dev->i2c_state = IIC_STATE_START;
+ solo_reg_write(solo_dev, SOLO_IIC_TXD, addr);
+ solo_i2c_flush(solo_dev, 1);
+}
+
+static void solo_i2c_stop(struct solo_dev *solo_dev)
+{
+ solo_irq_off(solo_dev, SOLO_IRQ_IIC);
+ solo_reg_write(solo_dev, SOLO_IIC_CTRL, 0);
+ solo_dev->i2c_state = IIC_STATE_STOP;
+ wake_up(&solo_dev->i2c_wait);
+}
+
+static int solo_i2c_handle_read(struct solo_dev *solo_dev)
+{
+prepare_read:
+ if (solo_dev->i2c_msg_ptr != solo_dev->i2c_msg->len) {
+ solo_i2c_flush(solo_dev, 0);
+ return 0;
+ }
+
+ solo_dev->i2c_msg_ptr = 0;
+ solo_dev->i2c_msg++;
+ solo_dev->i2c_msg_num--;
+
+ if (solo_dev->i2c_msg_num == 0) {
+ solo_i2c_stop(solo_dev);
+ return 0;
+ }
+
+ if (!(solo_dev->i2c_msg->flags & I2C_M_NOSTART)) {
+ solo_i2c_start(solo_dev);
+ } else {
+ if (solo_dev->i2c_msg->flags & I2C_M_RD)
+ goto prepare_read;
+ else
+ solo_i2c_stop(solo_dev);
+ }
+
+ return 0;
+}
+
+static int solo_i2c_handle_write(struct solo_dev *solo_dev)
+{
+retry_write:
+ if (solo_dev->i2c_msg_ptr != solo_dev->i2c_msg->len) {
+ solo_reg_write(solo_dev, SOLO_IIC_TXD,
+ solo_dev->i2c_msg->buf[solo_dev->i2c_msg_ptr]);
+ solo_dev->i2c_msg_ptr++;
+ solo_i2c_flush(solo_dev, 1);
+ return 0;
+ }
+
+ solo_dev->i2c_msg_ptr = 0;
+ solo_dev->i2c_msg++;
+ solo_dev->i2c_msg_num--;
+
+ if (solo_dev->i2c_msg_num == 0) {
+ solo_i2c_stop(solo_dev);
+ return 0;
+ }
+
+ if (!(solo_dev->i2c_msg->flags & I2C_M_NOSTART)) {
+ solo_i2c_start(solo_dev);
+ } else {
+ if (solo_dev->i2c_msg->flags & I2C_M_RD)
+ solo_i2c_stop(solo_dev);
+ else
+ goto retry_write;
+ }
+
+ return 0;
+}
+
+int solo_i2c_isr(struct solo_dev *solo_dev)
+{
+ u32 status = solo_reg_read(solo_dev, SOLO_IIC_CTRL);
+ int ret = -EINVAL;
+
+
+ if (CHK_FLAGS(status, SOLO_IIC_STATE_TRNS | SOLO_IIC_STATE_SIG_ERR)
+ || solo_dev->i2c_id < 0) {
+ solo_i2c_stop(solo_dev);
+ return -ENXIO;
+ }
+
+ switch (solo_dev->i2c_state) {
+ case IIC_STATE_START:
+ if (solo_dev->i2c_msg->flags & I2C_M_RD) {
+ solo_dev->i2c_state = IIC_STATE_READ;
+ ret = solo_i2c_handle_read(solo_dev);
+ break;
+ }
+
+ solo_dev->i2c_state = IIC_STATE_WRITE;
+ case IIC_STATE_WRITE:
+ ret = solo_i2c_handle_write(solo_dev);
+ break;
+
+ case IIC_STATE_READ:
+ solo_dev->i2c_msg->buf[solo_dev->i2c_msg_ptr] =
+ solo_reg_read(solo_dev, SOLO_IIC_RXD);
+ solo_dev->i2c_msg_ptr++;
+
+ ret = solo_i2c_handle_read(solo_dev);
+ break;
+
+ default:
+ solo_i2c_stop(solo_dev);
+ }
+
+ return ret;
+}
+
+static int solo_i2c_master_xfer(struct i2c_adapter *adap,
+ struct i2c_msg msgs[], int num)
+{
+ struct solo_dev *solo_dev = adap->algo_data;
+ unsigned long timeout;
+ int ret;
+ int i;
+ DEFINE_WAIT(wait);
+
+ for (i = 0; i < SOLO_I2C_ADAPTERS; i++) {
+ if (&solo_dev->i2c_adap[i] == adap)
+ break;
+ }
+
+ if (i == SOLO_I2C_ADAPTERS)
+ return num; /* XXX Right return value for failure? */
+
+ mutex_lock(&solo_dev->i2c_mutex);
+ solo_dev->i2c_id = i;
+ solo_dev->i2c_msg = msgs;
+ solo_dev->i2c_msg_num = num;
+ solo_dev->i2c_msg_ptr = 0;
+
+ solo_reg_write(solo_dev, SOLO_IIC_CTRL, 0);
+ solo_irq_on(solo_dev, SOLO_IRQ_IIC);
+ solo_i2c_start(solo_dev);
+
+ timeout = HZ / 2;
+
+ for (;;) {
+ prepare_to_wait(&solo_dev->i2c_wait, &wait,
+ TASK_INTERRUPTIBLE);
+
+ if (solo_dev->i2c_state == IIC_STATE_STOP)
+ break;
+
+ timeout = schedule_timeout(timeout);
+ if (!timeout)
+ break;
+
+ if (signal_pending(current))
+ break;
+ }
+
+ finish_wait(&solo_dev->i2c_wait, &wait);
+ ret = num - solo_dev->i2c_msg_num;
+ solo_dev->i2c_state = IIC_STATE_IDLE;
+ solo_dev->i2c_id = -1;
+
+ mutex_unlock(&solo_dev->i2c_mutex);
+
+ return ret;
+}
+
+static u32 solo_i2c_functionality(struct i2c_adapter *adap)
+{
+ return I2C_FUNC_I2C;
+}
+
+static const struct i2c_algorithm solo_i2c_algo = {
+ .master_xfer = solo_i2c_master_xfer,
+ .functionality = solo_i2c_functionality,
+};
+
+int solo_i2c_init(struct solo_dev *solo_dev)
+{
+ int i;
+ int ret;
+
+ solo_reg_write(solo_dev, SOLO_IIC_CFG,
+ SOLO_IIC_PRESCALE(8) | SOLO_IIC_ENABLE);
+
+ solo_dev->i2c_id = -1;
+ solo_dev->i2c_state = IIC_STATE_IDLE;
+ init_waitqueue_head(&solo_dev->i2c_wait);
+ mutex_init(&solo_dev->i2c_mutex);
+
+ for (i = 0; i < SOLO_I2C_ADAPTERS; i++) {
+ struct i2c_adapter *adap = &solo_dev->i2c_adap[i];
+
+ snprintf(adap->name, I2C_NAME_SIZE, "%s I2C %d",
+ SOLO6X10_NAME, i);
+ adap->algo = &solo_i2c_algo;
+ adap->algo_data = solo_dev;
+ adap->retries = 1;
+ adap->dev.parent = &solo_dev->pdev->dev;
+
+ ret = i2c_add_adapter(adap);
+ if (ret) {
+ adap->algo_data = NULL;
+ break;
+ }
+ }
+
+ if (ret) {
+ for (i = 0; i < SOLO_I2C_ADAPTERS; i++) {
+ if (!solo_dev->i2c_adap[i].algo_data)
+ break;
+ i2c_del_adapter(&solo_dev->i2c_adap[i]);
+ solo_dev->i2c_adap[i].algo_data = NULL;
+ }
+ return ret;
+ }
+
+ return 0;
+}
+
+void solo_i2c_exit(struct solo_dev *solo_dev)
+{
+ int i;
+
+ for (i = 0; i < SOLO_I2C_ADAPTERS; i++) {
+ if (!solo_dev->i2c_adap[i].algo_data)
+ continue;
+ i2c_del_adapter(&solo_dev->i2c_adap[i]);
+ solo_dev->i2c_adap[i].algo_data = NULL;
+ }
+}
diff --git a/drivers/staging/media/solo6x10/solo6x10-jpeg.h b/drivers/staging/media/solo6x10/solo6x10-jpeg.h
new file mode 100644
index 00000000000..c5218ceeabc
--- /dev/null
+++ b/drivers/staging/media/solo6x10/solo6x10-jpeg.h
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2010-2013 Bluecherry, LLC <http://www.bluecherrydvr.com>
+ *
+ * Original author:
+ * Ben Collins <bcollins@ubuntu.com>
+ *
+ * Additional work by:
+ * John Brooks <john.brooks@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 __SOLO6X10_JPEG_H
+#define __SOLO6X10_JPEG_H
+
+static const 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
+
+/* This is the byte marker for the start of the DQT */
+#define DQT_START 17
+#define DQT_LEN 138
+const unsigned char jpeg_dqt[4][DQT_LEN] = {
+ {
+ 0xff, 0xdb, 0x00, 0x43, 0x00,
+ 0x08, 0x06, 0x06, 0x07, 0x06, 0x05, 0x08, 0x07,
+ 0x07, 0x07, 0x09, 0x09, 0x08, 0x0a, 0x0c, 0x14,
+ 0x0d, 0x0c, 0x0b, 0x0b, 0x0c, 0x19, 0x12, 0x13,
+ 0x0f, 0x14, 0x1d, 0x1a, 0x1f, 0x1e, 0x1d, 0x1a,
+ 0x1c, 0x1c, 0x20, 0x24, 0x2e, 0x27, 0x20, 0x22,
+ 0x2c, 0x23, 0x1c, 0x1c, 0x28, 0x37, 0x29, 0x2c,
+ 0x30, 0x31, 0x34, 0x34, 0x34, 0x1f, 0x27, 0x39,
+ 0x3d, 0x38, 0x32, 0x3c, 0x2e, 0x33, 0x34, 0x32,
+ 0xff, 0xdb, 0x00, 0x43, 0x01,
+ 0x09, 0x09, 0x09, 0x0c, 0x0b, 0x0c, 0x18, 0x0d,
+ 0x0d, 0x18, 0x32, 0x21, 0x1c, 0x21, 0x32, 0x32,
+ 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32,
+ 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32,
+ 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32,
+ 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32,
+ 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32,
+ 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32
+ }, {
+ 0xff, 0xdb, 0x00, 0x43, 0x00,
+ 0x10, 0x0b, 0x0c, 0x0e, 0x0c, 0x0a, 0x10, 0x0e,
+ 0x0d, 0x0e, 0x12, 0x11, 0x10, 0x13, 0x18, 0x28,
+ 0x1a, 0x18, 0x16, 0x16, 0x18, 0x31, 0x23, 0x25,
+ 0x1d, 0x28, 0x3a, 0x33, 0x3d, 0x3c, 0x39, 0x33,
+ 0x38, 0x37, 0x40, 0x48, 0x5c, 0x4e, 0x40, 0x44,
+ 0x57, 0x45, 0x37, 0x38, 0x50, 0x6d, 0x51, 0x57,
+ 0x5f, 0x62, 0x67, 0x68, 0x67, 0x3e, 0x4d, 0x71,
+ 0x79, 0x70, 0x64, 0x78, 0x5c, 0x65, 0x67, 0x63,
+ 0xff, 0xdb, 0x00, 0x43, 0x01,
+ 0x11, 0x12, 0x12, 0x18, 0x15, 0x18, 0x2f, 0x1a,
+ 0x1a, 0x2f, 0x63, 0x42, 0x38, 0x42, 0x63, 0x63,
+ 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63,
+ 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63,
+ 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63,
+ 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63,
+ 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63,
+ 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63
+ }, {
+ 0xff, 0xdb, 0x00, 0x43, 0x00,
+ 0x20, 0x16, 0x18, 0x1c, 0x18, 0x14, 0x20, 0x1c,
+ 0x1a, 0x1c, 0x24, 0x22, 0x20, 0x26, 0x30, 0x50,
+ 0x34, 0x30, 0x2c, 0x2c, 0x30, 0x62, 0x46, 0x4a,
+ 0x3a, 0x50, 0x74, 0x66, 0x7a, 0x78, 0x72, 0x66,
+ 0x70, 0x6e, 0x80, 0x90, 0xb8, 0x9c, 0x80, 0x88,
+ 0xae, 0x8a, 0x6e, 0x70, 0xa0, 0xda, 0xa2, 0xae,
+ 0xbe, 0xc4, 0xce, 0xd0, 0xce, 0x7c, 0x9a, 0xe2,
+ 0xf2, 0xe0, 0xc8, 0xf0, 0xb8, 0xca, 0xce, 0xc6,
+ 0xff, 0xdb, 0x00, 0x43, 0x01,
+ 0x22, 0x24, 0x24, 0x30, 0x2a, 0x30, 0x5e, 0x34,
+ 0x34, 0x5e, 0xc6, 0x84, 0x70, 0x84, 0xc6, 0xc6,
+ 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6,
+ 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6,
+ 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6,
+ 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6,
+ 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6,
+ 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6
+ }, {
+ 0xff, 0xdb, 0x00, 0x43, 0x00,
+ 0x30, 0x21, 0x24, 0x2a, 0x24, 0x1e, 0x30, 0x2a,
+ 0x27, 0x2a, 0x36, 0x33, 0x30, 0x39, 0x48, 0x78,
+ 0x4e, 0x48, 0x42, 0x42, 0x48, 0x93, 0x69, 0x6f,
+ 0x57, 0x78, 0xae, 0x99, 0xb7, 0xb4, 0xab, 0x99,
+ 0xa8, 0xa5, 0xc0, 0xd8, 0xff, 0xea, 0xc0, 0xcc,
+ 0xff, 0xcf, 0xa5, 0xa8, 0xf0, 0xff, 0xf3, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xba, 0xe7, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xdb, 0x00, 0x43, 0x01,
+ 0x33, 0x36, 0x36, 0x48, 0x3f, 0x48, 0x8d, 0x4e,
+ 0x4e, 0x8d, 0xff, 0xc6, 0xa8, 0xc6, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
+ }
+};
+
+#endif /* __SOLO6X10_JPEG_H */
diff --git a/drivers/staging/media/solo6x10/solo6x10-offsets.h b/drivers/staging/media/solo6x10/solo6x10-offsets.h
new file mode 100644
index 00000000000..13eeb4470dc
--- /dev/null
+++ b/drivers/staging/media/solo6x10/solo6x10-offsets.h
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2010-2013 Bluecherry, LLC <http://www.bluecherrydvr.com>
+ *
+ * Original author:
+ * Ben Collins <bcollins@ubuntu.com>
+ *
+ * Additional work by:
+ * John Brooks <john.brooks@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 __SOLO6X10_OFFSETS_H
+#define __SOLO6X10_OFFSETS_H
+
+#define SOLO_DISP_EXT_ADDR 0x00000000
+#define SOLO_DISP_EXT_SIZE 0x00480000
+
+#define SOLO_EOSD_EXT_ADDR \
+ (SOLO_DISP_EXT_ADDR + SOLO_DISP_EXT_SIZE)
+#define SOLO_EOSD_EXT_SIZE(__solo) \
+ (__solo->type == SOLO_DEV_6010 ? 0x10000 : 0x20000)
+#define SOLO_EOSD_EXT_SIZE_MAX 0x20000
+#define SOLO_EOSD_EXT_AREA(__solo) \
+ (SOLO_EOSD_EXT_SIZE(__solo) * 32)
+#define SOLO_EOSD_EXT_ADDR_CHAN(__solo, ch) \
+ (SOLO_EOSD_EXT_ADDR + SOLO_EOSD_EXT_SIZE(__solo) * (ch))
+
+#define SOLO_MOTION_EXT_ADDR(__solo) \
+ (SOLO_EOSD_EXT_ADDR + SOLO_EOSD_EXT_AREA(__solo))
+#define SOLO_MOTION_EXT_SIZE 0x00080000
+
+#define SOLO_G723_EXT_ADDR(__solo) \
+ (SOLO_MOTION_EXT_ADDR(__solo) + SOLO_MOTION_EXT_SIZE)
+#define SOLO_G723_EXT_SIZE 0x00010000
+
+#define SOLO_CAP_EXT_ADDR(__solo) \
+ (SOLO_G723_EXT_ADDR(__solo) + SOLO_G723_EXT_SIZE)
+
+/* 18 is the maximum number of pages required for PAL@D1, the largest frame
+ * possible */
+#define SOLO_CAP_PAGE_SIZE (18 << 16)
+
+/* Always allow the encoder enough for 16 channels, even if we have less. The
+ * exception is if we have card with only 32Megs of memory. */
+#define SOLO_CAP_EXT_SIZE(__solo) \
+ ((((__solo->sdram_size <= (32 << 20)) ? 4 : 16) + 1) \
+ * SOLO_CAP_PAGE_SIZE)
+
+#define SOLO_EREF_EXT_ADDR(__solo) \
+ (SOLO_CAP_EXT_ADDR(__solo) + SOLO_CAP_EXT_SIZE(__solo))
+#define SOLO_EREF_EXT_SIZE 0x00140000
+#define SOLO_EREF_EXT_AREA(__solo) \
+ (SOLO_EREF_EXT_SIZE * __solo->nr_chans * 2)
+
+#define __SOLO_JPEG_MIN_SIZE(__solo) (__solo->nr_chans * 0x00080000)
+
+#define SOLO_MP4E_EXT_ADDR(__solo) \
+ (SOLO_EREF_EXT_ADDR(__solo) + SOLO_EREF_EXT_AREA(__solo))
+#define SOLO_MP4E_EXT_SIZE(__solo) \
+ max((__solo->nr_chans * 0x00080000), \
+ min(((__solo->sdram_size - SOLO_MP4E_EXT_ADDR(__solo)) - \
+ __SOLO_JPEG_MIN_SIZE(__solo)), 0x00ff0000))
+
+#define __SOLO_JPEG_MIN_SIZE(__solo) (__solo->nr_chans * 0x00080000)
+#define SOLO_JPEG_EXT_ADDR(__solo) \
+ (SOLO_MP4E_EXT_ADDR(__solo) + SOLO_MP4E_EXT_SIZE(__solo))
+#define SOLO_JPEG_EXT_SIZE(__solo) \
+ max(__SOLO_JPEG_MIN_SIZE(__solo), \
+ min((__solo->sdram_size - SOLO_JPEG_EXT_ADDR(__solo)), 0x00ff0000))
+
+#define SOLO_SDRAM_END(__solo) \
+ (SOLO_JPEG_EXT_ADDR(__solo) + SOLO_JPEG_EXT_SIZE(__solo))
+
+#endif /* __SOLO6X10_OFFSETS_H */
diff --git a/drivers/staging/media/solo6x10/solo6x10-p2m.c b/drivers/staging/media/solo6x10/solo6x10-p2m.c
new file mode 100644
index 00000000000..7f2f2472655
--- /dev/null
+++ b/drivers/staging/media/solo6x10/solo6x10-p2m.c
@@ -0,0 +1,333 @@
+/*
+ * Copyright (C) 2010-2013 Bluecherry, LLC <http://www.bluecherrydvr.com>
+ *
+ * Original author:
+ * Ben Collins <bcollins@ubuntu.com>
+ *
+ * Additional work by:
+ * John Brooks <john.brooks@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/slab.h>
+
+#include "solo6x10.h"
+
+static int multi_p2m;
+module_param(multi_p2m, uint, 0644);
+MODULE_PARM_DESC(multi_p2m,
+ "Use multiple P2M DMA channels (default: no, 6010-only)");
+
+static int desc_mode;
+module_param(desc_mode, uint, 0644);
+MODULE_PARM_DESC(desc_mode,
+ "Allow use of descriptor mode DMA (default: no, 6010-only)");
+
+int solo_p2m_dma(struct solo_dev *solo_dev, int wr,
+ void *sys_addr, u32 ext_addr, u32 size,
+ int repeat, u32 ext_size)
+{
+ dma_addr_t dma_addr;
+ int ret;
+
+ if (WARN_ON_ONCE((unsigned long)sys_addr & 0x03))
+ return -EINVAL;
+ if (WARN_ON_ONCE(!size))
+ return -EINVAL;
+
+ dma_addr = pci_map_single(solo_dev->pdev, sys_addr, size,
+ wr ? PCI_DMA_TODEVICE : PCI_DMA_FROMDEVICE);
+ if (pci_dma_mapping_error(solo_dev->pdev, dma_addr))
+ return -ENOMEM;
+
+ ret = solo_p2m_dma_t(solo_dev, wr, dma_addr, ext_addr, size,
+ repeat, ext_size);
+
+ pci_unmap_single(solo_dev->pdev, dma_addr, size,
+ wr ? PCI_DMA_TODEVICE : PCI_DMA_FROMDEVICE);
+
+ return ret;
+}
+
+/* Mutex must be held for p2m_id before calling this!! */
+int solo_p2m_dma_desc(struct solo_dev *solo_dev,
+ struct solo_p2m_desc *desc, dma_addr_t desc_dma,
+ int desc_cnt)
+{
+ struct solo_p2m_dev *p2m_dev;
+ unsigned int timeout;
+ unsigned int config = 0;
+ int ret = 0;
+ int p2m_id = 0;
+
+ /* Get next ID. According to Softlogic, 6110 has problems on !=0 P2M */
+ if (solo_dev->type != SOLO_DEV_6110 && multi_p2m) {
+ p2m_id = atomic_inc_return(&solo_dev->p2m_count) % SOLO_NR_P2M;
+ if (p2m_id < 0)
+ p2m_id = -p2m_id;
+ }
+
+ p2m_dev = &solo_dev->p2m_dev[p2m_id];
+
+ if (mutex_lock_interruptible(&p2m_dev->mutex))
+ return -EINTR;
+
+ reinit_completion(&p2m_dev->completion);
+ p2m_dev->error = 0;
+
+ if (desc_cnt > 1 && solo_dev->type != SOLO_DEV_6110 && desc_mode) {
+ /* For 6010 with more than one desc, we can do a one-shot */
+ p2m_dev->desc_count = p2m_dev->desc_idx = 0;
+ config = solo_reg_read(solo_dev, SOLO_P2M_CONFIG(p2m_id));
+
+ solo_reg_write(solo_dev, SOLO_P2M_DES_ADR(p2m_id), desc_dma);
+ solo_reg_write(solo_dev, SOLO_P2M_DESC_ID(p2m_id), desc_cnt);
+ solo_reg_write(solo_dev, SOLO_P2M_CONFIG(p2m_id), config |
+ SOLO_P2M_DESC_MODE);
+ } else {
+ /* For single descriptors and 6110, we need to run each desc */
+ p2m_dev->desc_count = desc_cnt;
+ p2m_dev->desc_idx = 1;
+ p2m_dev->descs = desc;
+
+ solo_reg_write(solo_dev, SOLO_P2M_TAR_ADR(p2m_id),
+ desc[1].dma_addr);
+ solo_reg_write(solo_dev, SOLO_P2M_EXT_ADR(p2m_id),
+ desc[1].ext_addr);
+ solo_reg_write(solo_dev, SOLO_P2M_EXT_CFG(p2m_id),
+ desc[1].cfg);
+ solo_reg_write(solo_dev, SOLO_P2M_CONTROL(p2m_id),
+ desc[1].ctrl);
+ }
+
+ timeout = wait_for_completion_timeout(&p2m_dev->completion,
+ solo_dev->p2m_jiffies);
+
+ if (WARN_ON_ONCE(p2m_dev->error))
+ ret = -EIO;
+ else if (timeout == 0) {
+ solo_dev->p2m_timeouts++;
+ ret = -EAGAIN;
+ }
+
+ solo_reg_write(solo_dev, SOLO_P2M_CONTROL(p2m_id), 0);
+
+ /* Don't write here for the no_desc_mode case, because config is 0.
+ * We can't test no_desc_mode again, it might race. */
+ if (desc_cnt > 1 && solo_dev->type != SOLO_DEV_6110 && config)
+ solo_reg_write(solo_dev, SOLO_P2M_CONFIG(p2m_id), config);
+
+ mutex_unlock(&p2m_dev->mutex);
+
+ return ret;
+}
+
+void solo_p2m_fill_desc(struct solo_p2m_desc *desc, int wr,
+ dma_addr_t dma_addr, u32 ext_addr, u32 size,
+ int repeat, u32 ext_size)
+{
+ WARN_ON_ONCE(dma_addr & 0x03);
+ WARN_ON_ONCE(!size);
+
+ desc->cfg = SOLO_P2M_COPY_SIZE(size >> 2);
+ desc->ctrl = SOLO_P2M_BURST_SIZE(SOLO_P2M_BURST_256) |
+ (wr ? SOLO_P2M_WRITE : 0) | SOLO_P2M_TRANS_ON;
+
+ if (repeat) {
+ desc->cfg |= SOLO_P2M_EXT_INC(ext_size >> 2);
+ desc->ctrl |= SOLO_P2M_PCI_INC(size >> 2) |
+ SOLO_P2M_REPEAT(repeat);
+ }
+
+ desc->dma_addr = dma_addr;
+ desc->ext_addr = ext_addr;
+}
+
+int solo_p2m_dma_t(struct solo_dev *solo_dev, int wr,
+ dma_addr_t dma_addr, u32 ext_addr, u32 size,
+ int repeat, u32 ext_size)
+{
+ struct solo_p2m_desc desc[2];
+
+ solo_p2m_fill_desc(&desc[1], wr, dma_addr, ext_addr, size, repeat,
+ ext_size);
+
+ /* No need for desc_dma since we know it is a single-shot */
+ return solo_p2m_dma_desc(solo_dev, desc, 0, 1);
+}
+
+void solo_p2m_isr(struct solo_dev *solo_dev, int id)
+{
+ struct solo_p2m_dev *p2m_dev = &solo_dev->p2m_dev[id];
+ struct solo_p2m_desc *desc;
+
+ if (p2m_dev->desc_count <= p2m_dev->desc_idx) {
+ complete(&p2m_dev->completion);
+ return;
+ }
+
+ /* Setup next descriptor */
+ p2m_dev->desc_idx++;
+ desc = &p2m_dev->descs[p2m_dev->desc_idx];
+
+ solo_reg_write(solo_dev, SOLO_P2M_CONTROL(id), 0);
+ solo_reg_write(solo_dev, SOLO_P2M_TAR_ADR(id), desc->dma_addr);
+ solo_reg_write(solo_dev, SOLO_P2M_EXT_ADR(id), desc->ext_addr);
+ solo_reg_write(solo_dev, SOLO_P2M_EXT_CFG(id), desc->cfg);
+ solo_reg_write(solo_dev, SOLO_P2M_CONTROL(id), desc->ctrl);
+}
+
+void solo_p2m_error_isr(struct solo_dev *solo_dev)
+{
+ unsigned int err = solo_reg_read(solo_dev, SOLO_PCI_ERR);
+ struct solo_p2m_dev *p2m_dev;
+ int i;
+
+ if (!(err & (SOLO_PCI_ERR_P2M | SOLO_PCI_ERR_P2M_DESC)))
+ return;
+
+ for (i = 0; i < SOLO_NR_P2M; i++) {
+ p2m_dev = &solo_dev->p2m_dev[i];
+ p2m_dev->error = 1;
+ solo_reg_write(solo_dev, SOLO_P2M_CONTROL(i), 0);
+ complete(&p2m_dev->completion);
+ }
+}
+
+void solo_p2m_exit(struct solo_dev *solo_dev)
+{
+ int i;
+
+ for (i = 0; i < SOLO_NR_P2M; i++)
+ solo_irq_off(solo_dev, SOLO_IRQ_P2M(i));
+}
+
+static int solo_p2m_test(struct solo_dev *solo_dev, int base, int size)
+{
+ u32 *wr_buf;
+ u32 *rd_buf;
+ int i;
+ int ret = -EIO;
+ int order = get_order(size);
+
+ wr_buf = (u32 *)__get_free_pages(GFP_KERNEL, order);
+ if (wr_buf == NULL)
+ return -1;
+
+ rd_buf = (u32 *)__get_free_pages(GFP_KERNEL, order);
+ if (rd_buf == NULL) {
+ free_pages((unsigned long)wr_buf, order);
+ return -1;
+ }
+
+ for (i = 0; i < (size >> 3); i++)
+ *(wr_buf + i) = (i << 16) | (i + 1);
+
+ for (i = (size >> 3); i < (size >> 2); i++)
+ *(wr_buf + i) = ~((i << 16) | (i + 1));
+
+ memset(rd_buf, 0x55, size);
+
+ if (solo_p2m_dma(solo_dev, 1, wr_buf, base, size, 0, 0))
+ goto test_fail;
+
+ if (solo_p2m_dma(solo_dev, 0, rd_buf, base, size, 0, 0))
+ goto test_fail;
+
+ for (i = 0; i < (size >> 2); i++) {
+ if (*(wr_buf + i) != *(rd_buf + i))
+ goto test_fail;
+ }
+
+ ret = 0;
+
+test_fail:
+ free_pages((unsigned long)wr_buf, order);
+ free_pages((unsigned long)rd_buf, order);
+
+ return ret;
+}
+
+int solo_p2m_init(struct solo_dev *solo_dev)
+{
+ struct solo_p2m_dev *p2m_dev;
+ int i;
+
+ for (i = 0; i < SOLO_NR_P2M; i++) {
+ p2m_dev = &solo_dev->p2m_dev[i];
+
+ mutex_init(&p2m_dev->mutex);
+ init_completion(&p2m_dev->completion);
+
+ solo_reg_write(solo_dev, SOLO_P2M_CONTROL(i), 0);
+ solo_reg_write(solo_dev, SOLO_P2M_CONFIG(i),
+ SOLO_P2M_CSC_16BIT_565 |
+ SOLO_P2M_DESC_INTR_OPT |
+ SOLO_P2M_DMA_INTERVAL(0) |
+ SOLO_P2M_PCI_MASTER_MODE);
+ solo_irq_on(solo_dev, SOLO_IRQ_P2M(i));
+ }
+
+ /* Find correct SDRAM size */
+ for (solo_dev->sdram_size = 0, i = 2; i >= 0; i--) {
+ solo_reg_write(solo_dev, SOLO_DMA_CTRL,
+ SOLO_DMA_CTRL_REFRESH_CYCLE(1) |
+ SOLO_DMA_CTRL_SDRAM_SIZE(i) |
+ SOLO_DMA_CTRL_SDRAM_CLK_INVERT |
+ SOLO_DMA_CTRL_READ_CLK_SELECT |
+ SOLO_DMA_CTRL_LATENCY(1));
+
+ solo_reg_write(solo_dev, SOLO_SYS_CFG, solo_dev->sys_config |
+ SOLO_SYS_CFG_RESET);
+ solo_reg_write(solo_dev, SOLO_SYS_CFG, solo_dev->sys_config);
+
+ switch (i) {
+ case 2:
+ if (solo_p2m_test(solo_dev, 0x07ff0000, 0x00010000) ||
+ solo_p2m_test(solo_dev, 0x05ff0000, 0x00010000))
+ continue;
+ break;
+
+ case 1:
+ if (solo_p2m_test(solo_dev, 0x03ff0000, 0x00010000))
+ continue;
+ break;
+
+ default:
+ if (solo_p2m_test(solo_dev, 0x01ff0000, 0x00010000))
+ continue;
+ }
+
+ solo_dev->sdram_size = (32 << 20) << i;
+ break;
+ }
+
+ if (!solo_dev->sdram_size) {
+ dev_err(&solo_dev->pdev->dev, "Error detecting SDRAM size\n");
+ return -EIO;
+ }
+
+ if (SOLO_SDRAM_END(solo_dev) > solo_dev->sdram_size) {
+ dev_err(&solo_dev->pdev->dev,
+ "SDRAM is not large enough (%u < %u)\n",
+ solo_dev->sdram_size, SOLO_SDRAM_END(solo_dev));
+ return -EIO;
+ }
+
+ return 0;
+}
diff --git a/drivers/staging/media/solo6x10/solo6x10-regs.h b/drivers/staging/media/solo6x10/solo6x10-regs.h
new file mode 100644
index 00000000000..428f6c95118
--- /dev/null
+++ b/drivers/staging/media/solo6x10/solo6x10-regs.h
@@ -0,0 +1,639 @@
+/*
+ * Copyright (C) 2010-2013 Bluecherry, LLC <http://www.bluecherrydvr.com>
+ *
+ * Original author:
+ * Ben Collins <bcollins@ubuntu.com>
+ *
+ * Additional work by:
+ * John Brooks <john.brooks@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 __SOLO6X10_REGISTERS_H
+#define __SOLO6X10_REGISTERS_H
+
+#include "solo6x10-offsets.h"
+
+/* Global 6010 system configuration */
+#define SOLO_SYS_CFG 0x0000
+#define SOLO_SYS_CFG_FOUT_EN 0x00000001
+#define SOLO_SYS_CFG_PLL_BYPASS 0x00000002
+#define SOLO_SYS_CFG_PLL_PWDN 0x00000004
+#define SOLO_SYS_CFG_OUTDIV(__n) (((__n) & 0x003) << 3)
+#define SOLO_SYS_CFG_FEEDBACKDIV(__n) (((__n) & 0x1ff) << 5)
+#define SOLO_SYS_CFG_INPUTDIV(__n) (((__n) & 0x01f) << 14)
+#define SOLO_SYS_CFG_CLOCK_DIV 0x00080000
+#define SOLO_SYS_CFG_NCLK_DELAY(__n) (((__n) & 0x003) << 24)
+#define SOLO_SYS_CFG_PCLK_DELAY(__n) (((__n) & 0x00f) << 26)
+#define SOLO_SYS_CFG_SDRAM64BIT 0x40000000
+#define SOLO_SYS_CFG_RESET 0x80000000
+
+#define SOLO_DMA_CTRL 0x0004
+#define SOLO_DMA_CTRL_REFRESH_CYCLE(n) ((n)<<8)
+/* 0=16/32MB, 1=32/64MB, 2=64/128MB, 3=128/256MB */
+#define SOLO_DMA_CTRL_SDRAM_SIZE(n) ((n)<<6)
+#define SOLO_DMA_CTRL_SDRAM_CLK_INVERT (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)
+
+/* Some things we set in this are undocumented. Why Softlogic?!?! */
+#define SOLO_DMA_CTRL1 0x0008
+
+#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_MASK 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_PLL_CONFIG 0x0020 /* 6110 Only */
+
+#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 (SOLO_EEPROM_ACCESS_EN | SOLO_EEPROM_CS)
+
+#define SOLO_PCI_ERR 0x0070
+#define SOLO_PCI_ERR_FATAL 0x00000001
+#define SOLO_PCI_ERR_PARITY 0x00000002
+#define SOLO_PCI_ERR_TARGET 0x00000004
+#define SOLO_PCI_ERR_TIMEOUT 0x00000008
+#define SOLO_PCI_ERR_P2M 0x00000010
+#define SOLO_PCI_ERR_ATA 0x00000020
+#define SOLO_PCI_ERR_P2M_DESC 0x00000040
+#define SOLO_PCI_ERR_FSM0(__s) (((__s) >> 16) & 0x0f)
+#define SOLO_PCI_ERR_FSM1(__s) (((__s) >> 20) & 0x0f)
+#define SOLO_PCI_ERR_FSM2(__s) (((__s) >> 24) & 0x1f)
+
+#define SOLO_P2M_BASE 0x0080
+
+#define SOLO_P2M_CONFIG(n) (0x0080 + ((n)*0x20))
+#define SOLO_P2M_DMA_INTERVAL(n) ((n)<<6)/* N*32 clocks */
+#define SOLO_P2M_CSC_BYTE_REORDER (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_NAH (1<<17)
+#define SOLO_VO_USER_COLOR_SET_NAV (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_MPEG_SIZE_H(n) ((n)<<28) /* 6110 Only */
+#define SOLO_VE_JPEG_SIZE_H(n) ((n)<<20) /* 6110 Only */
+#define SOLO_VE_INSERT_INDEX_JPEG (1<<19) /* 6110 Only */
+
+#define SOLO_VE_WMRK_POLY 0x061C
+#define SOLO_VE_VMRK_INIT_KEY 0x0620
+#define SOLO_VE_WMRK_STRL 0x0624
+#define SOLO_VE_ENCRYP_POLY 0x0628
+#define SOLO_VE_ENCRYP_INIT 0x062C
+#define SOLO_VE_ATTR 0x0630
+#define SOLO_VE_LITTLE_ENDIAN (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_COMPT_MOT 0x0634 /* 6110 Only */
+
+#define SOLO_VE_STATE(n) (0x0640+((n)*4))
+
+#define SOLO_VE_JPEG_QP_TBL 0x0670
+#define SOLO_VE_JPEG_QP_CH_L 0x0674
+#define SOLO_VE_JPEG_QP_CH_H 0x0678
+#define SOLO_VE_JPEG_CFG 0x067C
+#define SOLO_VE_JPEG_CTRL 0x0680
+#define SOLO_VE_CODE_ENCRYPT 0x0684 /* 6110 Only */
+#define SOLO_VE_JPEG_CFG1 0x0688 /* 6110 Only */
+#define SOLO_VE_WMRK_ENABLE 0x068C /* 6110 Only */
+#define SOLO_VE_OSD_CH 0x0690
+#define SOLO_VE_OSD_BASE 0x0694
+#define SOLO_VE_OSD_CLR 0x0698
+#define SOLO_VE_OSD_OPT 0x069C
+#define SOLO_VE_OSD_V_DOUBLE (1<<16) /* 6110 Only */
+#define SOLO_VE_OSD_H_SHADOW (1<<15)
+#define SOLO_VE_OSD_V_SHADOW (1<<14)
+#define SOLO_VE_OSD_H_OFFSET(n) ((n & 0x7f)<<7)
+#define SOLO_VE_OSD_V_OFFSET(n) (n & 0x7f)
+
+#define SOLO_VE_CH_INTL(ch) (0x0700+((ch)*4))
+#define SOLO_VE_CH_MOT(ch) (0x0740+((ch)*4))
+#define SOLO_VE_CH_QP(ch) (0x0780+((ch)*4))
+#define SOLO_VE_CH_QP_E(ch) (0x07C0+((ch)*4))
+#define SOLO_VE_CH_GOP(ch) (0x0800+((ch)*4))
+#define SOLO_VE_CH_GOP_E(ch) (0x0840+((ch)*4))
+#define SOLO_VE_CH_REF_BASE(ch) (0x0880+((ch)*4))
+#define SOLO_VE_CH_REF_BASE_E(ch) (0x08C0+((ch)*4))
+
+#define SOLO_VE_MPEG4_QUE(n) (0x0A00+((n)*8))
+#define SOLO_VE_JPEG_QUE(n) (0x0A04+((n)*8))
+
+#define SOLO_VD_CFG0 0x0900
+#define SOLO_VD_CFG_NO_WRITE_NO_WINDOW (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_USEC 0x0be8
+#define SOLO_TIMER_SEC 0x0bec
+#define SOLO_TIMER_USEC_LSB 0x0d20 /* 6110 Only */
+
+#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
+
+/*
+ * Watchdog configuration
+ */
+#define SOLO_WATCHDOG 0x0be4
+#define SOLO_WATCHDOG_SET(status, sec) (status << 8 | (sec & 0xff))
+
+#endif /* __SOLO6X10_REGISTERS_H */
diff --git a/drivers/staging/media/solo6x10/solo6x10-tw28.c b/drivers/staging/media/solo6x10/solo6x10-tw28.c
new file mode 100644
index 00000000000..36daa1720b5
--- /dev/null
+++ b/drivers/staging/media/solo6x10/solo6x10-tw28.c
@@ -0,0 +1,874 @@
+/*
+ * Copyright (C) 2010-2013 Bluecherry, LLC <http://www.bluecherrydvr.com>
+ *
+ * Original author:
+ * Ben Collins <bcollins@ubuntu.com>
+ *
+ * Additional work by:
+ * John Brooks <john.brooks@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/delay.h>
+
+#include "solo6x10.h"
+#include "solo6x10-tw28.h"
+
+#define DEFAULT_HDELAY_NTSC (32 - 8)
+#define DEFAULT_HACTIVE_NTSC (720 + 16)
+#define DEFAULT_VDELAY_NTSC (7 - 2)
+#define DEFAULT_VACTIVE_NTSC (240 + 4)
+
+#define DEFAULT_HDELAY_PAL (32 + 4)
+#define DEFAULT_HACTIVE_PAL (864-DEFAULT_HDELAY_PAL)
+#define DEFAULT_VDELAY_PAL (6)
+#define DEFAULT_VACTIVE_PAL (312-DEFAULT_VDELAY_PAL)
+
+
+static const u8 tbl_tw2864_ntsc_template[] = {
+ 0x00, 0xf0, 0x70, 0x30, 0x80, 0x80, 0x00, 0x02, /* 0x00 */
+ 0x12, 0xf5, 0x0c, 0xd0, 0x00, 0x00, 0x00, 0x7f,
+ 0x00, 0xf0, 0x70, 0x30, 0x80, 0x80, 0x00, 0x02, /* 0x10 */
+ 0x12, 0xf5, 0x0c, 0xd0, 0x00, 0x00, 0x00, 0x7f,
+ 0x00, 0xf0, 0x70, 0x30, 0x80, 0x80, 0x00, 0x02, /* 0x20 */
+ 0x12, 0xf5, 0x0c, 0xd0, 0x00, 0x00, 0x00, 0x7f,
+ 0x00, 0xf0, 0x70, 0x30, 0x80, 0x80, 0x00, 0x02, /* 0x30 */
+ 0x12, 0xf5, 0x0c, 0xd0, 0x00, 0x00, 0x00, 0x7f,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x40 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x50 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x60 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x70 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA3, 0x00,
+ 0x00, 0x02, 0x00, 0xcc, 0x00, 0x80, 0x44, 0x50, /* 0x80 */
+ 0x22, 0x01, 0xd8, 0xbc, 0xb8, 0x44, 0x38, 0x00,
+ 0x00, 0x78, 0x72, 0x3e, 0x14, 0xa5, 0xe4, 0x05, /* 0x90 */
+ 0x00, 0x28, 0x44, 0x44, 0xa0, 0x88, 0x5a, 0x01,
+ 0x08, 0x08, 0x08, 0x08, 0x1a, 0x1a, 0x1a, 0x1a, /* 0xa0 */
+ 0x00, 0x00, 0x00, 0xf0, 0xf0, 0xf0, 0xf0, 0x44,
+ 0x44, 0x0a, 0x00, 0xff, 0xef, 0xef, 0xef, 0xef, /* 0xb0 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0xc0 */
+ 0x00, 0x00, 0x55, 0x00, 0xb1, 0xe4, 0x40, 0x00,
+ 0x77, 0x77, 0x01, 0x13, 0x57, 0x9b, 0xdf, 0x20, /* 0xd0 */
+ 0x64, 0xa8, 0xec, 0xc1, 0x0f, 0x11, 0x11, 0x81,
+ 0x00, 0xe0, 0xbb, 0xbb, 0x00, 0x11, 0x00, 0x00, /* 0xe0 */
+ 0x11, 0x00, 0x00, 0x11, 0x00, 0x00, 0x11, 0x00,
+ 0x83, 0xb5, 0x09, 0x78, 0x85, 0x00, 0x01, 0x20, /* 0xf0 */
+ 0x64, 0x11, 0x40, 0xaf, 0xff, 0x00, 0x00, 0x00,
+};
+
+static const u8 tbl_tw2864_pal_template[] = {
+ 0x00, 0xf0, 0x70, 0x30, 0x80, 0x80, 0x00, 0x12, /* 0x00 */
+ 0x18, 0xf5, 0x0c, 0xd0, 0x00, 0x00, 0x01, 0x7f,
+ 0x00, 0xf0, 0x70, 0x30, 0x80, 0x80, 0x00, 0x12, /* 0x10 */
+ 0x18, 0xf5, 0x0c, 0xd0, 0x00, 0x00, 0x01, 0x7f,
+ 0x00, 0xf0, 0x70, 0x30, 0x80, 0x80, 0x00, 0x12, /* 0x20 */
+ 0x18, 0xf5, 0x0c, 0xd0, 0x00, 0x00, 0x01, 0x7f,
+ 0x00, 0xf0, 0x70, 0x30, 0x80, 0x80, 0x00, 0x12, /* 0x30 */
+ 0x18, 0xf5, 0x0c, 0xd0, 0x00, 0x00, 0x01, 0x7f,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x40 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x50 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x60 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x70 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA3, 0x00,
+ 0x00, 0x02, 0x00, 0xcc, 0x00, 0x80, 0x44, 0x50, /* 0x80 */
+ 0x22, 0x01, 0xd8, 0xbc, 0xb8, 0x44, 0x38, 0x00,
+ 0x00, 0x78, 0x72, 0x3e, 0x14, 0xa5, 0xe4, 0x05, /* 0x90 */
+ 0x00, 0x28, 0x44, 0x44, 0xa0, 0x90, 0x5a, 0x01,
+ 0x0a, 0x0a, 0x0a, 0x0a, 0x1a, 0x1a, 0x1a, 0x1a, /* 0xa0 */
+ 0x00, 0x00, 0x00, 0xf0, 0xf0, 0xf0, 0xf0, 0x44,
+ 0x44, 0x0a, 0x00, 0xff, 0xef, 0xef, 0xef, 0xef, /* 0xb0 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0xc0 */
+ 0x00, 0x00, 0x55, 0x00, 0xb1, 0xe4, 0x40, 0x00,
+ 0x77, 0x77, 0x01, 0x13, 0x57, 0x9b, 0xdf, 0x20, /* 0xd0 */
+ 0x64, 0xa8, 0xec, 0xc1, 0x0f, 0x11, 0x11, 0x81,
+ 0x00, 0xe0, 0xbb, 0xbb, 0x00, 0x11, 0x00, 0x00, /* 0xe0 */
+ 0x11, 0x00, 0x00, 0x11, 0x00, 0x00, 0x11, 0x00,
+ 0x83, 0xb5, 0x09, 0x00, 0xa0, 0x00, 0x01, 0x20, /* 0xf0 */
+ 0x64, 0x11, 0x40, 0xaf, 0xff, 0x00, 0x00, 0x00,
+};
+
+static const u8 tbl_tw2865_ntsc_template[] = {
+ 0x00, 0xf0, 0x70, 0x30, 0x80, 0x80, 0x00, 0x02, /* 0x00 */
+ 0x12, 0xff, 0x09, 0xd0, 0x00, 0x00, 0x00, 0x7f,
+ 0x00, 0xf0, 0x70, 0x30, 0x80, 0x80, 0x00, 0x02, /* 0x10 */
+ 0x12, 0xff, 0x09, 0xd0, 0x00, 0x00, 0x00, 0x7f,
+ 0x00, 0xf0, 0x70, 0x30, 0x80, 0x80, 0x00, 0x02, /* 0x20 */
+ 0x12, 0xff, 0x09, 0xd0, 0x00, 0x00, 0x00, 0x7f,
+ 0x00, 0xf0, 0x70, 0x48, 0x80, 0x80, 0x00, 0x02, /* 0x30 */
+ 0x12, 0xff, 0x09, 0xd0, 0x00, 0x00, 0x00, 0x7f,
+ 0x00, 0x00, 0x90, 0x68, 0x00, 0x38, 0x80, 0x80, /* 0x40 */
+ 0x80, 0x80, 0x77, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x50 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x45, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x60 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x43,
+ 0x08, 0x00, 0x00, 0x01, 0xf1, 0x03, 0xEF, 0x03, /* 0x70 */
+ 0xE9, 0x03, 0xD9, 0x15, 0x15, 0xE4, 0xA3, 0x80,
+ 0x00, 0x02, 0x00, 0xCC, 0x00, 0x80, 0x44, 0x50, /* 0x80 */
+ 0x22, 0x01, 0xD8, 0xBC, 0xB8, 0x44, 0x38, 0x00,
+ 0x00, 0x78, 0x44, 0x3D, 0x14, 0xA5, 0xE0, 0x05, /* 0x90 */
+ 0x00, 0x28, 0x44, 0x44, 0xA0, 0x90, 0x52, 0x13,
+ 0x08, 0x08, 0x08, 0x08, 0x1A, 0x1A, 0x1B, 0x1A, /* 0xa0 */
+ 0x00, 0x00, 0x00, 0xF0, 0xF0, 0xF0, 0xF0, 0x44,
+ 0x44, 0x4A, 0x00, 0xFF, 0xEF, 0xEF, 0xEF, 0xEF, /* 0xb0 */
+ 0xFF, 0xE7, 0xE9, 0xE9, 0xEB, 0xFF, 0xD6, 0xD8,
+ 0xD8, 0xD7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0xc0 */
+ 0x00, 0x00, 0x55, 0x00, 0xE4, 0x39, 0x00, 0x80,
+ 0x77, 0x77, 0x03, 0x20, 0x57, 0x9b, 0xdf, 0x31, /* 0xd0 */
+ 0x64, 0xa8, 0xec, 0xd1, 0x0f, 0x11, 0x11, 0x81,
+ 0x10, 0xC0, 0xAA, 0xAA, 0x00, 0x11, 0x00, 0x00, /* 0xe0 */
+ 0x11, 0x00, 0x00, 0x11, 0x00, 0x00, 0x11, 0x00,
+ 0x83, 0xB5, 0x09, 0x78, 0x85, 0x00, 0x01, 0x20, /* 0xf0 */
+ 0x64, 0x51, 0x40, 0xaf, 0xFF, 0xF0, 0x00, 0xC0,
+};
+
+static const u8 tbl_tw2865_pal_template[] = {
+ 0x00, 0xf0, 0x70, 0x30, 0x80, 0x80, 0x00, 0x12, /* 0x00 */
+ 0x11, 0xff, 0x01, 0xc3, 0x00, 0x00, 0x01, 0x7f,
+ 0x00, 0xf0, 0x70, 0x30, 0x80, 0x80, 0x00, 0x12, /* 0x10 */
+ 0x11, 0xff, 0x01, 0xc3, 0x00, 0x00, 0x01, 0x7f,
+ 0x00, 0xf0, 0x70, 0x30, 0x80, 0x80, 0x00, 0x12, /* 0x20 */
+ 0x11, 0xff, 0x01, 0xc3, 0x00, 0x00, 0x01, 0x7f,
+ 0x00, 0xf0, 0x70, 0x30, 0x80, 0x80, 0x00, 0x12, /* 0x30 */
+ 0x11, 0xff, 0x01, 0xc3, 0x00, 0x00, 0x01, 0x7f,
+ 0x00, 0x94, 0x90, 0x48, 0x00, 0x38, 0x7F, 0x80, /* 0x40 */
+ 0x80, 0x80, 0x77, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x50 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x45, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x60 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x43,
+ 0x08, 0x00, 0x00, 0x01, 0xf1, 0x03, 0xEF, 0x03, /* 0x70 */
+ 0xEA, 0x03, 0xD9, 0x15, 0x15, 0xE4, 0xA3, 0x80,
+ 0x00, 0x02, 0x00, 0xCC, 0x00, 0x80, 0x44, 0x50, /* 0x80 */
+ 0x22, 0x01, 0xD8, 0xBC, 0xB8, 0x44, 0x38, 0x00,
+ 0x00, 0x78, 0x44, 0x3D, 0x14, 0xA5, 0xE0, 0x05, /* 0x90 */
+ 0x00, 0x28, 0x44, 0x44, 0xA0, 0x90, 0x52, 0x13,
+ 0x08, 0x08, 0x08, 0x08, 0x1A, 0x1A, 0x1A, 0x1A, /* 0xa0 */
+ 0x00, 0x00, 0x00, 0xF0, 0xF0, 0xF0, 0xF0, 0x44,
+ 0x44, 0x4A, 0x00, 0xFF, 0xEF, 0xEF, 0xEF, 0xEF, /* 0xb0 */
+ 0xFF, 0xE7, 0xE9, 0xE9, 0xE9, 0xFF, 0xD7, 0xD8,
+ 0xD9, 0xD8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0xc0 */
+ 0x00, 0x00, 0x55, 0x00, 0xE4, 0x39, 0x00, 0x80,
+ 0x77, 0x77, 0x03, 0x20, 0x57, 0x9b, 0xdf, 0x31, /* 0xd0 */
+ 0x64, 0xa8, 0xec, 0xd1, 0x0f, 0x11, 0x11, 0x81,
+ 0x10, 0xC0, 0xAA, 0xAA, 0x00, 0x11, 0x00, 0x00, /* 0xe0 */
+ 0x11, 0x00, 0x00, 0x11, 0x00, 0x00, 0x11, 0x00,
+ 0x83, 0xB5, 0x09, 0x00, 0xA0, 0x00, 0x01, 0x20, /* 0xf0 */
+ 0x64, 0x51, 0x40, 0xaf, 0xFF, 0xF0, 0x00, 0xC0,
+};
+
+#define is_tw286x(__solo, __id) (!(__solo->tw2815 & (1 << __id)))
+
+static u8 tw_readbyte(struct solo_dev *solo_dev, int chip_id, u8 tw6x_off,
+ u8 tw_off)
+{
+ if (is_tw286x(solo_dev, chip_id))
+ return solo_i2c_readbyte(solo_dev, SOLO_I2C_TW,
+ TW_CHIP_OFFSET_ADDR(chip_id),
+ tw6x_off);
+ else
+ return solo_i2c_readbyte(solo_dev, SOLO_I2C_TW,
+ TW_CHIP_OFFSET_ADDR(chip_id),
+ tw_off);
+}
+
+static void tw_writebyte(struct solo_dev *solo_dev, int chip_id,
+ u8 tw6x_off, u8 tw_off, u8 val)
+{
+ if (is_tw286x(solo_dev, chip_id))
+ solo_i2c_writebyte(solo_dev, SOLO_I2C_TW,
+ TW_CHIP_OFFSET_ADDR(chip_id),
+ tw6x_off, val);
+ else
+ solo_i2c_writebyte(solo_dev, SOLO_I2C_TW,
+ TW_CHIP_OFFSET_ADDR(chip_id),
+ tw_off, val);
+}
+
+static void tw_write_and_verify(struct solo_dev *solo_dev, u8 addr, u8 off,
+ u8 val)
+{
+ int i;
+
+ for (i = 0; i < 5; i++) {
+ u8 rval = solo_i2c_readbyte(solo_dev, SOLO_I2C_TW, addr, off);
+ if (rval == val)
+ return;
+
+ solo_i2c_writebyte(solo_dev, SOLO_I2C_TW, addr, off, val);
+ msleep_interruptible(1);
+ }
+
+/* printk("solo6x10/tw28: Error writing register: %02x->%02x [%02x]\n", */
+/* addr, off, val); */
+}
+
+static int tw2865_setup(struct solo_dev *solo_dev, u8 dev_addr)
+{
+ u8 tbl_tw2865_common[256];
+ int i;
+
+ if (solo_dev->video_type == SOLO_VO_FMT_TYPE_PAL)
+ memcpy(tbl_tw2865_common, tbl_tw2865_pal_template,
+ sizeof(tbl_tw2865_common));
+ else
+ memcpy(tbl_tw2865_common, tbl_tw2865_ntsc_template,
+ sizeof(tbl_tw2865_common));
+
+ /* ALINK Mode */
+ if (solo_dev->nr_chans == 4) {
+ tbl_tw2865_common[0xd2] = 0x01;
+ tbl_tw2865_common[0xcf] = 0x00;
+ } else if (solo_dev->nr_chans == 8) {
+ tbl_tw2865_common[0xd2] = 0x02;
+ if (dev_addr == TW_CHIP_OFFSET_ADDR(1))
+ tbl_tw2865_common[0xcf] = 0x80;
+ } else if (solo_dev->nr_chans == 16) {
+ tbl_tw2865_common[0xd2] = 0x03;
+ if (dev_addr == TW_CHIP_OFFSET_ADDR(1))
+ tbl_tw2865_common[0xcf] = 0x83;
+ else if (dev_addr == TW_CHIP_OFFSET_ADDR(2))
+ tbl_tw2865_common[0xcf] = 0x83;
+ else if (dev_addr == TW_CHIP_OFFSET_ADDR(3))
+ tbl_tw2865_common[0xcf] = 0x80;
+ }
+
+ for (i = 0; i < 0xff; i++) {
+ /* Skip read only registers */
+ switch (i) {
+ case 0xb8 ... 0xc1:
+ case 0xc4 ... 0xc7:
+ case 0xfd:
+ continue;
+ }
+ switch (i & ~0x30) {
+ case 0x00:
+ case 0x0c ... 0x0d:
+ continue;
+ }
+
+ tw_write_and_verify(solo_dev, dev_addr, i,
+ tbl_tw2865_common[i]);
+ }
+
+ return 0;
+}
+
+static int tw2864_setup(struct solo_dev *solo_dev, u8 dev_addr)
+{
+ u8 tbl_tw2864_common[256];
+ int i;
+
+ if (solo_dev->video_type == SOLO_VO_FMT_TYPE_PAL)
+ memcpy(tbl_tw2864_common, tbl_tw2864_pal_template,
+ sizeof(tbl_tw2864_common));
+ else
+ memcpy(tbl_tw2864_common, tbl_tw2864_ntsc_template,
+ sizeof(tbl_tw2864_common));
+
+ if (solo_dev->tw2865 == 0) {
+ /* IRQ Mode */
+ if (solo_dev->nr_chans == 4) {
+ tbl_tw2864_common[0xd2] = 0x01;
+ tbl_tw2864_common[0xcf] = 0x00;
+ } else if (solo_dev->nr_chans == 8) {
+ tbl_tw2864_common[0xd2] = 0x02;
+ if (dev_addr == TW_CHIP_OFFSET_ADDR(0))
+ tbl_tw2864_common[0xcf] = 0x43;
+ else if (dev_addr == TW_CHIP_OFFSET_ADDR(1))
+ tbl_tw2864_common[0xcf] = 0x40;
+ } else if (solo_dev->nr_chans == 16) {
+ tbl_tw2864_common[0xd2] = 0x03;
+ if (dev_addr == TW_CHIP_OFFSET_ADDR(0))
+ tbl_tw2864_common[0xcf] = 0x43;
+ else if (dev_addr == TW_CHIP_OFFSET_ADDR(1))
+ tbl_tw2864_common[0xcf] = 0x43;
+ else if (dev_addr == TW_CHIP_OFFSET_ADDR(2))
+ tbl_tw2864_common[0xcf] = 0x43;
+ else if (dev_addr == TW_CHIP_OFFSET_ADDR(3))
+ tbl_tw2864_common[0xcf] = 0x40;
+ }
+ } else {
+ /* ALINK Mode. Assumes that the first tw28xx is a
+ * 2865 and these are in cascade. */
+ for (i = 0; i <= 4; i++)
+ tbl_tw2864_common[0x08 | i << 4] = 0x12;
+
+ if (solo_dev->nr_chans == 8) {
+ tbl_tw2864_common[0xd2] = 0x02;
+ if (dev_addr == TW_CHIP_OFFSET_ADDR(1))
+ tbl_tw2864_common[0xcf] = 0x80;
+ } else if (solo_dev->nr_chans == 16) {
+ tbl_tw2864_common[0xd2] = 0x03;
+ if (dev_addr == TW_CHIP_OFFSET_ADDR(1))
+ tbl_tw2864_common[0xcf] = 0x83;
+ else if (dev_addr == TW_CHIP_OFFSET_ADDR(2))
+ tbl_tw2864_common[0xcf] = 0x83;
+ else if (dev_addr == TW_CHIP_OFFSET_ADDR(3))
+ tbl_tw2864_common[0xcf] = 0x80;
+ }
+ }
+
+ for (i = 0; i < 0xff; i++) {
+ /* Skip read only registers */
+ switch (i) {
+ case 0xb8 ... 0xc1:
+ case 0xfd:
+ continue;
+ }
+ switch (i & ~0x30) {
+ case 0x00:
+ case 0x0c:
+ case 0x0d:
+ continue;
+ }
+
+ tw_write_and_verify(solo_dev, dev_addr, i,
+ tbl_tw2864_common[i]);
+ }
+
+ return 0;
+}
+
+static int tw2815_setup(struct solo_dev *solo_dev, u8 dev_addr)
+{
+ u8 tbl_ntsc_tw2815_common[] = {
+ 0x00, 0xc8, 0x20, 0xd0, 0x06, 0xf0, 0x08, 0x80,
+ 0x80, 0x80, 0x80, 0x02, 0x06, 0x00, 0x11,
+ };
+
+ u8 tbl_pal_tw2815_common[] = {
+ 0x00, 0x88, 0x20, 0xd0, 0x05, 0x20, 0x28, 0x80,
+ 0x80, 0x80, 0x80, 0x82, 0x06, 0x00, 0x11,
+ };
+
+ u8 tbl_tw2815_sfr[] = {
+ 0x00, 0x00, 0x00, 0xc0, 0x45, 0xa0, 0xd0, 0x2f, /* 0x00 */
+ 0x64, 0x80, 0x80, 0x82, 0x82, 0x00, 0x00, 0x00,
+ 0x00, 0x0f, 0x05, 0x00, 0x00, 0x80, 0x06, 0x00, /* 0x10 */
+ 0x00, 0x00, 0x00, 0xff, 0x8f, 0x00, 0x00, 0x00,
+ 0x88, 0x88, 0xc0, 0x00, 0x20, 0x64, 0xa8, 0xec, /* 0x20 */
+ 0x31, 0x75, 0xb9, 0xfd, 0x00, 0x00, 0x88, 0x88,
+ 0x88, 0x11, 0x00, 0x88, 0x88, 0x00, /* 0x30 */
+ };
+ u8 *tbl_tw2815_common;
+ int i;
+ int ch;
+
+ tbl_ntsc_tw2815_common[0x06] = 0;
+
+ /* Horizontal Delay Control */
+ tbl_ntsc_tw2815_common[0x02] = DEFAULT_HDELAY_NTSC & 0xff;
+ tbl_ntsc_tw2815_common[0x06] |= 0x03 & (DEFAULT_HDELAY_NTSC >> 8);
+
+ /* Horizontal Active Control */
+ tbl_ntsc_tw2815_common[0x03] = DEFAULT_HACTIVE_NTSC & 0xff;
+ tbl_ntsc_tw2815_common[0x06] |=
+ ((0x03 & (DEFAULT_HACTIVE_NTSC >> 8)) << 2);
+
+ /* Vertical Delay Control */
+ tbl_ntsc_tw2815_common[0x04] = DEFAULT_VDELAY_NTSC & 0xff;
+ tbl_ntsc_tw2815_common[0x06] |=
+ ((0x01 & (DEFAULT_VDELAY_NTSC >> 8)) << 4);
+
+ /* Vertical Active Control */
+ tbl_ntsc_tw2815_common[0x05] = DEFAULT_VACTIVE_NTSC & 0xff;
+ tbl_ntsc_tw2815_common[0x06] |=
+ ((0x01 & (DEFAULT_VACTIVE_NTSC >> 8)) << 5);
+
+ tbl_pal_tw2815_common[0x06] = 0;
+
+ /* Horizontal Delay Control */
+ tbl_pal_tw2815_common[0x02] = DEFAULT_HDELAY_PAL & 0xff;
+ tbl_pal_tw2815_common[0x06] |= 0x03 & (DEFAULT_HDELAY_PAL >> 8);
+
+ /* Horizontal Active Control */
+ tbl_pal_tw2815_common[0x03] = DEFAULT_HACTIVE_PAL & 0xff;
+ tbl_pal_tw2815_common[0x06] |=
+ ((0x03 & (DEFAULT_HACTIVE_PAL >> 8)) << 2);
+
+ /* Vertical Delay Control */
+ tbl_pal_tw2815_common[0x04] = DEFAULT_VDELAY_PAL & 0xff;
+ tbl_pal_tw2815_common[0x06] |=
+ ((0x01 & (DEFAULT_VDELAY_PAL >> 8)) << 4);
+
+ /* Vertical Active Control */
+ tbl_pal_tw2815_common[0x05] = DEFAULT_VACTIVE_PAL & 0xff;
+ tbl_pal_tw2815_common[0x06] |=
+ ((0x01 & (DEFAULT_VACTIVE_PAL >> 8)) << 5);
+
+ tbl_tw2815_common =
+ (solo_dev->video_type == SOLO_VO_FMT_TYPE_NTSC) ?
+ tbl_ntsc_tw2815_common : tbl_pal_tw2815_common;
+
+ /* Dual ITU-R BT.656 format */
+ tbl_tw2815_common[0x0d] |= 0x04;
+
+ /* Audio configuration */
+ tbl_tw2815_sfr[0x62 - 0x40] &= ~(3 << 6);
+
+ if (solo_dev->nr_chans == 4) {
+ tbl_tw2815_sfr[0x63 - 0x40] |= 1;
+ tbl_tw2815_sfr[0x62 - 0x40] |= 3 << 6;
+ } else if (solo_dev->nr_chans == 8) {
+ tbl_tw2815_sfr[0x63 - 0x40] |= 2;
+ if (dev_addr == TW_CHIP_OFFSET_ADDR(0))
+ tbl_tw2815_sfr[0x62 - 0x40] |= 1 << 6;
+ else if (dev_addr == TW_CHIP_OFFSET_ADDR(1))
+ tbl_tw2815_sfr[0x62 - 0x40] |= 2 << 6;
+ } else if (solo_dev->nr_chans == 16) {
+ tbl_tw2815_sfr[0x63 - 0x40] |= 3;
+ if (dev_addr == TW_CHIP_OFFSET_ADDR(0))
+ tbl_tw2815_sfr[0x62 - 0x40] |= 1 << 6;
+ else if (dev_addr == TW_CHIP_OFFSET_ADDR(1))
+ tbl_tw2815_sfr[0x62 - 0x40] |= 0 << 6;
+ else if (dev_addr == TW_CHIP_OFFSET_ADDR(2))
+ tbl_tw2815_sfr[0x62 - 0x40] |= 0 << 6;
+ else if (dev_addr == TW_CHIP_OFFSET_ADDR(3))
+ tbl_tw2815_sfr[0x62 - 0x40] |= 2 << 6;
+ }
+
+ /* Output mode of R_ADATM pin (0 mixing, 1 record) */
+ /* tbl_tw2815_sfr[0x63 - 0x40] |= 0 << 2; */
+
+ /* 8KHz, used to be 16KHz, but changed for remote client compat */
+ tbl_tw2815_sfr[0x62 - 0x40] |= 0 << 2;
+ tbl_tw2815_sfr[0x6c - 0x40] |= 0 << 2;
+
+ /* Playback of right channel */
+ tbl_tw2815_sfr[0x6c - 0x40] |= 1 << 5;
+
+ /* Reserved value (XXX ??) */
+ tbl_tw2815_sfr[0x5c - 0x40] |= 1 << 5;
+
+ /* Analog output gain and mix ratio playback on full */
+ tbl_tw2815_sfr[0x70 - 0x40] |= 0xff;
+ /* Select playback audio and mute all except */
+ tbl_tw2815_sfr[0x71 - 0x40] |= 0x10;
+ tbl_tw2815_sfr[0x6d - 0x40] |= 0x0f;
+
+ /* End of audio configuration */
+
+ for (ch = 0; ch < 4; ch++) {
+ tbl_tw2815_common[0x0d] &= ~3;
+ switch (ch) {
+ case 0:
+ tbl_tw2815_common[0x0d] |= 0x21;
+ break;
+ case 1:
+ tbl_tw2815_common[0x0d] |= 0x20;
+ break;
+ case 2:
+ tbl_tw2815_common[0x0d] |= 0x23;
+ break;
+ case 3:
+ tbl_tw2815_common[0x0d] |= 0x22;
+ break;
+ }
+
+ for (i = 0; i < 0x0f; i++) {
+ if (i == 0x00)
+ continue; /* read-only */
+ solo_i2c_writebyte(solo_dev, SOLO_I2C_TW,
+ dev_addr, (ch * 0x10) + i,
+ tbl_tw2815_common[i]);
+ }
+ }
+
+ for (i = 0x40; i < 0x76; i++) {
+ /* Skip read-only and nop registers */
+ if (i == 0x40 || i == 0x59 || i == 0x5a ||
+ i == 0x5d || i == 0x5e || i == 0x5f)
+ continue;
+
+ solo_i2c_writebyte(solo_dev, SOLO_I2C_TW, dev_addr, i,
+ tbl_tw2815_sfr[i - 0x40]);
+ }
+
+ return 0;
+}
+
+#define FIRST_ACTIVE_LINE 0x0008
+#define LAST_ACTIVE_LINE 0x0102
+
+static void saa712x_write_regs(struct solo_dev *dev, const uint8_t *vals,
+ int start, int n)
+{
+ for (; start < n; start++, vals++) {
+ /* Skip read-only registers */
+ switch (start) {
+ /* case 0x00 ... 0x25: */
+ case 0x2e ... 0x37:
+ case 0x60:
+ case 0x7d:
+ continue;
+ }
+ solo_i2c_writebyte(dev, SOLO_I2C_SAA, 0x46, start, *vals);
+ }
+}
+
+#define SAA712x_reg7c (0x80 | ((LAST_ACTIVE_LINE & 0x100) >> 2) \
+ | ((FIRST_ACTIVE_LINE & 0x100) >> 4))
+
+static void saa712x_setup(struct solo_dev *dev)
+{
+ const int reg_start = 0x26;
+ const uint8_t saa7128_regs_ntsc[] = {
+ /* :0x26 */
+ 0x0d, 0x00,
+ /* :0x28 */
+ 0x59, 0x1d, 0x75, 0x3f, 0x06, 0x3f,
+ /* :0x2e XXX: read-only */
+ 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ /* :0x38 */
+ 0x1a, 0x1a, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00,
+ /* :0x40 */
+ 0x00, 0x00, 0x00, 0x68, 0x10, 0x97, 0x4c, 0x18,
+ 0x9b, 0x93, 0x9f, 0xff, 0x7c, 0x34, 0x3f, 0x3f,
+ /* :0x50 */
+ 0x3f, 0x83, 0x83, 0x80, 0x0d, 0x0f, 0xc3, 0x06,
+ 0x02, 0x80, 0x71, 0x77, 0xa7, 0x67, 0x66, 0x2e,
+ /* :0x60 */
+ 0x7b, 0x11, 0x4f, 0x1f, 0x7c, 0xf0, 0x21, 0x77,
+ 0x41, 0x88, 0x41, 0x52, 0xed, 0x10, 0x10, 0x00,
+ /* :0x70 */
+ 0x41, 0xc3, 0x00, 0x3e, 0xb8, 0x02, 0x00, 0x00,
+ 0x00, 0x00, FIRST_ACTIVE_LINE, LAST_ACTIVE_LINE & 0xff,
+ SAA712x_reg7c, 0x00, 0xff, 0xff,
+ }, saa7128_regs_pal[] = {
+ /* :0x26 */
+ 0x0d, 0x00,
+ /* :0x28 */
+ 0xe1, 0x1d, 0x75, 0x3f, 0x06, 0x3f,
+ /* :0x2e XXX: read-only */
+ 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ /* :0x38 */
+ 0x1a, 0x1a, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00,
+ /* :0x40 */
+ 0x00, 0x00, 0x00, 0x68, 0x10, 0x97, 0x4c, 0x18,
+ 0x9b, 0x93, 0x9f, 0xff, 0x7c, 0x34, 0x3f, 0x3f,
+ /* :0x50 */
+ 0x3f, 0x83, 0x83, 0x80, 0x0d, 0x0f, 0xc3, 0x06,
+ 0x02, 0x80, 0x0f, 0x77, 0xa7, 0x67, 0x66, 0x2e,
+ /* :0x60 */
+ 0x7b, 0x02, 0x35, 0xcb, 0x8a, 0x09, 0x2a, 0x77,
+ 0x41, 0x88, 0x41, 0x52, 0xf1, 0x10, 0x20, 0x00,
+ /* :0x70 */
+ 0x41, 0xc3, 0x00, 0x3e, 0xb8, 0x02, 0x00, 0x00,
+ 0x00, 0x00, 0x12, 0x30,
+ SAA712x_reg7c | 0x40, 0x00, 0xff, 0xff,
+ };
+
+ if (dev->video_type == SOLO_VO_FMT_TYPE_PAL)
+ saa712x_write_regs(dev, saa7128_regs_pal, reg_start,
+ sizeof(saa7128_regs_pal));
+ else
+ saa712x_write_regs(dev, saa7128_regs_ntsc, reg_start,
+ sizeof(saa7128_regs_ntsc));
+}
+
+int solo_tw28_init(struct solo_dev *solo_dev)
+{
+ int i;
+ u8 value;
+
+ solo_dev->tw28_cnt = 0;
+
+ /* Detect techwell chip type(s) */
+ for (i = 0; i < solo_dev->nr_chans / 4; i++) {
+ value = solo_i2c_readbyte(solo_dev, SOLO_I2C_TW,
+ TW_CHIP_OFFSET_ADDR(i), 0xFF);
+
+ switch (value >> 3) {
+ case 0x18:
+ solo_dev->tw2865 |= 1 << i;
+ solo_dev->tw28_cnt++;
+ break;
+ case 0x0c:
+ solo_dev->tw2864 |= 1 << i;
+ solo_dev->tw28_cnt++;
+ break;
+ default:
+ value = solo_i2c_readbyte(solo_dev, SOLO_I2C_TW,
+ TW_CHIP_OFFSET_ADDR(i),
+ 0x59);
+ if ((value >> 3) == 0x04) {
+ solo_dev->tw2815 |= 1 << i;
+ solo_dev->tw28_cnt++;
+ }
+ }
+ }
+
+ if (solo_dev->tw28_cnt != (solo_dev->nr_chans >> 2)) {
+ dev_err(&solo_dev->pdev->dev,
+ "Could not initialize any techwell chips\n");
+ return -EINVAL;
+ }
+
+ saa712x_setup(solo_dev);
+
+ for (i = 0; i < solo_dev->tw28_cnt; i++) {
+ if ((solo_dev->tw2865 & (1 << i)))
+ tw2865_setup(solo_dev, TW_CHIP_OFFSET_ADDR(i));
+ else if ((solo_dev->tw2864 & (1 << i)))
+ tw2864_setup(solo_dev, TW_CHIP_OFFSET_ADDR(i));
+ else
+ tw2815_setup(solo_dev, TW_CHIP_OFFSET_ADDR(i));
+ }
+
+ return 0;
+}
+
+/*
+ * We accessed the video status signal in the Techwell chip through
+ * iic/i2c because the video status reported by register REG_VI_STATUS1
+ * (address 0x012C) of the SOLO6010 chip doesn't give the correct video
+ * status signal values.
+ */
+int tw28_get_video_status(struct solo_dev *solo_dev, u8 ch)
+{
+ u8 val, chip_num;
+
+ /* Get the right chip and on-chip channel */
+ chip_num = ch / 4;
+ ch %= 4;
+
+ val = tw_readbyte(solo_dev, chip_num, TW286x_AV_STAT_ADDR,
+ TW_AV_STAT_ADDR) & 0x0f;
+
+ return val & (1 << ch) ? 1 : 0;
+}
+
+#if 0
+/* Status of audio from up to 4 techwell chips are combined into 1 variable.
+ * See techwell datasheet for details. */
+u16 tw28_get_audio_status(struct solo_dev *solo_dev)
+{
+ u8 val;
+ u16 status = 0;
+ int i;
+
+ for (i = 0; i < solo_dev->tw28_cnt; i++) {
+ val = (tw_readbyte(solo_dev, i, TW286x_AV_STAT_ADDR,
+ TW_AV_STAT_ADDR) & 0xf0) >> 4;
+ status |= val << (i * 4);
+ }
+
+ return status;
+}
+#endif
+
+bool tw28_has_sharpness(struct solo_dev *solo_dev, u8 ch)
+{
+ return is_tw286x(solo_dev, ch / 4);
+}
+
+int tw28_set_ctrl_val(struct solo_dev *solo_dev, u32 ctrl, u8 ch,
+ s32 val)
+{
+ char sval;
+ u8 chip_num;
+
+ /* Get the right chip and on-chip channel */
+ chip_num = ch / 4;
+ ch %= 4;
+
+ if (val > 255 || val < 0)
+ return -ERANGE;
+
+ switch (ctrl) {
+ case V4L2_CID_SHARPNESS:
+ /* Only 286x has sharpness */
+ if (is_tw286x(solo_dev, chip_num)) {
+ u8 v = solo_i2c_readbyte(solo_dev, SOLO_I2C_TW,
+ TW_CHIP_OFFSET_ADDR(chip_num),
+ TW286x_SHARPNESS(chip_num));
+ v &= 0xf0;
+ v |= val;
+ solo_i2c_writebyte(solo_dev, SOLO_I2C_TW,
+ TW_CHIP_OFFSET_ADDR(chip_num),
+ TW286x_SHARPNESS(chip_num), v);
+ } else {
+ return -EINVAL;
+ }
+ break;
+
+ case V4L2_CID_HUE:
+ if (is_tw286x(solo_dev, chip_num))
+ sval = val - 128;
+ else
+ sval = (char)val;
+ tw_writebyte(solo_dev, chip_num, TW286x_HUE_ADDR(ch),
+ TW_HUE_ADDR(ch), sval);
+
+ break;
+
+ case V4L2_CID_SATURATION:
+ /* 286x chips have a U and V component for saturation */
+ if (is_tw286x(solo_dev, chip_num)) {
+ solo_i2c_writebyte(solo_dev, SOLO_I2C_TW,
+ TW_CHIP_OFFSET_ADDR(chip_num),
+ TW286x_SATURATIONU_ADDR(ch), val);
+ }
+ tw_writebyte(solo_dev, chip_num, TW286x_SATURATIONV_ADDR(ch),
+ TW_SATURATION_ADDR(ch), val);
+
+ break;
+
+ case V4L2_CID_CONTRAST:
+ tw_writebyte(solo_dev, chip_num, TW286x_CONTRAST_ADDR(ch),
+ TW_CONTRAST_ADDR(ch), val);
+ break;
+
+ case V4L2_CID_BRIGHTNESS:
+ if (is_tw286x(solo_dev, chip_num))
+ sval = val - 128;
+ else
+ sval = (char)val;
+ tw_writebyte(solo_dev, chip_num, TW286x_BRIGHTNESS_ADDR(ch),
+ TW_BRIGHTNESS_ADDR(ch), sval);
+
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+int tw28_get_ctrl_val(struct solo_dev *solo_dev, u32 ctrl, u8 ch,
+ s32 *val)
+{
+ u8 rval, chip_num;
+
+ /* Get the right chip and on-chip channel */
+ chip_num = ch / 4;
+ ch %= 4;
+
+ switch (ctrl) {
+ case V4L2_CID_SHARPNESS:
+ /* Only 286x has sharpness */
+ if (is_tw286x(solo_dev, chip_num)) {
+ rval = solo_i2c_readbyte(solo_dev, SOLO_I2C_TW,
+ TW_CHIP_OFFSET_ADDR(chip_num),
+ TW286x_SHARPNESS(chip_num));
+ *val = rval & 0x0f;
+ } else
+ *val = 0;
+ break;
+ case V4L2_CID_HUE:
+ rval = tw_readbyte(solo_dev, chip_num, TW286x_HUE_ADDR(ch),
+ TW_HUE_ADDR(ch));
+ if (is_tw286x(solo_dev, chip_num))
+ *val = (s32)((char)rval) + 128;
+ else
+ *val = rval;
+ break;
+ case V4L2_CID_SATURATION:
+ *val = tw_readbyte(solo_dev, chip_num,
+ TW286x_SATURATIONU_ADDR(ch),
+ TW_SATURATION_ADDR(ch));
+ break;
+ case V4L2_CID_CONTRAST:
+ *val = tw_readbyte(solo_dev, chip_num,
+ TW286x_CONTRAST_ADDR(ch),
+ TW_CONTRAST_ADDR(ch));
+ break;
+ case V4L2_CID_BRIGHTNESS:
+ rval = tw_readbyte(solo_dev, chip_num,
+ TW286x_BRIGHTNESS_ADDR(ch),
+ TW_BRIGHTNESS_ADDR(ch));
+ if (is_tw286x(solo_dev, chip_num))
+ *val = (s32)((char)rval) + 128;
+ else
+ *val = rval;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+#if 0
+/*
+ * For audio output volume, the output channel is only 1. In this case we
+ * don't need to offset TW_CHIP_OFFSET_ADDR. The TW_CHIP_OFFSET_ADDR used
+ * is the base address of the techwell chip.
+ */
+void tw2815_Set_AudioOutVol(struct solo_dev *solo_dev, unsigned int u_val)
+{
+ unsigned int val;
+ unsigned int chip_num;
+
+ chip_num = (solo_dev->nr_chans - 1) / 4;
+
+ val = tw_readbyte(solo_dev, chip_num, TW286x_AUDIO_OUTPUT_VOL_ADDR,
+ TW_AUDIO_OUTPUT_VOL_ADDR);
+
+ u_val = (val & 0x0f) | (u_val << 4);
+
+ tw_writebyte(solo_dev, chip_num, TW286x_AUDIO_OUTPUT_VOL_ADDR,
+ TW_AUDIO_OUTPUT_VOL_ADDR, u_val);
+}
+#endif
+
+u8 tw28_get_audio_gain(struct solo_dev *solo_dev, u8 ch)
+{
+ u8 val;
+ u8 chip_num;
+
+ /* Get the right chip and on-chip channel */
+ chip_num = ch / 4;
+ ch %= 4;
+
+ val = tw_readbyte(solo_dev, chip_num,
+ TW286x_AUDIO_INPUT_GAIN_ADDR(ch),
+ TW_AUDIO_INPUT_GAIN_ADDR(ch));
+
+ return (ch % 2) ? (val >> 4) : (val & 0x0f);
+}
+
+void tw28_set_audio_gain(struct solo_dev *solo_dev, u8 ch, u8 val)
+{
+ u8 old_val;
+ u8 chip_num;
+
+ /* Get the right chip and on-chip channel */
+ chip_num = ch / 4;
+ ch %= 4;
+
+ old_val = tw_readbyte(solo_dev, chip_num,
+ TW286x_AUDIO_INPUT_GAIN_ADDR(ch),
+ TW_AUDIO_INPUT_GAIN_ADDR(ch));
+
+ val = (old_val & ((ch % 2) ? 0x0f : 0xf0)) |
+ ((ch % 2) ? (val << 4) : val);
+
+ tw_writebyte(solo_dev, chip_num, TW286x_AUDIO_INPUT_GAIN_ADDR(ch),
+ TW_AUDIO_INPUT_GAIN_ADDR(ch), val);
+}
diff --git a/drivers/staging/media/solo6x10/solo6x10-tw28.h b/drivers/staging/media/solo6x10/solo6x10-tw28.h
new file mode 100644
index 00000000000..1a02c87d4cf
--- /dev/null
+++ b/drivers/staging/media/solo6x10/solo6x10-tw28.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2010-2013 Bluecherry, LLC <http://www.bluecherrydvr.com>
+ *
+ * Original author:
+ * Ben Collins <bcollins@ubuntu.com>
+ *
+ * Additional work by:
+ * John Brooks <john.brooks@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 __SOLO6X10_TW28_H
+#define __SOLO6X10_TW28_H
+
+#include "solo6x10.h"
+
+#define TW_NUM_CHIP 4
+#define TW_BASE_ADDR 0x28
+#define TW_CHIP_OFFSET_ADDR(n) (TW_BASE_ADDR + (n))
+
+/* tw2815 */
+#define TW_AV_STAT_ADDR 0x5a
+#define TW_HUE_ADDR(n) (0x07 | ((n) << 4))
+#define TW_SATURATION_ADDR(n) (0x08 | ((n) << 4))
+#define TW_CONTRAST_ADDR(n) (0x09 | ((n) << 4))
+#define TW_BRIGHTNESS_ADDR(n) (0x0a | ((n) << 4))
+#define TW_AUDIO_OUTPUT_VOL_ADDR 0x70
+#define TW_AUDIO_INPUT_GAIN_ADDR(n) (0x60 + ((n > 1) ? 1 : 0))
+
+/* tw286x */
+#define TW286x_AV_STAT_ADDR 0xfd
+#define TW286x_HUE_ADDR(n) (0x06 | ((n) << 4))
+#define TW286x_SATURATIONU_ADDR(n) (0x04 | ((n) << 4))
+#define TW286x_SATURATIONV_ADDR(n) (0x05 | ((n) << 4))
+#define TW286x_CONTRAST_ADDR(n) (0x02 | ((n) << 4))
+#define TW286x_BRIGHTNESS_ADDR(n) (0x01 | ((n) << 4))
+#define TW286x_SHARPNESS(n) (0x03 | ((n) << 4))
+#define TW286x_AUDIO_OUTPUT_VOL_ADDR 0xdf
+#define TW286x_AUDIO_INPUT_GAIN_ADDR(n) (0xD0 + ((n > 1) ? 1 : 0))
+
+int solo_tw28_init(struct solo_dev *solo_dev);
+
+int tw28_set_ctrl_val(struct solo_dev *solo_dev, u32 ctrl, u8 ch, s32 val);
+int tw28_get_ctrl_val(struct solo_dev *solo_dev, u32 ctrl, u8 ch, s32 *val);
+bool tw28_has_sharpness(struct solo_dev *solo_dev, u8 ch);
+
+u8 tw28_get_audio_gain(struct solo_dev *solo_dev, u8 ch);
+void tw28_set_audio_gain(struct solo_dev *solo_dev, u8 ch, u8 val);
+int tw28_get_video_status(struct solo_dev *solo_dev, u8 ch);
+
+#if 0
+unsigned int tw2815_get_audio_status(struct SOLO *solo);
+void tw2815_Set_AudioOutVol(struct SOLO *solo, unsigned int u_val);
+#endif
+
+#endif /* __SOLO6X10_TW28_H */
diff --git a/drivers/staging/media/solo6x10/solo6x10-v4l2-enc.c b/drivers/staging/media/solo6x10/solo6x10-v4l2-enc.c
new file mode 100644
index 00000000000..b8ff113c20f
--- /dev/null
+++ b/drivers/staging/media/solo6x10/solo6x10-v4l2-enc.c
@@ -0,0 +1,1414 @@
+/*
+ * Copyright (C) 2010-2013 Bluecherry, LLC <http://www.bluecherrydvr.com>
+ *
+ * Original author:
+ * Ben Collins <bcollins@ubuntu.com>
+ *
+ * Additional work by:
+ * John Brooks <john.brooks@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/v4l2-event.h>
+#include <media/videobuf2-dma-sg.h>
+
+#include "solo6x10.h"
+#include "solo6x10-tw28.h"
+#include "solo6x10-jpeg.h"
+
+#define MIN_VID_BUFFERS 2
+#define FRAME_BUF_SIZE (196 * 1024)
+#define MP4_QS 16
+#define DMA_ALIGN 4096
+
+/* 6010 M4V */
+static unsigned char vop_6010_ntsc_d1[] = {
+ 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x20,
+ 0x02, 0x48, 0x1d, 0xc0, 0x00, 0x40, 0x00, 0x40,
+ 0x00, 0x40, 0x00, 0x80, 0x00, 0x97, 0x53, 0x04,
+ 0x1f, 0x4c, 0x58, 0x10, 0xf0, 0x71, 0x18, 0x3f,
+};
+
+static unsigned char vop_6010_ntsc_cif[] = {
+ 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x20,
+ 0x02, 0x48, 0x1d, 0xc0, 0x00, 0x40, 0x00, 0x40,
+ 0x00, 0x40, 0x00, 0x80, 0x00, 0x97, 0x53, 0x04,
+ 0x1f, 0x4c, 0x2c, 0x10, 0x78, 0x51, 0x18, 0x3f,
+};
+
+static unsigned char vop_6010_pal_d1[] = {
+ 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x20,
+ 0x02, 0x48, 0x15, 0xc0, 0x00, 0x40, 0x00, 0x40,
+ 0x00, 0x40, 0x00, 0x80, 0x00, 0x97, 0x53, 0x04,
+ 0x1f, 0x4c, 0x58, 0x11, 0x20, 0x71, 0x18, 0x3f,
+};
+
+static unsigned char vop_6010_pal_cif[] = {
+ 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x20,
+ 0x02, 0x48, 0x15, 0xc0, 0x00, 0x40, 0x00, 0x40,
+ 0x00, 0x40, 0x00, 0x80, 0x00, 0x97, 0x53, 0x04,
+ 0x1f, 0x4c, 0x2c, 0x10, 0x90, 0x51, 0x18, 0x3f,
+};
+
+/* 6110 h.264 */
+static unsigned char vop_6110_ntsc_d1[] = {
+ 0x00, 0x00, 0x00, 0x01, 0x67, 0x42, 0x00, 0x1e,
+ 0x9a, 0x74, 0x05, 0x81, 0xec, 0x80, 0x00, 0x00,
+ 0x00, 0x01, 0x68, 0xce, 0x32, 0x28, 0x00, 0x00,
+};
+
+static unsigned char vop_6110_ntsc_cif[] = {
+ 0x00, 0x00, 0x00, 0x01, 0x67, 0x42, 0x00, 0x1e,
+ 0x9a, 0x74, 0x0b, 0x0f, 0xc8, 0x00, 0x00, 0x00,
+ 0x01, 0x68, 0xce, 0x32, 0x28, 0x00, 0x00, 0x00,
+};
+
+static unsigned char vop_6110_pal_d1[] = {
+ 0x00, 0x00, 0x00, 0x01, 0x67, 0x42, 0x00, 0x1e,
+ 0x9a, 0x74, 0x05, 0x80, 0x93, 0x20, 0x00, 0x00,
+ 0x00, 0x01, 0x68, 0xce, 0x32, 0x28, 0x00, 0x00,
+};
+
+static unsigned char vop_6110_pal_cif[] = {
+ 0x00, 0x00, 0x00, 0x01, 0x67, 0x42, 0x00, 0x1e,
+ 0x9a, 0x74, 0x0b, 0x04, 0xb2, 0x00, 0x00, 0x00,
+ 0x01, 0x68, 0xce, 0x32, 0x28, 0x00, 0x00, 0x00,
+};
+
+typedef __le32 vop_header[16];
+
+struct solo_enc_buf {
+ enum solo_enc_types type;
+ const vop_header *vh;
+ int motion;
+};
+
+static int solo_is_motion_on(struct solo_enc_dev *solo_enc)
+{
+ struct solo_dev *solo_dev = solo_enc->solo_dev;
+
+ return (solo_dev->motion_mask >> solo_enc->ch) & 1;
+}
+
+static int solo_motion_detected(struct solo_enc_dev *solo_enc)
+{
+ struct solo_dev *solo_dev = solo_enc->solo_dev;
+ unsigned long flags;
+ u32 ch_mask = 1 << solo_enc->ch;
+ int ret = 0;
+
+ spin_lock_irqsave(&solo_enc->motion_lock, flags);
+ if (solo_reg_read(solo_dev, SOLO_VI_MOT_STATUS) & ch_mask) {
+ solo_reg_write(solo_dev, SOLO_VI_MOT_CLEAR, ch_mask);
+ ret = 1;
+ }
+ spin_unlock_irqrestore(&solo_enc->motion_lock, flags);
+
+ return ret;
+}
+
+static void solo_motion_toggle(struct solo_enc_dev *solo_enc, int on)
+{
+ struct solo_dev *solo_dev = solo_enc->solo_dev;
+ u32 mask = 1 << solo_enc->ch;
+ unsigned long flags;
+
+ spin_lock_irqsave(&solo_enc->motion_lock, flags);
+
+ if (on)
+ solo_dev->motion_mask |= mask;
+ else
+ solo_dev->motion_mask &= ~mask;
+
+ solo_reg_write(solo_dev, SOLO_VI_MOT_CLEAR, mask);
+
+ solo_reg_write(solo_dev, SOLO_VI_MOT_ADR,
+ SOLO_VI_MOTION_EN(solo_dev->motion_mask) |
+ (SOLO_MOTION_EXT_ADDR(solo_dev) >> 16));
+
+ spin_unlock_irqrestore(&solo_enc->motion_lock, flags);
+}
+
+void solo_update_mode(struct solo_enc_dev *solo_enc)
+{
+ struct solo_dev *solo_dev = solo_enc->solo_dev;
+ int vop_len;
+ unsigned char *vop;
+
+ solo_enc->interlaced = (solo_enc->mode & 0x08) ? 1 : 0;
+ solo_enc->bw_weight = max(solo_dev->fps / solo_enc->interval, 1);
+
+ if (solo_enc->mode == SOLO_ENC_MODE_CIF) {
+ solo_enc->width = solo_dev->video_hsize >> 1;
+ solo_enc->height = solo_dev->video_vsize;
+ if (solo_dev->type == SOLO_DEV_6110) {
+ if (solo_dev->video_type == SOLO_VO_FMT_TYPE_NTSC) {
+ vop = vop_6110_ntsc_cif;
+ vop_len = sizeof(vop_6110_ntsc_cif);
+ } else {
+ vop = vop_6110_pal_cif;
+ vop_len = sizeof(vop_6110_pal_cif);
+ }
+ } else {
+ if (solo_dev->video_type == SOLO_VO_FMT_TYPE_NTSC) {
+ vop = vop_6010_ntsc_cif;
+ vop_len = sizeof(vop_6010_ntsc_cif);
+ } else {
+ vop = vop_6010_pal_cif;
+ vop_len = sizeof(vop_6010_pal_cif);
+ }
+ }
+ } else {
+ solo_enc->width = solo_dev->video_hsize;
+ solo_enc->height = solo_dev->video_vsize << 1;
+ solo_enc->bw_weight <<= 2;
+ if (solo_dev->type == SOLO_DEV_6110) {
+ if (solo_dev->video_type == SOLO_VO_FMT_TYPE_NTSC) {
+ vop = vop_6110_ntsc_d1;
+ vop_len = sizeof(vop_6110_ntsc_d1);
+ } else {
+ vop = vop_6110_pal_d1;
+ vop_len = sizeof(vop_6110_pal_d1);
+ }
+ } else {
+ if (solo_dev->video_type == SOLO_VO_FMT_TYPE_NTSC) {
+ vop = vop_6010_ntsc_d1;
+ vop_len = sizeof(vop_6010_ntsc_d1);
+ } else {
+ vop = vop_6010_pal_d1;
+ vop_len = sizeof(vop_6010_pal_d1);
+ }
+ }
+ }
+
+ memcpy(solo_enc->vop, vop, vop_len);
+
+ /* Some fixups for 6010/M4V */
+ if (solo_dev->type == SOLO_DEV_6010) {
+ u16 fps = solo_dev->fps * 1000;
+ u16 interval = solo_enc->interval * 1000;
+
+ vop = solo_enc->vop;
+
+ /* Frame rate and interval */
+ vop[22] = fps >> 4;
+ vop[23] = ((fps << 4) & 0xf0) | 0x0c
+ | ((interval >> 13) & 0x3);
+ vop[24] = (interval >> 5) & 0xff;
+ vop[25] = ((interval << 3) & 0xf8) | 0x04;
+ }
+
+ solo_enc->vop_len = vop_len;
+
+ /* Now handle the jpeg header */
+ vop = solo_enc->jpeg_header;
+ vop[SOF0_START + 5] = 0xff & (solo_enc->height >> 8);
+ vop[SOF0_START + 6] = 0xff & solo_enc->height;
+ vop[SOF0_START + 7] = 0xff & (solo_enc->width >> 8);
+ vop[SOF0_START + 8] = 0xff & solo_enc->width;
+
+ memcpy(vop + DQT_START,
+ jpeg_dqt[solo_g_jpeg_qp(solo_dev, solo_enc->ch)], DQT_LEN);
+}
+
+static int solo_enc_on(struct solo_enc_dev *solo_enc)
+{
+ u8 ch = solo_enc->ch;
+ struct solo_dev *solo_dev = solo_enc->solo_dev;
+ u8 interval;
+
+ solo_update_mode(solo_enc);
+
+ /* Make sure to do a bandwidth check */
+ if (solo_enc->bw_weight > solo_dev->enc_bw_remain)
+ return -EBUSY;
+ solo_enc->sequence = 0;
+ solo_dev->enc_bw_remain -= solo_enc->bw_weight;
+
+ if (solo_enc->type == SOLO_ENC_TYPE_EXT)
+ solo_reg_write(solo_dev, SOLO_CAP_CH_COMP_ENA_E(ch), 1);
+
+ /* Disable all encoding for this channel */
+ solo_reg_write(solo_dev, SOLO_CAP_CH_SCALE(ch), 0);
+
+ /* Common for both std and ext encoding */
+ solo_reg_write(solo_dev, SOLO_VE_CH_INTL(ch),
+ solo_enc->interlaced ? 1 : 0);
+
+ if (solo_enc->interlaced)
+ interval = solo_enc->interval - 1;
+ else
+ interval = solo_enc->interval;
+
+ /* Standard encoding only */
+ solo_reg_write(solo_dev, SOLO_VE_CH_GOP(ch), solo_enc->gop);
+ solo_reg_write(solo_dev, SOLO_VE_CH_QP(ch), solo_enc->qp);
+ solo_reg_write(solo_dev, SOLO_CAP_CH_INTV(ch), interval);
+
+ /* Extended encoding only */
+ solo_reg_write(solo_dev, SOLO_VE_CH_GOP_E(ch), solo_enc->gop);
+ solo_reg_write(solo_dev, SOLO_VE_CH_QP_E(ch), solo_enc->qp);
+ solo_reg_write(solo_dev, SOLO_CAP_CH_INTV_E(ch), interval);
+
+ /* Enables the standard encoder */
+ solo_reg_write(solo_dev, SOLO_CAP_CH_SCALE(ch), solo_enc->mode);
+
+ return 0;
+}
+
+static void solo_enc_off(struct solo_enc_dev *solo_enc)
+{
+ struct solo_dev *solo_dev = solo_enc->solo_dev;
+
+ solo_dev->enc_bw_remain += solo_enc->bw_weight;
+
+ solo_reg_write(solo_dev, SOLO_CAP_CH_SCALE(solo_enc->ch), 0);
+ solo_reg_write(solo_dev, SOLO_CAP_CH_COMP_ENA_E(solo_enc->ch), 0);
+}
+
+static int enc_get_mpeg_dma(struct solo_dev *solo_dev, dma_addr_t dma,
+ unsigned int off, unsigned int size)
+{
+ int ret;
+
+ if (off > SOLO_MP4E_EXT_SIZE(solo_dev))
+ return -EINVAL;
+
+ /* Single shot */
+ if (off + size <= SOLO_MP4E_EXT_SIZE(solo_dev)) {
+ return solo_p2m_dma_t(solo_dev, 0, dma,
+ SOLO_MP4E_EXT_ADDR(solo_dev) + off, size,
+ 0, 0);
+ }
+
+ /* Buffer wrap */
+ ret = solo_p2m_dma_t(solo_dev, 0, dma,
+ SOLO_MP4E_EXT_ADDR(solo_dev) + off,
+ SOLO_MP4E_EXT_SIZE(solo_dev) - off, 0, 0);
+
+ if (!ret) {
+ ret = solo_p2m_dma_t(solo_dev, 0,
+ dma + SOLO_MP4E_EXT_SIZE(solo_dev) - off,
+ SOLO_MP4E_EXT_ADDR(solo_dev),
+ size + off - SOLO_MP4E_EXT_SIZE(solo_dev), 0, 0);
+ }
+
+ return ret;
+}
+
+/* Build a descriptor queue out of an SG list and send it to the P2M for
+ * processing. */
+static int solo_send_desc(struct solo_enc_dev *solo_enc, int skip,
+ struct sg_table *vbuf, int off, int size,
+ unsigned int base, unsigned int base_size)
+{
+ struct solo_dev *solo_dev = solo_enc->solo_dev;
+ struct scatterlist *sg;
+ int i;
+ int ret;
+
+ if (WARN_ON_ONCE(size > FRAME_BUF_SIZE))
+ return -EINVAL;
+
+ solo_enc->desc_count = 1;
+
+ for_each_sg(vbuf->sgl, sg, vbuf->nents, i) {
+ struct solo_p2m_desc *desc;
+ dma_addr_t dma;
+ int len;
+ int left = base_size - off;
+
+ desc = &solo_enc->desc_items[solo_enc->desc_count++];
+ dma = sg_dma_address(sg);
+ len = sg_dma_len(sg);
+
+ /* We assume this is smaller than the scatter size */
+ BUG_ON(skip >= len);
+ if (skip) {
+ len -= skip;
+ dma += skip;
+ size -= skip;
+ skip = 0;
+ }
+
+ len = min(len, size);
+
+ if (len <= left) {
+ /* Single descriptor */
+ solo_p2m_fill_desc(desc, 0, dma, base + off,
+ len, 0, 0);
+ } else {
+ /* Buffer wrap */
+ /* XXX: Do these as separate DMA requests, to avoid
+ timeout errors triggered by awkwardly sized
+ descriptors. See
+ <https://github.com/bluecherrydvr/solo6x10/issues/8>
+ */
+ ret = solo_p2m_dma_t(solo_dev, 0, dma, base + off,
+ left, 0, 0);
+ if (ret)
+ return ret;
+
+ ret = solo_p2m_dma_t(solo_dev, 0, dma + left, base,
+ len - left, 0, 0);
+ if (ret)
+ return ret;
+
+ solo_enc->desc_count--;
+ }
+
+ size -= len;
+ if (size <= 0)
+ break;
+
+ off += len;
+ if (off >= base_size)
+ off -= base_size;
+
+ /* Because we may use two descriptors per loop */
+ if (solo_enc->desc_count >= (solo_enc->desc_nelts - 1)) {
+ ret = solo_p2m_dma_desc(solo_dev, solo_enc->desc_items,
+ solo_enc->desc_dma,
+ solo_enc->desc_count - 1);
+ if (ret)
+ return ret;
+ solo_enc->desc_count = 1;
+ }
+ }
+
+ if (solo_enc->desc_count <= 1)
+ return 0;
+
+ return solo_p2m_dma_desc(solo_dev, solo_enc->desc_items,
+ solo_enc->desc_dma, solo_enc->desc_count - 1);
+}
+
+/* Extract values from VOP header - VE_STATUSxx */
+static inline int vop_interlaced(const vop_header *vh)
+{
+ return (__le32_to_cpu((*vh)[0]) >> 30) & 1;
+}
+
+static inline u8 vop_channel(const vop_header *vh)
+{
+ return (__le32_to_cpu((*vh)[0]) >> 24) & 0x1F;
+}
+
+static inline u8 vop_type(const vop_header *vh)
+{
+ return (__le32_to_cpu((*vh)[0]) >> 22) & 3;
+}
+
+static inline u32 vop_mpeg_size(const vop_header *vh)
+{
+ return __le32_to_cpu((*vh)[0]) & 0xFFFFF;
+}
+
+static inline u8 vop_hsize(const vop_header *vh)
+{
+ return (__le32_to_cpu((*vh)[1]) >> 8) & 0xFF;
+}
+
+static inline u8 vop_vsize(const vop_header *vh)
+{
+ return __le32_to_cpu((*vh)[1]) & 0xFF;
+}
+
+static inline u32 vop_mpeg_offset(const vop_header *vh)
+{
+ return __le32_to_cpu((*vh)[2]);
+}
+
+static inline u32 vop_jpeg_offset(const vop_header *vh)
+{
+ return __le32_to_cpu((*vh)[3]);
+}
+
+static inline u32 vop_jpeg_size(const vop_header *vh)
+{
+ return __le32_to_cpu((*vh)[4]) & 0xFFFFF;
+}
+
+static inline u32 vop_sec(const vop_header *vh)
+{
+ return __le32_to_cpu((*vh)[5]);
+}
+
+static inline u32 vop_usec(const vop_header *vh)
+{
+ return __le32_to_cpu((*vh)[6]);
+}
+
+static int solo_fill_jpeg(struct solo_enc_dev *solo_enc,
+ struct vb2_buffer *vb, const vop_header *vh)
+{
+ struct solo_dev *solo_dev = solo_enc->solo_dev;
+ struct sg_table *vbuf = vb2_dma_sg_plane_desc(vb, 0);
+ int frame_size;
+ int ret;
+
+ vb->v4l2_buf.flags |= V4L2_BUF_FLAG_KEYFRAME;
+
+ if (vb2_plane_size(vb, 0) < vop_jpeg_size(vh) + solo_enc->jpeg_len)
+ return -EIO;
+
+ frame_size = ALIGN(vop_jpeg_size(vh) + solo_enc->jpeg_len, DMA_ALIGN);
+ vb2_set_plane_payload(vb, 0, vop_jpeg_size(vh) + solo_enc->jpeg_len);
+
+ /* may discard all previous data in vbuf->sgl */
+ dma_map_sg(&solo_dev->pdev->dev, vbuf->sgl, vbuf->nents,
+ DMA_FROM_DEVICE);
+ ret = solo_send_desc(solo_enc, solo_enc->jpeg_len, vbuf,
+ vop_jpeg_offset(vh) - SOLO_JPEG_EXT_ADDR(solo_dev),
+ frame_size, SOLO_JPEG_EXT_ADDR(solo_dev),
+ SOLO_JPEG_EXT_SIZE(solo_dev));
+ dma_unmap_sg(&solo_dev->pdev->dev, vbuf->sgl, vbuf->nents,
+ DMA_FROM_DEVICE);
+
+ /* add the header only after dma_unmap_sg() */
+ sg_copy_from_buffer(vbuf->sgl, vbuf->nents,
+ solo_enc->jpeg_header, solo_enc->jpeg_len);
+
+ return ret;
+}
+
+static int solo_fill_mpeg(struct solo_enc_dev *solo_enc,
+ struct vb2_buffer *vb, const vop_header *vh)
+{
+ struct solo_dev *solo_dev = solo_enc->solo_dev;
+ struct sg_table *vbuf = vb2_dma_sg_plane_desc(vb, 0);
+ int frame_off, frame_size;
+ int skip = 0;
+ int ret;
+
+ if (vb2_plane_size(vb, 0) < vop_mpeg_size(vh))
+ return -EIO;
+
+ /* If this is a key frame, add extra header */
+ vb->v4l2_buf.flags &= ~(V4L2_BUF_FLAG_KEYFRAME | V4L2_BUF_FLAG_PFRAME |
+ V4L2_BUF_FLAG_BFRAME);
+ if (!vop_type(vh)) {
+ skip = solo_enc->vop_len;
+ vb->v4l2_buf.flags |= V4L2_BUF_FLAG_KEYFRAME;
+ vb2_set_plane_payload(vb, 0, vop_mpeg_size(vh) +
+ solo_enc->vop_len);
+ } else {
+ vb->v4l2_buf.flags |= V4L2_BUF_FLAG_PFRAME;
+ vb2_set_plane_payload(vb, 0, vop_mpeg_size(vh));
+ }
+
+ /* Now get the actual mpeg payload */
+ frame_off = (vop_mpeg_offset(vh) - SOLO_MP4E_EXT_ADDR(solo_dev) +
+ sizeof(*vh)) % SOLO_MP4E_EXT_SIZE(solo_dev);
+ frame_size = ALIGN(vop_mpeg_size(vh) + skip, DMA_ALIGN);
+
+ /* may discard all previous data in vbuf->sgl */
+ dma_map_sg(&solo_dev->pdev->dev, vbuf->sgl, vbuf->nents,
+ DMA_FROM_DEVICE);
+ ret = solo_send_desc(solo_enc, skip, vbuf, frame_off, frame_size,
+ SOLO_MP4E_EXT_ADDR(solo_dev),
+ SOLO_MP4E_EXT_SIZE(solo_dev));
+ dma_unmap_sg(&solo_dev->pdev->dev, vbuf->sgl, vbuf->nents,
+ DMA_FROM_DEVICE);
+
+ /* add the header only after dma_unmap_sg() */
+ if (!vop_type(vh))
+ sg_copy_from_buffer(vbuf->sgl, vbuf->nents,
+ solo_enc->vop, solo_enc->vop_len);
+ return ret;
+}
+
+static int solo_enc_fillbuf(struct solo_enc_dev *solo_enc,
+ struct vb2_buffer *vb, struct solo_enc_buf *enc_buf)
+{
+ const vop_header *vh = enc_buf->vh;
+ int ret;
+
+ /* Check for motion flags */
+ vb->v4l2_buf.flags &= ~(V4L2_BUF_FLAG_MOTION_ON |
+ V4L2_BUF_FLAG_MOTION_DETECTED);
+ if (solo_is_motion_on(solo_enc)) {
+ vb->v4l2_buf.flags |= V4L2_BUF_FLAG_MOTION_ON;
+ if (enc_buf->motion)
+ vb->v4l2_buf.flags |= V4L2_BUF_FLAG_MOTION_DETECTED;
+ }
+
+ switch (solo_enc->fmt) {
+ case V4L2_PIX_FMT_MPEG4:
+ case V4L2_PIX_FMT_H264:
+ ret = solo_fill_mpeg(solo_enc, vb, vh);
+ break;
+ default: /* V4L2_PIX_FMT_MJPEG */
+ ret = solo_fill_jpeg(solo_enc, vb, vh);
+ break;
+ }
+
+ if (!ret) {
+ vb->v4l2_buf.sequence = solo_enc->sequence++;
+ vb->v4l2_buf.timestamp.tv_sec = vop_sec(vh);
+ vb->v4l2_buf.timestamp.tv_usec = vop_usec(vh);
+ }
+
+ vb2_buffer_done(vb, ret ? VB2_BUF_STATE_ERROR : VB2_BUF_STATE_DONE);
+
+ return ret;
+}
+
+static void solo_enc_handle_one(struct solo_enc_dev *solo_enc,
+ struct solo_enc_buf *enc_buf)
+{
+ struct solo_vb2_buf *vb;
+ unsigned long flags;
+
+ mutex_lock(&solo_enc->lock);
+ if (solo_enc->type != enc_buf->type)
+ goto unlock;
+
+ spin_lock_irqsave(&solo_enc->av_lock, flags);
+ if (list_empty(&solo_enc->vidq_active)) {
+ spin_unlock_irqrestore(&solo_enc->av_lock, flags);
+ goto unlock;
+ }
+ vb = list_first_entry(&solo_enc->vidq_active, struct solo_vb2_buf,
+ list);
+ list_del(&vb->list);
+ spin_unlock_irqrestore(&solo_enc->av_lock, flags);
+
+ solo_enc_fillbuf(solo_enc, &vb->vb, enc_buf);
+unlock:
+ mutex_unlock(&solo_enc->lock);
+}
+
+void solo_enc_v4l2_isr(struct solo_dev *solo_dev)
+{
+ wake_up_interruptible_all(&solo_dev->ring_thread_wait);
+}
+
+static void solo_handle_ring(struct solo_dev *solo_dev)
+{
+ for (;;) {
+ struct solo_enc_dev *solo_enc;
+ struct solo_enc_buf enc_buf;
+ u32 mpeg_current, off;
+ u8 ch;
+ u8 cur_q;
+
+ /* Check if the hardware has any new ones in the queue */
+ cur_q = solo_reg_read(solo_dev, SOLO_VE_STATE(11)) & 0xff;
+ if (cur_q == solo_dev->enc_idx)
+ break;
+
+ mpeg_current = solo_reg_read(solo_dev,
+ SOLO_VE_MPEG4_QUE(solo_dev->enc_idx));
+ solo_dev->enc_idx = (solo_dev->enc_idx + 1) % MP4_QS;
+
+ ch = (mpeg_current >> 24) & 0x1f;
+ off = mpeg_current & 0x00ffffff;
+
+ if (ch >= SOLO_MAX_CHANNELS) {
+ ch -= SOLO_MAX_CHANNELS;
+ enc_buf.type = SOLO_ENC_TYPE_EXT;
+ } else
+ enc_buf.type = SOLO_ENC_TYPE_STD;
+
+ solo_enc = solo_dev->v4l2_enc[ch];
+ if (solo_enc == NULL) {
+ dev_err(&solo_dev->pdev->dev,
+ "Got spurious packet for channel %d\n", ch);
+ continue;
+ }
+
+ /* FAIL... */
+ if (enc_get_mpeg_dma(solo_dev, solo_dev->vh_dma, off,
+ sizeof(vop_header)))
+ continue;
+
+ enc_buf.vh = solo_dev->vh_buf;
+
+ /* Sanity check */
+ if (vop_mpeg_offset(enc_buf.vh) !=
+ SOLO_MP4E_EXT_ADDR(solo_dev) + off)
+ continue;
+
+ if (solo_motion_detected(solo_enc))
+ enc_buf.motion = 1;
+ else
+ enc_buf.motion = 0;
+
+ solo_enc_handle_one(solo_enc, &enc_buf);
+ }
+}
+
+static int solo_ring_thread(void *data)
+{
+ struct solo_dev *solo_dev = data;
+ DECLARE_WAITQUEUE(wait, current);
+
+ set_freezable();
+ add_wait_queue(&solo_dev->ring_thread_wait, &wait);
+
+ for (;;) {
+ long timeout = schedule_timeout_interruptible(HZ);
+ if (timeout == -ERESTARTSYS || kthread_should_stop())
+ break;
+ solo_irq_off(solo_dev, SOLO_IRQ_ENCODER);
+ solo_handle_ring(solo_dev);
+ solo_irq_on(solo_dev, SOLO_IRQ_ENCODER);
+ try_to_freeze();
+ }
+
+ remove_wait_queue(&solo_dev->ring_thread_wait, &wait);
+
+ return 0;
+}
+
+static int solo_enc_queue_setup(struct vb2_queue *q,
+ const struct v4l2_format *fmt,
+ unsigned int *num_buffers,
+ unsigned int *num_planes, unsigned int sizes[],
+ void *alloc_ctxs[])
+{
+ sizes[0] = FRAME_BUF_SIZE;
+ *num_planes = 1;
+
+ if (*num_buffers < MIN_VID_BUFFERS)
+ *num_buffers = MIN_VID_BUFFERS;
+
+ return 0;
+}
+
+static void solo_enc_buf_queue(struct vb2_buffer *vb)
+{
+ struct vb2_queue *vq = vb->vb2_queue;
+ struct solo_enc_dev *solo_enc = vb2_get_drv_priv(vq);
+ struct solo_vb2_buf *solo_vb =
+ container_of(vb, struct solo_vb2_buf, vb);
+
+ spin_lock(&solo_enc->av_lock);
+ list_add_tail(&solo_vb->list, &solo_enc->vidq_active);
+ spin_unlock(&solo_enc->av_lock);
+}
+
+static int solo_ring_start(struct solo_dev *solo_dev)
+{
+ solo_dev->ring_thread = kthread_run(solo_ring_thread, solo_dev,
+ SOLO6X10_NAME "_ring");
+ if (IS_ERR(solo_dev->ring_thread)) {
+ int err = PTR_ERR(solo_dev->ring_thread);
+ solo_dev->ring_thread = NULL;
+ return err;
+ }
+
+ solo_irq_on(solo_dev, SOLO_IRQ_ENCODER);
+
+ return 0;
+}
+
+static void solo_ring_stop(struct solo_dev *solo_dev)
+{
+ if (solo_dev->ring_thread) {
+ kthread_stop(solo_dev->ring_thread);
+ solo_dev->ring_thread = NULL;
+ }
+
+ solo_irq_off(solo_dev, SOLO_IRQ_ENCODER);
+}
+
+static int solo_enc_start_streaming(struct vb2_queue *q, unsigned int count)
+{
+ struct solo_enc_dev *solo_enc = vb2_get_drv_priv(q);
+ int ret;
+
+ ret = solo_enc_on(solo_enc);
+ if (ret)
+ return ret;
+ return solo_ring_start(solo_enc->solo_dev);
+}
+
+static void solo_enc_stop_streaming(struct vb2_queue *q)
+{
+ struct solo_enc_dev *solo_enc = vb2_get_drv_priv(q);
+
+ solo_enc_off(solo_enc);
+ INIT_LIST_HEAD(&solo_enc->vidq_active);
+ solo_ring_stop(solo_enc->solo_dev);
+}
+
+static struct vb2_ops solo_enc_video_qops = {
+ .queue_setup = solo_enc_queue_setup,
+ .buf_queue = solo_enc_buf_queue,
+ .start_streaming = solo_enc_start_streaming,
+ .stop_streaming = solo_enc_stop_streaming,
+ .wait_prepare = vb2_ops_wait_prepare,
+ .wait_finish = vb2_ops_wait_finish,
+};
+
+static int solo_enc_querycap(struct file *file, void *priv,
+ struct v4l2_capability *cap)
+{
+ struct solo_enc_dev *solo_enc = video_drvdata(file);
+ struct solo_dev *solo_dev = solo_enc->solo_dev;
+
+ strcpy(cap->driver, SOLO6X10_NAME);
+ snprintf(cap->card, sizeof(cap->card), "Softlogic 6x10 Enc %d",
+ solo_enc->ch);
+ snprintf(cap->bus_info, sizeof(cap->bus_info), "PCI:%s",
+ pci_name(solo_dev->pdev));
+ cap->device_caps = V4L2_CAP_VIDEO_CAPTURE |
+ V4L2_CAP_READWRITE | V4L2_CAP_STREAMING;
+ cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS;
+ return 0;
+}
+
+static int solo_enc_enum_input(struct file *file, void *priv,
+ struct v4l2_input *input)
+{
+ struct solo_enc_dev *solo_enc = video_drvdata(file);
+ struct solo_dev *solo_dev = solo_enc->solo_dev;
+
+ if (input->index)
+ return -EINVAL;
+
+ snprintf(input->name, sizeof(input->name), "Encoder %d",
+ solo_enc->ch + 1);
+ input->type = V4L2_INPUT_TYPE_CAMERA;
+ input->std = solo_enc->vfd->tvnorms;
+
+ if (!tw28_get_video_status(solo_dev, solo_enc->ch))
+ input->status = V4L2_IN_ST_NO_SIGNAL;
+
+ return 0;
+}
+
+static int solo_enc_set_input(struct file *file, void *priv,
+ unsigned int index)
+{
+ if (index)
+ return -EINVAL;
+
+ return 0;
+}
+
+static int solo_enc_get_input(struct file *file, void *priv,
+ unsigned int *index)
+{
+ *index = 0;
+
+ return 0;
+}
+
+static int solo_enc_enum_fmt_cap(struct file *file, void *priv,
+ struct v4l2_fmtdesc *f)
+{
+ struct solo_enc_dev *solo_enc = video_drvdata(file);
+ int dev_type = solo_enc->solo_dev->type;
+
+ switch (f->index) {
+ case 0:
+ switch (dev_type) {
+ case SOLO_DEV_6010:
+ f->pixelformat = V4L2_PIX_FMT_MPEG4;
+ strcpy(f->description, "MPEG-4 part 2");
+ break;
+ case SOLO_DEV_6110:
+ f->pixelformat = V4L2_PIX_FMT_H264;
+ strcpy(f->description, "H.264");
+ break;
+ }
+ 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 inline int solo_valid_pixfmt(u32 pixfmt, int dev_type)
+{
+ return (pixfmt == V4L2_PIX_FMT_H264 && dev_type == SOLO_DEV_6110)
+ || (pixfmt == V4L2_PIX_FMT_MPEG4 && dev_type == SOLO_DEV_6010)
+ || pixfmt == V4L2_PIX_FMT_MJPEG ? 0 : -EINVAL;
+}
+
+static int solo_enc_try_fmt_cap(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct solo_enc_dev *solo_enc = video_drvdata(file);
+ struct solo_dev *solo_dev = solo_enc->solo_dev;
+ struct v4l2_pix_format *pix = &f->fmt.pix;
+
+ if (solo_valid_pixfmt(pix->pixelformat, solo_dev->type))
+ return -EINVAL;
+
+ if (pix->width < solo_dev->video_hsize ||
+ pix->height < solo_dev->video_vsize << 1) {
+ /* Default to CIF 1/2 size */
+ pix->width = solo_dev->video_hsize >> 1;
+ pix->height = solo_dev->video_vsize;
+ } else {
+ /* Full frame */
+ pix->width = solo_dev->video_hsize;
+ pix->height = solo_dev->video_vsize << 1;
+ }
+
+ switch (pix->field) {
+ case V4L2_FIELD_NONE:
+ case V4L2_FIELD_INTERLACED:
+ break;
+ case V4L2_FIELD_ANY:
+ default:
+ pix->field = V4L2_FIELD_INTERLACED;
+ break;
+ }
+
+ /* Just set these */
+ pix->colorspace = V4L2_COLORSPACE_SMPTE170M;
+ pix->sizeimage = FRAME_BUF_SIZE;
+ pix->bytesperline = 0;
+ pix->priv = 0;
+
+ return 0;
+}
+
+static int solo_enc_set_fmt_cap(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct solo_enc_dev *solo_enc = video_drvdata(file);
+ struct solo_dev *solo_dev = solo_enc->solo_dev;
+ struct v4l2_pix_format *pix = &f->fmt.pix;
+ int ret;
+
+ if (vb2_is_busy(&solo_enc->vidq))
+ return -EBUSY;
+
+ ret = solo_enc_try_fmt_cap(file, priv, f);
+ if (ret)
+ return ret;
+
+ if (pix->width == solo_dev->video_hsize)
+ solo_enc->mode = SOLO_ENC_MODE_D1;
+ else
+ solo_enc->mode = SOLO_ENC_MODE_CIF;
+
+ /* This does not change the encoder at all */
+ solo_enc->fmt = pix->pixelformat;
+
+ /*
+ * More information is needed about these 'extended' types. As far
+ * as I can tell these are basically additional video streams with
+ * different MPEG encoding attributes that can run in parallel with
+ * the main stream. If so, then this should be implemented as a
+ * second video node. Abusing priv like this is certainly not the
+ * right approach.
+ if (pix->priv)
+ solo_enc->type = SOLO_ENC_TYPE_EXT;
+ */
+ solo_update_mode(solo_enc);
+ return 0;
+}
+
+static int solo_enc_get_fmt_cap(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct solo_enc_dev *solo_enc = video_drvdata(file);
+ struct v4l2_pix_format *pix = &f->fmt.pix;
+
+ pix->width = solo_enc->width;
+ pix->height = solo_enc->height;
+ pix->pixelformat = solo_enc->fmt;
+ pix->field = solo_enc->interlaced ? V4L2_FIELD_INTERLACED :
+ V4L2_FIELD_NONE;
+ pix->sizeimage = FRAME_BUF_SIZE;
+ pix->colorspace = V4L2_COLORSPACE_SMPTE170M;
+ pix->priv = 0;
+
+ return 0;
+}
+
+static int solo_enc_g_std(struct file *file, void *priv, v4l2_std_id *i)
+{
+ struct solo_enc_dev *solo_enc = video_drvdata(file);
+ struct solo_dev *solo_dev = solo_enc->solo_dev;
+
+ if (solo_dev->video_type == SOLO_VO_FMT_TYPE_NTSC)
+ *i = V4L2_STD_NTSC_M;
+ else
+ *i = V4L2_STD_PAL;
+ return 0;
+}
+
+static int solo_enc_s_std(struct file *file, void *priv, v4l2_std_id std)
+{
+ struct solo_enc_dev *solo_enc = video_drvdata(file);
+
+ return solo_set_video_type(solo_enc->solo_dev, std & V4L2_STD_625_50);
+}
+
+static int solo_enum_framesizes(struct file *file, void *priv,
+ struct v4l2_frmsizeenum *fsize)
+{
+ struct solo_enc_dev *solo_enc = video_drvdata(file);
+ struct solo_dev *solo_dev = solo_enc->solo_dev;
+
+ if (solo_valid_pixfmt(fsize->pixel_format, solo_dev->type))
+ return -EINVAL;
+
+ switch (fsize->index) {
+ case 0:
+ fsize->discrete.width = solo_dev->video_hsize >> 1;
+ fsize->discrete.height = solo_dev->video_vsize;
+ break;
+ case 1:
+ fsize->discrete.width = solo_dev->video_hsize;
+ fsize->discrete.height = solo_dev->video_vsize << 1;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE;
+
+ return 0;
+}
+
+static int solo_enum_frameintervals(struct file *file, void *priv,
+ struct v4l2_frmivalenum *fintv)
+{
+ struct solo_enc_dev *solo_enc = video_drvdata(file);
+ struct solo_dev *solo_dev = solo_enc->solo_dev;
+
+ if (solo_valid_pixfmt(fintv->pixel_format, solo_dev->type))
+ return -EINVAL;
+ if (fintv->index)
+ return -EINVAL;
+ if ((fintv->width != solo_dev->video_hsize >> 1 ||
+ fintv->height != solo_dev->video_vsize) &&
+ (fintv->width != solo_dev->video_hsize ||
+ fintv->height != solo_dev->video_vsize << 1))
+ return -EINVAL;
+
+ fintv->type = V4L2_FRMIVAL_TYPE_STEPWISE;
+
+ fintv->stepwise.min.numerator = 1;
+ fintv->stepwise.min.denominator = solo_dev->fps;
+
+ fintv->stepwise.max.numerator = 15;
+ fintv->stepwise.max.denominator = solo_dev->fps;
+
+ fintv->stepwise.step.numerator = 1;
+ fintv->stepwise.step.denominator = solo_dev->fps;
+
+ return 0;
+}
+
+static int solo_g_parm(struct file *file, void *priv,
+ struct v4l2_streamparm *sp)
+{
+ struct solo_enc_dev *solo_enc = video_drvdata(file);
+ struct v4l2_captureparm *cp = &sp->parm.capture;
+
+ cp->capability = V4L2_CAP_TIMEPERFRAME;
+ cp->timeperframe.numerator = solo_enc->interval;
+ cp->timeperframe.denominator = solo_enc->solo_dev->fps;
+ cp->capturemode = 0;
+ /* XXX: Shouldn't we be able to get/set this from videobuf? */
+ cp->readbuffers = 2;
+
+ return 0;
+}
+
+static inline int calc_interval(u8 fps, u32 n, u32 d)
+{
+ if (!n || !d)
+ return 1;
+ if (d == fps)
+ return n;
+ n *= fps;
+ return min(15U, n / d + (n % d >= (fps >> 1)));
+}
+
+static int solo_s_parm(struct file *file, void *priv,
+ struct v4l2_streamparm *sp)
+{
+ struct solo_enc_dev *solo_enc = video_drvdata(file);
+ struct v4l2_fract *t = &sp->parm.capture.timeperframe;
+ u8 fps = solo_enc->solo_dev->fps;
+
+ if (vb2_is_streaming(&solo_enc->vidq))
+ return -EBUSY;
+
+ solo_enc->interval = calc_interval(fps, t->numerator, t->denominator);
+ solo_update_mode(solo_enc);
+ return solo_g_parm(file, priv, sp);
+}
+
+static long solo_enc_default(struct file *file, void *fh,
+ bool valid_prio, unsigned int cmd, void *arg)
+{
+ struct solo_enc_dev *solo_enc = video_drvdata(file);
+ struct solo_dev *solo_dev = solo_enc->solo_dev;
+ struct solo_motion_thresholds *thresholds = arg;
+
+ switch (cmd) {
+ case SOLO_IOC_G_MOTION_THRESHOLDS:
+ *thresholds = solo_enc->motion_thresholds;
+ return 0;
+
+ case SOLO_IOC_S_MOTION_THRESHOLDS:
+ if (!valid_prio)
+ return -EBUSY;
+ solo_enc->motion_thresholds = *thresholds;
+ if (solo_enc->motion_enabled && !solo_enc->motion_global)
+ return solo_set_motion_block(solo_dev, solo_enc->ch,
+ &solo_enc->motion_thresholds);
+ return 0;
+ default:
+ return -ENOTTY;
+ }
+}
+
+static int solo_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+ struct solo_enc_dev *solo_enc =
+ container_of(ctrl->handler, struct solo_enc_dev, hdl);
+ struct solo_dev *solo_dev = solo_enc->solo_dev;
+ int err;
+
+ switch (ctrl->id) {
+ case V4L2_CID_BRIGHTNESS:
+ case V4L2_CID_CONTRAST:
+ case V4L2_CID_SATURATION:
+ case V4L2_CID_HUE:
+ case V4L2_CID_SHARPNESS:
+ return tw28_set_ctrl_val(solo_dev, ctrl->id, solo_enc->ch,
+ ctrl->val);
+ case V4L2_CID_MPEG_VIDEO_GOP_SIZE:
+ solo_enc->gop = ctrl->val;
+ return 0;
+ case V4L2_CID_MOTION_THRESHOLD:
+ solo_enc->motion_thresh = ctrl->val;
+ if (!solo_enc->motion_global || !solo_enc->motion_enabled)
+ return 0;
+ return solo_set_motion_threshold(solo_dev, solo_enc->ch,
+ ctrl->val);
+ case V4L2_CID_MOTION_MODE:
+ solo_enc->motion_global = ctrl->val == 1;
+ solo_enc->motion_enabled = ctrl->val > 0;
+ if (ctrl->val) {
+ if (solo_enc->motion_global)
+ solo_set_motion_threshold(solo_dev,
+ solo_enc->ch, solo_enc->motion_thresh);
+ else
+ solo_set_motion_block(solo_dev, solo_enc->ch,
+ &solo_enc->motion_thresholds);
+ }
+ solo_motion_toggle(solo_enc, ctrl->val);
+ return 0;
+ case V4L2_CID_OSD_TEXT:
+ strcpy(solo_enc->osd_text, ctrl->string);
+ err = solo_osd_print(solo_enc);
+ return err;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static const struct v4l2_file_operations solo_enc_fops = {
+ .owner = THIS_MODULE,
+ .open = v4l2_fh_open,
+ .release = vb2_fop_release,
+ .read = vb2_fop_read,
+ .poll = vb2_fop_poll,
+ .mmap = vb2_fop_mmap,
+ .unlocked_ioctl = video_ioctl2,
+};
+
+static const struct v4l2_ioctl_ops solo_enc_ioctl_ops = {
+ .vidioc_querycap = solo_enc_querycap,
+ .vidioc_s_std = solo_enc_s_std,
+ .vidioc_g_std = solo_enc_g_std,
+ /* Input callbacks */
+ .vidioc_enum_input = solo_enc_enum_input,
+ .vidioc_s_input = solo_enc_set_input,
+ .vidioc_g_input = solo_enc_get_input,
+ /* Video capture format callbacks */
+ .vidioc_enum_fmt_vid_cap = solo_enc_enum_fmt_cap,
+ .vidioc_try_fmt_vid_cap = solo_enc_try_fmt_cap,
+ .vidioc_s_fmt_vid_cap = solo_enc_set_fmt_cap,
+ .vidioc_g_fmt_vid_cap = solo_enc_get_fmt_cap,
+ /* Streaming I/O */
+ .vidioc_reqbufs = vb2_ioctl_reqbufs,
+ .vidioc_querybuf = vb2_ioctl_querybuf,
+ .vidioc_qbuf = vb2_ioctl_qbuf,
+ .vidioc_dqbuf = vb2_ioctl_dqbuf,
+ .vidioc_streamon = vb2_ioctl_streamon,
+ .vidioc_streamoff = vb2_ioctl_streamoff,
+ /* Frame size and interval */
+ .vidioc_enum_framesizes = solo_enum_framesizes,
+ .vidioc_enum_frameintervals = solo_enum_frameintervals,
+ /* Video capture parameters */
+ .vidioc_s_parm = solo_s_parm,
+ .vidioc_g_parm = solo_g_parm,
+ /* Logging and events */
+ .vidioc_log_status = v4l2_ctrl_log_status,
+ .vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
+ .vidioc_unsubscribe_event = v4l2_event_unsubscribe,
+ .vidioc_default = solo_enc_default,
+};
+
+static const struct video_device solo_enc_template = {
+ .name = SOLO6X10_NAME,
+ .fops = &solo_enc_fops,
+ .ioctl_ops = &solo_enc_ioctl_ops,
+ .minor = -1,
+ .release = video_device_release,
+ .tvnorms = V4L2_STD_NTSC_M | V4L2_STD_PAL,
+};
+
+static const struct v4l2_ctrl_ops solo_ctrl_ops = {
+ .s_ctrl = solo_s_ctrl,
+};
+
+static const struct v4l2_ctrl_config solo_motion_threshold_ctrl = {
+ .ops = &solo_ctrl_ops,
+ .id = V4L2_CID_MOTION_THRESHOLD,
+ .name = "Motion Detection Threshold",
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .max = 0xffff,
+ .def = SOLO_DEF_MOT_THRESH,
+ .step = 1,
+ .flags = V4L2_CTRL_FLAG_SLIDER,
+};
+
+static const char * const solo_motion_mode_menu[] = {
+ "Disabled",
+ "Global Threshold",
+ "Regional Threshold",
+ NULL
+};
+
+static const struct v4l2_ctrl_config solo_motion_enable_ctrl = {
+ .ops = &solo_ctrl_ops,
+ .id = V4L2_CID_MOTION_MODE,
+ .name = "Motion Detection Mode",
+ .type = V4L2_CTRL_TYPE_MENU,
+ .qmenu = solo_motion_mode_menu,
+ .max = 2,
+};
+
+static const struct v4l2_ctrl_config solo_osd_text_ctrl = {
+ .ops = &solo_ctrl_ops,
+ .id = V4L2_CID_OSD_TEXT,
+ .name = "OSD Text",
+ .type = V4L2_CTRL_TYPE_STRING,
+ .max = OSD_TEXT_MAX,
+ .step = 1,
+};
+
+static struct solo_enc_dev *solo_enc_alloc(struct solo_dev *solo_dev,
+ u8 ch, unsigned nr)
+{
+ struct solo_enc_dev *solo_enc;
+ struct v4l2_ctrl_handler *hdl;
+ int ret;
+ int x, y;
+
+ solo_enc = kzalloc(sizeof(*solo_enc), GFP_KERNEL);
+ if (!solo_enc)
+ return ERR_PTR(-ENOMEM);
+
+ hdl = &solo_enc->hdl;
+ v4l2_ctrl_handler_init(hdl, 10);
+ v4l2_ctrl_new_std(hdl, &solo_ctrl_ops,
+ V4L2_CID_BRIGHTNESS, 0, 255, 1, 128);
+ v4l2_ctrl_new_std(hdl, &solo_ctrl_ops,
+ V4L2_CID_CONTRAST, 0, 255, 1, 128);
+ v4l2_ctrl_new_std(hdl, &solo_ctrl_ops,
+ V4L2_CID_SATURATION, 0, 255, 1, 128);
+ v4l2_ctrl_new_std(hdl, &solo_ctrl_ops,
+ V4L2_CID_HUE, 0, 255, 1, 128);
+ if (tw28_has_sharpness(solo_dev, ch))
+ v4l2_ctrl_new_std(hdl, &solo_ctrl_ops,
+ V4L2_CID_SHARPNESS, 0, 15, 1, 0);
+ v4l2_ctrl_new_std(hdl, &solo_ctrl_ops,
+ V4L2_CID_MPEG_VIDEO_GOP_SIZE, 1, 255, 1, solo_dev->fps);
+ v4l2_ctrl_new_custom(hdl, &solo_motion_threshold_ctrl, NULL);
+ v4l2_ctrl_new_custom(hdl, &solo_motion_enable_ctrl, NULL);
+ v4l2_ctrl_new_custom(hdl, &solo_osd_text_ctrl, NULL);
+ if (hdl->error) {
+ ret = hdl->error;
+ goto hdl_free;
+ }
+
+ solo_enc->solo_dev = solo_dev;
+ solo_enc->ch = ch;
+ mutex_init(&solo_enc->lock);
+ spin_lock_init(&solo_enc->av_lock);
+ INIT_LIST_HEAD(&solo_enc->vidq_active);
+ solo_enc->fmt = (solo_dev->type == SOLO_DEV_6010) ?
+ V4L2_PIX_FMT_MPEG4 : V4L2_PIX_FMT_H264;
+ solo_enc->type = SOLO_ENC_TYPE_STD;
+
+ solo_enc->qp = SOLO_DEFAULT_QP;
+ solo_enc->gop = solo_dev->fps;
+ solo_enc->interval = 1;
+ solo_enc->mode = SOLO_ENC_MODE_CIF;
+ solo_enc->motion_global = true;
+ solo_enc->motion_thresh = SOLO_DEF_MOT_THRESH;
+ for (y = 0; y < SOLO_MOTION_SZ; y++)
+ for (x = 0; x < SOLO_MOTION_SZ; x++)
+ solo_enc->motion_thresholds.thresholds[y][x] =
+ SOLO_DEF_MOT_THRESH;
+
+ solo_enc->vidq.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ solo_enc->vidq.io_modes = VB2_MMAP | VB2_USERPTR | VB2_READ;
+ solo_enc->vidq.ops = &solo_enc_video_qops;
+ solo_enc->vidq.mem_ops = &vb2_dma_sg_memops;
+ solo_enc->vidq.drv_priv = solo_enc;
+ solo_enc->vidq.gfp_flags = __GFP_DMA32;
+ solo_enc->vidq.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
+ solo_enc->vidq.buf_struct_size = sizeof(struct solo_vb2_buf);
+ solo_enc->vidq.lock = &solo_enc->lock;
+ ret = vb2_queue_init(&solo_enc->vidq);
+ if (ret)
+ goto hdl_free;
+ solo_update_mode(solo_enc);
+
+ spin_lock_init(&solo_enc->motion_lock);
+
+ /* Initialize this per encoder */
+ solo_enc->jpeg_len = sizeof(jpeg_header);
+ memcpy(solo_enc->jpeg_header, jpeg_header, solo_enc->jpeg_len);
+
+ solo_enc->desc_nelts = 32;
+ solo_enc->desc_items = pci_alloc_consistent(solo_dev->pdev,
+ sizeof(struct solo_p2m_desc) *
+ solo_enc->desc_nelts,
+ &solo_enc->desc_dma);
+ ret = -ENOMEM;
+ if (solo_enc->desc_items == NULL)
+ goto hdl_free;
+
+ solo_enc->vfd = video_device_alloc();
+ if (!solo_enc->vfd)
+ goto pci_free;
+
+ *solo_enc->vfd = solo_enc_template;
+ solo_enc->vfd->v4l2_dev = &solo_dev->v4l2_dev;
+ solo_enc->vfd->ctrl_handler = hdl;
+ solo_enc->vfd->queue = &solo_enc->vidq;
+ solo_enc->vfd->lock = &solo_enc->lock;
+ set_bit(V4L2_FL_USE_FH_PRIO, &solo_enc->vfd->flags);
+ video_set_drvdata(solo_enc->vfd, solo_enc);
+ ret = video_register_device(solo_enc->vfd, VFL_TYPE_GRABBER, nr);
+ if (ret < 0)
+ goto vdev_release;
+
+ snprintf(solo_enc->vfd->name, sizeof(solo_enc->vfd->name),
+ "%s-enc (%i/%i)", SOLO6X10_NAME, solo_dev->vfd->num,
+ solo_enc->vfd->num);
+
+ return solo_enc;
+
+vdev_release:
+ video_device_release(solo_enc->vfd);
+pci_free:
+ pci_free_consistent(solo_enc->solo_dev->pdev,
+ sizeof(struct solo_p2m_desc) * solo_enc->desc_nelts,
+ solo_enc->desc_items, solo_enc->desc_dma);
+hdl_free:
+ v4l2_ctrl_handler_free(hdl);
+ kfree(solo_enc);
+ return ERR_PTR(ret);
+}
+
+static void solo_enc_free(struct solo_enc_dev *solo_enc)
+{
+ if (solo_enc == NULL)
+ return;
+
+ video_unregister_device(solo_enc->vfd);
+ v4l2_ctrl_handler_free(&solo_enc->hdl);
+ kfree(solo_enc);
+}
+
+int solo_enc_v4l2_init(struct solo_dev *solo_dev, unsigned nr)
+{
+ int i;
+
+ init_waitqueue_head(&solo_dev->ring_thread_wait);
+
+ solo_dev->vh_size = sizeof(vop_header);
+ solo_dev->vh_buf = pci_alloc_consistent(solo_dev->pdev,
+ solo_dev->vh_size,
+ &solo_dev->vh_dma);
+ if (solo_dev->vh_buf == NULL)
+ return -ENOMEM;
+
+ for (i = 0; i < solo_dev->nr_chans; i++) {
+ solo_dev->v4l2_enc[i] = solo_enc_alloc(solo_dev, i, nr);
+ if (IS_ERR(solo_dev->v4l2_enc[i]))
+ break;
+ }
+
+ if (i != solo_dev->nr_chans) {
+ int ret = PTR_ERR(solo_dev->v4l2_enc[i]);
+ while (i--)
+ solo_enc_free(solo_dev->v4l2_enc[i]);
+ pci_free_consistent(solo_dev->pdev, solo_dev->vh_size,
+ solo_dev->vh_buf, solo_dev->vh_dma);
+ solo_dev->vh_buf = NULL;
+ return ret;
+ }
+
+ if (solo_dev->type == SOLO_DEV_6010)
+ solo_dev->enc_bw_remain = solo_dev->fps * 4 * 4;
+ else
+ solo_dev->enc_bw_remain = solo_dev->fps * 4 * 5;
+
+ dev_info(&solo_dev->pdev->dev, "Encoders as /dev/video%d-%d\n",
+ solo_dev->v4l2_enc[0]->vfd->num,
+ solo_dev->v4l2_enc[solo_dev->nr_chans - 1]->vfd->num);
+
+ return 0;
+}
+
+void solo_enc_v4l2_exit(struct solo_dev *solo_dev)
+{
+ int i;
+
+ for (i = 0; i < solo_dev->nr_chans; i++)
+ solo_enc_free(solo_dev->v4l2_enc[i]);
+
+ if (solo_dev->vh_buf)
+ pci_free_consistent(solo_dev->pdev, solo_dev->vh_size,
+ solo_dev->vh_buf, solo_dev->vh_dma);
+}
diff --git a/drivers/staging/media/solo6x10/solo6x10-v4l2.c b/drivers/staging/media/solo6x10/solo6x10-v4l2.c
new file mode 100644
index 00000000000..5d0100eb38e
--- /dev/null
+++ b/drivers/staging/media/solo6x10/solo6x10-v4l2.c
@@ -0,0 +1,734 @@
+/*
+ * Copyright (C) 2010-2013 Bluecherry, LLC <http://www.bluecherrydvr.com>
+ *
+ * Original author:
+ * Ben Collins <bcollins@ubuntu.com>
+ *
+ * Additional work by:
+ * John Brooks <john.brooks@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/v4l2-event.h>
+#include <media/videobuf2-dma-contig.h>
+
+#include "solo6x10.h"
+#include "solo6x10-tw28.h"
+
+/* Image size is two fields, SOLO_HW_BPL is one horizontal line in hardware */
+#define SOLO_HW_BPL 2048
+#define solo_vlines(__solo) (__solo->video_vsize * 2)
+#define solo_image_size(__solo) (solo_bytesperline(__solo) * \
+ solo_vlines(__solo))
+#define solo_bytesperline(__solo) (__solo->video_hsize * 2)
+
+#define MIN_VID_BUFFERS 2
+
+static inline void erase_on(struct solo_dev *solo_dev)
+{
+ solo_reg_write(solo_dev, SOLO_VO_DISP_ERASE, SOLO_VO_DISP_ERASE_ON);
+ solo_dev->erasing = 1;
+ solo_dev->frame_blank = 0;
+}
+
+static inline int erase_off(struct solo_dev *solo_dev)
+{
+ if (!solo_dev->erasing)
+ return 0;
+
+ /* First time around, assert erase off */
+ if (!solo_dev->frame_blank)
+ solo_reg_write(solo_dev, SOLO_VO_DISP_ERASE, 0);
+ /* Keep the erasing flag on for 8 frames minimum */
+ if (solo_dev->frame_blank++ >= 8)
+ solo_dev->erasing = 0;
+
+ return 1;
+}
+
+void solo_video_in_isr(struct solo_dev *solo_dev)
+{
+ wake_up_interruptible_all(&solo_dev->disp_thread_wait);
+}
+
+static void solo_win_setup(struct solo_dev *solo_dev, u8 ch,
+ int sx, int sy, int ex, int ey, int scale)
+{
+ if (ch >= solo_dev->nr_chans)
+ return;
+
+ /* Here, we just keep window/channel the same */
+ solo_reg_write(solo_dev, SOLO_VI_WIN_CTRL0(ch),
+ SOLO_VI_WIN_CHANNEL(ch) |
+ SOLO_VI_WIN_SX(sx) |
+ SOLO_VI_WIN_EX(ex) |
+ SOLO_VI_WIN_SCALE(scale));
+
+ solo_reg_write(solo_dev, SOLO_VI_WIN_CTRL1(ch),
+ SOLO_VI_WIN_SY(sy) |
+ SOLO_VI_WIN_EY(ey));
+}
+
+static int solo_v4l2_ch_ext_4up(struct solo_dev *solo_dev, u8 idx, int on)
+{
+ u8 ch = idx * 4;
+
+ if (ch >= solo_dev->nr_chans)
+ return -EINVAL;
+
+ if (!on) {
+ u8 i;
+ for (i = ch; i < ch + 4; i++)
+ solo_win_setup(solo_dev, i, solo_dev->video_hsize,
+ solo_vlines(solo_dev),
+ solo_dev->video_hsize,
+ solo_vlines(solo_dev), 0);
+ return 0;
+ }
+
+ /* Row 1 */
+ solo_win_setup(solo_dev, ch, 0, 0, solo_dev->video_hsize / 2,
+ solo_vlines(solo_dev) / 2, 3);
+ solo_win_setup(solo_dev, ch + 1, solo_dev->video_hsize / 2, 0,
+ solo_dev->video_hsize, solo_vlines(solo_dev) / 2, 3);
+ /* Row 2 */
+ solo_win_setup(solo_dev, ch + 2, 0, solo_vlines(solo_dev) / 2,
+ solo_dev->video_hsize / 2, solo_vlines(solo_dev), 3);
+ solo_win_setup(solo_dev, ch + 3, solo_dev->video_hsize / 2,
+ solo_vlines(solo_dev) / 2, solo_dev->video_hsize,
+ solo_vlines(solo_dev), 3);
+
+ return 0;
+}
+
+static int solo_v4l2_ch_ext_16up(struct solo_dev *solo_dev, int on)
+{
+ int sy, ysize, hsize, i;
+
+ if (!on) {
+ for (i = 0; i < 16; i++)
+ solo_win_setup(solo_dev, i, solo_dev->video_hsize,
+ solo_vlines(solo_dev),
+ solo_dev->video_hsize,
+ solo_vlines(solo_dev), 0);
+ return 0;
+ }
+
+ ysize = solo_vlines(solo_dev) / 4;
+ hsize = solo_dev->video_hsize / 4;
+
+ for (sy = 0, i = 0; i < 4; i++, sy += ysize) {
+ solo_win_setup(solo_dev, i * 4, 0, sy, hsize,
+ sy + ysize, 5);
+ solo_win_setup(solo_dev, (i * 4) + 1, hsize, sy,
+ hsize * 2, sy + ysize, 5);
+ solo_win_setup(solo_dev, (i * 4) + 2, hsize * 2, sy,
+ hsize * 3, sy + ysize, 5);
+ solo_win_setup(solo_dev, (i * 4) + 3, hsize * 3, sy,
+ solo_dev->video_hsize, sy + ysize, 5);
+ }
+
+ return 0;
+}
+
+static int solo_v4l2_ch(struct solo_dev *solo_dev, u8 ch, int on)
+{
+ u8 ext_ch;
+
+ if (ch < solo_dev->nr_chans) {
+ solo_win_setup(solo_dev, ch, on ? 0 : solo_dev->video_hsize,
+ on ? 0 : solo_vlines(solo_dev),
+ solo_dev->video_hsize, solo_vlines(solo_dev),
+ on ? 1 : 0);
+ return 0;
+ }
+
+ if (ch >= solo_dev->nr_chans + solo_dev->nr_ext)
+ return -EINVAL;
+
+ ext_ch = ch - solo_dev->nr_chans;
+
+ /* 4up's first */
+ if (ext_ch < 4)
+ return solo_v4l2_ch_ext_4up(solo_dev, ext_ch, on);
+
+ /* Remaining case is 16up for 16-port */
+ return solo_v4l2_ch_ext_16up(solo_dev, on);
+}
+
+static int solo_v4l2_set_ch(struct solo_dev *solo_dev, u8 ch)
+{
+ if (ch >= solo_dev->nr_chans + solo_dev->nr_ext)
+ return -EINVAL;
+
+ erase_on(solo_dev);
+
+ solo_v4l2_ch(solo_dev, solo_dev->cur_disp_ch, 0);
+ solo_v4l2_ch(solo_dev, ch, 1);
+
+ solo_dev->cur_disp_ch = ch;
+
+ return 0;
+}
+
+static void solo_fillbuf(struct solo_dev *solo_dev,
+ struct vb2_buffer *vb)
+{
+ dma_addr_t vbuf;
+ unsigned int fdma_addr;
+ int error = -1;
+ int i;
+
+ vbuf = vb2_dma_contig_plane_dma_addr(vb, 0);
+ if (!vbuf)
+ goto finish_buf;
+
+ if (erase_off(solo_dev)) {
+ void *p = vb2_plane_vaddr(vb, 0);
+ int image_size = solo_image_size(solo_dev);
+ for (i = 0; i < image_size; i += 2) {
+ ((u8 *)p)[i] = 0x80;
+ ((u8 *)p)[i + 1] = 0x00;
+ }
+ error = 0;
+ } else {
+ fdma_addr = SOLO_DISP_EXT_ADDR + (solo_dev->old_write *
+ (SOLO_HW_BPL * solo_vlines(solo_dev)));
+
+ error = solo_p2m_dma_t(solo_dev, 0, vbuf, fdma_addr,
+ solo_bytesperline(solo_dev),
+ solo_vlines(solo_dev), SOLO_HW_BPL);
+ }
+
+finish_buf:
+ if (!error) {
+ vb2_set_plane_payload(vb, 0,
+ solo_vlines(solo_dev) * solo_bytesperline(solo_dev));
+ vb->v4l2_buf.sequence = solo_dev->sequence++;
+ v4l2_get_timestamp(&vb->v4l2_buf.timestamp);
+ }
+
+ vb2_buffer_done(vb, error ? VB2_BUF_STATE_ERROR : VB2_BUF_STATE_DONE);
+}
+
+static void solo_thread_try(struct solo_dev *solo_dev)
+{
+ struct solo_vb2_buf *vb;
+
+ /* Only "break" from this loop if slock is held, otherwise
+ * just return. */
+ for (;;) {
+ unsigned int cur_write;
+
+ cur_write = SOLO_VI_STATUS0_PAGE(
+ solo_reg_read(solo_dev, SOLO_VI_STATUS0));
+ if (cur_write == solo_dev->old_write)
+ return;
+
+ spin_lock(&solo_dev->slock);
+
+ if (list_empty(&solo_dev->vidq_active))
+ break;
+
+ vb = list_first_entry(&solo_dev->vidq_active, struct solo_vb2_buf,
+ list);
+
+ solo_dev->old_write = cur_write;
+ list_del(&vb->list);
+
+ spin_unlock(&solo_dev->slock);
+
+ solo_fillbuf(solo_dev, &vb->vb);
+ }
+
+ assert_spin_locked(&solo_dev->slock);
+ spin_unlock(&solo_dev->slock);
+}
+
+static int solo_thread(void *data)
+{
+ struct solo_dev *solo_dev = data;
+ DECLARE_WAITQUEUE(wait, current);
+
+ set_freezable();
+ add_wait_queue(&solo_dev->disp_thread_wait, &wait);
+
+ for (;;) {
+ long timeout = schedule_timeout_interruptible(HZ);
+ if (timeout == -ERESTARTSYS || kthread_should_stop())
+ break;
+ solo_thread_try(solo_dev);
+ try_to_freeze();
+ }
+
+ remove_wait_queue(&solo_dev->disp_thread_wait, &wait);
+
+ return 0;
+}
+
+static int solo_start_thread(struct solo_dev *solo_dev)
+{
+ int ret = 0;
+
+ solo_dev->kthread = kthread_run(solo_thread, solo_dev, SOLO6X10_NAME "_disp");
+
+ if (IS_ERR(solo_dev->kthread)) {
+ ret = PTR_ERR(solo_dev->kthread);
+ solo_dev->kthread = NULL;
+ return ret;
+ }
+ solo_irq_on(solo_dev, SOLO_IRQ_VIDEO_IN);
+
+ return ret;
+}
+
+static void solo_stop_thread(struct solo_dev *solo_dev)
+{
+ if (!solo_dev->kthread)
+ return;
+
+ solo_irq_off(solo_dev, SOLO_IRQ_VIDEO_IN);
+ kthread_stop(solo_dev->kthread);
+ solo_dev->kthread = NULL;
+}
+
+static int solo_queue_setup(struct vb2_queue *q, const struct v4l2_format *fmt,
+ unsigned int *num_buffers, unsigned int *num_planes,
+ unsigned int sizes[], void *alloc_ctxs[])
+{
+ struct solo_dev *solo_dev = vb2_get_drv_priv(q);
+
+ sizes[0] = solo_image_size(solo_dev);
+ alloc_ctxs[0] = solo_dev->alloc_ctx;
+ *num_planes = 1;
+
+ if (*num_buffers < MIN_VID_BUFFERS)
+ *num_buffers = MIN_VID_BUFFERS;
+
+ return 0;
+}
+
+static int solo_start_streaming(struct vb2_queue *q, unsigned int count)
+{
+ struct solo_dev *solo_dev = vb2_get_drv_priv(q);
+
+ solo_dev->sequence = 0;
+ return solo_start_thread(solo_dev);
+}
+
+static void solo_stop_streaming(struct vb2_queue *q)
+{
+ struct solo_dev *solo_dev = vb2_get_drv_priv(q);
+
+ solo_stop_thread(solo_dev);
+ INIT_LIST_HEAD(&solo_dev->vidq_active);
+}
+
+static void solo_buf_queue(struct vb2_buffer *vb)
+{
+ struct vb2_queue *vq = vb->vb2_queue;
+ struct solo_dev *solo_dev = vb2_get_drv_priv(vq);
+ struct solo_vb2_buf *solo_vb =
+ container_of(vb, struct solo_vb2_buf, vb);
+
+ spin_lock(&solo_dev->slock);
+ list_add_tail(&solo_vb->list, &solo_dev->vidq_active);
+ spin_unlock(&solo_dev->slock);
+ wake_up_interruptible(&solo_dev->disp_thread_wait);
+}
+
+static const struct vb2_ops solo_video_qops = {
+ .queue_setup = solo_queue_setup,
+ .buf_queue = solo_buf_queue,
+ .start_streaming = solo_start_streaming,
+ .stop_streaming = solo_stop_streaming,
+ .wait_prepare = vb2_ops_wait_prepare,
+ .wait_finish = vb2_ops_wait_finish,
+};
+
+static int solo_querycap(struct file *file, void *priv,
+ struct v4l2_capability *cap)
+{
+ struct solo_dev *solo_dev = video_drvdata(file);
+
+ strcpy(cap->driver, SOLO6X10_NAME);
+ strcpy(cap->card, "Softlogic 6x10");
+ snprintf(cap->bus_info, sizeof(cap->bus_info), "PCI:%s",
+ pci_name(solo_dev->pdev));
+ cap->device_caps = V4L2_CAP_VIDEO_CAPTURE |
+ V4L2_CAP_READWRITE | V4L2_CAP_STREAMING;
+ cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS;
+ return 0;
+}
+
+static int solo_enum_ext_input(struct solo_dev *solo_dev,
+ struct v4l2_input *input)
+{
+ static const char * const dispnames_1[] = { "4UP" };
+ static const char * const dispnames_2[] = { "4UP-1", "4UP-2" };
+ static const char * const dispnames_5[] = {
+ "4UP-1", "4UP-2", "4UP-3", "4UP-4", "16UP"
+ };
+ const char * const *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_dev *solo_dev = video_drvdata(file);
+
+ if (input->index >= solo_dev->nr_chans) {
+ int ret = solo_enum_ext_input(solo_dev, input);
+ if (ret < 0)
+ return ret;
+ } else {
+ snprintf(input->name, sizeof(input->name), "Camera %d",
+ input->index + 1);
+
+ /* We can only check this for normal inputs */
+ if (!tw28_get_video_status(solo_dev, input->index))
+ input->status = V4L2_IN_ST_NO_SIGNAL;
+ }
+
+ input->type = V4L2_INPUT_TYPE_CAMERA;
+ input->std = solo_dev->vfd->tvnorms;
+ return 0;
+}
+
+static int solo_set_input(struct file *file, void *priv, unsigned int index)
+{
+ struct solo_dev *solo_dev = video_drvdata(file);
+ int ret = solo_v4l2_set_ch(solo_dev, index);
+
+ if (!ret) {
+ while (erase_off(solo_dev))
+ /* Do nothing */;
+ }
+
+ return ret;
+}
+
+static int solo_get_input(struct file *file, void *priv, unsigned int *index)
+{
+ struct solo_dev *solo_dev = video_drvdata(file);
+
+ *index = solo_dev->cur_disp_ch;
+
+ return 0;
+}
+
+static int solo_enum_fmt_cap(struct file *file, void *priv,
+ struct v4l2_fmtdesc *f)
+{
+ if (f->index)
+ return -EINVAL;
+
+ f->pixelformat = V4L2_PIX_FMT_UYVY;
+ 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_dev *solo_dev = video_drvdata(file);
+ struct v4l2_pix_format *pix = &f->fmt.pix;
+ int image_size = solo_image_size(solo_dev);
+
+ if (pix->pixelformat != V4L2_PIX_FMT_UYVY)
+ return -EINVAL;
+
+ pix->width = solo_dev->video_hsize;
+ pix->height = solo_vlines(solo_dev);
+ pix->sizeimage = image_size;
+ pix->field = V4L2_FIELD_INTERLACED;
+ pix->pixelformat = V4L2_PIX_FMT_UYVY;
+ pix->colorspace = V4L2_COLORSPACE_SMPTE170M;
+ pix->priv = 0;
+ return 0;
+}
+
+static int solo_set_fmt_cap(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct solo_dev *solo_dev = video_drvdata(file);
+
+ if (vb2_is_busy(&solo_dev->vidq))
+ return -EBUSY;
+
+ /* For right now, if it doesn't match our running config,
+ * then fail */
+ return solo_try_fmt_cap(file, priv, f);
+}
+
+static int solo_get_fmt_cap(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct solo_dev *solo_dev = video_drvdata(file);
+ struct v4l2_pix_format *pix = &f->fmt.pix;
+
+ pix->width = solo_dev->video_hsize;
+ pix->height = solo_vlines(solo_dev);
+ pix->pixelformat = V4L2_PIX_FMT_UYVY;
+ pix->field = V4L2_FIELD_INTERLACED;
+ pix->sizeimage = solo_image_size(solo_dev);
+ pix->colorspace = V4L2_COLORSPACE_SMPTE170M;
+ pix->bytesperline = solo_bytesperline(solo_dev);
+ pix->priv = 0;
+
+ return 0;
+}
+
+static int solo_g_std(struct file *file, void *priv, v4l2_std_id *i)
+{
+ struct solo_dev *solo_dev = video_drvdata(file);
+
+ if (solo_dev->video_type == SOLO_VO_FMT_TYPE_NTSC)
+ *i = V4L2_STD_NTSC_M;
+ else
+ *i = V4L2_STD_PAL;
+ return 0;
+}
+
+int solo_set_video_type(struct solo_dev *solo_dev, bool is_50hz)
+{
+ int i;
+
+ /* Make sure all video nodes are idle */
+ if (vb2_is_busy(&solo_dev->vidq))
+ return -EBUSY;
+ for (i = 0; i < solo_dev->nr_chans; i++)
+ if (vb2_is_busy(&solo_dev->v4l2_enc[i]->vidq))
+ return -EBUSY;
+ solo_dev->video_type = is_50hz ? SOLO_VO_FMT_TYPE_PAL :
+ SOLO_VO_FMT_TYPE_NTSC;
+ /* Reconfigure for the new standard */
+ solo_disp_init(solo_dev);
+ solo_enc_init(solo_dev);
+ solo_tw28_init(solo_dev);
+ for (i = 0; i < solo_dev->nr_chans; i++)
+ solo_update_mode(solo_dev->v4l2_enc[i]);
+ return solo_v4l2_set_ch(solo_dev, solo_dev->cur_disp_ch);
+}
+
+static int solo_s_std(struct file *file, void *priv, v4l2_std_id std)
+{
+ struct solo_dev *solo_dev = video_drvdata(file);
+
+ return solo_set_video_type(solo_dev, std & V4L2_STD_625_50);
+}
+
+static int solo_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+ struct solo_dev *solo_dev =
+ container_of(ctrl->handler, struct solo_dev, disp_hdl);
+
+ switch (ctrl->id) {
+ case V4L2_CID_MOTION_TRACE:
+ if (ctrl->val) {
+ solo_reg_write(solo_dev, SOLO_VI_MOTION_BORDER,
+ SOLO_VI_MOTION_Y_ADD |
+ SOLO_VI_MOTION_Y_VALUE(0x20) |
+ SOLO_VI_MOTION_CB_VALUE(0x10) |
+ SOLO_VI_MOTION_CR_VALUE(0x10));
+ solo_reg_write(solo_dev, SOLO_VI_MOTION_BAR,
+ SOLO_VI_MOTION_CR_ADD |
+ SOLO_VI_MOTION_Y_VALUE(0x10) |
+ SOLO_VI_MOTION_CB_VALUE(0x80) |
+ SOLO_VI_MOTION_CR_VALUE(0x10));
+ } else {
+ solo_reg_write(solo_dev, SOLO_VI_MOTION_BORDER, 0);
+ solo_reg_write(solo_dev, SOLO_VI_MOTION_BAR, 0);
+ }
+ return 0;
+ default:
+ break;
+ }
+ return -EINVAL;
+}
+
+static const struct v4l2_file_operations solo_v4l2_fops = {
+ .owner = THIS_MODULE,
+ .open = v4l2_fh_open,
+ .release = vb2_fop_release,
+ .read = vb2_fop_read,
+ .poll = vb2_fop_poll,
+ .mmap = vb2_fop_mmap,
+ .unlocked_ioctl = video_ioctl2,
+};
+
+static const struct v4l2_ioctl_ops solo_v4l2_ioctl_ops = {
+ .vidioc_querycap = solo_querycap,
+ .vidioc_s_std = solo_s_std,
+ .vidioc_g_std = solo_g_std,
+ /* Input callbacks */
+ .vidioc_enum_input = solo_enum_input,
+ .vidioc_s_input = solo_set_input,
+ .vidioc_g_input = solo_get_input,
+ /* Video capture format callbacks */
+ .vidioc_enum_fmt_vid_cap = solo_enum_fmt_cap,
+ .vidioc_try_fmt_vid_cap = solo_try_fmt_cap,
+ .vidioc_s_fmt_vid_cap = solo_set_fmt_cap,
+ .vidioc_g_fmt_vid_cap = solo_get_fmt_cap,
+ /* Streaming I/O */
+ .vidioc_reqbufs = vb2_ioctl_reqbufs,
+ .vidioc_querybuf = vb2_ioctl_querybuf,
+ .vidioc_qbuf = vb2_ioctl_qbuf,
+ .vidioc_dqbuf = vb2_ioctl_dqbuf,
+ .vidioc_streamon = vb2_ioctl_streamon,
+ .vidioc_streamoff = vb2_ioctl_streamoff,
+ /* Logging and events */
+ .vidioc_log_status = v4l2_ctrl_log_status,
+ .vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
+ .vidioc_unsubscribe_event = v4l2_event_unsubscribe,
+};
+
+static struct video_device solo_v4l2_template = {
+ .name = SOLO6X10_NAME,
+ .fops = &solo_v4l2_fops,
+ .ioctl_ops = &solo_v4l2_ioctl_ops,
+ .minor = -1,
+ .release = video_device_release,
+ .tvnorms = V4L2_STD_NTSC_M | V4L2_STD_PAL,
+};
+
+static const struct v4l2_ctrl_ops solo_ctrl_ops = {
+ .s_ctrl = solo_s_ctrl,
+};
+
+static const struct v4l2_ctrl_config solo_motion_trace_ctrl = {
+ .ops = &solo_ctrl_ops,
+ .id = V4L2_CID_MOTION_TRACE,
+ .name = "Motion Detection Trace",
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ .max = 1,
+ .step = 1,
+};
+
+int solo_v4l2_init(struct solo_dev *solo_dev, unsigned nr)
+{
+ int ret;
+ int i;
+
+ init_waitqueue_head(&solo_dev->disp_thread_wait);
+ spin_lock_init(&solo_dev->slock);
+ mutex_init(&solo_dev->lock);
+ INIT_LIST_HEAD(&solo_dev->vidq_active);
+
+ solo_dev->vfd = video_device_alloc();
+ if (!solo_dev->vfd)
+ return -ENOMEM;
+
+ *solo_dev->vfd = solo_v4l2_template;
+ solo_dev->vfd->v4l2_dev = &solo_dev->v4l2_dev;
+ solo_dev->vfd->queue = &solo_dev->vidq;
+ solo_dev->vfd->lock = &solo_dev->lock;
+ v4l2_ctrl_handler_init(&solo_dev->disp_hdl, 1);
+ v4l2_ctrl_new_custom(&solo_dev->disp_hdl, &solo_motion_trace_ctrl, NULL);
+ if (solo_dev->disp_hdl.error) {
+ ret = solo_dev->disp_hdl.error;
+ goto fail;
+ }
+ solo_dev->vfd->ctrl_handler = &solo_dev->disp_hdl;
+ set_bit(V4L2_FL_USE_FH_PRIO, &solo_dev->vfd->flags);
+
+ video_set_drvdata(solo_dev->vfd, solo_dev);
+
+ solo_dev->vidq.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ solo_dev->vidq.io_modes = VB2_MMAP | VB2_USERPTR | VB2_READ;
+ solo_dev->vidq.ops = &solo_video_qops;
+ solo_dev->vidq.mem_ops = &vb2_dma_contig_memops;
+ solo_dev->vidq.drv_priv = solo_dev;
+ solo_dev->vidq.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
+ solo_dev->vidq.gfp_flags = __GFP_DMA32;
+ solo_dev->vidq.buf_struct_size = sizeof(struct solo_vb2_buf);
+ solo_dev->vidq.lock = &solo_dev->lock;
+ ret = vb2_queue_init(&solo_dev->vidq);
+ if (ret < 0)
+ goto fail;
+
+ solo_dev->alloc_ctx = vb2_dma_contig_init_ctx(&solo_dev->pdev->dev);
+ if (IS_ERR(solo_dev->alloc_ctx)) {
+ dev_err(&solo_dev->pdev->dev, "Can't allocate buffer context");
+ return PTR_ERR(solo_dev->alloc_ctx);
+ }
+
+ /* Cycle all the channels and clear */
+ for (i = 0; i < solo_dev->nr_chans; i++) {
+ solo_v4l2_set_ch(solo_dev, i);
+ while (erase_off(solo_dev))
+ /* Do nothing */;
+ }
+
+ /* Set the default display channel */
+ solo_v4l2_set_ch(solo_dev, 0);
+ while (erase_off(solo_dev))
+ /* Do nothing */;
+
+ ret = video_register_device(solo_dev->vfd, VFL_TYPE_GRABBER, nr);
+ if (ret < 0)
+ goto fail;
+
+ snprintf(solo_dev->vfd->name, sizeof(solo_dev->vfd->name), "%s (%i)",
+ SOLO6X10_NAME, solo_dev->vfd->num);
+
+ dev_info(&solo_dev->pdev->dev, "Display as /dev/video%d with "
+ "%d inputs (%d extended)\n", solo_dev->vfd->num,
+ solo_dev->nr_chans, solo_dev->nr_ext);
+
+ return 0;
+
+fail:
+ video_device_release(solo_dev->vfd);
+ vb2_dma_contig_cleanup_ctx(solo_dev->alloc_ctx);
+ v4l2_ctrl_handler_free(&solo_dev->disp_hdl);
+ solo_dev->vfd = NULL;
+ return ret;
+}
+
+void solo_v4l2_exit(struct solo_dev *solo_dev)
+{
+ if (solo_dev->vfd == NULL)
+ return;
+
+ video_unregister_device(solo_dev->vfd);
+ vb2_dma_contig_cleanup_ctx(solo_dev->alloc_ctx);
+ v4l2_ctrl_handler_free(&solo_dev->disp_hdl);
+ solo_dev->vfd = NULL;
+}
diff --git a/drivers/staging/media/solo6x10/solo6x10.h b/drivers/staging/media/solo6x10/solo6x10.h
new file mode 100644
index 00000000000..8964f8be158
--- /dev/null
+++ b/drivers/staging/media/solo6x10/solo6x10.h
@@ -0,0 +1,426 @@
+/*
+ * Copyright (C) 2010-2013 Bluecherry, LLC <http://www.bluecherrydvr.com>
+ *
+ * Original author:
+ * Ben Collins <bcollins@ubuntu.com>
+ *
+ * Additional work by:
+ * John Brooks <john.brooks@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 __SOLO6X10_H
+#define __SOLO6X10_H
+
+#include <linux/pci.h>
+#include <linux/i2c.h>
+#include <linux/mutex.h>
+#include <linux/list.h>
+#include <linux/wait.h>
+#include <linux/stringify.h>
+#include <linux/io.h>
+#include <linux/atomic.h>
+#include <linux/slab.h>
+#include <linux/videodev2.h>
+
+#include <media/v4l2-dev.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-ctrls.h>
+#include <media/videobuf2-core.h>
+
+#include "solo6x10-regs.h"
+
+#ifndef PCI_VENDOR_ID_SOFTLOGIC
+#define PCI_VENDOR_ID_SOFTLOGIC 0x9413
+#define PCI_DEVICE_ID_SOLO6010 0x6010
+#define PCI_DEVICE_ID_SOLO6110 0x6110
+#endif
+
+#ifndef PCI_VENDOR_ID_BLUECHERRY
+#define PCI_VENDOR_ID_BLUECHERRY 0x1BB3
+/* Neugent Softlogic 6010 based cards */
+#define PCI_DEVICE_ID_NEUSOLO_4 0x4304
+#define PCI_DEVICE_ID_NEUSOLO_9 0x4309
+#define PCI_DEVICE_ID_NEUSOLO_16 0x4310
+/* Bluecherry Softlogic 6010 based cards */
+#define PCI_DEVICE_ID_BC_SOLO_4 0x4E04
+#define PCI_DEVICE_ID_BC_SOLO_9 0x4E09
+#define PCI_DEVICE_ID_BC_SOLO_16 0x4E10
+/* Bluecherry Softlogic 6110 based cards */
+#define PCI_DEVICE_ID_BC_6110_4 0x5304
+#define PCI_DEVICE_ID_BC_6110_8 0x5308
+#define PCI_DEVICE_ID_BC_6110_16 0x5310
+#endif /* Bluecherry */
+
+/* Used in pci_device_id, and solo_dev->type */
+#define SOLO_DEV_6010 0
+#define SOLO_DEV_6110 1
+
+#define SOLO6X10_NAME "solo6x10"
+
+#define SOLO_MAX_CHANNELS 16
+
+#define SOLO6X10_VERSION "3.0.0"
+
+/*
+ * The SOLO6x10 actually has 8 i2c channels, but we only use 2.
+ * 0 - Techwell chip(s)
+ * 1 - SAA7128
+ */
+#define SOLO_I2C_ADAPTERS 2
+#define SOLO_I2C_TW 0
+#define SOLO_I2C_SAA 1
+
+/* DMA Engine setup */
+#define SOLO_NR_P2M 4
+#define SOLO_NR_P2M_DESC 256
+#define SOLO_P2M_DESC_SIZE (SOLO_NR_P2M_DESC * 16)
+
+/* Encoder standard modes */
+#define SOLO_ENC_MODE_CIF 2
+#define SOLO_ENC_MODE_HD1 1
+#define SOLO_ENC_MODE_D1 9
+
+#define SOLO_DEFAULT_QP 3
+
+#ifndef V4L2_BUF_FLAG_MOTION_ON
+#define V4L2_BUF_FLAG_MOTION_ON 0x10000
+#define V4L2_BUF_FLAG_MOTION_DETECTED 0x20000
+#endif
+
+#define SOLO_CID_CUSTOM_BASE (V4L2_CID_USER_BASE | 0xf000)
+#define V4L2_CID_MOTION_MODE (SOLO_CID_CUSTOM_BASE+0)
+#define V4L2_CID_MOTION_THRESHOLD (SOLO_CID_CUSTOM_BASE+1)
+#define V4L2_CID_MOTION_TRACE (SOLO_CID_CUSTOM_BASE+2)
+#define V4L2_CID_OSD_TEXT (SOLO_CID_CUSTOM_BASE+3)
+
+/*
+ * Motion thresholds are in a table of 64x64 samples, with
+ * each sample representing 16x16 pixels of the source. In
+ * effect, 44x30 samples are used for NTSC, and 44x36 for PAL.
+ * The 5th sample on the 10th row is (10*64)+5 = 645.
+ *
+ * Using a 64x64 array will result in a problem on some architectures like
+ * the powerpc where the size of the argument is limited to 13 bits.
+ * Since both PAL and NTSC do not use the full table anyway I've chosen
+ * to limit the array to 45x45 (45*16 = 720, which is the maximum PAL/NTSC
+ * width).
+ */
+#define SOLO_MOTION_SZ (45)
+struct solo_motion_thresholds {
+ __u16 thresholds[SOLO_MOTION_SZ][SOLO_MOTION_SZ];
+};
+
+#define SOLO_IOC_G_MOTION_THRESHOLDS _IOR('V', BASE_VIDIOC_PRIVATE+0, struct solo_motion_thresholds)
+#define SOLO_IOC_S_MOTION_THRESHOLDS _IOW('V', BASE_VIDIOC_PRIVATE+1, struct solo_motion_thresholds)
+
+enum SOLO_I2C_STATE {
+ IIC_STATE_IDLE,
+ IIC_STATE_START,
+ IIC_STATE_READ,
+ IIC_STATE_WRITE,
+ IIC_STATE_STOP
+};
+
+/* Defined in Table 4-16, Page 68-69 of the 6010 Datasheet */
+struct solo_p2m_desc {
+ u32 ctrl;
+ u32 cfg;
+ u32 dma_addr;
+ u32 ext_addr;
+};
+
+struct solo_p2m_dev {
+ struct mutex mutex;
+ struct completion completion;
+ int desc_count;
+ int desc_idx;
+ struct solo_p2m_desc *descs;
+ int error;
+};
+
+#define OSD_TEXT_MAX 44
+
+struct solo_vb2_buf {
+ struct vb2_buffer vb;
+ struct list_head list;
+};
+
+enum solo_enc_types {
+ SOLO_ENC_TYPE_STD,
+ SOLO_ENC_TYPE_EXT,
+};
+
+struct solo_enc_dev {
+ struct solo_dev *solo_dev;
+ /* V4L2 Items */
+ struct v4l2_ctrl_handler hdl;
+ struct video_device *vfd;
+ /* General accounting */
+ struct mutex lock;
+ spinlock_t motion_lock;
+ u8 ch;
+ u8 mode, gop, qp, interlaced, interval;
+ u8 bw_weight;
+ u16 motion_thresh;
+ struct solo_motion_thresholds motion_thresholds;
+ bool motion_global;
+ bool motion_enabled;
+ u16 width;
+ u16 height;
+
+ /* OSD buffers */
+ char osd_text[OSD_TEXT_MAX + 1];
+ u8 osd_buf[SOLO_EOSD_EXT_SIZE_MAX]
+ __aligned(4);
+
+ /* VOP stuff */
+ unsigned char vop[64];
+ int vop_len;
+ unsigned char jpeg_header[1024];
+ int jpeg_len;
+
+ u32 fmt;
+ enum solo_enc_types type;
+ u32 sequence;
+ struct vb2_queue vidq;
+ struct list_head vidq_active;
+ int desc_count;
+ int desc_nelts;
+ struct solo_p2m_desc *desc_items;
+ dma_addr_t desc_dma;
+ spinlock_t av_lock;
+};
+
+/* The SOLO6x10 PCI Device */
+struct solo_dev {
+ /* General stuff */
+ struct pci_dev *pdev;
+ int type;
+ unsigned int time_sync;
+ unsigned int usec_lsb;
+ unsigned int clock_mhz;
+ u8 __iomem *reg_base;
+ int nr_chans;
+ int nr_ext;
+ u32 irq_mask;
+ u32 motion_mask;
+ spinlock_t reg_io_lock;
+ struct v4l2_device v4l2_dev;
+
+ /* tw28xx accounting */
+ u8 tw2865, tw2864, tw2815;
+ u8 tw28_cnt;
+
+ /* i2c related items */
+ struct i2c_adapter i2c_adap[SOLO_I2C_ADAPTERS];
+ enum SOLO_I2C_STATE i2c_state;
+ struct mutex i2c_mutex;
+ int i2c_id;
+ wait_queue_head_t i2c_wait;
+ struct i2c_msg *i2c_msg;
+ unsigned int i2c_msg_num;
+ unsigned int i2c_msg_ptr;
+
+ /* P2M DMA Engine */
+ struct solo_p2m_dev p2m_dev[SOLO_NR_P2M];
+ atomic_t p2m_count;
+ int p2m_jiffies;
+ unsigned int p2m_timeouts;
+
+ /* V4L2 Display items */
+ struct video_device *vfd;
+ unsigned int erasing;
+ unsigned int frame_blank;
+ u8 cur_disp_ch;
+ wait_queue_head_t disp_thread_wait;
+ struct v4l2_ctrl_handler disp_hdl;
+
+ /* V4L2 Encoder items */
+ struct solo_enc_dev *v4l2_enc[SOLO_MAX_CHANNELS];
+ u16 enc_bw_remain;
+ /* IDX into hw mp4 encoder */
+ u8 enc_idx;
+
+ /* Current video settings */
+ u32 video_type;
+ u16 video_hsize, video_vsize;
+ u16 vout_hstart, vout_vstart;
+ u16 vin_hstart, vin_vstart;
+ u8 fps;
+
+ /* JPEG Qp setting */
+ spinlock_t jpeg_qp_lock;
+ u32 jpeg_qp[2];
+
+ /* Audio components */
+ struct snd_card *snd_card;
+ struct snd_pcm *snd_pcm;
+ atomic_t snd_users;
+ int g723_hw_idx;
+
+ /* sysfs stuffs */
+ struct device dev;
+ int sdram_size;
+ struct bin_attribute sdram_attr;
+ unsigned int sys_config;
+
+ /* Ring thread */
+ struct task_struct *ring_thread;
+ wait_queue_head_t ring_thread_wait;
+
+ /* VOP_HEADER handling */
+ void *vh_buf;
+ dma_addr_t vh_dma;
+ int vh_size;
+
+ /* Buffer handling */
+ struct vb2_queue vidq;
+ struct vb2_alloc_ctx *alloc_ctx;
+ u32 sequence;
+ struct task_struct *kthread;
+ struct mutex lock;
+ spinlock_t slock;
+ int old_write;
+ struct list_head vidq_active;
+};
+
+static inline u32 solo_reg_read(struct solo_dev *solo_dev, int reg)
+{
+ 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 solo_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);
+}
+
+static inline void solo_irq_on(struct solo_dev *dev, u32 mask)
+{
+ dev->irq_mask |= mask;
+ solo_reg_write(dev, SOLO_IRQ_MASK, dev->irq_mask);
+}
+
+static inline void solo_irq_off(struct solo_dev *dev, u32 mask)
+{
+ dev->irq_mask &= ~mask;
+ solo_reg_write(dev, SOLO_IRQ_MASK, dev->irq_mask);
+}
+
+/* Init/exit routines for subsystems */
+int solo_disp_init(struct solo_dev *solo_dev);
+void solo_disp_exit(struct solo_dev *solo_dev);
+
+int solo_gpio_init(struct solo_dev *solo_dev);
+void solo_gpio_exit(struct solo_dev *solo_dev);
+
+int solo_i2c_init(struct solo_dev *solo_dev);
+void solo_i2c_exit(struct solo_dev *solo_dev);
+
+int solo_p2m_init(struct solo_dev *solo_dev);
+void solo_p2m_exit(struct solo_dev *solo_dev);
+
+int solo_v4l2_init(struct solo_dev *solo_dev, unsigned nr);
+void solo_v4l2_exit(struct solo_dev *solo_dev);
+
+int solo_enc_init(struct solo_dev *solo_dev);
+void solo_enc_exit(struct solo_dev *solo_dev);
+
+int solo_enc_v4l2_init(struct solo_dev *solo_dev, unsigned nr);
+void solo_enc_v4l2_exit(struct solo_dev *solo_dev);
+
+int solo_g723_init(struct solo_dev *solo_dev);
+void solo_g723_exit(struct solo_dev *solo_dev);
+
+/* ISR's */
+int solo_i2c_isr(struct solo_dev *solo_dev);
+void solo_p2m_isr(struct solo_dev *solo_dev, int id);
+void solo_p2m_error_isr(struct solo_dev *solo_dev);
+void solo_enc_v4l2_isr(struct solo_dev *solo_dev);
+void solo_g723_isr(struct solo_dev *solo_dev);
+void solo_motion_isr(struct solo_dev *solo_dev);
+void solo_video_in_isr(struct solo_dev *solo_dev);
+
+/* i2c read/write */
+u8 solo_i2c_readbyte(struct solo_dev *solo_dev, int id, u8 addr, u8 off);
+void solo_i2c_writebyte(struct solo_dev *solo_dev, int id, u8 addr, u8 off,
+ u8 data);
+
+/* P2M DMA */
+int solo_p2m_dma_t(struct solo_dev *solo_dev, int wr,
+ dma_addr_t dma_addr, u32 ext_addr, u32 size,
+ int repeat, u32 ext_size);
+int solo_p2m_dma(struct solo_dev *solo_dev, int wr,
+ void *sys_addr, u32 ext_addr, u32 size,
+ int repeat, u32 ext_size);
+void solo_p2m_fill_desc(struct solo_p2m_desc *desc, int wr,
+ dma_addr_t dma_addr, u32 ext_addr, u32 size,
+ int repeat, u32 ext_size);
+int solo_p2m_dma_desc(struct solo_dev *solo_dev,
+ struct solo_p2m_desc *desc, dma_addr_t desc_dma,
+ int desc_cnt);
+
+/* Global s_std ioctl */
+int solo_set_video_type(struct solo_dev *solo_dev, bool is_50hz);
+void solo_update_mode(struct solo_enc_dev *solo_enc);
+
+/* Set the threshold for motion detection */
+int solo_set_motion_threshold(struct solo_dev *solo_dev, u8 ch, u16 val);
+int solo_set_motion_block(struct solo_dev *solo_dev, u8 ch,
+ const struct solo_motion_thresholds *thresholds);
+#define SOLO_DEF_MOT_THRESH 0x0300
+
+/* Write text on OSD */
+int solo_osd_print(struct solo_enc_dev *solo_enc);
+
+/* EEPROM commands */
+unsigned int solo_eeprom_ewen(struct solo_dev *solo_dev, int w_en);
+unsigned short solo_eeprom_read(struct solo_dev *solo_dev, int loc);
+int solo_eeprom_write(struct solo_dev *solo_dev, int loc,
+ unsigned short data);
+
+/* JPEG Qp functions */
+void solo_s_jpeg_qp(struct solo_dev *solo_dev, unsigned int ch,
+ unsigned int qp);
+int solo_g_jpeg_qp(struct solo_dev *solo_dev, unsigned int ch);
+
+#define CHK_FLAGS(v, flags) (((v) & (flags)) == (flags))
+
+#endif /* __SOLO6X10_H */