diff options
author | Mauro Carvalho Chehab <mchehab@redhat.com> | 2011-11-02 00:23:55 (GMT) |
---|---|---|
committer | Mauro Carvalho Chehab <mchehab@redhat.com> | 2011-11-03 09:59:03 (GMT) |
commit | 4860c73804c6e7ef8e69f98958489bb2bea6f6d2 (patch) | |
tree | 6d8dc4ca7794478274e011e5fae4fd1005cf6877 /drivers/staging/media | |
parent | d6ce55de3abcc4910fef1d79212a17480c154704 (diff) | |
download | linux-4860c73804c6e7ef8e69f98958489bb2bea6f6d2.tar.xz |
staging: Move media drivers to staging/media
In practice, it is being hard to distinguish when a patch
should go to staging tree or to the media tree. Better
to distinguish it, by putting the media drivers at a
separate staging directory. Newer staging drivers that include
anything with "dvb*.h", "v4l2*.h" or "videodev2.h" should
go to the drivers/staging/media tree.
Acked-by: Greg Kroah-Hartman <gregkh@suse.de>
Signed-off-by: Mauro Carvalho Chehab <mchehab@redhat.com>
Diffstat (limited to 'drivers/staging/media')
79 files changed, 38964 insertions, 0 deletions
diff --git a/drivers/staging/media/Kconfig b/drivers/staging/media/Kconfig new file mode 100644 index 0000000..7e5caa3 --- /dev/null +++ b/drivers/staging/media/Kconfig @@ -0,0 +1,37 @@ +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, one 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/cxd2099/Kconfig" + +source "drivers/staging/media/dt3155v4l/Kconfig" + +source "drivers/staging/media/easycap/Kconfig" + +source "drivers/staging/media/go7007/Kconfig" + +source "drivers/staging/media/solo6x10/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 0000000..c69124c --- /dev/null +++ b/drivers/staging/media/Makefile @@ -0,0 +1,7 @@ +obj-$(CONFIG_DVB_AS102) += as102/ +obj-$(CONFIG_DVB_CXD2099) += cxd2099/ +obj-$(CONFIG_EASYCAP) += easycap/ +obj-$(CONFIG_LIRC_STAGING) += lirc/ +obj-$(CONFIG_SOLO6X10) += solo6x10/ +obj-$(CONFIG_VIDEO_DT3155) += dt3155v4l/ +obj-$(CONFIG_VIDEO_GO7007) += go7007/ diff --git a/drivers/staging/media/cxd2099/Kconfig b/drivers/staging/media/cxd2099/Kconfig new file mode 100644 index 0000000..b48aefd --- /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 0000000..64cfc77 --- /dev/null +++ b/drivers/staging/media/cxd2099/Makefile @@ -0,0 +1,5 @@ +obj-$(CONFIG_DVB_CXD2099) += cxd2099.o + +ccflags-y += -Idrivers/media/dvb/dvb-core/ +ccflags-y += -Idrivers/media/dvb/frontends/ +ccflags-y += -Idrivers/media/common/tuners/ diff --git a/drivers/staging/media/cxd2099/TODO b/drivers/staging/media/cxd2099/TODO new file mode 100644 index 0000000..375bb6f --- /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 0000000..1c04185 --- /dev/null +++ b/drivers/staging/media/cxd2099/cxd2099.c @@ -0,0 +1,716 @@ +/* + * 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/init.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) { + printk(KERN_ERR "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) { + printk(KERN_ERR "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 = ®, .len = 1}, + {.addr = adr, .flags = I2C_M_RD, + .buf = val, .len = 1} }; + + if (i2c_transfer(adapter, msgs, 2) != 2) { + printk(KERN_ERR "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 = ®, .len = 1}, + {.addr = adr, .flags = I2C_M_RD, + .buf = data, .len = n} }; + + if (i2c_transfer(adapter, msgs, 2) != 2) { + printk(KERN_ERR "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; + printk(KERN_INFO "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 + status = write_reg(ci, 0x09, 0x4D); /* Input Mode C, BYPass Serial, TIVAL = low, MSB */ + if (status < 0) + break; +#endif + status = write_reg(ci, 0x0A, 0xA7); /* TOSTRT = 8, Mode B (gated clock), falling Edge, Serial, POL=HIGH, MSB */ + 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); + printk(KERN_INFO "%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; + + printk(KERN_INFO "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; + printk(KERN_INFO "DR\n"); + } + if (istat&0x20) + printk(KERN_INFO "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); + printk(KERN_INFO "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); + + printk(KERN_INFO "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 = 0; + u8 val; + + if (i2c_read_reg(i2c, cfg->adr, 0, &val) < 0) { + printk(KERN_INFO "No CXD2099 detected at %02x\n", cfg->adr); + return 0; + } + + ci = kmalloc(sizeof(struct cxd), GFP_KERNEL); + if (!ci) + return 0; + memset(ci, 0, sizeof(*ci)); + + mutex_init(&ci->lock); + memcpy(&ci->cfg, cfg, sizeof(struct cxd2099_cfg)); + ci->i2c = i2c; + ci->lastaddress = 0xff; + ci->clk_reg_b = 0x4a; + ci->clk_reg_f = 0x1b; + + memcpy(&ci->en, &en_templ, sizeof(en_templ)); + ci->en.data = ci; + init(ci); + printk(KERN_INFO "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 0000000..19c588a --- /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) +{ + printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__); + return NULL; +} +#endif + +#endif diff --git a/drivers/staging/media/dt3155v4l/Kconfig b/drivers/staging/media/dt3155v4l/Kconfig new file mode 100644 index 0000000..226a1ca --- /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 0000000..ce7a3ec --- /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 0000000..04e93c4 --- /dev/null +++ b/drivers/staging/media/dt3155v4l/dt3155v4l.c @@ -0,0 +1,993 @@ +/*************************************************************************** + * 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/videobuf2-dma-contig.h> + +#include "dt3155v4l.h" + +#define DT3155_VENDOR_ID 0x8086 +#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, unsigned int *num_buffers, + unsigned int *num_planes, unsigned long 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 int +dt3155_start_streaming(struct vb2_queue *q) +{ + return 0; +} + +static int +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 */ + return 0; +} + +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 + */ + +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, + .start_streaming = dt3155_start_streaming, + .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) { + do_gettimeofday(&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 (!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->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; + vb2_queue_init(pd->q); /* cannot fail */ + 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++; + return 0; /* success */ +err_request_irq: + kfree(pd->q); + pd->q = NULL; +err_alloc_queue: + return ret; +} + +static int +dt3155_release(struct file *filp) +{ + struct dt3155_priv *pd = video_drvdata(filp); + + 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; + } + 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); + + return vb2_read(pd->q, user, size, loff, filp->f_flags & O_NONBLOCK); +} + +static unsigned int +dt3155_poll(struct file *filp, struct poll_table_struct *polltbl) +{ + struct dt3155_priv *pd = video_drvdata(filp); + + return vb2_poll(pd->q, filp, polltbl); +} + +static int +dt3155_mmap(struct file *filp, struct vm_area_struct *vma) +{ + struct dt3155_priv *pd = video_drvdata(filp); + + return vb2_mmap(pd->q, vma); +} + +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 __devinit +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 chanel 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, + .current_norm = 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 __devinit +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 __devexit +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 __devinit +dt3155_probe(struct pci_dev *pdev, const struct pci_device_id *id) +{ + int err; + struct dt3155_priv *pd; + + err = dma_set_mask(&pdev->dev, DMA_BIT_MASK(32)); + if (err) + return -ENODEV; + err = dma_set_coherent_mask(&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) + 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 __devexit +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 DEFINE_PCI_DEVICE_TABLE(pci_ids) = { + { PCI_DEVICE(DT3155_VENDOR_ID, 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 = __devexit_p(dt3155_remove), +}; + +static int __init +dt3155_init_module(void) +{ + return pci_register_driver(&pci_driver); +} + +static void __exit +dt3155_exit_module(void) +{ + pci_unregister_driver(&pci_driver); +} + +module_init(dt3155_init_module); +module_exit(dt3155_exit_module); + +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 0000000..2e4f89d --- /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/easycap/Kconfig b/drivers/staging/media/easycap/Kconfig new file mode 100644 index 0000000..a425a6f --- /dev/null +++ b/drivers/staging/media/easycap/Kconfig @@ -0,0 +1,30 @@ +config EASYCAP + tristate "EasyCAP USB ID 05e1:0408 support" + depends on USB && VIDEO_DEV && SND + select SND_PCM + + ---help--- + This is an integrated audio/video driver for EasyCAP cards with + USB ID 05e1:0408. It supports two hardware variants: + + * EasyCAP USB 2.0 Video Adapter with Audio, Model DC60, + having input cables labelled CVBS, S-VIDEO, AUDIO(L), AUDIO(R) + + * EasyCAP002 4-Channel USB 2.0 DVR, having input cables labelled + 1, 2, 3, 4 and an unlabelled input cable for a microphone. + + To compile this driver as a module, choose M here: the + module will be called easycap + +config EASYCAP_DEBUG + bool "Enable EasyCAP driver debugging" + depends on EASYCAP + + ---help--- + This option enables debug printouts + + To enable debug, pass the debug level to the debug module + parameter: + + modprobe easycap debug=[0..9] + diff --git a/drivers/staging/media/easycap/Makefile b/drivers/staging/media/easycap/Makefile new file mode 100644 index 0000000..a34e75f --- /dev/null +++ b/drivers/staging/media/easycap/Makefile @@ -0,0 +1,10 @@ +easycap-objs := easycap_main.o +easycap-objs += easycap_low.o +easycap-objs += easycap_ioctl.o +easycap-objs += easycap_settings.o +easycap-objs += easycap_testcard.o +easycap-objs += easycap_sound.o +obj-$(CONFIG_EASYCAP) += easycap.o + +ccflags-y := -Wall + diff --git a/drivers/staging/media/easycap/README b/drivers/staging/media/easycap/README new file mode 100644 index 0000000..796b032 --- /dev/null +++ b/drivers/staging/media/easycap/README @@ -0,0 +1,141 @@ + + *********************************************************** + * EasyCAP USB 2.0 Video Adapter with Audio, Model DC60 * + * and * + * EasyCAP002 4-Channel USB 2.0 DVR * + *********************************************************** + Mike Thomas <rmthomas@sciolus.org> + + + +SUPPORTED HARDWARE +------------------ + +This driver is intended for use with hardware having USB ID 05e1:0408. +Two kinds of EasyCAP have this USB ID, namely: + + * EasyCAP USB 2.0 Video Adapter with Audio, Model DC60, + having input cables labelled CVBS, S-VIDEO, AUDIO(L), AUDIO(R) + + * EasyCAP002 4-Channel USB 2.0 DVR, having input cables labelled + 1, 2, 3, 4 and an unlabelled input cable for a microphone. + + +BUILD OPTIONS AND DEPENDENCIES +------------------------------ + +Unless EASYCAP_DEBUG is defined during compilation it will not be possible +to select a debug level at the time of module installation. + + +KNOWN RUNTIME ISSUES +-------------------- + +(1) Intentionally, this driver will not stream material which is unambiguously +identified by the hardware as copy-protected. Normal video output will be +present for about a minute but will then freeze when this situation arises. + +(2) The controls for luminance, contrast, saturation, hue and volume may not +always work properly. + +(3) Reduced-resolution S-Video seems to suffer from moire artefacts. + + +INPUT NUMBERING +--------------- + +For the EasyCAP with S-VIDEO input cable the driver regards a request for +inputs numbered 0 or 1 as referring to CVBS and a request for input +numbered 5 as referring to S-VIDEO. + +For the EasyCAP with four CVBS inputs the driver expects to be asked for +any one of inputs numbered 1,2,3,4. If input 0 is asked for, it is +interpreted as input 1. + + +MODULE PARAMETERS +----------------- + +Three module parameters are defined: + +debug the easycap module is configured at diagnostic level n (0 to 9) +gain audio gain level n (0 to 31, default is 16) +bars whether to display testcard bars when incoming video signal is lost + 0 => no, 1 => yes (default) + + +SUPPORTED TV STANDARDS AND RESOLUTIONS +-------------------------------------- + +The following TV standards are natively supported by the hardware and are +usable as (for example) the "norm=" parameter in the mplayer command: + + PAL_BGHIN, NTSC_N_443, + PAL_Nc, NTSC_N, + SECAM, NTSC_M, NTSC_M_JP, + PAL_60, NTSC_443, + PAL_M. + +In addition, the driver offers "custom" pseudo-standards with a framerate +which is 20% of the usual framerate. These pseudo-standards are named: + + PAL_BGHIN_SLOW, NTSC_N_443_SLOW, + PAL_Nc_SLOW, NTSC_N_SLOW, + SECAM_SLOW, NTSC_M_SLOW, NTSC_M_JP_SLOW, + PAL_60_SLOW, NTSC_443_SLOW, + PAL_M_SLOW. + + +The available picture sizes are: + + at 25 frames per second: 720x576, 704x576, 640x480, 360x288, 320x240; + at 30 frames per second: 720x480, 640x480, 360x240, 320x240. + + +WHAT'S TESTED AND WHAT'S NOT +---------------------------- + +This driver is known to work with mplayer, mencoder, tvtime, zoneminder, +xawtv, gstreamer and sufficiently recent versions of vlc. An interface +to ffmpeg is implemented, but serious audio-video synchronization problems +remain. + +The driver is designed to support all the TV standards accepted by the +hardware, but as yet it has actually been tested on only a few of these. + +I have been unable to test and calibrate the S-video input myself because I +do not possess any equipment with S-video output. + + +UDEV RULES +---------- + +In order that the special files /dev/easycap0 and /dev/easysnd1 are created +with conveniently relaxed permissions when the EasyCAP is plugged in, a file +is preferably to be provided in directory /etc/udev/rules.d with content: + +ACTION!="add|change", GOTO="easycap_rules_end" +ATTRS{idVendor}=="05e1", ATTRS{idProduct}=="0408", \ + MODE="0666", OWNER="root", GROUP="root" +LABEL="easycap_rules_end" + + +MODPROBE CONFIGURATION +---------------------- + +The easycap module is in competition with the module snd-usb-audio for the +EasyCAP's audio channel, and its installation can be aided by providing a +file in directory /etc/modprobe.d with content: + +options easycap gain=16 bars=1 +install easycap /sbin/rmmod snd-usb-audio; /sbin/modprobe --ignore-install easycap + + +ACKNOWLEGEMENTS AND REFERENCES +------------------------------ +This driver makes use of information contained in the Syntek Semicon DC-1125 +Driver, presently maintained at http://sourceforge.net/projects/syntekdriver/ +by Nicolas Vivien. Particularly useful has been a patch to the latter driver +provided by Ivor Hewitt in January 2009. The NTSC implementation is taken +from the work of Ben Trask. + diff --git a/drivers/staging/media/easycap/easycap.h b/drivers/staging/media/easycap/easycap.h new file mode 100644 index 0000000..7b256a9 --- /dev/null +++ b/drivers/staging/media/easycap/easycap.h @@ -0,0 +1,594 @@ +/***************************************************************************** +* * +* easycap.h * +* * +*****************************************************************************/ +/* + * + * Copyright (C) 2010 R.M. Thomas <rmthomas@sciolus.org> + * + * + * This 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. + * + * The software 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 software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * +*/ +/*****************************************************************************/ +/*---------------------------------------------------------------------------*/ +/* + * THE FOLLOWING PARAMETERS ARE UNDEFINED: + * + * EASYCAP_DEBUG + * + * IF REQUIRED THEY MUST BE EXTERNALLY DEFINED, FOR EXAMPLE AS COMPILER + * OPTIONS. + */ +/*---------------------------------------------------------------------------*/ + +#ifndef __EASYCAP_H__ +#define __EASYCAP_H__ + +/*---------------------------------------------------------------------------*/ +/* + * THESE ARE NORMALLY DEFINED + */ +/*---------------------------------------------------------------------------*/ +#define PATIENCE 500 +#define PERSEVERE +/*---------------------------------------------------------------------------*/ +/* + * THESE ARE FOR MAINTENANCE ONLY - NORMALLY UNDEFINED: + */ +/*---------------------------------------------------------------------------*/ +#undef EASYCAP_TESTCARD +/*---------------------------------------------------------------------------*/ +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/kref.h> +#include <linux/usb.h> +#include <linux/uaccess.h> + +#include <linux/i2c.h> +#include <linux/workqueue.h> +#include <linux/poll.h> +#include <linux/mm.h> +#include <linux/fs.h> +#include <linux/delay.h> +#include <linux/types.h> + +#include <linux/vmalloc.h> +#include <linux/sound.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/info.h> +#include <sound/initval.h> +#include <sound/control.h> +#include <media/v4l2-dev.h> +#include <media/v4l2-device.h> +#include <linux/videodev2.h> +#include <linux/soundcard.h> + +/*---------------------------------------------------------------------------*/ +/* VENDOR, PRODUCT: Syntek Semiconductor Co., Ltd + * + * EITHER EasyCAP USB 2.0 Video Adapter with Audio, Model No. DC60 + * with input cabling: AUDIO(L), AUDIO(R), CVBS, S-VIDEO. + * + * OR EasyCAP 4CHANNEL USB 2.0 DVR, Model No. EasyCAP002 + * with input cabling: MICROPHONE, CVBS1, CVBS2, CVBS3, CVBS4. + */ +/*---------------------------------------------------------------------------*/ +#define USB_EASYCAP_VENDOR_ID 0x05e1 +#define USB_EASYCAP_PRODUCT_ID 0x0408 + +#define EASYCAP_DRIVER_VERSION "0.9.01" +#define EASYCAP_DRIVER_DESCRIPTION "easycapdc60" + +#define USB_SKEL_MINOR_BASE 192 +#define DONGLE_MANY 8 +#define INPUT_MANY 6 +/*---------------------------------------------------------------------------*/ +/* + * DEFAULT LUMINANCE, CONTRAST, SATURATION AND HUE + */ +/*---------------------------------------------------------------------------*/ +#define SAA_0A_DEFAULT 0x7F +#define SAA_0B_DEFAULT 0x3F +#define SAA_0C_DEFAULT 0x2F +#define SAA_0D_DEFAULT 0x00 +/*---------------------------------------------------------------------------*/ +/* + * VIDEO STREAMING PARAMETERS: + * USB 2.0 PROVIDES FOR HIGH-BANDWIDTH ENDPOINTS WITH AN UPPER LIMIT + * OF 3072 BYTES PER MICROFRAME for wMaxPacketSize. + */ +/*---------------------------------------------------------------------------*/ +#define VIDEO_ISOC_BUFFER_MANY 16 +#define VIDEO_ISOC_ORDER 3 +#define VIDEO_ISOC_FRAMESPERDESC ((unsigned int) 1 << VIDEO_ISOC_ORDER) +#define USB_2_0_MAXPACKETSIZE 3072 +#if (USB_2_0_MAXPACKETSIZE > PAGE_SIZE) +#error video_isoc_buffer[.] will not be big enough +#endif +#define VIDEO_JUNK_TOLERATE VIDEO_ISOC_BUFFER_MANY +#define VIDEO_LOST_TOLERATE 50 +/*---------------------------------------------------------------------------*/ +/* + * VIDEO BUFFERS + */ +/*---------------------------------------------------------------------------*/ +#define FIELD_BUFFER_SIZE (203 * PAGE_SIZE) +#define FRAME_BUFFER_SIZE (405 * PAGE_SIZE) +#define FIELD_BUFFER_MANY 4 +#define FRAME_BUFFER_MANY 6 +/*---------------------------------------------------------------------------*/ +/* + * AUDIO STREAMING PARAMETERS + */ +/*---------------------------------------------------------------------------*/ +#define AUDIO_ISOC_BUFFER_MANY 16 +#define AUDIO_ISOC_ORDER 1 +#define AUDIO_ISOC_FRAMESPERDESC 32 +#define AUDIO_ISOC_BUFFER_SIZE (PAGE_SIZE << AUDIO_ISOC_ORDER) +/*---------------------------------------------------------------------------*/ +/* + * AUDIO BUFFERS + */ +/*---------------------------------------------------------------------------*/ +#define AUDIO_FRAGMENT_MANY 32 +#define PAGES_PER_AUDIO_FRAGMENT 4 +/*---------------------------------------------------------------------------*/ +/* + * IT IS ESSENTIAL THAT EVEN-NUMBERED STANDARDS ARE 25 FRAMES PER SECOND, + * ODD-NUMBERED STANDARDS ARE 30 FRAMES PER SECOND. + * THE NUMBERING OF STANDARDS MUST NOT BE CHANGED WITHOUT DUE CARE. NOT + * ONLY MUST THE PARAMETER + * STANDARD_MANY + * BE CHANGED TO CORRESPOND TO THE NEW NUMBER OF STANDARDS, BUT ALSO THE + * NUMBERING MUST REMAIN AN UNBROKEN ASCENDING SEQUENCE: DUMMY STANDARDS + * MAY NEED TO BE ADDED. APPROPRIATE CHANGES WILL ALWAYS BE REQUIRED IN + * ROUTINE fillin_formats() AND POSSIBLY ELSEWHERE. BEWARE. + */ +/*---------------------------------------------------------------------------*/ +#define PAL_BGHIN 0 +#define PAL_Nc 2 +#define SECAM 4 +#define NTSC_N 6 +#define NTSC_N_443 8 +#define NTSC_M 1 +#define NTSC_443 3 +#define NTSC_M_JP 5 +#define PAL_60 7 +#define PAL_M 9 +#define PAL_BGHIN_SLOW 10 +#define PAL_Nc_SLOW 12 +#define SECAM_SLOW 14 +#define NTSC_N_SLOW 16 +#define NTSC_N_443_SLOW 18 +#define NTSC_M_SLOW 11 +#define NTSC_443_SLOW 13 +#define NTSC_M_JP_SLOW 15 +#define PAL_60_SLOW 17 +#define PAL_M_SLOW 19 +#define STANDARD_MANY 20 +/*---------------------------------------------------------------------------*/ +/* + * ENUMS + */ +/*---------------------------------------------------------------------------*/ +enum { + AT_720x576, + AT_704x576, + AT_640x480, + AT_720x480, + AT_360x288, + AT_320x240, + AT_360x240, + RESOLUTION_MANY +}; +enum { + FMT_UYVY, + FMT_YUY2, + FMT_RGB24, + FMT_RGB32, + FMT_BGR24, + FMT_BGR32, + PIXELFORMAT_MANY +}; +enum { + FIELD_NONE, + FIELD_INTERLACED, + INTERLACE_MANY +}; +#define SETTINGS_MANY (STANDARD_MANY * \ + RESOLUTION_MANY * \ + 2 * \ + PIXELFORMAT_MANY * \ + INTERLACE_MANY) +/*---------------------------------------------------------------------------*/ +/* + * STRUCTURE DEFINITIONS + */ +/*---------------------------------------------------------------------------*/ +struct easycap_dongle { + struct easycap *peasycap; + struct mutex mutex_video; + struct mutex mutex_audio; +}; +/*---------------------------------------------------------------------------*/ +struct data_buffer { + struct list_head list_head; + void *pgo; + void *pto; + u16 kount; + u16 input; +}; +/*---------------------------------------------------------------------------*/ +struct data_urb { + struct list_head list_head; + struct urb *purb; + int isbuf; + int length; +}; +/*---------------------------------------------------------------------------*/ +struct easycap_standard { + u16 mask; +struct v4l2_standard v4l2_standard; +}; +struct easycap_format { + u16 mask; + char name[128]; +struct v4l2_format v4l2_format; +}; +struct inputset { + int input; + int input_ok; + int standard_offset; + int standard_offset_ok; + int format_offset; + int format_offset_ok; + int brightness; + int brightness_ok; + int contrast; + int contrast_ok; + int saturation; + int saturation_ok; + int hue; + int hue_ok; +}; +/*---------------------------------------------------------------------------*/ +/* + * easycap.ilk == 0 => CVBS+S-VIDEO HARDWARE, AUDIO wMaxPacketSize=256 + * easycap.ilk == 2 => CVBS+S-VIDEO HARDWARE, AUDIO wMaxPacketSize=9 + * easycap.ilk == 3 => FOUR-CVBS HARDWARE, AUDIO wMaxPacketSize=9 + */ +/*---------------------------------------------------------------------------*/ +struct easycap { + int isdongle; + int minor; + + struct video_device video_device; + struct v4l2_device v4l2_device; + + int status; + unsigned int audio_pages_per_fragment; + unsigned int audio_bytes_per_fragment; + unsigned int audio_buffer_page_many; + +#define UPSAMPLE +#ifdef UPSAMPLE + s16 oldaudio; +#endif /*UPSAMPLE*/ + + int ilk; + bool microphone; + + struct usb_device *pusb_device; + struct usb_interface *pusb_interface; + + struct kref kref; + + int queued[FRAME_BUFFER_MANY]; + int done[FRAME_BUFFER_MANY]; + + wait_queue_head_t wq_video; + wait_queue_head_t wq_audio; + wait_queue_head_t wq_trigger; + + int input; + int polled; + int standard_offset; + int format_offset; + struct inputset inputset[INPUT_MANY]; + + bool ntsc; + int fps; + int usec; + int tolerate; + int skip; + int skipped; + int lost[INPUT_MANY]; + int merit[180]; + + long long int dnbydt; + + int video_interface; + int video_altsetting_on; + int video_altsetting_off; + int video_endpointnumber; + int video_isoc_maxframesize; + int video_isoc_buffer_size; + int video_isoc_framesperdesc; + + int video_isoc_streaming; + int video_isoc_sequence; + int video_idle; + int video_eof; + int video_junk; + + struct data_buffer video_isoc_buffer[VIDEO_ISOC_BUFFER_MANY]; + struct data_buffer field_buffer[FIELD_BUFFER_MANY] + [(FIELD_BUFFER_SIZE/PAGE_SIZE)]; + struct data_buffer frame_buffer[FRAME_BUFFER_MANY] + [(FRAME_BUFFER_SIZE/PAGE_SIZE)]; + + struct list_head urb_video_head; + struct list_head *purb_video_head; + + u8 cache[8]; + u8 *pcache; + int video_mt; + int audio_mt; + long long audio_bytes; + u32 isequence; + + int vma_many; +/*---------------------------------------------------------------------------*/ +/* + * BUFFER INDICATORS + */ +/*---------------------------------------------------------------------------*/ + int field_fill; /* Field buffer being filled by easycap_complete(). */ + /* Bumped only by easycap_complete(). */ + int field_page; /* Page of field buffer page being filled by */ + /* easycap_complete(). */ + int field_read; /* Field buffer to be read by field2frame(). */ + /* Bumped only by easycap_complete(). */ + int frame_fill; /* Frame buffer being filled by field2frame(). */ + /* Bumped only by easycap_dqbuf() when */ + /* field2frame() has created a complete frame. */ + int frame_read; /* Frame buffer offered to user by DQBUF. */ + /* Set only by easycap_dqbuf() to trail frame_fill.*/ + int frame_lock; /* Flag set to 1 by DQBUF and cleared by QBUF */ +/*---------------------------------------------------------------------------*/ +/* + * IMAGE PROPERTIES + */ +/*---------------------------------------------------------------------------*/ + u32 pixelformat; + int width; + int height; + int bytesperpixel; + bool byteswaporder; + bool decimatepixel; + bool offerfields; + int frame_buffer_used; + int frame_buffer_many; + int videofieldamount; + + int brightness; + int contrast; + int saturation; + int hue; + + int allocation_video_urb; + int allocation_video_page; + int allocation_video_struct; + int registered_video; +/*---------------------------------------------------------------------------*/ +/* + * ALSA + */ +/*---------------------------------------------------------------------------*/ + struct snd_pcm_hardware alsa_hardware; + struct snd_card *psnd_card; + struct snd_pcm *psnd_pcm; + struct snd_pcm_substream *psubstream; + int dma_fill; + int dma_next; + int dma_read; +/*---------------------------------------------------------------------------*/ +/* + * SOUND PROPERTIES + */ +/*---------------------------------------------------------------------------*/ + int audio_interface; + int audio_altsetting_on; + int audio_altsetting_off; + int audio_endpointnumber; + int audio_isoc_maxframesize; + int audio_isoc_buffer_size; + int audio_isoc_framesperdesc; + + int audio_isoc_streaming; + int audio_idle; + int audio_eof; + int volume; + int mute; + s8 gain; + + struct data_buffer audio_isoc_buffer[AUDIO_ISOC_BUFFER_MANY]; + + struct list_head urb_audio_head; + struct list_head *purb_audio_head; +/*---------------------------------------------------------------------------*/ +/* + * BUFFER INDICATORS + */ +/*---------------------------------------------------------------------------*/ + int audio_fill; /* Audio buffer being filled by easycap_complete(). */ + /* Bumped only by easycap_complete(). */ + int audio_read; /* Audio buffer page being read by easycap_read(). */ + /* Set by easycap_read() to trail audio_fill by */ + /* one fragment. */ +/*---------------------------------------------------------------------------*/ +/* + * SOUND PROPERTIES + */ +/*---------------------------------------------------------------------------*/ + + int audio_buffer_many; + + int allocation_audio_urb; + int allocation_audio_page; + int allocation_audio_struct; + int registered_audio; + + long long int audio_sample; + long long int audio_niveau; + long long int audio_square; + + struct data_buffer audio_buffer[]; +}; +/*---------------------------------------------------------------------------*/ +/* + * VIDEO FUNCTION PROTOTYPES + */ +/*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^*/ +long easycap_unlocked_ioctl(struct file *, unsigned int, unsigned long); +int easycap_dqbuf(struct easycap *, int); +int submit_video_urbs(struct easycap *); +int kill_video_urbs(struct easycap *); +int field2frame(struct easycap *); +int redaub(struct easycap *, void *, void *, + int, int, u8, u8, bool); +void easycap_testcard(struct easycap *, int); +int fillin_formats(void); +int newinput(struct easycap *, int); +int adjust_standard(struct easycap *, v4l2_std_id); +int adjust_format(struct easycap *, u32, u32, u32, + int, bool); +int adjust_brightness(struct easycap *, int); +int adjust_contrast(struct easycap *, int); +int adjust_saturation(struct easycap *, int); +int adjust_hue(struct easycap *, int); +int adjust_volume(struct easycap *, int); +/*---------------------------------------------------------------------------*/ +/* + * AUDIO FUNCTION PROTOTYPES + */ +/*---------------------------------------------------------------------------*/ +int easycap_alsa_probe(struct easycap *); +void easycap_alsa_complete(struct urb *); + +int easycap_sound_setup(struct easycap *); +int submit_audio_urbs(struct easycap *); +int kill_audio_urbs(struct easycap *); +void easyoss_testtone(struct easycap *, int); +int audio_setup(struct easycap *); +/*---------------------------------------------------------------------------*/ +/* + * LOW-LEVEL FUNCTION PROTOTYPES + */ +/*---------------------------------------------------------------------------*/ +int audio_gainget(struct usb_device *); +int audio_gainset(struct usb_device *, s8); + +int set_interface(struct usb_device *, u16); +int wakeup_device(struct usb_device *); +int confirm_resolution(struct usb_device *); +int confirm_stream(struct usb_device *); + +int setup_stk(struct usb_device *, bool); +int setup_saa(struct usb_device *, bool); +int setup_vt(struct usb_device *); +int check_stk(struct usb_device *, bool); +int check_saa(struct usb_device *, bool); +int ready_saa(struct usb_device *); +int merit_saa(struct usb_device *); +int check_vt(struct usb_device *); +int select_input(struct usb_device *, int, int); +int set_resolution(struct usb_device *, + u16, u16, u16, u16); + +int read_saa(struct usb_device *, u16); +int read_stk(struct usb_device *, u32); +int write_saa(struct usb_device *, u16, u16); +int write_000(struct usb_device *, u16, u16); +int start_100(struct usb_device *); +int stop_100(struct usb_device *); +int write_300(struct usb_device *); +int read_vt(struct usb_device *, u16); +int write_vt(struct usb_device *, u16, u16); +int isdongle(struct easycap *); +/*---------------------------------------------------------------------------*/ + + +/*---------------------------------------------------------------------------*/ +/* + * MACROS SAM(...) AND JOM(...) ALLOW DIAGNOSTIC OUTPUT TO BE TAGGED WITH + * THE IDENTITY OF THE DONGLE TO WHICH IT APPLIES, BUT IF INVOKED WHEN THE + * POINTER peasycap IS INVALID AN Oops IS LIKELY, AND ITS CAUSE MAY NOT BE + * IMMEDIATELY OBVIOUS FROM A CASUAL READING OF THE SOURCE CODE. BEWARE. +*/ +/*---------------------------------------------------------------------------*/ +const char *strerror(int err); + +#define SAY(format, args...) do { \ + printk(KERN_DEBUG "easycap:: %s: " \ + format, __func__, ##args); \ +} while (0) +#define SAM(format, args...) do { \ + printk(KERN_DEBUG "easycap::%i%s: " \ + format, peasycap->isdongle, __func__, ##args);\ +} while (0) + +#ifdef CONFIG_EASYCAP_DEBUG +extern int easycap_debug; +#define JOT(n, format, args...) do { \ + if (n <= easycap_debug) { \ + printk(KERN_DEBUG "easycap:: %s: " \ + format, __func__, ##args);\ + } \ +} while (0) +#define JOM(n, format, args...) do { \ + if (n <= easycap_debug) { \ + printk(KERN_DEBUG "easycap::%i%s: " \ + format, peasycap->isdongle, __func__, ##args);\ + } \ +} while (0) + +#else +#define JOT(n, format, args...) do {} while (0) +#define JOM(n, format, args...) do {} while (0) +#endif /* CONFIG_EASYCAP_DEBUG */ + +/*---------------------------------------------------------------------------*/ + +/*---------------------------------------------------------------------------*/ +/* globals + */ +/*---------------------------------------------------------------------------*/ + +extern bool easycap_readback; +extern const struct easycap_standard easycap_standard[]; +extern struct easycap_format easycap_format[]; +extern struct v4l2_queryctrl easycap_control[]; +extern struct usb_driver easycap_usb_driver; +extern struct easycap_dongle easycapdc60_dongle[]; + +#endif /* !__EASYCAP_H__ */ diff --git a/drivers/staging/media/easycap/easycap_ioctl.c b/drivers/staging/media/easycap/easycap_ioctl.c new file mode 100644 index 0000000..c99addf --- /dev/null +++ b/drivers/staging/media/easycap/easycap_ioctl.c @@ -0,0 +1,2450 @@ +/****************************************************************************** +* * +* easycap_ioctl.c * +* * +******************************************************************************/ +/* + * + * Copyright (C) 2010 R.M. Thomas <rmthomas@sciolus.org> + * + * + * This 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. + * + * The software 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 software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * +*/ +/*****************************************************************************/ + +#include <linux/version.h> +#include "easycap.h" + +/*--------------------------------------------------------------------------*/ +/* + * UNLESS THERE IS A PREMATURE ERROR RETURN THIS ROUTINE UPDATES THE + * FOLLOWING: + * peasycap->standard_offset + * peasycap->inputset[peasycap->input].standard_offset + * peasycap->fps + * peasycap->usec + * peasycap->tolerate + * peasycap->skip + */ +/*---------------------------------------------------------------------------*/ +int adjust_standard(struct easycap *peasycap, v4l2_std_id std_id) +{ + struct easycap_standard const *peasycap_standard; + u16 reg, set; + int ir, rc, need, k; + unsigned int itwas, isnow; + bool resubmit; + + if (!peasycap) { + SAY("ERROR: peasycap is NULL\n"); + return -EFAULT; + } + if (!peasycap->pusb_device) { + SAM("ERROR: peasycap->pusb_device is NULL\n"); + return -EFAULT; + } + peasycap_standard = &easycap_standard[0]; + while (0xFFFF != peasycap_standard->mask) { + if (std_id == peasycap_standard->v4l2_standard.id) + break; + peasycap_standard++; + } + if (0xFFFF == peasycap_standard->mask) { + peasycap_standard = &easycap_standard[0]; + while (0xFFFF != peasycap_standard->mask) { + if (std_id & peasycap_standard->v4l2_standard.id) + break; + peasycap_standard++; + } + } + if (0xFFFF == peasycap_standard->mask) { + SAM("ERROR: 0x%08X=std_id: standard not found\n", + (unsigned int)std_id); + return -EINVAL; + } + SAM("selected standard: %s\n", + &(peasycap_standard->v4l2_standard.name[0])); + if (peasycap->standard_offset == peasycap_standard - easycap_standard) { + SAM("requested standard already in effect\n"); + return 0; + } + peasycap->standard_offset = peasycap_standard - easycap_standard; + for (k = 0; k < INPUT_MANY; k++) { + if (!peasycap->inputset[k].standard_offset_ok) { + peasycap->inputset[k].standard_offset = + peasycap->standard_offset; + } + } + if ((0 <= peasycap->input) && (INPUT_MANY > peasycap->input)) { + peasycap->inputset[peasycap->input].standard_offset = + peasycap->standard_offset; + peasycap->inputset[peasycap->input].standard_offset_ok = 1; + } else + JOM(8, "%i=peasycap->input\n", peasycap->input); + + peasycap->fps = peasycap_standard->v4l2_standard.frameperiod.denominator / + peasycap_standard->v4l2_standard.frameperiod.numerator; + switch (peasycap->fps) { + case 6: + case 30: { + peasycap->ntsc = true; + break; + } + case 5: + case 25: { + peasycap->ntsc = false; + break; + } + default: { + SAM("MISTAKE: %i=frames-per-second\n", peasycap->fps); + return -ENOENT; + } + } + JOM(8, "%i frames-per-second\n", peasycap->fps); + if (0x8000 & peasycap_standard->mask) { + peasycap->skip = 5; + peasycap->usec = 1000000 / (2 * (5 * peasycap->fps)); + peasycap->tolerate = 1000 * (25 / (5 * peasycap->fps)); + } else { + peasycap->skip = 0; + peasycap->usec = 1000000 / (2 * peasycap->fps); + peasycap->tolerate = 1000 * (25 / peasycap->fps); + } + if (peasycap->video_isoc_streaming) { + resubmit = true; + kill_video_urbs(peasycap); + } else + resubmit = false; +/*--------------------------------------------------------------------------*/ +/* + * SAA7113H DATASHEET PAGE 44, TABLE 42 + */ +/*--------------------------------------------------------------------------*/ + need = 0; + itwas = 0; + reg = 0x00; + set = 0x00; + switch (peasycap_standard->mask & 0x000F) { + case NTSC_M_JP: { + reg = 0x0A; + set = 0x95; + ir = read_saa(peasycap->pusb_device, reg); + if (0 > ir) + SAM("ERROR: cannot read SAA register 0x%02X\n", reg); + else + itwas = (unsigned int)ir; + rc = write_saa(peasycap->pusb_device, reg, set); + if (rc) + SAM("ERROR: failed to set SAA register " + "0x%02X to 0x%02X for JP standard\n", reg, set); + else { + isnow = (unsigned int)read_saa(peasycap->pusb_device, reg); + if (0 > ir) + JOM(8, "SAA register 0x%02X changed " + "to 0x%02X\n", reg, isnow); + else + JOM(8, "SAA register 0x%02X changed " + "from 0x%02X to 0x%02X\n", reg, itwas, isnow); + } + + reg = 0x0B; + set = 0x48; + ir = read_saa(peasycap->pusb_device, reg); + if (0 > ir) + SAM("ERROR: cannot read SAA register 0x%02X\n", reg); + else + itwas = (unsigned int)ir; + rc = write_saa(peasycap->pusb_device, reg, set); + if (rc) + SAM("ERROR: failed to set SAA register 0x%02X to 0x%02X " + "for JP standard\n", reg, set); + else { + isnow = (unsigned int)read_saa(peasycap->pusb_device, reg); + if (0 > ir) + JOM(8, "SAA register 0x%02X changed " + "to 0x%02X\n", reg, isnow); + else + JOM(8, "SAA register 0x%02X changed " + "from 0x%02X to 0x%02X\n", reg, itwas, isnow); + } +/*--------------------------------------------------------------------------*/ +/* + * NOTE: NO break HERE: RUN ON TO NEXT CASE + */ +/*--------------------------------------------------------------------------*/ + } + case NTSC_M: + case PAL_BGHIN: { + reg = 0x0E; + set = 0x01; + need = 1; + break; + } + case NTSC_N_443: + case PAL_60: { + reg = 0x0E; + set = 0x11; + need = 1; + break; + } + case NTSC_443: + case PAL_Nc: { + reg = 0x0E; + set = 0x21; + need = 1; + break; + } + case NTSC_N: + case PAL_M: { + reg = 0x0E; + set = 0x31; + need = 1; + break; + } + case SECAM: { + reg = 0x0E; + set = 0x51; + need = 1; + break; + } + default: + break; + } +/*--------------------------------------------------------------------------*/ + if (need) { + ir = read_saa(peasycap->pusb_device, reg); + if (0 > ir) + SAM("ERROR: failed to read SAA register 0x%02X\n", reg); + else + itwas = (unsigned int)ir; + rc = write_saa(peasycap->pusb_device, reg, set); + if (0 != write_saa(peasycap->pusb_device, reg, set)) { + SAM("ERROR: failed to set SAA register " + "0x%02X to 0x%02X for table 42\n", reg, set); + } else { + isnow = (unsigned int)read_saa(peasycap->pusb_device, reg); + if (0 > ir) + JOM(8, "SAA register 0x%02X changed " + "to 0x%02X\n", reg, isnow); + else + JOM(8, "SAA register 0x%02X changed " + "from 0x%02X to 0x%02X\n", reg, itwas, isnow); + } + } +/*--------------------------------------------------------------------------*/ +/* + * SAA7113H DATASHEET PAGE 41 + */ +/*--------------------------------------------------------------------------*/ + reg = 0x08; + ir = read_saa(peasycap->pusb_device, reg); + if (0 > ir) + SAM("ERROR: failed to read SAA register 0x%02X " + "so cannot reset\n", reg); + else { + itwas = (unsigned int)ir; + if (peasycap_standard->mask & 0x0001) + set = itwas | 0x40 ; + else + set = itwas & ~0x40 ; + rc = write_saa(peasycap->pusb_device, reg, set); + if (rc) + SAM("ERROR: failed to set SAA register 0x%02X to 0x%02X\n", + reg, set); + else { + isnow = (unsigned int)read_saa(peasycap->pusb_device, reg); + if (0 > ir) + JOM(8, "SAA register 0x%02X changed to 0x%02X\n", + reg, isnow); + else + JOM(8, "SAA register 0x%02X changed " + "from 0x%02X to 0x%02X\n", reg, itwas, isnow); + } + } +/*--------------------------------------------------------------------------*/ +/* + * SAA7113H DATASHEET PAGE 51, TABLE 57 + */ +/*---------------------------------------------------------------------------*/ + reg = 0x40; + ir = read_saa(peasycap->pusb_device, reg); + if (0 > ir) + SAM("ERROR: failed to read SAA register 0x%02X " + "so cannot reset\n", reg); + else { + itwas = (unsigned int)ir; + if (peasycap_standard->mask & 0x0001) + set = itwas | 0x80 ; + else + set = itwas & ~0x80 ; + rc = write_saa(peasycap->pusb_device, reg, set); + if (rc) + SAM("ERROR: failed to set SAA register 0x%02X to 0x%02X\n", + reg, set); + else { + isnow = (unsigned int)read_saa(peasycap->pusb_device, reg); + if (0 > ir) + JOM(8, "SAA register 0x%02X changed to 0x%02X\n", + reg, isnow); + else + JOM(8, "SAA register 0x%02X changed " + "from 0x%02X to 0x%02X\n", reg, itwas, isnow); + } + } +/*--------------------------------------------------------------------------*/ +/* + * SAA7113H DATASHEET PAGE 53, TABLE 66 + */ +/*--------------------------------------------------------------------------*/ + reg = 0x5A; + ir = read_saa(peasycap->pusb_device, reg); + if (0 > ir) + SAM("ERROR: failed to read SAA register 0x%02X but continuing\n", reg); + itwas = (unsigned int)ir; + if (peasycap_standard->mask & 0x0001) + set = 0x0A ; + else + set = 0x07 ; + if (0 != write_saa(peasycap->pusb_device, reg, set)) + SAM("ERROR: failed to set SAA register 0x%02X to 0x%02X\n", + reg, set); + else { + isnow = (unsigned int)read_saa(peasycap->pusb_device, reg); + if (0 > ir) + JOM(8, "SAA register 0x%02X changed " + "to 0x%02X\n", reg, isnow); + else + JOM(8, "SAA register 0x%02X changed " + "from 0x%02X to 0x%02X\n", reg, itwas, isnow); + } + if (resubmit) + submit_video_urbs(peasycap); + return 0; +} +/*****************************************************************************/ +/*--------------------------------------------------------------------------*/ +/* + * THE ALGORITHM FOR RESPONDING TO THE VIDIO_S_FMT IOCTL REQUIRES + * A VALID VALUE OF peasycap->standard_offset, OTHERWISE -EBUSY IS RETURNED. + * + * PROVIDED THE ARGUMENT try IS false AND THERE IS NO PREMATURE ERROR RETURN + * THIS ROUTINE UPDATES THE FOLLOWING: + * peasycap->format_offset + * peasycap->inputset[peasycap->input].format_offset + * peasycap->pixelformat + * peasycap->height + * peasycap->width + * peasycap->bytesperpixel + * peasycap->byteswaporder + * peasycap->decimatepixel + * peasycap->frame_buffer_used + * peasycap->videofieldamount + * peasycap->offerfields + * + * IF SUCCESSFUL THE FUNCTION RETURNS THE OFFSET IN easycap_format[] + * IDENTIFYING THE FORMAT WHICH IS TO RETURNED TO THE USER. + * ERRORS RETURN A NEGATIVE NUMBER. + */ +/*--------------------------------------------------------------------------*/ +int adjust_format(struct easycap *peasycap, + u32 width, u32 height, u32 pixelformat, int field, bool try) +{ + struct easycap_format *peasycap_format, *peasycap_best_format; + u16 mask; + struct usb_device *p; + int miss, multiplier, best, k; + char bf[5], fo[32], *pc; + u32 uc; + bool resubmit; + + if (!peasycap) { + SAY("ERROR: peasycap is NULL\n"); + return -EFAULT; + } + if (0 > peasycap->standard_offset) { + JOM(8, "%i=peasycap->standard_offset\n", peasycap->standard_offset); + return -EBUSY; + } + p = peasycap->pusb_device; + if (!p) { + SAM("ERROR: peaycap->pusb_device is NULL\n"); + return -EFAULT; + } + pc = &bf[0]; + uc = pixelformat; + memcpy((void *)pc, (void *)(&uc), 4); + bf[4] = 0; + mask = 0xFF & easycap_standard[peasycap->standard_offset].mask; + SAM("sought: %ix%i,%s(0x%08X),%i=field,0x%02X=std mask\n", + width, height, pc, pixelformat, field, mask); + switch (field) { + case V4L2_FIELD_ANY: { + strcpy(&fo[0], "V4L2_FIELD_ANY "); + break; + } + case V4L2_FIELD_NONE: { + strcpy(&fo[0], "V4L2_FIELD_NONE"); + break; + } + case V4L2_FIELD_TOP: { + strcpy(&fo[0], "V4L2_FIELD_TOP"); + break; + } + case V4L2_FIELD_BOTTOM: { + strcpy(&fo[0], "V4L2_FIELD_BOTTOM"); + break; + } + case V4L2_FIELD_INTERLACED: { + strcpy(&fo[0], "V4L2_FIELD_INTERLACED"); + break; + } + case V4L2_FIELD_SEQ_TB: { + strcpy(&fo[0], "V4L2_FIELD_SEQ_TB"); + break; + } + case V4L2_FIELD_SEQ_BT: { + strcpy(&fo[0], "V4L2_FIELD_SEQ_BT"); + break; + } + case V4L2_FIELD_ALTERNATE: { + strcpy(&fo[0], "V4L2_FIELD_ALTERNATE"); + break; + } + case V4L2_FIELD_INTERLACED_TB: { + strcpy(&fo[0], "V4L2_FIELD_INTERLACED_TB"); + break; + } + case V4L2_FIELD_INTERLACED_BT: { + strcpy(&fo[0], "V4L2_FIELD_INTERLACED_BT"); + break; + } + default: { + strcpy(&fo[0], "V4L2_FIELD_... UNKNOWN "); + break; + } + } + SAM("sought: %s\n", &fo[0]); + if (V4L2_FIELD_ANY == field) { + field = V4L2_FIELD_NONE; + SAM("prefer: V4L2_FIELD_NONE=field, was V4L2_FIELD_ANY\n"); + } + peasycap_best_format = NULL; + peasycap_format = &easycap_format[0]; + while (0 != peasycap_format->v4l2_format.fmt.pix.width) { + JOM(16, ".> %i %i 0x%08X %ix%i\n", + peasycap_format->mask & 0x01, + peasycap_format->v4l2_format.fmt.pix.field, + peasycap_format->v4l2_format.fmt.pix.pixelformat, + peasycap_format->v4l2_format.fmt.pix.width, + peasycap_format->v4l2_format.fmt.pix.height); + + if (((peasycap_format->mask & 0x1F) == (mask & 0x1F)) && + (peasycap_format->v4l2_format.fmt.pix.field == field) && + (peasycap_format->v4l2_format.fmt.pix.pixelformat == pixelformat) && + (peasycap_format->v4l2_format.fmt.pix.width == width) && + (peasycap_format->v4l2_format.fmt.pix.height == height)) { + + peasycap_best_format = peasycap_format; + break; + } + peasycap_format++; + } + if (0 == peasycap_format->v4l2_format.fmt.pix.width) { + SAM("cannot do: %ix%i with standard mask 0x%02X\n", + width, height, mask); + peasycap_format = &easycap_format[0]; + best = -1; + while (0 != peasycap_format->v4l2_format.fmt.pix.width) { + if (((peasycap_format->mask & 0x1F) == (mask & 0x1F)) && + (peasycap_format->v4l2_format.fmt.pix.field == field) && + (peasycap_format->v4l2_format.fmt.pix.pixelformat == pixelformat)) { + + miss = abs(peasycap_format->v4l2_format.fmt.pix.width - width); + if ((best > miss) || (best < 0)) { + best = miss; + peasycap_best_format = peasycap_format; + if (!miss) + break; + } + } + peasycap_format++; + } + if (-1 == best) { + SAM("cannot do %ix... with standard mask 0x%02X\n", + width, mask); + SAM("cannot do ...x%i with standard mask 0x%02X\n", + height, mask); + SAM(" %ix%i unmatched\n", width, height); + return peasycap->format_offset; + } + } + if (!peasycap_best_format) { + SAM("MISTAKE: peasycap_best_format is NULL"); + return -EINVAL; + } + peasycap_format = peasycap_best_format; + +/*...........................................................................*/ + if (try) + return peasycap_best_format - easycap_format; +/*...........................................................................*/ + + if (false != try) { + SAM("MISTAKE: true==try where is should be false\n"); + return -EINVAL; + } + SAM("actioning: %ix%i %s\n", + peasycap_format->v4l2_format.fmt.pix.width, + peasycap_format->v4l2_format.fmt.pix.height, + &peasycap_format->name[0]); + peasycap->height = peasycap_format->v4l2_format.fmt.pix.height; + peasycap->width = peasycap_format->v4l2_format.fmt.pix.width; + peasycap->pixelformat = peasycap_format->v4l2_format.fmt.pix.pixelformat; + peasycap->format_offset = peasycap_format - easycap_format; + + + for (k = 0; k < INPUT_MANY; k++) { + if (!peasycap->inputset[k].format_offset_ok) { + peasycap->inputset[k].format_offset = + peasycap->format_offset; + } + } + if ((0 <= peasycap->input) && (INPUT_MANY > peasycap->input)) { + peasycap->inputset[peasycap->input].format_offset = + peasycap->format_offset; + peasycap->inputset[peasycap->input].format_offset_ok = 1; + } else + JOM(8, "%i=peasycap->input\n", peasycap->input); + + + + peasycap->bytesperpixel = (0x00E0 & peasycap_format->mask) >> 5 ; + if (0x0100 & peasycap_format->mask) + peasycap->byteswaporder = true; + else + peasycap->byteswaporder = false; + if (0x0200 & peasycap_format->mask) + peasycap->skip = 5; + else + peasycap->skip = 0; + if (0x0800 & peasycap_format->mask) + peasycap->decimatepixel = true; + else + peasycap->decimatepixel = false; + if (0x1000 & peasycap_format->mask) + peasycap->offerfields = true; + else + peasycap->offerfields = false; + if (peasycap->decimatepixel) + multiplier = 2; + else + multiplier = 1; + peasycap->videofieldamount = + multiplier * peasycap->width * multiplier * peasycap->height; + peasycap->frame_buffer_used = + peasycap->bytesperpixel * peasycap->width * peasycap->height; + if (peasycap->video_isoc_streaming) { + resubmit = true; + kill_video_urbs(peasycap); + } else + resubmit = false; +/*---------------------------------------------------------------------------*/ +/* + * PAL + */ +/*---------------------------------------------------------------------------*/ + if (0 == (0x01 & peasycap_format->mask)) { + if (((720 == peasycap_format->v4l2_format.fmt.pix.width) && + (576 == peasycap_format->v4l2_format.fmt.pix.height)) || + ((360 == peasycap_format->v4l2_format.fmt.pix.width) && + (288 == peasycap_format->v4l2_format.fmt.pix.height))) { + if (set_resolution(p, 0x0000, 0x0001, 0x05A0, 0x0121)) { + SAM("ERROR: set_resolution() failed\n"); + return -EINVAL; + } + } else if ((704 == peasycap_format->v4l2_format.fmt.pix.width) && + (576 == peasycap_format->v4l2_format.fmt.pix.height)) { + if (set_resolution(p, 0x0004, 0x0001, 0x0584, 0x0121)) { + SAM("ERROR: set_resolution() failed\n"); + return -EINVAL; + } + } else if (((640 == peasycap_format->v4l2_format.fmt.pix.width) && + (480 == peasycap_format->v4l2_format.fmt.pix.height)) || + ((320 == peasycap_format->v4l2_format.fmt.pix.width) && + (240 == peasycap_format->v4l2_format.fmt.pix.height))) { + if (set_resolution(p, 0x0014, 0x0020, 0x0514, 0x0110)) { + SAM("ERROR: set_resolution() failed\n"); + return -EINVAL; + } + } else { + SAM("MISTAKE: bad format, cannot set resolution\n"); + return -EINVAL; + } +/*---------------------------------------------------------------------------*/ +/* + * NTSC + */ +/*---------------------------------------------------------------------------*/ + } else { + if (((720 == peasycap_format->v4l2_format.fmt.pix.width) && + (480 == peasycap_format->v4l2_format.fmt.pix.height)) || + ((360 == peasycap_format->v4l2_format.fmt.pix.width) && + (240 == peasycap_format->v4l2_format.fmt.pix.height))) { + if (set_resolution(p, 0x0000, 0x0003, 0x05A0, 0x00F3)) { + SAM("ERROR: set_resolution() failed\n"); + return -EINVAL; + } + } else if (((640 == peasycap_format->v4l2_format.fmt.pix.width) && + (480 == peasycap_format->v4l2_format.fmt.pix.height)) || + ((320 == peasycap_format->v4l2_format.fmt.pix.width) && + (240 == peasycap_format->v4l2_format.fmt.pix.height))) { + if (set_resolution(p, 0x0014, 0x0003, 0x0514, 0x00F3)) { + SAM("ERROR: set_resolution() failed\n"); + return -EINVAL; + } + } else { + SAM("MISTAKE: bad format, cannot set resolution\n"); + return -EINVAL; + } + } +/*---------------------------------------------------------------------------*/ + if (resubmit) + submit_video_urbs(peasycap); + + return peasycap_best_format - easycap_format; +} +/*****************************************************************************/ +int adjust_brightness(struct easycap *peasycap, int value) +{ + unsigned int mood; + int i1, k; + + if (!peasycap) { + SAY("ERROR: peasycap is NULL\n"); + return -EFAULT; + } + if (!peasycap->pusb_device) { + SAM("ERROR: peasycap->pusb_device is NULL\n"); + return -EFAULT; + } + i1 = 0; + while (0xFFFFFFFF != easycap_control[i1].id) { + if (V4L2_CID_BRIGHTNESS == easycap_control[i1].id) { + if ((easycap_control[i1].minimum > value) || + (easycap_control[i1].maximum < value)) + value = easycap_control[i1].default_value; + + if ((easycap_control[i1].minimum <= peasycap->brightness) && + (easycap_control[i1].maximum >= peasycap->brightness)) { + if (peasycap->brightness == value) { + SAM("unchanged brightness at 0x%02X\n", + value); + return 0; + } + } + peasycap->brightness = value; + for (k = 0; k < INPUT_MANY; k++) { + if (!peasycap->inputset[k].brightness_ok) + peasycap->inputset[k].brightness = + peasycap->brightness; + } + if ((0 <= peasycap->input) && (INPUT_MANY > peasycap->input)) { + peasycap->inputset[peasycap->input].brightness = + peasycap->brightness; + peasycap->inputset[peasycap->input].brightness_ok = 1; + } else + JOM(8, "%i=peasycap->input\n", peasycap->input); + mood = 0x00FF & (unsigned int)peasycap->brightness; + if (!write_saa(peasycap->pusb_device, 0x0A, mood)) { + SAM("adjusting brightness to 0x%02X\n", mood); + return 0; + } else { + SAM("WARNING: failed to adjust brightness " + "to 0x%02X\n", mood); + return -ENOENT; + } + break; + } + i1++; + } + SAM("WARNING: failed to adjust brightness: control not found\n"); + return -ENOENT; +} +/*****************************************************************************/ +int adjust_contrast(struct easycap *peasycap, int value) +{ + unsigned int mood; + int i1, k; + + if (!peasycap) { + SAY("ERROR: peasycap is NULL\n"); + return -EFAULT; + } + if (!peasycap->pusb_device) { + SAM("ERROR: peasycap->pusb_device is NULL\n"); + return -EFAULT; + } + i1 = 0; + while (0xFFFFFFFF != easycap_control[i1].id) { + if (V4L2_CID_CONTRAST == easycap_control[i1].id) { + if ((easycap_control[i1].minimum > value) || + (easycap_control[i1].maximum < value)) + value = easycap_control[i1].default_value; + + + if ((easycap_control[i1].minimum <= peasycap->contrast) && + (easycap_control[i1].maximum >= peasycap->contrast)) { + if (peasycap->contrast == value) { + SAM("unchanged contrast at 0x%02X\n", value); + return 0; + } + } + peasycap->contrast = value; + for (k = 0; k < INPUT_MANY; k++) { + if (!peasycap->inputset[k].contrast_ok) + peasycap->inputset[k].contrast = peasycap->contrast; + } + + if ((0 <= peasycap->input) && (INPUT_MANY > peasycap->input)) { + peasycap->inputset[peasycap->input].contrast = + peasycap->contrast; + peasycap->inputset[peasycap->input].contrast_ok = 1; + } else + JOM(8, "%i=peasycap->input\n", peasycap->input); + + mood = 0x00FF & (unsigned int) (peasycap->contrast - 128); + if (!write_saa(peasycap->pusb_device, 0x0B, mood)) { + SAM("adjusting contrast to 0x%02X\n", mood); + return 0; + } else { + SAM("WARNING: failed to adjust contrast to " + "0x%02X\n", mood); + return -ENOENT; + } + break; + } + i1++; + } + SAM("WARNING: failed to adjust contrast: control not found\n"); + return -ENOENT; +} +/*****************************************************************************/ +int adjust_saturation(struct easycap *peasycap, int value) +{ + unsigned int mood; + int i1, k; + + if (!peasycap) { + SAY("ERROR: peasycap is NULL\n"); + return -EFAULT; + } + if (!peasycap->pusb_device) { + SAM("ERROR: peasycap->pusb_device is NULL\n"); + return -EFAULT; + } + i1 = 0; + while (0xFFFFFFFF != easycap_control[i1].id) { + if (V4L2_CID_SATURATION == easycap_control[i1].id) { + if ((easycap_control[i1].minimum > value) || + (easycap_control[i1].maximum < value)) + value = easycap_control[i1].default_value; + + + if ((easycap_control[i1].minimum <= peasycap->saturation) && + (easycap_control[i1].maximum >= peasycap->saturation)) { + if (peasycap->saturation == value) { + SAM("unchanged saturation at 0x%02X\n", + value); + return 0; + } + } + peasycap->saturation = value; + for (k = 0; k < INPUT_MANY; k++) { + if (!peasycap->inputset[k].saturation_ok) + peasycap->inputset[k].saturation = + peasycap->saturation; + } + if ((0 <= peasycap->input) && (INPUT_MANY > peasycap->input)) { + peasycap->inputset[peasycap->input].saturation = + peasycap->saturation; + peasycap->inputset[peasycap->input].saturation_ok = 1; + } else + JOM(8, "%i=peasycap->input\n", peasycap->input); + mood = 0x00FF & (unsigned int) (peasycap->saturation - 128); + if (!write_saa(peasycap->pusb_device, 0x0C, mood)) { + SAM("adjusting saturation to 0x%02X\n", mood); + return 0; + } else { + SAM("WARNING: failed to adjust saturation to " + "0x%02X\n", mood); + return -ENOENT; + } + break; + } + i1++; + } + SAM("WARNING: failed to adjust saturation: control not found\n"); + return -ENOENT; +} +/*****************************************************************************/ +int adjust_hue(struct easycap *peasycap, int value) +{ + unsigned int mood; + int i1, i2, k; + + if (!peasycap) { + SAY("ERROR: peasycap is NULL\n"); + return -EFAULT; + } + if (!peasycap->pusb_device) { + SAM("ERROR: peasycap->pusb_device is NULL\n"); + return -EFAULT; + } + i1 = 0; + while (0xFFFFFFFF != easycap_control[i1].id) { + if (V4L2_CID_HUE == easycap_control[i1].id) { + if ((easycap_control[i1].minimum > value) || + (easycap_control[i1].maximum < value)) + value = easycap_control[i1].default_value; + + if ((easycap_control[i1].minimum <= peasycap->hue) && + (easycap_control[i1].maximum >= peasycap->hue)) { + if (peasycap->hue == value) { + SAM("unchanged hue at 0x%02X\n", value); + return 0; + } + } + peasycap->hue = value; + for (k = 0; k < INPUT_MANY; k++) { + if (!peasycap->inputset[k].hue_ok) + peasycap->inputset[k].hue = peasycap->hue; + } + if (0 <= peasycap->input && INPUT_MANY > peasycap->input) { + peasycap->inputset[peasycap->input].hue = peasycap->hue; + peasycap->inputset[peasycap->input].hue_ok = 1; + } else + JOM(8, "%i=peasycap->input\n", peasycap->input); + i2 = peasycap->hue - 128; + mood = 0x00FF & ((int) i2); + if (!write_saa(peasycap->pusb_device, 0x0D, mood)) { + SAM("adjusting hue to 0x%02X\n", mood); + return 0; + } else { + SAM("WARNING: failed to adjust hue to 0x%02X\n", mood); + return -ENOENT; + } + break; + } + i1++; + } + SAM("WARNING: failed to adjust hue: control not found\n"); + return -ENOENT; +} +/*****************************************************************************/ +int adjust_volume(struct easycap *peasycap, int value) +{ + s8 mood; + int i1; + + if (!peasycap) { + SAY("ERROR: peasycap is NULL\n"); + return -EFAULT; + } + if (!peasycap->pusb_device) { + SAM("ERROR: peasycap->pusb_device is NULL\n"); + return -EFAULT; + } + i1 = 0; + while (0xFFFFFFFF != easycap_control[i1].id) { + if (V4L2_CID_AUDIO_VOLUME == easycap_control[i1].id) { + if ((easycap_control[i1].minimum > value) || + (easycap_control[i1].maximum < value)) + value = easycap_control[i1].default_value; + + if ((easycap_control[i1].minimum <= peasycap->volume) && + (easycap_control[i1].maximum >= peasycap->volume)) { + if (peasycap->volume == value) { + SAM("unchanged volume at 0x%02X\n", value); + return 0; + } + } + peasycap->volume = value; + mood = (16 > peasycap->volume) ? 16 : + ((31 < peasycap->volume) ? 31 : + (s8) peasycap->volume); + if (!audio_gainset(peasycap->pusb_device, mood)) { + SAM("adjusting volume to 0x%02X\n", mood); + return 0; + } else { + SAM("WARNING: failed to adjust volume to " + "0x%2X\n", mood); + return -ENOENT; + } + break; + } + i1++; + } + SAM("WARNING: failed to adjust volume: control not found\n"); + return -ENOENT; +} +/*****************************************************************************/ +/*---------------------------------------------------------------------------*/ +/* + * AN ALTERNATIVE METHOD OF MUTING MIGHT SEEM TO BE: + * usb_set_interface(peasycap->pusb_device, + * peasycap->audio_interface, + * peasycap->audio_altsetting_off); + * HOWEVER, AFTER THIS COMMAND IS ISSUED ALL SUBSEQUENT URBS RECEIVE STATUS + * -ESHUTDOWN. THE HANDLER ROUTINE easyxxx_complete() DECLINES TO RESUBMIT + * THE URB AND THE PIPELINE COLLAPSES IRRETRIEVABLY. BEWARE. + */ +/*---------------------------------------------------------------------------*/ +static int adjust_mute(struct easycap *peasycap, int value) +{ + int i1; + + if (!peasycap) { + SAY("ERROR: peasycap is NULL\n"); + return -EFAULT; + } + if (!peasycap->pusb_device) { + SAM("ERROR: peasycap->pusb_device is NULL\n"); + return -EFAULT; + } + i1 = 0; + while (0xFFFFFFFF != easycap_control[i1].id) { + if (V4L2_CID_AUDIO_MUTE == easycap_control[i1].id) { + peasycap->mute = value; + switch (peasycap->mute) { + case 1: { + peasycap->audio_idle = 1; + SAM("adjusting mute: %i=peasycap->audio_idle\n", + peasycap->audio_idle); + return 0; + } + default: { + peasycap->audio_idle = 0; + SAM("adjusting mute: %i=peasycap->audio_idle\n", + peasycap->audio_idle); + return 0; + } + } + break; + } + i1++; + } + SAM("WARNING: failed to adjust mute: control not found\n"); + return -ENOENT; +} +/*---------------------------------------------------------------------------*/ +long easycap_unlocked_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct easycap *peasycap; + struct usb_device *p; + int kd; + + if (!file) { + SAY("ERROR: file is NULL\n"); + return -ERESTARTSYS; + } + peasycap = file->private_data; + if (!peasycap) { + SAY("ERROR: peasycap is NULL\n"); + return -1; + } + p = peasycap->pusb_device; + if (!p) { + SAM("ERROR: peasycap->pusb_device is NULL\n"); + return -EFAULT; + } + kd = isdongle(peasycap); + if (0 <= kd && DONGLE_MANY > kd) { + if (mutex_lock_interruptible(&easycapdc60_dongle[kd].mutex_video)) { + SAY("ERROR: cannot lock " + "easycapdc60_dongle[%i].mutex_video\n", kd); + return -ERESTARTSYS; + } + JOM(4, "locked easycapdc60_dongle[%i].mutex_video\n", kd); +/*---------------------------------------------------------------------------*/ +/* + * MEANWHILE, easycap_usb_disconnect() MAY HAVE FREED POINTER peasycap, + * IN WHICH CASE A REPEAT CALL TO isdongle() WILL FAIL. + * IF NECESSARY, BAIL OUT. + */ +/*---------------------------------------------------------------------------*/ + if (kd != isdongle(peasycap)) + return -ERESTARTSYS; + if (!file) { + SAY("ERROR: file is NULL\n"); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -ERESTARTSYS; + } + peasycap = file->private_data; + if (!peasycap) { + SAY("ERROR: peasycap is NULL\n"); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -ERESTARTSYS; + } + if (!peasycap->pusb_device) { + SAM("ERROR: peasycap->pusb_device is NULL\n"); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -ERESTARTSYS; + } + } else { +/*---------------------------------------------------------------------------*/ +/* + * IF easycap_usb_disconnect() HAS ALREADY FREED POINTER peasycap BEFORE THE + * ATTEMPT TO ACQUIRE THE SEMAPHORE, isdongle() WILL HAVE FAILED. BAIL OUT. + */ +/*---------------------------------------------------------------------------*/ + return -ERESTARTSYS; + } +/*---------------------------------------------------------------------------*/ + switch (cmd) { + case VIDIOC_QUERYCAP: { + struct v4l2_capability v4l2_capability; + char version[16], *p1, *p2; + int i, rc, k[3]; + long lng; + + JOM(8, "VIDIOC_QUERYCAP\n"); + + if (16 <= strlen(EASYCAP_DRIVER_VERSION)) { + SAM("ERROR: bad driver version string\n"); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EINVAL; + } + strcpy(&version[0], EASYCAP_DRIVER_VERSION); + for (i = 0; i < 3; i++) + k[i] = 0; + p2 = &version[0]; + i = 0; + while (*p2) { + p1 = p2; + while (*p2 && ('.' != *p2)) + p2++; + if (*p2) + *p2++ = 0; + if (3 > i) { + rc = (int) strict_strtol(p1, 10, &lng); + if (rc) { + SAM("ERROR: %i=strict_strtol(%s,.,,)\n", + rc, p1); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EINVAL; + } + k[i] = (int)lng; + } + i++; + } + + memset(&v4l2_capability, 0, sizeof(struct v4l2_capability)); + strlcpy(&v4l2_capability.driver[0], + "easycap", sizeof(v4l2_capability.driver)); + + v4l2_capability.capabilities = V4L2_CAP_VIDEO_CAPTURE | + V4L2_CAP_STREAMING | + V4L2_CAP_AUDIO | + V4L2_CAP_READWRITE; + + v4l2_capability.version = KERNEL_VERSION(k[0], k[1], k[2]); + JOM(8, "v4l2_capability.version=(%i,%i,%i)\n", k[0], k[1], k[2]); + + strlcpy(&v4l2_capability.card[0], + "EasyCAP DC60", sizeof(v4l2_capability.card)); + + if (usb_make_path(peasycap->pusb_device, + &v4l2_capability.bus_info[0], + sizeof(v4l2_capability.bus_info)) < 0) { + + strlcpy(&v4l2_capability.bus_info[0], "EasyCAP bus_info", + sizeof(v4l2_capability.bus_info)); + JOM(8, "%s=v4l2_capability.bus_info\n", + &v4l2_capability.bus_info[0]); + } + if (copy_to_user((void __user *)arg, &v4l2_capability, + sizeof(struct v4l2_capability))) { + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EFAULT; + } + break; + } +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + case VIDIOC_ENUMINPUT: { + struct v4l2_input v4l2_input; + u32 index; + + JOM(8, "VIDIOC_ENUMINPUT\n"); + + if (copy_from_user(&v4l2_input, (void __user *)arg, + sizeof(struct v4l2_input))) { + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EFAULT; + } + + index = v4l2_input.index; + memset(&v4l2_input, 0, sizeof(struct v4l2_input)); + + switch (index) { + case 0: { + v4l2_input.index = index; + strcpy(&v4l2_input.name[0], "CVBS0"); + v4l2_input.type = V4L2_INPUT_TYPE_CAMERA; + v4l2_input.audioset = 0x01; + v4l2_input.tuner = 0; + v4l2_input.std = V4L2_STD_PAL | + V4L2_STD_SECAM | + V4L2_STD_NTSC ; + v4l2_input.status = 0; + JOM(8, "%i=index: %s\n", index, &v4l2_input.name[0]); + break; + } + case 1: { + v4l2_input.index = index; + strcpy(&v4l2_input.name[0], "CVBS1"); + v4l2_input.type = V4L2_INPUT_TYPE_CAMERA; + v4l2_input.audioset = 0x01; + v4l2_input.tuner = 0; + v4l2_input.std = V4L2_STD_PAL | V4L2_STD_SECAM | + V4L2_STD_NTSC; + v4l2_input.status = 0; + JOM(8, "%i=index: %s\n", index, &v4l2_input.name[0]); + break; + } + case 2: { + v4l2_input.index = index; + strcpy(&v4l2_input.name[0], "CVBS2"); + v4l2_input.type = V4L2_INPUT_TYPE_CAMERA; + v4l2_input.audioset = 0x01; + v4l2_input.tuner = 0; + v4l2_input.std = V4L2_STD_PAL | V4L2_STD_SECAM | + V4L2_STD_NTSC ; + v4l2_input.status = 0; + JOM(8, "%i=index: %s\n", index, &v4l2_input.name[0]); + break; + } + case 3: { + v4l2_input.index = index; + strcpy(&v4l2_input.name[0], "CVBS3"); + v4l2_input.type = V4L2_INPUT_TYPE_CAMERA; + v4l2_input.audioset = 0x01; + v4l2_input.tuner = 0; + v4l2_input.std = V4L2_STD_PAL | V4L2_STD_SECAM | + V4L2_STD_NTSC ; + v4l2_input.status = 0; + JOM(8, "%i=index: %s\n", index, &v4l2_input.name[0]); + break; + } + case 4: { + v4l2_input.index = index; + strcpy(&v4l2_input.name[0], "CVBS4"); + v4l2_input.type = V4L2_INPUT_TYPE_CAMERA; + v4l2_input.audioset = 0x01; + v4l2_input.tuner = 0; + v4l2_input.std = V4L2_STD_PAL | V4L2_STD_SECAM | + V4L2_STD_NTSC ; + v4l2_input.status = 0; + JOM(8, "%i=index: %s\n", index, &v4l2_input.name[0]); + break; + } + case 5: { + v4l2_input.index = index; + strcpy(&v4l2_input.name[0], "S-VIDEO"); + v4l2_input.type = V4L2_INPUT_TYPE_CAMERA; + v4l2_input.audioset = 0x01; + v4l2_input.tuner = 0; + v4l2_input.std = V4L2_STD_PAL | V4L2_STD_SECAM | + V4L2_STD_NTSC ; + v4l2_input.status = 0; + JOM(8, "%i=index: %s\n", index, &v4l2_input.name[0]); + break; + } + default: { + JOM(8, "%i=index: exhausts inputs\n", index); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EINVAL; + } + } + + if (copy_to_user((void __user *)arg, &v4l2_input, + sizeof(struct v4l2_input))) { + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EFAULT; + } + break; + } +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + case VIDIOC_G_INPUT: { + u32 index; + + JOM(8, "VIDIOC_G_INPUT\n"); + index = (u32)peasycap->input; + JOM(8, "user is told: %i\n", index); + if (copy_to_user((void __user *)arg, &index, sizeof(u32))) { + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EFAULT; + } + break; + } +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + case VIDIOC_S_INPUT: + { + u32 index; + int rc; + + JOM(8, "VIDIOC_S_INPUT\n"); + + if (0 != copy_from_user(&index, (void __user *)arg, sizeof(u32))) { + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EFAULT; + } + + JOM(8, "user requests input %i\n", index); + + if ((int)index == peasycap->input) { + SAM("requested input already in effect\n"); + break; + } + + if ((0 > index) || (INPUT_MANY <= index)) { + JOM(8, "ERROR: bad requested input: %i\n", index); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EINVAL; + } + + rc = newinput(peasycap, (int)index); + if (0 == rc) { + JOM(8, "newinput(.,%i) OK\n", (int)index); + } else { + SAM("ERROR: newinput(.,%i) returned %i\n", (int)index, rc); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EFAULT; + } + break; + } +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + case VIDIOC_ENUMAUDIO: { + JOM(8, "VIDIOC_ENUMAUDIO\n"); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EINVAL; + } +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + case VIDIOC_ENUMAUDOUT: { + struct v4l2_audioout v4l2_audioout; + + JOM(8, "VIDIOC_ENUMAUDOUT\n"); + + if (copy_from_user(&v4l2_audioout, (void __user *)arg, + sizeof(struct v4l2_audioout))) { + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EFAULT; + } + + if (0 != v4l2_audioout.index) { + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EINVAL; + } + memset(&v4l2_audioout, 0, sizeof(struct v4l2_audioout)); + v4l2_audioout.index = 0; + strcpy(&v4l2_audioout.name[0], "Soundtrack"); + + if (copy_to_user((void __user *)arg, &v4l2_audioout, + sizeof(struct v4l2_audioout))) { + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EFAULT; + } + break; + } +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + case VIDIOC_QUERYCTRL: { + int i1; + struct v4l2_queryctrl v4l2_queryctrl; + + JOM(8, "VIDIOC_QUERYCTRL\n"); + + if (0 != copy_from_user(&v4l2_queryctrl, (void __user *)arg, + sizeof(struct v4l2_queryctrl))) { + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EFAULT; + } + + i1 = 0; + while (0xFFFFFFFF != easycap_control[i1].id) { + if (easycap_control[i1].id == v4l2_queryctrl.id) { + JOM(8, "VIDIOC_QUERYCTRL %s=easycap_control[%i]" + ".name\n", &easycap_control[i1].name[0], i1); + memcpy(&v4l2_queryctrl, &easycap_control[i1], + sizeof(struct v4l2_queryctrl)); + break; + } + i1++; + } + if (0xFFFFFFFF == easycap_control[i1].id) { + JOM(8, "%i=index: exhausts controls\n", i1); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EINVAL; + } + if (copy_to_user((void __user *)arg, &v4l2_queryctrl, + sizeof(struct v4l2_queryctrl))) { + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EFAULT; + } + break; + } +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + case VIDIOC_QUERYMENU: { + JOM(8, "VIDIOC_QUERYMENU unsupported\n"); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EINVAL; + } +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + case VIDIOC_G_CTRL: { + struct v4l2_control *pv4l2_control; + + JOM(8, "VIDIOC_G_CTRL\n"); + pv4l2_control = memdup_user((void __user *)arg, + sizeof(struct v4l2_control)); + if (IS_ERR(pv4l2_control)) { + SAM("ERROR: copy from user failed\n"); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return PTR_ERR(pv4l2_control); + } + + switch (pv4l2_control->id) { + case V4L2_CID_BRIGHTNESS: { + pv4l2_control->value = peasycap->brightness; + JOM(8, "user enquires brightness: %i\n", pv4l2_control->value); + break; + } + case V4L2_CID_CONTRAST: { + pv4l2_control->value = peasycap->contrast; + JOM(8, "user enquires contrast: %i\n", pv4l2_control->value); + break; + } + case V4L2_CID_SATURATION: { + pv4l2_control->value = peasycap->saturation; + JOM(8, "user enquires saturation: %i\n", pv4l2_control->value); + break; + } + case V4L2_CID_HUE: { + pv4l2_control->value = peasycap->hue; + JOM(8, "user enquires hue: %i\n", pv4l2_control->value); + break; + } + case V4L2_CID_AUDIO_VOLUME: { + pv4l2_control->value = peasycap->volume; + JOM(8, "user enquires volume: %i\n", pv4l2_control->value); + break; + } + case V4L2_CID_AUDIO_MUTE: { + if (1 == peasycap->mute) + pv4l2_control->value = true; + else + pv4l2_control->value = false; + JOM(8, "user enquires mute: %i\n", pv4l2_control->value); + break; + } + default: { + SAM("ERROR: unknown V4L2 control: 0x%08X=id\n", + pv4l2_control->id); + kfree(pv4l2_control); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EINVAL; + } + } + if (copy_to_user((void __user *)arg, pv4l2_control, + sizeof(struct v4l2_control))) { + kfree(pv4l2_control); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EFAULT; + } + kfree(pv4l2_control); + break; + } +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + case VIDIOC_S_CTRL: { + struct v4l2_control v4l2_control; + + JOM(8, "VIDIOC_S_CTRL\n"); + + if (0 != copy_from_user(&v4l2_control, (void __user *)arg, + sizeof(struct v4l2_control))) { + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EFAULT; + } + + switch (v4l2_control.id) { + case V4L2_CID_BRIGHTNESS: { + JOM(8, "user requests brightness %i\n", v4l2_control.value); + if (0 != adjust_brightness(peasycap, v4l2_control.value)) + ; + break; + } + case V4L2_CID_CONTRAST: { + JOM(8, "user requests contrast %i\n", v4l2_control.value); + if (0 != adjust_contrast(peasycap, v4l2_control.value)) + ; + break; + } + case V4L2_CID_SATURATION: { + JOM(8, "user requests saturation %i\n", v4l2_control.value); + if (0 != adjust_saturation(peasycap, v4l2_control.value)) + ; + break; + } + case V4L2_CID_HUE: { + JOM(8, "user requests hue %i\n", v4l2_control.value); + if (0 != adjust_hue(peasycap, v4l2_control.value)) + ; + break; + } + case V4L2_CID_AUDIO_VOLUME: { + JOM(8, "user requests volume %i\n", v4l2_control.value); + if (0 != adjust_volume(peasycap, v4l2_control.value)) + ; + break; + } + case V4L2_CID_AUDIO_MUTE: { + int mute; + + JOM(8, "user requests mute %i\n", v4l2_control.value); + if (v4l2_control.value) + mute = 1; + else + mute = 0; + + if (0 != adjust_mute(peasycap, mute)) + SAM("WARNING: failed to adjust mute to %i\n", mute); + break; + } + default: { + SAM("ERROR: unknown V4L2 control: 0x%08X=id\n", + v4l2_control.id); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EINVAL; + } + } + break; + } +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + case VIDIOC_S_EXT_CTRLS: { + JOM(8, "VIDIOC_S_EXT_CTRLS unsupported\n"); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EINVAL; + } +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + case VIDIOC_ENUM_FMT: { + u32 index; + struct v4l2_fmtdesc v4l2_fmtdesc; + + JOM(8, "VIDIOC_ENUM_FMT\n"); + + if (0 != copy_from_user(&v4l2_fmtdesc, (void __user *)arg, + sizeof(struct v4l2_fmtdesc))) { + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EFAULT; + } + + index = v4l2_fmtdesc.index; + memset(&v4l2_fmtdesc, 0, sizeof(struct v4l2_fmtdesc)); + + v4l2_fmtdesc.index = index; + v4l2_fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + switch (index) { + case 0: { + v4l2_fmtdesc.flags = 0; + strcpy(&v4l2_fmtdesc.description[0], "uyvy"); + v4l2_fmtdesc.pixelformat = V4L2_PIX_FMT_UYVY; + JOM(8, "%i=index: %s\n", index, &v4l2_fmtdesc.description[0]); + break; + } + case 1: { + v4l2_fmtdesc.flags = 0; + strcpy(&v4l2_fmtdesc.description[0], "yuy2"); + v4l2_fmtdesc.pixelformat = V4L2_PIX_FMT_YUYV; + JOM(8, "%i=index: %s\n", index, &v4l2_fmtdesc.description[0]); + break; + } + case 2: { + v4l2_fmtdesc.flags = 0; + strcpy(&v4l2_fmtdesc.description[0], "rgb24"); + v4l2_fmtdesc.pixelformat = V4L2_PIX_FMT_RGB24; + JOM(8, "%i=index: %s\n", index, &v4l2_fmtdesc.description[0]); + break; + } + case 3: { + v4l2_fmtdesc.flags = 0; + strcpy(&v4l2_fmtdesc.description[0], "rgb32"); + v4l2_fmtdesc.pixelformat = V4L2_PIX_FMT_RGB32; + JOM(8, "%i=index: %s\n", index, &v4l2_fmtdesc.description[0]); + break; + } + case 4: { + v4l2_fmtdesc.flags = 0; + strcpy(&v4l2_fmtdesc.description[0], "bgr24"); + v4l2_fmtdesc.pixelformat = V4L2_PIX_FMT_BGR24; + JOM(8, "%i=index: %s\n", index, &v4l2_fmtdesc.description[0]); + break; + } + case 5: { + v4l2_fmtdesc.flags = 0; + strcpy(&v4l2_fmtdesc.description[0], "bgr32"); + v4l2_fmtdesc.pixelformat = V4L2_PIX_FMT_BGR32; + JOM(8, "%i=index: %s\n", index, &v4l2_fmtdesc.description[0]); + break; + } + default: { + JOM(8, "%i=index: exhausts formats\n", index); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EINVAL; + } + } + if (copy_to_user((void __user *)arg, &v4l2_fmtdesc, + sizeof(struct v4l2_fmtdesc))) { + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EFAULT; + } + break; + } +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ +/* + * THE RESPONSE TO VIDIOC_ENUM_FRAMESIZES MUST BE CONDITIONED ON THE + * THE CURRENT STANDARD, BECAUSE THAT IS WHAT gstreamer EXPECTS. BEWARE. + */ +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + case VIDIOC_ENUM_FRAMESIZES: { + u32 index; + struct v4l2_frmsizeenum v4l2_frmsizeenum; + + JOM(8, "VIDIOC_ENUM_FRAMESIZES\n"); + + if (0 != copy_from_user(&v4l2_frmsizeenum, (void __user *)arg, + sizeof(struct v4l2_frmsizeenum))) { + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EFAULT; + } + + index = v4l2_frmsizeenum.index; + + v4l2_frmsizeenum.type = (u32) V4L2_FRMSIZE_TYPE_DISCRETE; + + if (peasycap->ntsc) { + switch (index) { + case 0: { + v4l2_frmsizeenum.discrete.width = 640; + v4l2_frmsizeenum.discrete.height = 480; + JOM(8, "%i=index: %ix%i\n", index, + (int)(v4l2_frmsizeenum. + discrete.width), + (int)(v4l2_frmsizeenum. + discrete.height)); + break; + } + case 1: { + v4l2_frmsizeenum.discrete.width = 320; + v4l2_frmsizeenum.discrete.height = 240; + JOM(8, "%i=index: %ix%i\n", index, + (int)(v4l2_frmsizeenum. + discrete.width), + (int)(v4l2_frmsizeenum. + discrete.height)); + break; + } + case 2: { + v4l2_frmsizeenum.discrete.width = 720; + v4l2_frmsizeenum.discrete.height = 480; + JOM(8, "%i=index: %ix%i\n", index, + (int)(v4l2_frmsizeenum. + discrete.width), + (int)(v4l2_frmsizeenum. + discrete.height)); + break; + } + case 3: { + v4l2_frmsizeenum.discrete.width = 360; + v4l2_frmsizeenum.discrete.height = 240; + JOM(8, "%i=index: %ix%i\n", index, + (int)(v4l2_frmsizeenum. + discrete.width), + (int)(v4l2_frmsizeenum. + discrete.height)); + break; + } + default: { + JOM(8, "%i=index: exhausts framesizes\n", index); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EINVAL; + } + } + } else { + switch (index) { + case 0: { + v4l2_frmsizeenum.discrete.width = 640; + v4l2_frmsizeenum.discrete.height = 480; + JOM(8, "%i=index: %ix%i\n", index, + (int)(v4l2_frmsizeenum. + discrete.width), + (int)(v4l2_frmsizeenum. + discrete.height)); + break; + } + case 1: { + v4l2_frmsizeenum.discrete.width = 320; + v4l2_frmsizeenum.discrete.height = 240; + JOM(8, "%i=index: %ix%i\n", index, + (int)(v4l2_frmsizeenum. + discrete.width), + (int)(v4l2_frmsizeenum. + discrete.height)); + break; + } + case 2: { + v4l2_frmsizeenum.discrete.width = 704; + v4l2_frmsizeenum.discrete.height = 576; + JOM(8, "%i=index: %ix%i\n", index, + (int)(v4l2_frmsizeenum. + discrete.width), + (int)(v4l2_frmsizeenum. + discrete.height)); + break; + } + case 3: { + v4l2_frmsizeenum.discrete.width = 720; + v4l2_frmsizeenum.discrete.height = 576; + JOM(8, "%i=index: %ix%i\n", index, + (int)(v4l2_frmsizeenum. + discrete.width), + (int)(v4l2_frmsizeenum. + discrete.height)); + break; + } + case 4: { + v4l2_frmsizeenum.discrete.width = 360; + v4l2_frmsizeenum.discrete.height = 288; + JOM(8, "%i=index: %ix%i\n", index, + (int)(v4l2_frmsizeenum. + discrete.width), + (int)(v4l2_frmsizeenum. + discrete.height)); + break; + } + default: { + JOM(8, "%i=index: exhausts framesizes\n", index); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EINVAL; + } + } + } + if (copy_to_user((void __user *)arg, &v4l2_frmsizeenum, + sizeof(struct v4l2_frmsizeenum))) { + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EFAULT; + } + break; + } +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ +/* + * THE RESPONSE TO VIDIOC_ENUM_FRAMEINTERVALS MUST BE CONDITIONED ON THE + * THE CURRENT STANDARD, BECAUSE THAT IS WHAT gstreamer EXPECTS. BEWARE. + */ +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + case VIDIOC_ENUM_FRAMEINTERVALS: { + u32 index; + int denominator; + struct v4l2_frmivalenum v4l2_frmivalenum; + + JOM(8, "VIDIOC_ENUM_FRAMEINTERVALS\n"); + + if (peasycap->fps) + denominator = peasycap->fps; + else { + if (peasycap->ntsc) + denominator = 30; + else + denominator = 25; + } + + if (0 != copy_from_user(&v4l2_frmivalenum, (void __user *)arg, + sizeof(struct v4l2_frmivalenum))) { + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EFAULT; + } + + index = v4l2_frmivalenum.index; + + v4l2_frmivalenum.type = (u32) V4L2_FRMIVAL_TYPE_DISCRETE; + + switch (index) { + case 0: { + v4l2_frmivalenum.discrete.numerator = 1; + v4l2_frmivalenum.discrete.denominator = denominator; + JOM(8, "%i=index: %i/%i\n", index, + (int)(v4l2_frmivalenum.discrete.numerator), + (int)(v4l2_frmivalenum.discrete.denominator)); + break; + } + case 1: { + v4l2_frmivalenum.discrete.numerator = 1; + v4l2_frmivalenum.discrete.denominator = denominator/5; + JOM(8, "%i=index: %i/%i\n", index, + (int)(v4l2_frmivalenum.discrete.numerator), + (int)(v4l2_frmivalenum.discrete.denominator)); + break; + } + default: { + JOM(8, "%i=index: exhausts frameintervals\n", index); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EINVAL; + } + } + if (copy_to_user((void __user *)arg, &v4l2_frmivalenum, + sizeof(struct v4l2_frmivalenum))) { + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EFAULT; + } + break; + } +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + case VIDIOC_G_FMT: { + struct v4l2_format *pv4l2_format; + struct v4l2_pix_format *pv4l2_pix_format; + + JOM(8, "VIDIOC_G_FMT\n"); + pv4l2_format = kzalloc(sizeof(struct v4l2_format), GFP_KERNEL); + if (!pv4l2_format) { + SAM("ERROR: out of memory\n"); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -ENOMEM; + } + pv4l2_pix_format = kzalloc(sizeof(struct v4l2_pix_format), GFP_KERNEL); + if (!pv4l2_pix_format) { + SAM("ERROR: out of memory\n"); + kfree(pv4l2_format); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -ENOMEM; + } + if (0 != copy_from_user(pv4l2_format, (void __user *)arg, + sizeof(struct v4l2_format))) { + kfree(pv4l2_format); + kfree(pv4l2_pix_format); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EFAULT; + } + + if (pv4l2_format->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) { + kfree(pv4l2_format); + kfree(pv4l2_pix_format); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EINVAL; + } + + memset(pv4l2_pix_format, 0, sizeof(struct v4l2_pix_format)); + pv4l2_format->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + memcpy(&pv4l2_format->fmt.pix, + &easycap_format[peasycap->format_offset] + .v4l2_format.fmt.pix, sizeof(struct v4l2_pix_format)); + JOM(8, "user is told: %s\n", + &easycap_format[peasycap->format_offset].name[0]); + + if (copy_to_user((void __user *)arg, pv4l2_format, + sizeof(struct v4l2_format))) { + kfree(pv4l2_format); + kfree(pv4l2_pix_format); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EFAULT; + } + kfree(pv4l2_format); + kfree(pv4l2_pix_format); + break; + } +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + case VIDIOC_TRY_FMT: + case VIDIOC_S_FMT: { + struct v4l2_format v4l2_format; + struct v4l2_pix_format v4l2_pix_format; + bool try; + int best_format; + + if (VIDIOC_TRY_FMT == cmd) { + JOM(8, "VIDIOC_TRY_FMT\n"); + try = true; + } else { + JOM(8, "VIDIOC_S_FMT\n"); + try = false; + } + + if (0 != copy_from_user(&v4l2_format, (void __user *)arg, + sizeof(struct v4l2_format))) { + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EFAULT; + } + + best_format = adjust_format(peasycap, + v4l2_format.fmt.pix.width, + v4l2_format.fmt.pix.height, + v4l2_format.fmt.pix.pixelformat, + v4l2_format.fmt.pix.field, + try); + if (0 > best_format) { + if (-EBUSY == best_format) { + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EBUSY; + } + JOM(8, "WARNING: adjust_format() returned %i\n", best_format); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -ENOENT; + } +/*...........................................................................*/ + memset(&v4l2_pix_format, 0, sizeof(struct v4l2_pix_format)); + v4l2_format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + memcpy(&(v4l2_format.fmt.pix), + &(easycap_format[best_format].v4l2_format.fmt.pix), + sizeof(v4l2_pix_format)); + JOM(8, "user is told: %s\n", &easycap_format[best_format].name[0]); + + if (copy_to_user((void __user *)arg, &v4l2_format, + sizeof(struct v4l2_format))) { + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EFAULT; + } + break; + } +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + case VIDIOC_CROPCAP: { + struct v4l2_cropcap v4l2_cropcap; + + JOM(8, "VIDIOC_CROPCAP\n"); + + if (0 != copy_from_user(&v4l2_cropcap, (void __user *)arg, + sizeof(struct v4l2_cropcap))) { + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EFAULT; + } + + if (v4l2_cropcap.type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + JOM(8, "v4l2_cropcap.type != V4L2_BUF_TYPE_VIDEO_CAPTURE\n"); + + memset(&v4l2_cropcap, 0, sizeof(struct v4l2_cropcap)); + v4l2_cropcap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + v4l2_cropcap.bounds.left = 0; + v4l2_cropcap.bounds.top = 0; + v4l2_cropcap.bounds.width = peasycap->width; + v4l2_cropcap.bounds.height = peasycap->height; + v4l2_cropcap.defrect.left = 0; + v4l2_cropcap.defrect.top = 0; + v4l2_cropcap.defrect.width = peasycap->width; + v4l2_cropcap.defrect.height = peasycap->height; + v4l2_cropcap.pixelaspect.numerator = 1; + v4l2_cropcap.pixelaspect.denominator = 1; + + JOM(8, "user is told: %ix%i\n", peasycap->width, peasycap->height); + + if (copy_to_user((void __user *)arg, &v4l2_cropcap, + sizeof(struct v4l2_cropcap))) { + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EFAULT; + } + break; + } +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + case VIDIOC_G_CROP: + case VIDIOC_S_CROP: { + JOM(8, "VIDIOC_G_CROP|VIDIOC_S_CROP unsupported\n"); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EINVAL; + } +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + case VIDIOC_QUERYSTD: { + JOM(8, "VIDIOC_QUERYSTD: " + "EasyCAP is incapable of detecting standard\n"); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EINVAL; + break; + } + /*-------------------------------------------------------------------*/ + /* + * THE MANIPULATIONS INVOLVING last0,last1,last2,last3 + * CONSTITUTE A WORKAROUND * FOR WHAT APPEARS TO BE + * A BUG IN 64-BIT mplayer. + * NOT NEEDED, BUT HOPEFULLY HARMLESS, FOR 32-BIT mplayer. + */ + /*------------------------------------------------------------------*/ + case VIDIOC_ENUMSTD: { + int last0 = -1, last1 = -1, last2 = -1, last3 = -1; + struct v4l2_standard v4l2_standard; + u32 index; + struct easycap_standard const *peasycap_standard; + + JOM(8, "VIDIOC_ENUMSTD\n"); + + if (0 != copy_from_user(&v4l2_standard, (void __user *)arg, + sizeof(struct v4l2_standard))) { + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EFAULT; + } + index = v4l2_standard.index; + + last3 = last2; + last2 = last1; + last1 = last0; + last0 = index; + if ((index == last3) && (index == last2) && + (index == last1) && (index == last0)) { + index++; + last3 = last2; + last2 = last1; + last1 = last0; + last0 = index; + } + + memset(&v4l2_standard, 0, sizeof(struct v4l2_standard)); + + peasycap_standard = &easycap_standard[0]; + while (0xFFFF != peasycap_standard->mask) { + if ((int)(peasycap_standard - &easycap_standard[0]) == index) + break; + peasycap_standard++; + } + if (0xFFFF == peasycap_standard->mask) { + JOM(8, "%i=index: exhausts standards\n", index); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EINVAL; + } + JOM(8, "%i=index: %s\n", index, + &(peasycap_standard->v4l2_standard.name[0])); + memcpy(&v4l2_standard, &(peasycap_standard->v4l2_standard), + sizeof(struct v4l2_standard)); + + v4l2_standard.index = index; + + if (copy_to_user((void __user *)arg, &v4l2_standard, + sizeof(struct v4l2_standard))) { + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EFAULT; + } + break; + } +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + case VIDIOC_G_STD: { + v4l2_std_id std_id; + struct easycap_standard const *peasycap_standard; + + JOM(8, "VIDIOC_G_STD\n"); + + if (0 > peasycap->standard_offset) { + JOM(8, "%i=peasycap->standard_offset\n", + peasycap->standard_offset); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EBUSY; + } + + if (0 != copy_from_user(&std_id, (void __user *)arg, + sizeof(v4l2_std_id))) { + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EFAULT; + } + + peasycap_standard = &easycap_standard[peasycap->standard_offset]; + std_id = peasycap_standard->v4l2_standard.id; + + JOM(8, "user is told: %s\n", + &peasycap_standard->v4l2_standard.name[0]); + + if (copy_to_user((void __user *)arg, &std_id, + sizeof(v4l2_std_id))) { + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EFAULT; + } + break; + } +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + case VIDIOC_S_STD: { + v4l2_std_id std_id; + int rc; + + JOM(8, "VIDIOC_S_STD\n"); + + if (0 != copy_from_user(&std_id, (void __user *)arg, + sizeof(v4l2_std_id))) { + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EFAULT; + } + + JOM(8, "User requests standard: 0x%08X%08X\n", + (int)((std_id & (((v4l2_std_id)0xFFFFFFFF) << 32)) >> 32), + (int)(std_id & ((v4l2_std_id)0xFFFFFFFF))); + + rc = adjust_standard(peasycap, std_id); + if (0 > rc) { + JOM(8, "WARNING: adjust_standard() returned %i\n", rc); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -ENOENT; + } + break; + } +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + case VIDIOC_REQBUFS: { + int nbuffers; + struct v4l2_requestbuffers v4l2_requestbuffers; + + JOM(8, "VIDIOC_REQBUFS\n"); + + if (0 != copy_from_user(&v4l2_requestbuffers, + (void __user *)arg, + sizeof(struct v4l2_requestbuffers))) { + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EFAULT; + } + + if (v4l2_requestbuffers.type != V4L2_BUF_TYPE_VIDEO_CAPTURE) { + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EINVAL; + } + if (v4l2_requestbuffers.memory != V4L2_MEMORY_MMAP) { + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EINVAL; + } + nbuffers = v4l2_requestbuffers.count; + JOM(8, " User requests %i buffers ...\n", nbuffers); + if (nbuffers < 2) + nbuffers = 2; + if (nbuffers > FRAME_BUFFER_MANY) + nbuffers = FRAME_BUFFER_MANY; + if (v4l2_requestbuffers.count == nbuffers) { + JOM(8, " ... agree to %i buffers\n", + nbuffers); + } else { + JOM(8, " ... insist on %i buffers\n", + nbuffers); + v4l2_requestbuffers.count = nbuffers; + } + peasycap->frame_buffer_many = nbuffers; + + if (copy_to_user((void __user *)arg, &v4l2_requestbuffers, + sizeof(struct v4l2_requestbuffers))) { + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EFAULT; + } + break; + } +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + case VIDIOC_QUERYBUF: { + u32 index; + struct v4l2_buffer v4l2_buffer; + + JOM(8, "VIDIOC_QUERYBUF\n"); + + if (peasycap->video_eof) { + JOM(8, "returning -EIO because %i=video_eof\n", + peasycap->video_eof); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EIO; + } + + if (0 != copy_from_user(&v4l2_buffer, (void __user *)arg, + sizeof(struct v4l2_buffer))) { + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EFAULT; + } + + if (v4l2_buffer.type != V4L2_BUF_TYPE_VIDEO_CAPTURE) { + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EINVAL; + } + index = v4l2_buffer.index; + if (index < 0 || index >= peasycap->frame_buffer_many) + return -EINVAL; + memset(&v4l2_buffer, 0, sizeof(struct v4l2_buffer)); + v4l2_buffer.index = index; + v4l2_buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + v4l2_buffer.bytesused = peasycap->frame_buffer_used; + v4l2_buffer.flags = V4L2_BUF_FLAG_MAPPED | + peasycap->done[index] | + peasycap->queued[index]; + v4l2_buffer.field = V4L2_FIELD_NONE; + v4l2_buffer.memory = V4L2_MEMORY_MMAP; + v4l2_buffer.m.offset = index * FRAME_BUFFER_SIZE; + v4l2_buffer.length = FRAME_BUFFER_SIZE; + + JOM(16, " %10i=index\n", v4l2_buffer.index); + JOM(16, " 0x%08X=type\n", v4l2_buffer.type); + JOM(16, " %10i=bytesused\n", v4l2_buffer.bytesused); + JOM(16, " 0x%08X=flags\n", v4l2_buffer.flags); + JOM(16, " %10i=field\n", v4l2_buffer.field); + JOM(16, " %10li=timestamp.tv_usec\n", + (long)v4l2_buffer.timestamp.tv_usec); + JOM(16, " %10i=sequence\n", v4l2_buffer.sequence); + JOM(16, " 0x%08X=memory\n", v4l2_buffer.memory); + JOM(16, " %10i=m.offset\n", v4l2_buffer.m.offset); + JOM(16, " %10i=length\n", v4l2_buffer.length); + + if (copy_to_user((void __user *)arg, &v4l2_buffer, + sizeof(struct v4l2_buffer))) { + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EFAULT; + } + break; + } +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + case VIDIOC_QBUF: { + struct v4l2_buffer v4l2_buffer; + + JOM(8, "VIDIOC_QBUF\n"); + + if (0 != copy_from_user(&v4l2_buffer, (void __user *)arg, + sizeof(struct v4l2_buffer))) { + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EFAULT; + } + + if (v4l2_buffer.type != V4L2_BUF_TYPE_VIDEO_CAPTURE) { + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EINVAL; + } + if (v4l2_buffer.memory != V4L2_MEMORY_MMAP) { + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EINVAL; + } + if (v4l2_buffer.index < 0 || + v4l2_buffer.index >= peasycap->frame_buffer_many) { + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EINVAL; + } + v4l2_buffer.flags = V4L2_BUF_FLAG_MAPPED | V4L2_BUF_FLAG_QUEUED; + + peasycap->done[v4l2_buffer.index] = 0; + peasycap->queued[v4l2_buffer.index] = V4L2_BUF_FLAG_QUEUED; + + if (copy_to_user((void __user *)arg, &v4l2_buffer, + sizeof(struct v4l2_buffer))) { + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EFAULT; + } + + JOM(8, "..... user queueing frame buffer %i\n", + (int)v4l2_buffer.index); + + peasycap->frame_lock = 0; + + break; + } +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + case VIDIOC_DQBUF: + { + struct timeval timeval, timeval2; + int i, j; + struct v4l2_buffer v4l2_buffer; + int rcdq; + u16 input; + + JOM(8, "VIDIOC_DQBUF\n"); + + if ((peasycap->video_idle) || (peasycap->video_eof)) { + JOM(8, "returning -EIO because " + "%i=video_idle %i=video_eof\n", + peasycap->video_idle, peasycap->video_eof); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EIO; + } + + if (copy_from_user(&v4l2_buffer, (void __user *)arg, + sizeof(struct v4l2_buffer))) { + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EFAULT; + } + + if (v4l2_buffer.type != V4L2_BUF_TYPE_VIDEO_CAPTURE) { + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EINVAL; + } + + if (peasycap->offerfields) { + /*---------------------------------------------------*/ + /* + * IN ITS 50 "fps" MODE tvtime SEEMS ALWAYS TO REQUEST + * V4L2_FIELD_BOTTOM + */ + /*---------------------------------------------------*/ + if (V4L2_FIELD_TOP == v4l2_buffer.field) + JOM(8, "user wants V4L2_FIELD_TOP\n"); + else if (V4L2_FIELD_BOTTOM == v4l2_buffer.field) + JOM(8, "user wants V4L2_FIELD_BOTTOM\n"); + else if (V4L2_FIELD_ANY == v4l2_buffer.field) + JOM(8, "user wants V4L2_FIELD_ANY\n"); + else + JOM(8, "user wants V4L2_FIELD_...UNKNOWN: %i\n", + v4l2_buffer.field); + } + + if (!peasycap->video_isoc_streaming) { + JOM(16, "returning -EIO because video urbs not streaming\n"); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EIO; + } + /*-------------------------------------------------------------------*/ + /* + * IF THE USER HAS PREVIOUSLY CALLED easycap_poll(), + * AS DETERMINED BY FINDING + * THE FLAG peasycap->polled SET, THERE MUST BE + * NO FURTHER WAIT HERE. IN THIS + * CASE, JUST CHOOSE THE FRAME INDICATED BY peasycap->frame_read + */ + /*-------------------------------------------------------------------*/ + + if (!peasycap->polled) { + do { + rcdq = easycap_dqbuf(peasycap, 0); + if (-EIO == rcdq) { + JOM(8, "returning -EIO because " + "dqbuf() returned -EIO\n"); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EIO; + } + } while (0 != rcdq); + } else { + if (peasycap->video_eof) { + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EIO; + } + } + if (V4L2_BUF_FLAG_DONE != peasycap->done[peasycap->frame_read]) { + JOM(8, "V4L2_BUF_FLAG_DONE != 0x%08X\n", + peasycap->done[peasycap->frame_read]); + } + peasycap->polled = 0; + + if (!(peasycap->isequence % 10)) { + for (i = 0; i < 179; i++) + peasycap->merit[i] = peasycap->merit[i+1]; + peasycap->merit[179] = merit_saa(peasycap->pusb_device); + j = 0; + for (i = 0; i < 180; i++) + j += peasycap->merit[i]; + if (90 < j) { + SAM("easycap driver shutting down " + "on condition blue\n"); + peasycap->video_eof = 1; + peasycap->audio_eof = 1; + } + } + + v4l2_buffer.index = peasycap->frame_read; + v4l2_buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + v4l2_buffer.bytesused = peasycap->frame_buffer_used; + v4l2_buffer.flags = V4L2_BUF_FLAG_MAPPED | V4L2_BUF_FLAG_DONE; + if (peasycap->offerfields) + v4l2_buffer.field = V4L2_FIELD_BOTTOM; + else + v4l2_buffer.field = V4L2_FIELD_NONE; + do_gettimeofday(&timeval); + timeval2 = timeval; + + v4l2_buffer.timestamp = timeval2; + v4l2_buffer.sequence = peasycap->isequence++; + v4l2_buffer.memory = V4L2_MEMORY_MMAP; + v4l2_buffer.m.offset = v4l2_buffer.index * FRAME_BUFFER_SIZE; + v4l2_buffer.length = FRAME_BUFFER_SIZE; + + JOM(16, " %10i=index\n", v4l2_buffer.index); + JOM(16, " 0x%08X=type\n", v4l2_buffer.type); + JOM(16, " %10i=bytesused\n", v4l2_buffer.bytesused); + JOM(16, " 0x%08X=flags\n", v4l2_buffer.flags); + JOM(16, " %10i=field\n", v4l2_buffer.field); + JOM(16, " %10li=timestamp.tv_sec\n", + (long)v4l2_buffer.timestamp.tv_sec); + JOM(16, " %10li=timestamp.tv_usec\n", + (long)v4l2_buffer.timestamp.tv_usec); + JOM(16, " %10i=sequence\n", v4l2_buffer.sequence); + JOM(16, " 0x%08X=memory\n", v4l2_buffer.memory); + JOM(16, " %10i=m.offset\n", v4l2_buffer.m.offset); + JOM(16, " %10i=length\n", v4l2_buffer.length); + + if (copy_to_user((void __user *)arg, &v4l2_buffer, + sizeof(struct v4l2_buffer))) { + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EFAULT; + } + + input = peasycap->frame_buffer[peasycap->frame_read][0].input; + if (0x08 & input) { + JOM(8, "user is offered frame buffer %i, input %i\n", + peasycap->frame_read, (0x07 & input)); + } else { + JOM(8, "user is offered frame buffer %i\n", + peasycap->frame_read); + } + peasycap->frame_lock = 1; + JOM(8, "%i=peasycap->frame_fill\n", peasycap->frame_fill); + if (peasycap->frame_read == peasycap->frame_fill) { + if (peasycap->frame_lock) { + JOM(8, "WORRY: filling frame buffer " + "while offered to user\n"); + } + } + break; + } +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + case VIDIOC_STREAMON: { + int i; + + JOM(8, "VIDIOC_STREAMON\n"); + + peasycap->isequence = 0; + for (i = 0; i < 180; i++) + peasycap->merit[i] = 0; + if (!peasycap->pusb_device) { + SAM("ERROR: peasycap->pusb_device is NULL\n"); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EFAULT; + } + submit_video_urbs(peasycap); + peasycap->video_idle = 0; + peasycap->audio_idle = 0; + peasycap->video_eof = 0; + peasycap->audio_eof = 0; + break; + } +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + case VIDIOC_STREAMOFF: { + JOM(8, "VIDIOC_STREAMOFF\n"); + + if (!peasycap->pusb_device) { + SAM("ERROR: peasycap->pusb_device is NULL\n"); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EFAULT; + } + + peasycap->video_idle = 1; + peasycap->audio_idle = 1; +/*---------------------------------------------------------------------------*/ +/* + * IF THE WAIT QUEUES ARE NOT CLEARED IN RESPONSE TO THE STREAMOFF COMMAND + * THE USERSPACE PROGRAM, E.G. mplayer, MAY HANG ON EXIT. BEWARE. + */ +/*---------------------------------------------------------------------------*/ + JOM(8, "calling wake_up on wq_video and wq_audio\n"); + wake_up_interruptible(&(peasycap->wq_video)); + if (peasycap->psubstream) + snd_pcm_period_elapsed(peasycap->psubstream); + break; + } +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + case VIDIOC_G_PARM: { + struct v4l2_streamparm *pv4l2_streamparm; + + JOM(8, "VIDIOC_G_PARM\n"); + pv4l2_streamparm = memdup_user((void __user *)arg, + sizeof(struct v4l2_streamparm)); + if (IS_ERR(pv4l2_streamparm)) { + SAM("ERROR: copy from user failed\n"); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return PTR_ERR(pv4l2_streamparm); + } + + if (pv4l2_streamparm->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) { + kfree(pv4l2_streamparm); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EINVAL; + } + pv4l2_streamparm->parm.capture.capability = 0; + pv4l2_streamparm->parm.capture.capturemode = 0; + pv4l2_streamparm->parm.capture.timeperframe.numerator = 1; + + if (peasycap->fps) { + pv4l2_streamparm->parm.capture.timeperframe. + denominator = peasycap->fps; + } else { + if (peasycap->ntsc) { + pv4l2_streamparm->parm.capture.timeperframe. + denominator = 30; + } else { + pv4l2_streamparm->parm.capture.timeperframe. + denominator = 25; + } + } + + pv4l2_streamparm->parm.capture.readbuffers = + peasycap->frame_buffer_many; + pv4l2_streamparm->parm.capture.extendedmode = 0; + if (copy_to_user((void __user *)arg, + pv4l2_streamparm, + sizeof(struct v4l2_streamparm))) { + kfree(pv4l2_streamparm); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EFAULT; + } + kfree(pv4l2_streamparm); + break; + } +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + case VIDIOC_S_PARM: { + JOM(8, "VIDIOC_S_PARM unsupported\n"); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EINVAL; + } +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + case VIDIOC_G_AUDIO: { + JOM(8, "VIDIOC_G_AUDIO unsupported\n"); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EINVAL; + } +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + case VIDIOC_S_AUDIO: { + JOM(8, "VIDIOC_S_AUDIO unsupported\n"); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EINVAL; + } +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + case VIDIOC_S_TUNER: { + JOM(8, "VIDIOC_S_TUNER unsupported\n"); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EINVAL; + } +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + case VIDIOC_G_FBUF: + case VIDIOC_S_FBUF: + case VIDIOC_OVERLAY: { + JOM(8, "VIDIOC_G_FBUF|VIDIOC_S_FBUF|VIDIOC_OVERLAY unsupported\n"); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EINVAL; + } +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + case VIDIOC_G_TUNER: { + JOM(8, "VIDIOC_G_TUNER unsupported\n"); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EINVAL; + } + case VIDIOC_G_FREQUENCY: + case VIDIOC_S_FREQUENCY: { + JOM(8, "VIDIOC_G_FREQUENCY|VIDIOC_S_FREQUENCY unsupported\n"); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EINVAL; + } +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + default: { + JOM(8, "ERROR: unrecognized V4L2 IOCTL command: 0x%08X\n", cmd); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -ENOIOCTLCMD; + } + } + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + JOM(4, "unlocked easycapdc60_dongle[%i].mutex_video\n", kd); + return 0; +} +/*****************************************************************************/ diff --git a/drivers/staging/media/easycap/easycap_low.c b/drivers/staging/media/easycap/easycap_low.c new file mode 100644 index 0000000..0385735 --- /dev/null +++ b/drivers/staging/media/easycap/easycap_low.c @@ -0,0 +1,1129 @@ +/***************************************************************************** +* * +* * +* easycap_low.c * +* * +* * +*****************************************************************************/ +/* + * + * Copyright (C) 2010 R.M. Thomas <rmthomas@sciolus.org> + * + * + * This 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. + * + * The software 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 software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * +*/ +/*****************************************************************************/ +/* + * ACKNOWLEGEMENTS AND REFERENCES + * ------------------------------ + * This driver makes use of register information contained in the Syntek + * Semicon DC-1125 driver hosted at + * http://sourceforge.net/projects/syntekdriver/. + * Particularly useful has been a patch to the latter driver provided by + * Ivor Hewitt in January 2009. The NTSC implementation is taken from the + * work of Ben Trask. +*/ +/****************************************************************************/ + +#include "easycap.h" + +#define GET(X, Y, Z) do { \ + int __rc; \ + *(Z) = (u16)0; \ + __rc = regget(X, Y, Z, sizeof(u8)); \ + if (0 > __rc) { \ + JOT(8, ":-(%i\n", __LINE__); return __rc; \ + } \ +} while (0) + +#define SET(X, Y, Z) do { \ + int __rc; \ + __rc = regset(X, Y, Z); \ + if (0 > __rc) { \ + JOT(8, ":-(%i\n", __LINE__); return __rc; \ + } \ +} while (0) + +/*--------------------------------------------------------------------------*/ +static const struct stk1160config { + int reg; + int set; +} stk1160configPAL[256] = { + {0x000, 0x0098}, + {0x002, 0x0093}, + + {0x001, 0x0003}, + {0x003, 0x0080}, + {0x00D, 0x0000}, + {0x00F, 0x0002}, + {0x018, 0x0010}, + {0x019, 0x0000}, + {0x01A, 0x0014}, + {0x01B, 0x000E}, + {0x01C, 0x0046}, + + {0x100, 0x0033}, + {0x103, 0x0000}, + {0x104, 0x0000}, + {0x105, 0x0000}, + {0x106, 0x0000}, + +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ +/* + * RESOLUTION 640x480 +*/ +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + {0x110, 0x0008}, + {0x111, 0x0000}, + {0x112, 0x0020}, + {0x113, 0x0000}, + {0x114, 0x0508}, + {0x115, 0x0005}, + {0x116, 0x0110}, + {0x117, 0x0001}, +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + + {0x202, 0x000F}, + {0x203, 0x004A}, + {0x2FF, 0x0000}, + + {0xFFF, 0xFFFF} +}; +/*--------------------------------------------------------------------------*/ +static const struct stk1160config stk1160configNTSC[256] = { + {0x000, 0x0098}, + {0x002, 0x0093}, + + {0x001, 0x0003}, + {0x003, 0x0080}, + {0x00D, 0x0000}, + {0x00F, 0x0002}, + {0x018, 0x0010}, + {0x019, 0x0000}, + {0x01A, 0x0014}, + {0x01B, 0x000E}, + {0x01C, 0x0046}, + + {0x100, 0x0033}, + {0x103, 0x0000}, + {0x104, 0x0000}, + {0x105, 0x0000}, + {0x106, 0x0000}, + +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ +/* + * RESOLUTION 640x480 +*/ +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + {0x110, 0x0008}, + {0x111, 0x0000}, + {0x112, 0x0003}, + {0x113, 0x0000}, + {0x114, 0x0508}, + {0x115, 0x0005}, + {0x116, 0x00F3}, + {0x117, 0x0000}, +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + + {0x202, 0x000F}, + {0x203, 0x004A}, + {0x2FF, 0x0000}, + + {0xFFF, 0xFFFF} +}; +/*--------------------------------------------------------------------------*/ +static const struct saa7113config { + int reg; + int set; +} saa7113configPAL[256] = { + {0x01, 0x08}, + {0x02, 0x80}, + {0x03, 0x33}, + {0x04, 0x00}, + {0x05, 0x00}, + {0x06, 0xE9}, + {0x07, 0x0D}, + {0x08, 0x38}, + {0x09, 0x00}, + {0x0A, SAA_0A_DEFAULT}, + {0x0B, SAA_0B_DEFAULT}, + {0x0C, SAA_0C_DEFAULT}, + {0x0D, SAA_0D_DEFAULT}, + {0x0E, 0x01}, + {0x0F, 0x36}, + {0x10, 0x00}, + {0x11, 0x0C}, + {0x12, 0xE7}, + {0x13, 0x00}, + {0x15, 0x00}, + {0x16, 0x00}, + {0x40, 0x02}, + {0x41, 0xFF}, + {0x42, 0xFF}, + {0x43, 0xFF}, + {0x44, 0xFF}, + {0x45, 0xFF}, + {0x46, 0xFF}, + {0x47, 0xFF}, + {0x48, 0xFF}, + {0x49, 0xFF}, + {0x4A, 0xFF}, + {0x4B, 0xFF}, + {0x4C, 0xFF}, + {0x4D, 0xFF}, + {0x4E, 0xFF}, + {0x4F, 0xFF}, + {0x50, 0xFF}, + {0x51, 0xFF}, + {0x52, 0xFF}, + {0x53, 0xFF}, + {0x54, 0xFF}, + {0x55, 0xFF}, + {0x56, 0xFF}, + {0x57, 0xFF}, + {0x58, 0x40}, + {0x59, 0x54}, + {0x5A, 0x07}, + {0x5B, 0x83}, + + {0xFF, 0xFF} +}; +/*--------------------------------------------------------------------------*/ +static const struct saa7113config saa7113configNTSC[256] = { + {0x01, 0x08}, + {0x02, 0x80}, + {0x03, 0x33}, + {0x04, 0x00}, + {0x05, 0x00}, + {0x06, 0xE9}, + {0x07, 0x0D}, + {0x08, 0x78}, + {0x09, 0x00}, + {0x0A, SAA_0A_DEFAULT}, + {0x0B, SAA_0B_DEFAULT}, + {0x0C, SAA_0C_DEFAULT}, + {0x0D, SAA_0D_DEFAULT}, + {0x0E, 0x01}, + {0x0F, 0x36}, + {0x10, 0x00}, + {0x11, 0x0C}, + {0x12, 0xE7}, + {0x13, 0x00}, + {0x15, 0x00}, + {0x16, 0x00}, + {0x40, 0x82}, + {0x41, 0xFF}, + {0x42, 0xFF}, + {0x43, 0xFF}, + {0x44, 0xFF}, + {0x45, 0xFF}, + {0x46, 0xFF}, + {0x47, 0xFF}, + {0x48, 0xFF}, + {0x49, 0xFF}, + {0x4A, 0xFF}, + {0x4B, 0xFF}, + {0x4C, 0xFF}, + {0x4D, 0xFF}, + {0x4E, 0xFF}, + {0x4F, 0xFF}, + {0x50, 0xFF}, + {0x51, 0xFF}, + {0x52, 0xFF}, + {0x53, 0xFF}, + {0x54, 0xFF}, + {0x55, 0xFF}, + {0x56, 0xFF}, + {0x57, 0xFF}, + {0x58, 0x40}, + {0x59, 0x54}, + {0x5A, 0x0A}, + {0x5B, 0x83}, + + {0xFF, 0xFF} +}; + +static int regget(struct usb_device *pusb_device, + u16 index, void *reg, int reg_size) +{ + int rc; + + if (!pusb_device) + return -ENODEV; + + rc = usb_control_msg(pusb_device, usb_rcvctrlpipe(pusb_device, 0), + 0x00, + (USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE), + 0x00, + index, reg, reg_size, 50000); + + return rc; +} + +static int regset(struct usb_device *pusb_device, u16 index, u16 value) +{ + int rc; + + if (!pusb_device) + return -ENODEV; + + rc = usb_control_msg(pusb_device, usb_sndctrlpipe(pusb_device, 0), + 0x01, + (USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE), + value, index, NULL, 0, 500); + + if (rc < 0) + return rc; + + if (easycap_readback) { + u16 igot = 0; + rc = regget(pusb_device, index, &igot, sizeof(igot)); + igot = 0xFF & igot; + switch (index) { + case 0x000: + case 0x500: + case 0x502: + case 0x503: + case 0x504: + case 0x506: + case 0x507: + break; + + case 0x204: + case 0x205: + case 0x350: + case 0x351: + if (igot) + JOT(8, "unexpected 0x%02X " + "for STK register 0x%03X\n", + igot, index); + break; + + default: + if ((0xFF & value) != igot) + JOT(8, "unexpected 0x%02X != 0x%02X " + "for STK register 0x%03X\n", + igot, value, index); + break; + } + } + + return rc; +} +/*--------------------------------------------------------------------------*/ +/* + * FUNCTION wait_i2c() RETURNS 0 ON SUCCESS +*/ +/*--------------------------------------------------------------------------*/ +static int wait_i2c(struct usb_device *p) +{ + u16 get0; + u8 igot; + const int max = 2; + int k; + + if (!p) + return -ENODEV; + + for (k = 0; k < max; k++) { + GET(p, 0x0201, &igot); get0 = igot; + switch (get0) { + case 0x04: + case 0x01: + return 0; + case 0x00: + msleep(20); + continue; + default: + return get0 - 1; + } + } + return -1; +} + +/****************************************************************************/ +int confirm_resolution(struct usb_device *p) +{ + u8 get0, get1, get2, get3, get4, get5, get6, get7; + + if (!p) + return -ENODEV; + GET(p, 0x0110, &get0); + GET(p, 0x0111, &get1); + GET(p, 0x0112, &get2); + GET(p, 0x0113, &get3); + GET(p, 0x0114, &get4); + GET(p, 0x0115, &get5); + GET(p, 0x0116, &get6); + GET(p, 0x0117, &get7); + JOT(8, "0x%03X, 0x%03X, " + "0x%03X, 0x%03X, " + "0x%03X, 0x%03X, " + "0x%03X, 0x%03X\n", + get0, get1, get2, get3, get4, get5, get6, get7); + JOT(8, "....cf PAL_720x526: " + "0x%03X, 0x%03X, " + "0x%03X, 0x%03X, " + "0x%03X, 0x%03X, " + "0x%03X, 0x%03X\n", + 0x000, 0x000, 0x001, 0x000, 0x5A0, 0x005, 0x121, 0x001); + JOT(8, "....cf PAL_704x526: " + "0x%03X, 0x%03X, " + "0x%03X, 0x%03X, " + "0x%03X, 0x%03X, " + "0x%03X, 0x%03X\n", + 0x004, 0x000, 0x001, 0x000, 0x584, 0x005, 0x121, 0x001); + JOT(8, "....cf VGA_640x480: " + "0x%03X, 0x%03X, " + "0x%03X, 0x%03X, " + "0x%03X, 0x%03X, " + "0x%03X, 0x%03X\n", + 0x008, 0x000, 0x020, 0x000, 0x508, 0x005, 0x110, 0x001); + return 0; +} +/****************************************************************************/ +int confirm_stream(struct usb_device *p) +{ + u16 get2; + u8 igot; + + if (!p) + return -ENODEV; + GET(p, 0x0100, &igot); get2 = 0x80 & igot; + if (0x80 == get2) + JOT(8, "confirm_stream: OK\n"); + else + JOT(8, "confirm_stream: STUCK\n"); + return 0; +} +/****************************************************************************/ +int setup_stk(struct usb_device *p, bool ntsc) +{ + int i; + const struct stk1160config *cfg; + if (!p) + return -ENODEV; + cfg = (ntsc) ? stk1160configNTSC : stk1160configPAL; + for (i = 0; cfg[i].reg != 0xFFF; i++) + SET(p, cfg[i].reg, cfg[i].set); + + write_300(p); + + return 0; +} +/****************************************************************************/ +int setup_saa(struct usb_device *p, bool ntsc) +{ + int i, ir; + const struct saa7113config *cfg; + if (!p) + return -ENODEV; + cfg = (ntsc) ? saa7113configNTSC : saa7113configPAL; + for (i = 0; cfg[i].reg != 0xFF; i++) + ir = write_saa(p, cfg[i].reg, cfg[i].set); + return 0; +} +/****************************************************************************/ +int write_000(struct usb_device *p, u16 set2, u16 set0) +{ + u8 igot0, igot2; + + if (!p) + return -ENODEV; + GET(p, 0x0002, &igot2); + GET(p, 0x0000, &igot0); + SET(p, 0x0002, set2); + SET(p, 0x0000, set0); + return 0; +} +/****************************************************************************/ +int write_saa(struct usb_device *p, u16 reg0, u16 set0) +{ + if (!p) + return -ENODEV; + SET(p, 0x200, 0x00); + SET(p, 0x204, reg0); + SET(p, 0x205, set0); + SET(p, 0x200, 0x01); + return wait_i2c(p); +} +/****************************************************************************/ +/*--------------------------------------------------------------------------*/ +/* + * REGISTER 500: SETTING VALUE TO 0x008B READS FROM VT1612A (?) + * REGISTER 500: SETTING VALUE TO 0x008C WRITES TO VT1612A + * REGISTER 502: LEAST SIGNIFICANT BYTE OF VALUE TO SET + * REGISTER 503: MOST SIGNIFICANT BYTE OF VALUE TO SET + * REGISTER 504: TARGET ADDRESS ON VT1612A + */ +/*--------------------------------------------------------------------------*/ +int +write_vt(struct usb_device *p, u16 reg0, u16 set0) +{ + u8 igot; + u16 got502, got503; + u16 set502, set503; + + if (!p) + return -ENODEV; + SET(p, 0x0504, reg0); + SET(p, 0x0500, 0x008B); + + GET(p, 0x0502, &igot); got502 = (0xFF & igot); + GET(p, 0x0503, &igot); got503 = (0xFF & igot); + + JOT(16, "write_vt(., 0x%04X, 0x%04X): was 0x%04X\n", + reg0, set0, ((got503 << 8) | got502)); + + set502 = (0x00FF & set0); + set503 = ((0xFF00 & set0) >> 8); + + SET(p, 0x0504, reg0); + SET(p, 0x0502, set502); + SET(p, 0x0503, set503); + SET(p, 0x0500, 0x008C); + + return 0; +} +/****************************************************************************/ +/*--------------------------------------------------------------------------*/ +/* + * REGISTER 500: SETTING VALUE TO 0x008B READS FROM VT1612A (?) + * REGISTER 500: SETTING VALUE TO 0x008C WRITES TO VT1612A + * REGISTER 502: LEAST SIGNIFICANT BYTE OF VALUE TO GET + * REGISTER 503: MOST SIGNIFICANT BYTE OF VALUE TO GET + * REGISTER 504: TARGET ADDRESS ON VT1612A + */ +/*--------------------------------------------------------------------------*/ +int read_vt(struct usb_device *p, u16 reg0) +{ + u8 igot; + u16 got502, got503; + + if (!p) + return -ENODEV; + SET(p, 0x0504, reg0); + SET(p, 0x0500, 0x008B); + + GET(p, 0x0502, &igot); got502 = (0xFF & igot); + GET(p, 0x0503, &igot); got503 = (0xFF & igot); + + JOT(16, "read_vt(., 0x%04X): has 0x%04X\n", + reg0, ((got503 << 8) | got502)); + + return (got503 << 8) | got502; +} +/****************************************************************************/ +/*--------------------------------------------------------------------------*/ +/* + * THESE APPEAR TO HAVE NO EFFECT ON EITHER VIDEO OR AUDIO. + */ +/*--------------------------------------------------------------------------*/ +int write_300(struct usb_device *p) +{ + if (!p) + return -ENODEV; + SET(p, 0x300, 0x0012); + SET(p, 0x350, 0x002D); + SET(p, 0x351, 0x0001); + SET(p, 0x352, 0x0000); + SET(p, 0x353, 0x0000); + SET(p, 0x300, 0x0080); + return 0; +} +/****************************************************************************/ +/*--------------------------------------------------------------------------*/ +/* + * NOTE: THE FOLLOWING IS NOT CHECKED: + * REGISTER 0x0F, WHICH IS INVOLVED IN CHROMINANCE AUTOMATIC GAIN CONTROL. + */ +/*--------------------------------------------------------------------------*/ +int check_saa(struct usb_device *p, bool ntsc) +{ + int i, ir, rc = 0; + struct saa7113config const *cfg; + if (!p) + return -ENODEV; + + cfg = (ntsc) ? saa7113configNTSC : saa7113configPAL; + for (i = 0; cfg[i].reg != 0xFF; i++) { + if (0x0F == cfg[i].reg) + continue; + ir = read_saa(p, cfg[i].reg); + if (ir != cfg[i].set) { + SAY("SAA register 0x%02X has 0x%02X, expected 0x%02X\n", + cfg[i].reg, ir, cfg[i].set); + rc--; + } + } + + return (rc < -8) ? rc : 0; +} +/****************************************************************************/ +int merit_saa(struct usb_device *p) +{ + int rc; + + if (!p) + return -ENODEV; + rc = read_saa(p, 0x1F); + return ((0 > rc) || (0x02 & rc)) ? 1 : 0; +} +/****************************************************************************/ +int ready_saa(struct usb_device *p) +{ + int j, rc, rate; + const int max = 5, marktime = PATIENCE/5; +/*--------------------------------------------------------------------------*/ +/* + * RETURNS 0 FOR INTERLACED 50 Hz + * 1 FOR NON-INTERLACED 50 Hz + * 2 FOR INTERLACED 60 Hz + * 3 FOR NON-INTERLACED 60 Hz +*/ +/*--------------------------------------------------------------------------*/ + if (!p) + return -ENODEV; + j = 0; + while (max > j) { + rc = read_saa(p, 0x1F); + if (0 <= rc) { + if (0 == (0x40 & rc)) + break; + if (1 == (0x01 & rc)) + break; + } + msleep(marktime); + j++; + } + if (max == j) + return -1; + else { + if (0x20 & rc) { + rate = 2; + JOT(8, "hardware detects 60 Hz\n"); + } else { + rate = 0; + JOT(8, "hardware detects 50 Hz\n"); + } + if (0x80 & rc) + JOT(8, "hardware detects interlacing\n"); + else { + rate++; + JOT(8, "hardware detects no interlacing\n"); + } + } + return 0; +} +/****************************************************************************/ +/*--------------------------------------------------------------------------*/ +/* + * NOTE: THE FOLLOWING ARE NOT CHECKED: + * REGISTERS 0x000, 0x002: FUNCTIONALITY IS NOT KNOWN + * REGISTER 0x100: ACCEPT ALSO (0x80 | stk1160config....[.].set) + */ +/*--------------------------------------------------------------------------*/ +int check_stk(struct usb_device *p, bool ntsc) +{ + int i, ir; + const struct stk1160config *cfg; + + if (!p) + return -ENODEV; + cfg = (ntsc) ? stk1160configNTSC : stk1160configPAL; + + for (i = 0; 0xFFF != cfg[i].reg; i++) { + if (0x000 == cfg[i].reg || 0x002 == cfg[i].reg) + continue; + + + ir = read_stk(p, cfg[i].reg); + if (0x100 == cfg[i].reg) { + if ((ir != (0xFF & cfg[i].set)) && + (ir != (0x80 | (0xFF & cfg[i].set))) && + (0xFFFF != cfg[i].set)) { + SAY("STK reg[0x%03X]=0x%02X expected 0x%02X\n", + cfg[i].reg, ir, cfg[i].set); + } + continue; + } + if ((ir != (0xFF & cfg[i].set)) && (0xFFFF != cfg[i].set)) + SAY("STK register 0x%03X has 0x%02X,expected 0x%02X\n", + cfg[i].reg, ir, cfg[i].set); + } + return 0; +} +/****************************************************************************/ +int read_saa(struct usb_device *p, u16 reg0) +{ + u8 igot; + + if (!p) + return -ENODEV; + SET(p, 0x208, reg0); + SET(p, 0x200, 0x20); + if (0 != wait_i2c(p)) + return -1; + igot = 0; + GET(p, 0x0209, &igot); + return igot; +} +/****************************************************************************/ +int read_stk(struct usb_device *p, u32 reg0) +{ + u8 igot; + + if (!p) + return -ENODEV; + igot = 0; + GET(p, reg0, &igot); + return igot; +} +/****************************************************************************/ +/*--------------------------------------------------------------------------*/ +/* + * HARDWARE USERSPACE INPUT NUMBER PHYSICAL INPUT DRIVER input VALUE + * + * CVBS+S-VIDEO 0 or 1 CVBS 1 + * FOUR-CVBS 0 or 1 CVBS1 1 + * FOUR-CVBS 2 CVBS2 2 + * FOUR-CVBS 3 CVBS3 3 + * FOUR-CVBS 4 CVBS4 4 + * CVBS+S-VIDEO 5 S-VIDEO 5 + * + * WHEN 5==input THE ARGUMENT mode MUST ALSO BE SUPPLIED: + * + * mode 7 => GAIN TO BE SET EXPLICITLY USING REGISTER 0x05 (UNTESTED) + * mode 9 => USE AUTOMATIC GAIN CONTROL (DEFAULT) + * +*/ +/*---------------------------------------------------------------------------*/ +int +select_input(struct usb_device *p, int input, int mode) +{ + int ir; + + if (!p) + return -ENODEV; + stop_100(p); + switch (input) { + case 0: + case 1: { + if (0 != write_saa(p, 0x02, 0x80)) + SAY("ERROR: failed to set SAA register 0x02 " + "for input %i\n", input); + + SET(p, 0x0000, 0x0098); + SET(p, 0x0002, 0x0078); + break; + } + case 2: { + if (0 != write_saa(p, 0x02, 0x80)) + SAY("ERROR: failed to set SAA register 0x02 " + "for input %i\n", input); + + SET(p, 0x0000, 0x0090); + SET(p, 0x0002, 0x0078); + break; + } + case 3: { + if (0 != write_saa(p, 0x02, 0x80)) + SAY("ERROR: failed to set SAA register 0x02 " + " for input %i\n", input); + + SET(p, 0x0000, 0x0088); + SET(p, 0x0002, 0x0078); + break; + } + case 4: { + if (0 != write_saa(p, 0x02, 0x80)) { + SAY("ERROR: failed to set SAA register 0x02 " + "for input %i\n", input); + } + SET(p, 0x0000, 0x0080); + SET(p, 0x0002, 0x0078); + break; + } + case 5: { + if (9 != mode) + mode = 7; + switch (mode) { + case 7: { + if (0 != write_saa(p, 0x02, 0x87)) + SAY("ERROR: failed to set SAA register 0x02 " + "for input %i\n", input); + + if (0 != write_saa(p, 0x05, 0xFF)) + SAY("ERROR: failed to set SAA register 0x05 " + "for input %i\n", input); + + break; + } + case 9: { + if (0 != write_saa(p, 0x02, 0x89)) + SAY("ERROR: failed to set SAA register 0x02 " + "for input %i\n", input); + + if (0 != write_saa(p, 0x05, 0x00)) + SAY("ERROR: failed to set SAA register 0x05 " + "for input %i\n", input); + + break; + } + default: + SAY("MISTAKE: bad mode: %i\n", mode); + return -1; + } + + if (0 != write_saa(p, 0x04, 0x00)) + SAY("ERROR: failed to set SAA register 0x04 " + "for input %i\n", input); + + if (0 != write_saa(p, 0x09, 0x80)) + SAY("ERROR: failed to set SAA register 0x09 " + "for input %i\n", input); + + SET(p, 0x0002, 0x0093); + break; + } + default: + SAY("ERROR: bad input: %i\n", input); + return -1; + } + + ir = read_stk(p, 0x00); + JOT(8, "STK register 0x00 has 0x%02X\n", ir); + ir = read_saa(p, 0x02); + JOT(8, "SAA register 0x02 has 0x%02X\n", ir); + + start_100(p); + + return 0; +} +/****************************************************************************/ +int set_resolution(struct usb_device *p, + u16 set0, u16 set1, u16 set2, u16 set3) +{ + u16 u0x0111, u0x0113, u0x0115, u0x0117; + + if (!p) + return -ENODEV; + u0x0111 = ((0xFF00 & set0) >> 8); + u0x0113 = ((0xFF00 & set1) >> 8); + u0x0115 = ((0xFF00 & set2) >> 8); + u0x0117 = ((0xFF00 & set3) >> 8); + + SET(p, 0x0110, (0x00FF & set0)); + SET(p, 0x0111, u0x0111); + SET(p, 0x0112, (0x00FF & set1)); + SET(p, 0x0113, u0x0113); + SET(p, 0x0114, (0x00FF & set2)); + SET(p, 0x0115, u0x0115); + SET(p, 0x0116, (0x00FF & set3)); + SET(p, 0x0117, u0x0117); + + return 0; +} +/****************************************************************************/ +int start_100(struct usb_device *p) +{ + u16 get116, get117, get0; + u8 igot116, igot117, igot; + + if (!p) + return -ENODEV; + GET(p, 0x0116, &igot116); + get116 = igot116; + GET(p, 0x0117, &igot117); + get117 = igot117; + SET(p, 0x0116, 0x0000); + SET(p, 0x0117, 0x0000); + + GET(p, 0x0100, &igot); + get0 = igot; + SET(p, 0x0100, (0x80 | get0)); + + SET(p, 0x0116, get116); + SET(p, 0x0117, get117); + + return 0; +} +/****************************************************************************/ +int stop_100(struct usb_device *p) +{ + u16 get0; + u8 igot; + + if (!p) + return -ENODEV; + GET(p, 0x0100, &igot); + get0 = igot; + SET(p, 0x0100, (0x7F & get0)); + return 0; +} +/****************************************************************************/ +/****************************************************************************/ +/*****************************************************************************/ +int wakeup_device(struct usb_device *pusb_device) +{ + if (!pusb_device) + return -ENODEV; + return usb_control_msg(pusb_device, usb_sndctrlpipe(pusb_device, 0), + USB_REQ_SET_FEATURE, + USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_DEVICE, + USB_DEVICE_REMOTE_WAKEUP, + 0, NULL, 0, 50000); +} +/*****************************************************************************/ +int +audio_setup(struct easycap *peasycap) +{ + struct usb_device *pusb_device; + u8 buffer[1]; + int rc, id1, id2; +/*---------------------------------------------------------------------------*/ +/* + * IMPORTANT: + * THE MESSAGE OF TYPE (USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) + * CAUSES MUTING IF THE VALUE 0x0100 IS SENT. + * TO ENABLE AUDIO THE VALUE 0x0200 MUST BE SENT. + */ +/*---------------------------------------------------------------------------*/ + const u8 request = 0x01; + const u8 requesttype = USB_DIR_OUT | + USB_TYPE_CLASS | + USB_RECIP_INTERFACE; + const u16 value_unmute = 0x0200; + const u16 index = 0x0301; + const u16 length = 1; + + if (!peasycap) + return -EFAULT; + + pusb_device = peasycap->pusb_device; + if (!pusb_device) + return -ENODEV; + + JOM(8, "%02X %02X %02X %02X %02X %02X %02X %02X\n", + requesttype, request, + (0x00FF & value_unmute), + (0xFF00 & value_unmute) >> 8, + (0x00FF & index), + (0xFF00 & index) >> 8, + (0x00FF & length), + (0xFF00 & length) >> 8); + + buffer[0] = 0x01; + + rc = usb_control_msg(pusb_device, usb_sndctrlpipe(pusb_device, 0), + request, requesttype, value_unmute, + index, &buffer[0], length, 50000); + + JOT(8, "0x%02X=buffer\n", buffer[0]); + if (rc != (int)length) { + switch (rc) { + case -EPIPE: + SAY("usb_control_msg returned -EPIPE\n"); + break; + default: + SAY("ERROR: usb_control_msg returned %i\n", rc); + break; + } + } +/*--------------------------------------------------------------------------*/ +/* + * REGISTER 500: SETTING VALUE TO 0x0094 RESETS AUDIO CONFIGURATION ??? + * REGISTER 506: ANALOGUE AUDIO ATTENTUATOR ??? + * FOR THE CVBS+S-VIDEO HARDWARE: + * SETTING VALUE TO 0x0000 GIVES QUIET SOUND. + * THE UPPER BYTE SEEMS TO HAVE NO EFFECT. + * FOR THE FOUR-CVBS HARDWARE: + * SETTING VALUE TO 0x0000 SEEMS TO HAVE NO EFFECT. + * REGISTER 507: ANALOGUE AUDIO PREAMPLIFIER ON/OFF ??? + * FOR THE CVBS-S-VIDEO HARDWARE: + * SETTING VALUE TO 0x0001 GIVES VERY LOUD, DISTORTED SOUND. + * THE UPPER BYTE SEEMS TO HAVE NO EFFECT. + */ +/*--------------------------------------------------------------------------*/ + SET(pusb_device, 0x0500, 0x0094); + SET(pusb_device, 0x0500, 0x008C); + SET(pusb_device, 0x0506, 0x0001); + SET(pusb_device, 0x0507, 0x0000); + id1 = read_vt(pusb_device, 0x007C); + id2 = read_vt(pusb_device, 0x007E); + SAM("0x%04X:0x%04X is audio vendor id\n", id1, id2); +/*---------------------------------------------------------------------------*/ +/* + * SELECT AUDIO SOURCE "LINE IN" AND SET THE AUDIO GAIN. +*/ +/*---------------------------------------------------------------------------*/ + if (0 != audio_gainset(pusb_device, peasycap->gain)) + SAY("ERROR: audio_gainset() failed\n"); + check_vt(pusb_device); + return 0; +} +/*****************************************************************************/ +int check_vt(struct usb_device *pusb_device) +{ + int igot; + + if (!pusb_device) + return -ENODEV; + igot = read_vt(pusb_device, 0x0002); + if (0 > igot) + SAY("ERROR: failed to read VT1612A register 0x02\n"); + if (0x8000 & igot) + SAY("register 0x%02X muted\n", 0x02); + + igot = read_vt(pusb_device, 0x000E); + if (0 > igot) + SAY("ERROR: failed to read VT1612A register 0x0E\n"); + if (0x8000 & igot) + SAY("register 0x%02X muted\n", 0x0E); + + igot = read_vt(pusb_device, 0x0010); + if (0 > igot) + SAY("ERROR: failed to read VT1612A register 0x10\n"); + if (0x8000 & igot) + SAY("register 0x%02X muted\n", 0x10); + + igot = read_vt(pusb_device, 0x0012); + if (0 > igot) + SAY("ERROR: failed to read VT1612A register 0x12\n"); + if (0x8000 & igot) + SAY("register 0x%02X muted\n", 0x12); + + igot = read_vt(pusb_device, 0x0014); + if (0 > igot) + SAY("ERROR: failed to read VT1612A register 0x14\n"); + if (0x8000 & igot) + SAY("register 0x%02X muted\n", 0x14); + + igot = read_vt(pusb_device, 0x0016); + if (0 > igot) + SAY("ERROR: failed to read VT1612A register 0x16\n"); + if (0x8000 & igot) + SAY("register 0x%02X muted\n", 0x16); + + igot = read_vt(pusb_device, 0x0018); + if (0 > igot) + SAY("ERROR: failed to read VT1612A register 0x18\n"); + if (0x8000 & igot) + SAY("register 0x%02X muted\n", 0x18); + + igot = read_vt(pusb_device, 0x001C); + if (0 > igot) + SAY("ERROR: failed to read VT1612A register 0x1C\n"); + if (0x8000 & igot) + SAY("register 0x%02X muted\n", 0x1C); + + return 0; +} +/*****************************************************************************/ +/*---------------------------------------------------------------------------*/ +/* NOTE: THIS DOES INCREASE THE VOLUME DRAMATICALLY: + * audio_gainset(pusb_device, 0x000F); + * + * loud dB register 0x10 dB register 0x1C dB total + * 0 -34.5 0 -34.5 + * .. .... . .... + * 15 10.5 0 10.5 + * 16 12.0 0 12.0 + * 17 12.0 1.5 13.5 + * .. .... .... .... + * 31 12.0 22.5 34.5 +*/ +/*---------------------------------------------------------------------------*/ +int audio_gainset(struct usb_device *pusb_device, s8 loud) +{ + int igot; + u8 tmp; + u16 mute; + + if (!pusb_device) + return -ENODEV; + if (0 > loud) + loud = 0; + if (31 < loud) + loud = 31; + + write_vt(pusb_device, 0x0002, 0x8000); +/*---------------------------------------------------------------------------*/ + igot = read_vt(pusb_device, 0x000E); + if (0 > igot) { + SAY("ERROR: failed to read VT1612A register 0x0E\n"); + mute = 0x0000; + } else + mute = 0x8000 & ((unsigned int)igot); + mute = 0; + + if (16 > loud) + tmp = 0x01 | (0x001F & (((u8)(15 - loud)) << 1)); + else + tmp = 0; + + JOT(8, "0x%04X=(mute|tmp) for VT1612A register 0x0E\n", mute | tmp); + write_vt(pusb_device, 0x000E, (mute | tmp)); +/*---------------------------------------------------------------------------*/ + igot = read_vt(pusb_device, 0x0010); + if (0 > igot) { + SAY("ERROR: failed to read VT1612A register 0x10\n"); + mute = 0x0000; + } else + mute = 0x8000 & ((unsigned int)igot); + mute = 0; + + JOT(8, "0x%04X=(mute|tmp|(tmp<<8)) for VT1612A register 0x10,...0x18\n", + mute | tmp | (tmp << 8)); + write_vt(pusb_device, 0x0010, (mute | tmp | (tmp << 8))); + write_vt(pusb_device, 0x0012, (mute | tmp | (tmp << 8))); + write_vt(pusb_device, 0x0014, (mute | tmp | (tmp << 8))); + write_vt(pusb_device, 0x0016, (mute | tmp | (tmp << 8))); + write_vt(pusb_device, 0x0018, (mute | tmp | (tmp << 8))); +/*---------------------------------------------------------------------------*/ + igot = read_vt(pusb_device, 0x001C); + if (0 > igot) { + SAY("ERROR: failed to read VT1612A register 0x1C\n"); + mute = 0x0000; + } else + mute = 0x8000 & ((unsigned int)igot); + mute = 0; + + if (16 <= loud) + tmp = 0x000F & (u8)(loud - 16); + else + tmp = 0; + + JOT(8, "0x%04X=(mute|tmp|(tmp<<8)) for VT1612A register 0x1C\n", + mute | tmp | (tmp << 8)); + write_vt(pusb_device, 0x001C, (mute | tmp | (tmp << 8))); + write_vt(pusb_device, 0x001A, 0x0404); + write_vt(pusb_device, 0x0002, 0x0000); + return 0; +} +/*****************************************************************************/ +int audio_gainget(struct usb_device *pusb_device) +{ + int igot; + + if (!pusb_device) + return -ENODEV; + igot = read_vt(pusb_device, 0x001C); + if (0 > igot) + SAY("ERROR: failed to read VT1612A register 0x1C\n"); + return igot; +} +/*****************************************************************************/ diff --git a/drivers/staging/media/easycap/easycap_main.c b/drivers/staging/media/easycap/easycap_main.c new file mode 100644 index 0000000..a45c0b5 --- /dev/null +++ b/drivers/staging/media/easycap/easycap_main.c @@ -0,0 +1,4253 @@ +/****************************************************************************** +* * +* easycap_main.c * +* * +* Video driver for EasyCAP USB2.0 Video Capture Device DC60 * +* * +* * +******************************************************************************/ +/* + * + * Copyright (C) 2010 R.M. Thomas <rmthomas@sciolus.org> + * + * + * This 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. + * + * The software 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 software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * +*/ +/*****************************************************************************/ + +#include "easycap.h" +#include <linux/usb/audio.h> + + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("R.M. Thomas <rmthomas@sciolus.org>"); +MODULE_DESCRIPTION(EASYCAP_DRIVER_DESCRIPTION); +MODULE_VERSION(EASYCAP_DRIVER_VERSION); + +#ifdef CONFIG_EASYCAP_DEBUG +int easycap_debug; +module_param_named(debug, easycap_debug, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(debug, "Debug level: 0(default),1,2,...,9"); +#endif /* CONFIG_EASYCAP_DEBUG */ + +bool easycap_readback; +module_param_named(readback, easycap_readback, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(readback, "read back written registers: (default false)"); + +static int easycap_bars = 1; +module_param_named(bars, easycap_bars, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(bars, + "Testcard bars on input signal failure: 0=>no, 1=>yes(default)"); + +static int easycap_gain = 16; +module_param_named(gain, easycap_gain, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(gain, "Audio gain: 0,...,16(default),...31"); + +static bool easycap_ntsc; +module_param_named(ntsc, easycap_ntsc, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(ntsc, "NTSC default encoding (default PAL)"); + + + +struct easycap_dongle easycapdc60_dongle[DONGLE_MANY]; +static struct mutex mutex_dongle; +static void easycap_complete(struct urb *purb); +static int reset(struct easycap *peasycap); + +const char *strerror(int err) +{ +#define ERRNOSTR(_e) case _e: return # _e + switch (err) { + case 0: return "OK"; + ERRNOSTR(ENOMEM); + ERRNOSTR(ENODEV); + ERRNOSTR(ENXIO); + ERRNOSTR(EINVAL); + ERRNOSTR(EAGAIN); + ERRNOSTR(EFBIG); + ERRNOSTR(EPIPE); + ERRNOSTR(EMSGSIZE); + ERRNOSTR(ENOSPC); + ERRNOSTR(EINPROGRESS); + ERRNOSTR(ENOSR); + ERRNOSTR(EOVERFLOW); + ERRNOSTR(EPROTO); + ERRNOSTR(EILSEQ); + ERRNOSTR(ETIMEDOUT); + ERRNOSTR(EOPNOTSUPP); + ERRNOSTR(EPFNOSUPPORT); + ERRNOSTR(EAFNOSUPPORT); + ERRNOSTR(EADDRINUSE); + ERRNOSTR(EADDRNOTAVAIL); + ERRNOSTR(ENOBUFS); + ERRNOSTR(EISCONN); + ERRNOSTR(ENOTCONN); + ERRNOSTR(ESHUTDOWN); + ERRNOSTR(ENOENT); + ERRNOSTR(ECONNRESET); + ERRNOSTR(ETIME); + ERRNOSTR(ECOMM); + ERRNOSTR(EREMOTEIO); + ERRNOSTR(EXDEV); + ERRNOSTR(EPERM); + default: return "unknown"; + } + +#undef ERRNOSTR +} + +/*---------------------------------------------------------------------------*/ +/* + * PARAMETERS USED WHEN REGISTERING THE VIDEO INTERFACE + * + * NOTE: SOME KERNELS IGNORE usb_class_driver.minor_base, AS MENTIONED BY + * CORBET ET AL. "LINUX DEVICE DRIVERS", 3rd EDITION, PAGE 253. + * THIS IS THE CASE FOR OpenSUSE. + */ +/*---------------------------------------------------------------------------*/ +/*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^*/ +/****************************************************************************/ +/*---------------------------------------------------------------------------*/ +/* + * THIS ROUTINE DOES NOT DETECT DUPLICATE OCCURRENCES OF POINTER peasycap +*/ +/*---------------------------------------------------------------------------*/ +int isdongle(struct easycap *peasycap) +{ + int k; + if (!peasycap) + return -2; + for (k = 0; k < DONGLE_MANY; k++) { + if (easycapdc60_dongle[k].peasycap == peasycap) { + peasycap->isdongle = k; + return k; + } + } + return -1; +} +/*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^*/ +static int easycap_open(struct inode *inode, struct file *file) +{ + struct video_device *pvideo_device; + struct easycap *peasycap; + int rc; + + JOT(4, "\n"); + SAY("==========OPEN=========\n"); + + pvideo_device = video_devdata(file); + if (!pvideo_device) { + SAY("ERROR: pvideo_device is NULL.\n"); + return -EFAULT; + } + peasycap = (struct easycap *)video_get_drvdata(pvideo_device); + if (!peasycap) { + SAY("ERROR: peasycap is NULL\n"); + return -EFAULT; + } + if (!peasycap->pusb_device) { + SAM("ERROR: peasycap->pusb_device is NULL\n"); + return -EFAULT; + } else { + JOM(16, "peasycap->pusb_device=%p\n", peasycap->pusb_device); + } + file->private_data = peasycap; + rc = wakeup_device(peasycap->pusb_device); + if (0 == rc) + JOM(8, "wakeup_device() OK\n"); + else { + SAM("ERROR: wakeup_device() rc = %i\n", rc); + if (-ENODEV == rc) + SAM("ERROR: wakeup_device() returned -ENODEV\n"); + else + SAM("ERROR: wakeup_device() rc = %i\n", rc); + return rc; + } + peasycap->input = 0; + rc = reset(peasycap); + if (rc) { + SAM("ERROR: reset() rc = %i\n", rc); + return -EFAULT; + } + return 0; +} + +/*****************************************************************************/ +/*---------------------------------------------------------------------------*/ +/* + * RESET THE HARDWARE TO ITS REFERENCE STATE. + * + * THIS ROUTINE MAY BE CALLED REPEATEDLY IF easycap_complete() DETECTS + * A BAD VIDEO FRAME SIZE. +*/ +/*---------------------------------------------------------------------------*/ +static int reset(struct easycap *peasycap) +{ + struct easycap_standard const *peasycap_standard; + int fmtidx, input, rate; + bool ntsc, other; + int rc; + + if (!peasycap) { + SAY("ERROR: peasycap is NULL\n"); + return -EFAULT; + } + input = peasycap->input; + +/*---------------------------------------------------------------------------*/ +/* + * IF THE SAA7113H HAS ALREADY ACQUIRED SYNC, USE ITS HARDWARE-DETECTED + * FIELD FREQUENCY TO DISTINGUISH NTSC FROM PAL. THIS IS ESSENTIAL FOR + * gstreamer AND OTHER USERSPACE PROGRAMS WHICH MAY NOT ATTEMPT TO INITIATE + * A SWITCH BETWEEN PAL AND NTSC. + * + * FUNCTION ready_saa() MAY REQUIRE A SUBSTANTIAL FRACTION OF A SECOND TO + * COMPLETE, SO SHOULD NOT BE INVOKED WITHOUT GOOD REASON. +*/ +/*---------------------------------------------------------------------------*/ + other = false; + JOM(8, "peasycap->ntsc=%d\n", peasycap->ntsc); + + rate = ready_saa(peasycap->pusb_device); + if (rate < 0) { + JOM(8, "not ready to capture after %i ms ...\n", PATIENCE); + ntsc = !peasycap->ntsc; + JOM(8, "... trying %s ..\n", ntsc ? "NTSC" : "PAL"); + rc = setup_stk(peasycap->pusb_device, ntsc); + if (rc) { + SAM("ERROR: setup_stk() rc = %i\n", rc); + return -EFAULT; + } + rc = setup_saa(peasycap->pusb_device, ntsc); + if (rc) { + SAM("ERROR: setup_saa() rc = %i\n", rc); + return -EFAULT; + } + + rate = ready_saa(peasycap->pusb_device); + if (rate < 0) { + JOM(8, "not ready to capture after %i ms\n", PATIENCE); + JOM(8, "... saa register 0x1F has 0x%02X\n", + read_saa(peasycap->pusb_device, 0x1F)); + ntsc = peasycap->ntsc; + } else { + JOM(8, "... success at second try: %i=rate\n", rate); + ntsc = (0 < (rate/2)) ? true : false ; + other = true; + } + } else { + JOM(8, "... success at first try: %i=rate\n", rate); + ntsc = (0 < rate/2) ? true : false ; + } + JOM(8, "ntsc=%d\n", ntsc); +/*---------------------------------------------------------------------------*/ + + rc = setup_stk(peasycap->pusb_device, ntsc); + if (rc) { + SAM("ERROR: setup_stk() rc = %i\n", rc); + return -EFAULT; + } + rc = setup_saa(peasycap->pusb_device, ntsc); + if (rc) { + SAM("ERROR: setup_saa() rc = %i\n", rc); + return -EFAULT; + } + + memset(peasycap->merit, 0, sizeof(peasycap->merit)); + + peasycap->video_eof = 0; + peasycap->audio_eof = 0; +/*---------------------------------------------------------------------------*/ +/* + * RESTORE INPUT AND FORCE REFRESH OF STANDARD, FORMAT, ETC. + * + * WHILE THIS PROCEDURE IS IN PROGRESS, SOME IOCTL COMMANDS WILL RETURN -EBUSY. +*/ +/*---------------------------------------------------------------------------*/ + peasycap->input = -8192; + peasycap->standard_offset = -8192; + fmtidx = ntsc ? NTSC_M : PAL_BGHIN; + if (other) { + peasycap_standard = &easycap_standard[0]; + while (0xFFFF != peasycap_standard->mask) { + if (fmtidx == peasycap_standard->v4l2_standard.index) { + peasycap->inputset[input].standard_offset = + peasycap_standard - easycap_standard; + break; + } + peasycap_standard++; + } + if (0xFFFF == peasycap_standard->mask) { + SAM("ERROR: standard not found\n"); + return -EINVAL; + } + JOM(8, "%i=peasycap->inputset[%i].standard_offset\n", + peasycap->inputset[input].standard_offset, input); + } + peasycap->format_offset = -8192; + peasycap->brightness = -8192; + peasycap->contrast = -8192; + peasycap->saturation = -8192; + peasycap->hue = -8192; + + rc = newinput(peasycap, input); + + if (rc) { + SAM("ERROR: newinput(.,%i) rc = %i\n", rc, input); + return -EFAULT; + } + JOM(4, "restored input, standard and format\n"); + + JOM(8, "true=peasycap->ntsc %d\n", peasycap->ntsc); + + if (0 > peasycap->input) { + SAM("MISTAKE: %i=peasycap->input\n", peasycap->input); + return -ENOENT; + } + if (0 > peasycap->standard_offset) { + SAM("MISTAKE: %i=peasycap->standard_offset\n", + peasycap->standard_offset); + return -ENOENT; + } + if (0 > peasycap->format_offset) { + SAM("MISTAKE: %i=peasycap->format_offset\n", + peasycap->format_offset); + return -ENOENT; + } + if (0 > peasycap->brightness) { + SAM("MISTAKE: %i=peasycap->brightness\n", + peasycap->brightness); + return -ENOENT; + } + if (0 > peasycap->contrast) { + SAM("MISTAKE: %i=peasycap->contrast\n", peasycap->contrast); + return -ENOENT; + } + if (0 > peasycap->saturation) { + SAM("MISTAKE: %i=peasycap->saturation\n", + peasycap->saturation); + return -ENOENT; + } + if (0 > peasycap->hue) { + SAM("MISTAKE: %i=peasycap->hue\n", peasycap->hue); + return -ENOENT; + } + return 0; +} +/*****************************************************************************/ +/*---------------------------------------------------------------------------*/ +/* + * IF THE REQUESTED INPUT IS THE SAME AS THE EXISTING INPUT, DO NOTHING. + * OTHERWISE: + * KILL URBS, CLEAR FIELD AND FRAME BUFFERS AND RESET THEIR + * _read AND _fill POINTERS. + * SELECT THE NEW INPUT. + * ADJUST THE STANDARD, FORMAT, BRIGHTNESS, CONTRAST, SATURATION AND HUE + * ON THE BASIS OF INFORMATION IN STRUCTURE easycap.inputset[input]. + * RESUBMIT THE URBS IF STREAMING WAS ALREADY IN PROGRESS. + * + * NOTE: + * THIS ROUTINE MAY BE CALLED FREQUENTLY BY ZONEMINDER VIA IOCTL, + * SO IT SHOULD WRITE ONLY SPARINGLY TO THE LOGFILE. +*/ +/*---------------------------------------------------------------------------*/ +int +newinput(struct easycap *peasycap, int input) +{ + int rc, k, m, mood, off; + int inputnow, video_idlenow, audio_idlenow; + bool resubmit; + + if (!peasycap) { + SAY("ERROR: peasycap is NULL\n"); + return -EFAULT; + } + JOM(8, "%i=input sought\n", input); + + if (0 > input && INPUT_MANY <= input) + return -ENOENT; + inputnow = peasycap->input; + if (input == inputnow) + return 0; +/*---------------------------------------------------------------------------*/ +/* + * IF STREAMING IS IN PROGRESS THE URBS ARE KILLED AT THIS + * STAGE AND WILL BE RESUBMITTED PRIOR TO EXIT FROM THE ROUTINE. + * IF NO STREAMING IS IN PROGRESS NO URBS WILL BE SUBMITTED BY THE + * ROUTINE. +*/ +/*---------------------------------------------------------------------------*/ + video_idlenow = peasycap->video_idle; + audio_idlenow = peasycap->audio_idle; + + peasycap->video_idle = 1; + peasycap->audio_idle = 1; + if (peasycap->video_isoc_streaming) { + resubmit = true; + kill_video_urbs(peasycap); + } else { + resubmit = false; + } +/*---------------------------------------------------------------------------*/ + if (!peasycap->pusb_device) { + SAM("ERROR: peasycap->pusb_device is NULL\n"); + return -ENODEV; + } + rc = usb_set_interface(peasycap->pusb_device, + peasycap->video_interface, + peasycap->video_altsetting_off); + if (rc) { + SAM("ERROR: usb_set_interface() rc = %i\n", rc); + return -EFAULT; + } + rc = stop_100(peasycap->pusb_device); + if (rc) { + SAM("ERROR: stop_100() rc = %i\n", rc); + return -EFAULT; + } + for (k = 0; k < FIELD_BUFFER_MANY; k++) { + for (m = 0; m < FIELD_BUFFER_SIZE/PAGE_SIZE; m++) + memset(peasycap->field_buffer[k][m].pgo, 0, PAGE_SIZE); + } + for (k = 0; k < FRAME_BUFFER_MANY; k++) { + for (m = 0; m < FRAME_BUFFER_SIZE/PAGE_SIZE; m++) + memset(peasycap->frame_buffer[k][m].pgo, 0, PAGE_SIZE); + } + peasycap->field_page = 0; + peasycap->field_read = 0; + peasycap->field_fill = 0; + + peasycap->frame_read = 0; + peasycap->frame_fill = 0; + for (k = 0; k < peasycap->input; k++) { + (peasycap->frame_fill)++; + if (peasycap->frame_buffer_many <= peasycap->frame_fill) + peasycap->frame_fill = 0; + } + peasycap->input = input; + select_input(peasycap->pusb_device, peasycap->input, 9); +/*---------------------------------------------------------------------------*/ + if (input == peasycap->inputset[input].input) { + off = peasycap->inputset[input].standard_offset; + if (off != peasycap->standard_offset) { + rc = adjust_standard(peasycap, + easycap_standard[off].v4l2_standard.id); + if (rc) { + SAM("ERROR: adjust_standard() rc = %i\n", rc); + return -EFAULT; + } + JOM(8, "%i=peasycap->standard_offset\n", + peasycap->standard_offset); + } else { + JOM(8, "%i=peasycap->standard_offset unchanged\n", + peasycap->standard_offset); + } + off = peasycap->inputset[input].format_offset; + if (off != peasycap->format_offset) { + struct v4l2_pix_format *pix = + &easycap_format[off].v4l2_format.fmt.pix; + rc = adjust_format(peasycap, + pix->width, pix->height, + pix->pixelformat, pix->field, false); + if (0 > rc) { + SAM("ERROR: adjust_format() rc = %i\n", rc); + return -EFAULT; + } + JOM(8, "%i=peasycap->format_offset\n", + peasycap->format_offset); + } else { + JOM(8, "%i=peasycap->format_offset unchanged\n", + peasycap->format_offset); + } + mood = peasycap->inputset[input].brightness; + if (mood != peasycap->brightness) { + rc = adjust_brightness(peasycap, mood); + if (rc) { + SAM("ERROR: adjust_brightness rc = %i\n", rc); + return -EFAULT; + } + JOM(8, "%i=peasycap->brightness\n", + peasycap->brightness); + } + mood = peasycap->inputset[input].contrast; + if (mood != peasycap->contrast) { + rc = adjust_contrast(peasycap, mood); + if (rc) { + SAM("ERROR: adjust_contrast rc = %i\n", rc); + return -EFAULT; + } + JOM(8, "%i=peasycap->contrast\n", peasycap->contrast); + } + mood = peasycap->inputset[input].saturation; + if (mood != peasycap->saturation) { + rc = adjust_saturation(peasycap, mood); + if (rc) { + SAM("ERROR: adjust_saturation rc = %i\n", rc); + return -EFAULT; + } + JOM(8, "%i=peasycap->saturation\n", + peasycap->saturation); + } + mood = peasycap->inputset[input].hue; + if (mood != peasycap->hue) { + rc = adjust_hue(peasycap, mood); + if (rc) { + SAM("ERROR: adjust_hue rc = %i\n", rc); + return -EFAULT; + } + JOM(8, "%i=peasycap->hue\n", peasycap->hue); + } + } else { + SAM("MISTAKE: easycap.inputset[%i] unpopulated\n", input); + return -ENOENT; + } +/*---------------------------------------------------------------------------*/ + if (!peasycap->pusb_device) { + SAM("ERROR: peasycap->pusb_device is NULL\n"); + return -ENODEV; + } + rc = usb_set_interface(peasycap->pusb_device, + peasycap->video_interface, + peasycap->video_altsetting_on); + if (rc) { + SAM("ERROR: usb_set_interface() rc = %i\n", rc); + return -EFAULT; + } + rc = start_100(peasycap->pusb_device); + if (rc) { + SAM("ERROR: start_100() rc = %i\n", rc); + return -EFAULT; + } + if (resubmit) + submit_video_urbs(peasycap); + + peasycap->video_isoc_sequence = VIDEO_ISOC_BUFFER_MANY - 1; + peasycap->video_idle = video_idlenow; + peasycap->audio_idle = audio_idlenow; + peasycap->video_junk = 0; + + return 0; +} +/*****************************************************************************/ +int submit_video_urbs(struct easycap *peasycap) +{ + struct data_urb *pdata_urb; + struct urb *purb; + struct list_head *plist_head; + int j, isbad, nospc, m, rc; + int isbuf; + + if (!peasycap) { + SAY("ERROR: peasycap is NULL\n"); + return -EFAULT; + } + + if (!peasycap->purb_video_head) { + SAY("ERROR: peasycap->urb_video_head uninitialized\n"); + return -EFAULT; + } + if (!peasycap->pusb_device) { + SAY("ERROR: peasycap->pusb_device is NULL\n"); + return -ENODEV; + } + if (!peasycap->video_isoc_streaming) { + JOM(4, "submission of all video urbs\n"); + isbad = 0; nospc = 0; m = 0; + list_for_each(plist_head, (peasycap->purb_video_head)) { + pdata_urb = list_entry(plist_head, + struct data_urb, list_head); + if (pdata_urb && pdata_urb->purb) { + purb = pdata_urb->purb; + isbuf = pdata_urb->isbuf; + purb->interval = 1; + purb->dev = peasycap->pusb_device; + purb->pipe = + usb_rcvisocpipe(peasycap->pusb_device, + peasycap->video_endpointnumber); + purb->transfer_flags = URB_ISO_ASAP; + purb->transfer_buffer = + peasycap->video_isoc_buffer[isbuf].pgo; + purb->transfer_buffer_length = + peasycap->video_isoc_buffer_size; + purb->complete = easycap_complete; + purb->context = peasycap; + purb->start_frame = 0; + purb->number_of_packets = + peasycap->video_isoc_framesperdesc; + + for (j = 0; j < peasycap->video_isoc_framesperdesc; j++) { + purb->iso_frame_desc[j]. offset = + j * peasycap->video_isoc_maxframesize; + purb->iso_frame_desc[j]. length = + peasycap->video_isoc_maxframesize; + } + + rc = usb_submit_urb(purb, GFP_KERNEL); + if (rc) { + isbad++; + SAM("ERROR: usb_submit_urb() failed " + "for urb with rc:-%s\n", + strerror(rc)); + if (rc == -ENOSPC) + nospc++; + } else { + m++; + } + } else { + isbad++; + } + } + if (nospc) { + SAM("-ENOSPC=usb_submit_urb() for %i urbs\n", nospc); + SAM("..... possibly inadequate USB bandwidth\n"); + peasycap->video_eof = 1; + } + + if (isbad) { + JOM(4, "attempting cleanup instead of submitting\n"); + list_for_each(plist_head, (peasycap->purb_video_head)) { + pdata_urb = list_entry(plist_head, + struct data_urb, list_head); + if (pdata_urb) { + purb = pdata_urb->purb; + if (purb) + usb_kill_urb(purb); + } + } + peasycap->video_isoc_streaming = 0; + } else { + peasycap->video_isoc_streaming = 1; + JOM(4, "submitted %i video urbs\n", m); + } + } else { + JOM(4, "already streaming video urbs\n"); + } + return 0; +} +/*****************************************************************************/ +int kill_video_urbs(struct easycap *peasycap) +{ + int m; + struct list_head *plist_head; + struct data_urb *pdata_urb; + + if (!peasycap) { + SAY("ERROR: peasycap is NULL\n"); + return -EFAULT; + } + if (!peasycap->video_isoc_streaming) { + JOM(8, "%i=video_isoc_streaming, no video urbs killed\n", + peasycap->video_isoc_streaming); + return 0; + } + if (!peasycap->purb_video_head) { + SAM("ERROR: peasycap->purb_video_head is NULL\n"); + return -EFAULT; + } + + peasycap->video_isoc_streaming = 0; + JOM(4, "killing video urbs\n"); + m = 0; + list_for_each(plist_head, (peasycap->purb_video_head)) { + pdata_urb = list_entry(plist_head, struct data_urb, list_head); + if (pdata_urb && pdata_urb->purb) { + usb_kill_urb(pdata_urb->purb); + m++; + } + } + JOM(4, "%i video urbs killed\n", m); + + return 0; +} +/****************************************************************************/ +/*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^*/ +/*--------------------------------------------------------------------------*/ +static int easycap_open_noinode(struct file *file) +{ + return easycap_open(NULL, file); +} + +static int videodev_release(struct video_device *pvideo_device) +{ + struct easycap *peasycap; + + peasycap = video_get_drvdata(pvideo_device); + if (!peasycap) { + SAY("ERROR: peasycap is NULL\n"); + SAY("ending unsuccessfully\n"); + return -EFAULT; + } + if (0 != kill_video_urbs(peasycap)) { + SAM("ERROR: kill_video_urbs() failed\n"); + return -EFAULT; + } + JOM(4, "ending successfully\n"); + return 0; +} +/*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^*/ +/*****************************************************************************/ +/*--------------------------------------------------------------------------*/ +/* + * THIS FUNCTION IS CALLED FROM WITHIN easycap_usb_disconnect() AND IS + * PROTECTED BY SEMAPHORES SET AND CLEARED BY easycap_usb_disconnect(). + * + * BY THIS STAGE THE DEVICE HAS ALREADY BEEN PHYSICALLY UNPLUGGED, SO + * peasycap->pusb_device IS NO LONGER VALID. + */ +/*---------------------------------------------------------------------------*/ +static void easycap_delete(struct kref *pkref) +{ + struct easycap *peasycap; + struct data_urb *pdata_urb; + struct list_head *plist_head, *plist_next; + int k, m, gone, kd; + int allocation_video_urb; + int allocation_video_page; + int allocation_video_struct; + int allocation_audio_urb; + int allocation_audio_page; + int allocation_audio_struct; + int registered_video, registered_audio; + + peasycap = container_of(pkref, struct easycap, kref); + if (!peasycap) { + SAM("ERROR: peasycap is NULL: cannot perform deletions\n"); + return; + } + kd = isdongle(peasycap); +/*---------------------------------------------------------------------------*/ +/* + * FREE VIDEO. + */ +/*---------------------------------------------------------------------------*/ + if (peasycap->purb_video_head) { + JOM(4, "freeing video urbs\n"); + m = 0; + list_for_each(plist_head, (peasycap->purb_video_head)) { + pdata_urb = list_entry(plist_head, + struct data_urb, list_head); + if (!pdata_urb) { + JOM(4, "ERROR: pdata_urb is NULL\n"); + } else { + if (pdata_urb->purb) { + usb_free_urb(pdata_urb->purb); + pdata_urb->purb = NULL; + peasycap->allocation_video_urb -= 1; + m++; + } + } + } + + JOM(4, "%i video urbs freed\n", m); +/*---------------------------------------------------------------------------*/ + JOM(4, "freeing video data_urb structures.\n"); + m = 0; + list_for_each_safe(plist_head, plist_next, + peasycap->purb_video_head) { + pdata_urb = list_entry(plist_head, + struct data_urb, list_head); + if (pdata_urb) { + peasycap->allocation_video_struct -= + sizeof(struct data_urb); + kfree(pdata_urb); + pdata_urb = NULL; + m++; + } + } + JOM(4, "%i video data_urb structures freed\n", m); + JOM(4, "setting peasycap->purb_video_head=NULL\n"); + peasycap->purb_video_head = NULL; + } +/*---------------------------------------------------------------------------*/ + JOM(4, "freeing video isoc buffers.\n"); + m = 0; + for (k = 0; k < VIDEO_ISOC_BUFFER_MANY; k++) { + if (peasycap->video_isoc_buffer[k].pgo) { + free_pages((unsigned long) + peasycap->video_isoc_buffer[k].pgo, + VIDEO_ISOC_ORDER); + peasycap->video_isoc_buffer[k].pgo = NULL; + peasycap->allocation_video_page -= + BIT(VIDEO_ISOC_ORDER); + m++; + } + } + JOM(4, "isoc video buffers freed: %i pages\n", + m * (0x01 << VIDEO_ISOC_ORDER)); +/*---------------------------------------------------------------------------*/ + JOM(4, "freeing video field buffers.\n"); + gone = 0; + for (k = 0; k < FIELD_BUFFER_MANY; k++) { + for (m = 0; m < FIELD_BUFFER_SIZE/PAGE_SIZE; m++) { + if (peasycap->field_buffer[k][m].pgo) { + free_page((unsigned long) + peasycap->field_buffer[k][m].pgo); + peasycap->field_buffer[k][m].pgo = NULL; + peasycap->allocation_video_page -= 1; + gone++; + } + } + } + JOM(4, "video field buffers freed: %i pages\n", gone); +/*---------------------------------------------------------------------------*/ + JOM(4, "freeing video frame buffers.\n"); + gone = 0; + for (k = 0; k < FRAME_BUFFER_MANY; k++) { + for (m = 0; m < FRAME_BUFFER_SIZE/PAGE_SIZE; m++) { + if (peasycap->frame_buffer[k][m].pgo) { + free_page((unsigned long) + peasycap->frame_buffer[k][m].pgo); + peasycap->frame_buffer[k][m].pgo = NULL; + peasycap->allocation_video_page -= 1; + gone++; + } + } + } + JOM(4, "video frame buffers freed: %i pages\n", gone); +/*---------------------------------------------------------------------------*/ +/* + * FREE AUDIO. + */ +/*---------------------------------------------------------------------------*/ + if (peasycap->purb_audio_head) { + JOM(4, "freeing audio urbs\n"); + m = 0; + list_for_each(plist_head, (peasycap->purb_audio_head)) { + pdata_urb = list_entry(plist_head, + struct data_urb, list_head); + if (!pdata_urb) + JOM(4, "ERROR: pdata_urb is NULL\n"); + else { + if (pdata_urb->purb) { + usb_free_urb(pdata_urb->purb); + pdata_urb->purb = NULL; + peasycap->allocation_audio_urb -= 1; + m++; + } + } + } + JOM(4, "%i audio urbs freed\n", m); +/*---------------------------------------------------------------------------*/ + JOM(4, "freeing audio data_urb structures.\n"); + m = 0; + list_for_each_safe(plist_head, plist_next, + peasycap->purb_audio_head) { + pdata_urb = list_entry(plist_head, + struct data_urb, list_head); + if (pdata_urb) { + peasycap->allocation_audio_struct -= + sizeof(struct data_urb); + kfree(pdata_urb); + pdata_urb = NULL; + m++; + } + } + JOM(4, "%i audio data_urb structures freed\n", m); + JOM(4, "setting peasycap->purb_audio_head=NULL\n"); + peasycap->purb_audio_head = NULL; + } +/*---------------------------------------------------------------------------*/ + JOM(4, "freeing audio isoc buffers.\n"); + m = 0; + for (k = 0; k < AUDIO_ISOC_BUFFER_MANY; k++) { + if (peasycap->audio_isoc_buffer[k].pgo) { + free_pages((unsigned long) + (peasycap->audio_isoc_buffer[k].pgo), + AUDIO_ISOC_ORDER); + peasycap->audio_isoc_buffer[k].pgo = NULL; + peasycap->allocation_audio_page -= + BIT(AUDIO_ISOC_ORDER); + m++; + } + } + JOM(4, "easyoss_delete(): isoc audio buffers freed: %i pages\n", + m * (0x01 << AUDIO_ISOC_ORDER)); +/*---------------------------------------------------------------------------*/ + JOM(4, "freeing easycap structure.\n"); + allocation_video_urb = peasycap->allocation_video_urb; + allocation_video_page = peasycap->allocation_video_page; + allocation_video_struct = peasycap->allocation_video_struct; + registered_video = peasycap->registered_video; + allocation_audio_urb = peasycap->allocation_audio_urb; + allocation_audio_page = peasycap->allocation_audio_page; + allocation_audio_struct = peasycap->allocation_audio_struct; + registered_audio = peasycap->registered_audio; + + if (0 <= kd && DONGLE_MANY > kd) { + if (mutex_lock_interruptible(&mutex_dongle)) { + SAY("ERROR: cannot down mutex_dongle\n"); + } else { + JOM(4, "locked mutex_dongle\n"); + easycapdc60_dongle[kd].peasycap = NULL; + mutex_unlock(&mutex_dongle); + JOM(4, "unlocked mutex_dongle\n"); + JOT(4, " null-->dongle[%i].peasycap\n", kd); + allocation_video_struct -= sizeof(struct easycap); + } + } else { + SAY("ERROR: cannot purge dongle[].peasycap"); + } + + kfree(peasycap); + +/*---------------------------------------------------------------------------*/ + SAY("%8i=video urbs after all deletions\n", allocation_video_urb); + SAY("%8i=video pages after all deletions\n", allocation_video_page); + SAY("%8i=video structs after all deletions\n", allocation_video_struct); + SAY("%8i=video devices after all deletions\n", registered_video); + SAY("%8i=audio urbs after all deletions\n", allocation_audio_urb); + SAY("%8i=audio pages after all deletions\n", allocation_audio_page); + SAY("%8i=audio structs after all deletions\n", allocation_audio_struct); + SAY("%8i=audio devices after all deletions\n", registered_audio); + + JOT(4, "ending.\n"); + return; +} +/*****************************************************************************/ +static unsigned int easycap_poll(struct file *file, poll_table *wait) +{ + struct easycap *peasycap; + int rc, kd; + + JOT(8, "\n"); + + if (NULL == ((poll_table *)wait)) + JOT(8, "WARNING: poll table pointer is NULL ... continuing\n"); + if (!file) { + SAY("ERROR: file pointer is NULL\n"); + return -ERESTARTSYS; + } + peasycap = file->private_data; + if (!peasycap) { + SAY("ERROR: peasycap is NULL\n"); + return -EFAULT; + } + if (!peasycap->pusb_device) { + SAY("ERROR: peasycap->pusb_device is NULL\n"); + return -EFAULT; + } +/*---------------------------------------------------------------------------*/ + kd = isdongle(peasycap); + if (0 <= kd && DONGLE_MANY > kd) { + if (mutex_lock_interruptible(&easycapdc60_dongle[kd].mutex_video)) { + SAY("ERROR: cannot down dongle[%i].mutex_video\n", kd); + return -ERESTARTSYS; + } + JOM(4, "locked dongle[%i].mutex_video\n", kd); + /* + * MEANWHILE, easycap_usb_disconnect() MAY HAVE FREED POINTER + * peasycap, IN WHICH CASE A REPEAT CALL TO isdongle() WILL FAIL. + * IF NECESSARY, BAIL OUT. + */ + if (kd != isdongle(peasycap)) { + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -ERESTARTSYS; + } + if (!file) { + SAY("ERROR: file is NULL\n"); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -ERESTARTSYS; + } + peasycap = file->private_data; + if (!peasycap) { + SAY("ERROR: peasycap is NULL\n"); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -ERESTARTSYS; + } + if (!peasycap->pusb_device) { + SAM("ERROR: peasycap->pusb_device is NULL\n"); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -ERESTARTSYS; + } + } else + /* + * IF easycap_usb_disconnect() HAS ALREADY FREED POINTER peasycap + * BEFORE THE ATTEMPT TO ACQUIRE THE SEMAPHORE, isdongle() WILL + * HAVE FAILED. BAIL OUT. + */ + return -ERESTARTSYS; +/*---------------------------------------------------------------------------*/ + rc = easycap_dqbuf(peasycap, 0); + peasycap->polled = 1; + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + if (0 == rc) + return POLLIN | POLLRDNORM; + else + return POLLERR; + } +/*****************************************************************************/ +/*---------------------------------------------------------------------------*/ +/* + * IF mode IS NONZERO THIS ROUTINE RETURNS -EAGAIN RATHER THAN BLOCKING. + */ +/*---------------------------------------------------------------------------*/ +int easycap_dqbuf(struct easycap *peasycap, int mode) +{ + int input, ifield, miss, rc; + + + if (!peasycap) { + SAY("ERROR: peasycap is NULL\n"); + return -EFAULT; + } + if (!peasycap->pusb_device) { + SAY("ERROR: peasycap->pusb_device is NULL\n"); + return -EFAULT; + } + ifield = 0; + JOM(8, "%i=ifield\n", ifield); +/*---------------------------------------------------------------------------*/ +/* + * CHECK FOR LOST INPUT SIGNAL. + * + * FOR THE FOUR-CVBS EasyCAP, THIS DOES NOT WORK AS EXPECTED. + * IF INPUT 0 IS PRESENT AND SYNC ACQUIRED, UNPLUGGING INPUT 4 DOES NOT + * RESULT IN SETTING BIT 0x40 ON REGISTER 0x1F, PRESUMABLY BECAUSE THERE + * IS FLYWHEELING ON INPUT 0. THE UPSHOT IS: + * + * INPUT 0 PLUGGED, INPUT 4 PLUGGED => SCREEN 0 OK, SCREEN 4 OK + * INPUT 0 PLUGGED, INPUT 4 UNPLUGGED => SCREEN 0 OK, SCREEN 4 BLACK + * INPUT 0 UNPLUGGED, INPUT 4 PLUGGED => SCREEN 0 BARS, SCREEN 4 OK + * INPUT 0 UNPLUGGED, INPUT 4 UNPLUGGED => SCREEN 0 BARS, SCREEN 4 BARS +*/ +/*---------------------------------------------------------------------------*/ + input = peasycap->input; + if (0 <= input && INPUT_MANY > input) { + rc = read_saa(peasycap->pusb_device, 0x1F); + if (0 <= rc) { + if (rc & 0x40) + peasycap->lost[input] += 1; + else + peasycap->lost[input] -= 2; + + if (0 > peasycap->lost[input]) + peasycap->lost[input] = 0; + else if ((2 * VIDEO_LOST_TOLERATE) < peasycap->lost[input]) + peasycap->lost[input] = (2 * VIDEO_LOST_TOLERATE); + } + } +/*---------------------------------------------------------------------------*/ +/* + * WAIT FOR FIELD ifield (0 => TOP, 1 => BOTTOM) + */ +/*---------------------------------------------------------------------------*/ + miss = 0; + while ((peasycap->field_read == peasycap->field_fill) || + (0 != (0xFF00 & peasycap->field_buffer + [peasycap->field_read][0].kount)) || + (ifield != (0x00FF & peasycap->field_buffer + [peasycap->field_read][0].kount))) { + if (mode) + return -EAGAIN; + + JOM(8, "first wait on wq_video, %i=field_read %i=field_fill\n", + peasycap->field_read, peasycap->field_fill); + + if (0 != (wait_event_interruptible(peasycap->wq_video, + (peasycap->video_idle || peasycap->video_eof || + ((peasycap->field_read != peasycap->field_fill) && + (0 == (0xFF00 & peasycap->field_buffer[peasycap->field_read][0].kount)) && + (ifield == (0x00FF & peasycap->field_buffer[peasycap->field_read][0].kount))))))) { + SAM("aborted by signal\n"); + return -EIO; + } + if (peasycap->video_idle) { + JOM(8, "%i=peasycap->video_idle returning -EAGAIN\n", + peasycap->video_idle); + return -EAGAIN; + } + if (peasycap->video_eof) { + JOM(8, "%i=peasycap->video_eof\n", peasycap->video_eof); + #if defined(PERSEVERE) + if (1 == peasycap->status) { + JOM(8, "persevering ...\n"); + peasycap->video_eof = 0; + peasycap->audio_eof = 0; + if (0 != reset(peasycap)) { + JOM(8, " ... failed returning -EIO\n"); + peasycap->video_eof = 1; + peasycap->audio_eof = 1; + kill_video_urbs(peasycap); + return -EIO; + } + peasycap->status = 0; + JOM(8, " ... OK returning -EAGAIN\n"); + return -EAGAIN; + } + #endif /*PERSEVERE*/ + peasycap->video_eof = 1; + peasycap->audio_eof = 1; + kill_video_urbs(peasycap); + JOM(8, "returning -EIO\n"); + return -EIO; + } + miss++; + } + JOM(8, "first awakening on wq_video after %i waits\n", miss); + + rc = field2frame(peasycap); + if (rc) + SAM("ERROR: field2frame() rc = %i\n", rc); +/*---------------------------------------------------------------------------*/ +/* + * WAIT FOR THE OTHER FIELD + */ +/*---------------------------------------------------------------------------*/ + if (ifield) + ifield = 0; + else + ifield = 1; + miss = 0; + while ((peasycap->field_read == peasycap->field_fill) || + (0 != (0xFF00 & peasycap->field_buffer[peasycap->field_read][0].kount)) || + (ifield != (0x00FF & peasycap->field_buffer[peasycap->field_read][0].kount))) { + if (mode) + return -EAGAIN; + + JOM(8, "second wait on wq_video %i=field_read %i=field_fill\n", + peasycap->field_read, peasycap->field_fill); + if (0 != (wait_event_interruptible(peasycap->wq_video, + (peasycap->video_idle || peasycap->video_eof || + ((peasycap->field_read != peasycap->field_fill) && + (0 == (0xFF00 & peasycap->field_buffer[peasycap->field_read][0].kount)) && + (ifield == (0x00FF & peasycap->field_buffer[peasycap->field_read][0].kount))))))) { + SAM("aborted by signal\n"); + return -EIO; + } + if (peasycap->video_idle) { + JOM(8, "%i=peasycap->video_idle returning -EAGAIN\n", + peasycap->video_idle); + return -EAGAIN; + } + if (peasycap->video_eof) { + JOM(8, "%i=peasycap->video_eof\n", peasycap->video_eof); +#if defined(PERSEVERE) + if (1 == peasycap->status) { + JOM(8, "persevering ...\n"); + peasycap->video_eof = 0; + peasycap->audio_eof = 0; + if (0 != reset(peasycap)) { + JOM(8, " ... failed returning -EIO\n"); + peasycap->video_eof = 1; + peasycap->audio_eof = 1; + kill_video_urbs(peasycap); + return -EIO; + } + peasycap->status = 0; + JOM(8, " ... OK ... returning -EAGAIN\n"); + return -EAGAIN; + } +#endif /*PERSEVERE*/ + peasycap->video_eof = 1; + peasycap->audio_eof = 1; + kill_video_urbs(peasycap); + JOM(8, "returning -EIO\n"); + return -EIO; + } + miss++; + } + JOM(8, "second awakening on wq_video after %i waits\n", miss); + + rc = field2frame(peasycap); + if (rc) + SAM("ERROR: field2frame() rc = %i\n", rc); +/*---------------------------------------------------------------------------*/ +/* + * WASTE THIS FRAME +*/ +/*---------------------------------------------------------------------------*/ + if (peasycap->skip) { + peasycap->skipped++; + if (peasycap->skip != peasycap->skipped) + return peasycap->skip - peasycap->skipped; + else + peasycap->skipped = 0; + } +/*---------------------------------------------------------------------------*/ + peasycap->frame_read = peasycap->frame_fill; + peasycap->queued[peasycap->frame_read] = 0; + peasycap->done[peasycap->frame_read] = V4L2_BUF_FLAG_DONE; + + peasycap->frame_fill++; + if (peasycap->frame_buffer_many <= peasycap->frame_fill) + peasycap->frame_fill = 0; + + if (0x01 & easycap_standard[peasycap->standard_offset].mask) + peasycap->frame_buffer[peasycap->frame_read][0].kount = + V4L2_FIELD_TOP; + else + peasycap->frame_buffer[peasycap->frame_read][0].kount = + V4L2_FIELD_BOTTOM; + + + JOM(8, "setting: %i=peasycap->frame_read\n", peasycap->frame_read); + JOM(8, "bumped to: %i=peasycap->frame_fill\n", peasycap->frame_fill); + + return 0; +} +/*****************************************************************************/ +/*---------------------------------------------------------------------------*/ +/* + * BY DEFINITION, odd IS true FOR THE FIELD OCCUPYING LINES 1,3,5,...,479 + * odd IS false FOR THE FIELD OCCUPYING LINES 0,2,4,...,478 + * + * WHEN BOOLEAN PARAMETER decimatepixel IS true, ONLY THE FIELD FOR WHICH + * odd==false IS TRANSFERRED TO THE FRAME BUFFER. + * + * THE BOOLEAN PARAMETER offerfields IS true ONLY WHEN THE USER PROGRAM + * CHOOSES THE OPTION V4L2_FIELD_INTERLACED. + */ +/*---------------------------------------------------------------------------*/ +int +field2frame(struct easycap *peasycap) +{ + + void *pex, *pad; + int kex, kad, mex, mad, rex, rad, rad2; + int c2, c3, w2, w3, cz, wz; + int rc, bytesperpixel, multiplier; + int much, more, over, rump, caches, input; + u8 mask, margin; + bool odd, isuy, decimatepixel, offerfields, badinput; + + if (!peasycap) { + SAY("ERROR: peasycap is NULL\n"); + return -EFAULT; + } + + badinput = false; + input = 0x07 & peasycap->field_buffer[peasycap->field_read][0].input; + + JOM(8, "===== parity %i, input 0x%02X, field buffer %i --> " + "frame buffer %i\n", + peasycap->field_buffer[peasycap->field_read][0].kount, + peasycap->field_buffer[peasycap->field_read][0].input, + peasycap->field_read, peasycap->frame_fill); + JOM(8, "===== %i=bytesperpixel\n", peasycap->bytesperpixel); + if (peasycap->offerfields) + JOM(8, "===== offerfields\n"); + +/*---------------------------------------------------------------------------*/ +/* + * REJECT OR CLEAN BAD FIELDS + */ +/*---------------------------------------------------------------------------*/ + if (peasycap->field_read == peasycap->field_fill) { + SAM("ERROR: on entry, still filling field buffer %i\n", + peasycap->field_read); + return 0; + } +#ifdef EASYCAP_TESTCARD + easycap_testcard(peasycap, peasycap->field_read); +#else + if (0 <= input && INPUT_MANY > input) { + if (easycap_bars && VIDEO_LOST_TOLERATE <= peasycap->lost[input]) + easycap_testcard(peasycap, peasycap->field_read); + } +#endif /*EASYCAP_TESTCARD*/ +/*---------------------------------------------------------------------------*/ + + offerfields = peasycap->offerfields; + bytesperpixel = peasycap->bytesperpixel; + decimatepixel = peasycap->decimatepixel; + + if ((2 != bytesperpixel) && + (3 != bytesperpixel) && + (4 != bytesperpixel)) { + SAM("MISTAKE: %i=bytesperpixel\n", bytesperpixel); + return -EFAULT; + } + if (decimatepixel) + multiplier = 2; + else + multiplier = 1; + + w2 = 2 * multiplier * (peasycap->width); + w3 = bytesperpixel * multiplier * (peasycap->width); + wz = multiplier * (peasycap->height) * + multiplier * (peasycap->width); + + kex = peasycap->field_read; mex = 0; + kad = peasycap->frame_fill; mad = 0; + + pex = peasycap->field_buffer[kex][0].pgo; rex = PAGE_SIZE; + pad = peasycap->frame_buffer[kad][0].pgo; rad = PAGE_SIZE; + odd = !!(peasycap->field_buffer[kex][0].kount); + + if (odd && (!decimatepixel)) { + JOM(8, "initial skipping %4i bytes p.%4i\n", + w3/multiplier, mad); + pad += (w3 / multiplier); rad -= (w3 / multiplier); + } + isuy = true; + mask = 0; rump = 0; caches = 0; + + cz = 0; + while (cz < wz) { + /* + * PROCESS ONE LINE OF FRAME AT FULL RESOLUTION: + * READ w2 BYTES FROM FIELD BUFFER, + * WRITE w3 BYTES TO FRAME BUFFER + */ + if (!decimatepixel) { + over = w2; + do { + much = over; more = 0; + margin = 0; mask = 0x00; + if (rex < much) + much = rex; + rump = 0; + + if (much % 2) { + SAM("MISTAKE: much is odd\n"); + return -EFAULT; + } + + more = (bytesperpixel * + much) / 2; +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + if (1 < bytesperpixel) { + if (rad * 2 < much * bytesperpixel) { + /* + * INJUDICIOUS ALTERATION OF + * THIS STATEMENT BLOCK WILL + * CAUSE BREAKAGE. BEWARE. + */ + rad2 = rad + bytesperpixel - 1; + much = ((((2 * rad2)/bytesperpixel)/2) * 2); + rump = ((bytesperpixel * much) / 2) - rad; + more = rad; + } + mask = (u8)rump; + margin = 0; + if (much == rex) { + mask |= 0x04; + if ((mex + 1) < FIELD_BUFFER_SIZE / PAGE_SIZE) + margin = *((u8 *)(peasycap->field_buffer[kex][mex + 1].pgo)); + else + mask |= 0x08; + } + } else { + SAM("MISTAKE: %i=bytesperpixel\n", + bytesperpixel); + return -EFAULT; + } + if (rump) + caches++; + if (badinput) { + JOM(8, "ERROR: 0x%02X=->field_buffer" + "[%i][%i].input, " + "0x%02X=(0x08|->input)\n", + peasycap->field_buffer + [kex][mex].input, kex, mex, + (0x08|peasycap->input)); + } + rc = redaub(peasycap, pad, pex, much, more, + mask, margin, isuy); + if (0 > rc) { + SAM("ERROR: redaub() failed\n"); + return -EFAULT; + } + if (much % 4) + isuy = !isuy; + + over -= much; cz += much; + pex += much; rex -= much; + if (!rex) { + mex++; + pex = peasycap->field_buffer[kex][mex].pgo; + rex = PAGE_SIZE; + if (peasycap->field_buffer[kex][mex].input != (0x08|peasycap->input)) + badinput = true; + } + pad += more; + rad -= more; + if (!rad) { + mad++; + pad = peasycap->frame_buffer[kad][mad].pgo; + rad = PAGE_SIZE; + if (rump) { + pad += rump; + rad -= rump; + } + } + } while (over); +/*---------------------------------------------------------------------------*/ +/* + * SKIP w3 BYTES IN TARGET FRAME BUFFER, + * UNLESS IT IS THE LAST LINE OF AN ODD FRAME + */ +/*---------------------------------------------------------------------------*/ + if (!odd || (cz != wz)) { + over = w3; + do { + if (!rad) { + mad++; + pad = peasycap->frame_buffer + [kad][mad].pgo; + rad = PAGE_SIZE; + } + more = over; + if (rad < more) + more = rad; + over -= more; + pad += more; + rad -= more; + } while (over); + } +/*---------------------------------------------------------------------------*/ +/* + * PROCESS ONE LINE OF FRAME AT REDUCED RESOLUTION: + * ONLY IF false==odd, + * READ w2 BYTES FROM FIELD BUFFER, + * WRITE w3 / 2 BYTES TO FRAME BUFFER + */ +/*---------------------------------------------------------------------------*/ + } else if (!odd) { + over = w2; + do { + much = over; more = 0; margin = 0; mask = 0x00; + if (rex < much) + much = rex; + rump = 0; + + if (much % 2) { + SAM("MISTAKE: much is odd\n"); + return -EFAULT; + } + + more = (bytesperpixel * much) / 4; +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + if (1 < bytesperpixel) { + if (rad * 4 < much * bytesperpixel) { + /* + * INJUDICIOUS ALTERATION OF + * THIS STATEMENT BLOCK + * WILL CAUSE BREAKAGE. + * BEWARE. + */ + rad2 = rad + bytesperpixel - 1; + much = ((((2 * rad2) / bytesperpixel) / 2) * 4); + rump = ((bytesperpixel * much) / 4) - rad; + more = rad; + } + mask = (u8)rump; + margin = 0; + if (much == rex) { + mask |= 0x04; + if ((mex + 1) < FIELD_BUFFER_SIZE / PAGE_SIZE) + margin = *((u8 *)(peasycap->field_buffer[kex][mex + 1].pgo)); + else + mask |= 0x08; + } +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + } else { + SAM("MISTAKE: %i=bytesperpixel\n", + bytesperpixel); + return -EFAULT; + } +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + if (rump) + caches++; + + if (badinput) { + JOM(8, "ERROR: 0x%02X=->field_buffer" + "[%i][%i].input, " + "0x%02X=(0x08|->input)\n", + peasycap->field_buffer + [kex][mex].input, kex, mex, + (0x08|peasycap->input)); + } + rc = redaub(peasycap, pad, pex, much, more, + mask, margin, isuy); + if (0 > rc) { + SAM("ERROR: redaub() failed\n"); + return -EFAULT; + } + over -= much; cz += much; + pex += much; rex -= much; + if (!rex) { + mex++; + pex = peasycap->field_buffer[kex][mex].pgo; + rex = PAGE_SIZE; + if (peasycap->field_buffer[kex][mex].input != + (0x08|peasycap->input)) + badinput = true; + } + pad += more; + rad -= more; + if (!rad) { + mad++; + pad = peasycap->frame_buffer[kad][mad].pgo; + rad = PAGE_SIZE; + if (rump) { + pad += rump; + rad -= rump; + } + } + } while (over); +/*---------------------------------------------------------------------------*/ +/* + * OTHERWISE JUST + * READ w2 BYTES FROM FIELD BUFFER AND DISCARD THEM + */ +/*---------------------------------------------------------------------------*/ + } else { + over = w2; + do { + if (!rex) { + mex++; + pex = peasycap->field_buffer[kex][mex].pgo; + rex = PAGE_SIZE; + if (peasycap->field_buffer[kex][mex].input != + (0x08|peasycap->input)) { + JOM(8, "ERROR: 0x%02X=->field_buffer" + "[%i][%i].input, " + "0x%02X=(0x08|->input)\n", + peasycap->field_buffer + [kex][mex].input, kex, mex, + (0x08|peasycap->input)); + badinput = true; + } + } + much = over; + if (rex < much) + much = rex; + over -= much; + cz += much; + pex += much; + rex -= much; + } while (over); + } + } +/*---------------------------------------------------------------------------*/ +/* + * SANITY CHECKS + */ +/*---------------------------------------------------------------------------*/ + c2 = (mex + 1)*PAGE_SIZE - rex; + if (cz != c2) + SAM("ERROR: discrepancy %i in bytes read\n", c2 - cz); + c3 = (mad + 1)*PAGE_SIZE - rad; + + if (!decimatepixel) { + if (bytesperpixel * cz != c3) + SAM("ERROR: discrepancy %i in bytes written\n", + c3 - (bytesperpixel * cz)); + } else { + if (!odd) { + if (bytesperpixel * + cz != (4 * c3)) + SAM("ERROR: discrepancy %i in bytes written\n", + (2*c3)-(bytesperpixel * cz)); + } else { + if (0 != c3) + SAM("ERROR: discrepancy %i " + "in bytes written\n", c3); + } + } + if (rump) + SAM("WORRY: undischarged cache at end of line in frame buffer\n"); + + JOM(8, "===== field2frame(): %i bytes --> %i bytes (incl skip)\n", c2, c3); + JOM(8, "===== field2frame(): %i=mad %i=rad\n", mad, rad); + + if (odd) + JOM(8, "+++++ field2frame(): frame buffer %i is full\n", kad); + + if (peasycap->field_read == peasycap->field_fill) + SAM("WARNING: on exit, filling field buffer %i\n", + peasycap->field_read); + + if (caches) + JOM(8, "%i=caches\n", caches); + return 0; +} +/*---------------------------------------------------------------------------*/ +/* + * DECIMATION AND COLOURSPACE CONVERSION. + * + * THIS ROUTINE REQUIRES THAT ALL THE DATA TO BE READ RESIDES ON ONE PAGE + * AND THAT ALL THE DATA TO BE WRITTEN RESIDES ON ONE (DIFFERENT) PAGE. + * THE CALLING ROUTINE MUST ENSURE THAT THIS REQUIREMENT IS MET, AND MUST + * ALSO ENSURE THAT much IS EVEN. + * + * much BYTES ARE READ, AT LEAST (bytesperpixel * much)/2 BYTES ARE WRITTEN + * IF THERE IS NO DECIMATION, HALF THIS AMOUNT IF THERE IS DECIMATION. + * + * mask IS ZERO WHEN NO SPECIAL BEHAVIOUR REQUIRED. OTHERWISE IT IS SET THUS: + * 0x03 & mask = number of bytes to be written to cache instead of to + * frame buffer + * 0x04 & mask => use argument margin to set the chrominance for last pixel + * 0x08 & mask => do not set the chrominance for last pixel + * + * YUV to RGB CONVERSION IS (OR SHOULD BE) ITU-R BT 601. + * + * THERE IS A LOT OF CODE REPETITION IN THIS ROUTINE IN ORDER TO AVOID + * INEFFICIENT SWITCHING INSIDE INNER LOOPS. REARRANGING THE LOGIC TO + * REDUCE CODE LENGTH WILL GENERALLY IMPAIR RUNTIME PERFORMANCE. BEWARE. + */ +/*---------------------------------------------------------------------------*/ +int +redaub(struct easycap *peasycap, void *pad, void *pex, int much, int more, + u8 mask, u8 margin, bool isuy) +{ + static s32 ay[256], bu[256], rv[256], gu[256], gv[256]; + u8 *pcache; + u8 r, g, b, y, u, v, c, *p2, *p3, *pz, *pr; + int bytesperpixel; + bool byteswaporder, decimatepixel, last; + int j, rump; + s32 tmp; + + if (much % 2) { + SAM("MISTAKE: much is odd\n"); + return -EFAULT; + } + bytesperpixel = peasycap->bytesperpixel; + byteswaporder = peasycap->byteswaporder; + decimatepixel = peasycap->decimatepixel; + +/*---------------------------------------------------------------------------*/ + if (!bu[255]) { + for (j = 0; j < 112; j++) { + tmp = (0xFF00 & (453 * j)) >> 8; + bu[j + 128] = tmp; bu[127 - j] = -tmp; + tmp = (0xFF00 & (359 * j)) >> 8; + rv[j + 128] = tmp; rv[127 - j] = -tmp; + tmp = (0xFF00 & (88 * j)) >> 8; + gu[j + 128] = tmp; gu[127 - j] = -tmp; + tmp = (0xFF00 & (183 * j)) >> 8; + gv[j + 128] = tmp; gv[127 - j] = -tmp; + } + for (j = 0; j < 16; j++) { + bu[j] = bu[16]; rv[j] = rv[16]; + gu[j] = gu[16]; gv[j] = gv[16]; + } + for (j = 240; j < 256; j++) { + bu[j] = bu[239]; rv[j] = rv[239]; + gu[j] = gu[239]; gv[j] = gv[239]; + } + for (j = 16; j < 236; j++) + ay[j] = j; + for (j = 0; j < 16; j++) + ay[j] = ay[16]; + for (j = 236; j < 256; j++) + ay[j] = ay[235]; + JOM(8, "lookup tables are prepared\n"); + } + pcache = peasycap->pcache; + if (!pcache) + pcache = &peasycap->cache[0]; +/*---------------------------------------------------------------------------*/ +/* + * TRANSFER CONTENTS OF CACHE TO THE FRAME BUFFER + */ +/*---------------------------------------------------------------------------*/ + if (!pcache) { + SAM("MISTAKE: pcache is NULL\n"); + return -EFAULT; + } + + if (pcache != &peasycap->cache[0]) + JOM(16, "cache has %i bytes\n", (int)(pcache - &peasycap->cache[0])); + p2 = &peasycap->cache[0]; + p3 = (u8 *)pad - (int)(pcache - &peasycap->cache[0]); + while (p2 < pcache) { + *p3++ = *p2; p2++; + } + pcache = &peasycap->cache[0]; + if (p3 != pad) { + SAM("MISTAKE: pointer misalignment\n"); + return -EFAULT; + } +/*---------------------------------------------------------------------------*/ + rump = (int)(0x03 & mask); + u = 0; v = 0; + p2 = (u8 *)pex; pz = p2 + much; pr = p3 + more; last = false; + p2++; + + if (isuy) + u = *(p2 - 1); + else + v = *(p2 - 1); + + if (rump) + JOM(16, "%4i=much %4i=more %i=rump\n", much, more, rump); + +/*---------------------------------------------------------------------------*/ + switch (bytesperpixel) { + case 2: { + if (!decimatepixel) { + memcpy(pad, pex, (size_t)much); + if (!byteswaporder) { + /* UYVY */ + return 0; + } else { + /* YUYV */ + p3 = (u8 *)pad; pz = p3 + much; + while (pz > p3) { + c = *p3; + *p3 = *(p3 + 1); + *(p3 + 1) = c; + p3 += 2; + } + return 0; + } + } else { + if (!byteswaporder) { + /* UYVY DECIMATED */ + p2 = (u8 *)pex; p3 = (u8 *)pad; pz = p2 + much; + while (pz > p2) { + *p3 = *p2; + *(p3 + 1) = *(p2 + 1); + *(p3 + 2) = *(p2 + 2); + *(p3 + 3) = *(p2 + 3); + p3 += 4; p2 += 8; + } + return 0; + } else { + /* YUYV DECIMATED */ + p2 = (u8 *)pex; p3 = (u8 *)pad; pz = p2 + much; + while (pz > p2) { + *p3 = *(p2 + 1); + *(p3 + 1) = *p2; + *(p3 + 2) = *(p2 + 3); + *(p3 + 3) = *(p2 + 2); + p3 += 4; p2 += 8; + } + return 0; + } + } + break; + } + case 3: + { + if (!decimatepixel) { + if (!byteswaporder) { + /* RGB */ + while (pz > p2) { + if (pr <= (p3 + bytesperpixel)) + last = true; + else + last = false; + y = *p2; + if (last && (0x0C & mask)) { + if (0x04 & mask) { + if (isuy) + v = margin; + else + u = margin; + } else + if (0x08 & mask) + ; + } else { + if (isuy) + v = *(p2 + 1); + else + u = *(p2 + 1); + } + + tmp = ay[(int)y] + rv[(int)v]; + r = (255 < tmp) ? 255 : ((0 > tmp) ? + 0 : (u8)tmp); + tmp = ay[(int)y] - gu[(int)u] - gv[(int)v]; + g = (255 < tmp) ? 255 : ((0 > tmp) ? + 0 : (u8)tmp); + tmp = ay[(int)y] + bu[(int)u]; + b = (255 < tmp) ? 255 : ((0 > tmp) ? + 0 : (u8)tmp); + + if (last && rump) { + pcache = &peasycap->cache[0]; + switch (bytesperpixel - rump) { + case 1: { + *p3 = r; + *pcache++ = g; + *pcache++ = b; + break; + } + case 2: { + *p3 = r; + *(p3 + 1) = g; + *pcache++ = b; + break; + } + default: { + SAM("MISTAKE: %i=rump\n", + bytesperpixel - rump); + return -EFAULT; + } + } + } else { + *p3 = r; + *(p3 + 1) = g; + *(p3 + 2) = b; + } + p2 += 2; + if (isuy) + isuy = false; + else + isuy = true; + p3 += bytesperpixel; + } + return 0; + } else { + /* BGR */ + while (pz > p2) { + if (pr <= (p3 + bytesperpixel)) + last = true; + else + last = false; + y = *p2; + if (last && (0x0C & mask)) { + if (0x04 & mask) { + if (isuy) + v = margin; + else + u = margin; + } + else + if (0x08 & mask) + ; + } else { + if (isuy) + v = *(p2 + 1); + else + u = *(p2 + 1); + } + + tmp = ay[(int)y] + rv[(int)v]; + r = (255 < tmp) ? 255 : ((0 > tmp) ? + 0 : (u8)tmp); + tmp = ay[(int)y] - gu[(int)u] - gv[(int)v]; + g = (255 < tmp) ? 255 : ((0 > tmp) ? + 0 : (u8)tmp); + tmp = ay[(int)y] + bu[(int)u]; + b = (255 < tmp) ? 255 : ((0 > tmp) ? + 0 : (u8)tmp); + + if (last && rump) { + pcache = &peasycap->cache[0]; + switch (bytesperpixel - rump) { + case 1: { + *p3 = b; + *pcache++ = g; + *pcache++ = r; + break; + } + case 2: { + *p3 = b; + *(p3 + 1) = g; + *pcache++ = r; + break; + } + default: { + SAM("MISTAKE: %i=rump\n", + bytesperpixel - rump); + return -EFAULT; + } + } + } else { + *p3 = b; + *(p3 + 1) = g; + *(p3 + 2) = r; + } + p2 += 2; + if (isuy) + isuy = false; + else + isuy = true; + p3 += bytesperpixel; + } + } + return 0; + } else { + if (!byteswaporder) { + /* RGB DECIMATED */ + while (pz > p2) { + if (pr <= (p3 + bytesperpixel)) + last = true; + else + last = false; + y = *p2; + if (last && (0x0C & mask)) { + if (0x04 & mask) { + if (isuy) + v = margin; + else + u = margin; + } else + if (0x08 & mask) + ; + } else { + if (isuy) + v = *(p2 + 1); + else + u = *(p2 + 1); + } + + if (isuy) { + tmp = ay[(int)y] + rv[(int)v]; + r = (255 < tmp) ? 255 : ((0 > tmp) ? + 0 : (u8)tmp); + tmp = ay[(int)y] - gu[(int)u] - + gv[(int)v]; + g = (255 < tmp) ? 255 : ((0 > tmp) ? + 0 : (u8)tmp); + tmp = ay[(int)y] + bu[(int)u]; + b = (255 < tmp) ? 255 : ((0 > tmp) ? + 0 : (u8)tmp); + + if (last && rump) { + pcache = &peasycap->cache[0]; + switch (bytesperpixel - rump) { + case 1: { + *p3 = r; + *pcache++ = g; + *pcache++ = b; + break; + } + case 2: { + *p3 = r; + *(p3 + 1) = g; + *pcache++ = b; + break; + } + default: { + SAM("MISTAKE: " + "%i=rump\n", + bytesperpixel - rump); + return -EFAULT; + } + } + } else { + *p3 = r; + *(p3 + 1) = g; + *(p3 + 2) = b; + } + isuy = false; + p3 += bytesperpixel; + } else { + isuy = true; + } + p2 += 2; + } + return 0; + } else { + /* BGR DECIMATED */ + while (pz > p2) { + if (pr <= (p3 + bytesperpixel)) + last = true; + else + last = false; + y = *p2; + if (last && (0x0C & mask)) { + if (0x04 & mask) { + if (isuy) + v = margin; + else + u = margin; + } else + if (0x08 & mask) + ; + } else { + if (isuy) + v = *(p2 + 1); + else + u = *(p2 + 1); + } + + if (isuy) { + + tmp = ay[(int)y] + rv[(int)v]; + r = (255 < tmp) ? 255 : ((0 > tmp) ? + 0 : (u8)tmp); + tmp = ay[(int)y] - gu[(int)u] - + gv[(int)v]; + g = (255 < tmp) ? 255 : ((0 > tmp) ? + 0 : (u8)tmp); + tmp = ay[(int)y] + bu[(int)u]; + b = (255 < tmp) ? 255 : ((0 > tmp) ? + 0 : (u8)tmp); + + if (last && rump) { + pcache = &peasycap->cache[0]; + switch (bytesperpixel - rump) { + case 1: { + *p3 = b; + *pcache++ = g; + *pcache++ = r; + break; + } + case 2: { + *p3 = b; + *(p3 + 1) = g; + *pcache++ = r; + break; + } + default: { + SAM("MISTAKE: " + "%i=rump\n", + bytesperpixel - rump); + return -EFAULT; + } + } + } else { + *p3 = b; + *(p3 + 1) = g; + *(p3 + 2) = r; + } + isuy = false; + p3 += bytesperpixel; + } + else + isuy = true; + p2 += 2; + } + return 0; + } + } + break; + } + case 4: + { + if (!decimatepixel) { + if (!byteswaporder) { + /* RGBA */ + while (pz > p2) { + if (pr <= (p3 + bytesperpixel)) + last = true; + else + last = false; + y = *p2; + if (last && (0x0C & mask)) { + if (0x04 & mask) { + if (isuy) + v = margin; + else + u = margin; + } else + if (0x08 & mask) + ; + } else { + if (isuy) + v = *(p2 + 1); + else + u = *(p2 + 1); + } + + tmp = ay[(int)y] + rv[(int)v]; + r = (255 < tmp) ? 255 : ((0 > tmp) ? + 0 : (u8)tmp); + tmp = ay[(int)y] - gu[(int)u] - gv[(int)v]; + g = (255 < tmp) ? 255 : ((0 > tmp) ? + 0 : (u8)tmp); + tmp = ay[(int)y] + bu[(int)u]; + b = (255 < tmp) ? 255 : ((0 > tmp) ? + 0 : (u8)tmp); + + if (last && rump) { + pcache = &peasycap->cache[0]; + switch (bytesperpixel - rump) { + case 1: { + *p3 = r; + *pcache++ = g; + *pcache++ = b; + *pcache++ = 0; + break; + } + case 2: { + *p3 = r; + *(p3 + 1) = g; + *pcache++ = b; + *pcache++ = 0; + break; + } + case 3: { + *p3 = r; + *(p3 + 1) = g; + *(p3 + 2) = b; + *pcache++ = 0; + break; + } + default: { + SAM("MISTAKE: %i=rump\n", + bytesperpixel - rump); + return -EFAULT; + } + } + } else { + *p3 = r; + *(p3 + 1) = g; + *(p3 + 2) = b; + *(p3 + 3) = 0; + } + p2 += 2; + if (isuy) + isuy = false; + else + isuy = true; + p3 += bytesperpixel; + } + return 0; + } else { + /* + * BGRA + */ + while (pz > p2) { + if (pr <= (p3 + bytesperpixel)) + last = true; + else + last = false; + y = *p2; + if (last && (0x0C & mask)) { + if (0x04 & mask) { + if (isuy) + v = margin; + else + u = margin; + } else + if (0x08 & mask) + ; + } else { + if (isuy) + v = *(p2 + 1); + else + u = *(p2 + 1); + } + + tmp = ay[(int)y] + rv[(int)v]; + r = (255 < tmp) ? 255 : ((0 > tmp) ? + 0 : (u8)tmp); + tmp = ay[(int)y] - gu[(int)u] - gv[(int)v]; + g = (255 < tmp) ? 255 : ((0 > tmp) ? + 0 : (u8)tmp); + tmp = ay[(int)y] + bu[(int)u]; + b = (255 < tmp) ? 255 : ((0 > tmp) ? + 0 : (u8)tmp); + + if (last && rump) { + pcache = &peasycap->cache[0]; + switch (bytesperpixel - rump) { + case 1: { + *p3 = b; + *pcache++ = g; + *pcache++ = r; + *pcache++ = 0; + break; + } + case 2: { + *p3 = b; + *(p3 + 1) = g; + *pcache++ = r; + *pcache++ = 0; + break; + } + case 3: { + *p3 = b; + *(p3 + 1) = g; + *(p3 + 2) = r; + *pcache++ = 0; + break; + } + default: + SAM("MISTAKE: %i=rump\n", + bytesperpixel - rump); + return -EFAULT; + } + } else { + *p3 = b; + *(p3 + 1) = g; + *(p3 + 2) = r; + *(p3 + 3) = 0; + } + p2 += 2; + if (isuy) + isuy = false; + else + isuy = true; + p3 += bytesperpixel; + } + } + return 0; + } else { + if (!byteswaporder) { + /* + * RGBA DECIMATED + */ + while (pz > p2) { + if (pr <= (p3 + bytesperpixel)) + last = true; + else + last = false; + y = *p2; + if (last && (0x0C & mask)) { + if (0x04 & mask) { + if (isuy) + v = margin; + else + u = margin; + } else + if (0x08 & mask) + ; + } else { + if (isuy) + v = *(p2 + 1); + else + u = *(p2 + 1); + } + + if (isuy) { + + tmp = ay[(int)y] + rv[(int)v]; + r = (255 < tmp) ? 255 : ((0 > tmp) ? + 0 : (u8)tmp); + tmp = ay[(int)y] - gu[(int)u] - + gv[(int)v]; + g = (255 < tmp) ? 255 : ((0 > tmp) ? + 0 : (u8)tmp); + tmp = ay[(int)y] + bu[(int)u]; + b = (255 < tmp) ? 255 : ((0 > tmp) ? + 0 : (u8)tmp); + + if (last && rump) { + pcache = &peasycap->cache[0]; + switch (bytesperpixel - rump) { + case 1: { + *p3 = r; + *pcache++ = g; + *pcache++ = b; + *pcache++ = 0; + break; + } + case 2: { + *p3 = r; + *(p3 + 1) = g; + *pcache++ = b; + *pcache++ = 0; + break; + } + case 3: { + *p3 = r; + *(p3 + 1) = g; + *(p3 + 2) = b; + *pcache++ = 0; + break; + } + default: { + SAM("MISTAKE: " + "%i=rump\n", + bytesperpixel - + rump); + return -EFAULT; + } + } + } else { + *p3 = r; + *(p3 + 1) = g; + *(p3 + 2) = b; + *(p3 + 3) = 0; + } + isuy = false; + p3 += bytesperpixel; + } else + isuy = true; + p2 += 2; + } + return 0; + } else { + /* + * BGRA DECIMATED + */ + while (pz > p2) { + if (pr <= (p3 + bytesperpixel)) + last = true; + else + last = false; + y = *p2; + if (last && (0x0C & mask)) { + if (0x04 & mask) { + if (isuy) + v = margin; + else + u = margin; + } else + if (0x08 & mask) + ; + } else { + if (isuy) + v = *(p2 + 1); + else + u = *(p2 + 1); + } + + if (isuy) { + tmp = ay[(int)y] + rv[(int)v]; + r = (255 < tmp) ? 255 : ((0 > tmp) ? + 0 : (u8)tmp); + tmp = ay[(int)y] - gu[(int)u] - + gv[(int)v]; + g = (255 < tmp) ? 255 : ((0 > tmp) ? + 0 : (u8)tmp); + tmp = ay[(int)y] + bu[(int)u]; + b = (255 < tmp) ? 255 : ((0 > tmp) ? + 0 : (u8)tmp); + + if (last && rump) { + pcache = &peasycap->cache[0]; + switch (bytesperpixel - rump) { + case 1: { + *p3 = b; + *pcache++ = g; + *pcache++ = r; + *pcache++ = 0; + break; + } + case 2: { + *p3 = b; + *(p3 + 1) = g; + *pcache++ = r; + *pcache++ = 0; + break; + } + case 3: { + *p3 = b; + *(p3 + 1) = g; + *(p3 + 2) = r; + *pcache++ = 0; + break; + } + default: { + SAM("MISTAKE: " + "%i=rump\n", + bytesperpixel - rump); + return -EFAULT; + } + } + } else { + *p3 = b; + *(p3 + 1) = g; + *(p3 + 2) = r; + *(p3 + 3) = 0; + } + isuy = false; + p3 += bytesperpixel; + } else + isuy = true; + p2 += 2; + } + return 0; + } + } + break; + } + default: { + SAM("MISTAKE: %i=bytesperpixel\n", bytesperpixel); + return -EFAULT; + } + } + return 0; +} +/*****************************************************************************/ +/* + * SEE CORBET ET AL. "LINUX DEVICE DRIVERS", 3rd EDITION, PAGES 430-434 + */ +/*****************************************************************************/ +static void easycap_vma_open(struct vm_area_struct *pvma) +{ + struct easycap *peasycap; + + peasycap = pvma->vm_private_data; + if (!peasycap) { + SAY("ERROR: peasycap is NULL\n"); + return; + } + peasycap->vma_many++; + JOT(8, "%i=peasycap->vma_many\n", peasycap->vma_many); + return; +} +/*****************************************************************************/ +static void easycap_vma_close(struct vm_area_struct *pvma) +{ + struct easycap *peasycap; + + peasycap = pvma->vm_private_data; + if (!peasycap) { + SAY("ERROR: peasycap is NULL\n"); + return; + } + peasycap->vma_many--; + JOT(8, "%i=peasycap->vma_many\n", peasycap->vma_many); + return; +} +/*****************************************************************************/ +static int easycap_vma_fault(struct vm_area_struct *pvma, struct vm_fault *pvmf) +{ + int k, m, retcode; + void *pbuf; + struct page *page; + struct easycap *peasycap; + + retcode = VM_FAULT_NOPAGE; + + if (!pvma) { + SAY("pvma is NULL\n"); + return retcode; + } + if (!pvmf) { + SAY("pvmf is NULL\n"); + return retcode; + } + + k = (pvmf->pgoff) / (FRAME_BUFFER_SIZE/PAGE_SIZE); + m = (pvmf->pgoff) % (FRAME_BUFFER_SIZE/PAGE_SIZE); + + if (!m) + JOT(4, "%4i=k, %4i=m\n", k, m); + else + JOT(16, "%4i=k, %4i=m\n", k, m); + + if ((0 > k) || (FRAME_BUFFER_MANY <= k)) { + SAY("ERROR: buffer index %i out of range\n", k); + return retcode; + } + if ((0 > m) || (FRAME_BUFFER_SIZE/PAGE_SIZE <= m)) { + SAY("ERROR: page number %i out of range\n", m); + return retcode; + } + peasycap = pvma->vm_private_data; + if (!peasycap) { + SAY("ERROR: peasycap is NULL\n"); + return retcode; + } +/*---------------------------------------------------------------------------*/ + pbuf = peasycap->frame_buffer[k][m].pgo; + if (!pbuf) { + SAM("ERROR: pbuf is NULL\n"); + return retcode; + } + page = virt_to_page(pbuf); + if (!page) { + SAM("ERROR: page is NULL\n"); + return retcode; + } + get_page(page); +/*---------------------------------------------------------------------------*/ + if (!page) { + SAM("ERROR: page is NULL after get_page(page)\n"); + } else { + pvmf->page = page; + retcode = VM_FAULT_MINOR; + } + return retcode; +} + +static const struct vm_operations_struct easycap_vm_ops = { + .open = easycap_vma_open, + .close = easycap_vma_close, + .fault = easycap_vma_fault, +}; + +static int easycap_mmap(struct file *file, struct vm_area_struct *pvma) +{ + JOT(8, "\n"); + + pvma->vm_ops = &easycap_vm_ops; + pvma->vm_flags |= VM_RESERVED; + if (file) + pvma->vm_private_data = file->private_data; + easycap_vma_open(pvma); + return 0; +} +/*****************************************************************************/ +/*---------------------------------------------------------------------------*/ +/* + * ON COMPLETION OF A VIDEO URB ITS DATA IS COPIED TO THE FIELD BUFFERS + * PROVIDED peasycap->video_idle IS ZERO. REGARDLESS OF THIS BEING TRUE, + * IT IS RESUBMITTED PROVIDED peasycap->video_isoc_streaming IS NOT ZERO. + * + * THIS FUNCTION IS AN INTERRUPT SERVICE ROUTINE AND MUST NOT SLEEP. + * + * INFORMATION ABOUT THE VALIDITY OF THE CONTENTS OF THE FIELD BUFFER ARE + * STORED IN THE TWO-BYTE STATUS PARAMETER + * peasycap->field_buffer[peasycap->field_fill][0].kount + * NOTICE THAT THE INFORMATION IS STORED ONLY WITH PAGE 0 OF THE FIELD BUFFER. + * + * THE LOWER BYTE CONTAINS THE FIELD PARITY BYTE FURNISHED BY THE SAA7113H + * CHIP. + * + * THE UPPER BYTE IS ZERO IF NO PROBLEMS, OTHERWISE: + * 0 != (kount & 0x8000) => AT LEAST ONE URB COMPLETED WITH ERRORS + * 0 != (kount & 0x4000) => BUFFER HAS TOO MUCH DATA + * 0 != (kount & 0x2000) => BUFFER HAS NOT ENOUGH DATA + * 0 != (kount & 0x1000) => BUFFER HAS DATA FROM DISPARATE INPUTS + * 0 != (kount & 0x0400) => RESERVED + * 0 != (kount & 0x0200) => FIELD BUFFER NOT YET CHECKED + * 0 != (kount & 0x0100) => BUFFER HAS TWO EXTRA BYTES - WHY? + */ +/*---------------------------------------------------------------------------*/ +static void easycap_complete(struct urb *purb) +{ + struct easycap *peasycap; + struct data_buffer *pfield_buffer; + char errbuf[16]; + int i, more, much, leap, rc, last; + int videofieldamount; + unsigned int override, bad; + int framestatus, framelength, frameactual, frameoffset; + u8 *pu; + + if (!purb) { + SAY("ERROR: easycap_complete(): purb is NULL\n"); + return; + } + peasycap = purb->context; + if (!peasycap) { + SAY("ERROR: easycap_complete(): peasycap is NULL\n"); + return; + } + if (peasycap->video_eof) + return; + for (i = 0; i < VIDEO_ISOC_BUFFER_MANY; i++) + if (purb->transfer_buffer == peasycap->video_isoc_buffer[i].pgo) + break; + JOM(16, "%2i=urb\n", i); + last = peasycap->video_isoc_sequence; + if ((((VIDEO_ISOC_BUFFER_MANY - 1) == last) && (0 != i)) || + (((VIDEO_ISOC_BUFFER_MANY - 1) != last) && ((last + 1) != i))) { + JOM(16, "ERROR: out-of-order urbs %i,%i ... continuing\n", + last, i); + } + peasycap->video_isoc_sequence = i; + + if (peasycap->video_idle) { + JOM(16, "%i=video_idle %i=video_isoc_streaming\n", + peasycap->video_idle, peasycap->video_isoc_streaming); + if (peasycap->video_isoc_streaming) { + rc = usb_submit_urb(purb, GFP_ATOMIC); + if (rc) { + SAM("%s:%d ENOMEM\n", strerror(rc), rc); + if (-ENODEV != rc) + SAM("ERROR: while %i=video_idle, " + "usb_submit_urb() " + "failed with rc:\n", + peasycap->video_idle); + } + } + return; + } + override = 0; +/*---------------------------------------------------------------------------*/ + if (FIELD_BUFFER_MANY <= peasycap->field_fill) { + SAM("ERROR: bad peasycap->field_fill\n"); + return; + } + if (purb->status) { + if ((-ESHUTDOWN == purb->status) || (-ENOENT == purb->status)) { + JOM(8, "urb status -ESHUTDOWN or -ENOENT\n"); + return; + } + + (peasycap->field_buffer[peasycap->field_fill][0].kount) |= 0x8000 ; + SAM("ERROR: bad urb status -%s: %d\n", + strerror(purb->status), purb->status); +/*---------------------------------------------------------------------------*/ + } else { + for (i = 0; i < purb->number_of_packets; i++) { + if (0 != purb->iso_frame_desc[i].status) { + (peasycap->field_buffer + [peasycap->field_fill][0].kount) |= 0x8000 ; + /* FIXME: 1. missing '-' check boundaries */ + strcpy(&errbuf[0], + strerror(purb->iso_frame_desc[i].status)); + } + framestatus = purb->iso_frame_desc[i].status; + framelength = purb->iso_frame_desc[i].length; + frameactual = purb->iso_frame_desc[i].actual_length; + frameoffset = purb->iso_frame_desc[i].offset; + + JOM(16, "frame[%2i]:" + "%4i=status " + "%4i=actual " + "%4i=length " + "%5i=offset\n", + i, framestatus, frameactual, framelength, frameoffset); + if (!purb->iso_frame_desc[i].status) { + more = purb->iso_frame_desc[i].actual_length; + pfield_buffer = &peasycap->field_buffer + [peasycap->field_fill][peasycap->field_page]; + videofieldamount = (peasycap->field_page * + PAGE_SIZE) + + (int)(pfield_buffer->pto - pfield_buffer->pgo); + if (4 == more) + peasycap->video_mt++; + if (4 < more) { + if (peasycap->video_mt) { + JOM(8, "%4i empty video urb frames\n", + peasycap->video_mt); + peasycap->video_mt = 0; + } + if (FIELD_BUFFER_MANY <= peasycap->field_fill) { + SAM("ERROR: bad peasycap->field_fill\n"); + return; + } + if (FIELD_BUFFER_SIZE/PAGE_SIZE <= + peasycap->field_page) { + SAM("ERROR: bad peasycap->field_page\n"); + return; + } + pfield_buffer = &peasycap->field_buffer + [peasycap->field_fill][peasycap->field_page]; + pu = (u8 *)(purb->transfer_buffer + + purb->iso_frame_desc[i].offset); + if (0x80 & *pu) + leap = 8; + else + leap = 4; +/*--------------------------------------------------------------------------*/ +/* + * EIGHT-BYTE END-OF-VIDEOFIELD MARKER. + * NOTE: A SUCCESSION OF URB FRAMES FOLLOWING THIS ARE EMPTY, + * CORRESPONDING TO THE FIELD FLYBACK (VERTICAL BLANKING) PERIOD. + * + * PROVIDED THE FIELD BUFFER CONTAINS GOOD DATA AS INDICATED BY A ZERO UPPER + * BYTE OF + * peasycap->field_buffer[peasycap->field_fill][0].kount + * THE CONTENTS OF THE FIELD BUFFER ARE OFFERED TO dqbuf(), field_read IS + * UPDATED AND field_fill IS BUMPED. IF THE FIELD BUFFER CONTAINS BAD DATA + * NOTHING IS OFFERED TO dqbuf(). + * + * THE DECISION ON WHETHER THE PARITY OF THE OFFERED FIELD BUFFER IS RIGHT + * RESTS WITH dqbuf(). + */ +/*---------------------------------------------------------------------------*/ + if ((8 == more) || override) { + if (videofieldamount > + peasycap->videofieldamount) { + if (2 == videofieldamount - + peasycap-> + videofieldamount) { + (peasycap->field_buffer + [peasycap->field_fill] + [0].kount) |= 0x0100; + peasycap->video_junk += (1 + + VIDEO_JUNK_TOLERATE); + } else + (peasycap->field_buffer + [peasycap->field_fill] + [0].kount) |= 0x4000; + } else if (videofieldamount < + peasycap-> + videofieldamount) { + (peasycap->field_buffer + [peasycap->field_fill] + [0].kount) |= 0x2000; + } + bad = 0xFF00 & peasycap->field_buffer + [peasycap->field_fill] + [0].kount; + if (!bad) { + (peasycap->video_junk)--; + if (-VIDEO_JUNK_TOLERATE > + peasycap->video_junk) + peasycap->video_junk = + -VIDEO_JUNK_TOLERATE; + peasycap->field_read = + (peasycap-> + field_fill)++; + if (FIELD_BUFFER_MANY <= + peasycap-> + field_fill) + peasycap-> + field_fill = 0; + peasycap->field_page = 0; + pfield_buffer = &peasycap-> + field_buffer + [peasycap-> + field_fill] + [peasycap-> + field_page]; + pfield_buffer->pto = + pfield_buffer->pgo; + JOM(8, "bumped to: %i=" + "peasycap->" + "field_fill %i=" + "parity\n", + peasycap->field_fill, + 0x00FF & + pfield_buffer->kount); + JOM(8, "field buffer %i has " + "%i bytes fit to be " + "read\n", + peasycap->field_read, + videofieldamount); + JOM(8, "wakeup call to " + "wq_video, " + "%i=field_read " + "%i=field_fill " + "%i=parity\n", + peasycap->field_read, + peasycap->field_fill, + 0x00FF & peasycap-> + field_buffer + [peasycap-> + field_read][0].kount); + wake_up_interruptible + (&(peasycap-> + wq_video)); + } else { + peasycap->video_junk++; + if (bad & 0x0010) + peasycap->video_junk += + (1 + VIDEO_JUNK_TOLERATE/2); + JOM(8, "field buffer %i had %i " + "bytes, now discarded: " + "0x%04X\n", + peasycap->field_fill, + videofieldamount, + (0xFF00 & + peasycap->field_buffer + [peasycap->field_fill][0]. + kount)); + (peasycap->field_fill)++; + + if (FIELD_BUFFER_MANY <= + peasycap->field_fill) + peasycap->field_fill = 0; + peasycap->field_page = 0; + pfield_buffer = + &peasycap->field_buffer + [peasycap->field_fill] + [peasycap->field_page]; + pfield_buffer->pto = + pfield_buffer->pgo; + + JOM(8, "bumped to: %i=peasycap->" + "field_fill %i=parity\n", + peasycap->field_fill, + 0x00FF & pfield_buffer->kount); + } + if (8 == more) { + JOM(8, "end-of-field: received " + "parity byte 0x%02X\n", + (0xFF & *pu)); + if (0x40 & *pu) + pfield_buffer->kount = 0x0000; + else + pfield_buffer->kount = 0x0001; + pfield_buffer->input = 0x08 | + (0x07 & peasycap->input); + JOM(8, "end-of-field: 0x%02X=kount\n", + 0xFF & pfield_buffer->kount); + } + } +/*---------------------------------------------------------------------------*/ +/* + * COPY more BYTES FROM ISOC BUFFER TO FIELD BUFFER + */ +/*---------------------------------------------------------------------------*/ + pu += leap; + more -= leap; + + if (FIELD_BUFFER_MANY <= peasycap->field_fill) { + SAM("ERROR: bad peasycap->field_fill\n"); + return; + } + if (FIELD_BUFFER_SIZE/PAGE_SIZE <= peasycap->field_page) { + SAM("ERROR: bad peasycap->field_page\n"); + return; + } + pfield_buffer = &peasycap->field_buffer + [peasycap->field_fill][peasycap->field_page]; + while (more) { + pfield_buffer = &peasycap->field_buffer + [peasycap->field_fill] + [peasycap->field_page]; + if (PAGE_SIZE < (pfield_buffer->pto - + pfield_buffer->pgo)) { + SAM("ERROR: bad pfield_buffer->pto\n"); + return; + } + if (PAGE_SIZE == (pfield_buffer->pto - + pfield_buffer->pgo)) { + (peasycap->field_page)++; + if (FIELD_BUFFER_SIZE/PAGE_SIZE <= + peasycap->field_page) { + JOM(16, "wrapping peasycap->" + "field_page\n"); + peasycap->field_page = 0; + } + pfield_buffer = &peasycap-> + field_buffer + [peasycap->field_fill] + [peasycap->field_page]; + pfield_buffer->pto = pfield_buffer->pgo; + pfield_buffer->input = 0x08 | + (0x07 & peasycap->input); + if ((peasycap->field_buffer[peasycap-> + field_fill][0]). + input != + pfield_buffer->input) + (peasycap->field_buffer + [peasycap->field_fill] + [0]).kount |= 0x1000; + } + + much = PAGE_SIZE - + (int)(pfield_buffer->pto - + pfield_buffer->pgo); + + if (much > more) + much = more; + memcpy(pfield_buffer->pto, pu, much); + pu += much; + (pfield_buffer->pto) += much; + more -= much; + } + } + } + } + } +/*---------------------------------------------------------------------------*/ +/* + * RESUBMIT THIS URB, UNLESS A SEVERE PERSISTENT ERROR CONDITION EXISTS. + * + * IF THE WAIT QUEUES ARE NOT CLEARED IN RESPONSE TO AN ERROR CONDITION + * THE USERSPACE PROGRAM, E.G. mplayer, MAY HANG ON EXIT. BEWARE. + */ +/*---------------------------------------------------------------------------*/ + if (VIDEO_ISOC_BUFFER_MANY <= peasycap->video_junk) { + SAM("easycap driver shutting down on condition green\n"); + peasycap->status = 1; + peasycap->video_eof = 1; + peasycap->video_junk = 0; + wake_up_interruptible(&peasycap->wq_video); +#if !defined(PERSEVERE) + peasycap->audio_eof = 1; + wake_up_interruptible(&peasycap->wq_audio); +#endif /*PERSEVERE*/ + return; + } + if (peasycap->video_isoc_streaming) { + rc = usb_submit_urb(purb, GFP_ATOMIC); + if (rc) { + SAM("%s: %d\n", strerror(rc), rc); + if (-ENODEV != rc) + SAM("ERROR: while %i=video_idle, " + "usb_submit_urb() " + "failed with rc:\n", + peasycap->video_idle); + } + } + return; +} +static const struct file_operations easycap_fops = { + .owner = THIS_MODULE, + .open = easycap_open, + .unlocked_ioctl = easycap_unlocked_ioctl, + .poll = easycap_poll, + .mmap = easycap_mmap, + .llseek = no_llseek, +}; +static const struct usb_class_driver easycap_class = { + .name = "usb/easycap%d", + .fops = &easycap_fops, + .minor_base = USB_SKEL_MINOR_BASE, +}; +/*vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv*/ +static const struct v4l2_file_operations v4l2_fops = { + .owner = THIS_MODULE, + .open = easycap_open_noinode, + .unlocked_ioctl = easycap_unlocked_ioctl, + .poll = easycap_poll, + .mmap = easycap_mmap, +}; +/*****************************************************************************/ +/*---------------------------------------------------------------------------*/ +/* + * WHEN THE EasyCAP IS PHYSICALLY PLUGGED IN, THIS FUNCTION IS CALLED THREE + * TIMES, ONCE FOR EACH OF THE THREE INTERFACES. BEWARE. + */ +/*---------------------------------------------------------------------------*/ +static int easycap_usb_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct usb_device *usbdev; + struct usb_host_interface *alt; + struct usb_endpoint_descriptor *ep; + struct usb_interface_descriptor *interface; + struct urb *purb; + struct easycap *peasycap; + int ndong; + struct data_urb *pdata_urb; + int i, j, k, m, rc; + u8 bInterfaceNumber; + u8 bInterfaceClass; + u8 bInterfaceSubClass; + void *pbuf; + int okalt[8], isokalt; + int okepn[8]; + int okmps[8]; + int maxpacketsize; + u16 mask; + s32 value; + struct easycap_format *peasycap_format; + int fmtidx; + struct inputset *inputset; + + usbdev = interface_to_usbdev(intf); + +/*---------------------------------------------------------------------------*/ + alt = usb_altnum_to_altsetting(intf, 0); + if (!alt) { + SAY("ERROR: usb_host_interface not found\n"); + return -EFAULT; + } + interface = &alt->desc; + if (!interface) { + SAY("ERROR: intf_descriptor is NULL\n"); + return -EFAULT; + } +/*---------------------------------------------------------------------------*/ +/* + * GET PROPERTIES OF PROBED INTERFACE + */ +/*---------------------------------------------------------------------------*/ + bInterfaceNumber = interface->bInterfaceNumber; + bInterfaceClass = interface->bInterfaceClass; + bInterfaceSubClass = interface->bInterfaceSubClass; + + JOT(4, "intf[%i]: num_altsetting=%i\n", + bInterfaceNumber, intf->num_altsetting); + JOT(4, "intf[%i]: cur_altsetting - altsetting=%li\n", + bInterfaceNumber, + (long int)(intf->cur_altsetting - intf->altsetting)); + JOT(4, "intf[%i]: bInterfaceClass=0x%02X bInterfaceSubClass=0x%02X\n", + bInterfaceNumber, bInterfaceClass, bInterfaceSubClass); +/*---------------------------------------------------------------------------*/ +/* + * A NEW struct easycap IS ALWAYS ALLOCATED WHEN INTERFACE 0 IS PROBED. + * IT IS NOT POSSIBLE HERE TO FREE ANY EXISTING struct easycap. THIS + * SHOULD HAVE BEEN DONE BY easycap_delete() WHEN THE EasyCAP WAS + * PHYSICALLY UNPLUGGED. + * + * THE POINTER peasycap TO THE struct easycap IS REMEMBERED WHEN + * INTERFACES 1 AND 2 ARE PROBED. +*/ +/*---------------------------------------------------------------------------*/ + if (0 == bInterfaceNumber) { + peasycap = kzalloc(sizeof(struct easycap), GFP_KERNEL); + if (!peasycap) { + SAY("ERROR: Could not allocate peasycap\n"); + return -ENOMEM; + } +/*---------------------------------------------------------------------------*/ +/* + * PERFORM URGENT INTIALIZATIONS ... +*/ +/*---------------------------------------------------------------------------*/ + peasycap->minor = -1; + kref_init(&peasycap->kref); + JOM(8, "intf[%i]: after kref_init(..._video) " + "%i=peasycap->kref.refcount.counter\n", + bInterfaceNumber, peasycap->kref.refcount.counter); + + /* module params */ + peasycap->gain = (s8)clamp(easycap_gain, 0, 31); + + init_waitqueue_head(&peasycap->wq_video); + init_waitqueue_head(&peasycap->wq_audio); + init_waitqueue_head(&peasycap->wq_trigger); + + if (mutex_lock_interruptible(&mutex_dongle)) { + SAY("ERROR: cannot down mutex_dongle\n"); + return -ERESTARTSYS; + } else { +/*---------------------------------------------------------------------------*/ + /* + * FOR INTERFACES 1 AND 2 THE POINTER peasycap WILL NEED TO + * TO BE THE SAME AS THAT ALLOCATED NOW FOR INTERFACE 0. + * + * NORMALLY ndong WILL NOT HAVE CHANGED SINCE INTERFACE 0 WAS + * PROBED, BUT THIS MAY NOT BE THE CASE IF, FOR EXAMPLE, TWO + * EASYCAPs ARE PLUGGED IN SIMULTANEOUSLY. + */ +/*---------------------------------------------------------------------------*/ + for (ndong = 0; ndong < DONGLE_MANY; ndong++) { + if ((!easycapdc60_dongle[ndong].peasycap) && + (!mutex_is_locked(&easycapdc60_dongle + [ndong].mutex_video)) && + (!mutex_is_locked(&easycapdc60_dongle + [ndong].mutex_audio))) { + easycapdc60_dongle[ndong].peasycap = peasycap; + peasycap->isdongle = ndong; + JOM(8, "intf[%i]: peasycap-->easycap" + "_dongle[%i].peasycap\n", + bInterfaceNumber, ndong); + break; + } + } + if (DONGLE_MANY <= ndong) { + SAM("ERROR: too many dongles\n"); + mutex_unlock(&mutex_dongle); + return -ENOMEM; + } + mutex_unlock(&mutex_dongle); + } + peasycap->allocation_video_struct = sizeof(struct easycap); + peasycap->allocation_video_page = 0; + peasycap->allocation_video_urb = 0; + peasycap->allocation_audio_struct = 0; + peasycap->allocation_audio_page = 0; + peasycap->allocation_audio_urb = 0; + +/*---------------------------------------------------------------------------*/ +/* + * ... AND FURTHER INITIALIZE THE STRUCTURE +*/ +/*---------------------------------------------------------------------------*/ + peasycap->pusb_device = usbdev; + peasycap->pusb_interface = intf; + + peasycap->ilk = 0; + peasycap->microphone = false; + + peasycap->video_interface = -1; + peasycap->video_altsetting_on = -1; + peasycap->video_altsetting_off = -1; + peasycap->video_endpointnumber = -1; + peasycap->video_isoc_maxframesize = -1; + peasycap->video_isoc_buffer_size = -1; + + peasycap->audio_interface = -1; + peasycap->audio_altsetting_on = -1; + peasycap->audio_altsetting_off = -1; + peasycap->audio_endpointnumber = -1; + peasycap->audio_isoc_maxframesize = -1; + peasycap->audio_isoc_buffer_size = -1; + + peasycap->frame_buffer_many = FRAME_BUFFER_MANY; + + for (k = 0; k < INPUT_MANY; k++) + peasycap->lost[k] = 0; + peasycap->skip = 0; + peasycap->skipped = 0; + peasycap->offerfields = 0; +/*---------------------------------------------------------------------------*/ +/* + * DYNAMICALLY FILL IN THE AVAILABLE FORMATS ... + */ +/*---------------------------------------------------------------------------*/ + rc = fillin_formats(); + if (0 > rc) { + SAM("ERROR: fillin_formats() rc = %i\n", rc); + return -EFAULT; + } + JOM(4, "%i formats available\n", rc); +/*---------------------------------------------------------------------------*/ +/* + * ... AND POPULATE easycap.inputset[] +*/ +/*---------------------------------------------------------------------------*/ + /* FIXME: maybe we just use memset 0 */ + inputset = peasycap->inputset; + for (k = 0; k < INPUT_MANY; k++) { + inputset[k].input_ok = 0; + inputset[k].standard_offset_ok = 0; + inputset[k].format_offset_ok = 0; + inputset[k].brightness_ok = 0; + inputset[k].contrast_ok = 0; + inputset[k].saturation_ok = 0; + inputset[k].hue_ok = 0; + } + + fmtidx = peasycap->ntsc ? NTSC_M : PAL_BGHIN; + m = 0; + mask = 0; + for (i = 0; 0xFFFF != easycap_standard[i].mask; i++) { + if (fmtidx == easycap_standard[i].v4l2_standard.index) { + m++; + for (k = 0; k < INPUT_MANY; k++) + inputset[k].standard_offset = i; + + mask = easycap_standard[i].mask; + } + } + + if (1 != m) { + SAM("ERROR: " + "inputset->standard_offset unpopulated, %i=m\n", m); + return -ENOENT; + } + + peasycap_format = &easycap_format[0]; + m = 0; + for (i = 0; peasycap_format->v4l2_format.fmt.pix.width; i++) { + struct v4l2_pix_format *pix = + &peasycap_format->v4l2_format.fmt.pix; + if (((peasycap_format->mask & 0x0F) == (mask & 0x0F)) && + pix->field == V4L2_FIELD_NONE && + pix->pixelformat == V4L2_PIX_FMT_UYVY && + pix->width == 640 && pix->height == 480) { + m++; + for (k = 0; k < INPUT_MANY; k++) + inputset[k].format_offset = i; + break; + } + peasycap_format++; + } + if (1 != m) { + SAM("ERROR: inputset[]->format_offset unpopulated\n"); + return -ENOENT; + } + + m = 0; + for (i = 0; 0xFFFFFFFF != easycap_control[i].id; i++) { + value = easycap_control[i].default_value; + if (V4L2_CID_BRIGHTNESS == easycap_control[i].id) { + m++; + for (k = 0; k < INPUT_MANY; k++) + inputset[k].brightness = value; + } else if (V4L2_CID_CONTRAST == easycap_control[i].id) { + m++; + for (k = 0; k < INPUT_MANY; k++) + inputset[k].contrast = value; + } else if (V4L2_CID_SATURATION == easycap_control[i].id) { + m++; + for (k = 0; k < INPUT_MANY; k++) + inputset[k].saturation = value; + } else if (V4L2_CID_HUE == easycap_control[i].id) { + m++; + for (k = 0; k < INPUT_MANY; k++) + inputset[k].hue = value; + } + } + + if (4 != m) { + SAM("ERROR: inputset[]->brightness underpopulated\n"); + return -ENOENT; + } + for (k = 0; k < INPUT_MANY; k++) + inputset[k].input = k; + JOM(4, "populated inputset[]\n"); + JOM(4, "finished initialization\n"); + } else { +/*---------------------------------------------------------------------------*/ +/* + * FIXME + * + * IDENTIFY THE APPROPRIATE POINTER peasycap FOR INTERFACES 1 AND 2. + * THE ADDRESS OF peasycap->pusb_device IS RELUCTANTLY USED FOR THIS PURPOSE. + */ +/*---------------------------------------------------------------------------*/ + for (ndong = 0; ndong < DONGLE_MANY; ndong++) { + if (usbdev == easycapdc60_dongle[ndong].peasycap-> + pusb_device) { + peasycap = easycapdc60_dongle[ndong].peasycap; + JOT(8, "intf[%i]: dongle[%i].peasycap\n", + bInterfaceNumber, ndong); + break; + } + } + if (DONGLE_MANY <= ndong) { + SAY("ERROR: peasycap is unknown when probing interface %i\n", + bInterfaceNumber); + return -ENODEV; + } + if (!peasycap) { + SAY("ERROR: peasycap is NULL when probing interface %i\n", + bInterfaceNumber); + return -ENODEV; + } + } +/*---------------------------------------------------------------------------*/ + if ((USB_CLASS_VIDEO == bInterfaceClass) || + (USB_CLASS_VENDOR_SPEC == bInterfaceClass)) { + if (-1 == peasycap->video_interface) { + peasycap->video_interface = bInterfaceNumber; + JOM(4, "setting peasycap->video_interface=%i\n", + peasycap->video_interface); + } else { + if (peasycap->video_interface != bInterfaceNumber) { + SAM("ERROR: attempting to reset " + "peasycap->video_interface\n"); + SAM("...... continuing with " + "%i=peasycap->video_interface\n", + peasycap->video_interface); + } + } + } else if ((USB_CLASS_AUDIO == bInterfaceClass) && + (USB_SUBCLASS_AUDIOSTREAMING == bInterfaceSubClass)) { + if (-1 == peasycap->audio_interface) { + peasycap->audio_interface = bInterfaceNumber; + JOM(4, "setting peasycap->audio_interface=%i\n", + peasycap->audio_interface); + } else { + if (peasycap->audio_interface != bInterfaceNumber) { + SAM("ERROR: attempting to reset " + "peasycap->audio_interface\n"); + SAM("...... continuing with " + "%i=peasycap->audio_interface\n", + peasycap->audio_interface); + } + } + } +/*---------------------------------------------------------------------------*/ +/* + * INVESTIGATE ALL ALTSETTINGS. + * DONE IN DETAIL BECAUSE USB DEVICE 05e1:0408 HAS DISPARATE INCARNATIONS. + */ +/*---------------------------------------------------------------------------*/ + isokalt = 0; + + for (i = 0; i < intf->num_altsetting; i++) { + alt = usb_altnum_to_altsetting(intf, i); + if (!alt) { + SAM("ERROR: alt is NULL\n"); + return -EFAULT; + } + interface = &alt->desc; + if (!interface) { + SAM("ERROR: intf_descriptor is NULL\n"); + return -EFAULT; + } + + if (0 == interface->bNumEndpoints) + JOM(4, "intf[%i]alt[%i] has no endpoints\n", + bInterfaceNumber, i); +/*---------------------------------------------------------------------------*/ + for (j = 0; j < interface->bNumEndpoints; j++) { + ep = &alt->endpoint[j].desc; + if (!ep) { + SAM("ERROR: ep is NULL.\n"); + SAM("...... skipping\n"); + continue; + } + + if (!usb_endpoint_is_isoc_in(ep)) { + JOM(4, "intf[%i]alt[%i]end[%i] is a %d endpoint\n", + bInterfaceNumber, + i, j, ep->bmAttributes); + if (usb_endpoint_dir_out(ep)) { + SAM("ERROR: OUT endpoint unexpected\n"); + SAM("...... continuing\n"); + } + continue; + } + switch (bInterfaceClass) { + case USB_CLASS_VIDEO: + case USB_CLASS_VENDOR_SPEC: { + if (ep->wMaxPacketSize) { + if (8 > isokalt) { + okalt[isokalt] = i; + JOM(4, + "%i=okalt[%i]\n", + okalt[isokalt], + isokalt); + okepn[isokalt] = + ep-> + bEndpointAddress & + 0x0F; + JOM(4, + "%i=okepn[%i]\n", + okepn[isokalt], + isokalt); + okmps[isokalt] = + le16_to_cpu(ep-> + wMaxPacketSize); + JOM(4, + "%i=okmps[%i]\n", + okmps[isokalt], + isokalt); + isokalt++; + } + } else { + if (-1 == peasycap-> + video_altsetting_off) { + peasycap-> + video_altsetting_off = + i; + JOM(4, "%i=video_" + "altsetting_off " + "<====\n", + peasycap-> + video_altsetting_off); + } else { + SAM("ERROR: peasycap" + "->video_altsetting_" + "off already set\n"); + SAM("...... " + "continuing with " + "%i=peasycap->video_" + "altsetting_off\n", + peasycap-> + video_altsetting_off); + } + } + break; + } + case USB_CLASS_AUDIO: { + if (bInterfaceSubClass != + USB_SUBCLASS_AUDIOSTREAMING) + break; + if (!peasycap) { + SAM("MISTAKE: " + "peasycap is NULL\n"); + return -EFAULT; + } + if (ep->wMaxPacketSize) { + if (8 > isokalt) { + okalt[isokalt] = i ; + JOM(4, + "%i=okalt[%i]\n", + okalt[isokalt], + isokalt); + okepn[isokalt] = + ep-> + bEndpointAddress & + 0x0F; + JOM(4, + "%i=okepn[%i]\n", + okepn[isokalt], + isokalt); + okmps[isokalt] = + le16_to_cpu(ep-> + wMaxPacketSize); + JOM(4, + "%i=okmps[%i]\n", + okmps[isokalt], + isokalt); + isokalt++; + } + } else { + if (-1 == peasycap-> + audio_altsetting_off) { + peasycap-> + audio_altsetting_off = + i; + JOM(4, "%i=audio_" + "altsetting_off " + "<====\n", + peasycap-> + audio_altsetting_off); + } else { + SAM("ERROR: peasycap" + "->audio_altsetting_" + "off already set\n"); + SAM("...... " + "continuing with " + "%i=peasycap->" + "audio_altsetting_" + "off\n", + peasycap-> + audio_altsetting_off); + } + } + break; + } + default: + break; + } + if (0 == ep->wMaxPacketSize) { + JOM(4, "intf[%i]alt[%i]end[%i] " + "has zero packet size\n", + bInterfaceNumber, i, j); + } + } + } +/*---------------------------------------------------------------------------*/ +/* + * PERFORM INITIALIZATION OF THE PROBED INTERFACE + */ +/*---------------------------------------------------------------------------*/ + JOM(4, "initialization begins for interface %i\n", + interface->bInterfaceNumber); + switch (bInterfaceNumber) { +/*---------------------------------------------------------------------------*/ +/* + * INTERFACE 0 IS THE VIDEO INTERFACE + */ +/*---------------------------------------------------------------------------*/ + case 0: { + if (!peasycap) { + SAM("MISTAKE: peasycap is NULL\n"); + return -EFAULT; + } + if (!isokalt) { + SAM("ERROR: no viable video_altsetting_on\n"); + return -ENOENT; + } else { + peasycap->video_altsetting_on = okalt[isokalt - 1]; + JOM(4, "%i=video_altsetting_on <====\n", + peasycap->video_altsetting_on); + } +/*---------------------------------------------------------------------------*/ +/* + * DECIDE THE VIDEO STREAMING PARAMETERS + */ +/*---------------------------------------------------------------------------*/ + peasycap->video_endpointnumber = okepn[isokalt - 1]; + JOM(4, "%i=video_endpointnumber\n", peasycap->video_endpointnumber); + maxpacketsize = okmps[isokalt - 1]; + + peasycap->video_isoc_maxframesize = + min(maxpacketsize, USB_2_0_MAXPACKETSIZE); + if (0 >= peasycap->video_isoc_maxframesize) { + SAM("ERROR: bad video_isoc_maxframesize\n"); + SAM(" possibly because port is USB 1.1\n"); + return -ENOENT; + } + JOM(4, "%i=video_isoc_maxframesize\n", + peasycap->video_isoc_maxframesize); + + peasycap->video_isoc_framesperdesc = VIDEO_ISOC_FRAMESPERDESC; + JOM(4, "%i=video_isoc_framesperdesc\n", + peasycap->video_isoc_framesperdesc); + if (0 >= peasycap->video_isoc_framesperdesc) { + SAM("ERROR: bad video_isoc_framesperdesc\n"); + return -ENOENT; + } + peasycap->video_isoc_buffer_size = + peasycap->video_isoc_maxframesize * + peasycap->video_isoc_framesperdesc; + JOM(4, "%i=video_isoc_buffer_size\n", + peasycap->video_isoc_buffer_size); + if ((PAGE_SIZE << VIDEO_ISOC_ORDER) < + peasycap->video_isoc_buffer_size) { + SAM("MISTAKE: peasycap->video_isoc_buffer_size too big\n"); + return -EFAULT; + } +/*---------------------------------------------------------------------------*/ + if (-1 == peasycap->video_interface) { + SAM("MISTAKE: video_interface is unset\n"); + return -EFAULT; + } + if (-1 == peasycap->video_altsetting_on) { + SAM("MISTAKE: video_altsetting_on is unset\n"); + return -EFAULT; + } + if (-1 == peasycap->video_altsetting_off) { + SAM("MISTAKE: video_interface_off is unset\n"); + return -EFAULT; + } + if (-1 == peasycap->video_endpointnumber) { + SAM("MISTAKE: video_endpointnumber is unset\n"); + return -EFAULT; + } + if (-1 == peasycap->video_isoc_maxframesize) { + SAM("MISTAKE: video_isoc_maxframesize is unset\n"); + return -EFAULT; + } + if (-1 == peasycap->video_isoc_buffer_size) { + SAM("MISTAKE: video_isoc_buffer_size is unset\n"); + return -EFAULT; + } +/*---------------------------------------------------------------------------*/ +/* + * ALLOCATE MEMORY FOR VIDEO BUFFERS. LISTS MUST BE INITIALIZED FIRST. + */ +/*---------------------------------------------------------------------------*/ + INIT_LIST_HEAD(&(peasycap->urb_video_head)); + peasycap->purb_video_head = &(peasycap->urb_video_head); +/*---------------------------------------------------------------------------*/ + JOM(4, "allocating %i frame buffers of size %li\n", + FRAME_BUFFER_MANY, (long int)FRAME_BUFFER_SIZE); + JOM(4, ".... each scattered over %li pages\n", + FRAME_BUFFER_SIZE/PAGE_SIZE); + + for (k = 0; k < FRAME_BUFFER_MANY; k++) { + for (m = 0; m < FRAME_BUFFER_SIZE/PAGE_SIZE; m++) { + if (peasycap->frame_buffer[k][m].pgo) + SAM("attempting to reallocate frame " + " buffers\n"); + else { + pbuf = (void *)__get_free_page(GFP_KERNEL); + if (!pbuf) { + SAM("ERROR: Could not allocate frame " + "buffer %i page %i\n", k, m); + return -ENOMEM; + } else + peasycap->allocation_video_page += 1; + peasycap->frame_buffer[k][m].pgo = pbuf; + } + peasycap->frame_buffer[k][m].pto = + peasycap->frame_buffer[k][m].pgo; + } + } + + peasycap->frame_fill = 0; + peasycap->frame_read = 0; + JOM(4, "allocation of frame buffers done: %i pages\n", k * + m); +/*---------------------------------------------------------------------------*/ + JOM(4, "allocating %i field buffers of size %li\n", + FIELD_BUFFER_MANY, (long int)FIELD_BUFFER_SIZE); + JOM(4, ".... each scattered over %li pages\n", + FIELD_BUFFER_SIZE/PAGE_SIZE); + + for (k = 0; k < FIELD_BUFFER_MANY; k++) { + for (m = 0; m < FIELD_BUFFER_SIZE/PAGE_SIZE; m++) { + if (peasycap->field_buffer[k][m].pgo) { + SAM("ERROR: attempting to reallocate " + "field buffers\n"); + } else { + pbuf = (void *) __get_free_page(GFP_KERNEL); + if (!pbuf) { + SAM("ERROR: Could not allocate field" + " buffer %i page %i\n", k, m); + return -ENOMEM; + } + else + peasycap->allocation_video_page += 1; + peasycap->field_buffer[k][m].pgo = pbuf; + } + peasycap->field_buffer[k][m].pto = + peasycap->field_buffer[k][m].pgo; + } + peasycap->field_buffer[k][0].kount = 0x0200; + } + peasycap->field_fill = 0; + peasycap->field_page = 0; + peasycap->field_read = 0; + JOM(4, "allocation of field buffers done: %i pages\n", k * + m); +/*---------------------------------------------------------------------------*/ + JOM(4, "allocating %i isoc video buffers of size %i\n", + VIDEO_ISOC_BUFFER_MANY, + peasycap->video_isoc_buffer_size); + JOM(4, ".... each occupying contiguous memory pages\n"); + + for (k = 0; k < VIDEO_ISOC_BUFFER_MANY; k++) { + pbuf = (void *)__get_free_pages(GFP_KERNEL, + VIDEO_ISOC_ORDER); + if (!pbuf) { + SAM("ERROR: Could not allocate isoc video buffer " + "%i\n", k); + return -ENOMEM; + } else + peasycap->allocation_video_page += + BIT(VIDEO_ISOC_ORDER); + + peasycap->video_isoc_buffer[k].pgo = pbuf; + peasycap->video_isoc_buffer[k].pto = + pbuf + peasycap->video_isoc_buffer_size; + peasycap->video_isoc_buffer[k].kount = k; + } + JOM(4, "allocation of isoc video buffers done: %i pages\n", + k * (0x01 << VIDEO_ISOC_ORDER)); +/*---------------------------------------------------------------------------*/ +/* + * ALLOCATE AND INITIALIZE MULTIPLE struct urb ... + */ +/*---------------------------------------------------------------------------*/ + JOM(4, "allocating %i struct urb.\n", VIDEO_ISOC_BUFFER_MANY); + JOM(4, "using %i=peasycap->video_isoc_framesperdesc\n", + peasycap->video_isoc_framesperdesc); + JOM(4, "using %i=peasycap->video_isoc_maxframesize\n", + peasycap->video_isoc_maxframesize); + JOM(4, "using %i=peasycap->video_isoc_buffer_sizen", + peasycap->video_isoc_buffer_size); + + for (k = 0; k < VIDEO_ISOC_BUFFER_MANY; k++) { + purb = usb_alloc_urb(peasycap->video_isoc_framesperdesc, + GFP_KERNEL); + if (!purb) { + SAM("ERROR: usb_alloc_urb returned NULL for buffer " + "%i\n", k); + return -ENOMEM; + } else + peasycap->allocation_video_urb += 1; +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + pdata_urb = kzalloc(sizeof(struct data_urb), GFP_KERNEL); + if (!pdata_urb) { + SAM("ERROR: Could not allocate struct data_urb.\n"); + return -ENOMEM; + } else + peasycap->allocation_video_struct += + sizeof(struct data_urb); + + pdata_urb->purb = purb; + pdata_urb->isbuf = k; + pdata_urb->length = 0; + list_add_tail(&(pdata_urb->list_head), + peasycap->purb_video_head); +/*---------------------------------------------------------------------------*/ +/* + * ... AND INITIALIZE THEM + */ +/*---------------------------------------------------------------------------*/ + if (!k) { + JOM(4, "initializing video urbs thus:\n"); + JOM(4, " purb->interval = 1;\n"); + JOM(4, " purb->dev = peasycap->pusb_device;\n"); + JOM(4, " purb->pipe = usb_rcvisocpipe" + "(peasycap->pusb_device,%i);\n", + peasycap->video_endpointnumber); + JOM(4, " purb->transfer_flags = URB_ISO_ASAP;\n"); + JOM(4, " purb->transfer_buffer = peasycap->" + "video_isoc_buffer[.].pgo;\n"); + JOM(4, " purb->transfer_buffer_length = %i;\n", + peasycap->video_isoc_buffer_size); + JOM(4, " purb->complete = easycap_complete;\n"); + JOM(4, " purb->context = peasycap;\n"); + JOM(4, " purb->start_frame = 0;\n"); + JOM(4, " purb->number_of_packets = %i;\n", + peasycap->video_isoc_framesperdesc); + JOM(4, " for (j = 0; j < %i; j++)\n", + peasycap->video_isoc_framesperdesc); + JOM(4, " {\n"); + JOM(4, " purb->iso_frame_desc[j].offset = j*%i;\n", + peasycap->video_isoc_maxframesize); + JOM(4, " purb->iso_frame_desc[j].length = %i;\n", + peasycap->video_isoc_maxframesize); + JOM(4, " }\n"); + } + + purb->interval = 1; + purb->dev = peasycap->pusb_device; + purb->pipe = usb_rcvisocpipe(peasycap->pusb_device, + peasycap->video_endpointnumber); + purb->transfer_flags = URB_ISO_ASAP; + purb->transfer_buffer = peasycap->video_isoc_buffer[k].pgo; + purb->transfer_buffer_length = + peasycap->video_isoc_buffer_size; + purb->complete = easycap_complete; + purb->context = peasycap; + purb->start_frame = 0; + purb->number_of_packets = peasycap->video_isoc_framesperdesc; + for (j = 0; j < peasycap->video_isoc_framesperdesc; j++) { + purb->iso_frame_desc[j].offset = j * + peasycap->video_isoc_maxframesize; + purb->iso_frame_desc[j].length = + peasycap->video_isoc_maxframesize; + } + } + JOM(4, "allocation of %i struct urb done.\n", k); +/*--------------------------------------------------------------------------*/ +/* + * SAVE POINTER peasycap IN THIS INTERFACE. + */ +/*--------------------------------------------------------------------------*/ + usb_set_intfdata(intf, peasycap); +/*---------------------------------------------------------------------------*/ +/* + * IT IS ESSENTIAL TO INITIALIZE THE HARDWARE BEFORE, RATHER THAN AFTER, + * THE DEVICE IS REGISTERED, BECAUSE SOME VERSIONS OF THE videodev MODULE + * CALL easycap_open() IMMEDIATELY AFTER REGISTRATION, CAUSING A CLASH. + * BEWARE. +*/ +/*---------------------------------------------------------------------------*/ + peasycap->ntsc = easycap_ntsc; + JOM(8, "defaulting initially to %s\n", + easycap_ntsc ? "NTSC" : "PAL"); + rc = reset(peasycap); + if (rc) { + SAM("ERROR: reset() rc = %i\n", rc); + return -EFAULT; + } +/*--------------------------------------------------------------------------*/ +/* + * THE VIDEO DEVICE CAN BE REGISTERED NOW, AS IT IS READY. + */ +/*--------------------------------------------------------------------------*/ + if (v4l2_device_register(&intf->dev, &peasycap->v4l2_device)) { + SAM("v4l2_device_register() failed\n"); + return -ENODEV; + } + JOM(4, "registered device instance: %s\n", + peasycap->v4l2_device.name); +/*---------------------------------------------------------------------------*/ +/* + * FIXME + * + * + * THIS IS BELIEVED TO BE HARMLESS, BUT MAY WELL BE UNNECESSARY OR WRONG: +*/ +/*---------------------------------------------------------------------------*/ + peasycap->video_device.v4l2_dev = NULL; +/*---------------------------------------------------------------------------*/ + + + strcpy(&peasycap->video_device.name[0], "easycapdc60"); + peasycap->video_device.fops = &v4l2_fops; + peasycap->video_device.minor = -1; + peasycap->video_device.release = (void *)(&videodev_release); + + video_set_drvdata(&(peasycap->video_device), (void *)peasycap); + + if (0 != (video_register_device(&(peasycap->video_device), + VFL_TYPE_GRABBER, -1))) { + err("Not able to register with videodev"); + videodev_release(&(peasycap->video_device)); + return -ENODEV; + } else { + (peasycap->registered_video)++; + SAM("registered with videodev: %i=minor\n", + peasycap->video_device.minor); + peasycap->minor = peasycap->video_device.minor; + } +/*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^*/ + + break; + } +/*--------------------------------------------------------------------------*/ +/* + * INTERFACE 1 IS THE AUDIO CONTROL INTERFACE + * INTERFACE 2 IS THE AUDIO STREAMING INTERFACE + */ +/*--------------------------------------------------------------------------*/ + case 1: { + if (!peasycap) { + SAM("MISTAKE: peasycap is NULL\n"); + return -EFAULT; + } +/*--------------------------------------------------------------------------*/ +/* + * SAVE POINTER peasycap IN INTERFACE 1 + */ +/*--------------------------------------------------------------------------*/ + usb_set_intfdata(intf, peasycap); + JOM(4, "no initialization required for interface %i\n", + interface->bInterfaceNumber); + break; + } +/*--------------------------------------------------------------------------*/ + case 2: { + if (!peasycap) { + SAM("MISTAKE: peasycap is NULL\n"); + return -EFAULT; + } + if (!isokalt) { + SAM("ERROR: no viable audio_altsetting_on\n"); + return -ENOENT; + } else { + peasycap->audio_altsetting_on = okalt[isokalt - 1]; + JOM(4, "%i=audio_altsetting_on <====\n", + peasycap->audio_altsetting_on); + } + + peasycap->audio_endpointnumber = okepn[isokalt - 1]; + JOM(4, "%i=audio_endpointnumber\n", peasycap->audio_endpointnumber); + + peasycap->audio_isoc_maxframesize = okmps[isokalt - 1]; + JOM(4, "%i=audio_isoc_maxframesize\n", + peasycap->audio_isoc_maxframesize); + if (0 >= peasycap->audio_isoc_maxframesize) { + SAM("ERROR: bad audio_isoc_maxframesize\n"); + return -ENOENT; + } + if (9 == peasycap->audio_isoc_maxframesize) { + peasycap->ilk |= 0x02; + SAM("audio hardware is microphone\n"); + peasycap->microphone = true; + peasycap->audio_pages_per_fragment = + PAGES_PER_AUDIO_FRAGMENT; + } else if (256 == peasycap->audio_isoc_maxframesize) { + peasycap->ilk &= ~0x02; + SAM("audio hardware is AC'97\n"); + peasycap->microphone = false; + peasycap->audio_pages_per_fragment = + PAGES_PER_AUDIO_FRAGMENT; + } else { + SAM("hardware is unidentified:\n"); + SAM("%i=audio_isoc_maxframesize\n", + peasycap->audio_isoc_maxframesize); + return -ENOENT; + } + + peasycap->audio_bytes_per_fragment = + peasycap->audio_pages_per_fragment * PAGE_SIZE; + peasycap->audio_buffer_page_many = (AUDIO_FRAGMENT_MANY * + peasycap->audio_pages_per_fragment); + + JOM(4, "%6i=AUDIO_FRAGMENT_MANY\n", AUDIO_FRAGMENT_MANY); + JOM(4, "%6i=audio_pages_per_fragment\n", + peasycap->audio_pages_per_fragment); + JOM(4, "%6i=audio_bytes_per_fragment\n", + peasycap->audio_bytes_per_fragment); + JOM(4, "%6i=audio_buffer_page_many\n", + peasycap->audio_buffer_page_many); + + peasycap->audio_isoc_framesperdesc = AUDIO_ISOC_FRAMESPERDESC; + + JOM(4, "%i=audio_isoc_framesperdesc\n", + peasycap->audio_isoc_framesperdesc); + if (0 >= peasycap->audio_isoc_framesperdesc) { + SAM("ERROR: bad audio_isoc_framesperdesc\n"); + return -ENOENT; + } + + peasycap->audio_isoc_buffer_size = + peasycap->audio_isoc_maxframesize * + peasycap->audio_isoc_framesperdesc; + JOM(4, "%i=audio_isoc_buffer_size\n", + peasycap->audio_isoc_buffer_size); + if (AUDIO_ISOC_BUFFER_SIZE < peasycap->audio_isoc_buffer_size) { + SAM("MISTAKE: audio_isoc_buffer_size bigger " + "than %li=AUDIO_ISOC_BUFFER_SIZE\n", + AUDIO_ISOC_BUFFER_SIZE); + return -EFAULT; + } + if (-1 == peasycap->audio_interface) { + SAM("MISTAKE: audio_interface is unset\n"); + return -EFAULT; + } + if (-1 == peasycap->audio_altsetting_on) { + SAM("MISTAKE: audio_altsetting_on is unset\n"); + return -EFAULT; + } + if (-1 == peasycap->audio_altsetting_off) { + SAM("MISTAKE: audio_interface_off is unset\n"); + return -EFAULT; + } + if (-1 == peasycap->audio_endpointnumber) { + SAM("MISTAKE: audio_endpointnumber is unset\n"); + return -EFAULT; + } + if (-1 == peasycap->audio_isoc_maxframesize) { + SAM("MISTAKE: audio_isoc_maxframesize is unset\n"); + return -EFAULT; + } + if (-1 == peasycap->audio_isoc_buffer_size) { + SAM("MISTAKE: audio_isoc_buffer_size is unset\n"); + return -EFAULT; + } +/*---------------------------------------------------------------------------*/ +/* + * ALLOCATE MEMORY FOR AUDIO BUFFERS. LISTS MUST BE INITIALIZED FIRST. + */ +/*---------------------------------------------------------------------------*/ + INIT_LIST_HEAD(&(peasycap->urb_audio_head)); + peasycap->purb_audio_head = &(peasycap->urb_audio_head); + +/*---------------------------------------------------------------------------*/ + JOM(4, "allocating %i isoc audio buffers of size %i\n", + AUDIO_ISOC_BUFFER_MANY, + peasycap->audio_isoc_buffer_size); + JOM(4, ".... each occupying contiguous memory pages\n"); + + for (k = 0; k < AUDIO_ISOC_BUFFER_MANY; k++) { + pbuf = (void *)__get_free_pages(GFP_KERNEL, + AUDIO_ISOC_ORDER); + if (!pbuf) { + SAM("ERROR: Could not allocate isoc audio buffer " + "%i\n", k); + return -ENOMEM; + } else + peasycap->allocation_audio_page += + BIT(AUDIO_ISOC_ORDER); + + peasycap->audio_isoc_buffer[k].pgo = pbuf; + peasycap->audio_isoc_buffer[k].pto = pbuf + + peasycap->audio_isoc_buffer_size; + peasycap->audio_isoc_buffer[k].kount = k; + } + JOM(4, "allocation of isoc audio buffers done.\n"); +/*---------------------------------------------------------------------------*/ +/* + * ALLOCATE AND INITIALIZE MULTIPLE struct urb ... + */ +/*---------------------------------------------------------------------------*/ + JOM(4, "allocating %i struct urb.\n", AUDIO_ISOC_BUFFER_MANY); + JOM(4, "using %i=peasycap->audio_isoc_framesperdesc\n", + peasycap->audio_isoc_framesperdesc); + JOM(4, "using %i=peasycap->audio_isoc_maxframesize\n", + peasycap->audio_isoc_maxframesize); + JOM(4, "using %i=peasycap->audio_isoc_buffer_size\n", + peasycap->audio_isoc_buffer_size); + + for (k = 0; k < AUDIO_ISOC_BUFFER_MANY; k++) { + purb = usb_alloc_urb(peasycap->audio_isoc_framesperdesc, + GFP_KERNEL); + if (!purb) { + SAM("ERROR: usb_alloc_urb returned NULL for buffer " + "%i\n", k); + return -ENOMEM; + } + peasycap->allocation_audio_urb += 1 ; +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + pdata_urb = kzalloc(sizeof(struct data_urb), GFP_KERNEL); + if (!pdata_urb) { + SAM("ERROR: Could not allocate struct data_urb.\n"); + return -ENOMEM; + } + peasycap->allocation_audio_struct += + sizeof(struct data_urb); + + pdata_urb->purb = purb; + pdata_urb->isbuf = k; + pdata_urb->length = 0; + list_add_tail(&(pdata_urb->list_head), + peasycap->purb_audio_head); +/*---------------------------------------------------------------------------*/ +/* + * ... AND INITIALIZE THEM + */ +/*---------------------------------------------------------------------------*/ + if (!k) { + JOM(4, "initializing audio urbs thus:\n"); + JOM(4, " purb->interval = 1;\n"); + JOM(4, " purb->dev = peasycap->pusb_device;\n"); + JOM(4, " purb->pipe = usb_rcvisocpipe(peasycap->" + "pusb_device,%i);\n", + peasycap->audio_endpointnumber); + JOM(4, " purb->transfer_flags = URB_ISO_ASAP;\n"); + JOM(4, " purb->transfer_buffer = " + "peasycap->audio_isoc_buffer[.].pgo;\n"); + JOM(4, " purb->transfer_buffer_length = %i;\n", + peasycap->audio_isoc_buffer_size); + JOM(4, " purb->complete = easycap_alsa_complete;\n"); + JOM(4, " purb->context = peasycap;\n"); + JOM(4, " purb->start_frame = 0;\n"); + JOM(4, " purb->number_of_packets = %i;\n", + peasycap->audio_isoc_framesperdesc); + JOM(4, " for (j = 0; j < %i; j++)\n", + peasycap->audio_isoc_framesperdesc); + JOM(4, " {\n"); + JOM(4, " purb->iso_frame_desc[j].offset = j*%i;\n", + peasycap->audio_isoc_maxframesize); + JOM(4, " purb->iso_frame_desc[j].length = %i;\n", + peasycap->audio_isoc_maxframesize); + JOM(4, " }\n"); + } + + purb->interval = 1; + purb->dev = peasycap->pusb_device; + purb->pipe = usb_rcvisocpipe(peasycap->pusb_device, + peasycap->audio_endpointnumber); + purb->transfer_flags = URB_ISO_ASAP; + purb->transfer_buffer = peasycap->audio_isoc_buffer[k].pgo; + purb->transfer_buffer_length = + peasycap->audio_isoc_buffer_size; + purb->complete = easycap_alsa_complete; + purb->context = peasycap; + purb->start_frame = 0; + purb->number_of_packets = peasycap->audio_isoc_framesperdesc; + for (j = 0; j < peasycap->audio_isoc_framesperdesc; j++) { + purb->iso_frame_desc[j].offset = j * + peasycap->audio_isoc_maxframesize; + purb->iso_frame_desc[j].length = + peasycap->audio_isoc_maxframesize; + } + } + JOM(4, "allocation of %i struct urb done.\n", k); +/*---------------------------------------------------------------------------*/ +/* + * SAVE POINTER peasycap IN THIS INTERFACE. + */ +/*---------------------------------------------------------------------------*/ + usb_set_intfdata(intf, peasycap); +/*---------------------------------------------------------------------------*/ +/* + * THE AUDIO DEVICE CAN BE REGISTERED NOW, AS IT IS READY. + */ +/*---------------------------------------------------------------------------*/ + JOM(4, "initializing ALSA card\n"); + + rc = easycap_alsa_probe(peasycap); + if (rc) { + err("easycap_alsa_probe() rc = %i\n", rc); + return -ENODEV; + } + + + JOM(8, "kref_get() with %i=kref.refcount.counter\n", + peasycap->kref.refcount.counter); + kref_get(&peasycap->kref); + peasycap->registered_audio++; + break; + } +/*---------------------------------------------------------------------------*/ +/* + * INTERFACES OTHER THAN 0, 1 AND 2 ARE UNEXPECTED + */ +/*---------------------------------------------------------------------------*/ + default: + JOM(4, "ERROR: unexpected interface %i\n", bInterfaceNumber); + return -EINVAL; + } + SAM("ends successfully for interface %i\n", bInterfaceNumber); + return 0; +} +/*****************************************************************************/ +/*---------------------------------------------------------------------------*/ +/* + * WHEN THIS FUNCTION IS CALLED THE EasyCAP HAS ALREADY BEEN PHYSICALLY + * UNPLUGGED. HENCE peasycap->pusb_device IS NO LONGER VALID. + * + * THIS FUNCTION AFFECTS ALSA. BEWARE. + */ +/*---------------------------------------------------------------------------*/ +static void easycap_usb_disconnect(struct usb_interface *pusb_interface) +{ + struct usb_host_interface *pusb_host_interface; + struct usb_interface_descriptor *pusb_interface_descriptor; + u8 bInterfaceNumber; + struct easycap *peasycap; + + struct list_head *plist_head; + struct data_urb *pdata_urb; + int minor, m, kd; + + JOT(4, "\n"); + + pusb_host_interface = pusb_interface->cur_altsetting; + if (!pusb_host_interface) { + JOT(4, "ERROR: pusb_host_interface is NULL\n"); + return; + } + pusb_interface_descriptor = &(pusb_host_interface->desc); + if (!pusb_interface_descriptor) { + JOT(4, "ERROR: pusb_interface_descriptor is NULL\n"); + return; + } + bInterfaceNumber = pusb_interface_descriptor->bInterfaceNumber; + minor = pusb_interface->minor; + JOT(4, "intf[%i]: minor=%i\n", bInterfaceNumber, minor); + + if (1 == bInterfaceNumber) + return; + + peasycap = usb_get_intfdata(pusb_interface); + if (!peasycap) { + SAY("ERROR: peasycap is NULL\n"); + return; + } +/*---------------------------------------------------------------------------*/ +/* + * IF THE WAIT QUEUES ARE NOT CLEARED A DEADLOCK IS POSSIBLE. BEWARE. +*/ +/*---------------------------------------------------------------------------*/ + peasycap->video_eof = 1; + peasycap->audio_eof = 1; + wake_up_interruptible(&(peasycap->wq_video)); + wake_up_interruptible(&(peasycap->wq_audio)); +/*---------------------------------------------------------------------------*/ + switch (bInterfaceNumber) { + case 0: { + if (peasycap->purb_video_head) { + JOM(4, "killing video urbs\n"); + m = 0; + list_for_each(plist_head, peasycap->purb_video_head) { + pdata_urb = list_entry(plist_head, + struct data_urb, list_head); + if (pdata_urb) { + if (pdata_urb->purb) { + usb_kill_urb(pdata_urb->purb); + m++; + } + } + } + JOM(4, "%i video urbs killed\n", m); + } + break; + } +/*---------------------------------------------------------------------------*/ + case 2: { + if (peasycap->purb_audio_head) { + JOM(4, "killing audio urbs\n"); + m = 0; + list_for_each(plist_head, peasycap->purb_audio_head) { + pdata_urb = list_entry(plist_head, + struct data_urb, list_head); + if (pdata_urb) { + if (pdata_urb->purb) { + usb_kill_urb(pdata_urb->purb); + m++; + } + } + } + JOM(4, "%i audio urbs killed\n", m); + } + break; + } + default: + break; + } +/*--------------------------------------------------------------------------*/ +/* + * DEREGISTER + * + * THIS PROCEDURE WILL BLOCK UNTIL easycap_poll(), VIDEO IOCTL AND AUDIO + * IOCTL ARE ALL UNLOCKED. IF THIS IS NOT DONE AN Oops CAN OCCUR WHEN + * AN EasyCAP IS UNPLUGGED WHILE THE URBS ARE RUNNING. BEWARE. + */ +/*--------------------------------------------------------------------------*/ + kd = isdongle(peasycap); + switch (bInterfaceNumber) { + case 0: { + if (0 <= kd && DONGLE_MANY > kd) { + wake_up_interruptible(&peasycap->wq_video); + JOM(4, "about to lock dongle[%i].mutex_video\n", kd); + if (mutex_lock_interruptible(&easycapdc60_dongle[kd]. + mutex_video)) { + SAY("ERROR: " + "cannot lock dongle[%i].mutex_video\n", kd); + return; + } + JOM(4, "locked dongle[%i].mutex_video\n", kd); + } else { + SAY("ERROR: %i=kd is bad: cannot lock dongle\n", kd); + } +/*---------------------------------------------------------------------------*/ + if (!peasycap->v4l2_device.name[0]) { + SAM("ERROR: peasycap->v4l2_device.name is empty\n"); + if (0 <= kd && DONGLE_MANY > kd) + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return; + } + v4l2_device_disconnect(&peasycap->v4l2_device); + JOM(4, "v4l2_device_disconnect() OK\n"); + v4l2_device_unregister(&peasycap->v4l2_device); + JOM(4, "v4l2_device_unregister() OK\n"); + + video_unregister_device(&peasycap->video_device); + JOM(4, "intf[%i]: video_unregister_device() minor=%i\n", + bInterfaceNumber, minor); + peasycap->registered_video--; +/*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^*/ + + if (0 <= kd && DONGLE_MANY > kd) { + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + JOM(4, "unlocked dongle[%i].mutex_video\n", kd); + } + break; + } + case 2: { + if (0 <= kd && DONGLE_MANY > kd) { + wake_up_interruptible(&peasycap->wq_audio); + JOM(4, "about to lock dongle[%i].mutex_audio\n", kd); + if (mutex_lock_interruptible(&easycapdc60_dongle[kd]. + mutex_audio)) { + SAY("ERROR: " + "cannot lock dongle[%i].mutex_audio\n", kd); + return; + } + JOM(4, "locked dongle[%i].mutex_audio\n", kd); + } else + SAY("ERROR: %i=kd is bad: cannot lock dongle\n", kd); + if (0 != snd_card_free(peasycap->psnd_card)) { + SAY("ERROR: snd_card_free() failed\n"); + } else { + peasycap->psnd_card = NULL; + (peasycap->registered_audio)--; + } + if (0 <= kd && DONGLE_MANY > kd) { + mutex_unlock(&easycapdc60_dongle[kd].mutex_audio); + JOM(4, "unlocked dongle[%i].mutex_audio\n", kd); + } + break; + } + default: + break; + } +/*---------------------------------------------------------------------------*/ +/* + * CALL easycap_delete() IF NO REMAINING REFERENCES TO peasycap + * (ALSO WHEN ALSA HAS BEEN IN USE) + */ +/*---------------------------------------------------------------------------*/ + if (!peasycap->kref.refcount.counter) { + SAM("ERROR: peasycap->kref.refcount.counter is zero " + "so cannot call kref_put()\n"); + SAM("ending unsuccessfully: may cause memory leak\n"); + return; + } + if (0 <= kd && DONGLE_MANY > kd) { + JOM(4, "about to lock dongle[%i].mutex_video\n", kd); + if (mutex_lock_interruptible(&easycapdc60_dongle[kd].mutex_video)) { + SAY("ERROR: cannot lock dongle[%i].mutex_video\n", kd); + SAM("ending unsuccessfully: may cause memory leak\n"); + return; + } + JOM(4, "locked dongle[%i].mutex_video\n", kd); + JOM(4, "about to lock dongle[%i].mutex_audio\n", kd); + if (mutex_lock_interruptible(&easycapdc60_dongle[kd].mutex_audio)) { + SAY("ERROR: cannot lock dongle[%i].mutex_audio\n", kd); + mutex_unlock(&(easycapdc60_dongle[kd].mutex_video)); + JOM(4, "unlocked dongle[%i].mutex_video\n", kd); + SAM("ending unsuccessfully: may cause memory leak\n"); + return; + } + JOM(4, "locked dongle[%i].mutex_audio\n", kd); + } + JOM(4, "intf[%i]: %i=peasycap->kref.refcount.counter\n", + bInterfaceNumber, (int)peasycap->kref.refcount.counter); + kref_put(&peasycap->kref, easycap_delete); + JOT(4, "intf[%i]: kref_put() done.\n", bInterfaceNumber); + if (0 <= kd && DONGLE_MANY > kd) { + mutex_unlock(&(easycapdc60_dongle[kd].mutex_audio)); + JOT(4, "unlocked dongle[%i].mutex_audio\n", kd); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + JOT(4, "unlocked dongle[%i].mutex_video\n", kd); + } +/*---------------------------------------------------------------------------*/ + JOM(4, "ends\n"); + return; +} +/*****************************************************************************/ + +/*---------------------------------------------------------------------------*/ +/* + * PARAMETERS APPLICABLE TO ENTIRE DRIVER, I.E. BOTH VIDEO AND AUDIO + */ +/*---------------------------------------------------------------------------*/ +static struct usb_device_id easycap_usb_device_id_table[] = { + {USB_DEVICE(USB_EASYCAP_VENDOR_ID, USB_EASYCAP_PRODUCT_ID)}, + { } +}; + +MODULE_DEVICE_TABLE(usb, easycap_usb_device_id_table); +struct usb_driver easycap_usb_driver = { + .name = "easycap", + .id_table = easycap_usb_device_id_table, + .probe = easycap_usb_probe, + .disconnect = easycap_usb_disconnect, +}; + +static int __init easycap_module_init(void) +{ + int k, rc; + + printk(KERN_INFO "Easycap version: "EASYCAP_DRIVER_VERSION "\n"); + + JOT(4, "begins. %i=debug %i=bars %i=gain\n", + easycap_debug, easycap_bars, easycap_gain); + + mutex_init(&mutex_dongle); + for (k = 0; k < DONGLE_MANY; k++) { + easycapdc60_dongle[k].peasycap = NULL; + mutex_init(&easycapdc60_dongle[k].mutex_video); + mutex_init(&easycapdc60_dongle[k].mutex_audio); + } + rc = usb_register(&easycap_usb_driver); + if (rc) + printk(KERN_ERR "Easycap: usb_register failed rc=%d\n", rc); + + return rc; +} +/*****************************************************************************/ +static void __exit easycap_module_exit(void) +{ + usb_deregister(&easycap_usb_driver); +} +/*****************************************************************************/ + +module_init(easycap_module_init); +module_exit(easycap_module_exit); + +/*****************************************************************************/ diff --git a/drivers/staging/media/easycap/easycap_settings.c b/drivers/staging/media/easycap/easycap_settings.c new file mode 100644 index 0000000..70f59b1 --- /dev/null +++ b/drivers/staging/media/easycap/easycap_settings.c @@ -0,0 +1,696 @@ +/****************************************************************************** +* * +* easycap_settings.c * +* * +******************************************************************************/ +/* + * + * Copyright (C) 2010 R.M. Thomas <rmthomas@sciolus.org> + * + * + * This 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. + * + * The software 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 software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * +*/ +/*****************************************************************************/ + +#include "easycap.h" + +/*---------------------------------------------------------------------------*/ +/* + * THE LEAST SIGNIFICANT BIT OF easycap_standard.mask HAS MEANING: + * 0 => 25 fps + * 1 => 30 fps + * + * THE MOST SIGNIFICANT BIT OF easycap_standard.mask HAS MEANING: + * 0 => full framerate + * 1 => 20% framerate + */ +/*---------------------------------------------------------------------------*/ +const struct easycap_standard easycap_standard[] = { + { + .mask = 0x00FF & PAL_BGHIN , + .v4l2_standard = { + .index = PAL_BGHIN, + .id = (V4L2_STD_PAL_B | + V4L2_STD_PAL_G | V4L2_STD_PAL_H | + V4L2_STD_PAL_I | V4L2_STD_PAL_N), + .name = "PAL_BGHIN", + .frameperiod = {1, 25}, + .framelines = 625, + .reserved = {0, 0, 0, 0} + } + }, +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + { + .mask = 0x00FF & NTSC_N_443 , + .v4l2_standard = { + .index = NTSC_N_443, + .id = V4L2_STD_UNKNOWN, + .name = "NTSC_N_443", + .frameperiod = {1, 25}, + .framelines = 480, + .reserved = {0, 0, 0, 0} + } + }, +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + { + .mask = 0x00FF & PAL_Nc , + .v4l2_standard = { + .index = PAL_Nc, + .id = V4L2_STD_PAL_Nc, + .name = "PAL_Nc", + .frameperiod = {1, 25}, + .framelines = 625, + .reserved = {0, 0, 0, 0} + } + }, +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + { + .mask = 0x00FF & NTSC_N , + .v4l2_standard = { + .index = NTSC_N, + .id = V4L2_STD_UNKNOWN, + .name = "NTSC_N", + .frameperiod = {1, 25}, + .framelines = 525, + .reserved = {0, 0, 0, 0} + } + }, +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + { + .mask = 0x00FF & SECAM , + .v4l2_standard = { + .index = SECAM, + .id = V4L2_STD_SECAM, + .name = "SECAM", + .frameperiod = {1, 25}, + .framelines = 625, + .reserved = {0, 0, 0, 0} + } + }, +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + { + .mask = 0x00FF & NTSC_M , + .v4l2_standard = { + .index = NTSC_M, + .id = V4L2_STD_NTSC_M, + .name = "NTSC_M", + .frameperiod = {1, 30}, + .framelines = 525, + .reserved = {0, 0, 0, 0} + } + }, +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + { + .mask = 0x00FF & NTSC_M_JP , + .v4l2_standard = { + .index = NTSC_M_JP, + .id = V4L2_STD_NTSC_M_JP, + .name = "NTSC_M_JP", + .frameperiod = {1, 30}, + .framelines = 525, + .reserved = {0, 0, 0, 0} + } + }, +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + { + .mask = 0x00FF & PAL_60 , + .v4l2_standard = { + .index = PAL_60, + .id = V4L2_STD_PAL_60, + .name = "PAL_60", + .frameperiod = {1, 30}, + .framelines = 525, + .reserved = {0, 0, 0, 0} + } + }, +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + { + .mask = 0x00FF & NTSC_443 , + .v4l2_standard = { + .index = NTSC_443, + .id = V4L2_STD_NTSC_443, + .name = "NTSC_443", + .frameperiod = {1, 30}, + .framelines = 525, + .reserved = {0, 0, 0, 0} + } + }, +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + { + .mask = 0x00FF & PAL_M , + .v4l2_standard = { + .index = PAL_M, + .id = V4L2_STD_PAL_M, + .name = "PAL_M", + .frameperiod = {1, 30}, + .framelines = 525, + .reserved = {0, 0, 0, 0} + } + }, +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + { + .mask = 0x8000 | (0x00FF & PAL_BGHIN_SLOW), + .v4l2_standard = { + .index = PAL_BGHIN_SLOW, + .id = (V4L2_STD_PAL_B | V4L2_STD_PAL_G | + V4L2_STD_PAL_H | + V4L2_STD_PAL_I | V4L2_STD_PAL_N | + (((v4l2_std_id)0x01) << 32)), + .name = "PAL_BGHIN_SLOW", + .frameperiod = {1, 5}, + .framelines = 625, + .reserved = {0, 0, 0, 0} + } + }, +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + { + .mask = 0x8000 | (0x00FF & NTSC_N_443_SLOW), + .v4l2_standard = { + .index = NTSC_N_443_SLOW, + .id = (V4L2_STD_UNKNOWN | (((v4l2_std_id)0x11) << 32)), + .name = "NTSC_N_443_SLOW", + .frameperiod = {1, 5}, + .framelines = 480, + .reserved = {0, 0, 0, 0} + } + }, +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + { + .mask = 0x8000 | (0x00FF & PAL_Nc_SLOW), + .v4l2_standard = { + .index = PAL_Nc_SLOW, + .id = (V4L2_STD_PAL_Nc | (((v4l2_std_id)0x01) << 32)), + .name = "PAL_Nc_SLOW", + .frameperiod = {1, 5}, + .framelines = 625, + .reserved = {0, 0, 0, 0} + } + }, +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + { + .mask = 0x8000 | (0x00FF & NTSC_N_SLOW), + .v4l2_standard = { + .index = NTSC_N_SLOW, + .id = (V4L2_STD_UNKNOWN | (((v4l2_std_id)0x21) << 32)), + .name = "NTSC_N_SLOW", + .frameperiod = {1, 5}, + .framelines = 525, + .reserved = {0, 0, 0, 0} + } + }, +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + { + .mask = 0x8000 | (0x00FF & SECAM_SLOW), + .v4l2_standard = { + .index = SECAM_SLOW, + .id = (V4L2_STD_SECAM | (((v4l2_std_id)0x01) << 32)), + .name = "SECAM_SLOW", + .frameperiod = {1, 5}, + .framelines = 625, + .reserved = {0, 0, 0, 0} + } + }, +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + { + .mask = 0x8000 | (0x00FF & NTSC_M_SLOW), + .v4l2_standard = { + .index = NTSC_M_SLOW, + .id = (V4L2_STD_NTSC_M | (((v4l2_std_id)0x01) << 32)), + .name = "NTSC_M_SLOW", + .frameperiod = {1, 6}, + .framelines = 525, + .reserved = {0, 0, 0, 0} + } + }, +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + { + .mask = 0x8000 | (0x00FF & NTSC_M_JP_SLOW), + .v4l2_standard = { + .index = NTSC_M_JP_SLOW, + .id = (V4L2_STD_NTSC_M_JP | + (((v4l2_std_id)0x01) << 32)), + .name = "NTSC_M_JP_SLOW", + .frameperiod = {1, 6}, + .framelines = 525, + .reserved = {0, 0, 0, 0} + } + }, +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + { + .mask = 0x8000 | (0x00FF & PAL_60_SLOW), + .v4l2_standard = { + .index = PAL_60_SLOW, + .id = (V4L2_STD_PAL_60 | (((v4l2_std_id)0x01) << 32)), + .name = "PAL_60_SLOW", + .frameperiod = {1, 6}, + .framelines = 525, + .reserved = {0, 0, 0, 0} + } + }, +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + { + .mask = 0x8000 | (0x00FF & NTSC_443_SLOW), + .v4l2_standard = { + .index = NTSC_443_SLOW, + .id = (V4L2_STD_NTSC_443 | (((v4l2_std_id)0x01) << 32)), + .name = "NTSC_443_SLOW", + .frameperiod = {1, 6}, + .framelines = 525, + .reserved = {0, 0, 0, 0} + } + }, +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + { + .mask = 0x8000 | (0x00FF & PAL_M_SLOW), + .v4l2_standard = { + .index = PAL_M_SLOW, + .id = (V4L2_STD_PAL_M | (((v4l2_std_id)0x01) << 32)), + .name = "PAL_M_SLOW", + .frameperiod = {1, 6}, + .framelines = 525, + .reserved = {0, 0, 0, 0} + } + }, +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + { + .mask = 0xFFFF + } +}; +/*---------------------------------------------------------------------------*/ +/* + * THE 16-BIT easycap_format.mask HAS MEANING: + * (least significant) BIT 0: 0 => PAL, 25 FPS; 1 => NTSC, 30 FPS + * BITS 2-4: RESERVED FOR DIFFERENTIATING STANDARDS + * BITS 5-7: NUMBER OF BYTES PER PIXEL + * BIT 8: 0 => NATIVE BYTE ORDER; 1 => SWAPPED + * BITS 9-10: RESERVED FOR OTHER BYTE PERMUTATIONS + * BIT 11: 0 => UNDECIMATED; 1 => DECIMATED + * BIT 12: 0 => OFFER FRAMES; 1 => OFFER FIELDS + * BIT 13: 0 => FULL FRAMERATE; 1 => REDUCED + * (most significant) BITS 14-15: RESERVED FOR OTHER FIELD/FRAME OPTIONS + * IT FOLLOWS THAT: + * bytesperpixel IS ((0x00E0 & easycap_format.mask) >> 5) + * byteswaporder IS true IF (0 != (0x0100 & easycap_format.mask)) + * + * decimatepixel IS true IF (0 != (0x0800 & easycap_format.mask)) + * + * offerfields IS true IF (0 != (0x1000 & easycap_format.mask)) + */ +/*---------------------------------------------------------------------------*/ + +struct easycap_format easycap_format[1 + SETTINGS_MANY]; + +int fillin_formats(void) +{ + const char *name1, *name2, *name3, *name4; + struct v4l2_format *fmt; + int i, j, k, m, n; + u32 width, height, pixelformat, bytesperline, sizeimage; + u16 mask1, mask2, mask3, mask4; + enum v4l2_field field; + enum v4l2_colorspace colorspace; + + for (i = 0, n = 0; i < STANDARD_MANY; i++) { + mask1 = 0x0000; + switch (i) { + case PAL_BGHIN: { + mask1 = 0x1F & PAL_BGHIN; + name1 = "PAL_BGHIN"; + colorspace = V4L2_COLORSPACE_470_SYSTEM_BG; + break; + } + case SECAM: { + mask1 = 0x1F & SECAM; + name1 = "SECAM"; + colorspace = V4L2_COLORSPACE_470_SYSTEM_BG; + break; + } + case PAL_Nc: { + mask1 = 0x1F & PAL_Nc; + name1 = "PAL_Nc"; + colorspace = V4L2_COLORSPACE_470_SYSTEM_BG; + break; + } + case PAL_60: { + mask1 = 0x1F & PAL_60; + name1 = "PAL_60"; + colorspace = V4L2_COLORSPACE_470_SYSTEM_BG; + break; + } + case PAL_M: { + mask1 = 0x1F & PAL_M; + name1 = "PAL_M"; + colorspace = V4L2_COLORSPACE_470_SYSTEM_BG; + break; + } + case NTSC_M: { + mask1 = 0x1F & NTSC_M; + name1 = "NTSC_M"; + colorspace = V4L2_COLORSPACE_470_SYSTEM_M; + break; + } + case NTSC_443: { + mask1 = 0x1F & NTSC_443; + name1 = "NTSC_443"; + colorspace = V4L2_COLORSPACE_470_SYSTEM_M; + break; + } + case NTSC_M_JP: { + mask1 = 0x1F & NTSC_M_JP; + name1 = "NTSC_M_JP"; + colorspace = V4L2_COLORSPACE_470_SYSTEM_M; + break; + } + case NTSC_N: { + mask1 = 0x1F & NTSC_M; + name1 = "NTSC_N"; + colorspace = V4L2_COLORSPACE_470_SYSTEM_M; + break; + } + case NTSC_N_443: { + mask1 = 0x1F & NTSC_N_443; + name1 = "NTSC_N_443"; + colorspace = V4L2_COLORSPACE_470_SYSTEM_M; + break; + } + case PAL_BGHIN_SLOW: { + mask1 = 0x001F & PAL_BGHIN_SLOW; + mask1 |= 0x0200; + name1 = "PAL_BGHIN_SLOW"; + colorspace = V4L2_COLORSPACE_470_SYSTEM_BG; + break; + } + case SECAM_SLOW: { + mask1 = 0x001F & SECAM_SLOW; + mask1 |= 0x0200; + name1 = "SECAM_SLOW"; + colorspace = V4L2_COLORSPACE_470_SYSTEM_BG; + break; + } + case PAL_Nc_SLOW: { + mask1 = 0x001F & PAL_Nc_SLOW; + mask1 |= 0x0200; + name1 = "PAL_Nc_SLOW"; + colorspace = V4L2_COLORSPACE_470_SYSTEM_BG; + break; + } + case PAL_60_SLOW: { + mask1 = 0x001F & PAL_60_SLOW; + mask1 |= 0x0200; + name1 = "PAL_60_SLOW"; + colorspace = V4L2_COLORSPACE_470_SYSTEM_BG; + break; + } + case PAL_M_SLOW: { + mask1 = 0x001F & PAL_M_SLOW; + mask1 |= 0x0200; + name1 = "PAL_M_SLOW"; + colorspace = V4L2_COLORSPACE_470_SYSTEM_BG; + break; + } + case NTSC_M_SLOW: { + mask1 = 0x001F & NTSC_M_SLOW; + mask1 |= 0x0200; + name1 = "NTSC_M_SLOW"; + colorspace = V4L2_COLORSPACE_470_SYSTEM_M; + break; + } + case NTSC_443_SLOW: { + mask1 = 0x001F & NTSC_443_SLOW; + mask1 |= 0x0200; + name1 = "NTSC_443_SLOW"; + colorspace = V4L2_COLORSPACE_470_SYSTEM_M; + break; + } + case NTSC_M_JP_SLOW: { + mask1 = 0x001F & NTSC_M_JP_SLOW; + mask1 |= 0x0200; + name1 = "NTSC_M_JP_SLOW"; + colorspace = V4L2_COLORSPACE_470_SYSTEM_M; + break; + } + case NTSC_N_SLOW: { + mask1 = 0x001F & NTSC_N_SLOW; + mask1 |= 0x0200; + name1 = "NTSC_N_SLOW"; + colorspace = V4L2_COLORSPACE_470_SYSTEM_M; + break; + } + case NTSC_N_443_SLOW: { + mask1 = 0x001F & NTSC_N_443_SLOW; + mask1 |= 0x0200; + name1 = "NTSC_N_443_SLOW"; + colorspace = V4L2_COLORSPACE_470_SYSTEM_M; + break; + } + default: + return -1; + } + + for (j = 0; j < RESOLUTION_MANY; j++) { + mask2 = 0x0000; + switch (j) { + case AT_720x576: { + if (0x1 & mask1) + continue; + name2 = "_AT_720x576"; + width = 720; + height = 576; + break; + } + case AT_704x576: { + if (0x1 & mask1) + continue; + name2 = "_AT_704x576"; + width = 704; + height = 576; + break; + } + case AT_640x480: { + name2 = "_AT_640x480"; + width = 640; + height = 480; + break; + } + case AT_720x480: { + if (!(0x1 & mask1)) + continue; + name2 = "_AT_720x480"; + width = 720; + height = 480; + break; + } + case AT_360x288: { + if (0x1 & mask1) + continue; + name2 = "_AT_360x288"; + width = 360; + height = 288; + mask2 = 0x0800; + break; + } + case AT_320x240: { + name2 = "_AT_320x240"; + width = 320; + height = 240; + mask2 = 0x0800; + break; + } + case AT_360x240: { + if (!(0x1 & mask1)) + continue; + name2 = "_AT_360x240"; + width = 360; + height = 240; + mask2 = 0x0800; + break; + } + default: + return -2; + } + + for (k = 0; k < PIXELFORMAT_MANY; k++) { + mask3 = 0x0000; + switch (k) { + case FMT_UYVY: { + name3 = __stringify(FMT_UYVY); + pixelformat = V4L2_PIX_FMT_UYVY; + mask3 |= (0x02 << 5); + break; + } + case FMT_YUY2: { + name3 = __stringify(FMT_YUY2); + pixelformat = V4L2_PIX_FMT_YUYV; + mask3 |= (0x02 << 5); + mask3 |= 0x0100; + break; + } + case FMT_RGB24: { + name3 = __stringify(FMT_RGB24); + pixelformat = V4L2_PIX_FMT_RGB24; + mask3 |= (0x03 << 5); + break; + } + case FMT_RGB32: { + name3 = __stringify(FMT_RGB32); + pixelformat = V4L2_PIX_FMT_RGB32; + mask3 |= (0x04 << 5); + break; + } + case FMT_BGR24: { + name3 = __stringify(FMT_BGR24); + pixelformat = V4L2_PIX_FMT_BGR24; + mask3 |= (0x03 << 5); + mask3 |= 0x0100; + break; + } + case FMT_BGR32: { + name3 = __stringify(FMT_BGR32); + pixelformat = V4L2_PIX_FMT_BGR32; + mask3 |= (0x04 << 5); + mask3 |= 0x0100; + break; + } + default: + return -3; + } + bytesperline = width * ((mask3 & 0x00E0) >> 5); + sizeimage = bytesperline * height; + + for (m = 0; m < INTERLACE_MANY; m++) { + mask4 = 0x0000; + switch (m) { + case FIELD_NONE: { + name4 = "-n"; + field = V4L2_FIELD_NONE; + break; + } + case FIELD_INTERLACED: { + name4 = "-i"; + mask4 |= 0x1000; + field = V4L2_FIELD_INTERLACED; + break; + } + default: + return -4; + } + if (SETTINGS_MANY <= n) + return -5; + + strcpy(easycap_format[n].name, name1); + strcat(easycap_format[n].name, name2); + strcat(easycap_format[n].name, "_"); + strcat(easycap_format[n].name, name3); + strcat(easycap_format[n].name, name4); + easycap_format[n].mask = + mask1 | mask2 | mask3 | mask4; + fmt = &easycap_format[n].v4l2_format; + + 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 = field; + fmt->fmt.pix.bytesperline = bytesperline; + fmt->fmt.pix.sizeimage = sizeimage; + fmt->fmt.pix.colorspace = colorspace; + fmt->fmt.pix.priv = 0; + n++; + } + } + } + } + if ((1 + SETTINGS_MANY) <= n) + return -6; + easycap_format[n].mask = 0xFFFF; + return n; +} +/*---------------------------------------------------------------------------*/ +struct v4l2_queryctrl easycap_control[] = { + { + .id = V4L2_CID_BRIGHTNESS, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Brightness", + .minimum = 0, + .maximum = 255, + .step = 1, + .default_value = SAA_0A_DEFAULT, + .flags = 0, + .reserved = {0, 0} + }, +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + { + .id = V4L2_CID_CONTRAST, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Contrast", + .minimum = 0, + .maximum = 255, + .step = 1, + .default_value = SAA_0B_DEFAULT + 128, + .flags = 0, + .reserved = {0, 0} + }, +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + { + .id = V4L2_CID_SATURATION, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Saturation", + .minimum = 0, + .maximum = 255, + .step = 1, + .default_value = SAA_0C_DEFAULT + 128, + .flags = 0, + .reserved = {0, 0} + }, +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + { + .id = V4L2_CID_HUE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Hue", + .minimum = 0, + .maximum = 255, + .step = 1, + .default_value = SAA_0D_DEFAULT + 128, + .flags = 0, + .reserved = {0, 0} + }, +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + { + .id = V4L2_CID_AUDIO_VOLUME, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Volume", + .minimum = 0, + .maximum = 31, + .step = 1, + .default_value = 16, + .flags = 0, + .reserved = {0, 0} + }, +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + { + .id = V4L2_CID_AUDIO_MUTE, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "Mute", + .default_value = true, + .flags = 0, + .reserved = {0, 0} + }, +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + { + .id = 0xFFFFFFFF + } +}; +/*****************************************************************************/ diff --git a/drivers/staging/media/easycap/easycap_sound.c b/drivers/staging/media/easycap/easycap_sound.c new file mode 100644 index 0000000..b22bb39 --- /dev/null +++ b/drivers/staging/media/easycap/easycap_sound.c @@ -0,0 +1,816 @@ +/****************************************************************************** +* * +* easycap_sound.c * +* * +* Audio driver for EasyCAP USB2.0 Video Capture Device DC60 * +* * +* * +******************************************************************************/ +/* + * + * Copyright (C) 2010 R.M. Thomas <rmthomas@sciolus.org> + * + * + * This 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. + * + * The software 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 software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * +*/ +/*****************************************************************************/ + +#include "easycap.h" + +/*--------------------------------------------------------------------------*/ +/* + * PARAMETERS USED WHEN REGISTERING THE AUDIO INTERFACE + */ +/*--------------------------------------------------------------------------*/ +static const struct snd_pcm_hardware alsa_hardware = { + .info = SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP_VALID, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_48000, + .rate_min = 32000, + .rate_max = 48000, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = PAGE_SIZE * + PAGES_PER_AUDIO_FRAGMENT * + AUDIO_FRAGMENT_MANY, + .period_bytes_min = PAGE_SIZE * PAGES_PER_AUDIO_FRAGMENT, + .period_bytes_max = PAGE_SIZE * PAGES_PER_AUDIO_FRAGMENT * 2, + .periods_min = AUDIO_FRAGMENT_MANY, + .periods_max = AUDIO_FRAGMENT_MANY * 2, +}; + + +/*****************************************************************************/ +/*---------------------------------------------------------------------------*/ +/* + * ON COMPLETION OF AN AUDIO URB ITS DATA IS COPIED TO THE DAM BUFFER + * PROVIDED peasycap->audio_idle IS ZERO. REGARDLESS OF THIS BEING TRUE, + * IT IS RESUBMITTED PROVIDED peasycap->audio_isoc_streaming IS NOT ZERO. + */ +/*---------------------------------------------------------------------------*/ +void +easycap_alsa_complete(struct urb *purb) +{ + struct easycap *peasycap; + struct snd_pcm_substream *pss; + struct snd_pcm_runtime *prt; + int dma_bytes, fragment_bytes; + int isfragment; + u8 *p1, *p2; + s16 tmp; + int i, j, more, much, rc; +#ifdef UPSAMPLE + int k; + s16 oldaudio, newaudio, delta; +#endif /*UPSAMPLE*/ + + JOT(16, "\n"); + + if (!purb) { + SAY("ERROR: purb is NULL\n"); + return; + } + peasycap = purb->context; + if (!peasycap) { + SAY("ERROR: peasycap is NULL\n"); + return; + } + much = 0; + if (peasycap->audio_idle) { + JOM(16, "%i=audio_idle %i=audio_isoc_streaming\n", + peasycap->audio_idle, peasycap->audio_isoc_streaming); + if (peasycap->audio_isoc_streaming) + goto resubmit; + } +/*---------------------------------------------------------------------------*/ + pss = peasycap->psubstream; + if (!pss) + goto resubmit; + prt = pss->runtime; + if (!prt) + goto resubmit; + dma_bytes = (int)prt->dma_bytes; + if (0 == dma_bytes) + goto resubmit; + fragment_bytes = 4 * ((int)prt->period_size); + if (0 == fragment_bytes) + goto resubmit; +/* -------------------------------------------------------------------------*/ + if (purb->status) { + if ((-ESHUTDOWN == purb->status) || (-ENOENT == purb->status)) { + JOM(16, "urb status -ESHUTDOWN or -ENOENT\n"); + return; + } + SAM("ERROR: non-zero urb status: -%s: %d\n", + strerror(purb->status), purb->status); + goto resubmit; + } +/*---------------------------------------------------------------------------*/ +/* + * PROCEED HERE WHEN NO ERROR + */ +/*---------------------------------------------------------------------------*/ + +#ifdef UPSAMPLE + oldaudio = peasycap->oldaudio; +#endif /*UPSAMPLE*/ + + for (i = 0; i < purb->number_of_packets; i++) { + if (purb->iso_frame_desc[i].status < 0) { + SAM("-%s: %d\n", + strerror(purb->iso_frame_desc[i].status), + purb->iso_frame_desc[i].status); + } + if (purb->iso_frame_desc[i].status) { + JOM(12, "discarding audio samples because " + "%i=purb->iso_frame_desc[i].status\n", + purb->iso_frame_desc[i].status); + continue; + } + more = purb->iso_frame_desc[i].actual_length; + if (more == 0) { + peasycap->audio_mt++; + continue; + } + if (0 > more) { + SAM("MISTAKE: more is negative\n"); + return; + } + + if (peasycap->audio_mt) { + JOM(12, "%4i empty audio urb frames\n", + peasycap->audio_mt); + peasycap->audio_mt = 0; + } + + p1 = (u8 *)(purb->transfer_buffer + + purb->iso_frame_desc[i].offset); + + /* + * COPY more BYTES FROM ISOC BUFFER + * TO THE DMA BUFFER, CONVERTING + * 8-BIT MONO TO 16-BIT SIGNED + * LITTLE-ENDIAN SAMPLES IF NECESSARY + */ + while (more) { + much = dma_bytes - peasycap->dma_fill; + if (0 > much) { + SAM("MISTAKE: much is negative\n"); + return; + } + if (0 == much) { + peasycap->dma_fill = 0; + peasycap->dma_next = fragment_bytes; + JOM(8, "wrapped dma buffer\n"); + } + if (!peasycap->microphone) { + if (much > more) + much = more; + memcpy(prt->dma_area + peasycap->dma_fill, + p1, much); + p1 += much; + more -= much; + } else { +#ifdef UPSAMPLE + if (much % 16) + JOM(8, "MISTAKE? much" + " is not divisible by 16\n"); + if (much > (16 * more)) + much = 16 * more; + p2 = (u8 *)(prt->dma_area + peasycap->dma_fill); + + for (j = 0; j < (much / 16); j++) { + newaudio = ((int) *p1) - 128; + newaudio = 128 * newaudio; + + delta = (newaudio - oldaudio) / 4; + tmp = oldaudio + delta; + + for (k = 0; k < 4; k++) { + *p2 = (0x00FF & tmp); + *(p2 + 1) = (0xFF00 & tmp) >> 8; + p2 += 2; + *p2 = (0x00FF & tmp); + *(p2 + 1) = (0xFF00 & tmp) >> 8; + p2 += 2; + tmp += delta; + } + p1++; + more--; + oldaudio = tmp; + } +#else /*!UPSAMPLE*/ + if (much > (2 * more)) + much = 2 * more; + p2 = (u8 *)(prt->dma_area + peasycap->dma_fill); + + for (j = 0; j < (much / 2); j++) { + tmp = ((int) *p1) - 128; + tmp = 128 * tmp; + *p2 = (0x00FF & tmp); + *(p2 + 1) = (0xFF00 & tmp) >> 8; + p1++; + p2 += 2; + more--; + } +#endif /*UPSAMPLE*/ + } + peasycap->dma_fill += much; + if (peasycap->dma_fill >= peasycap->dma_next) { + isfragment = peasycap->dma_fill / fragment_bytes; + if (0 > isfragment) { + SAM("MISTAKE: isfragment is negative\n"); + return; + } + peasycap->dma_read = (isfragment - 1) * fragment_bytes; + peasycap->dma_next = (isfragment + 1) * fragment_bytes; + if (dma_bytes < peasycap->dma_next) + peasycap->dma_next = fragment_bytes; + + if (0 <= peasycap->dma_read) { + JOM(8, "snd_pcm_period_elapsed(), %i=" + "isfragment\n", isfragment); + snd_pcm_period_elapsed(pss); + } + } + } + +#ifdef UPSAMPLE + peasycap->oldaudio = oldaudio; +#endif /*UPSAMPLE*/ + + } +/*---------------------------------------------------------------------------*/ +/* + * RESUBMIT THIS URB + */ +/*---------------------------------------------------------------------------*/ +resubmit: + if (peasycap->audio_isoc_streaming == 0) + return; + + rc = usb_submit_urb(purb, GFP_ATOMIC); + if (rc) { + if ((-ENODEV != rc) && (-ENOENT != rc)) { + SAM("ERROR: while %i=audio_idle, usb_submit_urb failed " + "with rc: -%s :%d\n", + peasycap->audio_idle, strerror(rc), rc); + } + if (0 < peasycap->audio_isoc_streaming) + peasycap->audio_isoc_streaming--; + } + return; +} +/*****************************************************************************/ +static int easycap_alsa_open(struct snd_pcm_substream *pss) +{ + struct snd_pcm *psnd_pcm; + struct snd_card *psnd_card; + struct easycap *peasycap; + + JOT(4, "\n"); + if (!pss) { + SAY("ERROR: pss is NULL\n"); + return -EFAULT; + } + psnd_pcm = pss->pcm; + if (!psnd_pcm) { + SAY("ERROR: psnd_pcm is NULL\n"); + return -EFAULT; + } + psnd_card = psnd_pcm->card; + if (!psnd_card) { + SAY("ERROR: psnd_card is NULL\n"); + return -EFAULT; + } + + peasycap = psnd_card->private_data; + if (!peasycap) { + SAY("ERROR: peasycap is NULL\n"); + return -EFAULT; + } + if (peasycap->psnd_card != psnd_card) { + SAM("ERROR: bad peasycap->psnd_card\n"); + return -EFAULT; + } + if (peasycap->psubstream) { + SAM("ERROR: bad peasycap->psubstream\n"); + return -EFAULT; + } + pss->private_data = peasycap; + peasycap->psubstream = pss; + pss->runtime->hw = peasycap->alsa_hardware; + pss->runtime->private_data = peasycap; + pss->private_data = peasycap; + + if (0 != easycap_sound_setup(peasycap)) { + JOM(4, "ending unsuccessfully\n"); + return -EFAULT; + } + JOM(4, "ending successfully\n"); + return 0; +} +/*****************************************************************************/ +static int easycap_alsa_close(struct snd_pcm_substream *pss) +{ + struct easycap *peasycap; + + JOT(4, "\n"); + if (!pss) { + SAY("ERROR: pss is NULL\n"); + return -EFAULT; + } + peasycap = snd_pcm_substream_chip(pss); + if (!peasycap) { + SAY("ERROR: peasycap is NULL\n"); + return -EFAULT; + } + pss->private_data = NULL; + peasycap->psubstream = NULL; + JOT(4, "ending successfully\n"); + return 0; +} +/*****************************************************************************/ +static int easycap_alsa_vmalloc(struct snd_pcm_substream *pss, size_t sz) +{ + struct snd_pcm_runtime *prt; + JOT(4, "\n"); + + if (!pss) { + SAY("ERROR: pss is NULL\n"); + return -EFAULT; + } + prt = pss->runtime; + if (!prt) { + SAY("ERROR: substream.runtime is NULL\n"); + return -EFAULT; + } + if (prt->dma_area) { + if (prt->dma_bytes > sz) + return 0; + vfree(prt->dma_area); + } + prt->dma_area = vmalloc(sz); + if (!prt->dma_area) + return -ENOMEM; + prt->dma_bytes = sz; + return 0; +} +/*****************************************************************************/ +static int easycap_alsa_hw_params(struct snd_pcm_substream *pss, + struct snd_pcm_hw_params *phw) +{ + int rc; + + JOT(4, "%i\n", (params_buffer_bytes(phw))); + if (!pss) { + SAY("ERROR: pss is NULL\n"); + return -EFAULT; + } + rc = easycap_alsa_vmalloc(pss, params_buffer_bytes(phw)); + if (rc) + return rc; + return 0; +} +/*****************************************************************************/ +static int easycap_alsa_hw_free(struct snd_pcm_substream *pss) +{ + struct snd_pcm_runtime *prt; + JOT(4, "\n"); + + if (!pss) { + SAY("ERROR: pss is NULL\n"); + return -EFAULT; + } + prt = pss->runtime; + if (!prt) { + SAY("ERROR: substream.runtime is NULL\n"); + return -EFAULT; + } + if (prt->dma_area) { + JOT(8, "prt->dma_area = %p\n", prt->dma_area); + vfree(prt->dma_area); + prt->dma_area = NULL; + } else + JOT(8, "dma_area already freed\n"); + return 0; +} +/*****************************************************************************/ +static int easycap_alsa_prepare(struct snd_pcm_substream *pss) +{ + struct easycap *peasycap; + struct snd_pcm_runtime *prt; + + JOT(4, "\n"); + if (!pss) { + SAY("ERROR: pss is NULL\n"); + return -EFAULT; + } + prt = pss->runtime; + peasycap = snd_pcm_substream_chip(pss); + if (!peasycap) { + SAY("ERROR: peasycap is NULL\n"); + return -EFAULT; + } + + JOM(16, "ALSA decides %8i Hz=rate\n", pss->runtime->rate); + JOM(16, "ALSA decides %8ld =period_size\n", pss->runtime->period_size); + JOM(16, "ALSA decides %8i =periods\n", pss->runtime->periods); + JOM(16, "ALSA decides %8ld =buffer_size\n", pss->runtime->buffer_size); + JOM(16, "ALSA decides %8zd =dma_bytes\n", pss->runtime->dma_bytes); + JOM(16, "ALSA decides %8ld =boundary\n", pss->runtime->boundary); + JOM(16, "ALSA decides %8i =period_step\n", pss->runtime->period_step); + JOM(16, "ALSA decides %8i =sample_bits\n", pss->runtime->sample_bits); + JOM(16, "ALSA decides %8i =frame_bits\n", pss->runtime->frame_bits); + JOM(16, "ALSA decides %8ld =min_align\n", pss->runtime->min_align); + JOM(12, "ALSA decides %8ld =hw_ptr_base\n", pss->runtime->hw_ptr_base); + JOM(12, "ALSA decides %8ld =hw_ptr_interrupt\n", + pss->runtime->hw_ptr_interrupt); + + if (prt->dma_bytes != 4 * ((int)prt->period_size) * ((int)prt->periods)) { + SAY("MISTAKE: unexpected ALSA parameters\n"); + return -ENOENT; + } + return 0; +} +/*****************************************************************************/ +static int easycap_alsa_ack(struct snd_pcm_substream *pss) +{ + return 0; +} +/*****************************************************************************/ +static int easycap_alsa_trigger(struct snd_pcm_substream *pss, int cmd) +{ + struct easycap *peasycap; + int retval; + + JOT(4, "%i=cmd cf %i=START %i=STOP\n", cmd, SNDRV_PCM_TRIGGER_START, + SNDRV_PCM_TRIGGER_STOP); + if (!pss) { + SAY("ERROR: pss is NULL\n"); + return -EFAULT; + } + peasycap = snd_pcm_substream_chip(pss); + if (!peasycap) { + SAY("ERROR: peasycap is NULL\n"); + return -EFAULT; + } + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: { + peasycap->audio_idle = 0; + break; + } + case SNDRV_PCM_TRIGGER_STOP: { + peasycap->audio_idle = 1; + break; + } + default: + retval = -EINVAL; + } + return 0; +} +/*****************************************************************************/ +static snd_pcm_uframes_t easycap_alsa_pointer(struct snd_pcm_substream *pss) +{ + struct easycap *peasycap; + snd_pcm_uframes_t offset; + + JOT(16, "\n"); + if (!pss) { + SAY("ERROR: pss is NULL\n"); + return -EFAULT; + } + peasycap = snd_pcm_substream_chip(pss); + if (!peasycap) { + SAY("ERROR: peasycap is NULL\n"); + return -EFAULT; + } + if ((0 != peasycap->audio_eof) || (0 != peasycap->audio_idle)) { + JOM(8, "returning -EIO because " + "%i=audio_idle %i=audio_eof\n", + peasycap->audio_idle, peasycap->audio_eof); + return -EIO; + } +/*---------------------------------------------------------------------------*/ + if (0 > peasycap->dma_read) { + JOM(8, "returning -EBUSY\n"); + return -EBUSY; + } + offset = ((snd_pcm_uframes_t)peasycap->dma_read)/4; + JOM(8, "ALSA decides %8i =hw_ptr_base\n", (int)pss->runtime->hw_ptr_base); + JOM(8, "ALSA decides %8i =hw_ptr_interrupt\n", + (int)pss->runtime->hw_ptr_interrupt); + JOM(8, "%7i=offset %7i=dma_read %7i=dma_next\n", + (int)offset, peasycap->dma_read, peasycap->dma_next); + return offset; +} +/*****************************************************************************/ +static struct page * +easycap_alsa_page(struct snd_pcm_substream *pss, unsigned long offset) +{ + return vmalloc_to_page(pss->runtime->dma_area + offset); +} +/*****************************************************************************/ + +static struct snd_pcm_ops easycap_alsa_pcm_ops = { + .open = easycap_alsa_open, + .close = easycap_alsa_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = easycap_alsa_hw_params, + .hw_free = easycap_alsa_hw_free, + .prepare = easycap_alsa_prepare, + .ack = easycap_alsa_ack, + .trigger = easycap_alsa_trigger, + .pointer = easycap_alsa_pointer, + .page = easycap_alsa_page, +}; + +/*****************************************************************************/ +/*---------------------------------------------------------------------------*/ +/* + * THE FUNCTION snd_card_create() HAS THIS_MODULE AS AN ARGUMENT. THIS + * MEANS MODULE easycap. BEWARE. +*/ +/*---------------------------------------------------------------------------*/ +int easycap_alsa_probe(struct easycap *peasycap) +{ + int rc; + struct snd_card *psnd_card; + struct snd_pcm *psnd_pcm; + + if (!peasycap) { + SAY("ERROR: peasycap is NULL\n"); + return -ENODEV; + } + if (0 > peasycap->minor) { + SAY("ERROR: no minor\n"); + return -ENODEV; + } + + peasycap->alsa_hardware = alsa_hardware; + if (peasycap->microphone) { + peasycap->alsa_hardware.rates = SNDRV_PCM_RATE_32000; + peasycap->alsa_hardware.rate_min = 32000; + peasycap->alsa_hardware.rate_max = 32000; + } else { + peasycap->alsa_hardware.rates = SNDRV_PCM_RATE_48000; + peasycap->alsa_hardware.rate_min = 48000; + peasycap->alsa_hardware.rate_max = 48000; + } + + if (0 != snd_card_create(SNDRV_DEFAULT_IDX1, "easycap_alsa", + THIS_MODULE, 0, &psnd_card)) { + SAY("ERROR: Cannot do ALSA snd_card_create()\n"); + return -EFAULT; + } + + sprintf(&psnd_card->id[0], "EasyALSA%i", peasycap->minor); + strcpy(&psnd_card->driver[0], EASYCAP_DRIVER_DESCRIPTION); + strcpy(&psnd_card->shortname[0], "easycap_alsa"); + sprintf(&psnd_card->longname[0], "%s", &psnd_card->shortname[0]); + + psnd_card->dev = &peasycap->pusb_device->dev; + psnd_card->private_data = peasycap; + peasycap->psnd_card = psnd_card; + + rc = snd_pcm_new(psnd_card, "easycap_pcm", 0, 0, 1, &psnd_pcm); + if (rc) { + SAM("ERROR: Cannot do ALSA snd_pcm_new()\n"); + snd_card_free(psnd_card); + return -EFAULT; + } + + snd_pcm_set_ops(psnd_pcm, SNDRV_PCM_STREAM_CAPTURE, + &easycap_alsa_pcm_ops); + psnd_pcm->info_flags = 0; + strcpy(&psnd_pcm->name[0], &psnd_card->id[0]); + psnd_pcm->private_data = peasycap; + peasycap->psnd_pcm = psnd_pcm; + peasycap->psubstream = NULL; + + rc = snd_card_register(psnd_card); + if (rc) { + SAM("ERROR: Cannot do ALSA snd_card_register()\n"); + snd_card_free(psnd_card); + return -EFAULT; + } + + SAM("registered %s\n", &psnd_card->id[0]); + return 0; +} + +/*****************************************************************************/ +/*****************************************************************************/ +/*****************************************************************************/ +/*****************************************************************************/ +/*****************************************************************************/ +/*****************************************************************************/ +/*---------------------------------------------------------------------------*/ +/* + * COMMON AUDIO INITIALIZATION + */ +/*---------------------------------------------------------------------------*/ +int +easycap_sound_setup(struct easycap *peasycap) +{ + int rc; + + JOM(4, "starting initialization\n"); + + if (!peasycap) { + SAY("ERROR: peasycap is NULL.\n"); + return -EFAULT; + } + if (!peasycap->pusb_device) { + SAM("ERROR: peasycap->pusb_device is NULL\n"); + return -ENODEV; + } + JOM(16, "0x%08lX=peasycap->pusb_device\n", (long int)peasycap->pusb_device); + + rc = audio_setup(peasycap); + JOM(8, "audio_setup() returned %i\n", rc); + + if (!peasycap->pusb_device) { + SAM("ERROR: peasycap->pusb_device has become NULL\n"); + return -ENODEV; + } +/*---------------------------------------------------------------------------*/ + if (!peasycap->pusb_device) { + SAM("ERROR: peasycap->pusb_device has become NULL\n"); + return -ENODEV; + } + rc = usb_set_interface(peasycap->pusb_device, peasycap->audio_interface, + peasycap->audio_altsetting_on); + JOM(8, "usb_set_interface(.,%i,%i) returned %i\n", peasycap->audio_interface, + peasycap->audio_altsetting_on, rc); + + rc = wakeup_device(peasycap->pusb_device); + JOM(8, "wakeup_device() returned %i\n", rc); + + peasycap->audio_eof = 0; + peasycap->audio_idle = 0; + + submit_audio_urbs(peasycap); + + JOM(4, "finished initialization\n"); + return 0; +} +/*****************************************************************************/ +/*---------------------------------------------------------------------------*/ +/* + * SUBMIT ALL AUDIO URBS. + */ +/*---------------------------------------------------------------------------*/ +int +submit_audio_urbs(struct easycap *peasycap) +{ + struct data_urb *pdata_urb; + struct urb *purb; + struct list_head *plist_head; + int j, isbad, nospc, m, rc; + int isbuf; + + if (!peasycap) { + SAY("ERROR: peasycap is NULL\n"); + return -EFAULT; + } + if (!peasycap->purb_audio_head) { + SAM("ERROR: peasycap->urb_audio_head uninitialized\n"); + return -EFAULT; + } + if (!peasycap->pusb_device) { + SAM("ERROR: peasycap->pusb_device is NULL\n"); + return -EFAULT; + } + + if (peasycap->audio_isoc_streaming) { + JOM(4, "already streaming audio urbs\n"); + return 0; + } + + JOM(4, "initial submission of all audio urbs\n"); + rc = usb_set_interface(peasycap->pusb_device, + peasycap->audio_interface, + peasycap->audio_altsetting_on); + JOM(8, "usb_set_interface(.,%i,%i) returned %i\n", + peasycap->audio_interface, + peasycap->audio_altsetting_on, rc); + + isbad = 0; + nospc = 0; + m = 0; + list_for_each(plist_head, peasycap->purb_audio_head) { + pdata_urb = list_entry(plist_head, struct data_urb, list_head); + if (pdata_urb && pdata_urb->purb) { + purb = pdata_urb->purb; + isbuf = pdata_urb->isbuf; + + purb->interval = 1; + purb->dev = peasycap->pusb_device; + purb->pipe = usb_rcvisocpipe(peasycap->pusb_device, + peasycap->audio_endpointnumber); + purb->transfer_flags = URB_ISO_ASAP; + purb->transfer_buffer = peasycap->audio_isoc_buffer[isbuf].pgo; + purb->transfer_buffer_length = peasycap->audio_isoc_buffer_size; + purb->complete = easycap_alsa_complete; + purb->context = peasycap; + purb->start_frame = 0; + purb->number_of_packets = peasycap->audio_isoc_framesperdesc; + for (j = 0; j < peasycap->audio_isoc_framesperdesc; j++) { + purb->iso_frame_desc[j].offset = j * peasycap->audio_isoc_maxframesize; + purb->iso_frame_desc[j].length = peasycap->audio_isoc_maxframesize; + } + + rc = usb_submit_urb(purb, GFP_KERNEL); + if (rc) { + isbad++; + SAM("ERROR: usb_submit_urb() failed" + " for urb with rc: -%s: %d\n", + strerror(rc), rc); + } else { + m++; + } + } else { + isbad++; + } + } + if (nospc) { + SAM("-ENOSPC=usb_submit_urb() for %i urbs\n", nospc); + SAM("..... possibly inadequate USB bandwidth\n"); + peasycap->audio_eof = 1; + } + if (isbad) { + JOM(4, "attempting cleanup instead of submitting\n"); + list_for_each(plist_head, (peasycap->purb_audio_head)) { + pdata_urb = list_entry(plist_head, struct data_urb, list_head); + if (pdata_urb && pdata_urb->purb) + usb_kill_urb(pdata_urb->purb); + } + peasycap->audio_isoc_streaming = 0; + } else { + peasycap->audio_isoc_streaming = m; + JOM(4, "submitted %i audio urbs\n", m); + } + + return 0; +} +/*****************************************************************************/ +/*---------------------------------------------------------------------------*/ +/* + * KILL ALL AUDIO URBS. + */ +/*---------------------------------------------------------------------------*/ +int +kill_audio_urbs(struct easycap *peasycap) +{ + int m; + struct list_head *plist_head; + struct data_urb *pdata_urb; + + if (!peasycap) { + SAY("ERROR: peasycap is NULL\n"); + return -EFAULT; + } + + if (!peasycap->audio_isoc_streaming) { + JOM(8, "%i=audio_isoc_streaming, no audio urbs killed\n", + peasycap->audio_isoc_streaming); + return 0; + } + + if (!peasycap->purb_audio_head) { + SAM("ERROR: peasycap->purb_audio_head is NULL\n"); + return -EFAULT; + } + + peasycap->audio_isoc_streaming = 0; + JOM(4, "killing audio urbs\n"); + m = 0; + list_for_each(plist_head, (peasycap->purb_audio_head)) { + pdata_urb = list_entry(plist_head, struct data_urb, list_head); + if (pdata_urb && pdata_urb->purb) { + usb_kill_urb(pdata_urb->purb); + m++; + } + } + JOM(4, "%i audio urbs killed\n", m); + + return 0; +} +/*****************************************************************************/ diff --git a/drivers/staging/media/easycap/easycap_testcard.c b/drivers/staging/media/easycap/easycap_testcard.c new file mode 100644 index 0000000..0f71470 --- /dev/null +++ b/drivers/staging/media/easycap/easycap_testcard.c @@ -0,0 +1,155 @@ +/****************************************************************************** +* * +* easycap_testcard.c * +* * +******************************************************************************/ +/* + * + * Copyright (C) 2010 R.M. Thomas <rmthomas@sciolus.org> + * + * + * This 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. + * + * The software 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 software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * +*/ +/*****************************************************************************/ + +#include "easycap.h" + +/*****************************************************************************/ +#define TESTCARD_BYTESPERLINE (2 * 720) +void +easycap_testcard(struct easycap *peasycap, int field) +{ + int total; + int y, u, v, r, g, b; + unsigned char uyvy[4]; + int i1, line, k, m, n, more, much, barwidth, barheight; + unsigned char bfbar[TESTCARD_BYTESPERLINE / 8], *p1, *p2; + struct data_buffer *pfield_buffer; + + if (!peasycap) { + SAY("ERROR: peasycap is NULL\n"); + return; + } + JOM(8, "%i=field\n", field); + switch (peasycap->width) { + case 720: + case 360: { + barwidth = (2 * 720) / 8; + break; + } + case 704: + case 352: { + barwidth = (2 * 704) / 8; + break; + } + case 640: + case 320: { + barwidth = (2 * 640) / 8; + break; + } + default: { + SAM("ERROR: cannot set barwidth\n"); + return; + } + } + if (TESTCARD_BYTESPERLINE < barwidth) { + SAM("ERROR: barwidth is too large\n"); + return; + } + switch (peasycap->height) { + case 576: + case 288: { + barheight = 576; + break; + } + case 480: + case 240: { + barheight = 480; + break; + } + default: { + SAM("ERROR: cannot set barheight\n"); + return; + } + } + total = 0; + k = field; + m = 0; + n = 0; + + for (line = 0; line < (barheight / 2); line++) { + for (i1 = 0; i1 < 8; i1++) { + r = (i1 * 256)/8; + g = (i1 * 256)/8; + b = (i1 * 256)/8; + + y = 299*r/1000 + 587*g/1000 + 114*b/1000 ; + u = -147*r/1000 - 289*g/1000 + 436*b/1000 ; + u = u + 128; + v = 615*r/1000 - 515*g/1000 - 100*b/1000 ; + v = v + 128; + + uyvy[0] = 0xFF & u ; + uyvy[1] = 0xFF & y ; + uyvy[2] = 0xFF & v ; + uyvy[3] = 0xFF & y ; + + p1 = &bfbar[0]; + while (p1 < &bfbar[barwidth]) { + *p1++ = uyvy[0] ; + *p1++ = uyvy[1] ; + *p1++ = uyvy[2] ; + *p1++ = uyvy[3] ; + total += 4; + } + + p1 = &bfbar[0]; + more = barwidth; + + while (more) { + if ((FIELD_BUFFER_SIZE/PAGE_SIZE) <= m) { + SAM("ERROR: bad m reached\n"); + return; + } + if (PAGE_SIZE < n) { + SAM("ERROR: bad n reached\n"); + return; + } + + if (0 > more) { + SAM("ERROR: internal fault\n"); + return; + } + + much = PAGE_SIZE - n; + if (much > more) + much = more; + pfield_buffer = &peasycap->field_buffer[k][m]; + p2 = pfield_buffer->pgo + n; + memcpy(p2, p1, much); + + p1 += much; + n += much; + more -= much; + if (PAGE_SIZE == n) { + m++; + n = 0; + } + } + } + } + return; +} diff --git a/drivers/staging/media/go7007/Kconfig b/drivers/staging/media/go7007/Kconfig new file mode 100644 index 0000000..7dfb281 --- /dev/null +++ b/drivers/staging/media/go7007/Kconfig @@ -0,0 +1,109 @@ +config VIDEO_GO7007 + tristate "WIS GO7007 MPEG encoder support" + depends on VIDEO_DEV && PCI && I2C + depends on SND + select VIDEOBUF_DMA_SG + depends on RC_CORE + select VIDEO_TUNER + select VIDEO_TVEEPROM + select SND_PCM + select CRC32 + default N + ---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 + default N + ---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_USB_S2250_BOARD + tristate "Sensoray 2250/2251 support" + depends on VIDEO_GO7007_USB && DVB_USB + default N + ---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 + +config VIDEO_GO7007_OV7640 + tristate "OV7640 subdev support" + depends on VIDEO_GO7007 + default N + ---help--- + This is a video4linux driver for the OV7640 sub-device. + + To compile this driver as a module, choose M here: the + module will be called wis-ov7640 + +config VIDEO_GO7007_SAA7113 + tristate "SAA7113 subdev support" + depends on VIDEO_GO7007 + default N + ---help--- + This is a video4linux driver for the SAA7113 sub-device. + + To compile this driver as a module, choose M here: the + module will be called wis-saa7113 + +config VIDEO_GO7007_SAA7115 + tristate "SAA7115 subdev support" + depends on VIDEO_GO7007 + default N + ---help--- + This is a video4linux driver for the SAA7115 sub-device. + + To compile this driver as a module, choose M here: the + module will be called wis-saa7115 + +config VIDEO_GO7007_TW9903 + tristate "TW9903 subdev support" + depends on VIDEO_GO7007 + default N + ---help--- + This is a video4linux driver for the TW9903 sub-device. + + To compile this driver as a module, choose M here: the + module will be called wis-tw9903 + +config VIDEO_GO7007_UDA1342 + tristate "UDA1342 subdev support" + depends on VIDEO_GO7007 + default N + ---help--- + This is a video4linux driver for the UDA1342 sub-device. + + To compile this driver as a module, choose M here: the + module will be called wis-uda1342 + +config VIDEO_GO7007_SONY_TUNER + tristate "Sony tuner subdev support" + depends on VIDEO_GO7007 + default N + ---help--- + This is a video4linux driver for the Sony Tuner sub-device. + + To compile this driver as a module, choose M here: the + module will be called wis-sony-tuner + +config VIDEO_GO7007_TW2804 + tristate "TW2804 subdev support" + depends on VIDEO_GO7007 + default N + ---help--- + This is a video4linux driver for the TW2804 sub-device. + + To compile this driver as a module, choose M here: the + module will be called wis-tw2804 + diff --git a/drivers/staging/media/go7007/Makefile b/drivers/staging/media/go7007/Makefile new file mode 100644 index 0000000..6ee837c --- /dev/null +++ b/drivers/staging/media/go7007/Makefile @@ -0,0 +1,30 @@ +#obj-m += go7007.o go7007-usb.o snd-go7007.o wis-saa7115.o wis-tw9903.o \ + wis-uda1342.o wis-sony-tuner.o wis-saa7113.o wis-ov7640.o \ + wis-tw2804.o + + +obj-$(CONFIG_VIDEO_GO7007) += go7007.o +obj-$(CONFIG_VIDEO_GO7007_USB) += go7007-usb.o +obj-$(CONFIG_VIDEO_GO7007_USB_S2250_BOARD) += s2250.o s2250-loader.o +obj-$(CONFIG_VIDEO_GO7007_SAA7113) += wis-saa7113.o +obj-$(CONFIG_VIDEO_GO7007_OV7640) += wis-ov7640.o +obj-$(CONFIG_VIDEO_GO7007_SAA7115) += wis-saa7115.o +obj-$(CONFIG_VIDEO_GO7007_TW9903) += wis-tw9903.o +obj-$(CONFIG_VIDEO_GO7007_UDA1342) += wis-uda1342.o +obj-$(CONFIG_VIDEO_GO7007_SONY_TUNER) += wis-sony-tuner.o +obj-$(CONFIG_VIDEO_GO7007_TW2804) += wis-tw2804.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/video/saa7134 -DSAA7134_MPEG_GO7007=3 + +# S2250 needs cypress ezusb loader from dvb-usb +ccflags-$(CONFIG_VIDEO_GO7007_USB_S2250_BOARD:m=y) += -Idrivers/media/dvb/dvb-usb + +ccflags-y += -Idrivers/media/dvb/frontends +ccflags-y += -Idrivers/media/dvb/dvb-core diff --git a/drivers/staging/media/go7007/README b/drivers/staging/media/go7007/README new file mode 100644 index 0000000..48f4476 --- /dev/null +++ b/drivers/staging/media/go7007/README @@ -0,0 +1,11 @@ +Todo: + - checkpatch.pl cleanups + - sparse cleanups + - lots of little modules, should be merged together + and added to the build. + - testing? + - handle churn in v4l layer. + +Please send patchs to Greg Kroah-Hartman <greg@kroah.com> and Cc: Ross +Cohen <rcohen@snurgle.org> as well. + diff --git a/drivers/staging/media/go7007/go7007-driver.c b/drivers/staging/media/go7007/go7007-driver.c new file mode 100644 index 0000000..6c9279a --- /dev/null +++ b/drivers/staging/media/go7007/go7007-driver.c @@ -0,0 +1,659 @@ +/* + * 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/init.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 <asm/system.h> +#include <linux/videodev2.h> +#include <media/tuner.h> +#include <media/v4l2-common.h> + +#include "go7007-priv.h" +#include "wis-i2c.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[] = "go7007fw.bin"; + void *bounce; + int fw_len, rv = 0; + u16 intr_val, intr_data; + + 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 = kmalloc(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; + } + memcpy(bounce, fw_entry->data + 16, fw_len); + release_firmware(fw_entry); + if (go7007_interface_reset(go) < 0 || + go7007_send_firmware(go, bounce, 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; + } + kfree(bounce); + return rv; +} + +MODULE_FIRMWARE("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); + } + if (go->board_id == 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); + } + 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 char *type, + int addr) +{ + struct go7007 *go = i2c_get_adapdata(adapter); + struct v4l2_device *v4l2_dev = &go->v4l2_dev; + + if (v4l2_i2c_new_subdev(v4l2_dev, adapter, type, addr, NULL)) + return 0; + + printk(KERN_INFO "go7007: probing for module i2c:%s failed\n", type); + return -1; +} + +/* + * 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) +{ + int i, ret; + + printk(KERN_INFO "go7007: registering new %s\n", go->name); + + mutex_lock(&go->hw_lock); + ret = go7007_init_encoder(go); + mutex_unlock(&go->hw_lock); + if (ret < 0) + return -1; + + /* v4l2 init must happen before i2c subdevs */ + ret = go7007_v4l2_init(go); + if (ret < 0) + return ret; + + if (!go->i2c_adapter_online && + go->board_info->flags & GO7007_BOARD_USE_ONBOARD_I2C) { + if (go7007_i2c_init(go) < 0) + return -1; + go->i2c_adapter_online = 1; + } + if (go->i2c_adapter_online) { + for (i = 0; i < go->board_info->num_i2c_devs; ++i) + init_i2c_module(&go->i2c_adapter, + go->board_info->i2c_devs[i].type, + go->board_info->i2c_devs[i].addr); + if (go->board_id == GO7007_BOARDID_ADLINK_MPG24) + i2c_clients_command(&go->i2c_adapter, + DECODER_SET_CHANNEL, &go->channel_number); + } + 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 *gobuf, u8 byte) +{ + if (gobuf != NULL && gobuf->bytesused < GO7007_BUF_SIZE) { + unsigned int pgidx = gobuf->offset >> PAGE_SHIFT; + unsigned int pgoff = gobuf->offset & ~PAGE_MASK; + + *((u8 *)page_address(gobuf->pages[pgidx]) + pgoff) = byte; + ++gobuf->offset; + ++gobuf->bytesused; + } +} + +/* + * Deliver the last video buffer and get a new one to start writing to. + */ +static void frame_boundary(struct go7007 *go) +{ + struct go7007_buffer *gobuf; + int i; + + if (go->active_buf) { + if (go->active_buf->modet_active) { + if (go->active_buf->bytesused + 216 < GO7007_BUF_SIZE) { + for (i = 0; i < 216; ++i) + store_byte(go->active_buf, + go->active_map[i]); + go->active_buf->bytesused -= 216; + } else + go->active_buf->modet_active = 0; + } + go->active_buf->state = BUF_STATE_DONE; + wake_up_interruptible(&go->frame_waitq); + go->active_buf = NULL; + } + list_for_each_entry(gobuf, &go->stream, stream) + if (gobuf->state == BUF_STATE_QUEUED) { + gobuf->seq = go->next_seq; + do_gettimeofday(&gobuf->timestamp); + go->active_buf = gobuf; + break; + } + ++go->next_seq; +} + +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) +{ + int i, seq_start_code = -1, frame_start_code = -1; + + spin_lock(&go->spinlock); + + switch (go->format) { + case GO7007_FORMAT_MPEG4: + seq_start_code = 0xB0; + frame_start_code = 0xB6; + break; + case GO7007_FORMAT_MPEG1: + case GO7007_FORMAT_MPEG2: + seq_start_code = 0xB3; + frame_start_code = 0x00; + break; + } + + for (i = 0; i < length; ++i) { + if (go->active_buf != NULL && + go->active_buf->bytesused >= GO7007_BUF_SIZE - 3) { + v4l2_info(&go->v4l2_dev, "dropping oversized frame\n"); + go->active_buf->offset -= go->active_buf->bytesused; + go->active_buf->bytesused = 0; + go->active_buf->modet_active = 0; + 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(go->active_buf, buf[i]); + break; + } + break; + case STATE_00: + switch (buf[i]) { + case 0x00: + go->state = STATE_00_00; + break; + case 0xFF: + store_byte(go->active_buf, 0x00); + go->state = STATE_FF; + break; + default: + store_byte(go->active_buf, 0x00); + store_byte(go->active_buf, buf[i]); + go->state = STATE_DATA; + break; + } + break; + case STATE_00_00: + switch (buf[i]) { + case 0x00: + store_byte(go->active_buf, 0x00); + /* go->state remains STATE_00_00 */ + break; + case 0x01: + go->state = STATE_00_00_01; + break; + case 0xFF: + store_byte(go->active_buf, 0x00); + store_byte(go->active_buf, 0x00); + go->state = STATE_FF; + break; + default: + store_byte(go->active_buf, 0x00); + store_byte(go->active_buf, 0x00); + store_byte(go->active_buf, 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(go->active_buf, 0x00); + store_byte(go->active_buf, 0x00); + store_byte(go->active_buf, 0x01); + store_byte(go->active_buf, 0xF8); + go->state = STATE_DATA; + break; + } + /* If this is the start of a new MPEG frame, + * get a new buffer */ + if ((go->format == GO7007_FORMAT_MPEG1 || + go->format == GO7007_FORMAT_MPEG2 || + go->format == GO7007_FORMAT_MPEG4) && + (buf[i] == seq_start_code || + buf[i] == 0xB8 || /* GOP code */ + buf[i] == frame_start_code)) { + if (go->active_buf == NULL || go->seen_frame) + frame_boundary(go); + if (buf[i] == frame_start_code) { + if (go->active_buf != NULL) + go->active_buf->frame_offset = + go->active_buf->offset; + go->seen_frame = 1; + } else { + go->seen_frame = 0; + } + } + /* 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(go->active_buf, 0x00); + store_byte(go->active_buf, 0x00); + store_byte(go->active_buf, 0x01); + go->state = STATE_FF; + break; + default: + store_byte(go->active_buf, 0x00); + store_byte(go->active_buf, 0x00); + store_byte(go->active_buf, 0x01); + store_byte(go->active_buf, buf[i]); + go->state = STATE_DATA; + break; + } + break; + case STATE_FF: + switch (buf[i]) { + case 0x00: + store_byte(go->active_buf, 0xFF); + go->state = STATE_00; + break; + case 0xFF: + store_byte(go->active_buf, 0xFF); + /* go->state remains STATE_FF */ + break; + case 0xD8: + if (go->format == GO7007_FORMAT_MJPEG) + frame_boundary(go); + /* fall through */ + default: + store_byte(go->active_buf, 0xFF); + store_byte(go->active_buf, 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 && go->active_buf) { + go->active_buf->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; + } + } + + spin_unlock(&go->spinlock); +} +EXPORT_SYMBOL(go7007_parse_video_stream); + +/* + * Allocate a new go7007 struct. Used by the hardware-specific probe. + */ +struct go7007 *go7007_alloc(struct go7007_board_info *board, struct device *dev) +{ + struct go7007 *go; + int i; + + go = kmalloc(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->video_dev = NULL; + go->ref_count = 0; + 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->in_use = 0; + go->input = 0; + if (board->sensor_flags & GO7007_SENSOR_TV) { + go->standard = GO7007_STD_NTSC; + 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; + go->encoder_h_halve = 0; + go->encoder_v_halve = 0; + go->encoder_subsample = 0; + go->streaming = 0; + go->format = GO7007_FORMAT_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; + INIT_LIST_HEAD(&go->stream); + + return go; +} +EXPORT_SYMBOL(go7007_alloc); + +/* + * 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. + */ +void go7007_remove(struct go7007 *go) +{ + if (go->i2c_adapter_online) { + if (i2c_del_adapter(&go->i2c_adapter) == 0) + go->i2c_adapter_online = 0; + else + v4l2_err(&go->v4l2_dev, + "error removing I2C adapter!\n"); + } + + if (go->audio_enabled) + go7007_snd_remove(go); + go7007_v4l2_remove(go); +} +EXPORT_SYMBOL(go7007_remove); + +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 0000000..c9a6409 --- /dev/null +++ b/drivers/staging/media/go7007/go7007-fw.c @@ -0,0 +1,1636 @@ +/* + * 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/init.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" + +/* 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) { + printk(KERN_ERR "go7007: unable to allocate 4096 bytes for " + "firmware construction\n"); + 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 == GO7007_FORMAT_MPEG2 ? 0x7 : 0x4, 4); + if (frame != PFRAME) + CODE_ADD(c, go->format == GO7007_FORMAT_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 == GO7007_FORMAT_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 == GO7007_FORMAT_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 == GO7007_FORMAT_MPEG2 ? 20000 : 0x3ffff, 18); + CODE_ADD(c, 1, 1); + CODE_ADD(c, go->format == GO7007_FORMAT_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 == GO7007_FORMAT_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) { + printk(KERN_ERR "go7007: unable to allocate 5120 bytes for " + "firmware construction\n"); + 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) { + printk(KERN_ERR "go7007: unable to allocate 5120 bytes for " + "firmware construction\n"); + 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 == GO7007_FORMAT_MJPEG || go->dvd_mode) ? + 100 : 0; + int peak_rate = 6 * go->bitrate / 5; + int vbv_buffer = go->format == GO7007_FORMAT_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 == GO7007_FORMAT_MPEG4 ? 0 : + (go->format == GO7007_FORMAT_H263 ? 0 : 1), + 0xc680, go->format == GO7007_FORMAT_MPEG4 ? 0xf1 : + (go->format == GO7007_FORMAT_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 == GO7007_FORMAT_MPEG4 ? 0x11 : + (go->format == GO7007_FORMAT_MPEG1 ? 0x02 : + (go->format == GO7007_FORMAT_MPEG2 ? 0x04 : + (go->format == GO7007_FORMAT_H263 ? 0x08 : + 0x20)))), + 0xbf0a, (go->format == GO7007_FORMAT_MPEG4 ? 8 : + (go->format == GO7007_FORMAT_MPEG1 ? 1 : + (go->format == GO7007_FORMAT_MPEG2 ? 2 : + (go->format == GO7007_FORMAT_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 == GO7007_FORMAT_MPEG4 ? 0x0404 : 0, + (1 << 15) | ((go->interlace_coding ? 1 : 0) << 13) | + ((go->closed_gop ? 1 : 0) << 12) | + ((go->format == GO7007_FORMAT_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 == GO7007_FORMAT_MPEG1 ? 0x89a0 : + (go->format == GO7007_FORMAT_MPEG2 ? 0x89a0 : + (go->format == GO7007_FORMAT_MJPEG ? 0x89a0 : + (go->format == GO7007_FORMAT_MPEG4 ? 0x8920 : + (go->format == GO7007_FORMAT_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 GO7007_FORMAT_MJPEG: + return gen_mjpeghdr_to_package(go, code, space); + case GO7007_FORMAT_MPEG1: + case GO7007_FORMAT_MPEG2: + return gen_mpeg1hdr_to_package(go, code, space, + framelen); + case GO7007_FORMAT_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 GO7007_FORMAT_MPEG1: + case GO7007_FORMAT_MPEG2: + return seqhead_to_package(go, code, space, + mpeg1_sequence_header); + case GO7007_FORMAT_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); + } + printk(KERN_ERR + "go7007: 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 GO7007_FORMAT_MJPEG: + mode_flag = FLAG_MODE_MJPEG; + break; + case GO7007_FORMAT_MPEG1: + mode_flag = FLAG_MODE_MPEG1; + break; + case GO7007_FORMAT_MPEG2: + mode_flag = FLAG_MODE_MPEG2; + break; + case GO7007_FORMAT_MPEG4: + mode_flag = FLAG_MODE_MPEG4; + break; + default: + return -1; + } + if (request_firmware(&fw_entry, go->board_info->firmware, go->dev)) { + printk(KERN_ERR + "go7007: unable to load firmware from file \"%s\"\n", + go->board_info->firmware); + return -1; + } + code = kzalloc(codespace * 2, GFP_KERNEL); + if (code == NULL) { + printk(KERN_ERR "go7007: unable to allocate %d bytes for " + "firmware construction\n", codespace * 2); + 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) { + printk(KERN_ERR "go7007: firmware file \"%s\" " + "appears to be corrupted\n", + go->board_info->firmware); + 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) { + printk(KERN_ERR "go7007: insufficient " + "memory for firmware " + "construction\n"); + goto fw_failed; + } + i += ret; + } else { + if (codespace - i < chunk_len) { + printk(KERN_ERR "go7007: 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; +} diff --git a/drivers/staging/media/go7007/go7007-i2c.c b/drivers/staging/media/go7007/go7007-i2c.c new file mode 100644 index 0000000..b8cfa1a --- /dev/null +++ b/drivers/staging/media/go7007/go7007-i2c.c @@ -0,0 +1,225 @@ +/* + * 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/init.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 <asm/system.h> + +#include "go7007-priv.h" +#include "wis-i2c.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 = -1; + u16 val; + + if (go->status == STATUS_SHUTDOWN) + return -1; + +#ifdef GO7007_I2C_DEBUG + if (read) + printk(KERN_DEBUG "go7007-i2c: reading 0x%02x on 0x%02x\n", + command, addr); + else + printk(KERN_DEBUG + "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) { + printk(KERN_ERR "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) { + printk(KERN_ERR "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 -1; + 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 -1; + 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 -1; + ++i; + } else if (msgs[i].len == 3) { + if (msgs[i].flags & I2C_M_RD) + return -1; + if (msgs[i].len != 3) + return -1; + 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 -1; + } else + return -1; + } + + return 0; +} + +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) { + printk(KERN_ERR + "go7007-i2c: error: i2c_add_adapter failed\n"); + return -1; + } + return 0; +} diff --git a/drivers/staging/media/go7007/go7007-priv.h b/drivers/staging/media/go7007/go7007-priv.h new file mode 100644 index 0000000..b58c394 --- /dev/null +++ b/drivers/staging/media/go7007/go7007-priv.h @@ -0,0 +1,290 @@ +/* + * 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> + +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 16 +#define GO7007_BOARDID_PX_TV402U_ANY 17 /* need to check tuner model */ +#define GO7007_BOARDID_PX_TV402U_NA 18 /* detected NTSC tuner */ +#define GO7007_BOARDID_PX_TV402U_EU 19 /* detected PAL tuner */ +#define GO7007_BOARDID_PX_TV402U_JP 20 /* detected NTSC-J tuner */ +#define GO7007_BOARDID_LIFEVIEW_LR192 21 /* TV Walker Ultra */ +#define GO7007_BOARDID_ENDURA 22 +#define GO7007_BOARDID_ADLINK_MPG24 23 +#define GO7007_BOARDID_SENSORAY_2250 24 /* Sensoray 2250/2251 */ + +/* 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) + +/* 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 { + char *firmware; + 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 { + const char *type; + int id; + int addr; + } i2c_devs[4]; + int num_inputs; + struct { + int video_input; + int audio_input; + char *name; + } inputs[4]; +}; + +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); +}; + +/* 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 go7007 *go; /* Reverse reference for VMA ops */ + int index; /* Reverse reference for DQBUF */ + enum { BUF_STATE_IDLE, BUF_STATE_QUEUED, BUF_STATE_DONE } state; + u32 seq; + struct timeval timestamp; + struct list_head stream; + struct page *pages[GO7007_BUF_PAGES + 1]; /* extra for straddling */ + unsigned long user_addr; + unsigned int page_count; + unsigned int offset; + unsigned int bytesused; + unsigned int frame_offset; + u32 modet_active; + int mapped; +}; + +struct go7007_file { + struct go7007 *go; + struct mutex lock; + int buf_count; + struct go7007_buffer *bufs; +}; + +#define GO7007_FORMAT_MJPEG 0 +#define GO7007_FORMAT_MPEG4 1 +#define GO7007_FORMAT_MPEG1 2 +#define GO7007_FORMAT_MPEG2 3 +#define GO7007_FORMAT_H263 4 + +#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; + 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 *video_dev; + struct v4l2_device v4l2_dev; + int ref_count; + enum { STATUS_INIT, STATUS_ONLINE, STATUS_SHUTDOWN } status; + spinlock_t spinlock; + struct mutex hw_lock; + int streaming; + int in_use; + int audio_enabled; + + /* Video input */ + int input; + enum { GO7007_STD_NTSC, GO7007_STD_PAL, GO7007_STD_OTHER } standard; + 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 */ + int 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 go7007_buffer *active_buf; + enum go7007_parser_state state; + int parse_length; + u16 modet_word; + int seen_frame; + u32 next_seq; + struct list_head stream; + wait_queue_head_t frame_waitq; + + /* 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); +int go7007_start_encoder(struct go7007 *go); +void go7007_parse_video_stream(struct go7007 *go, u8 *buf, int length); +struct go7007 *go7007_alloc(struct go7007_board_info *board, + struct device *dev); +void go7007_remove(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); +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 0000000..3db3b0a --- /dev/null +++ b/drivers/staging/media/go7007/go7007-usb.c @@ -0,0 +1,1288 @@ +/* + * 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/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/tvaudio.h> + +#include "go7007-priv.h" +#include "wis-i2c.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_USB_DEBUG */ +/* #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 { + 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 struct go7007_usb_board board_matrix_ii = { + .flags = GO7007_USB_EZUSB, + .main_info = { + .firmware = "go7007tv.bin", + .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 = 1, + .i2c_devs = { + { + .type = "wis_saa7115", + .id = I2C_DRIVERID_WIS_SAA7115, + .addr = 0x20, + }, + }, + .num_inputs = 2, + .inputs = { + { + .video_input = 0, + .name = "Composite", + }, + { + .video_input = 9, + .name = "S-Video", + }, + }, + }, +}; + +static struct go7007_usb_board board_matrix_reload = { + .flags = GO7007_USB_EZUSB, + .main_info = { + .firmware = "go7007tv.bin", + .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 = "wis_saa7113", + .id = I2C_DRIVERID_WIS_SAA7113, + .addr = 0x25, + }, + }, + .num_inputs = 2, + .inputs = { + { + .video_input = 0, + .name = "Composite", + }, + { + .video_input = 9, + .name = "S-Video", + }, + }, + }, +}; + +static struct go7007_usb_board board_star_trek = { + .flags = GO7007_USB_EZUSB | GO7007_USB_EZUSB_I2C, + .main_info = { + .firmware = "go7007tv.bin", + .flags = GO7007_BOARD_HAS_AUDIO, /* | + GO7007_BOARD_HAS_TUNER, */ + .sensor_flags = GO7007_SENSOR_656 | + GO7007_SENSOR_VALID_ENABLE | + GO7007_SENSOR_TV | + 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 = "wis_saa7115", + .id = I2C_DRIVERID_WIS_SAA7115, + .addr = 0x20, + }, + }, + .num_inputs = 2, + .inputs = { + { + .video_input = 1, + /* .audio_input = AUDIO_EXTERN, */ + .name = "Composite", + }, + { + .video_input = 8, + /* .audio_input = AUDIO_EXTERN, */ + .name = "S-Video", + }, + /* { + * .video_input = 3, + * .audio_input = AUDIO_TUNER, + * .name = "Tuner", + * }, + */ + }, + }, +}; + +static struct go7007_usb_board board_px_tv402u = { + .flags = GO7007_USB_EZUSB | GO7007_USB_EZUSB_I2C, + .main_info = { + .firmware = "go7007tv.bin", + .flags = GO7007_BOARD_HAS_AUDIO | + GO7007_BOARD_HAS_TUNER, + .sensor_flags = GO7007_SENSOR_656 | + GO7007_SENSOR_VALID_ENABLE | + GO7007_SENSOR_TV | + 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 = 3, + .i2c_devs = { + { + .type = "wis_saa7115", + .id = I2C_DRIVERID_WIS_SAA7115, + .addr = 0x20, + }, + { + .type = "wis_uda1342", + .id = I2C_DRIVERID_WIS_UDA1342, + .addr = 0x1a, + }, + { + .type = "wis_sony_tuner", + .id = I2C_DRIVERID_WIS_SONY_TUNER, + .addr = 0x60, + }, + }, + .num_inputs = 3, + .inputs = { + { + .video_input = 1, + .audio_input = TVAUDIO_INPUT_EXTERN, + .name = "Composite", + }, + { + .video_input = 8, + .audio_input = TVAUDIO_INPUT_EXTERN, + .name = "S-Video", + }, + { + .video_input = 3, + .audio_input = TVAUDIO_INPUT_TUNER, + .name = "Tuner", + }, + }, + }, +}; + +static struct go7007_usb_board board_xmen = { + .flags = 0, + .main_info = { + .firmware = "go7007tv.bin", + .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 = "wis_ov7640", + .id = I2C_DRIVERID_WIS_OV7640, + .addr = 0x21, + }, + }, + .num_inputs = 1, + .inputs = { + { + .name = "Camera", + }, + }, + }, +}; + +static struct go7007_usb_board board_matrix_revolution = { + .flags = GO7007_USB_EZUSB, + .main_info = { + .firmware = "go7007tv.bin", + .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 = "wis_tw9903", + .id = I2C_DRIVERID_WIS_TW9903, + .addr = 0x44, + }, + }, + .num_inputs = 2, + .inputs = { + { + .video_input = 2, + .name = "Composite", + }, + { + .video_input = 8, + .name = "S-Video", + }, + }, + }, +}; + +static struct go7007_usb_board board_lifeview_lr192 = { + .flags = GO7007_USB_EZUSB, + .main_info = { + .firmware = "go7007tv.bin", + .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 struct go7007_usb_board board_endura = { + .flags = 0, + .main_info = { + .firmware = "go7007tv.bin", + .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 struct go7007_usb_board board_adlink_mpg24 = { + .flags = 0, + .main_info = { + .firmware = "go7007tv.bin", + .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 = "wis_tw2804", + .id = I2C_DRIVERID_WIS_TW2804, + .addr = 0x00, /* yes, really */ + }, + }, + .num_inputs = 1, + .inputs = { + { + .name = "Composite", + }, + }, + }, +}; + +static struct go7007_usb_board board_sensoray_2250 = { + .flags = GO7007_USB_EZUSB | GO7007_USB_EZUSB_I2C, + .main_info = { + .firmware = "go7007tv.bin", + .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", + .id = I2C_DRIVERID_S2250, + .addr = 0x43, + }, + }, + .num_inputs = 2, + .inputs = { + { + .video_input = 0, + .name = "Composite", + }, + { + .video_input = 1, + .name = "S-Video", + }, + }, + }, +}; + +MODULE_FIRMWARE("go7007tv.bin"); + +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_ANY, + }, + { + .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, + }, + { } /* 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; + + /* 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 */ +#ifdef GO7007_USB_DEBUG + printk(KERN_DEBUG "go7007-usb: resetting EZ-USB buffers\n"); +#endif + 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) { + printk(KERN_ERR + "go7007-usb: 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; + int timeout = 500; + +#ifdef GO7007_USB_DEBUG + printk(KERN_DEBUG + "go7007-usb: WriteInterrupt: %04x %04x\n", addr, data); +#endif + + 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, &status_reg, + sizeof(status_reg), timeout); + if (r < 0) + goto write_int_error; + __le16_to_cpus(&status_reg); + if (!(status_reg & 0x0010)) + break; + msleep(10); + } + if (i == 100) { + printk(KERN_ERR + "go7007-usb: 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: + printk(KERN_ERR "go7007-usb: 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; + u8 *tbuf; + int r; + int timeout = 500; + +#ifdef GO7007_USB_DEBUG + printk(KERN_DEBUG + "go7007-usb: WriteInterrupt: %04x %04x\n", addr, data); +#endif + + tbuf = kzalloc(8, GFP_KERNEL); + if (tbuf == NULL) + return -ENOMEM; + tbuf[0] = data & 0xff; + tbuf[1] = data >> 8; + tbuf[2] = addr & 0xff; + tbuf[3] = addr >> 8; + r = usb_control_msg(usb->usbdev, usb_sndctrlpipe(usb->usbdev, 2), 0x00, + USB_TYPE_VENDOR | USB_RECIP_ENDPOINT, 0x55aa, + 0xf0f0, tbuf, 8, timeout); + kfree(tbuf); + if (r < 0) { + printk(KERN_ERR "go7007-usb: 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) { + printk(KERN_ERR + "go7007-usb: error in read interrupt: %d\n", + urb->status); + } else { + wake_up(&go->interrupt_waitq); + return; + } + } else if (urb->actual_length != urb->transfer_buffer_length) { + printk(KERN_ERR "go7007-usb: 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]); +#ifdef GO7007_USB_DEBUG + printk(KERN_DEBUG "go7007-usb: ReadInterrupt: %04x %04x\n", + go->interrupt_value, go->interrupt_data); +#endif + } + + 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) { + printk(KERN_ERR + "go7007-usb: 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 (!go->streaming) { + wake_up_interruptible(&go->frame_waitq); + return; + } + if (status) { + printk(KERN_ERR "go7007-usb: error in video pipe: %d\n", + status); + return; + } + if (urb->actual_length != urb->transfer_buffer_length) { + printk(KERN_ERR "go7007-usb: 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) + printk(KERN_ERR "go7007-usb: 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 (!go->streaming) + return; + if (status) { + printk(KERN_ERR "go7007-usb: error in audio pipe: %d\n", + status); + return; + } + if (urb->actual_length != urb->transfer_buffer_length) { + printk(KERN_ERR "go7007-usb: 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) + printk(KERN_ERR "go7007-usb: 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) { + printk(KERN_ERR "go7007-usb: 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) { + printk(KERN_ERR "go7007-usb: 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; + +#ifdef GO7007_USB_DEBUG + printk(KERN_DEBUG "go7007-usb: DownloadBuffer sending %d bytes\n", len); +#endif + + 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 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, +}; + +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, +}; + +/********************* 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[16]; + int buf_len, i; + int ret = -1; + + if (go->status == STATUS_SHUTDOWN) + return -1; + + 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 + printk(KERN_DEBUG "go7007-usb: 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 + printk(KERN_DEBUG "go7007-usb: 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 + printk(KERN_DEBUG "go7007-usb: 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, sizeof(buf)); + 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 = 0; + +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; + struct go7007_usb_board *board; + struct usb_device *usbdev = interface_to_usbdev(intf); + char *name; + int video_pipe, i, v_urb_len; + + printk(KERN_DEBUG "go7007-usb: 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_ANY: + name = "Plextor PX-TV402U (unknown tuner)"; + board = &board_px_tv402u; + break; + case GO7007_BOARDID_LIFEVIEW_LR192: + printk(KERN_ERR "go7007-usb: The Lifeview TV Walker Ultra " + "is not supported. Sorry!\n"); + return 0; + name = "Lifeview TV Walker Ultra"; + board = &board_lifeview_lr192; + break; + case GO7007_BOARDID_SENSORAY_2250: + printk(KERN_INFO "Sensoray 2250 found\n"); + name = "Sensoray 2250/2251"; + board = &board_sensoray_2250; + break; + default: + printk(KERN_ERR "go7007-usb: unknown board ID %d!\n", + (unsigned int)id->driver_info); + return 0; + } + + usb = kzalloc(sizeof(struct go7007_usb), GFP_KERNEL); + if (usb == NULL) + return -ENOMEM; + + /* 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; + + go = go7007_alloc(&board->main_info, &intf->dev); + if (go == NULL) + goto allocfail; + usb->board = board; + usb->usbdev = usbdev; + 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; + 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 initfail; + + /* 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) { + printk(KERN_ERR + "go7007-usb: error: i2c_add_adapter failed\n"); + goto initfail; + } + 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; + + /* set GPIO5 to be an output, currently low */ + go7007_write_addr(go, 0x3c82, 0x0000); + go7007_write_addr(go, 0x3c80, 0x00df); + /* 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); + } + } + } + + /* Probe the tuner model on the TV402U */ + if (go->board_id == GO7007_BOARDID_PX_TV402U_ANY) { + u8 data[3]; + + /* Board strapping indicates tuner model */ + if (go7007_usb_vendor_request(go, 0x41, 0, 0, data, 3, 1) < 0) { + printk(KERN_ERR "go7007-usb: GPIO read failed!\n"); + goto initfail; + } + switch (data[0] >> 6) { + case 1: + go->board_id = GO7007_BOARDID_PX_TV402U_EU; + go->tuner_type = TUNER_SONY_BTF_PG472Z; + strncpy(go->name, "Plextor PX-TV402U-EU", + sizeof(go->name)); + break; + case 2: + go->board_id = GO7007_BOARDID_PX_TV402U_JP; + go->tuner_type = TUNER_SONY_BTF_PK467Z; + strncpy(go->name, "Plextor PX-TV402U-JP", + sizeof(go->name)); + break; + case 3: + go->board_id = GO7007_BOARDID_PX_TV402U_NA; + go->tuner_type = TUNER_SONY_BTF_PB463Z; + strncpy(go->name, "Plextor PX-TV402U-NA", + sizeof(go->name)); + break; + default: + printk(KERN_DEBUG "go7007-usb: 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) { + printk(KERN_ERR "go7007-usb: GPIO write failed!\n"); + goto initfail; + } + } + + /* 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) + printk(KERN_ERR "go7007-usb: *** 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"); + + /* Do any final GO7007 initialization, then register the + * V4L2 and ALSA interfaces */ + if (go7007_register_encoder(go) < 0) + goto initfail; + + /* 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 initfail; + usb->video_urbs[i]->transfer_buffer = + kmalloc(v_urb_len, GFP_KERNEL); + if (usb->video_urbs[i]->transfer_buffer == NULL) + goto initfail; + 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) && go->audio_enabled) + for (i = 0; i < 8; ++i) { + usb->audio_urbs[i] = usb_alloc_urb(0, GFP_KERNEL); + if (usb->audio_urbs[i] == NULL) + goto initfail; + usb->audio_urbs[i]->transfer_buffer = kmalloc(4096, + GFP_KERNEL); + if (usb->audio_urbs[i]->transfer_buffer == NULL) + goto initfail; + 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); + } + + + go->status = STATUS_ONLINE; + return 0; + +initfail: + go->status = STATUS_SHUTDOWN; + return 0; + +allocfail: + if (usb->intr_urb) { + kfree(usb->intr_urb->transfer_buffer); + usb_free_urb(usb->intr_urb); + } + kfree(usb); + return -ENOMEM; +} + +static void go7007_usb_disconnect(struct usb_interface *intf) +{ + struct go7007 *go = to_go7007(usb_get_intfdata(intf)); + struct go7007_usb *usb = go->hpi_context; + struct urb *vurb, *aurb; + int i; + + go->status = STATUS_SHUTDOWN; + usb_kill_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(usb->intr_urb->transfer_buffer); + usb_free_urb(usb->intr_urb); + + kfree(go->hpi_context); + + go7007_remove(go); +} + +static struct usb_driver go7007_usb_driver = { + .name = "go7007", + .probe = go7007_usb_probe, + .disconnect = go7007_usb_disconnect, + .id_table = go7007_usb_id_table, +}; + +static int __init go7007_usb_init(void) +{ + return usb_register(&go7007_usb_driver); +} + +static void __exit go7007_usb_cleanup(void) +{ + usb_deregister(&go7007_usb_driver); +} + +module_init(go7007_usb_init); +module_exit(go7007_usb_cleanup); + +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 0000000..2b27d8d --- /dev/null +++ b/drivers/staging/media/go7007/go7007-v4l2.c @@ -0,0 +1,1839 @@ +/* + * 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/init.h> +#include <linux/version.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/videodev2.h> +#include <media/v4l2-common.h> +#include <media/v4l2-ioctl.h> +#include <media/v4l2-subdev.h> +#include <linux/i2c.h> +#include <linux/mutex.h> +#include <linux/uaccess.h> +#include <asm/system.h> + +#include "go7007.h" +#include "go7007-priv.h" +#include "wis-i2c.h" + +/* Temporary defines until accepted in v4l-dvb */ +#ifndef V4L2_MPEG_STREAM_TYPE_MPEG_ELEM +#define V4L2_MPEG_STREAM_TYPE_MPEG_ELEM 6 /* MPEG elementary stream */ +#endif +#ifndef V4L2_MPEG_VIDEO_ENCODING_MPEG_4 +#define V4L2_MPEG_VIDEO_ENCODING_MPEG_4 3 +#endif + +#define call_all(dev, o, f, args...) \ + v4l2_device_call_until_err(dev, 0, o, f, ##args) + +static void deactivate_buffer(struct go7007_buffer *gobuf) +{ + int i; + + if (gobuf->state != BUF_STATE_IDLE) { + list_del(&gobuf->stream); + gobuf->state = BUF_STATE_IDLE; + } + if (gobuf->page_count > 0) { + for (i = 0; i < gobuf->page_count; ++i) + page_cache_release(gobuf->pages[i]); + gobuf->page_count = 0; + } +} + +static void abort_queued(struct go7007 *go) +{ + struct go7007_buffer *gobuf, *next; + + list_for_each_entry_safe(gobuf, next, &go->stream, stream) { + deactivate_buffer(gobuf); + } +} + +static int go7007_streamoff(struct go7007 *go) +{ + int retval = -EINVAL; + unsigned long flags; + + mutex_lock(&go->hw_lock); + if (go->streaming) { + go->streaming = 0; + go7007_stream_stop(go); + spin_lock_irqsave(&go->spinlock, flags); + abort_queued(go); + spin_unlock_irqrestore(&go->spinlock, flags); + go7007_reset_encoder(go); + retval = 0; + } + mutex_unlock(&go->hw_lock); + return 0; +} + +static int go7007_open(struct file *file) +{ + struct go7007 *go = video_get_drvdata(video_devdata(file)); + struct go7007_file *gofh; + + if (go->status != STATUS_ONLINE) + return -EBUSY; + gofh = kmalloc(sizeof(struct go7007_file), GFP_KERNEL); + if (gofh == NULL) + return -ENOMEM; + ++go->ref_count; + gofh->go = go; + mutex_init(&gofh->lock); + gofh->buf_count = 0; + file->private_data = gofh; + return 0; +} + +static int go7007_release(struct file *file) +{ + struct go7007_file *gofh = file->private_data; + struct go7007 *go = gofh->go; + + if (gofh->buf_count > 0) { + go7007_streamoff(go); + go->in_use = 0; + kfree(gofh->bufs); + gofh->buf_count = 0; + } + kfree(gofh); + if (--go->ref_count == 0) + kfree(go); + file->private_data = NULL; + return 0; +} + +static u32 get_frame_type_flag(struct go7007_buffer *gobuf, int format) +{ + u8 *f = page_address(gobuf->pages[0]); + + switch (format) { + case GO7007_FORMAT_MJPEG: + return V4L2_BUF_FLAG_KEYFRAME; + case GO7007_FORMAT_MPEG4: + switch ((f[gobuf->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 GO7007_FORMAT_MPEG1: + case GO7007_FORMAT_MPEG2: + switch ((f[gobuf->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 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 && fmt->fmt.pix.pixelformat != V4L2_PIX_FMT_MJPEG && + fmt->fmt.pix.pixelformat != V4L2_PIX_FMT_MPEG && + fmt->fmt.pix.pixelformat != V4L2_PIX_FMT_MPEG4) + return -EINVAL; + + switch (go->standard) { + case GO7007_STD_NTSC: + sensor_width = 720; + sensor_height = 480; + break; + case GO7007_STD_PAL: + sensor_width = 720; + sensor_height = 576; + break; + case GO7007_STD_OTHER: + sensor_width = go->board_info->sensor_width; + sensor_height = go->board_info->sensor_height; + break; + } + + 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 { + int requested_size = fmt->fmt.pix.width * fmt->fmt.pix.height; + int sensor_size = sensor_width * sensor_height; + + if (64 * requested_size < 9 * sensor_size) { + width = sensor_width / 4; + height = sensor_height / 4; + } else if (64 * requested_size < 36 * sensor_size) { + 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; + + 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; + if (fmt != NULL) + mbus_fmt.width = fmt->fmt.pix.width; + else + mbus_fmt.width = width; + + if (height > sensor_height / 2) { + mbus_fmt.height = height / 2; + go->encoder_v_halve = 0; + } else { + mbus_fmt.height = height; + go->encoder_v_halve = 1; + } + 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; + } + } + + if (fmt == NULL) + return 0; + + switch (fmt->fmt.pix.pixelformat) { + case V4L2_PIX_FMT_MPEG: + if (go->format == GO7007_FORMAT_MPEG1 || + go->format == GO7007_FORMAT_MPEG2 || + go->format == GO7007_FORMAT_MPEG4) + break; + go->format = GO7007_FORMAT_MPEG1; + go->pali = 0; + go->aspect_ratio = GO7007_RATIO_1_1; + go->gop_size = go->sensor_framerate / 1000; + go->ipb = 0; + go->closed_gop = 1; + go->repeat_seqhead = 1; + go->seq_header_enable = 1; + go->gop_header_enable = 1; + go->dvd_mode = 0; + break; + /* Backwards compatibility only! */ + case V4L2_PIX_FMT_MPEG4: + if (go->format == GO7007_FORMAT_MPEG4) + break; + go->format = GO7007_FORMAT_MPEG4; + go->pali = 0xf5; + go->aspect_ratio = GO7007_RATIO_1_1; + go->gop_size = go->sensor_framerate / 1000; + go->ipb = 0; + go->closed_gop = 1; + go->repeat_seqhead = 1; + go->seq_header_enable = 1; + go->gop_header_enable = 1; + go->dvd_mode = 0; + break; + case V4L2_PIX_FMT_MJPEG: + go->format = GO7007_FORMAT_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; + break; + } + 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 mpeg_query_ctrl(struct v4l2_queryctrl *ctrl) +{ + static const u32 mpeg_ctrls[] = { + V4L2_CID_MPEG_CLASS, + V4L2_CID_MPEG_STREAM_TYPE, + V4L2_CID_MPEG_VIDEO_ENCODING, + V4L2_CID_MPEG_VIDEO_ASPECT, + V4L2_CID_MPEG_VIDEO_GOP_SIZE, + V4L2_CID_MPEG_VIDEO_GOP_CLOSURE, + V4L2_CID_MPEG_VIDEO_BITRATE, + 0 + }; + static const u32 *ctrl_classes[] = { + mpeg_ctrls, + NULL + }; + + ctrl->id = v4l2_ctrl_next(ctrl_classes, ctrl->id); + + switch (ctrl->id) { + case V4L2_CID_MPEG_CLASS: + return v4l2_ctrl_query_fill(ctrl, 0, 0, 0, 0); + case V4L2_CID_MPEG_STREAM_TYPE: + return v4l2_ctrl_query_fill(ctrl, + V4L2_MPEG_STREAM_TYPE_MPEG2_DVD, + V4L2_MPEG_STREAM_TYPE_MPEG_ELEM, 1, + V4L2_MPEG_STREAM_TYPE_MPEG_ELEM); + case V4L2_CID_MPEG_VIDEO_ENCODING: + return v4l2_ctrl_query_fill(ctrl, + V4L2_MPEG_VIDEO_ENCODING_MPEG_1, + V4L2_MPEG_VIDEO_ENCODING_MPEG_4, 1, + V4L2_MPEG_VIDEO_ENCODING_MPEG_2); + case V4L2_CID_MPEG_VIDEO_ASPECT: + return v4l2_ctrl_query_fill(ctrl, + V4L2_MPEG_VIDEO_ASPECT_1x1, + V4L2_MPEG_VIDEO_ASPECT_16x9, 1, + V4L2_MPEG_VIDEO_ASPECT_1x1); + case V4L2_CID_MPEG_VIDEO_GOP_SIZE: + return v4l2_ctrl_query_fill(ctrl, 0, 34, 1, 15); + case V4L2_CID_MPEG_VIDEO_GOP_CLOSURE: + return v4l2_ctrl_query_fill(ctrl, 0, 1, 1, 0); + case V4L2_CID_MPEG_VIDEO_BITRATE: + return v4l2_ctrl_query_fill(ctrl, + 64000, + 10000000, 1, + 1500000); + default: + return -EINVAL; + } + return 0; +} + +static int mpeg_s_ctrl(struct v4l2_control *ctrl, struct go7007 *go) +{ + /* pretty sure we can't change any of these while streaming */ + if (go->streaming) + return -EBUSY; + + switch (ctrl->id) { + case V4L2_CID_MPEG_STREAM_TYPE: + switch (ctrl->value) { + case V4L2_MPEG_STREAM_TYPE_MPEG2_DVD: + go->format = GO7007_FORMAT_MPEG2; + go->bitrate = 9800000; + go->gop_size = 15; + go->pali = 0x48; + go->closed_gop = 1; + go->repeat_seqhead = 0; + go->seq_header_enable = 1; + go->gop_header_enable = 1; + go->dvd_mode = 1; + break; + case V4L2_MPEG_STREAM_TYPE_MPEG_ELEM: + /* todo: */ + break; + default: + return -EINVAL; + } + break; + case V4L2_CID_MPEG_VIDEO_ENCODING: + switch (ctrl->value) { + case V4L2_MPEG_VIDEO_ENCODING_MPEG_1: + go->format = GO7007_FORMAT_MPEG1; + go->pali = 0; + break; + case V4L2_MPEG_VIDEO_ENCODING_MPEG_2: + go->format = GO7007_FORMAT_MPEG2; + /*if (mpeg->pali >> 24 == 2) + go->pali = mpeg->pali & 0xff; + else*/ + go->pali = 0x48; + break; + case V4L2_MPEG_VIDEO_ENCODING_MPEG_4: + go->format = GO7007_FORMAT_MPEG4; + /*if (mpeg->pali >> 24 == 4) + go->pali = mpeg->pali & 0xff; + else*/ + go->pali = 0xf5; + break; + default: + return -EINVAL; + } + go->gop_header_enable = + /*mpeg->flags & GO7007_MPEG_OMIT_GOP_HEADER + ? 0 :*/ 1; + /*if (mpeg->flags & GO7007_MPEG_REPEAT_SEQHEADER) + go->repeat_seqhead = 1; + else*/ + go->repeat_seqhead = 0; + go->dvd_mode = 0; + break; + case V4L2_CID_MPEG_VIDEO_ASPECT: + if (go->format == GO7007_FORMAT_MJPEG) + return -EINVAL; + switch (ctrl->value) { + 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; + case V4L2_MPEG_VIDEO_ASPECT_221x100: + default: + return -EINVAL; + } + break; + case V4L2_CID_MPEG_VIDEO_GOP_SIZE: + if (ctrl->value < 0 || ctrl->value > 34) + return -EINVAL; + go->gop_size = ctrl->value; + break; + case V4L2_CID_MPEG_VIDEO_GOP_CLOSURE: + if (ctrl->value != 0 && ctrl->value != 1) + return -EINVAL; + go->closed_gop = ctrl->value; + break; + case V4L2_CID_MPEG_VIDEO_BITRATE: + /* Upper bound is kind of arbitrary here */ + if (ctrl->value < 64000 || ctrl->value > 10000000) + return -EINVAL; + go->bitrate = ctrl->value; + break; + default: + return -EINVAL; + } + return 0; +} + +static int mpeg_g_ctrl(struct v4l2_control *ctrl, struct go7007 *go) +{ + switch (ctrl->id) { + case V4L2_CID_MPEG_STREAM_TYPE: + if (go->dvd_mode) + ctrl->value = V4L2_MPEG_STREAM_TYPE_MPEG2_DVD; + else + ctrl->value = V4L2_MPEG_STREAM_TYPE_MPEG_ELEM; + break; + case V4L2_CID_MPEG_VIDEO_ENCODING: + switch (go->format) { + case GO7007_FORMAT_MPEG1: + ctrl->value = V4L2_MPEG_VIDEO_ENCODING_MPEG_1; + break; + case GO7007_FORMAT_MPEG2: + ctrl->value = V4L2_MPEG_VIDEO_ENCODING_MPEG_2; + break; + case GO7007_FORMAT_MPEG4: + ctrl->value = V4L2_MPEG_VIDEO_ENCODING_MPEG_4; + break; + default: + return -EINVAL; + } + break; + case V4L2_CID_MPEG_VIDEO_ASPECT: + switch (go->aspect_ratio) { + case GO7007_RATIO_1_1: + ctrl->value = V4L2_MPEG_VIDEO_ASPECT_1x1; + break; + case GO7007_RATIO_4_3: + ctrl->value = V4L2_MPEG_VIDEO_ASPECT_4x3; + break; + case GO7007_RATIO_16_9: + ctrl->value = V4L2_MPEG_VIDEO_ASPECT_16x9; + break; + default: + return -EINVAL; + } + break; + case V4L2_CID_MPEG_VIDEO_GOP_SIZE: + ctrl->value = go->gop_size; + break; + case V4L2_CID_MPEG_VIDEO_GOP_CLOSURE: + ctrl->value = go->closed_gop; + break; + case V4L2_CID_MPEG_VIDEO_BITRATE: + ctrl->value = go->bitrate; + break; + default: + return -EINVAL; + } + return 0; +} + +static int vidioc_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + struct go7007 *go = ((struct go7007_file *) priv)->go; + + strlcpy(cap->driver, "go7007", sizeof(cap->driver)); + strlcpy(cap->card, go->name, sizeof(cap->card)); +#if 0 + strlcpy(cap->bus_info, dev_name(&dev->udev->dev), sizeof(cap->bus_info)); +#endif + + cap->version = KERNEL_VERSION(0, 9, 8); + + cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | + V4L2_CAP_STREAMING; /* | V4L2_CAP_AUDIO; */ + + if (go->board_info->flags & GO7007_BOARD_HAS_TUNER) + cap->capabilities |= V4L2_CAP_TUNER; + + 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_MPEG; + desc = "MPEG1/MPEG2/MPEG4"; + 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 = ((struct go7007_file *) priv)->go; + + 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 == GO7007_FORMAT_MJPEG) ? + V4L2_PIX_FMT_MJPEG : V4L2_PIX_FMT_MPEG; + 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 = ((struct go7007_file *) priv)->go; + + 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 = ((struct go7007_file *) priv)->go; + + if (go->streaming) + return -EBUSY; + + return set_capture_size(go, fmt, 0); +} + +static int vidioc_reqbufs(struct file *file, void *priv, + struct v4l2_requestbuffers *req) +{ + struct go7007_file *gofh = priv; + struct go7007 *go = gofh->go; + int retval = -EBUSY; + unsigned int count, i; + + if (go->streaming) + return retval; + + if (req->type != V4L2_BUF_TYPE_VIDEO_CAPTURE || + req->memory != V4L2_MEMORY_MMAP) + return -EINVAL; + + mutex_lock(&gofh->lock); + for (i = 0; i < gofh->buf_count; ++i) + if (gofh->bufs[i].mapped > 0) + goto unlock_and_return; + + mutex_lock(&go->hw_lock); + if (go->in_use > 0 && gofh->buf_count == 0) { + mutex_unlock(&go->hw_lock); + goto unlock_and_return; + } + + if (gofh->buf_count > 0) + kfree(gofh->bufs); + + retval = -ENOMEM; + count = req->count; + if (count > 0) { + if (count < 2) + count = 2; + if (count > 32) + count = 32; + + gofh->bufs = kcalloc(count, sizeof(struct go7007_buffer), + GFP_KERNEL); + + if (!gofh->bufs) { + mutex_unlock(&go->hw_lock); + goto unlock_and_return; + } + + for (i = 0; i < count; ++i) { + gofh->bufs[i].go = go; + gofh->bufs[i].index = i; + gofh->bufs[i].state = BUF_STATE_IDLE; + gofh->bufs[i].mapped = 0; + } + + go->in_use = 1; + } else { + go->in_use = 0; + } + + gofh->buf_count = count; + mutex_unlock(&go->hw_lock); + mutex_unlock(&gofh->lock); + + memset(req, 0, sizeof(*req)); + + req->count = count; + req->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + req->memory = V4L2_MEMORY_MMAP; + + return 0; + +unlock_and_return: + mutex_unlock(&gofh->lock); + return retval; +} + +static int vidioc_querybuf(struct file *file, void *priv, + struct v4l2_buffer *buf) +{ + struct go7007_file *gofh = priv; + int retval = -EINVAL; + unsigned int index; + + if (buf->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return retval; + + index = buf->index; + + mutex_lock(&gofh->lock); + if (index >= gofh->buf_count) + goto unlock_and_return; + + memset(buf, 0, sizeof(*buf)); + buf->index = index; + buf->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + switch (gofh->bufs[index].state) { + case BUF_STATE_QUEUED: + buf->flags = V4L2_BUF_FLAG_QUEUED; + break; + case BUF_STATE_DONE: + buf->flags = V4L2_BUF_FLAG_DONE; + break; + default: + buf->flags = 0; + } + + if (gofh->bufs[index].mapped) + buf->flags |= V4L2_BUF_FLAG_MAPPED; + buf->memory = V4L2_MEMORY_MMAP; + buf->m.offset = index * GO7007_BUF_SIZE; + buf->length = GO7007_BUF_SIZE; + mutex_unlock(&gofh->lock); + + return 0; + +unlock_and_return: + mutex_unlock(&gofh->lock); + return retval; +} + +static int vidioc_qbuf(struct file *file, void *priv, struct v4l2_buffer *buf) +{ + struct go7007_file *gofh = priv; + struct go7007 *go = gofh->go; + struct go7007_buffer *gobuf; + unsigned long flags; + int retval = -EINVAL; + int ret; + + if (buf->type != V4L2_BUF_TYPE_VIDEO_CAPTURE || + buf->memory != V4L2_MEMORY_MMAP) + return retval; + + mutex_lock(&gofh->lock); + if (buf->index < 0 || buf->index >= gofh->buf_count) + goto unlock_and_return; + + gobuf = &gofh->bufs[buf->index]; + if (!gobuf->mapped) + goto unlock_and_return; + + retval = -EBUSY; + if (gobuf->state != BUF_STATE_IDLE) + goto unlock_and_return; + + /* offset will be 0 until we really support USERPTR streaming */ + gobuf->offset = gobuf->user_addr & ~PAGE_MASK; + gobuf->bytesused = 0; + gobuf->frame_offset = 0; + gobuf->modet_active = 0; + if (gobuf->offset > 0) + gobuf->page_count = GO7007_BUF_PAGES + 1; + else + gobuf->page_count = GO7007_BUF_PAGES; + + retval = -ENOMEM; + down_read(¤t->mm->mmap_sem); + ret = get_user_pages(current, current->mm, + gobuf->user_addr & PAGE_MASK, gobuf->page_count, + 1, 1, gobuf->pages, NULL); + up_read(¤t->mm->mmap_sem); + + if (ret != gobuf->page_count) { + int i; + for (i = 0; i < ret; ++i) + page_cache_release(gobuf->pages[i]); + gobuf->page_count = 0; + goto unlock_and_return; + } + + gobuf->state = BUF_STATE_QUEUED; + spin_lock_irqsave(&go->spinlock, flags); + list_add_tail(&gobuf->stream, &go->stream); + spin_unlock_irqrestore(&go->spinlock, flags); + mutex_unlock(&gofh->lock); + + return 0; + +unlock_and_return: + mutex_unlock(&gofh->lock); + return retval; +} + + +static int vidioc_dqbuf(struct file *file, void *priv, struct v4l2_buffer *buf) +{ + struct go7007_file *gofh = priv; + struct go7007 *go = gofh->go; + struct go7007_buffer *gobuf; + int retval = -EINVAL; + unsigned long flags; + u32 frame_type_flag; + DEFINE_WAIT(wait); + + if (buf->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return retval; + if (buf->memory != V4L2_MEMORY_MMAP) + return retval; + + mutex_lock(&gofh->lock); + if (list_empty(&go->stream)) + goto unlock_and_return; + gobuf = list_entry(go->stream.next, + struct go7007_buffer, stream); + + retval = -EAGAIN; + if (gobuf->state != BUF_STATE_DONE && + !(file->f_flags & O_NONBLOCK)) { + for (;;) { + prepare_to_wait(&go->frame_waitq, &wait, + TASK_INTERRUPTIBLE); + if (gobuf->state == BUF_STATE_DONE) + break; + if (signal_pending(current)) { + retval = -ERESTARTSYS; + break; + } + schedule(); + } + finish_wait(&go->frame_waitq, &wait); + } + if (gobuf->state != BUF_STATE_DONE) + goto unlock_and_return; + + spin_lock_irqsave(&go->spinlock, flags); + deactivate_buffer(gobuf); + spin_unlock_irqrestore(&go->spinlock, flags); + frame_type_flag = get_frame_type_flag(gobuf, go->format); + gobuf->state = BUF_STATE_IDLE; + + memset(buf, 0, sizeof(*buf)); + buf->index = gobuf->index; + buf->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf->bytesused = gobuf->bytesused; + buf->flags = V4L2_BUF_FLAG_MAPPED | frame_type_flag; + buf->field = V4L2_FIELD_NONE; + buf->timestamp = gobuf->timestamp; + buf->sequence = gobuf->seq; + buf->memory = V4L2_MEMORY_MMAP; + buf->m.offset = gobuf->index * GO7007_BUF_SIZE; + buf->length = GO7007_BUF_SIZE; + buf->reserved = gobuf->modet_active; + + mutex_unlock(&gofh->lock); + return 0; + +unlock_and_return: + mutex_unlock(&gofh->lock); + return retval; +} + +static int vidioc_streamon(struct file *file, void *priv, + enum v4l2_buf_type type) +{ + struct go7007_file *gofh = priv; + struct go7007 *go = gofh->go; + int retval = 0; + + if (type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + mutex_lock(&gofh->lock); + mutex_lock(&go->hw_lock); + + if (!go->streaming) { + go->streaming = 1; + go->next_seq = 0; + go->active_buf = NULL; + if (go7007_start_encoder(go) < 0) + retval = -EIO; + else + retval = 0; + } + mutex_unlock(&go->hw_lock); + mutex_unlock(&gofh->lock); + + return retval; +} + +static int vidioc_streamoff(struct file *file, void *priv, + enum v4l2_buf_type type) +{ + struct go7007_file *gofh = priv; + struct go7007 *go = gofh->go; + + if (type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + mutex_lock(&gofh->lock); + go7007_streamoff(go); + mutex_unlock(&gofh->lock); + + return 0; +} + +static int vidioc_queryctrl(struct file *file, void *priv, + struct v4l2_queryctrl *query) +{ + struct go7007 *go = ((struct go7007_file *) priv)->go; + int id = query->id; + + if (0 == call_all(&go->v4l2_dev, core, queryctrl, query)) + return 0; + + query->id = id; + return mpeg_query_ctrl(query); +} + +static int vidioc_g_ctrl(struct file *file, void *priv, + struct v4l2_control *ctrl) +{ + struct go7007 *go = ((struct go7007_file *) priv)->go; + + if (0 == call_all(&go->v4l2_dev, core, g_ctrl, ctrl)) + return 0; + + return mpeg_g_ctrl(ctrl, go); +} + +static int vidioc_s_ctrl(struct file *file, void *priv, + struct v4l2_control *ctrl) +{ + struct go7007 *go = ((struct go7007_file *) priv)->go; + + if (0 == call_all(&go->v4l2_dev, core, s_ctrl, ctrl)) + return 0; + + return mpeg_s_ctrl(ctrl, go); +} + +static int vidioc_g_parm(struct file *filp, void *priv, + struct v4l2_streamparm *parm) +{ + struct go7007 *go = ((struct go7007_file *) priv)->go; + 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.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 = ((struct go7007_file *) priv)->go; + unsigned int n, d; + + if (parm->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + if (parm->parm.capture.capturemode != 0) + 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 0; +} + +/* VIDIOC_ENUMSTD on go7007 were used for enumberating the supported fps and + its resolution, when the device is not connected to TV. + This were an API abuse, probably used by the lack of specific IOCTL's to + enumberate it, by the time the driver were 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 bellow implements the newer ioctls +*/ +static int vidioc_enum_framesizes(struct file *filp, void *priv, + struct v4l2_frmsizeenum *fsize) +{ + struct go7007 *go = ((struct go7007_file *) priv)->go; + + /* Return -EINVAL, if it is a TV board */ + if ((go->board_info->flags & GO7007_BOARD_HAS_TUNER) || + (go->board_info->sensor_flags & GO7007_SENSOR_TV)) + return -EINVAL; + + if (fsize->index > 0) + return -EINVAL; + + fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE; + fsize->discrete.width = go->board_info->sensor_width; + fsize->discrete.height = go->board_info->sensor_height; + + return 0; +} + +static int vidioc_enum_frameintervals(struct file *filp, void *priv, + struct v4l2_frmivalenum *fival) +{ + struct go7007 *go = ((struct go7007_file *) priv)->go; + + /* Return -EINVAL, if it is a TV board */ + if ((go->board_info->flags & GO7007_BOARD_HAS_TUNER) || + (go->board_info->sensor_flags & GO7007_SENSOR_TV)) + return -EINVAL; + + if (fival->index > 0) + return -EINVAL; + + fival->type = V4L2_FRMIVAL_TYPE_DISCRETE; + fival->discrete.numerator = 1001; + fival->discrete.denominator = go->board_info->sensor_framerate; + + return 0; +} + +static int vidioc_g_std(struct file *file, void *priv, v4l2_std_id *std) +{ + struct go7007 *go = ((struct go7007_file *) priv)->go; + + switch (go->standard) { + case GO7007_STD_NTSC: + *std = V4L2_STD_NTSC; + break; + case GO7007_STD_PAL: + *std = V4L2_STD_PAL; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int vidioc_s_std(struct file *file, void *priv, v4l2_std_id *std) +{ + struct go7007 *go = ((struct go7007_file *) priv)->go; + + if (go->streaming) + return -EBUSY; + + if (!(go->board_info->sensor_flags & GO7007_SENSOR_TV) && *std != 0) + return -EINVAL; + + if (*std == 0) + return -EINVAL; + + if ((go->board_info->flags & GO7007_BOARD_HAS_TUNER) && + go->input == go->board_info->num_inputs - 1) { + if (!go->i2c_adapter_online) + return -EIO; + if (call_all(&go->v4l2_dev, core, s_std, *std) < 0) + return -EINVAL; + } + + if (*std & V4L2_STD_NTSC) { + go->standard = GO7007_STD_NTSC; + go->sensor_framerate = 30000; + } else if (*std & V4L2_STD_PAL) { + go->standard = GO7007_STD_PAL; + go->sensor_framerate = 25025; + } else if (*std & V4L2_STD_SECAM) { + go->standard = GO7007_STD_PAL; + go->sensor_framerate = 25025; + } else + return -EINVAL; + + call_all(&go->v4l2_dev, core, s_std, *std); + set_capture_size(go, NULL, 0); + + return 0; +} + +static int vidioc_querystd(struct file *file, void *priv, v4l2_std_id *std) +{ + struct go7007 *go = ((struct go7007_file *) priv)->go; + + if ((go->board_info->flags & GO7007_BOARD_HAS_TUNER) && + go->input == go->board_info->num_inputs - 1) { + if (!go->i2c_adapter_online) + return -EIO; + return call_all(&go->v4l2_dev, video, querystd, std); + } else if (go->board_info->sensor_flags & GO7007_SENSOR_TV) + *std = V4L2_STD_NTSC | V4L2_STD_PAL | V4L2_STD_SECAM; + else + *std = 0; + + return 0; +} + +static int vidioc_enum_input(struct file *file, void *priv, + struct v4l2_input *inp) +{ + struct go7007 *go = ((struct go7007_file *) priv)->go; + + 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 last input */ + if ((go->board_info->flags & GO7007_BOARD_HAS_TUNER) && + inp->index == go->board_info->num_inputs - 1) + inp->type = V4L2_INPUT_TYPE_TUNER; + else + inp->type = V4L2_INPUT_TYPE_CAMERA; + + inp->audioset = 0; + inp->tuner = 0; + if (go->board_info->sensor_flags & GO7007_SENSOR_TV) + inp->std = V4L2_STD_NTSC | V4L2_STD_PAL | + V4L2_STD_SECAM; + else + inp->std = 0; + + return 0; +} + + +static int vidioc_g_input(struct file *file, void *priv, unsigned int *input) +{ + struct go7007 *go = ((struct go7007_file *) priv)->go; + + *input = go->input; + + return 0; +} + +static int vidioc_s_input(struct file *file, void *priv, unsigned int input) +{ + struct go7007 *go = ((struct go7007_file *) priv)->go; + + if (input >= go->board_info->num_inputs) + return -EINVAL; + if (go->streaming) + return -EBUSY; + + go->input = input; + + return call_all(&go->v4l2_dev, video, s_routing, input, 0, 0); +} + +static int vidioc_g_tuner(struct file *file, void *priv, + struct v4l2_tuner *t) +{ + struct go7007 *go = ((struct go7007_file *) priv)->go; + + if (!(go->board_info->flags & GO7007_BOARD_HAS_TUNER)) + return -EINVAL; + if (t->index != 0) + return -EINVAL; + if (!go->i2c_adapter_online) + return -EIO; + + return call_all(&go->v4l2_dev, tuner, g_tuner, t); +} + +static int vidioc_s_tuner(struct file *file, void *priv, + struct v4l2_tuner *t) +{ + struct go7007 *go = ((struct go7007_file *) priv)->go; + + if (!(go->board_info->flags & GO7007_BOARD_HAS_TUNER)) + return -EINVAL; + if (t->index != 0) + return -EINVAL; + if (!go->i2c_adapter_online) + return -EIO; + + switch (go->board_id) { + case GO7007_BOARDID_PX_TV402U_NA: + case GO7007_BOARDID_PX_TV402U_JP: + /* No selectable options currently */ + if (t->audmode != V4L2_TUNER_MODE_STEREO) + return -EINVAL; + break; + } + + 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 = ((struct go7007_file *) priv)->go; + + if (!(go->board_info->flags & GO7007_BOARD_HAS_TUNER)) + return -EINVAL; + if (!go->i2c_adapter_online) + return -EIO; + + f->type = V4L2_TUNER_ANALOG_TV; + + return call_all(&go->v4l2_dev, tuner, g_frequency, f); +} + +static int vidioc_s_frequency(struct file *file, void *priv, + struct v4l2_frequency *f) +{ + struct go7007 *go = ((struct go7007_file *) priv)->go; + + if (!(go->board_info->flags & GO7007_BOARD_HAS_TUNER)) + return -EINVAL; + if (!go->i2c_adapter_online) + return -EIO; + + return call_all(&go->v4l2_dev, tuner, s_frequency, f); +} + +static int vidioc_cropcap(struct file *file, void *priv, + struct v4l2_cropcap *cropcap) +{ + struct go7007 *go = ((struct go7007_file *) priv)->go; + + if (cropcap->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + /* These specify the raw input of the sensor */ + switch (go->standard) { + case GO7007_STD_NTSC: + cropcap->bounds.top = 0; + cropcap->bounds.left = 0; + cropcap->bounds.width = 720; + cropcap->bounds.height = 480; + cropcap->defrect.top = 0; + cropcap->defrect.left = 0; + cropcap->defrect.width = 720; + cropcap->defrect.height = 480; + break; + case GO7007_STD_PAL: + cropcap->bounds.top = 0; + cropcap->bounds.left = 0; + cropcap->bounds.width = 720; + cropcap->bounds.height = 576; + cropcap->defrect.top = 0; + cropcap->defrect.left = 0; + cropcap->defrect.width = 720; + cropcap->defrect.height = 576; + break; + case GO7007_STD_OTHER: + cropcap->bounds.top = 0; + cropcap->bounds.left = 0; + cropcap->bounds.width = go->board_info->sensor_width; + cropcap->bounds.height = go->board_info->sensor_height; + cropcap->defrect.top = 0; + cropcap->defrect.left = 0; + cropcap->defrect.width = go->board_info->sensor_width; + cropcap->defrect.height = go->board_info->sensor_height; + break; + } + + return 0; +} + +static int vidioc_g_crop(struct file *file, void *priv, struct v4l2_crop *crop) +{ + struct go7007 *go = ((struct go7007_file *) priv)->go; + + if (crop->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + crop->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + /* These specify the raw input of the sensor */ + switch (go->standard) { + case GO7007_STD_NTSC: + crop->c.top = 0; + crop->c.left = 0; + crop->c.width = 720; + crop->c.height = 480; + break; + case GO7007_STD_PAL: + crop->c.top = 0; + crop->c.left = 0; + crop->c.width = 720; + crop->c.height = 576; + break; + case GO7007_STD_OTHER: + crop->c.top = 0; + crop->c.left = 0; + crop->c.width = go->board_info->sensor_width; + crop->c.height = go->board_info->sensor_height; + break; + } + + return 0; +} + +/* FIXME: vidioc_s_crop is not really implemented!!! + */ +static int vidioc_s_crop(struct file *file, void *priv, struct v4l2_crop *crop) +{ + if (crop->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + return 0; +} + +static int vidioc_g_jpegcomp(struct file *file, void *priv, + struct v4l2_jpegcompression *params) +{ + memset(params, 0, sizeof(*params)); + params->quality = 50; /* ?? */ + params->jpeg_markers = V4L2_JPEG_MARKER_DHT | + V4L2_JPEG_MARKER_DQT; + + return 0; +} + +static int vidioc_s_jpegcomp(struct file *file, void *priv, + struct v4l2_jpegcompression *params) +{ + if (params->quality != 50 || + params->jpeg_markers != (V4L2_JPEG_MARKER_DHT | + V4L2_JPEG_MARKER_DQT)) + return -EINVAL; + + return 0; +} + +/* 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 + /* Temporary ioctls for controlling compression characteristics */ + case GO7007IOC_S_BITRATE: + { + int *bitrate = arg; + + if (go->streaming) + return -EINVAL; + /* Upper bound is kind of arbitrary here */ + if (*bitrate < 64000 || *bitrate > 10000000) + return -EINVAL; + go->bitrate = *bitrate; + return 0; + } + case GO7007IOC_G_BITRATE: + { + int *bitrate = arg; + + *bitrate = go->bitrate; + return 0; + } + case GO7007IOC_S_COMP_PARAMS: + { + struct go7007_comp_params *comp = arg; + + if (go->format == GO7007_FORMAT_MJPEG) + return -EINVAL; + if (comp->gop_size > 0) + go->gop_size = comp->gop_size; + else + go->gop_size = go->sensor_framerate / 1000; + if (go->gop_size != 15) + go->dvd_mode = 0; + /*go->ipb = comp->max_b_frames > 0;*/ /* completely untested */ + if (go->board_info->sensor_flags & GO7007_SENSOR_TV) { + switch (comp->aspect_ratio) { + case GO7007_ASPECT_RATIO_4_3_NTSC: + case GO7007_ASPECT_RATIO_4_3_PAL: + go->aspect_ratio = GO7007_RATIO_4_3; + break; + case GO7007_ASPECT_RATIO_16_9_NTSC: + case GO7007_ASPECT_RATIO_16_9_PAL: + go->aspect_ratio = GO7007_RATIO_16_9; + break; + default: + go->aspect_ratio = GO7007_RATIO_1_1; + break; + } + } + if (comp->flags & GO7007_COMP_OMIT_SEQ_HEADER) { + go->dvd_mode = 0; + go->seq_header_enable = 0; + } else { + go->seq_header_enable = 1; + } + /* fall-through */ + } + case GO7007IOC_G_COMP_PARAMS: + { + struct go7007_comp_params *comp = arg; + + if (go->format == GO7007_FORMAT_MJPEG) + return -EINVAL; + memset(comp, 0, sizeof(*comp)); + comp->gop_size = go->gop_size; + comp->max_b_frames = go->ipb ? 2 : 0; + switch (go->aspect_ratio) { + case GO7007_RATIO_4_3: + if (go->standard == GO7007_STD_NTSC) + comp->aspect_ratio = + GO7007_ASPECT_RATIO_4_3_NTSC; + else + comp->aspect_ratio = + GO7007_ASPECT_RATIO_4_3_PAL; + break; + case GO7007_RATIO_16_9: + if (go->standard == GO7007_STD_NTSC) + comp->aspect_ratio = + GO7007_ASPECT_RATIO_16_9_NTSC; + else + comp->aspect_ratio = + GO7007_ASPECT_RATIO_16_9_PAL; + break; + default: + comp->aspect_ratio = GO7007_ASPECT_RATIO_1_1; + break; + } + if (go->closed_gop) + comp->flags |= GO7007_COMP_CLOSED_GOP; + if (!go->seq_header_enable) + comp->flags |= GO7007_COMP_OMIT_SEQ_HEADER; + return 0; + } + case GO7007IOC_S_MPEG_PARAMS: + { + struct go7007_mpeg_params *mpeg = arg; + + if (go->format != GO7007_FORMAT_MPEG1 && + go->format != GO7007_FORMAT_MPEG2 && + go->format != GO7007_FORMAT_MPEG4) + return -EINVAL; + + if (mpeg->flags & GO7007_MPEG_FORCE_DVD_MODE) { + go->format = GO7007_FORMAT_MPEG2; + go->bitrate = 9800000; + go->gop_size = 15; + go->pali = 0x48; + go->closed_gop = 1; + go->repeat_seqhead = 0; + go->seq_header_enable = 1; + go->gop_header_enable = 1; + go->dvd_mode = 1; + } else { + switch (mpeg->mpeg_video_standard) { + case GO7007_MPEG_VIDEO_MPEG1: + go->format = GO7007_FORMAT_MPEG1; + go->pali = 0; + break; + case GO7007_MPEG_VIDEO_MPEG2: + go->format = GO7007_FORMAT_MPEG2; + if (mpeg->pali >> 24 == 2) + go->pali = mpeg->pali & 0xff; + else + go->pali = 0x48; + break; + case GO7007_MPEG_VIDEO_MPEG4: + go->format = GO7007_FORMAT_MPEG4; + if (mpeg->pali >> 24 == 4) + go->pali = mpeg->pali & 0xff; + else + go->pali = 0xf5; + break; + default: + return -EINVAL; + } + go->gop_header_enable = + mpeg->flags & GO7007_MPEG_OMIT_GOP_HEADER + ? 0 : 1; + if (mpeg->flags & GO7007_MPEG_REPEAT_SEQHEADER) + go->repeat_seqhead = 1; + else + go->repeat_seqhead = 0; + go->dvd_mode = 0; + } + /* fall-through */ + } + case GO7007IOC_G_MPEG_PARAMS: + { + struct go7007_mpeg_params *mpeg = arg; + + memset(mpeg, 0, sizeof(*mpeg)); + switch (go->format) { + case GO7007_FORMAT_MPEG1: + mpeg->mpeg_video_standard = GO7007_MPEG_VIDEO_MPEG1; + mpeg->pali = 0; + break; + case GO7007_FORMAT_MPEG2: + mpeg->mpeg_video_standard = GO7007_MPEG_VIDEO_MPEG2; + mpeg->pali = GO7007_MPEG_PROFILE(2, go->pali); + break; + case GO7007_FORMAT_MPEG4: + mpeg->mpeg_video_standard = GO7007_MPEG_VIDEO_MPEG4; + mpeg->pali = GO7007_MPEG_PROFILE(4, go->pali); + break; + default: + return -EINVAL; + } + if (!go->gop_header_enable) + mpeg->flags |= GO7007_MPEG_OMIT_GOP_HEADER; + if (go->repeat_seqhead) + mpeg->flags |= GO7007_MPEG_REPEAT_SEQHEADER; + if (go->dvd_mode) + mpeg->flags |= GO7007_MPEG_FORCE_DVD_MODE; + return 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_G_MD_PARAMS: + { + struct go7007_md_params *mdp = arg; + int region = mdp->region; + + if (mdp->region > 3) + return -EINVAL; + memset(mdp, 0, sizeof(struct go7007_md_params)); + mdp->region = region; + if (!go->modet[region].enable) + return 0; + mdp->pixel_threshold = + (go->modet[region].pixel_threshold << 1) + 1; + mdp->motion_threshold = + (go->modet[region].motion_threshold << 1) + 1; + mdp->trigger = + (go->modet[region].mb_threshold << 1) + 1; + return 0; + } + 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 ssize_t go7007_read(struct file *file, char __user *data, + size_t count, loff_t *ppos) +{ + return -EINVAL; +} + +static void go7007_vm_open(struct vm_area_struct *vma) +{ + struct go7007_buffer *gobuf = vma->vm_private_data; + + ++gobuf->mapped; +} + +static void go7007_vm_close(struct vm_area_struct *vma) +{ + struct go7007_buffer *gobuf = vma->vm_private_data; + unsigned long flags; + + if (--gobuf->mapped == 0) { + spin_lock_irqsave(&gobuf->go->spinlock, flags); + deactivate_buffer(gobuf); + spin_unlock_irqrestore(&gobuf->go->spinlock, flags); + } +} + +/* Copied from videobuf-dma-sg.c */ +static int go7007_vm_fault(struct vm_area_struct *vma, struct vm_fault *vmf) +{ + struct page *page; + + page = alloc_page(GFP_USER | __GFP_DMA32); + if (!page) + return VM_FAULT_OOM; + clear_user_highpage(page, (unsigned long)vmf->virtual_address); + vmf->page = page; + return 0; +} + +static struct vm_operations_struct go7007_vm_ops = { + .open = go7007_vm_open, + .close = go7007_vm_close, + .fault = go7007_vm_fault, +}; + +static int go7007_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct go7007_file *gofh = file->private_data; + unsigned int index; + + if (gofh->go->status != STATUS_ONLINE) + return -EIO; + if (!(vma->vm_flags & VM_SHARED)) + return -EINVAL; /* only support VM_SHARED mapping */ + if (vma->vm_end - vma->vm_start != GO7007_BUF_SIZE) + return -EINVAL; /* must map exactly one full buffer */ + mutex_lock(&gofh->lock); + index = vma->vm_pgoff / GO7007_BUF_PAGES; + if (index >= gofh->buf_count) { + mutex_unlock(&gofh->lock); + return -EINVAL; /* trying to map beyond requested buffers */ + } + if (index * GO7007_BUF_PAGES != vma->vm_pgoff) { + mutex_unlock(&gofh->lock); + return -EINVAL; /* offset is not aligned on buffer boundary */ + } + if (gofh->bufs[index].mapped > 0) { + mutex_unlock(&gofh->lock); + return -EBUSY; + } + gofh->bufs[index].mapped = 1; + gofh->bufs[index].user_addr = vma->vm_start; + vma->vm_ops = &go7007_vm_ops; + vma->vm_flags |= VM_DONTEXPAND; + vma->vm_flags &= ~VM_IO; + vma->vm_private_data = &gofh->bufs[index]; + mutex_unlock(&gofh->lock); + return 0; +} + +static unsigned int go7007_poll(struct file *file, poll_table *wait) +{ + struct go7007_file *gofh = file->private_data; + struct go7007_buffer *gobuf; + + if (list_empty(&gofh->go->stream)) + return POLLERR; + gobuf = list_entry(gofh->go->stream.next, struct go7007_buffer, stream); + poll_wait(file, &gofh->go->frame_waitq, wait); + if (gobuf->state == BUF_STATE_DONE) + return POLLIN | POLLRDNORM; + return 0; +} + +static void go7007_vfl_release(struct video_device *vfd) +{ + struct go7007 *go = video_get_drvdata(vfd); + + video_device_release(vfd); + if (--go->ref_count == 0) + kfree(go); +} + +static struct v4l2_file_operations go7007_fops = { + .owner = THIS_MODULE, + .open = go7007_open, + .release = go7007_release, + .ioctl = video_ioctl2, + .read = go7007_read, + .mmap = go7007_mmap, + .poll = go7007_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 = vidioc_reqbufs, + .vidioc_querybuf = vidioc_querybuf, + .vidioc_qbuf = vidioc_qbuf, + .vidioc_dqbuf = vidioc_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_queryctrl = vidioc_queryctrl, + .vidioc_g_ctrl = vidioc_g_ctrl, + .vidioc_s_ctrl = vidioc_s_ctrl, + .vidioc_streamon = vidioc_streamon, + .vidioc_streamoff = vidioc_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_cropcap = vidioc_cropcap, + .vidioc_g_crop = vidioc_g_crop, + .vidioc_s_crop = vidioc_s_crop, + .vidioc_g_jpegcomp = vidioc_g_jpegcomp, + .vidioc_s_jpegcomp = vidioc_s_jpegcomp, +}; + +static struct video_device go7007_template = { + .name = "go7007", + .fops = &go7007_fops, + .release = go7007_vfl_release, + .ioctl_ops = &video_ioctl_ops, + .tvnorms = V4L2_STD_ALL, + .current_norm = V4L2_STD_NTSC, +}; + +int go7007_v4l2_init(struct go7007 *go) +{ + int rv; + + go->video_dev = video_device_alloc(); + if (go->video_dev == NULL) + return -ENOMEM; + *go->video_dev = go7007_template; + go->video_dev->parent = go->dev; + rv = video_register_device(go->video_dev, VFL_TYPE_GRABBER, -1); + if (rv < 0) { + video_device_release(go->video_dev); + go->video_dev = NULL; + return rv; + } + rv = v4l2_device_register(go->dev, &go->v4l2_dev); + if (rv < 0) { + video_device_release(go->video_dev); + go->video_dev = NULL; + return rv; + } + video_set_drvdata(go->video_dev, go); + ++go->ref_count; + printk(KERN_INFO "%s: registered device %s [v4l2]\n", + go->video_dev->name, video_device_node_name(go->video_dev)); + + return 0; +} + +void go7007_v4l2_remove(struct go7007 *go) +{ + unsigned long flags; + + mutex_lock(&go->hw_lock); + if (go->streaming) { + go->streaming = 0; + go7007_stream_stop(go); + spin_lock_irqsave(&go->spinlock, flags); + abort_queued(go); + spin_unlock_irqrestore(&go->spinlock, flags); + } + mutex_unlock(&go->hw_lock); + if (go->video_dev) + video_unregister_device(go->video_dev); + v4l2_device_unregister(&go->v4l2_dev); +} diff --git a/drivers/staging/media/go7007/go7007.h b/drivers/staging/media/go7007/go7007.h new file mode 100644 index 0000000..7399c91 --- /dev/null +++ b/drivers/staging/media/go7007/go7007.h @@ -0,0 +1,114 @@ +/* + * 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. + */ + +/* DEPRECATED -- use V4L2_PIX_FMT_MPEG and then call GO7007IOC_S_MPEG_PARAMS + * to select between MPEG1, MPEG2, and MPEG4 */ +#define V4L2_PIX_FMT_MPEG4 v4l2_fourcc('M', 'P', 'G', '4') /* MPEG4 */ + +/* These will be replaced with a better interface + * soon, so don't get too attached to them */ +#define GO7007IOC_S_BITRATE _IOW('V', BASE_VIDIOC_PRIVATE + 0, int) +#define GO7007IOC_G_BITRATE _IOR('V', BASE_VIDIOC_PRIVATE + 1, int) + +enum go7007_aspect_ratio { + GO7007_ASPECT_RATIO_1_1 = 0, + GO7007_ASPECT_RATIO_4_3_NTSC = 1, + GO7007_ASPECT_RATIO_4_3_PAL = 2, + GO7007_ASPECT_RATIO_16_9_NTSC = 3, + GO7007_ASPECT_RATIO_16_9_PAL = 4, +}; + +/* Used to set generic compression parameters */ +struct go7007_comp_params { + __u32 gop_size; + __u32 max_b_frames; + enum go7007_aspect_ratio aspect_ratio; + __u32 flags; + __u32 reserved[8]; +}; + +#define GO7007_COMP_CLOSED_GOP 0x00000001 +#define GO7007_COMP_OMIT_SEQ_HEADER 0x00000002 + +enum go7007_mpeg_video_standard { + GO7007_MPEG_VIDEO_MPEG1 = 0, + GO7007_MPEG_VIDEO_MPEG2 = 1, + GO7007_MPEG_VIDEO_MPEG4 = 2, +}; + +/* Used to set parameters for V4L2_PIX_FMT_MPEG format */ +struct go7007_mpeg_params { + enum go7007_mpeg_video_standard mpeg_video_standard; + __u32 flags; + __u32 pali; + __u32 reserved[8]; +}; + +#define GO7007_MPEG_FORCE_DVD_MODE 0x00000001 +#define GO7007_MPEG_OMIT_GOP_HEADER 0x00000002 +#define GO7007_MPEG_REPEAT_SEQHEADER 0x00000004 + +#define GO7007_MPEG_PROFILE(format, pali) (((format)<<24)|(pali)) + +#define GO7007_MPEG2_PROFILE_MAIN_MAIN GO7007_MPEG_PROFILE(2, 0x48) + +#define GO7007_MPEG4_PROFILE_S_L0 GO7007_MPEG_PROFILE(4, 0x08) +#define GO7007_MPEG4_PROFILE_S_L1 GO7007_MPEG_PROFILE(4, 0x01) +#define GO7007_MPEG4_PROFILE_S_L2 GO7007_MPEG_PROFILE(4, 0x02) +#define GO7007_MPEG4_PROFILE_S_L3 GO7007_MPEG_PROFILE(4, 0x03) +#define GO7007_MPEG4_PROFILE_ARTS_L1 GO7007_MPEG_PROFILE(4, 0x91) +#define GO7007_MPEG4_PROFILE_ARTS_L2 GO7007_MPEG_PROFILE(4, 0x92) +#define GO7007_MPEG4_PROFILE_ARTS_L3 GO7007_MPEG_PROFILE(4, 0x93) +#define GO7007_MPEG4_PROFILE_ARTS_L4 GO7007_MPEG_PROFILE(4, 0x94) +#define GO7007_MPEG4_PROFILE_AS_L0 GO7007_MPEG_PROFILE(4, 0xf0) +#define GO7007_MPEG4_PROFILE_AS_L1 GO7007_MPEG_PROFILE(4, 0xf1) +#define GO7007_MPEG4_PROFILE_AS_L2 GO7007_MPEG_PROFILE(4, 0xf2) +#define GO7007_MPEG4_PROFILE_AS_L3 GO7007_MPEG_PROFILE(4, 0xf3) +#define GO7007_MPEG4_PROFILE_AS_L4 GO7007_MPEG_PROFILE(4, 0xf4) +#define GO7007_MPEG4_PROFILE_AS_L5 GO7007_MPEG_PROFILE(4, 0xf5) + +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_MPEG_PARAMS _IOWR('V', BASE_VIDIOC_PRIVATE + 2, \ + struct go7007_mpeg_params) +#define GO7007IOC_G_MPEG_PARAMS _IOR('V', BASE_VIDIOC_PRIVATE + 3, \ + struct go7007_mpeg_params) +#define GO7007IOC_S_COMP_PARAMS _IOWR('V', BASE_VIDIOC_PRIVATE + 4, \ + struct go7007_comp_params) +#define GO7007IOC_G_COMP_PARAMS _IOR('V', BASE_VIDIOC_PRIVATE + 5, \ + struct go7007_comp_params) +#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 0000000..9db1f39 --- /dev/null +++ b/drivers/staging/media/go7007/go7007.txt @@ -0,0 +1,481 @@ +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_HOTPLUG - Support for hot-pluggable devices + CONFIG_MODULES - Enable loadable module support + CONFIG_KMOD - Automatic kernel module loading + 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_DEVICEFS - USB device filesystem + 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 0000000..e7736a9 --- /dev/null +++ b/drivers/staging/media/go7007/s2250-board.c @@ -0,0 +1,698 @@ +/* + * 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/init.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"); + +#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; + 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) { + printk(KERN_INFO "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; + 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) { + printk(KERN_INFO "i2c lock failed\n"); + kfree(buf); + return -EINTR; + } + if (go7007_usb_vendor_request(go, 0x57, addr, val, buf, 16, 1) < 0) { + kfree(buf); + return -EFAULT; + } + + mutex_unlock(&usb->i2c_lock); + 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) { + printk(KERN_INFO "invalid fp write %x %x\n", + val_read, val); + return -EFAULT; + } + if (subaddr != addr) { + printk(KERN_INFO "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; + 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) { + printk(KERN_INFO "i2c lock failed\n"); + kfree(buf); + return -EINTR; + } + if (go7007_usb_vendor_request(go, 0x58, addr, 0, buf, 16, 1) < 0) { + kfree(buf); + return -EFAULT; + } + mutex_unlock(&usb->i2c_lock); + + *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) { + printk(KERN_INFO "s2250: 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) { + printk(KERN_INFO "s2250: 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; + switch (norm) { + case V4L2_STD_NTSC: + write_regs_fp(client, vid_regs_fp); + write_reg_fp(client, 0x20, vidsource | 1); + break; + case V4L2_STD_PAL: + write_regs_fp(client, vid_regs_fp); + write_regs_fp(client, vid_regs_fp_pal); + write_reg_fp(client, 0x20, vidsource); + break; + default: + return -EINVAL; + } + state->std = norm; + return 0; +} + +static int s2250_queryctrl(struct v4l2_subdev *sd, struct v4l2_queryctrl *query) +{ + switch (query->id) { + case V4L2_CID_BRIGHTNESS: + return v4l2_ctrl_query_fill(query, 0, 100, 1, 50); + case V4L2_CID_CONTRAST: + return v4l2_ctrl_query_fill(query, 0, 100, 1, 50); + case V4L2_CID_SATURATION: + return v4l2_ctrl_query_fill(query, 0, 100, 1, 50); + case V4L2_CID_HUE: + return v4l2_ctrl_query_fill(query, -50, 50, 1, 0); + default: + return -EINVAL; + } + return 0; +} + +static int s2250_s_ctrl(struct v4l2_subdev *sd, struct v4l2_control *ctrl) +{ + struct s2250 *state = to_state(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + int value1; + u16 oldvalue; + + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + if (ctrl->value > 100) + state->brightness = 100; + else if (ctrl->value < 0) + state->brightness = 0; + else + state->brightness = ctrl->value; + value1 = (state->brightness - 50) * 255 / 100; + read_reg_fp(client, VPX322_ADDR_BRIGHTNESS0, &oldvalue); + write_reg_fp(client, VPX322_ADDR_BRIGHTNESS0, + value1 | (oldvalue & ~0xff)); + read_reg_fp(client, VPX322_ADDR_BRIGHTNESS1, &oldvalue); + write_reg_fp(client, VPX322_ADDR_BRIGHTNESS1, + value1 | (oldvalue & ~0xff)); + write_reg_fp(client, 0x140, 0x60); + break; + case V4L2_CID_CONTRAST: + if (ctrl->value > 100) + state->contrast = 100; + else if (ctrl->value < 0) + state->contrast = 0; + else + state->contrast = ctrl->value; + value1 = state->contrast * 0x40 / 100; + if (value1 > 0x3f) + value1 = 0x3f; /* max */ + read_reg_fp(client, VPX322_ADDR_CONTRAST0, &oldvalue); + write_reg_fp(client, VPX322_ADDR_CONTRAST0, + value1 | (oldvalue & ~0x3f)); + read_reg_fp(client, VPX322_ADDR_CONTRAST1, &oldvalue); + write_reg_fp(client, VPX322_ADDR_CONTRAST1, + value1 | (oldvalue & ~0x3f)); + write_reg_fp(client, 0x140, 0x60); + break; + case V4L2_CID_SATURATION: + if (ctrl->value > 100) + state->saturation = 100; + else if (ctrl->value < 0) + state->saturation = 0; + else + state->saturation = ctrl->value; + value1 = state->saturation * 4140 / 100; + if (value1 > 4094) + value1 = 4094; + write_reg_fp(client, VPX322_ADDR_SAT, value1); + break; + case V4L2_CID_HUE: + if (ctrl->value > 50) + state->hue = 50; + else if (ctrl->value < -50) + state->hue = -50; + else + state->hue = ctrl->value; + /* clamp the hue range */ + value1 = state->hue * 280 / 50; + write_reg_fp(client, VPX322_ADDR_HUE, value1); + break; + default: + return -EINVAL; + } + return 0; +} + +static int s2250_g_ctrl(struct v4l2_subdev *sd, struct v4l2_control *ctrl) +{ + struct s2250 *state = to_state(sd); + + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + ctrl->value = state->brightness; + break; + case V4L2_CID_CONTRAST: + ctrl->value = state->contrast; + break; + case V4L2_CID_SATURATION: + ctrl->value = state->saturation; + break; + case V4L2_CID_HUE: + ctrl->value = state->hue; + 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, "Brightness: %d\n", state->brightness); + v4l2_info(sd, "Contrast: %d\n", state->contrast); + v4l2_info(sd, "Saturation: %d\n", state->saturation); + v4l2_info(sd, "Hue: %d\n", state->hue); return 0; + 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 0; +} + +/* --------------------------------------------------------------------------*/ + +static const struct v4l2_subdev_core_ops s2250_core_ops = { + .log_status = s2250_log_status, + .g_ctrl = s2250_g_ctrl, + .s_ctrl = s2250_s_ctrl, + .queryctrl = s2250_queryctrl, + .s_std = s2250_s_std, +}; + +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_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 = kmalloc(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); + + 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) { + printk(KERN_ERR + "s2250: error initializing audio\n"); + i2c_unregister_device(audio); + kfree(state); + return 0; + } + + if (write_regs(client, vid_regs) < 0) { + printk(KERN_ERR + "s2250: error initializing decoder\n"); + i2c_unregister_device(audio); + kfree(state); + return 0; + } + if (write_regs_fp(client, vid_regs_fp) < 0) { + printk(KERN_ERR + "s2250: error initializing decoder\n"); + i2c_unregister_device(audio); + kfree(state); + return 0; + } + /* 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; +} + +static int s2250_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + + v4l2_device_unregister_subdev(sd); + kfree(to_state(sd)); + 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, +}; + +static __init int init_s2250(void) +{ + return i2c_add_driver(&s2250_driver); +} + +static __exit void exit_s2250(void) +{ + i2c_del_driver(&s2250_driver); +} + +module_init(init_s2250); +module_exit(exit_s2250); diff --git a/drivers/staging/media/go7007/s2250-loader.c b/drivers/staging/media/go7007/s2250-loader.c new file mode 100644 index 0000000..4e13251 --- /dev/null +++ b/drivers/staging/media/go7007/s2250-loader.c @@ -0,0 +1,191 @@ +/* + * 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/init.h> +#include <linux/slab.h> +#include <linux/usb.h> +#include <dvb-usb.h> + +#define S2250_LOADER_FIRMWARE "s2250_loader.fw" +#define S2250_FIRMWARE "s2250.fw" + +typedef struct device_extension_s { + struct kref kref; + int minor; + struct usb_device *usbdev; +} device_extension_t, *pdevice_extension_t; + +#define USB_s2250loader_MAJOR 240 +#define USB_s2250loader_MINOR_BASE 0 +#define MAX_DEVICES 256 + +static pdevice_extension_t s2250_dev_table[MAX_DEVICES]; +static DEFINE_MUTEX(s2250_dev_table_mutex); + +#define to_s2250loader_dev_common(d) container_of(d, device_extension_t, kref) +static void s2250loader_delete(struct kref *kref) +{ + pdevice_extension_t s = to_s2250loader_dev_common(kref); + s2250_dev_table[s->minor] = NULL; + kfree(s); +} + +static int s2250loader_probe(struct usb_interface *interface, + const struct usb_device_id *id) +{ + struct usb_device *usbdev; + int minor, ret; + pdevice_extension_t s = NULL; + const struct firmware *fw; + + usbdev = usb_get_dev(interface_to_usbdev(interface)); + if (!usbdev) { + printk(KERN_ERR "Enter s2250loader_probe failed\n"); + return -1; + } + printk(KERN_INFO "Enter s2250loader_probe 2.6 kernel\n"); + printk(KERN_INFO "vendor id 0x%x, device id 0x%x devnum:%d\n", + usbdev->descriptor.idVendor, usbdev->descriptor.idProduct, + usbdev->devnum); + + if (usbdev->descriptor.bNumConfigurations != 1) { + printk(KERN_ERR "can't handle multiple config\n"); + return -1; + } + mutex_lock(&s2250_dev_table_mutex); + + for (minor = 0; minor < MAX_DEVICES; minor++) { + if (s2250_dev_table[minor] == NULL) + break; + } + + if (minor < 0 || minor >= MAX_DEVICES) { + printk(KERN_ERR "Invalid minor: %d\n", minor); + goto failed; + } + + /* Allocate dev data structure */ + s = kmalloc(sizeof(device_extension_t), GFP_KERNEL); + if (s == NULL) { + printk(KERN_ERR "Out of memory\n"); + goto failed; + } + s2250_dev_table[minor] = s; + + printk(KERN_INFO "s2250loader_probe: Device %d on Bus %d Minor %d\n", + usbdev->devnum, usbdev->bus->busnum, minor); + + memset(s, 0, sizeof(device_extension_t)); + s->usbdev = usbdev; + printk(KERN_INFO "loading 2250 loader\n"); + + kref_init(&(s->kref)); + + mutex_unlock(&s2250_dev_table_mutex); + + if (request_firmware(&fw, S2250_LOADER_FIRMWARE, &usbdev->dev)) { + printk(KERN_ERR + "s2250: unable to load firmware from file \"%s\"\n", + S2250_LOADER_FIRMWARE); + goto failed2; + } + ret = usb_cypress_load_firmware(usbdev, fw, CYPRESS_FX2); + release_firmware(fw); + if (0 != ret) { + printk(KERN_ERR "loader download failed\n"); + goto failed2; + } + + if (request_firmware(&fw, S2250_FIRMWARE, &usbdev->dev)) { + printk(KERN_ERR + "s2250: unable to load firmware from file \"%s\"\n", + S2250_FIRMWARE); + goto failed2; + } + ret = usb_cypress_load_firmware(usbdev, fw, CYPRESS_FX2); + release_firmware(fw); + if (0 != ret) { + printk(KERN_ERR "firmware_s2250 download failed\n"); + goto failed2; + } + + usb_set_intfdata(interface, s); + return 0; + +failed: + mutex_unlock(&s2250_dev_table_mutex); +failed2: + if (s) + kref_put(&(s->kref), s2250loader_delete); + + printk(KERN_ERR "probe failed\n"); + return -1; +} + +static void s2250loader_disconnect(struct usb_interface *interface) +{ + pdevice_extension_t s; + printk(KERN_INFO "s2250: disconnect\n"); + s = usb_get_intfdata(interface); + usb_set_intfdata(interface, NULL); + kref_put(&(s->kref), s2250loader_delete); +} + +static const struct usb_device_id s2250loader_ids[] = { + {USB_DEVICE(0x1943, 0xa250)}, + {} /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE(usb, s2250loader_ids); + +static struct usb_driver s2250loader_driver = { + .name = "s2250-loader", + .probe = s2250loader_probe, + .disconnect = s2250loader_disconnect, + .id_table = s2250loader_ids, +}; + +static int __init s2250loader_init(void) +{ + int r; + unsigned i = 0; + + for (i = 0; i < MAX_DEVICES; i++) + s2250_dev_table[i] = NULL; + + r = usb_register(&s2250loader_driver); + if (r) { + printk(KERN_ERR "usb_register failed. Error number %d\n", r); + return -1; + } + + printk(KERN_INFO "s2250loader_init: driver registered\n"); + return 0; +} +module_init(s2250loader_init); + +static void __exit s2250loader_cleanup(void) +{ + printk(KERN_INFO "s2250loader_cleanup\n"); + usb_deregister(&s2250loader_driver); +} +module_exit(s2250loader_cleanup); + +MODULE_AUTHOR(""); +MODULE_DESCRIPTION("firmware loader for Sensoray 2250/2251"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/staging/media/go7007/s2250-loader.h b/drivers/staging/media/go7007/s2250-loader.h new file mode 100644 index 0000000..b7c301a --- /dev/null +++ b/drivers/staging/media/go7007/s2250-loader.h @@ -0,0 +1,24 @@ +/* + * 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. + */ + +#ifndef _S2250_LOADER_H_ +#define _S2250_LOADER_H_ + +extern int s2250loader_init(void); +extern void s2250loader_cleanup(void); + +#endif diff --git a/drivers/staging/media/go7007/saa7134-go7007.c b/drivers/staging/media/go7007/saa7134-go7007.c new file mode 100644 index 0000000..cf7c34a --- /dev/null +++ b/drivers/staging/media/go7007/saa7134-go7007.c @@ -0,0 +1,532 @@ +/* + * 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 "saa7134-reg.h" +#include "saa7134.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 saa7134_dev *dev; + u8 *top; + u8 *bottom; + dma_addr_t top_dma; + dma_addr_t bottom_dma; +}; + +static struct go7007_board_info board_voyager = { + .firmware = "go7007tv.bin", + .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", + }, + }, +}; +MODULE_FIRMWARE("go7007tv.bin"); + +/********************* 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 (!go->streaming) + 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 (!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 (!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 int saa7134_go7007_send_command(struct go7007 *go, unsigned int cmd, + void *arg) +{ + struct saa7134_go7007 *saa = go->hpi_context; + struct saa7134_dev *dev = saa->dev; + + switch (cmd) { + case VIDIOC_S_STD: + { + v4l2_std_id *std = arg; + return saa7134_s_std_internal(dev, NULL, std); + } + case VIDIOC_G_STD: + { + v4l2_std_id *std = arg; + *std = dev->tvnorm->id; + return 0; + } + case VIDIOC_QUERYCTRL: + { + struct v4l2_queryctrl *ctrl = arg; + if (V4L2_CTRL_ID2CLASS(ctrl->id) == V4L2_CTRL_CLASS_USER) + return saa7134_queryctrl(NULL, NULL, ctrl); + } + case VIDIOC_G_CTRL: + { + struct v4l2_control *ctrl = arg; + if (V4L2_CTRL_ID2CLASS(ctrl->id) == V4L2_CTRL_CLASS_USER) + return saa7134_g_ctrl_internal(dev, NULL, ctrl); + } + case VIDIOC_S_CTRL: + { + struct v4l2_control *ctrl = arg; + if (V4L2_CTRL_ID2CLASS(ctrl->id) == V4L2_CTRL_CLASS_USER) + return saa7134_s_ctrl_internal(dev, NULL, ctrl); + } + } + return -EINVAL; + +} + +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, + .send_command = saa7134_go7007_send_command, +}; + +/********************* Add/remove functions *********************/ + +static int saa7134_go7007_init(struct saa7134_dev *dev) +{ + struct go7007 *go; + struct saa7134_go7007 *saa; + + printk(KERN_DEBUG "saa7134-go7007: probing new SAA713X board\n"); + + saa = kzalloc(sizeof(struct saa7134_go7007), GFP_KERNEL); + if (saa == NULL) + return -ENOMEM; + + /* 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; + + go = go7007_alloc(&board_voyager, &dev->pci->dev); + if (go == NULL) + goto allocfail; + go->board_id = GO7007_BOARDID_PCI_VOYAGER; + strncpy(go->name, saa7134_boards[dev->board].name, sizeof(go->name)); + go->hpi_ops = &saa7134_go7007_hpi_ops; + go->hpi_context = saa; + saa->dev = dev; + + /* Boot the GO7007 */ + if (go7007_boot_encoder(go, go->board_info->flags & + GO7007_BOARD_USE_ONBOARD_I2C) < 0) + goto initfail; + + /* Do any final GO7007 initialization, then register the + * V4L2 and ALSA interfaces */ + if (go7007_register_encoder(go) < 0) + goto initfail; + dev->empress_dev = go->video_dev; + video_set_drvdata(dev->empress_dev, go); + + go->status = STATUS_ONLINE; + return 0; + +initfail: + go->status = STATUS_SHUTDOWN; + return 0; + +allocfail: + if (saa->top) + free_page((unsigned long)saa->top); + if (saa->bottom) + free_page((unsigned long)saa->bottom); + kfree(saa); + 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); + saa = go->hpi_context; + go->status = STATUS_SHUTDOWN; + free_page((unsigned long)saa->top); + free_page((unsigned long)saa->bottom); + kfree(saa); + go7007_remove(go); + 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 0000000..deac938 --- /dev/null +++ b/drivers/staging/media/go7007/snd-go7007.c @@ -0,0 +1,306 @@ +/* + * 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/init.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 <asm/system.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 int 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; + if (--go->ref_count == 0) + kfree(go); + 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_create(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; + } + snd_card_set_dev(gosnd->card, go->dev); + ret = snd_pcm_new(gosnd->card, "go7007", 0, 0, 1, &gosnd->pcm); + if (ret < 0) { + snd_card_free(gosnd->card); + kfree(gosnd); + return ret; + } + strncpy(gosnd->card->driver, "go7007", sizeof(gosnd->card->driver)); + strncpy(gosnd->card->shortname, go->name, sizeof(gosnd->card->driver)); + strncpy(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; + ++dev; + ++go->ref_count; + + 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); + return 0; +} +EXPORT_SYMBOL(go7007_snd_remove); + +MODULE_LICENSE("GPL v2"); diff --git a/drivers/staging/media/go7007/wis-i2c.h b/drivers/staging/media/go7007/wis-i2c.h new file mode 100644 index 0000000..3c2b9be --- /dev/null +++ b/drivers/staging/media/go7007/wis-i2c.h @@ -0,0 +1,47 @@ +/* + * 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. + */ + +/* Temporary I2C IDs -- these need to be replaced with real registered IDs */ +#define I2C_DRIVERID_WIS_SAA7115 0xf0f0 +#define I2C_DRIVERID_WIS_UDA1342 0xf0f1 +#define I2C_DRIVERID_WIS_SONY_TUNER 0xf0f2 +#define I2C_DRIVERID_WIS_TW9903 0xf0f3 +#define I2C_DRIVERID_WIS_SAA7113 0xf0f4 +#define I2C_DRIVERID_WIS_OV7640 0xf0f5 +#define I2C_DRIVERID_WIS_TW2804 0xf0f6 +#define I2C_DRIVERID_S2250 0xf0f7 + +/* Flag to indicate that the client needs to be accessed with SCCB semantics */ +/* We re-use the I2C_M_TEN value so the flag passes through the masks in the + * core I2C code. Major kludge, but the I2C layer ain't exactly flexible. */ +#define I2C_CLIENT_SCCB 0x10 + +/* Definitions for new video decoder commands */ + +struct video_decoder_resolution { + unsigned int width; + unsigned int height; +}; + +#define DECODER_SET_RESOLUTION _IOW('d', 200, struct video_decoder_resolution) +#define DECODER_SET_CHANNEL _IOW('d', 201, int) + +/* Sony tuner types */ + +#define TUNER_SONY_BTF_PG472Z 200 +#define TUNER_SONY_BTF_PK467Z 201 +#define TUNER_SONY_BTF_PB463Z 202 diff --git a/drivers/staging/media/go7007/wis-ov7640.c b/drivers/staging/media/go7007/wis-ov7640.c new file mode 100644 index 0000000..6bc9470 --- /dev/null +++ b/drivers/staging/media/go7007/wis-ov7640.c @@ -0,0 +1,108 @@ +/* + * 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/init.h> +#include <linux/i2c.h> +#include <linux/videodev2.h> + +#include "wis-i2c.h" + +struct wis_ov7640 { + int brightness; + int contrast; + int saturation; + int hue; +}; + +static u8 initial_registers[] = +{ + 0x12, 0x80, + 0x12, 0x54, + 0x14, 0x24, + 0x15, 0x01, + 0x28, 0x20, + 0x75, 0x82, + 0xFF, 0xFF, /* Terminator (reg 0xFF is unused) */ +}; + +static int write_regs(struct i2c_client *client, u8 *regs) +{ + int i; + + for (i = 0; regs[i] != 0xFF; i += 2) + if (i2c_smbus_write_byte_data(client, regs[i], regs[i + 1]) < 0) + return -1; + return 0; +} + +static int wis_ov7640_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct i2c_adapter *adapter = client->adapter; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + client->flags = I2C_CLIENT_SCCB; + + printk(KERN_DEBUG + "wis-ov7640: initializing OV7640 at address %d on %s\n", + client->addr, adapter->name); + + if (write_regs(client, initial_registers) < 0) { + printk(KERN_ERR "wis-ov7640: error initializing OV7640\n"); + return -ENODEV; + } + + return 0; +} + +static int wis_ov7640_remove(struct i2c_client *client) +{ + return 0; +} + +static const struct i2c_device_id wis_ov7640_id[] = { + { "wis_ov7640", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wis_ov7640_id); + +static struct i2c_driver wis_ov7640_driver = { + .driver = { + .name = "WIS OV7640 I2C driver", + }, + .probe = wis_ov7640_probe, + .remove = wis_ov7640_remove, + .id_table = wis_ov7640_id, +}; + +static int __init wis_ov7640_init(void) +{ + return i2c_add_driver(&wis_ov7640_driver); +} + +static void __exit wis_ov7640_cleanup(void) +{ + i2c_del_driver(&wis_ov7640_driver); +} + +module_init(wis_ov7640_init); +module_exit(wis_ov7640_cleanup); + +MODULE_LICENSE("GPL v2"); diff --git a/drivers/staging/media/go7007/wis-saa7113.c b/drivers/staging/media/go7007/wis-saa7113.c new file mode 100644 index 0000000..05e0e10 --- /dev/null +++ b/drivers/staging/media/go7007/wis-saa7113.c @@ -0,0 +1,336 @@ +/* + * 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/init.h> +#include <linux/i2c.h> +#include <linux/videodev2.h> +#include <linux/ioctl.h> +#include <linux/slab.h> + +#include "wis-i2c.h" + +struct wis_saa7113 { + int norm; + int brightness; + int contrast; + int saturation; + int hue; +}; + +static u8 initial_registers[] = +{ + 0x01, 0x08, + 0x02, 0xc0, + 0x03, 0x33, + 0x04, 0x00, + 0x05, 0x00, + 0x06, 0xe9, + 0x07, 0x0d, + 0x08, 0xd8, + 0x09, 0x40, + 0x0a, 0x80, + 0x0b, 0x47, + 0x0c, 0x40, + 0x0d, 0x00, + 0x0e, 0x01, + 0x0f, 0x2a, + 0x10, 0x40, + 0x11, 0x0c, + 0x12, 0xfe, + 0x13, 0x00, + 0x14, 0x00, + 0x15, 0x04, + 0x16, 0x00, + 0x17, 0x00, + 0x18, 0x00, + 0x19, 0x00, + 0x1a, 0x00, + 0x1b, 0x00, + 0x1c, 0x00, + 0x1d, 0x00, + 0x1e, 0x00, + 0x1f, 0xc8, + 0x40, 0x00, + 0x41, 0xff, + 0x42, 0xff, + 0x43, 0xff, + 0x44, 0xff, + 0x45, 0xff, + 0x46, 0xff, + 0x47, 0xff, + 0x48, 0xff, + 0x49, 0xff, + 0x4a, 0xff, + 0x4b, 0xff, + 0x4c, 0xff, + 0x4d, 0xff, + 0x4e, 0xff, + 0x4f, 0xff, + 0x50, 0xff, + 0x51, 0xff, + 0x52, 0xff, + 0x53, 0xff, + 0x54, 0xff, + 0x55, 0xff, + 0x56, 0xff, + 0x57, 0xff, + 0x58, 0x00, + 0x59, 0x54, + 0x5a, 0x07, + 0x5b, 0x83, + 0x5c, 0x00, + 0x5d, 0x00, + 0x5e, 0x00, + 0x5f, 0x00, + 0x60, 0x00, + 0x61, 0x00, + 0x00, 0x00, /* Terminator (reg 0x00 is read-only) */ +}; + +static int write_reg(struct i2c_client *client, u8 reg, u8 value) +{ + return i2c_smbus_write_byte_data(client, reg, value); +} + +static int write_regs(struct i2c_client *client, u8 *regs) +{ + int i; + + for (i = 0; regs[i] != 0x00; i += 2) + if (i2c_smbus_write_byte_data(client, regs[i], regs[i + 1]) < 0) + return -1; + return 0; +} + +static int wis_saa7113_command(struct i2c_client *client, + unsigned int cmd, void *arg) +{ + struct wis_saa7113 *dec = i2c_get_clientdata(client); + + switch (cmd) { + case VIDIOC_S_INPUT: + { + int *input = arg; + + i2c_smbus_write_byte_data(client, 0x02, 0xC0 | *input); + i2c_smbus_write_byte_data(client, 0x09, + *input < 6 ? 0x40 : 0x80); + break; + } + case VIDIOC_S_STD: + { + v4l2_std_id *input = arg; + dec->norm = *input; + if (dec->norm & V4L2_STD_NTSC) { + write_reg(client, 0x0e, 0x01); + write_reg(client, 0x10, 0x40); + } else if (dec->norm & V4L2_STD_PAL) { + write_reg(client, 0x0e, 0x01); + write_reg(client, 0x10, 0x48); + } else if (dec->norm * V4L2_STD_SECAM) { + write_reg(client, 0x0e, 0x50); + write_reg(client, 0x10, 0x48); + } + break; + } + case VIDIOC_QUERYCTRL: + { + struct v4l2_queryctrl *ctrl = arg; + + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + ctrl->type = V4L2_CTRL_TYPE_INTEGER; + strncpy(ctrl->name, "Brightness", sizeof(ctrl->name)); + ctrl->minimum = 0; + ctrl->maximum = 255; + ctrl->step = 1; + ctrl->default_value = 128; + ctrl->flags = 0; + break; + case V4L2_CID_CONTRAST: + ctrl->type = V4L2_CTRL_TYPE_INTEGER; + strncpy(ctrl->name, "Contrast", sizeof(ctrl->name)); + ctrl->minimum = 0; + ctrl->maximum = 127; + ctrl->step = 1; + ctrl->default_value = 71; + ctrl->flags = 0; + break; + case V4L2_CID_SATURATION: + ctrl->type = V4L2_CTRL_TYPE_INTEGER; + strncpy(ctrl->name, "Saturation", sizeof(ctrl->name)); + ctrl->minimum = 0; + ctrl->maximum = 127; + ctrl->step = 1; + ctrl->default_value = 64; + ctrl->flags = 0; + break; + case V4L2_CID_HUE: + ctrl->type = V4L2_CTRL_TYPE_INTEGER; + strncpy(ctrl->name, "Hue", sizeof(ctrl->name)); + ctrl->minimum = -128; + ctrl->maximum = 127; + ctrl->step = 1; + ctrl->default_value = 0; + ctrl->flags = 0; + break; + } + break; + } + case VIDIOC_S_CTRL: + { + struct v4l2_control *ctrl = arg; + + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + if (ctrl->value > 255) + dec->brightness = 255; + else if (ctrl->value < 0) + dec->brightness = 0; + else + dec->brightness = ctrl->value; + write_reg(client, 0x0a, dec->brightness); + break; + case V4L2_CID_CONTRAST: + if (ctrl->value > 127) + dec->contrast = 127; + else if (ctrl->value < 0) + dec->contrast = 0; + else + dec->contrast = ctrl->value; + write_reg(client, 0x0b, dec->contrast); + break; + case V4L2_CID_SATURATION: + if (ctrl->value > 127) + dec->saturation = 127; + else if (ctrl->value < 0) + dec->saturation = 0; + else + dec->saturation = ctrl->value; + write_reg(client, 0x0c, dec->saturation); + break; + case V4L2_CID_HUE: + if (ctrl->value > 127) + dec->hue = 127; + else if (ctrl->value < -128) + dec->hue = -128; + else + dec->hue = ctrl->value; + write_reg(client, 0x0d, dec->hue); + break; + } + break; + } + case VIDIOC_G_CTRL: + { + struct v4l2_control *ctrl = arg; + + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + ctrl->value = dec->brightness; + break; + case V4L2_CID_CONTRAST: + ctrl->value = dec->contrast; + break; + case V4L2_CID_SATURATION: + ctrl->value = dec->saturation; + break; + case V4L2_CID_HUE: + ctrl->value = dec->hue; + break; + } + break; + } + default: + break; + } + return 0; +} + +static int wis_saa7113_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct i2c_adapter *adapter = client->adapter; + struct wis_saa7113 *dec; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + dec = kmalloc(sizeof(struct wis_saa7113), GFP_KERNEL); + if (dec == NULL) + return -ENOMEM; + + dec->norm = V4L2_STD_NTSC; + dec->brightness = 128; + dec->contrast = 71; + dec->saturation = 64; + dec->hue = 0; + i2c_set_clientdata(client, dec); + + printk(KERN_DEBUG + "wis-saa7113: initializing SAA7113 at address %d on %s\n", + client->addr, adapter->name); + + if (write_regs(client, initial_registers) < 0) { + printk(KERN_ERR + "wis-saa7113: error initializing SAA7113\n"); + kfree(dec); + return -ENODEV; + } + + return 0; +} + +static int wis_saa7113_remove(struct i2c_client *client) +{ + struct wis_saa7113 *dec = i2c_get_clientdata(client); + + kfree(dec); + return 0; +} + +static const struct i2c_device_id wis_saa7113_id[] = { + { "wis_saa7113", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wis_saa7113_id); + +static struct i2c_driver wis_saa7113_driver = { + .driver = { + .name = "WIS SAA7113 I2C driver", + }, + .probe = wis_saa7113_probe, + .remove = wis_saa7113_remove, + .command = wis_saa7113_command, + .id_table = wis_saa7113_id, +}; + +static int __init wis_saa7113_init(void) +{ + return i2c_add_driver(&wis_saa7113_driver); +} + +static void __exit wis_saa7113_cleanup(void) +{ + i2c_del_driver(&wis_saa7113_driver); +} + +module_init(wis_saa7113_init); +module_exit(wis_saa7113_cleanup); + +MODULE_LICENSE("GPL v2"); diff --git a/drivers/staging/media/go7007/wis-saa7115.c b/drivers/staging/media/go7007/wis-saa7115.c new file mode 100644 index 0000000..46cff59 --- /dev/null +++ b/drivers/staging/media/go7007/wis-saa7115.c @@ -0,0 +1,469 @@ +/* + * 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/init.h> +#include <linux/i2c.h> +#include <linux/videodev2.h> +#include <linux/ioctl.h> +#include <linux/slab.h> + +#include "wis-i2c.h" + +struct wis_saa7115 { + int norm; + int brightness; + int contrast; + int saturation; + int hue; +}; + +static u8 initial_registers[] = +{ + 0x01, 0x08, + 0x02, 0xc0, + 0x03, 0x20, + 0x04, 0x80, + 0x05, 0x80, + 0x06, 0xeb, + 0x07, 0xe0, + 0x08, 0xf0, /* always toggle FID */ + 0x09, 0x40, + 0x0a, 0x80, + 0x0b, 0x40, + 0x0c, 0x40, + 0x0d, 0x00, + 0x0e, 0x03, + 0x0f, 0x2a, + 0x10, 0x0e, + 0x11, 0x00, + 0x12, 0x8d, + 0x13, 0x00, + 0x14, 0x00, + 0x15, 0x11, + 0x16, 0x01, + 0x17, 0xda, + 0x18, 0x40, + 0x19, 0x80, + 0x1a, 0x00, + 0x1b, 0x42, + 0x1c, 0xa9, + 0x30, 0x66, + 0x31, 0x90, + 0x32, 0x01, + 0x34, 0x00, + 0x35, 0x00, + 0x36, 0x20, + 0x38, 0x03, + 0x39, 0x20, + 0x3a, 0x88, + 0x40, 0x00, + 0x41, 0xff, + 0x42, 0xff, + 0x43, 0xff, + 0x44, 0xff, + 0x45, 0xff, + 0x46, 0xff, + 0x47, 0xff, + 0x48, 0xff, + 0x49, 0xff, + 0x4a, 0xff, + 0x4b, 0xff, + 0x4c, 0xff, + 0x4d, 0xff, + 0x4e, 0xff, + 0x4f, 0xff, + 0x50, 0xff, + 0x51, 0xff, + 0x52, 0xff, + 0x53, 0xff, + 0x54, 0xf4 /*0xff*/, + 0x55, 0xff, + 0x56, 0xff, + 0x57, 0xff, + 0x58, 0x40, + 0x59, 0x47, + 0x5a, 0x06 /*0x03*/, + 0x5b, 0x83, + 0x5d, 0x06, + 0x5e, 0x00, + 0x80, 0x30, /* window defined scaler operation, task A and B enabled */ + 0x81, 0x03, /* use scaler datapath generated V */ + 0x83, 0x00, + 0x84, 0x00, + 0x85, 0x00, + 0x86, 0x45, + 0x87, 0x31, + 0x88, 0xc0, + 0x90, 0x02, /* task A process top field */ + 0x91, 0x08, + 0x92, 0x09, + 0x93, 0x80, + 0x94, 0x06, + 0x95, 0x00, + 0x96, 0xc0, + 0x97, 0x02, + 0x98, 0x12, + 0x99, 0x00, + 0x9a, 0xf2, + 0x9b, 0x00, + 0x9c, 0xd0, + 0x9d, 0x02, + 0x9e, 0xf2, + 0x9f, 0x00, + 0xa0, 0x01, + 0xa1, 0x01, + 0xa2, 0x01, + 0xa4, 0x80, + 0xa5, 0x40, + 0xa6, 0x40, + 0xa8, 0x00, + 0xa9, 0x04, + 0xaa, 0x00, + 0xac, 0x00, + 0xad, 0x02, + 0xae, 0x00, + 0xb0, 0x00, + 0xb1, 0x04, + 0xb2, 0x00, + 0xb3, 0x04, + 0xb4, 0x00, + 0xb8, 0x00, + 0xbc, 0x00, + 0xc0, 0x03, /* task B process bottom field */ + 0xc1, 0x08, + 0xc2, 0x09, + 0xc3, 0x80, + 0xc4, 0x06, + 0xc5, 0x00, + 0xc6, 0xc0, + 0xc7, 0x02, + 0xc8, 0x12, + 0xc9, 0x00, + 0xca, 0xf2, + 0xcb, 0x00, + 0xcc, 0xd0, + 0xcd, 0x02, + 0xce, 0xf2, + 0xcf, 0x00, + 0xd0, 0x01, + 0xd1, 0x01, + 0xd2, 0x01, + 0xd4, 0x80, + 0xd5, 0x40, + 0xd6, 0x40, + 0xd8, 0x00, + 0xd9, 0x04, + 0xda, 0x00, + 0xdc, 0x00, + 0xdd, 0x02, + 0xde, 0x00, + 0xe0, 0x00, + 0xe1, 0x04, + 0xe2, 0x00, + 0xe3, 0x04, + 0xe4, 0x00, + 0xe8, 0x00, + 0x88, 0xf0, /* End of original static list */ + 0x00, 0x00, /* Terminator (reg 0x00 is read-only) */ +}; + +static int write_reg(struct i2c_client *client, u8 reg, u8 value) +{ + return i2c_smbus_write_byte_data(client, reg, value); +} + +static int write_regs(struct i2c_client *client, u8 *regs) +{ + int i; + + for (i = 0; regs[i] != 0x00; i += 2) + if (i2c_smbus_write_byte_data(client, regs[i], regs[i + 1]) < 0) + return -1; + return 0; +} + +static int wis_saa7115_command(struct i2c_client *client, + unsigned int cmd, void *arg) +{ + struct wis_saa7115 *dec = i2c_get_clientdata(client); + + switch (cmd) { + case VIDIOC_S_INPUT: + { + int *input = arg; + + i2c_smbus_write_byte_data(client, 0x02, 0xC0 | *input); + i2c_smbus_write_byte_data(client, 0x09, + *input < 6 ? 0x40 : 0xC0); + break; + } + case DECODER_SET_RESOLUTION: + { + struct video_decoder_resolution *res = arg; + /* Course-grained scaler */ + int h_integer_scaler = res->width < 704 ? 704 / res->width : 1; + /* Fine-grained scaler to take care of remainder */ + int h_scaling_increment = (704 / h_integer_scaler) * + 1024 / res->width; + /* Fine-grained scaler only */ + int v_scaling_increment = (dec->norm & V4L2_STD_NTSC ? + 240 : 288) * 1024 / res->height; + u8 regs[] = { + 0x88, 0xc0, + 0x9c, res->width & 0xff, + 0x9d, res->width >> 8, + 0x9e, res->height & 0xff, + 0x9f, res->height >> 8, + 0xa0, h_integer_scaler, + 0xa1, 1, + 0xa2, 1, + 0xa8, h_scaling_increment & 0xff, + 0xa9, h_scaling_increment >> 8, + 0xac, (h_scaling_increment / 2) & 0xff, + 0xad, (h_scaling_increment / 2) >> 8, + 0xb0, v_scaling_increment & 0xff, + 0xb1, v_scaling_increment >> 8, + 0xb2, v_scaling_increment & 0xff, + 0xb3, v_scaling_increment >> 8, + 0xcc, res->width & 0xff, + 0xcd, res->width >> 8, + 0xce, res->height & 0xff, + 0xcf, res->height >> 8, + 0xd0, h_integer_scaler, + 0xd1, 1, + 0xd2, 1, + 0xd8, h_scaling_increment & 0xff, + 0xd9, h_scaling_increment >> 8, + 0xdc, (h_scaling_increment / 2) & 0xff, + 0xdd, (h_scaling_increment / 2) >> 8, + 0xe0, v_scaling_increment & 0xff, + 0xe1, v_scaling_increment >> 8, + 0xe2, v_scaling_increment & 0xff, + 0xe3, v_scaling_increment >> 8, + 0x88, 0xf0, + 0, 0, + }; + write_regs(client, regs); + break; + } + case VIDIOC_S_STD: + { + v4l2_std_id *input = arg; + u8 regs[] = { + 0x88, 0xc0, + 0x98, *input & V4L2_STD_NTSC ? 0x12 : 0x16, + 0x9a, *input & V4L2_STD_NTSC ? 0xf2 : 0x20, + 0x9b, *input & V4L2_STD_NTSC ? 0x00 : 0x01, + 0xc8, *input & V4L2_STD_NTSC ? 0x12 : 0x16, + 0xca, *input & V4L2_STD_NTSC ? 0xf2 : 0x20, + 0xcb, *input & V4L2_STD_NTSC ? 0x00 : 0x01, + 0x88, 0xf0, + 0x30, *input & V4L2_STD_NTSC ? 0x66 : 0x00, + 0x31, *input & V4L2_STD_NTSC ? 0x90 : 0xe0, + 0, 0, + }; + write_regs(client, regs); + dec->norm = *input; + break; + } + case VIDIOC_QUERYCTRL: + { + struct v4l2_queryctrl *ctrl = arg; + + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + ctrl->type = V4L2_CTRL_TYPE_INTEGER; + strncpy(ctrl->name, "Brightness", sizeof(ctrl->name)); + ctrl->minimum = 0; + ctrl->maximum = 255; + ctrl->step = 1; + ctrl->default_value = 128; + ctrl->flags = 0; + break; + case V4L2_CID_CONTRAST: + ctrl->type = V4L2_CTRL_TYPE_INTEGER; + strncpy(ctrl->name, "Contrast", sizeof(ctrl->name)); + ctrl->minimum = 0; + ctrl->maximum = 127; + ctrl->step = 1; + ctrl->default_value = 64; + ctrl->flags = 0; + break; + case V4L2_CID_SATURATION: + ctrl->type = V4L2_CTRL_TYPE_INTEGER; + strncpy(ctrl->name, "Saturation", sizeof(ctrl->name)); + ctrl->minimum = 0; + ctrl->maximum = 127; + ctrl->step = 1; + ctrl->default_value = 64; + ctrl->flags = 0; + break; + case V4L2_CID_HUE: + ctrl->type = V4L2_CTRL_TYPE_INTEGER; + strncpy(ctrl->name, "Hue", sizeof(ctrl->name)); + ctrl->minimum = -128; + ctrl->maximum = 127; + ctrl->step = 1; + ctrl->default_value = 0; + ctrl->flags = 0; + break; + } + break; + } + case VIDIOC_S_CTRL: + { + struct v4l2_control *ctrl = arg; + + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + if (ctrl->value > 255) + dec->brightness = 255; + else if (ctrl->value < 0) + dec->brightness = 0; + else + dec->brightness = ctrl->value; + write_reg(client, 0x0a, dec->brightness); + break; + case V4L2_CID_CONTRAST: + if (ctrl->value > 127) + dec->contrast = 127; + else if (ctrl->value < 0) + dec->contrast = 0; + else + dec->contrast = ctrl->value; + write_reg(client, 0x0b, dec->contrast); + break; + case V4L2_CID_SATURATION: + if (ctrl->value > 127) + dec->saturation = 127; + else if (ctrl->value < 0) + dec->saturation = 0; + else + dec->saturation = ctrl->value; + write_reg(client, 0x0c, dec->saturation); + break; + case V4L2_CID_HUE: + if (ctrl->value > 127) + dec->hue = 127; + else if (ctrl->value < -128) + dec->hue = -128; + else + dec->hue = ctrl->value; + write_reg(client, 0x0d, dec->hue); + break; + } + break; + } + case VIDIOC_G_CTRL: + { + struct v4l2_control *ctrl = arg; + + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + ctrl->value = dec->brightness; + break; + case V4L2_CID_CONTRAST: + ctrl->value = dec->contrast; + break; + case V4L2_CID_SATURATION: + ctrl->value = dec->saturation; + break; + case V4L2_CID_HUE: + ctrl->value = dec->hue; + break; + } + break; + } + default: + break; + } + return 0; +} + +static int wis_saa7115_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct i2c_adapter *adapter = client->adapter; + struct wis_saa7115 *dec; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + dec = kmalloc(sizeof(struct wis_saa7115), GFP_KERNEL); + if (dec == NULL) + return -ENOMEM; + + dec->norm = V4L2_STD_NTSC; + dec->brightness = 128; + dec->contrast = 64; + dec->saturation = 64; + dec->hue = 0; + i2c_set_clientdata(client, dec); + + printk(KERN_DEBUG + "wis-saa7115: initializing SAA7115 at address %d on %s\n", + client->addr, adapter->name); + + if (write_regs(client, initial_registers) < 0) { + printk(KERN_ERR + "wis-saa7115: error initializing SAA7115\n"); + kfree(dec); + return -ENODEV; + } + + return 0; +} + +static int wis_saa7115_remove(struct i2c_client *client) +{ + struct wis_saa7115 *dec = i2c_get_clientdata(client); + + kfree(dec); + return 0; +} + +static const struct i2c_device_id wis_saa7115_id[] = { + { "wis_saa7115", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wis_saa7115_id); + +static struct i2c_driver wis_saa7115_driver = { + .driver = { + .name = "WIS SAA7115 I2C driver", + }, + .probe = wis_saa7115_probe, + .remove = wis_saa7115_remove, + .command = wis_saa7115_command, + .id_table = wis_saa7115_id, +}; + +static int __init wis_saa7115_init(void) +{ + return i2c_add_driver(&wis_saa7115_driver); +} + +static void __exit wis_saa7115_cleanup(void) +{ + i2c_del_driver(&wis_saa7115_driver); +} + +module_init(wis_saa7115_init); +module_exit(wis_saa7115_cleanup); + +MODULE_LICENSE("GPL v2"); diff --git a/drivers/staging/media/go7007/wis-sony-tuner.c b/drivers/staging/media/go7007/wis-sony-tuner.c new file mode 100644 index 0000000..8f1b7d4 --- /dev/null +++ b/drivers/staging/media/go7007/wis-sony-tuner.c @@ -0,0 +1,720 @@ +/* + * 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/init.h> +#include <linux/i2c.h> +#include <linux/videodev2.h> +#include <linux/slab.h> +#include <media/tuner.h> +#include <media/v4l2-common.h> +#include <media/v4l2-ioctl.h> + +#include "wis-i2c.h" + +/* #define MPX_DEBUG */ + +/* AS(IF/MPX) pin: LOW HIGH/OPEN + * IF/MPX address: 0x42/0x40 0x43/0x44 + */ +#define IF_I2C_ADDR 0x43 +#define MPX_I2C_ADDR 0x44 + +static v4l2_std_id force_band; +static char force_band_str[] = "-"; +module_param_string(force_band, force_band_str, sizeof(force_band_str), 0644); +static int force_mpx_mode = -1; +module_param(force_mpx_mode, int, 0644); + +/* Store tuner info in the same format as tuner.c, so maybe we can put the + * Sony tuner support in there. */ +struct sony_tunertype { + char *name; + unsigned char Vendor; /* unused here */ + unsigned char Type; /* unused here */ + + unsigned short thresh1; /* band switch VHF_LO <=> VHF_HI */ + unsigned short thresh2; /* band switch VHF_HI <=> UHF */ + unsigned char VHF_L; + unsigned char VHF_H; + unsigned char UHF; + unsigned char config; + unsigned short IFPCoff; +}; + +/* This array is indexed by (tuner_type - 200) */ +static struct sony_tunertype sony_tuners[] = { + { "Sony PAL+SECAM (BTF-PG472Z)", 0, 0, + 16*144.25, 16*427.25, 0x01, 0x02, 0x04, 0xc6, 623}, + { "Sony NTSC_JP (BTF-PK467Z)", 0, 0, + 16*220.25, 16*467.25, 0x01, 0x02, 0x04, 0xc6, 940}, + { "Sony NTSC (BTF-PB463Z)", 0, 0, + 16*130.25, 16*364.25, 0x01, 0x02, 0x04, 0xc6, 732}, +}; + +struct wis_sony_tuner { + int type; + v4l2_std_id std; + unsigned int freq; + int mpxmode; + u32 audmode; +}; + +/* Basically the same as default_set_tv_freq() in tuner.c */ +static int set_freq(struct i2c_client *client, int freq) +{ + struct wis_sony_tuner *t = i2c_get_clientdata(client); + char *band_name; + int n; + int band_select; + struct sony_tunertype *tun; + u8 buffer[4]; + + tun = &sony_tuners[t->type - 200]; + if (freq < tun->thresh1) { + band_name = "VHF_L"; + band_select = tun->VHF_L; + } else if (freq < tun->thresh2) { + band_name = "VHF_H"; + band_select = tun->VHF_H; + } else { + band_name = "UHF"; + band_select = tun->UHF; + } + printk(KERN_DEBUG "wis-sony-tuner: tuning to frequency %d.%04d (%s)\n", + freq / 16, (freq % 16) * 625, band_name); + n = freq + tun->IFPCoff; + + buffer[0] = n >> 8; + buffer[1] = n & 0xff; + buffer[2] = tun->config; + buffer[3] = band_select; + i2c_master_send(client, buffer, 4); + + return 0; +} + +static int mpx_write(struct i2c_client *client, int dev, int addr, int val) +{ + u8 buffer[5]; + struct i2c_msg msg; + + buffer[0] = dev; + buffer[1] = addr >> 8; + buffer[2] = addr & 0xff; + buffer[3] = val >> 8; + buffer[4] = val & 0xff; + msg.addr = MPX_I2C_ADDR; + msg.flags = 0; + msg.len = 5; + msg.buf = buffer; + i2c_transfer(client->adapter, &msg, 1); + return 0; +} + +/* + * MPX register values for the BTF-PG472Z: + * + * FM_ NICAM_ SCART_ + * MODUS SOURCE ACB PRESCAL PRESCAL PRESCAL SYSTEM VOLUME + * 10/0030 12/0008 12/0013 12/000E 12/0010 12/0000 10/0020 12/0000 + * --------------------------------------------------------------- + * Auto 1003 0020 0100 2603 5000 XXXX 0001 7500 + * + * B/G + * Mono 1003 0020 0100 2603 5000 XXXX 0003 7500 + * A2 1003 0020 0100 2601 5000 XXXX 0003 7500 + * NICAM 1003 0120 0100 2603 5000 XXXX 0008 7500 + * + * I + * Mono 1003 0020 0100 2603 7900 XXXX 000A 7500 + * NICAM 1003 0120 0100 2603 7900 XXXX 000A 7500 + * + * D/K + * Mono 1003 0020 0100 2603 5000 XXXX 0004 7500 + * A2-1 1003 0020 0100 2601 5000 XXXX 0004 7500 + * A2-2 1003 0020 0100 2601 5000 XXXX 0005 7500 + * A2-3 1003 0020 0100 2601 5000 XXXX 0007 7500 + * NICAM 1003 0120 0100 2603 5000 XXXX 000B 7500 + * + * L/L' + * Mono 0003 0200 0100 7C03 5000 2200 0009 7500 + * NICAM 0003 0120 0100 7C03 5000 XXXX 0009 7500 + * + * M + * Mono 1003 0200 0100 2B03 5000 2B00 0002 7500 + * + * For Asia, replace the 0x26XX in FM_PRESCALE with 0x14XX. + * + * Bilingual selection in A2/NICAM: + * + * High byte of SOURCE Left chan Right chan + * 0x01 MAIN SUB + * 0x03 MAIN MAIN + * 0x04 SUB SUB + * + * Force mono in NICAM by setting the high byte of SOURCE to 0x02 (L/L') or + * 0x00 (all other bands). Force mono in A2 with FMONO_A2: + * + * FMONO_A2 + * 10/0022 + * -------- + * Forced mono ON 07F0 + * Forced mono OFF 0190 + */ + +static struct { + enum { AUD_MONO, AUD_A2, AUD_NICAM, AUD_NICAM_L } audio_mode; + u16 modus; + u16 source; + u16 acb; + u16 fm_prescale; + u16 nicam_prescale; + u16 scart_prescale; + u16 system; + u16 volume; +} mpx_audio_modes[] = { + /* Auto */ { AUD_MONO, 0x1003, 0x0020, 0x0100, 0x2603, + 0x5000, 0x0000, 0x0001, 0x7500 }, + /* B/G Mono */ { AUD_MONO, 0x1003, 0x0020, 0x0100, 0x2603, + 0x5000, 0x0000, 0x0003, 0x7500 }, + /* B/G A2 */ { AUD_A2, 0x1003, 0x0020, 0x0100, 0x2601, + 0x5000, 0x0000, 0x0003, 0x7500 }, + /* B/G NICAM */ { AUD_NICAM, 0x1003, 0x0120, 0x0100, 0x2603, + 0x5000, 0x0000, 0x0008, 0x7500 }, + /* I Mono */ { AUD_MONO, 0x1003, 0x0020, 0x0100, 0x2603, + 0x7900, 0x0000, 0x000A, 0x7500 }, + /* I NICAM */ { AUD_NICAM, 0x1003, 0x0120, 0x0100, 0x2603, + 0x7900, 0x0000, 0x000A, 0x7500 }, + /* D/K Mono */ { AUD_MONO, 0x1003, 0x0020, 0x0100, 0x2603, + 0x5000, 0x0000, 0x0004, 0x7500 }, + /* D/K A2-1 */ { AUD_A2, 0x1003, 0x0020, 0x0100, 0x2601, + 0x5000, 0x0000, 0x0004, 0x7500 }, + /* D/K A2-2 */ { AUD_A2, 0x1003, 0x0020, 0x0100, 0x2601, + 0x5000, 0x0000, 0x0005, 0x7500 }, + /* D/K A2-3 */ { AUD_A2, 0x1003, 0x0020, 0x0100, 0x2601, + 0x5000, 0x0000, 0x0007, 0x7500 }, + /* D/K NICAM */ { AUD_NICAM, 0x1003, 0x0120, 0x0100, 0x2603, + 0x5000, 0x0000, 0x000B, 0x7500 }, + /* L/L' Mono */ { AUD_MONO, 0x0003, 0x0200, 0x0100, 0x7C03, + 0x5000, 0x2200, 0x0009, 0x7500 }, + /* L/L' NICAM */{ AUD_NICAM_L, 0x0003, 0x0120, 0x0100, 0x7C03, + 0x5000, 0x0000, 0x0009, 0x7500 }, +}; + +#define MPX_NUM_MODES ARRAY_SIZE(mpx_audio_modes) + +static int mpx_setup(struct i2c_client *client) +{ + struct wis_sony_tuner *t = i2c_get_clientdata(client); + u16 source = 0; + u8 buffer[3]; + struct i2c_msg msg; + + /* reset MPX */ + buffer[0] = 0x00; + buffer[1] = 0x80; + buffer[2] = 0x00; + msg.addr = MPX_I2C_ADDR; + msg.flags = 0; + msg.len = 3; + msg.buf = buffer; + i2c_transfer(client->adapter, &msg, 1); + buffer[1] = 0x00; + i2c_transfer(client->adapter, &msg, 1); + + if (mpx_audio_modes[t->mpxmode].audio_mode != AUD_MONO) { + switch (t->audmode) { + case V4L2_TUNER_MODE_MONO: + switch (mpx_audio_modes[t->mpxmode].audio_mode) { + case AUD_A2: + source = mpx_audio_modes[t->mpxmode].source; + break; + case AUD_NICAM: + source = 0x0000; + break; + case AUD_NICAM_L: + source = 0x0200; + break; + default: + break; + } + break; + case V4L2_TUNER_MODE_STEREO: + source = mpx_audio_modes[t->mpxmode].source; + break; + case V4L2_TUNER_MODE_LANG1: + source = 0x0300; + break; + case V4L2_TUNER_MODE_LANG2: + source = 0x0400; + break; + } + source |= mpx_audio_modes[t->mpxmode].source & 0x00ff; + } else + source = mpx_audio_modes[t->mpxmode].source; + + mpx_write(client, 0x10, 0x0030, mpx_audio_modes[t->mpxmode].modus); + mpx_write(client, 0x12, 0x0008, source); + mpx_write(client, 0x12, 0x0013, mpx_audio_modes[t->mpxmode].acb); + mpx_write(client, 0x12, 0x000e, + mpx_audio_modes[t->mpxmode].fm_prescale); + mpx_write(client, 0x12, 0x0010, + mpx_audio_modes[t->mpxmode].nicam_prescale); + mpx_write(client, 0x12, 0x000d, + mpx_audio_modes[t->mpxmode].scart_prescale); + mpx_write(client, 0x10, 0x0020, mpx_audio_modes[t->mpxmode].system); + mpx_write(client, 0x12, 0x0000, mpx_audio_modes[t->mpxmode].volume); + if (mpx_audio_modes[t->mpxmode].audio_mode == AUD_A2) + mpx_write(client, 0x10, 0x0022, + t->audmode == V4L2_TUNER_MODE_MONO ? 0x07f0 : 0x0190); + +#ifdef MPX_DEBUG + { + u8 buf1[3], buf2[2]; + struct i2c_msg msgs[2]; + + printk(KERN_DEBUG "wis-sony-tuner: MPX registers: %04x %04x " + "%04x %04x %04x %04x %04x %04x\n", + mpx_audio_modes[t->mpxmode].modus, + source, + mpx_audio_modes[t->mpxmode].acb, + mpx_audio_modes[t->mpxmode].fm_prescale, + mpx_audio_modes[t->mpxmode].nicam_prescale, + mpx_audio_modes[t->mpxmode].scart_prescale, + mpx_audio_modes[t->mpxmode].system, + mpx_audio_modes[t->mpxmode].volume); + buf1[0] = 0x11; + buf1[1] = 0x00; + buf1[2] = 0x7e; + msgs[0].addr = MPX_I2C_ADDR; + msgs[0].flags = 0; + msgs[0].len = 3; + msgs[0].buf = buf1; + msgs[1].addr = MPX_I2C_ADDR; + msgs[1].flags = I2C_M_RD; + msgs[1].len = 2; + msgs[1].buf = buf2; + i2c_transfer(client->adapter, msgs, 2); + printk(KERN_DEBUG "wis-sony-tuner: MPX system: %02x%02x\n", + buf2[0], buf2[1]); + buf1[0] = 0x11; + buf1[1] = 0x02; + buf1[2] = 0x00; + i2c_transfer(client->adapter, msgs, 2); + printk(KERN_DEBUG "wis-sony-tuner: MPX status: %02x%02x\n", + buf2[0], buf2[1]); + } +#endif + return 0; +} + +/* + * IF configuration values for the BTF-PG472Z: + * + * B/G: 0x94 0x70 0x49 + * I: 0x14 0x70 0x4a + * D/K: 0x14 0x70 0x4b + * L: 0x04 0x70 0x4b + * L': 0x44 0x70 0x53 + * M: 0x50 0x30 0x4c + */ + +static int set_if(struct i2c_client *client) +{ + struct wis_sony_tuner *t = i2c_get_clientdata(client); + u8 buffer[4]; + struct i2c_msg msg; + int default_mpx_mode = 0; + + /* configure IF */ + buffer[0] = 0; + if (t->std & V4L2_STD_PAL_BG) { + buffer[1] = 0x94; + buffer[2] = 0x70; + buffer[3] = 0x49; + default_mpx_mode = 1; + } else if (t->std & V4L2_STD_PAL_I) { + buffer[1] = 0x14; + buffer[2] = 0x70; + buffer[3] = 0x4a; + default_mpx_mode = 4; + } else if (t->std & V4L2_STD_PAL_DK) { + buffer[1] = 0x14; + buffer[2] = 0x70; + buffer[3] = 0x4b; + default_mpx_mode = 6; + } else if (t->std & V4L2_STD_SECAM_L) { + buffer[1] = 0x04; + buffer[2] = 0x70; + buffer[3] = 0x4b; + default_mpx_mode = 11; + } + msg.addr = IF_I2C_ADDR; + msg.flags = 0; + msg.len = 4; + msg.buf = buffer; + i2c_transfer(client->adapter, &msg, 1); + + /* Select MPX mode if not forced by the user */ + if (force_mpx_mode >= 0 && force_mpx_mode < MPX_NUM_MODES) + t->mpxmode = force_mpx_mode; + else + t->mpxmode = default_mpx_mode; + printk(KERN_DEBUG "wis-sony-tuner: setting MPX to mode %d\n", + t->mpxmode); + mpx_setup(client); + + return 0; +} + +static int tuner_command(struct i2c_client *client, unsigned int cmd, void *arg) +{ + struct wis_sony_tuner *t = i2c_get_clientdata(client); + + switch (cmd) { +#if 0 +#ifdef TUNER_SET_TYPE_ADDR + case TUNER_SET_TYPE_ADDR: + { + struct tuner_setup *tun_setup = arg; + int *type = &tun_setup->type; +#else + case TUNER_SET_TYPE: + { + int *type = arg; +#endif + + if (t->type >= 0) { + if (t->type != *type) + printk(KERN_ERR "wis-sony-tuner: type already " + "set to %d, ignoring request for %d\n", + t->type, *type); + break; + } + t->type = *type; + switch (t->type) { + case TUNER_SONY_BTF_PG472Z: + switch (force_band_str[0]) { + case 'b': + case 'B': + case 'g': + case 'G': + printk(KERN_INFO "wis-sony-tuner: forcing " + "tuner to PAL-B/G bands\n"); + force_band = V4L2_STD_PAL_BG; + break; + case 'i': + case 'I': + printk(KERN_INFO "wis-sony-tuner: forcing " + "tuner to PAL-I band\n"); + force_band = V4L2_STD_PAL_I; + break; + case 'd': + case 'D': + case 'k': + case 'K': + printk(KERN_INFO "wis-sony-tuner: forcing " + "tuner to PAL-D/K bands\n"); + force_band = V4L2_STD_PAL_I; + break; + case 'l': + case 'L': + printk(KERN_INFO "wis-sony-tuner: forcing " + "tuner to SECAM-L band\n"); + force_band = V4L2_STD_SECAM_L; + break; + default: + force_band = 0; + break; + } + if (force_band) + t->std = force_band; + else + t->std = V4L2_STD_PAL_BG; + set_if(client); + break; + case TUNER_SONY_BTF_PK467Z: + t->std = V4L2_STD_NTSC_M_JP; + break; + case TUNER_SONY_BTF_PB463Z: + t->std = V4L2_STD_NTSC_M; + break; + default: + printk(KERN_ERR "wis-sony-tuner: tuner type %d is not " + "supported by this module\n", *type); + break; + } + if (type >= 0) + printk(KERN_INFO + "wis-sony-tuner: type set to %d (%s)\n", + t->type, sony_tuners[t->type - 200].name); + break; + } +#endif + case VIDIOC_G_FREQUENCY: + { + struct v4l2_frequency *f = arg; + + f->frequency = t->freq; + break; + } + case VIDIOC_S_FREQUENCY: + { + struct v4l2_frequency *f = arg; + + t->freq = f->frequency; + set_freq(client, t->freq); + break; + } + case VIDIOC_ENUMSTD: + { + struct v4l2_standard *std = arg; + + switch (t->type) { + case TUNER_SONY_BTF_PG472Z: + switch (std->index) { + case 0: + v4l2_video_std_construct(std, + V4L2_STD_PAL_BG, "PAL-B/G"); + break; + case 1: + v4l2_video_std_construct(std, + V4L2_STD_PAL_I, "PAL-I"); + break; + case 2: + v4l2_video_std_construct(std, + V4L2_STD_PAL_DK, "PAL-D/K"); + break; + case 3: + v4l2_video_std_construct(std, + V4L2_STD_SECAM_L, "SECAM-L"); + break; + default: + std->id = 0; /* hack to indicate EINVAL */ + break; + } + break; + case TUNER_SONY_BTF_PK467Z: + if (std->index != 0) { + std->id = 0; /* hack to indicate EINVAL */ + break; + } + v4l2_video_std_construct(std, + V4L2_STD_NTSC_M_JP, "NTSC-J"); + break; + case TUNER_SONY_BTF_PB463Z: + if (std->index != 0) { + std->id = 0; /* hack to indicate EINVAL */ + break; + } + v4l2_video_std_construct(std, V4L2_STD_NTSC_M, "NTSC"); + break; + } + break; + } + case VIDIOC_G_STD: + { + v4l2_std_id *std = arg; + + *std = t->std; + break; + } + case VIDIOC_S_STD: + { + v4l2_std_id *std = arg; + v4l2_std_id old = t->std; + + switch (t->type) { + case TUNER_SONY_BTF_PG472Z: + if (force_band && (*std & force_band) != *std && + *std != V4L2_STD_PAL && + *std != V4L2_STD_SECAM) { + printk(KERN_DEBUG "wis-sony-tuner: ignoring " + "requested TV standard in " + "favor of force_band value\n"); + t->std = force_band; + } else if (*std & V4L2_STD_PAL_BG) { /* default */ + t->std = V4L2_STD_PAL_BG; + } else if (*std & V4L2_STD_PAL_I) { + t->std = V4L2_STD_PAL_I; + } else if (*std & V4L2_STD_PAL_DK) { + t->std = V4L2_STD_PAL_DK; + } else if (*std & V4L2_STD_SECAM_L) { + t->std = V4L2_STD_SECAM_L; + } else { + printk(KERN_ERR "wis-sony-tuner: TV standard " + "not supported\n"); + *std = 0; /* hack to indicate EINVAL */ + break; + } + if (old != t->std) + set_if(client); + break; + case TUNER_SONY_BTF_PK467Z: + if (!(*std & V4L2_STD_NTSC_M_JP)) { + printk(KERN_ERR "wis-sony-tuner: TV standard " + "not supported\n"); + *std = 0; /* hack to indicate EINVAL */ + } + break; + case TUNER_SONY_BTF_PB463Z: + if (!(*std & V4L2_STD_NTSC_M)) { + printk(KERN_ERR "wis-sony-tuner: TV standard " + "not supported\n"); + *std = 0; /* hack to indicate EINVAL */ + } + break; + } + break; + } + case VIDIOC_QUERYSTD: + { + v4l2_std_id *std = arg; + + switch (t->type) { + case TUNER_SONY_BTF_PG472Z: + if (force_band) + *std = force_band; + else + *std = V4L2_STD_PAL_BG | V4L2_STD_PAL_I | + V4L2_STD_PAL_DK | V4L2_STD_SECAM_L; + break; + case TUNER_SONY_BTF_PK467Z: + *std = V4L2_STD_NTSC_M_JP; + break; + case TUNER_SONY_BTF_PB463Z: + *std = V4L2_STD_NTSC_M; + break; + } + break; + } + case VIDIOC_G_TUNER: + { + struct v4l2_tuner *tun = arg; + + memset(tun, 0, sizeof(*tun)); + strcpy(tun->name, "Television"); + tun->type = V4L2_TUNER_ANALOG_TV; + tun->rangelow = 0UL; /* does anything use these? */ + tun->rangehigh = 0xffffffffUL; + switch (t->type) { + case TUNER_SONY_BTF_PG472Z: + tun->capability = V4L2_TUNER_CAP_NORM | + V4L2_TUNER_CAP_STEREO | V4L2_TUNER_CAP_LANG1 | + V4L2_TUNER_CAP_LANG2; + tun->rxsubchans = V4L2_TUNER_SUB_MONO | + V4L2_TUNER_SUB_STEREO | V4L2_TUNER_SUB_LANG1 | + V4L2_TUNER_SUB_LANG2; + break; + case TUNER_SONY_BTF_PK467Z: + case TUNER_SONY_BTF_PB463Z: + tun->capability = V4L2_TUNER_CAP_STEREO; + tun->rxsubchans = V4L2_TUNER_SUB_MONO | + V4L2_TUNER_SUB_STEREO; + break; + } + tun->audmode = t->audmode; + return 0; + } + case VIDIOC_S_TUNER: + { + struct v4l2_tuner *tun = arg; + + switch (t->type) { + case TUNER_SONY_BTF_PG472Z: + if (tun->audmode != t->audmode) { + t->audmode = tun->audmode; + mpx_setup(client); + } + break; + case TUNER_SONY_BTF_PK467Z: + case TUNER_SONY_BTF_PB463Z: + break; + } + return 0; + } + default: + break; + } + return 0; +} + +static int wis_sony_tuner_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct i2c_adapter *adapter = client->adapter; + struct wis_sony_tuner *t; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_I2C_BLOCK)) + return -ENODEV; + + t = kmalloc(sizeof(struct wis_sony_tuner), GFP_KERNEL); + if (t == NULL) + return -ENOMEM; + + t->type = -1; + t->freq = 0; + t->mpxmode = 0; + t->audmode = V4L2_TUNER_MODE_STEREO; + i2c_set_clientdata(client, t); + + printk(KERN_DEBUG + "wis-sony-tuner: initializing tuner at address %d on %s\n", + client->addr, adapter->name); + + return 0; +} + +static int wis_sony_tuner_remove(struct i2c_client *client) +{ + struct wis_sony_tuner *t = i2c_get_clientdata(client); + + kfree(t); + return 0; +} + +static const struct i2c_device_id wis_sony_tuner_id[] = { + { "wis_sony_tuner", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wis_sony_tuner_id); + +static struct i2c_driver wis_sony_tuner_driver = { + .driver = { + .name = "WIS Sony TV Tuner I2C driver", + }, + .probe = wis_sony_tuner_probe, + .remove = wis_sony_tuner_remove, + .command = tuner_command, + .id_table = wis_sony_tuner_id, +}; + +static int __init wis_sony_tuner_init(void) +{ + return i2c_add_driver(&wis_sony_tuner_driver); +} + +static void __exit wis_sony_tuner_cleanup(void) +{ + i2c_del_driver(&wis_sony_tuner_driver); +} + +module_init(wis_sony_tuner_init); +module_exit(wis_sony_tuner_cleanup); + +MODULE_LICENSE("GPL v2"); diff --git a/drivers/staging/media/go7007/wis-tw2804.c b/drivers/staging/media/go7007/wis-tw2804.c new file mode 100644 index 0000000..9134f03 --- /dev/null +++ b/drivers/staging/media/go7007/wis-tw2804.c @@ -0,0 +1,357 @@ +/* + * 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/init.h> +#include <linux/i2c.h> +#include <linux/videodev2.h> +#include <linux/ioctl.h> +#include <linux/slab.h> + +#include "wis-i2c.h" + +struct wis_tw2804 { + int channel; + int norm; + int brightness; + int contrast; + int saturation; + int hue; +}; + +static u8 global_registers[] = { + 0x39, 0x00, + 0x3a, 0xff, + 0x3b, 0x84, + 0x3c, 0x80, + 0x3d, 0x80, + 0x3e, 0x82, + 0x3f, 0x82, + 0xff, 0xff, /* Terminator (reg 0xff does not exist) */ +}; + +static u8 channel_registers[] = { + 0x01, 0xc4, + 0x02, 0xa5, + 0x03, 0x20, + 0x04, 0xd0, + 0x05, 0x20, + 0x06, 0xd0, + 0x07, 0x88, + 0x08, 0x20, + 0x09, 0x07, + 0x0a, 0xf0, + 0x0b, 0x07, + 0x0c, 0xf0, + 0x0d, 0x40, + 0x0e, 0xd2, + 0x0f, 0x80, + 0x10, 0x80, + 0x11, 0x80, + 0x12, 0x80, + 0x13, 0x1f, + 0x14, 0x00, + 0x15, 0x00, + 0x16, 0x00, + 0x17, 0x00, + 0x18, 0xff, + 0x19, 0xff, + 0x1a, 0xff, + 0x1b, 0xff, + 0x1c, 0xff, + 0x1d, 0xff, + 0x1e, 0xff, + 0x1f, 0xff, + 0x20, 0x07, + 0x21, 0x07, + 0x22, 0x00, + 0x23, 0x91, + 0x24, 0x51, + 0x25, 0x03, + 0x26, 0x00, + 0x27, 0x00, + 0x28, 0x00, + 0x29, 0x00, + 0x2a, 0x00, + 0x2b, 0x00, + 0x2c, 0x00, + 0x2d, 0x00, + 0x2e, 0x00, + 0x2f, 0x00, + 0x30, 0x00, + 0x31, 0x00, + 0x32, 0x00, + 0x33, 0x00, + 0x34, 0x00, + 0x35, 0x00, + 0x36, 0x00, + 0x37, 0x00, + 0xff, 0xff, /* Terminator (reg 0xff does not exist) */ +}; + +static int write_reg(struct i2c_client *client, u8 reg, u8 value, int channel) +{ + return i2c_smbus_write_byte_data(client, reg | (channel << 6), value); +} + +static int write_regs(struct i2c_client *client, u8 *regs, int channel) +{ + int i; + + for (i = 0; regs[i] != 0xff; i += 2) + if (i2c_smbus_write_byte_data(client, + regs[i] | (channel << 6), regs[i + 1]) < 0) + return -1; + return 0; +} + +static int wis_tw2804_command(struct i2c_client *client, + unsigned int cmd, void *arg) +{ + struct wis_tw2804 *dec = i2c_get_clientdata(client); + + if (cmd == DECODER_SET_CHANNEL) { + int *input = arg; + + if (*input < 0 || *input > 3) { + printk(KERN_ERR "wis-tw2804: channel %d is not " + "between 0 and 3!\n", *input); + return 0; + } + dec->channel = *input; + printk(KERN_DEBUG "wis-tw2804: initializing TW2804 " + "channel %d\n", dec->channel); + if (dec->channel == 0 && + write_regs(client, global_registers, 0) < 0) { + printk(KERN_ERR "wis-tw2804: error initializing " + "TW2804 global registers\n"); + return 0; + } + if (write_regs(client, channel_registers, dec->channel) < 0) { + printk(KERN_ERR "wis-tw2804: error initializing " + "TW2804 channel %d\n", dec->channel); + return 0; + } + return 0; + } + + if (dec->channel < 0) { + printk(KERN_DEBUG "wis-tw2804: ignoring command %08x until " + "channel number is set\n", cmd); + return 0; + } + + switch (cmd) { + case VIDIOC_S_STD: + { + v4l2_std_id *input = arg; + u8 regs[] = { + 0x01, *input & V4L2_STD_NTSC ? 0xc4 : 0x84, + 0x09, *input & V4L2_STD_NTSC ? 0x07 : 0x04, + 0x0a, *input & V4L2_STD_NTSC ? 0xf0 : 0x20, + 0x0b, *input & V4L2_STD_NTSC ? 0x07 : 0x04, + 0x0c, *input & V4L2_STD_NTSC ? 0xf0 : 0x20, + 0x0d, *input & V4L2_STD_NTSC ? 0x40 : 0x4a, + 0x16, *input & V4L2_STD_NTSC ? 0x00 : 0x40, + 0x17, *input & V4L2_STD_NTSC ? 0x00 : 0x40, + 0x20, *input & V4L2_STD_NTSC ? 0x07 : 0x0f, + 0x21, *input & V4L2_STD_NTSC ? 0x07 : 0x0f, + 0xff, 0xff, + }; + write_regs(client, regs, dec->channel); + dec->norm = *input; + break; + } + case VIDIOC_QUERYCTRL: + { + struct v4l2_queryctrl *ctrl = arg; + + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + ctrl->type = V4L2_CTRL_TYPE_INTEGER; + strncpy(ctrl->name, "Brightness", sizeof(ctrl->name)); + ctrl->minimum = 0; + ctrl->maximum = 255; + ctrl->step = 1; + ctrl->default_value = 128; + ctrl->flags = 0; + break; + case V4L2_CID_CONTRAST: + ctrl->type = V4L2_CTRL_TYPE_INTEGER; + strncpy(ctrl->name, "Contrast", sizeof(ctrl->name)); + ctrl->minimum = 0; + ctrl->maximum = 255; + ctrl->step = 1; + ctrl->default_value = 128; + ctrl->flags = 0; + break; + case V4L2_CID_SATURATION: + ctrl->type = V4L2_CTRL_TYPE_INTEGER; + strncpy(ctrl->name, "Saturation", sizeof(ctrl->name)); + ctrl->minimum = 0; + ctrl->maximum = 255; + ctrl->step = 1; + ctrl->default_value = 128; + ctrl->flags = 0; + break; + case V4L2_CID_HUE: + ctrl->type = V4L2_CTRL_TYPE_INTEGER; + strncpy(ctrl->name, "Hue", sizeof(ctrl->name)); + ctrl->minimum = 0; + ctrl->maximum = 255; + ctrl->step = 1; + ctrl->default_value = 128; + ctrl->flags = 0; + break; + } + break; + } + case VIDIOC_S_CTRL: + { + struct v4l2_control *ctrl = arg; + + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + if (ctrl->value > 255) + dec->brightness = 255; + else if (ctrl->value < 0) + dec->brightness = 0; + else + dec->brightness = ctrl->value; + write_reg(client, 0x12, dec->brightness, dec->channel); + break; + case V4L2_CID_CONTRAST: + if (ctrl->value > 255) + dec->contrast = 255; + else if (ctrl->value < 0) + dec->contrast = 0; + else + dec->contrast = ctrl->value; + write_reg(client, 0x11, dec->contrast, dec->channel); + break; + case V4L2_CID_SATURATION: + if (ctrl->value > 255) + dec->saturation = 255; + else if (ctrl->value < 0) + dec->saturation = 0; + else + dec->saturation = ctrl->value; + write_reg(client, 0x10, dec->saturation, dec->channel); + break; + case V4L2_CID_HUE: + if (ctrl->value > 255) + dec->hue = 255; + else if (ctrl->value < 0) + dec->hue = 0; + else + dec->hue = ctrl->value; + write_reg(client, 0x0f, dec->hue, dec->channel); + break; + } + break; + } + case VIDIOC_G_CTRL: + { + struct v4l2_control *ctrl = arg; + + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + ctrl->value = dec->brightness; + break; + case V4L2_CID_CONTRAST: + ctrl->value = dec->contrast; + break; + case V4L2_CID_SATURATION: + ctrl->value = dec->saturation; + break; + case V4L2_CID_HUE: + ctrl->value = dec->hue; + break; + } + break; + } + default: + break; + } + return 0; +} + +static int wis_tw2804_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct i2c_adapter *adapter = client->adapter; + struct wis_tw2804 *dec; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + dec = kmalloc(sizeof(struct wis_tw2804), GFP_KERNEL); + if (dec == NULL) + return -ENOMEM; + + dec->channel = -1; + dec->norm = V4L2_STD_NTSC; + dec->brightness = 128; + dec->contrast = 128; + dec->saturation = 128; + dec->hue = 128; + i2c_set_clientdata(client, dec); + + printk(KERN_DEBUG "wis-tw2804: creating TW2804 at address %d on %s\n", + client->addr, adapter->name); + + return 0; +} + +static int wis_tw2804_remove(struct i2c_client *client) +{ + struct wis_tw2804 *dec = i2c_get_clientdata(client); + + kfree(dec); + return 0; +} + +static const struct i2c_device_id wis_tw2804_id[] = { + { "wis_tw2804", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wis_tw2804_id); + +static struct i2c_driver wis_tw2804_driver = { + .driver = { + .name = "WIS TW2804 I2C driver", + }, + .probe = wis_tw2804_probe, + .remove = wis_tw2804_remove, + .command = wis_tw2804_command, + .id_table = wis_tw2804_id, +}; + +static int __init wis_tw2804_init(void) +{ + return i2c_add_driver(&wis_tw2804_driver); +} + +static void __exit wis_tw2804_cleanup(void) +{ + i2c_del_driver(&wis_tw2804_driver); +} + +module_init(wis_tw2804_init); +module_exit(wis_tw2804_cleanup); + +MODULE_LICENSE("GPL v2"); diff --git a/drivers/staging/media/go7007/wis-tw9903.c b/drivers/staging/media/go7007/wis-tw9903.c new file mode 100644 index 0000000..9230f4a --- /dev/null +++ b/drivers/staging/media/go7007/wis-tw9903.c @@ -0,0 +1,341 @@ +/* + * 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/init.h> +#include <linux/i2c.h> +#include <linux/videodev2.h> +#include <linux/ioctl.h> +#include <linux/slab.h> + +#include "wis-i2c.h" + +struct wis_tw9903 { + int norm; + int brightness; + int contrast; + int hue; +}; + +static u8 initial_registers[] = +{ + 0x02, 0x44, /* input 1, composite */ + 0x03, 0x92, /* correct digital format */ + 0x04, 0x00, + 0x05, 0x80, /* or 0x00 for PAL */ + 0x06, 0x40, /* second internal current reference */ + 0x07, 0x02, /* window */ + 0x08, 0x14, /* window */ + 0x09, 0xf0, /* window */ + 0x0a, 0x81, /* window */ + 0x0b, 0xd0, /* window */ + 0x0c, 0x8c, + 0x0d, 0x00, /* scaling */ + 0x0e, 0x11, /* scaling */ + 0x0f, 0x00, /* scaling */ + 0x10, 0x00, /* brightness */ + 0x11, 0x60, /* contrast */ + 0x12, 0x01, /* sharpness */ + 0x13, 0x7f, /* U gain */ + 0x14, 0x5a, /* V gain */ + 0x15, 0x00, /* hue */ + 0x16, 0xc3, /* sharpness */ + 0x18, 0x00, + 0x19, 0x58, /* vbi */ + 0x1a, 0x80, + 0x1c, 0x0f, /* video norm */ + 0x1d, 0x7f, /* video norm */ + 0x20, 0xa0, /* clamping gain (working 0x50) */ + 0x21, 0x22, + 0x22, 0xf0, + 0x23, 0xfe, + 0x24, 0x3c, + 0x25, 0x38, + 0x26, 0x44, + 0x27, 0x20, + 0x28, 0x00, + 0x29, 0x15, + 0x2a, 0xa0, + 0x2b, 0x44, + 0x2c, 0x37, + 0x2d, 0x00, + 0x2e, 0xa5, /* burst PLL control (working: a9) */ + 0x2f, 0xe0, /* 0xea is blue test frame -- 0xe0 for normal */ + 0x31, 0x00, + 0x33, 0x22, + 0x34, 0x11, + 0x35, 0x35, + 0x3b, 0x05, + 0x06, 0xc0, /* reset device */ + 0x00, 0x00, /* Terminator (reg 0x00 is read-only) */ +}; + +static int write_reg(struct i2c_client *client, u8 reg, u8 value) +{ + return i2c_smbus_write_byte_data(client, reg, value); +} + +static int write_regs(struct i2c_client *client, u8 *regs) +{ + int i; + + for (i = 0; regs[i] != 0x00; i += 2) + if (i2c_smbus_write_byte_data(client, regs[i], regs[i + 1]) < 0) + return -1; + return 0; +} + +static int wis_tw9903_command(struct i2c_client *client, + unsigned int cmd, void *arg) +{ + struct wis_tw9903 *dec = i2c_get_clientdata(client); + + switch (cmd) { + case VIDIOC_S_INPUT: + { + int *input = arg; + + i2c_smbus_write_byte_data(client, 0x02, 0x40 | (*input << 1)); + break; + } +#if 0 + /* The scaler on this thing seems to be horribly broken */ + case DECODER_SET_RESOLUTION: + { + struct video_decoder_resolution *res = arg; + /*int hscale = 256 * 720 / res->width;*/ + int hscale = 256 * 720 / (res->width - (res->width > 704 ? 0 : 8)); + int vscale = 256 * (dec->norm & V4L2_STD_NTSC ? 240 : 288) + / res->height; + u8 regs[] = { + 0x0d, vscale & 0xff, + 0x0f, hscale & 0xff, + 0x0e, ((vscale & 0xf00) >> 4) | ((hscale & 0xf00) >> 8), + 0x06, 0xc0, /* reset device */ + 0, 0, + }; + printk(KERN_DEBUG "vscale is %04x, hscale is %04x\n", + vscale, hscale); + /*write_regs(client, regs);*/ + break; + } +#endif + case VIDIOC_S_STD: + { + v4l2_std_id *input = arg; + u8 regs[] = { + 0x05, *input & V4L2_STD_NTSC ? 0x80 : 0x00, + 0x07, *input & V4L2_STD_NTSC ? 0x02 : 0x12, + 0x08, *input & V4L2_STD_NTSC ? 0x14 : 0x18, + 0x09, *input & V4L2_STD_NTSC ? 0xf0 : 0x20, + 0, 0, + }; + write_regs(client, regs); + dec->norm = *input; + break; + } + case VIDIOC_QUERYCTRL: + { + struct v4l2_queryctrl *ctrl = arg; + + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + ctrl->type = V4L2_CTRL_TYPE_INTEGER; + strncpy(ctrl->name, "Brightness", sizeof(ctrl->name)); + ctrl->minimum = -128; + ctrl->maximum = 127; + ctrl->step = 1; + ctrl->default_value = 0x00; + ctrl->flags = 0; + break; + case V4L2_CID_CONTRAST: + ctrl->type = V4L2_CTRL_TYPE_INTEGER; + strncpy(ctrl->name, "Contrast", sizeof(ctrl->name)); + ctrl->minimum = 0; + ctrl->maximum = 255; + ctrl->step = 1; + ctrl->default_value = 0x60; + ctrl->flags = 0; + break; +#if 0 + /* I don't understand how the Chroma Gain registers work... */ + case V4L2_CID_SATURATION: + ctrl->type = V4L2_CTRL_TYPE_INTEGER; + strncpy(ctrl->name, "Saturation", sizeof(ctrl->name)); + ctrl->minimum = 0; + ctrl->maximum = 127; + ctrl->step = 1; + ctrl->default_value = 64; + ctrl->flags = 0; + break; +#endif + case V4L2_CID_HUE: + ctrl->type = V4L2_CTRL_TYPE_INTEGER; + strncpy(ctrl->name, "Hue", sizeof(ctrl->name)); + ctrl->minimum = -128; + ctrl->maximum = 127; + ctrl->step = 1; + ctrl->default_value = 0; + ctrl->flags = 0; + break; + } + break; + } + case VIDIOC_S_CTRL: + { + struct v4l2_control *ctrl = arg; + + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + if (ctrl->value > 127) + dec->brightness = 127; + else if (ctrl->value < -128) + dec->brightness = -128; + else + dec->brightness = ctrl->value; + write_reg(client, 0x10, dec->brightness); + break; + case V4L2_CID_CONTRAST: + if (ctrl->value > 255) + dec->contrast = 255; + else if (ctrl->value < 0) + dec->contrast = 0; + else + dec->contrast = ctrl->value; + write_reg(client, 0x11, dec->contrast); + break; +#if 0 + case V4L2_CID_SATURATION: + if (ctrl->value > 127) + dec->saturation = 127; + else if (ctrl->value < 0) + dec->saturation = 0; + else + dec->saturation = ctrl->value; + /*write_reg(client, 0x0c, dec->saturation);*/ + break; +#endif + case V4L2_CID_HUE: + if (ctrl->value > 127) + dec->hue = 127; + else if (ctrl->value < -128) + dec->hue = -128; + else + dec->hue = ctrl->value; + write_reg(client, 0x15, dec->hue); + break; + } + break; + } + case VIDIOC_G_CTRL: + { + struct v4l2_control *ctrl = arg; + + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + ctrl->value = dec->brightness; + break; + case V4L2_CID_CONTRAST: + ctrl->value = dec->contrast; + break; +#if 0 + case V4L2_CID_SATURATION: + ctrl->value = dec->saturation; + break; +#endif + case V4L2_CID_HUE: + ctrl->value = dec->hue; + break; + } + break; + } + default: + break; + } + return 0; +} + +static int wis_tw9903_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct i2c_adapter *adapter = client->adapter; + struct wis_tw9903 *dec; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + dec = kmalloc(sizeof(struct wis_tw9903), GFP_KERNEL); + if (dec == NULL) + return -ENOMEM; + + dec->norm = V4L2_STD_NTSC; + dec->brightness = 0; + dec->contrast = 0x60; + dec->hue = 0; + i2c_set_clientdata(client, dec); + + printk(KERN_DEBUG + "wis-tw9903: initializing TW9903 at address %d on %s\n", + client->addr, adapter->name); + + if (write_regs(client, initial_registers) < 0) { + printk(KERN_ERR "wis-tw9903: error initializing TW9903\n"); + kfree(dec); + return -ENODEV; + } + + return 0; +} + +static int wis_tw9903_remove(struct i2c_client *client) +{ + struct wis_tw9903 *dec = i2c_get_clientdata(client); + + kfree(dec); + return 0; +} + +static const struct i2c_device_id wis_tw9903_id[] = { + { "wis_tw9903", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wis_tw9903_id); + +static struct i2c_driver wis_tw9903_driver = { + .driver = { + .name = "WIS TW9903 I2C driver", + }, + .probe = wis_tw9903_probe, + .remove = wis_tw9903_remove, + .command = wis_tw9903_command, + .id_table = wis_tw9903_id, +}; + +static int __init wis_tw9903_init(void) +{ + return i2c_add_driver(&wis_tw9903_driver); +} + +static void __exit wis_tw9903_cleanup(void) +{ + i2c_del_driver(&wis_tw9903_driver); +} + +module_init(wis_tw9903_init); +module_exit(wis_tw9903_cleanup); + +MODULE_LICENSE("GPL v2"); diff --git a/drivers/staging/media/go7007/wis-uda1342.c b/drivers/staging/media/go7007/wis-uda1342.c new file mode 100644 index 0000000..0127be2 --- /dev/null +++ b/drivers/staging/media/go7007/wis-uda1342.c @@ -0,0 +1,114 @@ +/* + * 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/init.h> +#include <linux/i2c.h> +#include <linux/videodev2.h> +#include <media/tvaudio.h> +#include <media/v4l2-common.h> + +#include "wis-i2c.h" + +static int write_reg(struct i2c_client *client, int reg, int value) +{ + /* UDA1342 wants MSB first, but SMBus sends LSB first */ + i2c_smbus_write_word_data(client, reg, swab16(value)); + return 0; +} + +static int wis_uda1342_command(struct i2c_client *client, + unsigned int cmd, void *arg) +{ + switch (cmd) { + case VIDIOC_S_AUDIO: + { + int *inp = arg; + + switch (*inp) { + case TVAUDIO_INPUT_TUNER: + write_reg(client, 0x00, 0x1441); /* select input 2 */ + break; + case TVAUDIO_INPUT_EXTERN: + write_reg(client, 0x00, 0x1241); /* select input 1 */ + break; + default: + printk(KERN_ERR "wis-uda1342: input %d not supported\n", + *inp); + break; + } + break; + } + default: + break; + } + return 0; +} + +static int wis_uda1342_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct i2c_adapter *adapter = client->adapter; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WORD_DATA)) + return -ENODEV; + + printk(KERN_DEBUG + "wis-uda1342: initializing UDA1342 at address %d on %s\n", + client->addr, adapter->name); + + write_reg(client, 0x00, 0x8000); /* reset registers */ + write_reg(client, 0x00, 0x1241); /* select input 1 */ + + return 0; +} + +static int wis_uda1342_remove(struct i2c_client *client) +{ + return 0; +} + +static const struct i2c_device_id wis_uda1342_id[] = { + { "wis_uda1342", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wis_uda1342_id); + +static struct i2c_driver wis_uda1342_driver = { + .driver = { + .name = "WIS UDA1342 I2C driver", + }, + .probe = wis_uda1342_probe, + .remove = wis_uda1342_remove, + .command = wis_uda1342_command, + .id_table = wis_uda1342_id, +}; + +static int __init wis_uda1342_init(void) +{ + return i2c_add_driver(&wis_uda1342_driver); +} + +static void __exit wis_uda1342_cleanup(void) +{ + i2c_del_driver(&wis_uda1342_driver); +} + +module_init(wis_uda1342_init); +module_exit(wis_uda1342_cleanup); + +MODULE_LICENSE("GPL v2"); diff --git a/drivers/staging/media/lirc/Kconfig b/drivers/staging/media/lirc/Kconfig new file mode 100644 index 0000000..526ec0f --- /dev/null +++ b/drivers/staging/media/lirc/Kconfig @@ -0,0 +1,78 @@ +# +# 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_TTUSBIR + tristate "Technotrend USB IR Receiver" + depends on LIRC && USB + help + Driver for the Technotrend USB IR Receiver + +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 0000000..d76b0fa --- /dev/null +++ b/drivers/staging/media/lirc/Makefile @@ -0,0 +1,14 @@ +# 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_TTUSBIR) += lirc_ttusbir.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 0000000..b6cb593 --- /dev/null +++ b/drivers/staging/media/lirc/TODO @@ -0,0 +1,8 @@ +- 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). + +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 0000000..a97800a --- /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 0000000..c5a0d27 --- /dev/null +++ b/drivers/staging/media/lirc/lirc_bt829.c @@ -0,0 +1,383 @@ +/* + * 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 +*/ + +#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 int debug; +#define dprintk(fmt, args...) \ + do { \ + if (debug) \ + printk(KERN_DEBUG DRIVER_NAME ": "fmt, ## args); \ + } while (0) + +static int atir_minor; +static unsigned long pci_addr_phys; +static unsigned char *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) { + printk(KERN_ERR DRIVER_NAME ": 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; + printk(KERN_INFO DRIVER_NAME ": memory at 0x%08X\n", + (unsigned int)pci_addr_phys); + } + if (pci_addr_phys == 0) { + printk(KERN_ERR DRIVER_NAME ": no memory resource ?\n"); + return NULL; + } + } else { + printk(KERN_ERR DRIVER_NAME ": 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; + + pdev = do_pci_probe(); + if (pdev == NULL) + return -ENODEV; + + if (!atir_init_start()) + return -ENODEV; + + strcpy(atir_driver.name, "ATIR"); + atir_driver.minor = -1; + atir_driver.code_length = 8; + atir_driver.sample_rate = 10; + atir_driver.data = 0; + 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) { + printk(KERN_ERR DRIVER_NAME ": failed to register driver!\n"); + return atir_minor; + } + dprintk("driver is registered on minor %d\n", atir_minor); + + return 0; +} + + +void cleanup_module(void) +{ + lirc_unregister_driver(atir_minor); +} + + +static int atir_init_start(void) +{ + pci_addr_lin = ioremap(pci_addr_phys + DATA_PCI_OFF, 0x400); + if (pci_addr_lin == 0) { + printk(KERN_INFO DRIVER_NAME ": pci mem must be mapped\n"); + return 0; + } + return 1; +} + +static void cycle_delay(int cycle) +{ + udelay(WAIT_CYCLE*cycle); +} + + +static int poll_main() +{ + 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 *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 *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_ene0100.h b/drivers/staging/media/lirc/lirc_ene0100.h new file mode 100644 index 0000000..06bebd6 --- /dev/null +++ b/drivers/staging/media/lirc/lirc_ene0100.h @@ -0,0 +1,169 @@ +/* + * driver for ENE KB3926 B/C/D CIR (also known as ENE0100) + * + * Copyright (C) 2009 Maxim Levitsky <maximlevitsky@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. + * + * 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 <media/lirc.h> +#include <media/lirc_dev.h> + +/* hardware address */ +#define ENE_STATUS 0 /* hardware status - unused */ +#define ENE_ADDR_HI 1 /* hi byte of register address */ +#define ENE_ADDR_LO 2 /* low byte of register address */ +#define ENE_IO 3 /* read/write window */ +#define ENE_MAX_IO 4 + +/* 8 bytes of samples, divided in 2 halfs*/ +#define ENE_SAMPLE_BUFFER 0xF8F0 /* regular sample buffer */ +#define ENE_SAMPLE_SPC_MASK (1 << 7) /* sample is space */ +#define ENE_SAMPLE_VALUE_MASK 0x7F +#define ENE_SAMPLE_OVERFLOW 0x7F +#define ENE_SAMPLES_SIZE 4 + +/* fan input sample buffer */ +#define ENE_SAMPLE_BUFFER_FAN 0xF8FB /* this buffer holds high byte of */ + /* each sample of normal buffer */ + +#define ENE_FAN_SMPL_PULS_MSK 0x8000 /* this bit of combined sample */ + /* if set, says that sample is pulse */ +#define ENE_FAN_VALUE_MASK 0x0FFF /* mask for valid bits of the value */ + +/* first firmware register */ +#define ENE_FW1 0xF8F8 +#define ENE_FW1_ENABLE (1 << 0) /* enable fw processing */ +#define ENE_FW1_TXIRQ (1 << 1) /* TX interrupt pending */ +#define ENE_FW1_WAKE (1 << 6) /* enable wake from S3 */ +#define ENE_FW1_IRQ (1 << 7) /* enable interrupt */ + +/* second firmware register */ +#define ENE_FW2 0xF8F9 +#define ENE_FW2_BUF_HIGH (1 << 0) /* which half of the buffer to read */ +#define ENE_FW2_IRQ_CLR (1 << 2) /* clear this on IRQ */ +#define ENE_FW2_GP40_AS_LEARN (1 << 4) /* normal input is used as */ + /* learning input */ +#define ENE_FW2_FAN_AS_NRML_IN (1 << 6) /* fan is used as normal input */ +#define ENE_FW2_LEARNING (1 << 7) /* hardware supports learning and TX */ + +/* fan as input settings - only if learning capable */ +#define ENE_FAN_AS_IN1 0xFE30 /* fan init reg 1 */ +#define ENE_FAN_AS_IN1_EN 0xCD +#define ENE_FAN_AS_IN2 0xFE31 /* fan init reg 2 */ +#define ENE_FAN_AS_IN2_EN 0x03 +#define ENE_SAMPLE_PERIOD_FAN 61 /* fan input has fixed sample period */ + +/* IRQ registers block (for revision B) */ +#define ENEB_IRQ 0xFD09 /* IRQ number */ +#define ENEB_IRQ_UNK1 0xFD17 /* unknown setting = 1 */ +#define ENEB_IRQ_STATUS 0xFD80 /* irq status */ +#define ENEB_IRQ_STATUS_IR (1 << 5) /* IR irq */ + +/* IRQ registers block (for revision C,D) */ +#define ENEC_IRQ 0xFE9B /* new irq settings register */ +#define ENEC_IRQ_MASK 0x0F /* irq number mask */ +#define ENEC_IRQ_UNK_EN (1 << 4) /* always enabled */ +#define ENEC_IRQ_STATUS (1 << 5) /* irq status and ACK */ + +/* CIR block settings */ +#define ENE_CIR_CONF1 0xFEC0 +#define ENE_CIR_CONF1_ADC_ON 0x7 /* receiver on gpio40 enabled */ +#define ENE_CIR_CONF1_LEARN1 (1 << 3) /* enabled on learning mode */ +#define ENE_CIR_CONF1_TX_ON 0x30 /* enabled on transmit */ +#define ENE_CIR_CONF1_TX_CARR (1 << 7) /* send TX carrier or not */ + +#define ENE_CIR_CONF2 0xFEC1 /* unknown setting = 0 */ +#define ENE_CIR_CONF2_LEARN2 (1 << 4) /* set on enable learning */ +#define ENE_CIR_CONF2_GPIO40DIS (1 << 5) /* disable normal input via gpio40 */ + +#define ENE_CIR_SAMPLE_PERIOD 0xFEC8 /* sample period in us */ +#define ENE_CIR_SAMPLE_OVERFLOW (1 << 7) /* interrupt on overflows if set */ + + +/* transmitter - not implemented yet */ +/* KB3926C and higher */ +/* transmission is very similar to receiving, a byte is written to */ +/* ENE_TX_INPUT, in same manner as it is read from sample buffer */ +/* sample period is fixed*/ + + +/* transmitter ports */ +#define ENE_TX_PORT1 0xFC01 /* this enables one or both */ +#define ENE_TX_PORT1_EN (1 << 5) /* TX ports */ +#define ENE_TX_PORT2 0xFC08 +#define ENE_TX_PORT2_EN (1 << 1) + +#define ENE_TX_INPUT 0xFEC9 /* next byte to transmit */ +#define ENE_TX_SPC_MASK (1 << 7) /* Transmitted sample is space */ +#define ENE_TX_UNK1 0xFECB /* set to 0x63 */ +#define ENE_TX_SMPL_PERIOD 50 /* transmit sample period */ + + +#define ENE_TX_CARRIER 0xFECE /* TX carrier * 2 (khz) */ +#define ENE_TX_CARRIER_UNKBIT 0x80 /* This bit set on transmit */ +#define ENE_TX_CARRIER_LOW 0xFECF /* TX carrier / 2 */ + +/* Hardware versions */ +#define ENE_HW_VERSION 0xFF00 /* hardware revision */ +#define ENE_HW_UNK 0xFF1D +#define ENE_HW_UNK_CLR (1 << 2) +#define ENE_HW_VER_MAJOR 0xFF1E /* chip version */ +#define ENE_HW_VER_MINOR 0xFF1F +#define ENE_HW_VER_OLD 0xFD00 + +#define same_sign(a, b) ((((a) > 0) && (b) > 0) || ((a) < 0 && (b) < 0)) + +#define ENE_DRIVER_NAME "enecir" +#define ENE_MAXGAP 250000 /* this is amount of time we wait + before turning the sampler, chosen + arbitry */ + +#define space(len) (-(len)) /* add a space */ + +/* software defines */ +#define ENE_IRQ_RX 1 +#define ENE_IRQ_TX 2 + +#define ENE_HW_B 1 /* 3926B */ +#define ENE_HW_C 2 /* 3926C */ +#define ENE_HW_D 3 /* 3926D */ + +#define ene_printk(level, text, ...) \ + printk(level ENE_DRIVER_NAME ": " text, ## __VA_ARGS__) + +struct ene_device { + struct pnp_dev *pnp_dev; + struct lirc_driver *lirc_driver; + + /* hw settings */ + unsigned long hw_io; + int irq; + + int hw_revision; /* hardware revision */ + int hw_learning_and_tx_capable; /* learning capable */ + int hw_gpio40_learning; /* gpio40 is learning */ + int hw_fan_as_normal_input; /* fan input is used as regular input */ + + /* device data */ + int idle; + int fan_input_inuse; + + int sample; + int in_use; + + struct timeval gap_start; +}; diff --git a/drivers/staging/media/lirc/lirc_igorplugusb.c b/drivers/staging/media/lirc/lirc_igorplugusb.c new file mode 100644 index 0000000..0dc2c2b --- /dev/null +++ b/drivers/staging/media/lirc/lirc_igorplugusb.c @@ -0,0 +1,577 @@ +/* + * 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" + +/* debugging support */ +#ifdef CONFIG_USB_DEBUG +static int debug = 1; +#else +static int debug; +#endif + +#define dprintk(fmt, args...) \ + do { \ + if (debug) \ + printk(KERN_DEBUG fmt, ## args); \ + } while (0) + +/* 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) { + printk(KERN_ERR "%s: called with NULL device struct!\n", + __func__); + return -EINVAL; + } + + devnum = ir->devnum; + d = ir->d; + + if (!d) { + printk(KERN_ERR "%s: called with NULL lirc driver struct!\n", + __func__); + return -EINVAL; + } + + dprintk(DRIVER_NAME "[%d]: calling lirc_unregister_driver\n", devnum); + lirc_unregister_driver(d->minor); + + kfree(d); + ir->d = NULL; + kfree(ir); + + 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; + } + + dprintk(DRIVER_NAME "[%d]: set use inc\n", ir->devnum); + + 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; + } + + dprintk(DRIVER_NAME "[%d]: set use dec\n", ir->devnum); +} + +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; + + dprintk(DRIVER_NAME ": Got %d bytes. Header: %02x %02x %02x\n", + ret, ir->buf_in[0], ir->buf_in[1], ir->buf_in[2]); + + 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 { + printk(KERN_WARNING DRIVER_NAME + "[%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 = NULL; + struct usb_host_interface *idesc = NULL; + struct usb_endpoint_descriptor *ep; + struct igorplug *ir = NULL; + struct lirc_driver *driver = NULL; + int devnum, pipe, maxp; + int minor = 0; + char buf[63], name[128] = ""; + int mem_failure = 0; + int ret; + + dprintk(DRIVER_NAME ": usb probe called.\n"); + + 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)); + + dprintk(DRIVER_NAME "[%d]: bytes_in_key=%zu maxp=%d\n", + devnum, CODE_LENGTH, maxp); + + mem_failure = 0; + ir = kzalloc(sizeof(struct igorplug), GFP_KERNEL); + if (!ir) { + mem_failure = 1; + goto mem_failure_switch; + } + driver = kzalloc(sizeof(struct lirc_driver), GFP_KERNEL); + if (!driver) { + mem_failure = 2; + goto mem_failure_switch; + } + + ir->buf_in = usb_alloc_coherent(dev, DEVICE_BUFLEN + DEVICE_HEADERLEN, + GFP_ATOMIC, &ir->dma_in); + if (!ir->buf_in) { + mem_failure = 3; + goto mem_failure_switch; + } + + 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; + + minor = lirc_register_driver(driver); + if (minor < 0) + mem_failure = 9; + +mem_failure_switch: + + switch (mem_failure) { + case 9: + usb_free_coherent(dev, DEVICE_BUFLEN + DEVICE_HEADERLEN, + ir->buf_in, ir->dma_in); + case 3: + kfree(driver); + case 2: + kfree(ir); + case 1: + printk(DRIVER_NAME "[%d]: out of memory (code=%d)\n", + devnum, mem_failure); + return -ENOMEM; + } + + driver->minor = minor; + 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 +}; + +static int __init igorplugusb_remote_init(void) +{ + int ret = 0; + + dprintk(DRIVER_NAME ": loaded, debug mode enabled\n"); + + ret = usb_register(&igorplugusb_remote_driver); + if (ret) + printk(KERN_ERR DRIVER_NAME ": usb register failed!\n"); + + return ret; +} + +static void __exit igorplugusb_remote_exit(void) +{ + usb_deregister(&igorplugusb_remote_driver); +} + +module_init(igorplugusb_remote_init); +module_exit(igorplugusb_remote_exit); + +#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)"); + +module_param(debug, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(debug, "Debug enabled or not"); diff --git a/drivers/staging/media/lirc/lirc_imon.c b/drivers/staging/media/lirc/lirc_imon.c new file mode 100644 index 0000000..f5308d5 --- /dev/null +++ b/drivers/staging/media/lirc/lirc_imon.c @@ -0,0 +1,1050 @@ +/* + * 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. + */ + +#include <linux/errno.h> +#include <linux/init.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 *buf, + size_t n_bytes, loff_t *pos); + +/* LIRC driver function prototypes */ +static int ir_open(void *data); +static void ir_close(void *data); + +/* Driver init/exit prototypes */ +static int __init imon_init(void); +static void __exit imon_exit(void); + +/*** 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) + err("%s: unable to deregister from lirc(%d)", + __func__, retval); + else + printk(KERN_INFO MOD_NAME ": 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) { + err("%s: could not find interface for minor %d", + __func__, subminor); + retval = -ENODEV; + goto exit; + } + context = usb_get_intfdata(interface); + + if (!context) { + err("%s: no context found for minor %d", + __func__, subminor); + retval = -ENODEV; + goto exit; + } + + mutex_lock(&context->ctx_lock); + + if (!context->display) { + err("%s: display not supported by device", __func__); + retval = -ENODEV; + } else if (context->display_isopen) { + err("%s: display port is already open", __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) { + err("%s: no context for device", __func__); + return -ENODEV; + } + + mutex_lock(&context->ctx_lock); + + if (!context->display) { + err("%s: display not supported by device", __func__); + retval = -ENODEV; + } else if (!context->display_isopen) { + err("%s: display is not open", __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); + err("%s: error submitting urb(%d)", __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) + err("%s: task interrupted", __func__); + mutex_lock(&context->ctx_lock); + + retval = context->tx.status; + if (retval) + err("%s: packet tx failed (%d)", __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 *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) { + err("%s: no context for device", __func__); + return -ENODEV; + } + + mutex_lock(&context->ctx_lock); + + if (!context->dev_present) { + err("%s: no iMON device present", __func__); + retval = -ENODEV; + goto exit; + } + + if (n_bytes <= 0 || n_bytes > IMON_DATA_BUF_SZ - 3) { + err("%s: invalid payload size", __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) { + err("%s: send packet failed for packet #%d", + __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) + err("%s: send packet failed for packet #%d", + __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) { + err("%s: no context for device", __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) { + printk(KERN_INFO "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) { + err("%s: kzalloc failed for context", __func__); + 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) { + err("%s: no valid input (IR) endpoint found.", __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) { + err("%s: kzalloc failed for lirc_driver", __func__); + alloc_status = 2; + goto alloc_status_switch; + } + rbuf = kmalloc(sizeof(struct lirc_buffer), GFP_KERNEL); + if (!rbuf) { + err("%s: kmalloc failed for lirc_buffer", __func__); + alloc_status = 3; + goto alloc_status_switch; + } + if (lirc_buffer_init(rbuf, BUF_CHUNK_SIZE, BUF_SIZE)) { + err("%s: lirc_buffer_init failed", __func__); + alloc_status = 4; + goto alloc_status_switch; + } + rx_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!rx_urb) { + err("%s: usb_alloc_urb failed for IR urb", __func__); + alloc_status = 5; + goto alloc_status_switch; + } + tx_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!tx_urb) { + err("%s: usb_alloc_urb failed for display urb", + __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) { + err("%s: lirc_register_driver failed", __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) { + err("%s: usb_submit_urb failed for intf0 (%d)", + __func__, retval); + mutex_unlock(&context->ctx_lock); + goto exit; + } + + 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 7: + usb_free_urb(tx_urb); + case 6: + usb_free_urb(rx_urb); + case 5: + if (rbuf) + lirc_buffer_free(rbuf); + case 4: + kfree(rbuf); + case 3: + kfree(driver); + case 2: + kfree(context); + context = NULL; + case 1: + if (retval != -ENODEV) + retval = -ENOMEM; + break; + case 0: + retval = 0; + } + +exit: + 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); + + printk(KERN_INFO "%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; +} + +static int __init imon_init(void) +{ + int rc; + + printk(KERN_INFO MOD_NAME ": " MOD_DESC ", v" MOD_VERSION "\n"); + + rc = usb_register(&imon_driver); + if (rc) { + err("%s: usb register failed(%d)", __func__, rc); + return -ENODEV; + } + + return 0; +} + +static void __exit imon_exit(void) +{ + usb_deregister(&imon_driver); + printk(KERN_INFO MOD_NAME ": module removed. Goodbye!\n"); +} + +module_init(imon_init); +module_exit(imon_exit); diff --git a/drivers/staging/media/lirc/lirc_parallel.c b/drivers/staging/media/lirc/lirc_parallel.c new file mode 100644 index 0000000..792aac0 --- /dev/null +++ b/drivers/staging/media/lirc/lirc_parallel.c @@ -0,0 +1,755 @@ +/* + * 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 + * + */ + +/*** 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 int debug; +static int check_pselecd; + +unsigned int irq = LIRC_IRQ; +unsigned int io = LIRC_PORT; +#ifdef LIRC_TIMER +unsigned int timer; +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]; + +DECLARE_WAIT_QUEUE_HEAD(lirc_wait); + +unsigned int rptr; +unsigned int wptr; +unsigned int lost_irqs; +int is_open; + +struct parport *pport; +struct pardevice *ppdevice; +int is_claimed; + +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: + printk(KERN_INFO "%s: attempt to write to status register\n", + LIRC_DRIVER_NAME); + 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; + printk(KERN_INFO "%s: %u Hz timer detected\n", + LIRC_DRIVER_NAME, newtimer); + return newtimer; + } else { + newtimer = (1000000*count)/timeelapsed; + if (abs(newtimer - default_timer) > default_timer/10) { + /* bad timer */ + printk(KERN_NOTICE "%s: bad timer: %u Hz\n", + LIRC_DRIVER_NAME, newtimer); + printk(KERN_NOTICE "%s: using default timer: " + "%u Hz\n", + LIRC_DRIVER_NAME, default_timer); + return default_timer; + } else { + printk(KERN_INFO "%s: %u Hz timer detected\n", + LIRC_DRIVER_NAME, newtimer); + return newtimer; /* use detected value */ + } + } + } else { + printk(KERN_NOTICE "%s: no timer detected\n", LIRC_DRIVER_NAME); + return 0; + } +} + +static int lirc_claim(void) +{ + if (parport_claim(ppdevice) != 0) { + printk(KERN_WARNING "%s: could not claim port\n", + LIRC_DRIVER_NAME); + printk(KERN_WARNING "%s: waiting for port becoming available" + "\n", LIRC_DRIVER_NAME); + if (parport_claim_or_block(ppdevice) < 0) { + printk(KERN_NOTICE "%s: could not claim port, giving" + " up\n", LIRC_DRIVER_NAME); + 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++; + printk(KERN_NOTICE "%s: buffer overrun\n", LIRC_DRIVER_NAME); + return; + } + rbuf[wptr] = signal; + wptr = nwptr; +} + +static void 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; + printk(KERN_NOTICE "%s: timeout\n", LIRC_DRIVER_NAME); + 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 *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 *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 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, (__u32 *) arg); + if (result) + return result; + break; + case LIRC_GET_SEND_MODE: + result = put_user(LIRC_MODE_PULSE, (__u32 *) arg); + if (result) + return result; + break; + case LIRC_GET_REC_MODE: + result = put_user(LIRC_MODE_MODE2, (__u32 *) arg); + if (result) + return result; + break; + case LIRC_SET_SEND_MODE: + result = get_user(mode, (__u32 *) arg); + if (result) + return result; + if (mode != LIRC_MODE_PULSE) + return -EINVAL; + break; + case LIRC_SET_REC_MODE: + result = get_user(mode, (__u32 *) arg); + if (result) + return result; + if (mode != LIRC_MODE_MODE2) + return -ENOSYS; + break; + case LIRC_SET_TRANSMITTER_MASK: + result = get_user(value, (__u32 *) arg); + 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 __devinit lirc_parallel_probe(struct platform_device *dev) +{ + return 0; +} + +static int __devexit 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 = __devexit_p(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) { + printk(KERN_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) { + printk(KERN_NOTICE "%s: no port at %x found\n", + LIRC_DRIVER_NAME, io); + result = -ENXIO; + goto exit_device_put; + } + ppdevice = parport_register_device(pport, LIRC_DRIVER_NAME, + pf, kf, irq_handler, 0, NULL); + parport_put_port(pport); + if (ppdevice == NULL) { + printk(KERN_NOTICE "%s: parport_register_device() failed\n", + LIRC_DRIVER_NAME); + 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) { + printk(KERN_NOTICE "%s: register_chrdev() failed\n", + LIRC_DRIVER_NAME); + parport_unregister_device(ppdevice); + result = -EIO; + goto exit_device_put; + } + printk(KERN_INFO "%s: installed using port 0x%04x irq %d\n", + LIRC_DRIVER_NAME, 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(debug, "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 0000000..4bed6af --- /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 0000000..a2d18b0 --- /dev/null +++ b/drivers/staging/media/lirc/lirc_sasem.c @@ -0,0 +1,939 @@ +/* + * 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 + */ + +#include <linux/errno.h> +#include <linux/init.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 *buf, + size_t n_bytes, loff_t *pos); + +/* LIRC driver function prototypes */ +static int ir_open(void *data); +static void ir_close(void *data); + +/* Driver init/exit prototypes */ +static int __init sasem_init(void); +static void __exit sasem_exit(void); + +/*** 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) + printk(KERN_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) + err("%s: unable to deregister from lirc (%d)", + __func__, retval); + else + printk(KERN_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) { + err("%s: could not find interface for minor %d", + __func__, subminor); + retval = -ENODEV; + goto exit; + } + context = usb_get_intfdata(interface); + + if (!context) { + err("%s: no context found for minor %d", + __func__, subminor); + retval = -ENODEV; + goto exit; + } + + mutex_lock(&context->ctx_lock); + + if (context->vfd_isopen) { + err("%s: VFD port is already open", __func__); + retval = -EBUSY; + } else { + context->vfd_isopen = 1; + file->private_data = context; + printk(KERN_INFO "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) { + err("%s: no context for device", __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: + printk(KERN_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) { + err("%s: no context for device", __func__); + return -ENODEV; + } + + mutex_lock(&context->ctx_lock); + + if (!context->vfd_isopen) { + err("%s: VFD is not open", __func__); + retval = -EIO; + } else { + context->vfd_isopen = 0; + printk(KERN_INFO "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); + err("%s: error submitting urb (%d)", __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) + err("%s: packet tx failed (%d)", __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 *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) { + err("%s: no context for device", __func__); + return -ENODEV; + } + + mutex_lock(&context->ctx_lock); + + if (!context->dev_present) { + err("%s: no Sasem device present", __func__); + retval = -ENODEV; + goto exit; + } + + if (n_bytes <= 0 || n_bytes > SASEM_DATA_BUF_SZ) { + err("%s: invalid payload size", __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 < 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) { + + err("%s: send packet failed for packet #%d", + __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) { + err("%s: IR port is already open", __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) + err("%s: usb_submit_urb failed for ir_open (%d)", + __func__, retval); + else { + context->ir_isopen = 1; + printk(KERN_INFO "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) { + err("%s: no context for device", __func__); + return; + } + + mutex_lock(&context->ctx_lock); + + usb_kill_urb(context->rx_urb); + context->ir_isopen = 0; + printk(KERN_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) { + printk(KERN_WARNING "%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: + printk(KERN_WARNING "%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; + + printk(KERN_INFO "%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) + printk(KERN_INFO "%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) + printk(KERN_INFO "%s: found VFD endpoint\n", + __func__); + } + } + + /* Input endpoint is mandatory */ + if (!ir_ep_found) { + + err("%s: no valid input (IR) endpoint found.", __func__); + retval = -ENODEV; + goto exit; + } + + if (!vfd_ep_found) + printk(KERN_INFO "%s: no valid output (VFD) endpoint found.\n", + __func__); + + + /* Allocate memory */ + alloc_status = 0; + + context = kzalloc(sizeof(struct sasem_context), GFP_KERNEL); + if (!context) { + err("%s: kzalloc failed for context", __func__); + alloc_status = 1; + goto alloc_status_switch; + } + driver = kzalloc(sizeof(struct lirc_driver), GFP_KERNEL); + if (!driver) { + err("%s: kzalloc failed for lirc_driver", __func__); + alloc_status = 2; + goto alloc_status_switch; + } + rbuf = kmalloc(sizeof(struct lirc_buffer), GFP_KERNEL); + if (!rbuf) { + err("%s: kmalloc failed for lirc_buffer", __func__); + alloc_status = 3; + goto alloc_status_switch; + } + if (lirc_buffer_init(rbuf, BUF_CHUNK_SIZE, BUF_SIZE)) { + err("%s: lirc_buffer_init failed", __func__); + alloc_status = 4; + goto alloc_status_switch; + } + rx_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!rx_urb) { + err("%s: usb_alloc_urb failed for IR urb", __func__); + alloc_status = 5; + goto alloc_status_switch; + } + if (vfd_ep_found) { + tx_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!tx_urb) { + err("%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) { + err("%s: lirc_register_driver failed", __func__); + alloc_status = 7; + retval = lirc_minor; + goto unlock; + } else + printk(KERN_INFO "%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) + printk(KERN_INFO "Registering VFD with sysfs\n"); + if (usb_register_dev(interface, &sasem_class)) + /* Not a fatal error, so ignore */ + printk(KERN_INFO "%s: could not get a minor number " + "for VFD\n", __func__); + } + + printk(KERN_INFO "%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); + case 5: + lirc_buffer_free(rbuf); + case 4: + kfree(rbuf); + case 3: + kfree(driver); + case 2: + kfree(context); + context = NULL; + case 1: + if (retval == 0) + retval = -ENOMEM; + } + +exit: + return retval; +} + +/** + * Callback function for USB core API: disonnect + */ +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); + + printk(KERN_INFO "%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); +} + +static int __init sasem_init(void) +{ + int rc; + + printk(KERN_INFO MOD_DESC ", v" MOD_VERSION "\n"); + printk(KERN_INFO MOD_AUTHOR "\n"); + + rc = usb_register(&sasem_driver); + if (rc < 0) { + err("%s: usb register failed (%d)", __func__, rc); + return -ENODEV; + } + return 0; +} + +static void __exit sasem_exit(void) +{ + usb_deregister(&sasem_driver); + printk(KERN_INFO "module removed. Goodbye!\n"); +} + + +module_init(sasem_init); +module_exit(sasem_exit); diff --git a/drivers/staging/media/lirc/lirc_serial.c b/drivers/staging/media/lirc/lirc_serial.c new file mode 100644 index 0000000..8a060a8 --- /dev/null +++ b/drivers/staging/media/lirc/lirc_serial.c @@ -0,0 +1,1315 @@ +/* + * 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 + */ + +#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 <asm/system.h> +#include <linux/io.h> +#include <linux/irq.h> +#include <linux/fcntl.h> +#include <linux/spinlock.h> + +#ifdef CONFIG_LIRC_SERIAL_NSLU2 +#include <asm/hardware.h> +#endif +/* 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 int iommap; +static int ioshift; +static int softcarrier = 1; +static int share_irq; +static int debug; +static int sense = -1; /* -1 = auto, 0 = active high, 1 = active low */ +static int 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] = { + .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] = { + .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] = { + .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] = { + .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] = { + .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 + }, + +#ifdef CONFIG_LIRC_SERIAL_NSLU2 + /* + * Modified Linksys Network Storage Link USB 2.0 (NSLU2): + * We receive on CTS of the 2nd serial port (R142,LHS), we + * transmit with a IR diode between GPIO[1] (green status LED), + * and ground (Matthias Goebl <matthias.goebl@goebl.net>). + * See also http://www.nslu2-linux.org for this device + */ + [LIRC_NSLU2] = { + .signal_pin = UART_MSR_CTS, + .signal_pin_change = UART_MSR_DCTS, + .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 + }, +#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) +{ +#ifdef CONFIG_LIRC_SERIAL_NSLU2 + /* + * On NSLU2, we put the transmit diode between the output of the green + * status LED and ground + */ + if (type == LIRC_NSLU2) { + gpio_line_set(NSLU2_LED_GRN, IXP4XX_GPIO_LOW); + return; + } +#endif + if (txsense) + soutp(UART_MCR, hardware[type].off); + else + soutp(UART_MCR, hardware[type].on); +} + +static void off(void) +{ +#ifdef CONFIG_LIRC_SERIAL_NSLU2 + if (type == LIRC_NSLU2) { + gpio_line_set(NSLU2_LED_GRN, IXP4XX_GPIO_HIGH); + return; + } +#endif + 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 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) { + printk(KERN_WARNING LIRC_DRIVER_NAME ": 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) { + printk(KERN_WARNING LIRC_DRIVER_NAME + ": ignoring spike: %d %d %lx %lx %lx %lx\n", + dcd, sense, + tv.tv_sec, lasttv.tv_sec, + tv.tv_usec, 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)) { + printk(KERN_WARNING LIRC_DRIVER_NAME + ": AIEEEE: your clock just jumped " + "backwards\n"); + printk(KERN_WARNING LIRC_DRIVER_NAME + ": %d %d %lx %lx %lx %lx\n", + dcd, sense, + tv.tv_sec, lasttv.tv_sec, + tv.tv_usec, lasttv.tv_usec); + data = PULSE_MASK; + } else if (deltv > 15) { + data = PULSE_MASK; /* really long time */ + if (!(dcd^sense)) { + /* sanity check */ + printk(KERN_WARNING LIRC_DRIVER_NAME + ": AIEEEE: " + "%d %d %lx %lx %lx %lx\n", + dcd, sense, + tv.tv_sec, lasttv.tv_sec, + tv.tv_usec, 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 */ + printk(KERN_ERR LIRC_DRIVER_NAME ": port existence test " + "failed, cannot continue\n"); + return -EINVAL; + } + + + + /* 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); + +#ifdef CONFIG_LIRC_SERIAL_NSLU2 + if (type == LIRC_NSLU2) { + /* Setup NSLU2 UART */ + + /* Enable UART */ + soutp(UART_IER, sinp(UART_IER) | UART_IE_IXP42X_UUE); + /* Disable Receiver data Time out interrupt */ + soutp(UART_IER, sinp(UART_IER) & ~UART_IE_IXP42X_RTOIE); + /* set out2 = interrupt unmask; off() doesn't set MCR + on NSLU2 */ + soutp(UART_MCR, UART_MCR_RTS|UART_MCR_OUT2); + } +#endif + + /* 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 init_port(void) +{ + int i, nlow, nhigh, result; + + result = request_irq(irq, irq_handler, + (share_irq ? IRQF_SHARED : 0), + LIRC_DRIVER_NAME, (void *)&hardware); + + switch (result) { + case -EBUSY: + printk(KERN_ERR LIRC_DRIVER_NAME ": IRQ %d busy\n", irq); + return -EBUSY; + case -EINVAL: + printk(KERN_ERR LIRC_DRIVER_NAME + ": Bad irq number or handler\n"); + return -EINVAL; + default: + break; + }; + + /* 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))) { + printk(KERN_ERR LIRC_DRIVER_NAME + ": port %04x already in use\n", io); + printk(KERN_WARNING LIRC_DRIVER_NAME + ": use 'setserial /dev/ttySX uart none'\n"); + printk(KERN_WARNING LIRC_DRIVER_NAME + ": or compile the serial port driver as module and\n"); + printk(KERN_WARNING LIRC_DRIVER_NAME + ": make sure this module is loaded first\n"); + return -EBUSY; + } + + if (hardware_init_port() < 0) + return -EINVAL; + + /* 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); + printk(KERN_INFO LIRC_DRIVER_NAME ": auto-detected active " + "%s receiver\n", sense ? "low" : "high"); + } else + printk(KERN_INFO LIRC_DRIVER_NAME ": Manually using active " + "%s receiver\n", sense ? "low" : "high"); + + dprintk("Interrupt %d, port %04x obtained\n", irq, io); + 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 *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 -EBADF; + + 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 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), + (__u32 *) arg); + if (result) + return result; + break; + + case LIRC_SET_SEND_MODE: + if (!(hardware[type].features&LIRC_CAN_SEND_MASK)) + return -ENOIOCTLCMD; + + result = get_user(value, (__u32 *) arg); + if (result) + return result; + /* only LIRC_MODE_PULSE supported */ + if (value != LIRC_MODE_PULSE) + return -ENOSYS; + break; + + case LIRC_GET_LENGTH: + return -ENOSYS; + 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, (__u32 *) arg); + 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, (__u32 *) arg); + 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 __devinit lirc_serial_probe(struct platform_device *dev) +{ + return 0; +} + +static int __devexit lirc_serial_remove(struct platform_device *dev) +{ + return 0; +} + +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; + + if (hardware_init_port() < 0) { + lirc_serial_exit(); + return -EINVAL; + } + + 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 = __devexit_p(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 -ENOMEM; + + 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; + + result = lirc_serial_init(); + if (result) + return 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; +#ifdef CONFIG_LIRC_SERIAL_NSLU2 + case LIRC_NSLU2: + io = io ? io : IRQ_IXP4XX_UART2; + irq = irq ? irq : (IXP4XX_UART2_BASE_VIRT + REG_OFFSET); + iommap = iommap ? iommap : IXP4XX_UART2_BASE_PHYS; + ioshift = ioshift ? ioshift : 2; + break; +#endif + default: + result = -EINVAL; + goto exit_serial_exit; + } + if (!softcarrier) { + switch (type) { + case LIRC_HOMEBREW: + case LIRC_IGOR: +#ifdef CONFIG_LIRC_SERIAL_NSLU2 + case LIRC_NSLU2: +#endif + hardware[type].features &= + ~(LIRC_CAN_SET_SEND_DUTY_CYCLE| + LIRC_CAN_SET_SEND_CARRIER); + break; + } + } + + result = init_port(); + if (result < 0) + goto exit_serial_exit; + driver.features = hardware[type].features; + driver.dev = &lirc_serial_dev->dev; + driver.minor = lirc_register_driver(&driver); + if (driver.minor < 0) { + printk(KERN_ERR LIRC_DRIVER_NAME + ": register_chrdev failed!\n"); + result = -EIO; + goto exit_release; + } + return 0; +exit_release: + release_region(io, 8); +exit_serial_exit: + lirc_serial_exit(); + return result; +} + +static void __exit lirc_serial_exit_module(void) +{ + lirc_serial_exit(); + + free_irq(irq, (void *)&hardware); + + if (iommap != 0) + release_mem_region(iommap, 8 << ioshift); + else + release_region(io, 8); + lirc_unregister_driver(driver.minor); + 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/serial/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, bool, 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 0000000..6903d39 --- /dev/null +++ b/drivers/staging/media/lirc/lirc_sir.c @@ -0,0 +1,1279 @@ +/* + * 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 + */ + +#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 <asm/system.h> +#include <linux/io.h> +#include <asm/irq.h> +#include <linux/fcntl.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 int 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 *buf, size_t count, + loff_t *ppos); +static ssize_t lirc_write(struct file *file, const char *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 *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((void *) buf + n, + (void *) (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 *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) +{ + 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, (__u32 *) arg); + break; + + case LIRC_SET_SEND_MODE: + case LIRC_SET_REC_MODE: + retval = get_user(value, (__u32 *) arg); + break; +#ifdef LIRC_ON_SA1100 + case LIRC_SET_SEND_DUTY_CYCLE: + retval = get_user(value, (__u32 *) arg); + 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, (__u32 *) arg); + 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 int init_chrdev(void) +{ + driver.minor = lirc_register_driver(&driver); + if (driver.minor < 0) { + printk(KERN_ERR LIRC_DRIVER_NAME ": 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) + printk(KERN_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_BITSY + if (machine_is_bitsy()) { + printk(KERN_INFO "Power on IR module\n"); + set_bitsy_egpio(EGPIO_BITSY_IR_ON); + } +#endif +#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 */ + printk(KERN_INFO LIRC_DRIVER_NAME + ": 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_BITSY + if (machine_is_bitsy()) + clr_bitsy_egpio(EGPIO_BITSY_IR_ON); +#endif +#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) { + printk(KERN_ERR LIRC_DRIVER_NAME + ": 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 + printk(KERN_ERR LIRC_DRIVER_NAME + ": IRQ %d already in use.\n", + irq); + return retval; + } +#ifndef LIRC_ON_SA1100 + printk(KERN_INFO LIRC_DRIVER_NAME + ": 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(); + printk(KERN_INFO LIRC_DRIVER_NAME + ": Installed.\n"); + return 0; +} + + +static int __init lirc_sir_init(void) +{ + int retval; + + retval = init_chrdev(); + if (retval < 0) + return retval; + retval = init_lirc_sir(); + if (retval) { + drop_chrdev(); + return retval; + } + return 0; +} + +static void __exit lirc_sir_exit(void) +{ + drop_hardware(); + drop_chrdev(); + drop_port(); + printk(KERN_INFO LIRC_DRIVER_NAME ": 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_ttusbir.c b/drivers/staging/media/lirc/lirc_ttusbir.c new file mode 100644 index 0000000..e4b329b --- /dev/null +++ b/drivers/staging/media/lirc/lirc_ttusbir.c @@ -0,0 +1,395 @@ +/* + * lirc_ttusbir.c + * + * lirc_ttusbir - LIRC device driver for the TechnoTrend USB IR Receiver + * + * Copyright (C) 2007 Stefan Macher <st_maker-lirc@yahoo.de> + * + * This LIRC driver provides access to the TechnoTrend USB IR Receiver. + * The receiver delivers the IR signal as raw sampled true/false data in + * isochronous USB packets each of size 128 byte. + * Currently the driver reduces the sampling rate by factor of 8 as this + * is still more than enough to decode RC-5 - others should be analyzed. + * But the driver does not rely on RC-5 it should be able to decode every + * IR signal that is not too fast. + */ + +/* + * 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/init.h> +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/usb.h> + +#include <media/lirc.h> +#include <media/lirc_dev.h> + +MODULE_DESCRIPTION("TechnoTrend USB IR device driver for LIRC"); +MODULE_AUTHOR("Stefan Macher (st_maker-lirc@yahoo.de)"); +MODULE_LICENSE("GPL"); + +/* #define DEBUG */ +#ifdef DEBUG +#define DPRINTK printk +#else +#define DPRINTK(_x_, a...) +#endif + +/* function declarations */ +static int probe(struct usb_interface *intf, const struct usb_device_id *id); +static void disconnect(struct usb_interface *intf); +static void urb_complete(struct urb *urb); +static int set_use_inc(void *data); +static void set_use_dec(void *data); + +static int num_urbs = 2; +module_param(num_urbs, int, S_IRUGO); +MODULE_PARM_DESC(num_urbs, + "Number of URBs in queue. Try to increase to 4 in case " + "of problems (default: 2; minimum: 2)"); + +/* table of devices that work with this driver */ +static struct usb_device_id device_id_table[] = { + /* TechnoTrend USB IR Receiver */ + { USB_DEVICE(0x0B48, 0x2003) }, + /* Terminating entry */ + { } +}; +MODULE_DEVICE_TABLE(usb, device_id_table); + +/* USB driver definition */ +static struct usb_driver usb_driver = { + .name = "TTUSBIR", + .id_table = &(device_id_table[0]), + .probe = probe, + .disconnect = disconnect, +}; + +/* USB device definition */ +struct ttusbir_device { + struct usb_driver *usb_driver; + struct usb_device *udev; + struct usb_interface *interf; + struct usb_class_driver class_driver; + unsigned int ifnum; /* Interface number to use */ + unsigned int alt_setting; /* alternate setting to use */ + unsigned int endpoint; /* Endpoint to use */ + struct urb **urb; /* num_urb URB pointers*/ + char **buffer; /* 128 byte buffer for each URB */ + struct lirc_buffer rbuf; /* Buffer towards LIRC */ + struct lirc_driver driver; + int minor; + int last_pulse; /* remembers if last received byte was pulse or space */ + int last_num; /* remembers how many last bytes appeared */ + int opened; +}; + +/*** LIRC specific functions ***/ +static int set_use_inc(void *data) +{ + int i, retval; + struct ttusbir_device *ttusbir = data; + + DPRINTK("Sending first URBs\n"); + /* @TODO Do I need to check if I am already opened */ + ttusbir->opened = 1; + + for (i = 0; i < num_urbs; i++) { + retval = usb_submit_urb(ttusbir->urb[i], GFP_KERNEL); + if (retval) { + err("%s: usb_submit_urb failed on urb %d", + __func__, i); + return retval; + } + } + return 0; +} + +static void set_use_dec(void *data) +{ + struct ttusbir_device *ttusbir = data; + + DPRINTK("Device closed\n"); + + ttusbir->opened = 0; +} + +/*** USB specific functions ***/ + +/* + * This mapping table is used to do a very simple filtering of the + * input signal. + * For a value with at least 4 bits set it returns 0xFF otherwise + * 0x00. For faster IR signals this can not be used. But for RC-5 we + * still have about 14 samples per pulse/space, i.e. we sample with 14 + * times higher frequency than the signal frequency + */ +const unsigned char map_table[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0x00, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0x00, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0x00, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0x00, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0x00, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0x00, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF +}; + +static void urb_complete(struct urb *urb) +{ + struct ttusbir_device *ttusbir; + unsigned char *buf; + int i; + int l; + + ttusbir = urb->context; + + if (!ttusbir->opened) + return; + + buf = (unsigned char *)urb->transfer_buffer; + + for (i = 0; i < 128; i++) { + /* Here we do the filtering and some kind of down sampling */ + buf[i] = ~map_table[buf[i]]; + if (ttusbir->last_pulse == buf[i]) { + if (ttusbir->last_num < PULSE_MASK/63) + ttusbir->last_num++; + /* + * else we are in a idle period and do not need to + * increment any longer + */ + } else { + l = ttusbir->last_num * 62; /* about 62 = us/byte */ + if (ttusbir->last_pulse) /* pulse or space? */ + l |= PULSE_BIT; + if (!lirc_buffer_full(&ttusbir->rbuf)) { + lirc_buffer_write(&ttusbir->rbuf, (void *)&l); + wake_up_interruptible(&ttusbir->rbuf.wait_poll); + } + ttusbir->last_num = 0; + ttusbir->last_pulse = buf[i]; + } + } + usb_submit_urb(urb, GFP_ATOMIC); /* keep data rolling :-) */ +} + +/* + * Called whenever the USB subsystem thinks we could be the right driver + * to handle this device + */ +static int probe(struct usb_interface *intf, const struct usb_device_id *id) +{ + int alt_set, endp; + int found = 0; + int i, j; + int struct_size; + struct usb_host_interface *host_interf; + struct usb_interface_descriptor *interf_desc; + struct usb_host_endpoint *host_endpoint; + struct ttusbir_device *ttusbir; + + DPRINTK("Module ttusbir probe\n"); + + /* To reduce memory fragmentation we use only one allocation */ + struct_size = sizeof(struct ttusbir_device) + + (sizeof(struct urb *) * num_urbs) + + (sizeof(char *) * num_urbs) + + (num_urbs * 128); + ttusbir = kzalloc(struct_size, GFP_KERNEL); + if (!ttusbir) + return -ENOMEM; + + ttusbir->urb = (struct urb **)((char *)ttusbir + + sizeof(struct ttusbir_device)); + ttusbir->buffer = (char **)((char *)ttusbir->urb + + (sizeof(struct urb *) * num_urbs)); + for (i = 0; i < num_urbs; i++) + ttusbir->buffer[i] = (char *)ttusbir->buffer + + (sizeof(char *)*num_urbs) + (i * 128); + + ttusbir->usb_driver = &usb_driver; + ttusbir->alt_setting = -1; + /* @TODO check if error can be returned */ + ttusbir->udev = usb_get_dev(interface_to_usbdev(intf)); + ttusbir->interf = intf; + ttusbir->last_pulse = 0x00; + ttusbir->last_num = 0; + + /* + * Now look for interface setting we can handle + * We are searching for the alt setting where end point + * 0x82 has max packet size 16 + */ + for (alt_set = 0; alt_set < intf->num_altsetting && !found; alt_set++) { + host_interf = &intf->altsetting[alt_set]; + interf_desc = &host_interf->desc; + for (endp = 0; endp < interf_desc->bNumEndpoints; endp++) { + host_endpoint = &host_interf->endpoint[endp]; + if ((host_endpoint->desc.bEndpointAddress == 0x82) && + (host_endpoint->desc.wMaxPacketSize == 0x10)) { + ttusbir->alt_setting = alt_set; + ttusbir->endpoint = endp; + found = 1; + break; + } + } + } + if (ttusbir->alt_setting != -1) + DPRINTK("alt setting: %d\n", ttusbir->alt_setting); + else { + err("Could not find alternate setting\n"); + kfree(ttusbir); + return -EINVAL; + } + + /* OK lets setup this interface setting */ + usb_set_interface(ttusbir->udev, 0, ttusbir->alt_setting); + + /* Store device info in interface structure */ + usb_set_intfdata(intf, ttusbir); + + /* Register as a LIRC driver */ + if (lirc_buffer_init(&ttusbir->rbuf, sizeof(int), 256) < 0) { + err("Could not get memory for LIRC data buffer\n"); + usb_set_intfdata(intf, NULL); + kfree(ttusbir); + return -ENOMEM; + } + strcpy(ttusbir->driver.name, "TTUSBIR"); + ttusbir->driver.minor = -1; + ttusbir->driver.code_length = 1; + ttusbir->driver.sample_rate = 0; + ttusbir->driver.data = ttusbir; + ttusbir->driver.add_to_buf = NULL; + ttusbir->driver.rbuf = &ttusbir->rbuf; + ttusbir->driver.set_use_inc = set_use_inc; + ttusbir->driver.set_use_dec = set_use_dec; + ttusbir->driver.dev = &intf->dev; + ttusbir->driver.owner = THIS_MODULE; + ttusbir->driver.features = LIRC_CAN_REC_MODE2; + ttusbir->minor = lirc_register_driver(&ttusbir->driver); + if (ttusbir->minor < 0) { + err("Error registering as LIRC driver\n"); + usb_set_intfdata(intf, NULL); + lirc_buffer_free(&ttusbir->rbuf); + kfree(ttusbir); + return -EIO; + } + + /* Allocate and setup the URB that we will use to talk to the device */ + for (i = 0; i < num_urbs; i++) { + ttusbir->urb[i] = usb_alloc_urb(8, GFP_KERNEL); + if (!ttusbir->urb[i]) { + err("Could not allocate memory for the URB\n"); + for (j = i - 1; j >= 0; j--) + kfree(ttusbir->urb[j]); + lirc_buffer_free(&ttusbir->rbuf); + lirc_unregister_driver(ttusbir->minor); + kfree(ttusbir); + usb_set_intfdata(intf, NULL); + return -ENOMEM; + } + ttusbir->urb[i]->dev = ttusbir->udev; + ttusbir->urb[i]->context = ttusbir; + ttusbir->urb[i]->pipe = usb_rcvisocpipe(ttusbir->udev, + ttusbir->endpoint); + ttusbir->urb[i]->interval = 1; + ttusbir->urb[i]->transfer_flags = URB_ISO_ASAP; + ttusbir->urb[i]->transfer_buffer = &ttusbir->buffer[i][0]; + ttusbir->urb[i]->complete = urb_complete; + ttusbir->urb[i]->number_of_packets = 8; + ttusbir->urb[i]->transfer_buffer_length = 128; + for (j = 0; j < 8; j++) { + ttusbir->urb[i]->iso_frame_desc[j].offset = j*16; + ttusbir->urb[i]->iso_frame_desc[j].length = 16; + } + } + return 0; +} + +/** + * Called when the driver is unloaded or the device is unplugged + */ +static void disconnect(struct usb_interface *intf) +{ + int i; + struct ttusbir_device *ttusbir; + + DPRINTK("Module ttusbir disconnect\n"); + + ttusbir = (struct ttusbir_device *) usb_get_intfdata(intf); + usb_set_intfdata(intf, NULL); + lirc_unregister_driver(ttusbir->minor); + DPRINTK("unregistered\n"); + + for (i = 0; i < num_urbs; i++) { + usb_kill_urb(ttusbir->urb[i]); + usb_free_urb(ttusbir->urb[i]); + } + DPRINTK("URBs killed\n"); + lirc_buffer_free(&ttusbir->rbuf); + kfree(ttusbir); +} + +static int ttusbir_init_module(void) +{ + int result; + + DPRINTK(KERN_DEBUG "Module ttusbir init\n"); + + /* register this driver with the USB subsystem */ + result = usb_register(&usb_driver); + if (result) + err("usb_register failed. Error number %d", result); + return result; +} + +static void ttusbir_exit_module(void) +{ + printk(KERN_DEBUG "Module ttusbir exit\n"); + usb_deregister(&usb_driver); +} + +module_init(ttusbir_init_module); +module_exit(ttusbir_exit_module); diff --git a/drivers/staging/media/lirc/lirc_zilog.c b/drivers/staging/media/lirc/lirc_zilog.c new file mode 100644 index 0000000..0302d82 --- /dev/null +++ b/drivers/staging/media/lirc/lirc_zilog.c @@ -0,0 +1,1676 @@ +/* + * 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> + +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 int debug; /* debug output */ +static int 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("%02x %02x %02x %02x %02x", + buf[0], buf[1], buf[2], buf[3], buf[4]); + 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 *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[rbuf->chunk_size]; + m = lirc_buffer_read(rbuf, buf); + if (m == rbuf->chunk_size) { + ret = copy_to_user((void *)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 *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; + int result; + unsigned long mode, features; + + features = ir->l.features; + + switch (cmd) { + case LIRC_GET_LENGTH: + result = put_user((unsigned long)13, + (unsigned long *)arg); + break; + case LIRC_GET_FEATURES: + result = put_user(features, (unsigned long *) arg); + break; + case LIRC_GET_REC_MODE: + if (!(features&LIRC_CAN_REC_MASK)) + return -ENOSYS; + + result = put_user(LIRC_REC2MODE + (features&LIRC_CAN_REC_MASK), + (unsigned long *)arg); + break; + case LIRC_SET_REC_MODE: + if (!(features&LIRC_CAN_REC_MASK)) + return -ENOSYS; + + result = get_user(mode, (unsigned long *)arg); + 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, (unsigned long *) arg); + break; + case LIRC_SET_SEND_MODE: + if (!(features&LIRC_CAN_SEND_MASK)) + return -ENOSYS; + + result = get_user(mode, (unsigned long *) arg); + 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/solo6x10/Kconfig b/drivers/staging/media/solo6x10/Kconfig new file mode 100644 index 0000000..03dcac4 --- /dev/null +++ b/drivers/staging/media/solo6x10/Kconfig @@ -0,0 +1,8 @@ +config SOLO6X10 + tristate "Softlogic 6x10 MPEG codec cards" + depends on PCI && VIDEO_DEV && SND && I2C + select VIDEOBUF_DMA_SG + select SND_PCM + ---help--- + This driver supports the Softlogic based MPEG-4 and h.264 codec + codec cards. diff --git a/drivers/staging/media/solo6x10/Makefile b/drivers/staging/media/solo6x10/Makefile new file mode 100644 index 0000000..72816cf --- /dev/null +++ b/drivers/staging/media/solo6x10/Makefile @@ -0,0 +1,3 @@ +solo6x10-y := core.o i2c.o p2m.o v4l2.o tw28.o gpio.o disp.o enc.o v4l2-enc.o g723.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 0000000..7e6c4fa --- /dev/null +++ b/drivers/staging/media/solo6x10/TODO @@ -0,0 +1,24 @@ +TODO (staging => main): + + * Motion detection flags need to be moved to v4l2 + * Some private CIDs need to be moved to v4l2 + +TODO (general): + + * 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 + * switch between 4 frames/irq to 1 when using mjpeg (and then back + when not) + * implement a CID control for motion areas/thresholds + * implement CID controls for mozaic areas + * allow for higher level of interval (for < 1 fps) + * sound: + - implement playback via external sound jack + - implement loopback of external sound jack with incoming audio? + - implement pause/resume + +Plase send patches to Greg Kroah-Hartman <greg@kroah.com> and Cc Ben Collins +<bcollins@bluecherry.net> diff --git a/drivers/staging/media/solo6x10/core.c b/drivers/staging/media/solo6x10/core.c new file mode 100644 index 0000000..f974f64 --- /dev/null +++ b/drivers/staging/media/solo6x10/core.c @@ -0,0 +1,332 @@ +/* + * Copyright (C) 2010 Bluecherry, LLC www.bluecherrydvr.com + * Copyright (C) 2010 Ben Collins <bcollins@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/slab.h> +#include <linux/videodev2.h> +#include "solo6x10.h" +#include "tw28.h" + +MODULE_DESCRIPTION("Softlogic 6x10 MP4/H.264 Encoder/Decoder V4L2/ALSA Driver"); +MODULE_AUTHOR("Ben Collins <bcollins@bluecherry.net>"); +MODULE_VERSION(SOLO6X10_VERSION); +MODULE_LICENSE("GPL"); + +void solo_irq_on(struct solo_dev *solo_dev, u32 mask) +{ + solo_dev->irq_mask |= mask; + solo_reg_write(solo_dev, SOLO_IRQ_ENABLE, solo_dev->irq_mask); +} + +void solo_irq_off(struct solo_dev *solo_dev, u32 mask) +{ + solo_dev->irq_mask &= ~mask; + solo_reg_write(solo_dev, SOLO_IRQ_ENABLE, solo_dev->irq_mask); +} + +/* XXX We should check the return value of the sub-device ISR's */ +static irqreturn_t 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) { + u32 err = solo_reg_read(solo_dev, SOLO_PCI_ERR); + solo_p2m_error_isr(solo_dev, err); + solo_reg_write(solo_dev, SOLO_IRQ_STAT, SOLO_IRQ_PCI_ERR); + } + + for (i = 0; i < SOLO_NR_P2M; i++) + if (status & SOLO_IRQ_P2M(i)) + solo_p2m_isr(solo_dev, i); + + if (status & SOLO_IRQ_IIC) + solo_i2c_isr(solo_dev); + + if (status & SOLO_IRQ_VIDEO_IN) + solo_video_in_isr(solo_dev); + + /* Call this first so enc gets detected flag set */ + if (status & SOLO_IRQ_MOTION) + solo_motion_isr(solo_dev); + + if (status & SOLO_IRQ_ENCODER) + solo_enc_v4l2_isr(solo_dev); + + if (status & SOLO_IRQ_G723) + solo_g723_isr(solo_dev); + + return IRQ_HANDLED; +} + +static void free_solo_dev(struct solo_dev *solo_dev) +{ + struct pci_dev *pdev; + + if (!solo_dev) + return; + + pdev = solo_dev->pdev; + + /* If we never initialized the PCI device, then nothing else + * below here needs cleanup */ + if (!pdev) { + kfree(solo_dev); + return; + } + + /* Bring down the sub-devices first */ + solo_g723_exit(solo_dev); + solo_enc_v4l2_exit(solo_dev); + solo_enc_exit(solo_dev); + solo_v4l2_exit(solo_dev); + solo_disp_exit(solo_dev); + solo_gpio_exit(solo_dev); + solo_p2m_exit(solo_dev); + solo_i2c_exit(solo_dev); + + /* Now cleanup the PCI device */ + if (solo_dev->reg_base) { + solo_irq_off(solo_dev, ~0); + pci_iounmap(pdev, solo_dev->reg_base); + free_irq(pdev->irq, solo_dev); + } + + pci_release_regions(pdev); + pci_disable_device(pdev); + pci_set_drvdata(pdev, NULL); + + kfree(solo_dev); +} + +static int __devinit solo_pci_probe(struct pci_dev *pdev, + const struct pci_device_id *id) +{ + struct solo_dev *solo_dev; + int ret; + int sdram; + u8 chip_id; + u32 reg; + + solo_dev = kzalloc(sizeof(*solo_dev), GFP_KERNEL); + if (solo_dev == NULL) + return -ENOMEM; + + solo_dev->pdev = pdev; + spin_lock_init(&solo_dev->reg_io_lock); + pci_set_drvdata(pdev, solo_dev); + + ret = pci_enable_device(pdev); + if (ret) + goto fail_probe; + + pci_set_master(pdev); + + 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, " + "defaulting to 4 channels\n", + chip_id); + case 5: + solo_dev->nr_chans = 4; + solo_dev->nr_ext = 1; + } + + solo_dev->flags = id->driver_data; + + /* Disable all interrupts to start */ + solo_irq_off(solo_dev, ~0); + + reg = SOLO_SYS_CFG_SDRAM64BIT; + /* Initial global settings */ + if (!(solo_dev->flags & FLAGS_6110)) + reg |= SOLO6010_SYS_CFG_INPUTDIV(25) | + SOLO6010_SYS_CFG_FEEDBACKDIV((SOLO_CLOCK_MHZ * 2) - 2) | + SOLO6010_SYS_CFG_OUTDIV(3); + solo_reg_write(solo_dev, SOLO_SYS_CFG, reg); + + if (solo_dev->flags & FLAGS_6110) { + u32 sys_clock_MHz = SOLO_CLOCK_MHZ; + u32 pll_DIVQ; + u32 pll_DIVF; + + if (sys_clock_MHz < 125) { + pll_DIVQ = 3; + pll_DIVF = (sys_clock_MHz * 4) / 3; + } else { + pll_DIVQ = 2; + pll_DIVF = (sys_clock_MHz * 2) / 3; + } + + solo_reg_write(solo_dev, SOLO6110_PLL_CONFIG, + SOLO6110_PLL_RANGE_5_10MHZ | + SOLO6110_PLL_DIVR(9) | + SOLO6110_PLL_DIVQ_EXP(pll_DIVQ) | + SOLO6110_PLL_DIVF(pll_DIVF) | SOLO6110_PLL_FSEN); + mdelay(1); // PLL Locking time (1ms) + + solo_reg_write(solo_dev, SOLO_DMA_CTRL1, 3 << 8); /* ? */ + } else + solo_reg_write(solo_dev, SOLO_DMA_CTRL1, 1 << 8); /* ? */ + + solo_reg_write(solo_dev, SOLO_TIMER_CLOCK_NUM, SOLO_CLOCK_MHZ - 1); + + /* PLL locking time of 1ms */ + mdelay(1); + + ret = request_irq(pdev->irq, 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 */ + sdram = (solo_dev->nr_chans >= 8) ? 2 : 1; + solo_reg_write(solo_dev, SOLO_DMA_CTRL, + SOLO_DMA_CTRL_REFRESH_CYCLE(1) | + SOLO_DMA_CTRL_SDRAM_SIZE(sdram) | + SOLO_DMA_CTRL_SDRAM_CLK_INVERT | + SOLO_DMA_CTRL_READ_CLK_SELECT | + SOLO_DMA_CTRL_LATENCY(1)); + + 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); + if (ret) + goto fail_probe; + + ret = solo_enc_init(solo_dev); + if (ret) + goto fail_probe; + + ret = solo_enc_v4l2_init(solo_dev); + if (ret) + goto fail_probe; + + ret = solo_g723_init(solo_dev); + if (ret) + goto fail_probe; + + return 0; + +fail_probe: + free_solo_dev(solo_dev); + return ret; +} + +static void __devexit solo_pci_remove(struct pci_dev *pdev) +{ + struct solo_dev *solo_dev = pci_get_drvdata(pdev); + + free_solo_dev(solo_dev); +} + +static struct pci_device_id solo_id_table[] = { + /* 6010 based cards */ + {PCI_DEVICE(PCI_VENDOR_ID_SOFTLOGIC, PCI_DEVICE_ID_SOLO6010)}, + {PCI_DEVICE(PCI_VENDOR_ID_SOFTLOGIC, PCI_DEVICE_ID_SOLO6110), + .driver_data = FLAGS_6110}, + {PCI_DEVICE(PCI_VENDOR_ID_BLUECHERRY, PCI_DEVICE_ID_NEUSOLO_4)}, + {PCI_DEVICE(PCI_VENDOR_ID_BLUECHERRY, PCI_DEVICE_ID_NEUSOLO_9)}, + {PCI_DEVICE(PCI_VENDOR_ID_BLUECHERRY, PCI_DEVICE_ID_NEUSOLO_16)}, + {PCI_DEVICE(PCI_VENDOR_ID_BLUECHERRY, PCI_DEVICE_ID_BC_SOLO_4)}, + {PCI_DEVICE(PCI_VENDOR_ID_BLUECHERRY, PCI_DEVICE_ID_BC_SOLO_9)}, + {PCI_DEVICE(PCI_VENDOR_ID_BLUECHERRY, PCI_DEVICE_ID_BC_SOLO_16)}, + /* 6110 based cards */ + {PCI_DEVICE(PCI_VENDOR_ID_BLUECHERRY, PCI_DEVICE_ID_BC_6110_4)}, + {PCI_DEVICE(PCI_VENDOR_ID_BLUECHERRY, PCI_DEVICE_ID_BC_6110_8)}, + {PCI_DEVICE(PCI_VENDOR_ID_BLUECHERRY, PCI_DEVICE_ID_BC_6110_16)}, + {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, +}; + +static int __init solo_module_init(void) +{ + return pci_register_driver(&solo_pci_driver); +} + +static void __exit solo_module_exit(void) +{ + pci_unregister_driver(&solo_pci_driver); +} + +module_init(solo_module_init); +module_exit(solo_module_exit); diff --git a/drivers/staging/media/solo6x10/disp.c b/drivers/staging/media/solo6x10/disp.c new file mode 100644 index 0000000..884c0eb --- /dev/null +++ b/drivers/staging/media/solo6x10/disp.c @@ -0,0 +1,270 @@ +/* + * Copyright (C) 2010 Bluecherry, LLC www.bluecherrydvr.com + * Copyright (C) 2010 Ben Collins <bcollins@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 512 +#define SOLO_MOT_FLAG_AREA (SOLO_MOT_FLAG_SIZE * 32) + +static unsigned video_type; +module_param(video_type, uint, 0644); +MODULE_PARM_DESC(video_type, "video_type (0 = NTSC/Default, 1 = PAL)"); + +static void solo_vin_config(struct 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)); + + solo_reg_write(solo_dev, SOLO_VI_FMT_CFG, 0); + solo_reg_write(solo_dev, SOLO_VI_PAGE_SW, 2); + + if (solo_dev->video_type == SOLO_VO_FMT_TYPE_NTSC) { + solo_reg_write(solo_dev, SOLO_VI_PB_CONFIG, + SOLO_VI_PB_USER_MODE); + solo_reg_write(solo_dev, SOLO_VI_PB_RANGE_HV, + SOLO_VI_PB_HSIZE(858) | SOLO_VI_PB_VSIZE(246)); + solo_reg_write(solo_dev, SOLO_VI_PB_ACT_V, + SOLO_VI_PB_VSTART(4) | + SOLO_VI_PB_VSTOP(4 + 240)); + } else { + solo_reg_write(solo_dev, SOLO_VI_PB_CONFIG, + SOLO_VI_PB_USER_MODE | SOLO_VI_PB_PAL); + solo_reg_write(solo_dev, SOLO_VI_PB_RANGE_HV, + SOLO_VI_PB_HSIZE(864) | SOLO_VI_PB_VSIZE(294)); + solo_reg_write(solo_dev, SOLO_VI_PB_ACT_V, + SOLO_VI_PB_VSTART(4) | + SOLO_VI_PB_VSTOP(4 + 288)); + } + solo_reg_write(solo_dev, SOLO_VI_PB_ACT_H, SOLO_VI_PB_HSTART(16) | + SOLO_VI_PB_HSTOP(16 + 720)); +} + +static void solo_disp_config(struct solo_dev *solo_dev) +{ + solo_dev->vout_hstart = 6; + solo_dev->vout_vstart = 8; + + solo_reg_write(solo_dev, SOLO_VO_BORDER_LINE_COLOR, + (0xa0 << 24) | (0x88 << 16) | (0xa0 << 8) | 0x88); + solo_reg_write(solo_dev, SOLO_VO_BORDER_FILL_COLOR, + (0x10 << 24) | (0x8f << 16) | (0x10 << 8) | 0x8f); + solo_reg_write(solo_dev, SOLO_VO_BKG_COLOR, + (16 << 24) | (128 << 16) | (16 << 8) | 128); + + solo_reg_write(solo_dev, SOLO_VO_FMT_ENC, + solo_dev->video_type | + SOLO_VO_USER_COLOR_SET_NAV | + SOLO_VO_NA_COLOR_Y(0) | + SOLO_VO_NA_COLOR_CB(0) | + SOLO_VO_NA_COLOR_CR(0)); + + solo_reg_write(solo_dev, SOLO_VO_ACT_H, + SOLO_VO_H_START(solo_dev->vout_hstart) | + SOLO_VO_H_STOP(solo_dev->vout_hstart + + solo_dev->video_hsize)); + + solo_reg_write(solo_dev, SOLO_VO_ACT_V, + SOLO_VO_V_START(solo_dev->vout_vstart) | + SOLO_VO_V_STOP(solo_dev->vout_vstart + + solo_dev->video_vsize)); + + solo_reg_write(solo_dev, SOLO_VO_RANGE_HV, + SOLO_VO_H_LEN(solo_dev->video_hsize) | + SOLO_VO_V_LEN(solo_dev->video_vsize)); + + solo_reg_write(solo_dev, SOLO_VI_WIN_SW, 5); + + solo_reg_write(solo_dev, SOLO_VO_DISP_CTRL, SOLO_VO_DISP_ON | + SOLO_VO_DISP_ERASE_COUNT(8) | + SOLO_VO_DISP_BASE(SOLO_DISP_EXT_ADDR)); + + solo_reg_write(solo_dev, SOLO_VO_DISP_ERASE, SOLO_VO_DISP_ERASE_ON); + + /* Enable channels we support */ + solo_reg_write(solo_dev, SOLO_VI_CH_ENA, (1 << solo_dev->nr_chans) - 1); + + /* Disable the watchdog */ + solo_reg_write(solo_dev, SOLO_WATCHDOG, 0); +} + +static int solo_dma_vin_region(struct solo_dev *solo_dev, u32 off, + u16 val, int reg_size) +{ + u16 buf[64]; + int i; + int ret = 0; + + for (i = 0; i < sizeof(buf) >> 1; i++) + buf[i] = val; + + for (i = 0; i < reg_size; i += sizeof(buf)) + ret |= solo_p2m_dma(solo_dev, SOLO_P2M_DMA_ID_VIN, 1, buf, + SOLO_MOTION_EXT_ADDR(solo_dev) + off + i, + sizeof(buf)); + + return ret; +} + +void solo_set_motion_threshold(struct solo_dev *solo_dev, u8 ch, u16 val) +{ + if (ch > solo_dev->nr_chans) + return; + + solo_dma_vin_region(solo_dev, SOLO_MOT_FLAG_AREA + + (ch * SOLO_MOT_THRESH_SIZE * 2), + val, SOLO_MOT_THRESH_REAL); +} + +/* First 8k is motion flag (512 bytes * 16). Following that is an 8k+8k + * threshold and working table for each channel. Atleast that's what the + * spec says. However, this code (take from rdk) has some mystery 8k + * block right after the flag area, before the first thresh table. */ +static void solo_motion_config(struct 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 + + SOLO_MOT_THRESH_SIZE + + (i * SOLO_MOT_THRESH_SIZE * 2), + 0x0000, SOLO_MOT_THRESH_REAL); + + /* Set default threshold table */ + solo_set_motion_threshold(solo_dev, i, SOLO_DEF_MOT_THRESH); + } + + /* Default motion settings */ + solo_reg_write(solo_dev, SOLO_VI_MOT_ADR, SOLO_VI_MOTION_EN(0) | + (SOLO_MOTION_EXT_ADDR(solo_dev) >> 16)); + solo_reg_write(solo_dev, SOLO_VI_MOT_CTRL, + SOLO_VI_MOTION_FRAME_COUNT(3) | + SOLO_VI_MOTION_SAMPLE_LENGTH(solo_dev->video_hsize / 16) + | /* SOLO_VI_MOTION_INTR_START_STOP | */ + SOLO_VI_MOTION_SAMPLE_COUNT(10)); + + solo_reg_write(solo_dev, SOLO_VI_MOTION_BORDER, 0); + solo_reg_write(solo_dev, SOLO_VI_MOTION_BAR, 0); +} + +int solo_disp_init(struct solo_dev *solo_dev) +{ + int i; + + solo_dev->video_hsize = 704; + if (video_type == 0) { + solo_dev->video_type = SOLO_VO_FMT_TYPE_NTSC; + solo_dev->video_vsize = 240; + solo_dev->fps = 30; + } else { + solo_dev->video_type = SOLO_VO_FMT_TYPE_PAL; + solo_dev->video_vsize = 288; + solo_dev->fps = 25; + } + + solo_vin_config(solo_dev); + solo_motion_config(solo_dev); + solo_disp_config(solo_dev); + + for (i = 0; i < solo_dev->nr_chans; i++) + solo_reg_write(solo_dev, SOLO_VI_WIN_ON(i), 1); + + return 0; +} + +void solo_disp_exit(struct solo_dev *solo_dev) +{ + int i; + + solo_irq_off(solo_dev, SOLO_IRQ_MOTION); + + solo_reg_write(solo_dev, SOLO_VO_DISP_CTRL, 0); + solo_reg_write(solo_dev, SOLO_VO_ZOOM_CTRL, 0); + solo_reg_write(solo_dev, SOLO_VO_FREEZE_CTRL, 0); + + for (i = 0; i < solo_dev->nr_chans; i++) { + solo_reg_write(solo_dev, SOLO_VI_WIN_CTRL0(i), 0); + solo_reg_write(solo_dev, SOLO_VI_WIN_CTRL1(i), 0); + solo_reg_write(solo_dev, SOLO_VI_WIN_ON(i), 0); + } + + /* Set default border */ + for (i = 0; i < 5; i++) + solo_reg_write(solo_dev, SOLO_VO_BORDER_X(i), 0); + + for (i = 0; i < 5; i++) + solo_reg_write(solo_dev, SOLO_VO_BORDER_Y(i), 0); + + solo_reg_write(solo_dev, SOLO_VO_BORDER_LINE_MASK, 0); + solo_reg_write(solo_dev, SOLO_VO_BORDER_FILL_MASK, 0); + + solo_reg_write(solo_dev, SOLO_VO_RECTANGLE_CTRL(0), 0); + solo_reg_write(solo_dev, SOLO_VO_RECTANGLE_START(0), 0); + solo_reg_write(solo_dev, SOLO_VO_RECTANGLE_STOP(0), 0); + + solo_reg_write(solo_dev, SOLO_VO_RECTANGLE_CTRL(1), 0); + solo_reg_write(solo_dev, SOLO_VO_RECTANGLE_START(1), 0); + solo_reg_write(solo_dev, SOLO_VO_RECTANGLE_STOP(1), 0); +} diff --git a/drivers/staging/media/solo6x10/enc.c b/drivers/staging/media/solo6x10/enc.c new file mode 100644 index 0000000..de50259 --- /dev/null +++ b/drivers/staging/media/solo6x10/enc.c @@ -0,0 +1,238 @@ +/* + * Copyright (C) 2010 Bluecherry, LLC www.bluecherrydvr.com + * Copyright (C) 2010 Ben Collins <bcollins@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/slab.h> +#include "solo6x10.h" +#include "osd-font.h" + +#define CAPTURE_MAX_BANDWIDTH 32 /* D1 4channel (D1 == 4) */ +#define OSG_BUFFER_SIZE 1024 + +#define VI_PROG_HSIZE (1280 - 16) +#define VI_PROG_VSIZE (1024 - 16) + +static void solo_capture_config(struct solo_dev *solo_dev) +{ + int i, j; + unsigned long height; + unsigned long width; + unsigned char *buf; + + solo_reg_write(solo_dev, SOLO_CAP_BASE, + SOLO_CAP_MAX_PAGE(SOLO_CAP_EXT_MAX_PAGE * + solo_dev->nr_chans) | + SOLO_CAP_BASE_ADDR(SOLO_CAP_EXT_ADDR(solo_dev) >> 16)); + solo_reg_write(solo_dev, SOLO_CAP_BTW, + (1 << 17) | SOLO_CAP_PROG_BANDWIDTH(2) | + SOLO_CAP_MAX_BANDWIDTH(CAPTURE_MAX_BANDWIDTH)); + + /* Set scale 1, 9 dimension */ + width = solo_dev->video_hsize; + height = solo_dev->video_vsize; + solo_reg_write(solo_dev, SOLO_DIM_SCALE1, + SOLO_DIM_H_MB_NUM(width / 16) | + SOLO_DIM_V_MB_NUM_FRAME(height / 8) | + SOLO_DIM_V_MB_NUM_FIELD(height / 16)); + + /* Set scale 2, 10 dimension */ + width = solo_dev->video_hsize / 2; + height = solo_dev->video_vsize; + solo_reg_write(solo_dev, SOLO_DIM_SCALE2, + SOLO_DIM_H_MB_NUM(width / 16) | + SOLO_DIM_V_MB_NUM_FRAME(height / 8) | + SOLO_DIM_V_MB_NUM_FIELD(height / 16)); + + /* Set scale 3, 11 dimension */ + width = solo_dev->video_hsize / 2; + height = solo_dev->video_vsize / 2; + solo_reg_write(solo_dev, SOLO_DIM_SCALE3, + SOLO_DIM_H_MB_NUM(width / 16) | + SOLO_DIM_V_MB_NUM_FRAME(height / 8) | + SOLO_DIM_V_MB_NUM_FIELD(height / 16)); + + /* Set scale 4, 12 dimension */ + width = solo_dev->video_hsize / 3; + height = solo_dev->video_vsize / 3; + solo_reg_write(solo_dev, SOLO_DIM_SCALE4, + SOLO_DIM_H_MB_NUM(width / 16) | + SOLO_DIM_V_MB_NUM_FRAME(height / 8) | + SOLO_DIM_V_MB_NUM_FIELD(height / 16)); + + /* Set scale 5, 13 dimension */ + width = solo_dev->video_hsize / 4; + height = solo_dev->video_vsize / 2; + solo_reg_write(solo_dev, SOLO_DIM_SCALE5, + SOLO_DIM_H_MB_NUM(width / 16) | + SOLO_DIM_V_MB_NUM_FRAME(height / 8) | + SOLO_DIM_V_MB_NUM_FIELD(height / 16)); + + /* Progressive */ + width = VI_PROG_HSIZE; + height = VI_PROG_VSIZE; + solo_reg_write(solo_dev, SOLO_DIM_PROG, + SOLO_DIM_H_MB_NUM(width / 16) | + SOLO_DIM_V_MB_NUM_FRAME(height / 16) | + SOLO_DIM_V_MB_NUM_FIELD(height / 16)); + + /* Clear OSD */ + solo_reg_write(solo_dev, SOLO_VE_OSD_CH, 0); + solo_reg_write(solo_dev, SOLO_VE_OSD_BASE, SOLO_EOSD_EXT_ADDR >> 16); + solo_reg_write(solo_dev, SOLO_VE_OSD_CLR, + 0xF0 << 16 | 0x80 << 8 | 0x80); + solo_reg_write(solo_dev, SOLO_VE_OSD_OPT, 0); + + /* Clear OSG buffer */ + buf = kzalloc(OSG_BUFFER_SIZE, GFP_KERNEL); + if (!buf) + return; + + for (i = 0; i < solo_dev->nr_chans; i++) { + for (j = 0; j < SOLO_EOSD_EXT_SIZE; j += OSG_BUFFER_SIZE) { + solo_p2m_dma(solo_dev, SOLO_P2M_DMA_ID_MP4E, 1, buf, + SOLO_EOSD_EXT_ADDR + + (i * SOLO_EOSD_EXT_SIZE) + j, + OSG_BUFFER_SIZE); + } + } + kfree(buf); +} + +int solo_osd_print(struct solo_enc_dev *solo_enc) +{ + struct solo_dev *solo_dev = solo_enc->solo_dev; + char *str = solo_enc->osd_text; + u8 *buf; + u32 reg = solo_reg_read(solo_dev, SOLO_VE_OSD_CH); + int len = strlen(str); + int i, j; + int x = 1, y = 1; + + if (len == 0) { + reg &= ~(1 << solo_enc->ch); + solo_reg_write(solo_dev, SOLO_VE_OSD_CH, reg); + return 0; + } + + buf = kzalloc(SOLO_EOSD_EXT_SIZE, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + for (i = 0; i < len; i++) { + for (j = 0; j < 16; j++) { + buf[(j*2) + (i%2) + ((x + (i/2)) * 32) + (y * 2048)] = + (solo_osd_font[(str[i] * 4) + (j / 4)] + >> ((3 - (j % 4)) * 8)) & 0xff; + } + } + + solo_p2m_dma(solo_dev, 0, 1, buf, SOLO_EOSD_EXT_ADDR + + (solo_enc->ch * SOLO_EOSD_EXT_SIZE), SOLO_EOSD_EXT_SIZE); + reg |= (1 << solo_enc->ch); + solo_reg_write(solo_dev, SOLO_VE_OSD_CH, reg); + + kfree(buf); + + return 0; +} + +static void solo_jpeg_config(struct solo_dev *solo_dev) +{ + u32 reg; + if (solo_dev->flags & FLAGS_6110) + reg = (4 << 24) | (3 << 16) | (2 << 8) | (1 << 0); + else + reg = (2 << 24) | (2 << 16) | (2 << 8) | (2 << 0); + solo_reg_write(solo_dev, SOLO_VE_JPEG_QP_TBL, reg); + solo_reg_write(solo_dev, SOLO_VE_JPEG_QP_CH_L, 0); + solo_reg_write(solo_dev, SOLO_VE_JPEG_QP_CH_H, 0); + solo_reg_write(solo_dev, SOLO_VE_JPEG_CFG, + (SOLO_JPEG_EXT_SIZE(solo_dev) & 0xffff0000) | + ((SOLO_JPEG_EXT_ADDR(solo_dev) >> 16) & 0x0000ffff)); + solo_reg_write(solo_dev, SOLO_VE_JPEG_CTRL, 0xffffffff); + /* que limit, samp limit, pos limit */ + solo_reg_write(solo_dev, 0x0688, (0 << 16) | (30 << 8) | 60); +} + +static void solo_mp4e_config(struct solo_dev *solo_dev) +{ + int i; + u32 reg; + + /* We can only use VE_INTR_CTRL(0) if we want to support mjpeg */ + solo_reg_write(solo_dev, SOLO_VE_CFG0, + SOLO_VE_INTR_CTRL(0) | + SOLO_VE_BLOCK_SIZE(SOLO_MP4E_EXT_SIZE(solo_dev) >> 16) | + SOLO_VE_BLOCK_BASE(SOLO_MP4E_EXT_ADDR(solo_dev) >> 16)); + + solo_reg_write(solo_dev, SOLO_VE_CFG1, + SOLO_VE_INSERT_INDEX | SOLO_VE_MOTION_MODE(0)); + + solo_reg_write(solo_dev, SOLO_VE_WMRK_POLY, 0); + solo_reg_write(solo_dev, SOLO_VE_VMRK_INIT_KEY, 0); + solo_reg_write(solo_dev, SOLO_VE_WMRK_STRL, 0); + solo_reg_write(solo_dev, SOLO_VE_ENCRYP_POLY, 0); + solo_reg_write(solo_dev, SOLO_VE_ENCRYP_INIT, 0); + + reg = SOLO_VE_LITTLE_ENDIAN | SOLO_COMP_ATTR_FCODE(1) | + SOLO_COMP_TIME_INC(0) | SOLO_COMP_TIME_WIDTH(15); + if (solo_dev->flags & FLAGS_6110) + reg |= SOLO_DCT_INTERVAL(10); + else + reg |= SOLO_DCT_INTERVAL(36 / 4); + solo_reg_write(solo_dev, SOLO_VE_ATTR, reg); + + 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); + + if (solo_dev->flags & FLAGS_6110) + solo_reg_write(solo_dev, 0x0634, 0x00040008); /* ? */ +} + +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); + } + + solo_irq_on(solo_dev, SOLO_IRQ_ENCODER); + + return 0; +} + +void solo_enc_exit(struct solo_dev *solo_dev) +{ + int i; + + solo_irq_off(solo_dev, SOLO_IRQ_ENCODER); + + for (i = 0; i < solo_dev->nr_chans; i++) { + solo_reg_write(solo_dev, SOLO_CAP_CH_SCALE(i), 0); + solo_reg_write(solo_dev, SOLO_CAP_CH_COMP_ENA_E(i), 0); + } +} diff --git a/drivers/staging/media/solo6x10/g723.c b/drivers/staging/media/solo6x10/g723.c new file mode 100644 index 0000000..59274bf --- /dev/null +++ b/drivers/staging/media/solo6x10/g723.c @@ -0,0 +1,399 @@ +/* + * Copyright (C) 2010 Bluecherry, LLC www.bluecherrydvr.com + * Copyright (C) 2010 Ben Collins <bcollins@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/slab.h> +#include <linux/freezer.h> +#include <sound/core.h> +#include <sound/initval.h> +#include <sound/pcm.h> +#include <sound/control.h> +#include "solo6x10.h" +#include "tw28.h" + +#define G723_INTR_ORDER 0 +#define G723_FDMA_PAGES 32 +#define G723_PERIOD_BYTES 48 +#define G723_PERIOD_BLOCK 1024 +#define G723_FRAMES_PER_PAGE 48 + +/* Sets up channels 16-19 for decoding and 0-15 for encoding */ +#define OUTMODE_MASK 0x300 + +#define SAMPLERATE 8000 +#define BITRATE 25 + +/* The solo writes to 1k byte pages, 32 pages, in the dma. Each 1k page + * is broken down to 20 * 48 byte regions (one for each channel possible) + * with the rest of the page being dummy data. */ +#define MAX_BUFFER (G723_PERIOD_BYTES * PERIODS_MAX) +#define IRQ_PAGES 4 /* 0 - 4 */ +#define PERIODS_MIN (1 << IRQ_PAGES) +#define PERIODS_MAX G723_FDMA_PAGES + +struct solo_snd_pcm { + int on; + spinlock_t lock; + struct solo_dev *solo_dev; + unsigned char g723_buf[G723_PERIOD_BYTES]; +}; + +static void solo_g723_config(struct solo_dev *solo_dev) +{ + int clk_div; + + clk_div = SOLO_CLOCK_MHZ / (SAMPLERATE * (BITRATE * 2) * 2); + + solo_reg_write(solo_dev, SOLO_AUDIO_SAMPLE, + SOLO_AUDIO_BITRATE(BITRATE) | + SOLO_AUDIO_CLK_DIV(clk_div)); + + solo_reg_write(solo_dev, SOLO_AUDIO_FDMA_INTR, + SOLO_AUDIO_FDMA_INTERVAL(IRQ_PAGES) | + SOLO_AUDIO_INTR_ORDER(G723_INTR_ORDER) | + SOLO_AUDIO_FDMA_BASE(SOLO_G723_EXT_ADDR(solo_dev) >> 16)); + + solo_reg_write(solo_dev, SOLO_AUDIO_CONTROL, + SOLO_AUDIO_ENABLE | SOLO_AUDIO_I2S_MODE | + SOLO_AUDIO_I2S_MULTI(3) | SOLO_AUDIO_MODE(OUTMODE_MASK)); +} + +void solo_g723_isr(struct 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; + + solo_reg_write(solo_dev, SOLO_IRQ_STAT, SOLO_IRQ_G723); + + for (ss = pstr->substream; ss != NULL; ss = ss->next) { + if (snd_pcm_substream_chip(ss) == NULL) + continue; + + /* This means open() hasn't been called on this one */ + if (snd_pcm_substream_chip(ss) == solo_dev) + continue; + + /* Haven't triggered a start yet */ + solo_pcm = snd_pcm_substream_chip(ss); + if (!solo_pcm->on) + continue; + + snd_pcm_period_elapsed(ss); + } +} + +static int snd_solo_hw_params(struct snd_pcm_substream *ss, + struct snd_pcm_hw_params *hw_params) +{ + return snd_pcm_lib_malloc_pages(ss, params_buffer_bytes(hw_params)); +} + +static int snd_solo_hw_free(struct snd_pcm_substream *ss) +{ + return snd_pcm_lib_free_pages(ss); +} + +static struct snd_pcm_hardware snd_solo_pcm_hw = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID), + .formats = SNDRV_PCM_FMTBIT_U8, + .rates = SNDRV_PCM_RATE_8000, + .rate_min = 8000, + .rate_max = 8000, + .channels_min = 1, + .channels_max = 1, + .buffer_bytes_max = MAX_BUFFER, + .period_bytes_min = G723_PERIOD_BYTES, + .period_bytes_max = G723_PERIOD_BYTES, + .periods_min = PERIODS_MIN, + .periods_max = PERIODS_MAX, +}; + +static int snd_solo_pcm_open(struct snd_pcm_substream *ss) +{ + struct 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) + return -ENOMEM; + + spin_lock_init(&solo_pcm->lock); + solo_pcm->solo_dev = solo_dev; + ss->runtime->hw = snd_solo_pcm_hw; + + snd_pcm_substream_chip(ss) = solo_pcm; + + return 0; +} + +static int snd_solo_pcm_close(struct snd_pcm_substream *ss) +{ + struct solo_snd_pcm *solo_pcm = snd_pcm_substream_chip(ss); + + snd_pcm_substream_chip(ss) = solo_pcm->solo_dev; + kfree(solo_pcm); + + return 0; +} + +static int snd_solo_pcm_trigger(struct snd_pcm_substream *ss, int cmd) +{ + struct solo_snd_pcm *solo_pcm = snd_pcm_substream_chip(ss); + struct 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(solo_dev, SOLO_P2M_DMA_ID_G723E, 0, + solo_pcm->g723_buf, + SOLO_G723_EXT_ADDR(solo_dev) + + (page * G723_PERIOD_BLOCK) + + (ss->number * G723_PERIOD_BYTES), + G723_PERIOD_BYTES); + if (err) + return err; + + err = copy_to_user(dst + (i * G723_PERIOD_BYTES), + solo_pcm->g723_buf, G723_PERIOD_BYTES); + + if (err) + return -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), + MAX_BUFFER, 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_create(SNDRV_DEFAULT_IDX1, name, THIS_MODULE, 0, + &solo_dev->snd_card); + if (ret < 0) + return ret; + + card = solo_dev->snd_card; + + strcpy(card->driver, 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); + snd_card_set_dev(card, &solo_dev->pdev->dev); + + ret = snd_device_new(card, SNDRV_DEV_LOWLEVEL, solo_dev, &ops); + if (ret < 0) + goto snd_error; + + /* Mixer controls */ + strcpy(card->mixername, "SOLO-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) +{ + solo_reg_write(solo_dev, SOLO_AUDIO_CONTROL, 0); + solo_irq_off(solo_dev, SOLO_IRQ_G723); + + snd_card_free(solo_dev->snd_card); +} diff --git a/drivers/staging/media/solo6x10/gpio.c b/drivers/staging/media/solo6x10/gpio.c new file mode 100644 index 0000000..0925e6f --- /dev/null +++ b/drivers/staging/media/solo6x10/gpio.c @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2010 Bluecherry, LLC www.bluecherrydvr.com + * Copyright (C) 2010 Ben Collins <bcollins@bluecherry.net> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include <linux/kernel.h> +#include <linux/fs.h> +#include <asm/uaccess.h> +#include "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/i2c.c b/drivers/staging/media/solo6x10/i2c.c new file mode 100644 index 0000000..ef95a50 --- /dev/null +++ b/drivers/staging/media/solo6x10/i2c.c @@ -0,0 +1,330 @@ +/* + * Copyright (C) 2010 Bluecherry, LLC www.bluecherrydvr.com + * Copyright (C) 2010 Ben Collins <bcollins@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; + + solo_reg_write(solo_dev, SOLO_IRQ_STAT, SOLO_IRQ_IIC); + + if (status & (SOLO_IIC_STATE_TRNS & SOLO_IIC_STATE_SIG_ERR) || + solo_dev->i2c_id < 0) { + solo_i2c_stop(solo_dev); + return -ENXIO; + } + + switch (solo_dev->i2c_state) { + case IIC_STATE_START: + if (solo_dev->i2c_msg->flags & I2C_M_RD) { + solo_dev->i2c_state = IIC_STATE_READ; + ret = solo_i2c_handle_read(solo_dev); + break; + } + + solo_dev->i2c_state = IIC_STATE_WRITE; + case IIC_STATE_WRITE: + ret = solo_i2c_handle_write(solo_dev); + break; + + case IIC_STATE_READ: + solo_dev->i2c_msg->buf[solo_dev->i2c_msg_ptr] = + solo_reg_read(solo_dev, SOLO_IIC_RXD); + solo_dev->i2c_msg_ptr++; + + ret = solo_i2c_handle_read(solo_dev); + break; + + default: + solo_i2c_stop(solo_dev); + } + + return ret; +} + +static int solo_i2c_master_xfer(struct i2c_adapter *adap, + struct i2c_msg msgs[], int num) +{ + struct 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 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; + } + + dev_info(&solo_dev->pdev->dev, "Enabled %d i2c adapters\n", + SOLO_I2C_ADAPTERS); + + 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/jpeg.h b/drivers/staging/media/solo6x10/jpeg.h new file mode 100644 index 0000000..50defec --- /dev/null +++ b/drivers/staging/media/solo6x10/jpeg.h @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2010 Bluecherry, LLC www.bluecherrydvr.com + * Copyright (C) 2010 Ben Collins <bcollins@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 unsigned char jpeg_header[] = { + 0xff, 0xd8, 0xff, 0xfe, 0x00, 0x0d, 0x42, 0x6c, + 0x75, 0x65, 0x63, 0x68, 0x65, 0x72, 0x72, 0x79, + 0x20, 0xff, 0xdb, 0x00, 0x43, 0x00, 0x20, 0x16, + 0x18, 0x1c, 0x18, 0x14, 0x20, 0x1c, 0x1a, 0x1c, + 0x24, 0x22, 0x20, 0x26, 0x30, 0x50, 0x34, 0x30, + 0x2c, 0x2c, 0x30, 0x62, 0x46, 0x4a, 0x3a, 0x50, + 0x74, 0x66, 0x7a, 0x78, 0x72, 0x66, 0x70, 0x6e, + 0x80, 0x90, 0xb8, 0x9c, 0x80, 0x88, 0xae, 0x8a, + 0x6e, 0x70, 0xa0, 0xda, 0xa2, 0xae, 0xbe, 0xc4, + 0xce, 0xd0, 0xce, 0x7c, 0x9a, 0xe2, 0xf2, 0xe0, + 0xc8, 0xf0, 0xb8, 0xca, 0xce, 0xc6, 0xff, 0xdb, + 0x00, 0x43, 0x01, 0x22, 0x24, 0x24, 0x30, 0x2a, + 0x30, 0x5e, 0x34, 0x34, 0x5e, 0xc6, 0x84, 0x70, + 0x84, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, + 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, + 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, + 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, + 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, + 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, + 0xc6, 0xc6, 0xc6, 0xff, 0xc4, 0x01, 0xa2, 0x00, + 0x00, 0x01, 0x05, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x10, 0x00, 0x02, 0x01, + 0x03, 0x03, 0x02, 0x04, 0x03, 0x05, 0x05, 0x04, + 0x04, 0x00, 0x00, 0x01, 0x7d, 0x01, 0x02, 0x03, + 0x00, 0x04, 0x11, 0x05, 0x12, 0x21, 0x31, 0x41, + 0x06, 0x13, 0x51, 0x61, 0x07, 0x22, 0x71, 0x14, + 0x32, 0x81, 0x91, 0xa1, 0x08, 0x23, 0x42, 0xb1, + 0xc1, 0x15, 0x52, 0xd1, 0xf0, 0x24, 0x33, 0x62, + 0x72, 0x82, 0x09, 0x0a, 0x16, 0x17, 0x18, 0x19, + 0x1a, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x34, + 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x43, 0x44, + 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x53, 0x54, + 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, + 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x73, 0x74, + 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x83, 0x84, + 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x92, 0x93, + 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, + 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, + 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, + 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, + 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, + 0xd8, 0xd9, 0xda, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, + 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf1, 0xf2, 0xf3, + 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0x01, + 0x00, 0x03, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x11, 0x00, 0x02, 0x01, + 0x02, 0x04, 0x04, 0x03, 0x04, 0x07, 0x05, 0x04, + 0x04, 0x00, 0x01, 0x02, 0x77, 0x00, 0x01, 0x02, + 0x03, 0x11, 0x04, 0x05, 0x21, 0x31, 0x06, 0x12, + 0x41, 0x51, 0x07, 0x61, 0x71, 0x13, 0x22, 0x32, + 0x81, 0x08, 0x14, 0x42, 0x91, 0xa1, 0xb1, 0xc1, + 0x09, 0x23, 0x33, 0x52, 0xf0, 0x15, 0x62, 0x72, + 0xd1, 0x0a, 0x16, 0x24, 0x34, 0xe1, 0x25, 0xf1, + 0x17, 0x18, 0x19, 0x1a, 0x26, 0x27, 0x28, 0x29, + 0x2a, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x43, + 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x53, + 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, + 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x73, + 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x82, + 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, + 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, + 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, + 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, + 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, + 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5, + 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe2, 0xe3, 0xe4, + 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf2, 0xf3, + 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xff, + 0xc0, 0x00, 0x11, 0x08, 0x00, 0xf0, 0x02, 0xc0, + 0x03, 0x01, 0x22, 0x00, 0x02, 0x11, 0x01, 0x03, + 0x11, 0x01, 0xff, 0xda, 0x00, 0x0c, 0x03, 0x01, + 0x00, 0x02, 0x11, 0x03, 0x11, 0x00, 0x3f, 0x00 +}; + +/* This is the byte marker for the start of SOF0: 0xffc0 marker */ +#define SOF0_START 575 + +#endif /* __SOLO6X10_JPEG_H */ diff --git a/drivers/staging/media/solo6x10/offsets.h b/drivers/staging/media/solo6x10/offsets.h new file mode 100644 index 0000000..3d7e569 --- /dev/null +++ b/drivers/staging/media/solo6x10/offsets.h @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2010 Bluecherry, LLC www.bluecherrydvr.com + * Copyright (C) 2010 Ben Collins <bcollins@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 + +/* Offsets and sizes of the external address */ +#define SOLO_DISP_EXT_ADDR 0x00000000 +#define SOLO_DISP_EXT_SIZE 0x00480000 + +#define SOLO_DEC2LIVE_EXT_ADDR (SOLO_DISP_EXT_ADDR + SOLO_DISP_EXT_SIZE) +#define SOLO_DEC2LIVE_EXT_SIZE 0x00240000 + +#define SOLO_OSG_EXT_ADDR (SOLO_DEC2LIVE_EXT_ADDR + SOLO_DEC2LIVE_EXT_SIZE) +#define SOLO_OSG_EXT_SIZE 0x00120000 + +#define SOLO_EOSD_EXT_ADDR (SOLO_OSG_EXT_ADDR + SOLO_OSG_EXT_SIZE) +#define SOLO_EOSD_EXT_SIZE 0x00010000 + +#define SOLO_MOTION_EXT_ADDR(__solo) (SOLO_EOSD_EXT_ADDR + \ + (SOLO_EOSD_EXT_SIZE * __solo->nr_chans)) +#define SOLO_MOTION_EXT_SIZE 0x00080000 + +#define SOLO_G723_EXT_ADDR(__solo) \ + (SOLO_MOTION_EXT_ADDR(__solo) + SOLO_MOTION_EXT_SIZE) +#define SOLO_G723_EXT_SIZE 0x00010000 + +#define SOLO_CAP_EXT_ADDR(__solo) \ + (SOLO_G723_EXT_ADDR(__solo) + SOLO_G723_EXT_SIZE) +#define SOLO_CAP_EXT_MAX_PAGE (18 + 15) +#define SOLO_CAP_EXT_SIZE (SOLO_CAP_EXT_MAX_PAGE * 65536) + +/* This +1 is very important -- Why?! -- BenC */ +#define SOLO_EREF_EXT_ADDR(__solo) \ + (SOLO_CAP_EXT_ADDR(__solo) + \ + (SOLO_CAP_EXT_SIZE * (__solo->nr_chans + 1))) +#define SOLO_EREF_EXT_SIZE 0x00140000 + +#define SOLO_MP4E_EXT_ADDR(__solo) \ + (SOLO_EREF_EXT_ADDR(__solo) + \ + (SOLO_EREF_EXT_SIZE * __solo->nr_chans)) +#define SOLO_MP4E_EXT_SIZE(__solo) (0x00080000 * __solo->nr_chans) + +#define SOLO_DREF_EXT_ADDR(__solo) \ + (SOLO_MP4E_EXT_ADDR(__solo) + SOLO_MP4E_EXT_SIZE(__solo)) +#define SOLO_DREF_EXT_SIZE 0x00140000 + +#define SOLO_MP4D_EXT_ADDR(__solo) \ + (SOLO_DREF_EXT_ADDR(__solo) + \ + (SOLO_DREF_EXT_SIZE * __solo->nr_chans)) +#define SOLO_MP4D_EXT_SIZE 0x00080000 + +#define SOLO_JPEG_EXT_ADDR(__solo) \ + (SOLO_MP4D_EXT_ADDR(__solo) + \ + (SOLO_MP4D_EXT_SIZE * __solo->nr_chans)) +#define SOLO_JPEG_EXT_SIZE(__solo) (0x00080000 * __solo->nr_chans) + +#endif /* __SOLO6X10_OFFSETS_H */ diff --git a/drivers/staging/media/solo6x10/osd-font.h b/drivers/staging/media/solo6x10/osd-font.h new file mode 100644 index 0000000..591e0e8 --- /dev/null +++ b/drivers/staging/media/solo6x10/osd-font.h @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2010 Bluecherry, LLC www.bluecherrydvr.com + * Copyright (C) 2010 Ben Collins <bcollins@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_OSD_FONT_H +#define __SOLO6X10_OSD_FONT_H + +static const unsigned int solo_osd_font[] = { + 0x00000000, 0x0000c0c8, 0xccfefe0c, 0x08000000, + 0x00000000, 0x10103838, 0x7c7cfefe, 0x00000000, /* 0 */ + 0x00000000, 0xfefe7c7c, 0x38381010, 0x10000000, + 0x00000000, 0x7c82fefe, 0xfefefe7c, 0x00000000, + 0x00000000, 0x00001038, 0x10000000, 0x00000000, + 0x00000000, 0x0010387c, 0xfe7c3810, 0x00000000, + 0x00000000, 0x00384444, 0x44380000, 0x00000000, + 0x00000000, 0x38448282, 0x82443800, 0x00000000, + 0x00000000, 0x007c7c7c, 0x7c7c0000, 0x00000000, + 0x00000000, 0x6c6c6c6c, 0x6c6c6c6c, 0x00000000, + 0x00000000, 0x061e7efe, 0xfe7e1e06, 0x00000000, + 0x00000000, 0xc0f0fcfe, 0xfefcf0c0, 0x00000000, + 0x00000000, 0xc6cedefe, 0xfedecec6, 0x00000000, + 0x00000000, 0xc6e6f6fe, 0xfef6e6c6, 0x00000000, + 0x00000000, 0x12367efe, 0xfe7e3612, 0x00000000, + 0x00000000, 0x90d8fcfe, 0xfefcd890, 0x00000000, + 0x00000038, 0x7cc692ba, 0x92c67c38, 0x00000000, + 0x00000038, 0x7cc6aa92, 0xaac67c38, 0x00000000, + 0x00000038, 0x7830107c, 0xbaa8680c, 0x00000000, + 0x00000038, 0x3c18127c, 0xb8382c60, 0x00000000, + 0x00000044, 0xaa6c8254, 0x38eec67c, 0x00000000, + 0x00000082, 0x44288244, 0x38c6827c, 0x00000000, + 0x00000038, 0x444444fe, 0xfeeec6fe, 0x00000000, + 0x00000018, 0x78187818, 0x3c7e7e3c, 0x00000000, + 0x00000000, 0x3854929a, 0x82443800, 0x00000000, + 0x00000000, 0x00c0c8cc, 0xfefe0c08, 0x00000000, + 0x0000e0a0, 0xe040e00e, 0x8a0ea40e, 0x00000000, + 0x0000e0a0, 0xe040e00e, 0x0a8e440e, 0x00000000, + 0x0000007c, 0x82829292, 0x929282fe, 0x00000000, + 0x000000f8, 0xfc046494, 0x946404fc, 0x00000000, + 0x0000003f, 0x7f404c52, 0x524c407f, 0x00000000, + 0x0000007c, 0x82ba82ba, 0x82ba82fe, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x183c3c3c, 0x18180018, 0x18000000, /* 32 ! */ + 0x00000066, 0x66240000, 0x00000000, 0x00000000, + 0x00000000, 0x6c6cfe6c, 0x6c6cfe6c, 0x6c000000, /* 34 " # */ + 0x00001010, 0x7cd6d616, 0x7cd0d6d6, 0x7c101000, + 0x00000000, 0x0086c660, 0x30180cc6, 0xc2000000, /* 36 $ % */ + 0x00000000, 0x386c6c38, 0xdc766666, 0xdc000000, + 0x0000000c, 0x0c0c0600, 0x00000000, 0x00000000, /* 38 & ' */ + 0x00000000, 0x30180c0c, 0x0c0c0c18, 0x30000000, + 0x00000000, 0x0c183030, 0x30303018, 0x0c000000, /* 40 ( ) */ + 0x00000000, 0x0000663c, 0xff3c6600, 0x00000000, + 0x00000000, 0x00001818, 0x7e181800, 0x00000000, /* 42 * + */ + 0x00000000, 0x00000000, 0x00000e0e, 0x0c060000, + 0x00000000, 0x00000000, 0x7e000000, 0x00000000, /* 44 , - */ + 0x00000000, 0x00000000, 0x00000006, 0x06000000, + 0x00000000, 0x80c06030, 0x180c0602, 0x00000000, /* 46 . / */ + 0x0000007c, 0xc6e6f6de, 0xcec6c67c, 0x00000000, + 0x00000030, 0x383c3030, 0x303030fc, 0x00000000, /* 48 0 1 */ + 0x0000007c, 0xc6c06030, 0x180cc6fe, 0x00000000, + 0x0000007c, 0xc6c0c07c, 0xc0c0c67c, 0x00000000, /* 50 2 3 */ + 0x00000060, 0x70786c66, 0xfe6060f0, 0x00000000, + 0x000000fe, 0x0606067e, 0xc0c0c67c, 0x00000000, /* 52 4 5 */ + 0x00000038, 0x0c06067e, 0xc6c6c67c, 0x00000000, + 0x000000fe, 0xc6c06030, 0x18181818, 0x00000000, /* 54 6 7 */ + 0x0000007c, 0xc6c6c67c, 0xc6c6c67c, 0x00000000, + 0x0000007c, 0xc6c6c6fc, 0xc0c06038, 0x00000000, /* 56 8 9 */ + 0x00000000, 0x18180000, 0x00181800, 0x00000000, + 0x00000000, 0x18180000, 0x0018180c, 0x00000000, /* 58 : ; */ + 0x00000060, 0x30180c06, 0x0c183060, 0x00000000, + 0x00000000, 0x007e0000, 0x007e0000, 0x00000000, + 0x00000006, 0x0c183060, 0x30180c06, 0x00000000, + 0x0000007c, 0xc6c66030, 0x30003030, 0x00000000, + 0x0000007c, 0xc6f6d6d6, 0x7606067c, 0x00000000, + 0x00000010, 0x386cc6c6, 0xfec6c6c6, 0x00000000, /* 64 @ A */ + 0x0000007e, 0xc6c6c67e, 0xc6c6c67e, 0x00000000, + 0x00000078, 0xcc060606, 0x0606cc78, 0x00000000, /* 66 */ + 0x0000003e, 0x66c6c6c6, 0xc6c6663e, 0x00000000, + 0x000000fe, 0x0606063e, 0x060606fe, 0x00000000, /* 68 */ + 0x000000fe, 0x0606063e, 0x06060606, 0x00000000, + 0x00000078, 0xcc060606, 0xf6c6ccb8, 0x00000000, /* 70 */ + 0x000000c6, 0xc6c6c6fe, 0xc6c6c6c6, 0x00000000, + 0x0000003c, 0x18181818, 0x1818183c, 0x00000000, /* 72 */ + 0x00000060, 0x60606060, 0x6066663c, 0x00000000, + 0x000000c6, 0xc666361e, 0x3666c6c6, 0x00000000, /* 74 */ + 0x00000006, 0x06060606, 0x060606fe, 0x00000000, + 0x000000c6, 0xeefed6c6, 0xc6c6c6c6, 0x00000000, /* 76 */ + 0x000000c6, 0xcedefef6, 0xe6c6c6c6, 0x00000000, + 0x00000038, 0x6cc6c6c6, 0xc6c66c38, 0x00000000, /* 78 */ + 0x0000007e, 0xc6c6c67e, 0x06060606, 0x00000000, + 0x00000038, 0x6cc6c6c6, 0xc6d67c38, 0x60000000, /* 80 */ + 0x0000007e, 0xc6c6c67e, 0x66c6c6c6, 0x00000000, + 0x0000007c, 0xc6c60c38, 0x60c6c67c, 0x00000000, /* 82 */ + 0x0000007e, 0x18181818, 0x18181818, 0x00000000, + 0x000000c6, 0xc6c6c6c6, 0xc6c6c67c, 0x00000000, /* 84 */ + 0x000000c6, 0xc6c6c6c6, 0xc66c3810, 0x00000000, + 0x000000c6, 0xc6c6c6c6, 0xd6d6fe6c, 0x00000000, /* 86 */ + 0x000000c6, 0xc6c66c38, 0x6cc6c6c6, 0x00000000, + 0x00000066, 0x66666666, 0x3c181818, 0x00000000, /* 88 */ + 0x000000fe, 0xc0603018, 0x0c0606fe, 0x00000000, + 0x0000003c, 0x0c0c0c0c, 0x0c0c0c3c, 0x00000000, /* 90 */ + 0x00000002, 0x060c1830, 0x60c08000, 0x00000000, + 0x0000003c, 0x30303030, 0x3030303c, 0x00000000, /* 92 */ + 0x00001038, 0x6cc60000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00fe0000, + 0x00001818, 0x30000000, 0x00000000, 0x00000000, + 0x00000000, 0x00003c60, 0x7c66667c, 0x00000000, + 0x0000000c, 0x0c0c7ccc, 0xcccccc7c, 0x00000000, + 0x00000000, 0x00007cc6, 0x0606c67c, 0x00000000, + 0x00000060, 0x60607c66, 0x6666667c, 0x00000000, + 0x00000000, 0x00007cc6, 0xfe06c67c, 0x00000000, + 0x00000078, 0x0c0c0c3e, 0x0c0c0c0c, 0x00000000, + 0x00000000, 0x00007c66, 0x6666667c, 0x60603e00, + 0x0000000c, 0x0c0c7ccc, 0xcccccccc, 0x00000000, + 0x00000030, 0x30003830, 0x30303078, 0x00000000, + 0x00000030, 0x30003c30, 0x30303030, 0x30301f00, + 0x0000000c, 0x0c0ccc6c, 0x3c6ccccc, 0x00000000, + 0x00000030, 0x30303030, 0x30303030, 0x00000000, + 0x00000000, 0x000066fe, 0xd6d6d6d6, 0x00000000, + 0x00000000, 0x000078cc, 0xcccccccc, 0x00000000, + 0x00000000, 0x00007cc6, 0xc6c6c67c, 0x00000000, + 0x00000000, 0x00007ccc, 0xcccccc7c, 0x0c0c0c00, + 0x00000000, 0x00007c66, 0x6666667c, 0x60606000, + 0x00000000, 0x000076dc, 0x0c0c0c0c, 0x00000000, + 0x00000000, 0x00007cc6, 0x1c70c67c, 0x00000000, + 0x00000000, 0x1818fe18, 0x18181870, 0x00000000, + 0x00000000, 0x00006666, 0x6666663c, 0x00000000, + 0x00000000, 0x0000c6c6, 0xc66c3810, 0x00000000, + 0x00000000, 0x0000c6d6, 0xd6d6fe6c, 0x00000000, + 0x00000000, 0x0000c66c, 0x38386cc6, 0x00000000, + 0x00000000, 0x00006666, 0x6666667c, 0x60603e00, + 0x00000000, 0x0000fe60, 0x30180cfe, 0x00000000, + 0x00000070, 0x1818180e, 0x18181870, 0x00000000, + 0x00000018, 0x18181800, 0x18181818, 0x00000000, + 0x0000000e, 0x18181870, 0x1818180e, 0x00000000, + 0x000000dc, 0x76000000, 0x00000000, 0x00000000, + 0x00000000, 0x0010386c, 0xc6c6fe00, 0x00000000 +}; + +#endif /* __SOLO6X10_OSD_FONT_H */ diff --git a/drivers/staging/media/solo6x10/p2m.c b/drivers/staging/media/solo6x10/p2m.c new file mode 100644 index 0000000..56210f0 --- /dev/null +++ b/drivers/staging/media/solo6x10/p2m.c @@ -0,0 +1,306 @@ +/* + * Copyright (C) 2010 Bluecherry, LLC www.bluecherrydvr.com + * Copyright (C) 2010 Ben Collins <bcollins@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/slab.h> +#include <linux/scatterlist.h> +#include "solo6x10.h" + +/* #define SOLO_TEST_P2M */ + +int solo_p2m_dma(struct solo_dev *solo_dev, u8 id, int wr, + void *sys_addr, u32 ext_addr, u32 size) +{ + dma_addr_t dma_addr; + int ret; + + WARN_ON(!size); + BUG_ON(id >= SOLO_NR_P2M); + + if (!size) + return -EINVAL; + + dma_addr = pci_map_single(solo_dev->pdev, sys_addr, size, + wr ? PCI_DMA_TODEVICE : PCI_DMA_FROMDEVICE); + + ret = solo_p2m_dma_t(solo_dev, id, wr, dma_addr, ext_addr, size); + + pci_unmap_single(solo_dev->pdev, dma_addr, size, + wr ? PCI_DMA_TODEVICE : PCI_DMA_FROMDEVICE); + + return ret; +} + +int solo_p2m_dma_t(struct solo_dev *solo_dev, u8 id, int wr, + dma_addr_t dma_addr, u32 ext_addr, u32 size) +{ + struct p2m_desc *desc = kzalloc(sizeof(*desc) * 2, GFP_DMA); + int ret; + + if (desc == NULL) + return -ENOMEM; + + solo_p2m_push_desc(&desc[1], wr, dma_addr, ext_addr, size, 0, 0); + ret = solo_p2m_dma_desc(solo_dev, id, desc, 2); + kfree(desc); + + return ret; +} + +void solo_p2m_push_desc(struct p2m_desc *desc, int wr, dma_addr_t dma_addr, + u32 ext_addr, u32 size, int repeat, u32 ext_size) +{ + desc->ta = cpu_to_le32(dma_addr); + desc->fa = cpu_to_le32(ext_addr); + + desc->ext = cpu_to_le32(SOLO_P2M_COPY_SIZE(size >> 2)); + desc->ctrl = cpu_to_le32(SOLO_P2M_BURST_SIZE(SOLO_P2M_BURST_256) | + (wr ? SOLO_P2M_WRITE : 0) | SOLO_P2M_TRANS_ON); + + /* Ext size only matters when we're repeating */ + if (repeat) { + desc->ext |= cpu_to_le32(SOLO_P2M_EXT_INC(ext_size >> 2)); + desc->ctrl |= cpu_to_le32(SOLO_P2M_PCI_INC(size >> 2) | + SOLO_P2M_REPEAT(repeat)); + } +} + +int solo_p2m_dma_desc(struct solo_dev *solo_dev, u8 id, + struct p2m_desc *desc, int desc_count) +{ + struct solo_p2m_dev *p2m_dev; + unsigned int timeout; + int ret = 0; + u32 config = 0; + dma_addr_t desc_dma = 0; + + BUG_ON(id >= SOLO_NR_P2M); + BUG_ON(!desc_count || desc_count > SOLO_NR_P2M_DESC); + + p2m_dev = &solo_dev->p2m_dev[id]; + + mutex_lock(&p2m_dev->mutex); + + solo_reg_write(solo_dev, SOLO_P2M_CONTROL(id), 0); + + INIT_COMPLETION(p2m_dev->completion); + p2m_dev->error = 0; + + /* Enable the descriptors */ + config = solo_reg_read(solo_dev, SOLO_P2M_CONFIG(id)); + desc_dma = pci_map_single(solo_dev->pdev, desc, + desc_count * sizeof(*desc), + PCI_DMA_TODEVICE); + solo_reg_write(solo_dev, SOLO_P2M_DES_ADR(id), desc_dma); + solo_reg_write(solo_dev, SOLO_P2M_DESC_ID(id), desc_count - 1); + solo_reg_write(solo_dev, SOLO_P2M_CONFIG(id), config | + SOLO_P2M_DESC_MODE); + + /* Should have all descriptors completed from one interrupt */ + timeout = wait_for_completion_timeout(&p2m_dev->completion, HZ); + + solo_reg_write(solo_dev, SOLO_P2M_CONTROL(id), 0); + + /* Reset back to non-descriptor mode */ + solo_reg_write(solo_dev, SOLO_P2M_CONFIG(id), config); + solo_reg_write(solo_dev, SOLO_P2M_DESC_ID(id), 0); + solo_reg_write(solo_dev, SOLO_P2M_DES_ADR(id), 0); + pci_unmap_single(solo_dev->pdev, desc_dma, + desc_count * sizeof(*desc), + PCI_DMA_TODEVICE); + + if (p2m_dev->error) + ret = -EIO; + else if (timeout == 0) + ret = -EAGAIN; + + mutex_unlock(&p2m_dev->mutex); + + WARN_ON_ONCE(ret); + + return ret; +} + +int solo_p2m_dma_sg(struct solo_dev *solo_dev, u8 id, + struct p2m_desc *pdesc, int wr, + struct scatterlist *sg, u32 sg_off, + u32 ext_addr, u32 size) +{ + int i; + int idx; + + BUG_ON(id >= SOLO_NR_P2M); + + if (WARN_ON_ONCE(!size)) + return -EINVAL; + + memset(pdesc, 0, sizeof(*pdesc)); + + /* Should rewrite this to handle > SOLO_NR_P2M_DESC transactions */ + for (i = 0, idx = 1; idx < SOLO_NR_P2M_DESC && sg && size > 0; + i++, sg = sg_next(sg)) { + struct p2m_desc *desc = &pdesc[idx]; + u32 sg_len = sg_dma_len(sg); + u32 len; + + if (sg_off >= sg_len) { + sg_off -= sg_len; + continue; + } + + sg_len -= sg_off; + len = min(sg_len, size); + + solo_p2m_push_desc(desc, wr, sg_dma_address(sg) + sg_off, + ext_addr, len, 0, 0); + + size -= len; + ext_addr += len; + idx++; + + sg_off = 0; + } + + WARN_ON_ONCE(size || i >= SOLO_NR_P2M_DESC); + + return solo_p2m_dma_desc(solo_dev, id, pdesc, idx); +} + +#ifdef SOLO_TEST_P2M + +#define P2M_TEST_CHAR 0xbe + +static unsigned long long p2m_test(struct solo_dev *solo_dev, u8 id, + u32 base, int size) +{ + u8 *wr_buf; + u8 *rd_buf; + int i; + unsigned long long err_cnt = 0; + + wr_buf = kmalloc(size, GFP_KERNEL); + if (!wr_buf) { + printk(SOLO6X10_NAME ": Failed to malloc for p2m_test\n"); + return size; + } + + rd_buf = kmalloc(size, GFP_KERNEL); + if (!rd_buf) { + printk(SOLO6X10_NAME ": Failed to malloc for p2m_test\n"); + kfree(wr_buf); + return size; + } + + memset(wr_buf, P2M_TEST_CHAR, size); + memset(rd_buf, P2M_TEST_CHAR + 1, size); + + solo_p2m_dma(solo_dev, id, 1, wr_buf, base, size); + solo_p2m_dma(solo_dev, id, 0, rd_buf, base, size); + + for (i = 0; i < size; i++) + if (wr_buf[i] != rd_buf[i]) + err_cnt++; + + kfree(wr_buf); + kfree(rd_buf); + + return err_cnt; +} + +#define TEST_CHUNK_SIZE (8 * 1024) + +static void run_p2m_test(struct solo_dev *solo_dev) +{ + unsigned long long errs = 0; + u32 size = SOLO_JPEG_EXT_ADDR(solo_dev) + SOLO_JPEG_EXT_SIZE(solo_dev); + int i, d; + + printk(KERN_WARNING "%s: Testing %u bytes of external ram\n", + SOLO6X10_NAME, size); + + for (i = 0; i < size; i += TEST_CHUNK_SIZE) + for (d = 0; d < 4; d++) + errs += p2m_test(solo_dev, d, i, TEST_CHUNK_SIZE); + + printk(KERN_WARNING "%s: Found %llu errors during p2m test\n", + SOLO6X10_NAME, errs); + + return; +} +#else +#define run_p2m_test(__solo) do {} while (0) +#endif + +void solo_p2m_isr(struct solo_dev *solo_dev, int id) +{ + struct solo_p2m_dev *p2m_dev = &solo_dev->p2m_dev[id]; + + solo_reg_write(solo_dev, SOLO_IRQ_STAT, SOLO_IRQ_P2M(id)); + + complete(&p2m_dev->completion); +} + +void solo_p2m_error_isr(struct solo_dev *solo_dev, u32 status) +{ + struct solo_p2m_dev *p2m_dev; + int i; + + if (!(status & SOLO_PCI_ERR_P2M)) + return; + + for (i = 0; i < SOLO_NR_P2M; i++) { + p2m_dev = &solo_dev->p2m_dev[i]; + p2m_dev->error = 1; + solo_reg_write(solo_dev, SOLO_P2M_CONTROL(i), 0); + complete(&p2m_dev->completion); + } +} + +void solo_p2m_exit(struct solo_dev *solo_dev) +{ + int i; + + for (i = 0; i < SOLO_NR_P2M; i++) + solo_irq_off(solo_dev, SOLO_IRQ_P2M(i)); +} + +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_DMA_INTERVAL(3) | + SOLO_P2M_DESC_INTR_OPT | + SOLO_P2M_PCI_MASTER_MODE); + solo_irq_on(solo_dev, SOLO_IRQ_P2M(i)); + } + + run_p2m_test(solo_dev); + + return 0; +} diff --git a/drivers/staging/media/solo6x10/registers.h b/drivers/staging/media/solo6x10/registers.h new file mode 100644 index 0000000..aca5444 --- /dev/null +++ b/drivers/staging/media/solo6x10/registers.h @@ -0,0 +1,637 @@ +/* + * Copyright (C) 2010 Bluecherry, LLC www.bluecherrydvr.com + * Copyright (C) 2010 Ben Collins <bcollins@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 "offsets.h" + +/* Global 6X10 system configuration */ +#define SOLO_SYS_CFG 0x0000 +#define SOLO6010_SYS_CFG_FOUT_EN 0x00000001 /* 6010 only */ +#define SOLO6010_SYS_CFG_PLL_BYPASS 0x00000002 /* 6010 only */ +#define SOLO6010_SYS_CFG_PLL_PWDN 0x00000004 /* 6010 only */ +#define SOLO6010_SYS_CFG_OUTDIV(__n) (((__n) & 0x003) << 3) /* 6010 only */ +#define SOLO6010_SYS_CFG_FEEDBACKDIV(__n) (((__n) & 0x1ff) << 5) /* 6010 only */ +#define SOLO6010_SYS_CFG_INPUTDIV(__n) (((__n) & 0x01f) << 14) /* 6010 only */ +#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 /* 6110: must be set */ +#define SOLO_SYS_CFG_RESET 0x80000000 + +#define SOLO_DMA_CTRL 0x0004 +#define SOLO_DMA_CTRL_REFRESH_CYCLE(n) ((n)<<8) +/* 0=16/32MB, 1=32/64MB, 2=64/128MB, 3=128/256MB */ +#define SOLO_DMA_CTRL_SDRAM_SIZE(n) ((n)<<6) +#define SOLO_DMA_CTRL_SDRAM_CLK_INVERT (1<<5) +#define SOLO_DMA_CTRL_STROBE_SELECT (1<<4) +#define SOLO_DMA_CTRL_READ_DATA_SELECT (1<<3) +#define SOLO_DMA_CTRL_READ_CLK_SELECT (1<<2) +#define SOLO_DMA_CTRL_LATENCY(n) ((n)<<0) +#define SOLO_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_ENABLE 0x0014 +#define SOLO_IRQ_P2M(n) (1<<((n)+17)) +#define SOLO_IRQ_GPIO (1<<16) +#define SOLO_IRQ_VIDEO_LOSS (1<<15) +#define SOLO_IRQ_VIDEO_IN (1<<14) +#define SOLO_IRQ_MOTION (1<<13) +#define SOLO_IRQ_ATA_CMD (1<<12) +#define SOLO_IRQ_ATA_DIR (1<<11) +#define SOLO_IRQ_PCI_ERR (1<<10) +#define SOLO_IRQ_PS2_1 (1<<9) +#define SOLO_IRQ_PS2_0 (1<<8) +#define SOLO_IRQ_SPI (1<<7) +#define SOLO_IRQ_IIC (1<<6) +#define SOLO_IRQ_UART(n) (1<<((n) + 4)) +#define SOLO_IRQ_G723 (1<<3) +#define SOLO_IRQ_DECODER (1<<1) +#define SOLO_IRQ_ENCODER (1<<0) + +#define SOLO_CHIP_OPTION 0x001C +#define SOLO_CHIP_ID_MASK 0x00000007 + +#define SOLO6110_PLL_CONFIG 0x0020 +#define SOLO6110_PLL_RANGE_BYPASS (0 << 20) +#define SOLO6110_PLL_RANGE_5_10MHZ (1 << 20) +#define SOLO6110_PLL_RANGE_8_16MHZ (2 << 20) +#define SOLO6110_PLL_RANGE_13_26MHZ (3 << 20) +#define SOLO6110_PLL_RANGE_21_42MHZ (4 << 20) +#define SOLO6110_PLL_RANGE_34_68MHZ (5 << 20) +#define SOLO6110_PLL_RANGE_54_108MHZ (6 << 20) +#define SOLO6110_PLL_RANGE_88_200MHZ (7 << 20) +#define SOLO6110_PLL_DIVR(x) (((x) - 1) << 15) +#define SOLO6110_PLL_DIVQ_EXP(x) ((x) << 12) +#define SOLO6110_PLL_DIVF(x) (((x) - 1) << 4) +#define SOLO6110_PLL_RESET (1 << 3) +#define SOLO6110_PLL_BYPASS (1 << 2) +#define SOLO6110_PLL_FSEN (1 << 1) +#define SOLO6110_PLL_FB (1 << 0) + +#define SOLO_EEPROM_CTRL 0x0060 +#define SOLO_EEPROM_ACCESS_EN (1<<7) +#define SOLO_EEPROM_CS (1<<3) +#define SOLO_EEPROM_CLK (1<<2) +#define SOLO_EEPROM_DO (1<<1) +#define SOLO_EEPROM_DI (1<<0) +#define SOLO_EEPROM_ENABLE (EEPROM_ACCESS_EN | EEPROM_CS) + +#define SOLO_PCI_ERR 0x0070 +#define SOLO_PCI_ERR_FATAL 0x00000001 +#define SOLO_PCI_ERR_PARITY 0x00000002 +#define SOLO_PCI_ERR_TARGET 0x00000004 +#define SOLO_PCI_ERR_TIMEOUT 0x00000008 +#define SOLO_PCI_ERR_P2M 0x00000010 +#define SOLO_PCI_ERR_ATA 0x00000020 +#define SOLO_PCI_ERR_P2M_DESC 0x00000040 +#define SOLO_PCI_ERR_FSM0(__s) (((__s) >> 16) & 0x0f) +#define SOLO_PCI_ERR_FSM1(__s) (((__s) >> 20) & 0x0f) +#define SOLO_PCI_ERR_FSM2(__s) (((__s) >> 24) & 0x1f) + +#define SOLO_P2M_BASE 0x0080 + +#define SOLO_P2M_CONFIG(n) (0x0080 + ((n)*0x20)) +#define SOLO_P2M_DMA_INTERVAL(n) ((n)<<6)/* N*32 clocks */ +#define SOLO_P2M_CSC_BYTE_REORDER (1<<5) /* BGR -> RGB */ +/* 0:r=[14:10] g=[9:5] b=[4:0], 1:r=[15:11] g=[10:5] b=[4:0] */ +#define SOLO_P2M_CSC_16BIT_565 (1<<4) +#define SOLO_P2M_UV_SWAP (1<<3) +#define SOLO_P2M_PCI_MASTER_MODE (1<<2) +#define SOLO_P2M_DESC_INTR_OPT (1<<1) /* 1:Empty, 0:Each */ +#define SOLO_P2M_DESC_MODE (1<<0) + +#define SOLO_P2M_DES_ADR(n) (0x0084 + ((n)*0x20)) + +#define SOLO_P2M_DESC_ID(n) (0x0088 + ((n)*0x20)) +#define SOLO_P2M_UPDATE_ID(n) ((n)<<0) + +#define SOLO_P2M_STATUS(n) (0x008C + ((n)*0x20)) +#define SOLO_P2M_COMMAND_DONE (1<<8) +#define SOLO_P2M_CURRENT_ID(stat) (0xff & (stat)) + +#define SOLO_P2M_CONTROL(n) (0x0090 + ((n)*0x20)) +#define SOLO_P2M_PCI_INC(n) ((n)<<20) +#define SOLO_P2M_REPEAT(n) ((n)<<10) +/* 0:512, 1:256, 2:128, 3:64, 4:32, 5:128(2page) */ +#define SOLO_P2M_BURST_SIZE(n) ((n)<<7) +#define SOLO_P2M_BURST_512 0 +#define SOLO_P2M_BURST_256 1 +#define SOLO_P2M_BURST_128 2 +#define SOLO_P2M_BURST_64 3 +#define SOLO_P2M_BURST_32 4 +#define SOLO_P2M_CSC_16BIT (1<<6) /* 0:24bit, 1:16bit */ +/* 0:Y[0]<-0(OFF), 1:Y[0]<-1(ON), 2:Y[0]<-G[0], 3:Y[0]<-Bit[15] */ +#define SOLO_P2M_ALPHA_MODE(n) ((n)<<4) +#define SOLO_P2M_CSC_ON (1<<3) +#define SOLO_P2M_INTERRUPT_REQ (1<<2) +#define SOLO_P2M_WRITE (1<<1) +#define SOLO_P2M_TRANS_ON (1<<0) + +#define SOLO_P2M_EXT_CFG(n) (0x0094 + ((n)*0x20)) +#define SOLO_P2M_EXT_INC(n) ((n)<<20) +#define SOLO_P2M_COPY_SIZE(n) ((n)<<0) + +#define SOLO_P2M_TAR_ADR(n) (0x0098 + ((n)*0x20)) + +#define SOLO_P2M_EXT_ADR(n) (0x009C + ((n)*0x20)) + +#define SOLO_P2M_BUFFER(i) (0x2000 + ((i)*4)) + +#define SOLO_VI_CH_SWITCH_0 0x0100 +#define SOLO_VI_CH_SWITCH_1 0x0104 +#define SOLO_VI_CH_SWITCH_2 0x0108 + +#define SOLO_VI_CH_ENA 0x010C +#define SOLO_VI_CH_FORMAT 0x0110 +#define SOLO_VI_FD_SEL_MASK(n) ((n)<<16) +#define SOLO_VI_PROG_MASK(n) ((n)<<0) + +#define SOLO_VI_FMT_CFG 0x0114 +#define SOLO_VI_FMT_CHECK_VCOUNT (1<<31) +#define SOLO_VI_FMT_CHECK_HCOUNT (1<<30) +#define SOLO_VI_FMT_TEST_SIGNAL (1<<28) + +#define SOLO_VI_PAGE_SW 0x0118 +#define SOLO_FI_INV_DISP_LIVE(n) ((n)<<8) +#define SOLO_FI_INV_DISP_OUT(n) ((n)<<7) +#define SOLO_DISP_SYNC_FI(n) ((n)<<6) +#define SOLO_PIP_PAGE_ADD(n) ((n)<<3) +#define SOLO_NORMAL_PAGE_ADD(n) ((n)<<0) + +#define SOLO_VI_ACT_I_P 0x011C +#define SOLO_VI_ACT_I_S 0x0120 +#define SOLO_VI_ACT_P 0x0124 +#define SOLO_VI_FI_INVERT (1<<31) +#define SOLO_VI_H_START(n) ((n)<<21) +#define SOLO_VI_V_START(n) ((n)<<11) +#define SOLO_VI_V_STOP(n) ((n)<<0) + +#define SOLO_VI_STATUS0 0x0128 +#define SOLO_VI_STATUS0_PAGE(__n) ((__n) & 0x07) +#define SOLO_VI_STATUS1 0x012C + +/* XXX: Might be better off in kernel level disp.h */ +#define DISP_PAGE(stat) ((stat) & 0x07) + +#define SOLO_VI_PB_CONFIG 0x0130 +#define SOLO_VI_PB_USER_MODE (1<<1) +#define SOLO_VI_PB_PAL (1<<0) +#define SOLO_VI_PB_RANGE_HV 0x0134 +#define SOLO_VI_PB_HSIZE(h) ((h)<<12) +#define SOLO_VI_PB_VSIZE(v) ((v)<<0) +#define SOLO_VI_PB_ACT_H 0x0138 +#define SOLO_VI_PB_HSTART(n) ((n)<<12) +#define SOLO_VI_PB_HSTOP(n) ((n)<<0) +#define SOLO_VI_PB_ACT_V 0x013C +#define SOLO_VI_PB_VSTART(n) ((n)<<12) +#define SOLO_VI_PB_VSTOP(n) ((n)<<0) + +#define SOLO_VI_MOSAIC(ch) (0x0140 + ((ch)*4)) +#define SOLO_VI_MOSAIC_SX(x) ((x)<<24) +#define SOLO_VI_MOSAIC_EX(x) ((x)<<16) +#define SOLO_VI_MOSAIC_SY(x) ((x)<<8) +#define SOLO_VI_MOSAIC_EY(x) ((x)<<0) + +#define SOLO_VI_WIN_CTRL0(ch) (0x0180 + ((ch)*4)) +#define SOLO_VI_WIN_CTRL1(ch) (0x01C0 + ((ch)*4)) + +#define SOLO_VI_WIN_CHANNEL(n) ((n)<<28) + +#define SOLO_VI_WIN_PIP(n) ((n)<<27) +#define SOLO_VI_WIN_SCALE(n) ((n)<<24) + +#define SOLO_VI_WIN_SX(x) ((x)<<12) +#define SOLO_VI_WIN_EX(x) ((x)<<0) + +#define SOLO_VI_WIN_SY(x) ((x)<<12) +#define SOLO_VI_WIN_EY(x) ((x)<<0) + +#define SOLO_VI_WIN_ON(ch) (0x0200 + ((ch)*4)) + +#define SOLO_VI_WIN_SW 0x0240 +#define SOLO_VI_WIN_LIVE_AUTO_MUTE 0x0244 + +#define SOLO_VI_MOT_ADR 0x0260 +#define SOLO_VI_MOTION_EN(mask) ((mask)<<16) +#define SOLO_VI_MOT_CTRL 0x0264 +#define SOLO_VI_MOTION_FRAME_COUNT(n) ((n)<<24) +#define SOLO_VI_MOTION_SAMPLE_LENGTH(n) ((n)<<16) +#define SOLO_VI_MOTION_INTR_START_STOP (1<<15) +#define SOLO_VI_MOTION_FREEZE_DATA (1<<14) +#define SOLO_VI_MOTION_SAMPLE_COUNT(n) ((n)<<0) +#define SOLO_VI_MOT_CLEAR 0x0268 +#define SOLO_VI_MOT_STATUS 0x026C +#define SOLO_VI_MOTION_CNT(n) ((n)<<0) +#define SOLO_VI_MOTION_BORDER 0x0270 +#define SOLO_VI_MOTION_BAR 0x0274 +#define SOLO_VI_MOTION_Y_SET (1<<29) +#define SOLO_VI_MOTION_Y_ADD (1<<28) +#define SOLO_VI_MOTION_CB_SET (1<<27) +#define SOLO_VI_MOTION_CB_ADD (1<<26) +#define SOLO_VI_MOTION_CR_SET (1<<25) +#define SOLO_VI_MOTION_CR_ADD (1<<24) +#define SOLO_VI_MOTION_Y_VALUE(v) ((v)<<16) +#define SOLO_VI_MOTION_CB_VALUE(v) ((v)<<8) +#define SOLO_VI_MOTION_CR_VALUE(v) ((v)<<0) + +#define SOLO_VO_FMT_ENC 0x0300 +#define SOLO_VO_SCAN_MODE_PROGRESSIVE (1<<31) +#define SOLO_VO_FMT_TYPE_PAL (1<<30) +#define SOLO_VO_FMT_TYPE_NTSC 0 +#define SOLO_VO_USER_SET (1<<29) + +#define SOLO_VO_FI_CHANGE (1<<20) +#define SOLO_VO_USER_COLOR_SET_VSYNC (1<<19) +#define SOLO_VO_USER_COLOR_SET_HSYNC (1<<18) +#define SOLO_VO_USER_COLOR_SET_NAV (1<<17) +#define SOLO_VO_USER_COLOR_SET_NAH (1<<16) +#define SOLO_VO_NA_COLOR_Y(Y) ((Y)<<8) +#define SOLO_VO_NA_COLOR_CB(CB) (((CB)/16)<<4) +#define SOLO_VO_NA_COLOR_CR(CR) (((CR)/16)<<0) + +#define SOLO_VO_ACT_H 0x0304 +#define SOLO_VO_H_BLANK(n) ((n)<<22) +#define SOLO_VO_H_START(n) ((n)<<11) +#define SOLO_VO_H_STOP(n) ((n)<<0) + +#define SOLO_VO_ACT_V 0x0308 +#define SOLO_VO_V_BLANK(n) ((n)<<22) +#define SOLO_VO_V_START(n) ((n)<<11) +#define SOLO_VO_V_STOP(n) ((n)<<0) + +#define SOLO_VO_RANGE_HV 0x030C +#define SOLO_VO_SYNC_INVERT (1<<24) +#define SOLO_VO_HSYNC_INVERT (1<<23) +#define SOLO_VO_VSYNC_INVERT (1<<22) +#define SOLO_VO_H_LEN(n) ((n)<<11) +#define SOLO_VO_V_LEN(n) ((n)<<0) + +#define SOLO_VO_DISP_CTRL 0x0310 +#define SOLO_VO_DISP_ON (1<<31) +#define SOLO_VO_DISP_ERASE_COUNT(n) ((n&0xf)<<24) +#define SOLO_VO_DISP_DOUBLE_SCAN (1<<22) +#define SOLO_VO_DISP_SINGLE_PAGE (1<<21) +#define SOLO_VO_DISP_BASE(n) (((n)>>16) & 0xffff) + +#define SOLO_VO_DISP_ERASE 0x0314 +#define SOLO_VO_DISP_ERASE_ON (1<<0) + +#define SOLO_VO_ZOOM_CTRL 0x0318 +#define SOLO_VO_ZOOM_VER_ON (1<<24) +#define SOLO_VO_ZOOM_HOR_ON (1<<23) +#define SOLO_VO_ZOOM_V_COMP (1<<22) +#define SOLO_VO_ZOOM_SX(h) (((h)/2)<<11) +#define SOLO_VO_ZOOM_SY(v) (((v)/2)<<0) + +#define SOLO_VO_FREEZE_CTRL 0x031C +#define SOLO_VO_FREEZE_ON (1<<1) +#define SOLO_VO_FREEZE_INTERPOLATION (1<<0) + +#define SOLO_VO_BKG_COLOR 0x0320 +#define SOLO_BG_Y(y) ((y)<<16) +#define SOLO_BG_U(u) ((u)<<8) +#define SOLO_BG_V(v) ((v)<<0) + +#define SOLO_VO_DEINTERLACE 0x0324 +#define SOLO_VO_DEINTERLACE_THRESHOLD(n) ((n)<<8) +#define SOLO_VO_DEINTERLACE_EDGE_VALUE(n) ((n)<<0) + +#define SOLO_VO_BORDER_LINE_COLOR 0x0330 +#define SOLO_VO_BORDER_FILL_COLOR 0x0334 +#define SOLO_VO_BORDER_LINE_MASK 0x0338 +#define SOLO_VO_BORDER_FILL_MASK 0x033c + +#define SOLO_VO_BORDER_X(n) (0x0340+((n)*4)) +#define SOLO_VO_BORDER_Y(n) (0x0354+((n)*4)) + +#define SOLO_VO_CELL_EXT_SET 0x0368 +#define SOLO_VO_CELL_EXT_START 0x036c +#define SOLO_VO_CELL_EXT_STOP 0x0370 + +#define SOLO_VO_CELL_EXT_SET2 0x0374 +#define SOLO_VO_CELL_EXT_START2 0x0378 +#define SOLO_VO_CELL_EXT_STOP2 0x037c + +#define SOLO_VO_RECTANGLE_CTRL(n) (0x0368+((n)*12)) +#define SOLO_VO_RECTANGLE_START(n) (0x036c+((n)*12)) +#define SOLO_VO_RECTANGLE_STOP(n) (0x0370+((n)*12)) + +#define SOLO_VO_CURSOR_POS (0x0380) +#define SOLO_VO_CURSOR_CLR (0x0384) +#define SOLO_VO_CURSOR_CLR2 (0x0388) +#define SOLO_VO_CURSOR_MASK(id) (0x0390+((id)*4)) + +#define SOLO_VO_EXPANSION(id) (0x0250+((id)*4)) + +#define SOLO_OSG_CONFIG 0x03E0 +#define SOLO_VO_OSG_ON (1<<31) +#define SOLO_VO_OSG_COLOR_MUTE (1<<28) +#define SOLO_VO_OSG_ALPHA_RATE(n) ((n)<<22) +#define SOLO_VO_OSG_ALPHA_BG_RATE(n) ((n)<<16) +#define SOLO_VO_OSG_BASE(offset) (((offset)>>16)&0xffff) + +#define SOLO_OSG_ERASE 0x03E4 +#define SOLO_OSG_ERASE_ON (0x80) +#define SOLO_OSG_ERASE_OFF (0x00) + +#define SOLO_VO_OSG_BLINK 0x03E8 +#define SOLO_VO_OSG_BLINK_ON (1<<1) +#define SOLO_VO_OSG_BLINK_INTREVAL18 (1<<0) + +#define SOLO_CAP_BASE 0x0400 +#define SOLO_CAP_MAX_PAGE(n) ((n)<<16) +#define SOLO_CAP_BASE_ADDR(n) ((n)<<0) +#define SOLO_CAP_BTW 0x0404 +#define SOLO_CAP_PROG_BANDWIDTH(n) ((n)<<8) +#define SOLO_CAP_MAX_BANDWIDTH(n) ((n)<<0) + +#define SOLO_DIM_SCALE1 0x0408 +#define SOLO_DIM_SCALE2 0x040C +#define SOLO_DIM_SCALE3 0x0410 +#define SOLO_DIM_SCALE4 0x0414 +#define SOLO_DIM_SCALE5 0x0418 +#define SOLO_DIM_V_MB_NUM_FRAME(n) ((n)<<16) +#define SOLO_DIM_V_MB_NUM_FIELD(n) ((n)<<8) +#define SOLO_DIM_H_MB_NUM(n) ((n)<<0) + +#define SOLO_DIM_PROG 0x041C +#define SOLO_CAP_STATUS 0x0420 + +#define SOLO_CAP_CH_SCALE(ch) (0x0440+((ch)*4)) +#define SOLO_CAP_CH_COMP_ENA_E(ch) (0x0480+((ch)*4)) +#define SOLO_CAP_CH_INTV(ch) (0x04C0+((ch)*4)) +#define SOLO_CAP_CH_INTV_E(ch) (0x0500+((ch)*4)) + + +#define SOLO_VE_CFG0 0x0610 +#define SOLO_VE_TWO_PAGE_MODE (1<<31) +#define SOLO_VE_INTR_CTRL(n) ((n)<<24) +#define SOLO_VE_BLOCK_SIZE(n) ((n)<<16) +#define SOLO_VE_BLOCK_BASE(n) ((n)<<0) + +#define SOLO_VE_CFG1 0x0614 +#define SOLO6110_VE_MPEG_SIZE_H(n) ((n)<<28) /* 6110 only */ +#define SOLO6010_VE_BYTE_ALIGN(n) ((n)<<24) /* 6010 only */ +#define SOLO6110_VE_JPEG_SIZE_H(n) ((n)<<20) /* 6110 only */ +#define SOLO_VE_INSERT_INDEX (1<<18) +#define SOLO_VE_MOTION_MODE(n) ((n)<<16) +#define SOLO_VE_MOTION_BASE(n) ((n)<<0) + +#define SOLO_VE_WMRK_POLY 0x061C +#define SOLO_VE_VMRK_INIT_KEY 0x0620 +#define SOLO_VE_WMRK_STRL 0x0624 +#define SOLO_VE_ENCRYP_POLY 0x0628 +#define SOLO_VE_ENCRYP_INIT 0x062C +#define SOLO_VE_ATTR 0x0630 +#define SOLO_VE_LITTLE_ENDIAN (1<<31) +#define SOLO_COMP_ATTR_RN (1<<30) +#define SOLO_COMP_ATTR_FCODE(n) ((n)<<27) +#define SOLO_COMP_TIME_INC(n) ((n)<<25) +#define SOLO_COMP_TIME_WIDTH(n) ((n)<<21) +#define SOLO_DCT_INTERVAL(n) ((n)<<16) + +#define SOLO_VE_STATE(n) (0x0640+((n)*4)) + +#define SOLO_VE_JPEG_QP_TBL 0x0670 +#define SOLO_VE_JPEG_QP_CH_L 0x0674 +#define SOLO_VE_JPEG_QP_CH_H 0x0678 +#define SOLO_VE_JPEG_CFG 0x067C +#define SOLO_VE_JPEG_CTRL 0x0680 + +#define SOLO_VE_OSD_CH 0x0690 +#define SOLO_VE_OSD_BASE 0x0694 +#define SOLO_VE_OSD_CLR 0x0698 +#define SOLO_VE_OSD_OPT 0x069C + +#define SOLO_VE_CH_INTL(ch) (0x0700+((ch)*4)) +#define SOLO6010_VE_CH_MOT(ch) (0x0740+((ch)*4)) /* 6010 only */ +#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 SOLO6010_VD_CFG_NO_WRITE_NO_WINDOW (1<<24) /* 6010 only */ +#define SOLO_VD_CFG_BUSY_WIAT_CODE (1<<23) +#define SOLO_VD_CFG_BUSY_WIAT_REF (1<<22) +#define SOLO_VD_CFG_BUSY_WIAT_RES (1<<21) +#define SOLO_VD_CFG_BUSY_WIAT_MS (1<<20) +#define SOLO_VD_CFG_SINGLE_MODE (1<<18) +#define SOLO_VD_CFG_SCAL_MANUAL (1<<17) +#define SOLO_VD_CFG_USER_PAGE_CTRL (1<<16) +#define SOLO_VD_CFG_LITTLE_ENDIAN (1<<15) +#define SOLO_VD_CFG_START_FI (1<<14) +#define SOLO_VD_CFG_ERR_LOCK (1<<13) +#define SOLO_VD_CFG_ERR_INT_ENA (1<<12) +#define SOLO_VD_CFG_TIME_WIDTH(n) ((n)<<8) +#define SOLO_VD_CFG_DCT_INTERVAL(n) ((n)<<0) + +#define SOLO_VD_CFG1 0x0904 + +#define SOLO_VD_DEINTERLACE 0x0908 +#define SOLO_VD_DEINTERLACE_THRESHOLD(n) ((n)<<8) +#define SOLO_VD_DEINTERLACE_EDGE_VALUE(n) ((n)<<0) + +#define SOLO_VD_CODE_ADR 0x090C + +#define SOLO_VD_CTRL 0x0910 +#define SOLO_VD_OPER_ON (1<<31) +#define SOLO_VD_MAX_ITEM(n) ((n)<<0) + +#define SOLO_VD_STATUS0 0x0920 +#define SOLO_VD_STATUS0_INTR_ACK (1<<22) +#define SOLO_VD_STATUS0_INTR_EMPTY (1<<21) +#define SOLO_VD_STATUS0_INTR_ERR (1<<20) + +#define SOLO_VD_STATUS1 0x0924 + +#define SOLO_VD_IDX0 0x0930 +#define SOLO_VD_IDX_INTERLACE (1<<30) +#define SOLO_VD_IDX_CHANNEL(n) ((n)<<24) +#define SOLO_VD_IDX_SIZE(n) ((n)<<0) + +#define SOLO_VD_IDX1 0x0934 +#define SOLO_VD_IDX_SRC_SCALE(n) ((n)<<28) +#define SOLO_VD_IDX_WINDOW(n) ((n)<<24) +#define SOLO_VD_IDX_DEINTERLACE (1<<16) +#define SOLO_VD_IDX_H_BLOCK(n) ((n)<<8) +#define SOLO_VD_IDX_V_BLOCK(n) ((n)<<0) + +#define SOLO_VD_IDX2 0x0938 +#define SOLO_VD_IDX_REF_BASE_SIDE (1<<31) +#define SOLO_VD_IDX_REF_BASE(n) (((n)>>16)&0xffff) + +#define SOLO_VD_IDX3 0x093C +#define SOLO_VD_IDX_DISP_SCALE(n) ((n)<<28) +#define SOLO_VD_IDX_INTERLACE_WR (1<<27) +#define SOLO_VD_IDX_INTERPOL (1<<26) +#define SOLO_VD_IDX_HOR2X (1<<25) +#define SOLO_VD_IDX_OFFSET_X(n) ((n)<<12) +#define SOLO_VD_IDX_OFFSET_Y(n) ((n)<<0) + +#define SOLO_VD_IDX4 0x0940 +#define SOLO_VD_IDX_DEC_WR_PAGE(n) ((n)<<8) +#define SOLO_VD_IDX_DISP_RD_PAGE(n) ((n)<<0) + +#define SOLO_VD_WR_PAGE(n) (0x03F0 + ((n) * 4)) + + +#define SOLO_GPIO_CONFIG_0 0x0B00 +#define SOLO_GPIO_CONFIG_1 0x0B04 +#define SOLO_GPIO_DATA_OUT 0x0B08 +#define SOLO_GPIO_DATA_IN 0x0B0C +#define SOLO_GPIO_INT_ACK_STA 0x0B10 +#define SOLO_GPIO_INT_ENA 0x0B14 +#define SOLO_GPIO_INT_CFG_0 0x0B18 +#define SOLO_GPIO_INT_CFG_1 0x0B1C + + +#define SOLO_IIC_CFG 0x0B20 +#define SOLO_IIC_ENABLE (1<<8) +#define SOLO_IIC_PRESCALE(n) ((n)<<0) + +#define SOLO_IIC_CTRL 0x0B24 +#define SOLO_IIC_AUTO_CLEAR (1<<20) +#define SOLO_IIC_STATE_RX_ACK (1<<19) +#define SOLO_IIC_STATE_BUSY (1<<18) +#define SOLO_IIC_STATE_SIG_ERR (1<<17) +#define SOLO_IIC_STATE_TRNS (1<<16) +#define SOLO_IIC_CH_SET(n) ((n)<<5) +#define SOLO_IIC_ACK_EN (1<<4) +#define SOLO_IIC_START (1<<3) +#define SOLO_IIC_STOP (1<<2) +#define SOLO_IIC_READ (1<<1) +#define SOLO_IIC_WRITE (1<<0) + +#define SOLO_IIC_TXD 0x0B28 +#define SOLO_IIC_RXD 0x0B2C + +/* + * UART REGISTER + */ +#define SOLO_UART_CONTROL(n) (0x0BA0 + ((n)*0x20)) +#define SOLO_UART_CLK_DIV(n) ((n)<<24) +#define SOLO_MODEM_CTRL_EN (1<<20) +#define SOLO_PARITY_ERROR_DROP (1<<18) +#define SOLO_IRQ_ERR_EN (1<<17) +#define SOLO_IRQ_RX_EN (1<<16) +#define SOLO_IRQ_TX_EN (1<<15) +#define SOLO_RX_EN (1<<14) +#define SOLO_TX_EN (1<<13) +#define SOLO_UART_HALF_DUPLEX (1<<12) +#define SOLO_UART_LOOPBACK (1<<11) + +#define SOLO_BAUDRATE_230400 ((0<<9)|(0<<6)) +#define SOLO_BAUDRATE_115200 ((0<<9)|(1<<6)) +#define SOLO_BAUDRATE_57600 ((0<<9)|(2<<6)) +#define SOLO_BAUDRATE_38400 ((0<<9)|(3<<6)) +#define SOLO_BAUDRATE_19200 ((0<<9)|(4<<6)) +#define SOLO_BAUDRATE_9600 ((0<<9)|(5<<6)) +#define SOLO_BAUDRATE_4800 ((0<<9)|(6<<6)) +#define SOLO_BAUDRATE_2400 ((1<<9)|(6<<6)) +#define SOLO_BAUDRATE_1200 ((2<<9)|(6<<6)) +#define SOLO_BAUDRATE_300 ((3<<9)|(6<<6)) + +#define SOLO_UART_DATA_BIT_8 (3<<4) +#define SOLO_UART_DATA_BIT_7 (2<<4) +#define SOLO_UART_DATA_BIT_6 (1<<4) +#define SOLO_UART_DATA_BIT_5 (0<<4) + +#define SOLO_UART_STOP_BIT_1 (0<<2) +#define SOLO_UART_STOP_BIT_2 (1<<2) + +#define SOLO_UART_PARITY_NONE (0<<0) +#define SOLO_UART_PARITY_EVEN (2<<0) +#define SOLO_UART_PARITY_ODD (3<<0) + +#define SOLO_UART_STATUS(n) (0x0BA4 + ((n)*0x20)) +#define SOLO_UART_CTS (1<<15) +#define SOLO_UART_RX_BUSY (1<<14) +#define SOLO_UART_OVERRUN (1<<13) +#define SOLO_UART_FRAME_ERR (1<<12) +#define SOLO_UART_PARITY_ERR (1<<11) +#define SOLO_UART_TX_BUSY (1<<5) + +#define SOLO_UART_RX_BUFF_CNT(stat) (((stat)>>6) & 0x1f) +#define SOLO_UART_RX_BUFF_SIZE 8 +#define SOLO_UART_TX_BUFF_CNT(stat) (((stat)>>0) & 0x1f) +#define SOLO_UART_TX_BUFF_SIZE 8 + +#define SOLO_UART_TX_DATA(n) (0x0BA8 + ((n)*0x20)) +#define SOLO_UART_TX_DATA_PUSH (1<<8) +#define SOLO_UART_RX_DATA(n) (0x0BAC + ((n)*0x20)) +#define SOLO_UART_RX_DATA_POP (1<<8) + +#define SOLO_TIMER_CLOCK_NUM 0x0be0 +#define SOLO_TIMER_WATCHDOG 0x0be4 +#define SOLO_TIMER_USEC 0x0be8 +#define SOLO_TIMER_SEC 0x0bec + +#define SOLO_AUDIO_CONTROL 0x0D00 +#define SOLO_AUDIO_ENABLE (1<<31) +#define SOLO_AUDIO_MASTER_MODE (1<<30) +#define SOLO_AUDIO_I2S_MODE (1<<29) +#define SOLO_AUDIO_I2S_LR_SWAP (1<<27) +#define SOLO_AUDIO_I2S_8BIT (1<<26) +#define SOLO_AUDIO_I2S_MULTI(n) ((n)<<24) +#define SOLO_AUDIO_MIX_9TO0 (1<<23) +#define SOLO_AUDIO_DEC_9TO0_VOL(n) ((n)<<20) +#define SOLO_AUDIO_MIX_19TO10 (1<<19) +#define SOLO_AUDIO_DEC_19TO10_VOL(n) ((n)<<16) +#define SOLO_AUDIO_MODE(n) ((n)<<0) +#define SOLO_AUDIO_SAMPLE 0x0D04 +#define SOLO_AUDIO_EE_MODE_ON (1<<30) +#define SOLO_AUDIO_EE_ENC_CH(ch) ((ch)<<25) +#define SOLO_AUDIO_BITRATE(n) ((n)<<16) +#define SOLO_AUDIO_CLK_DIV(n) ((n)<<0) +#define SOLO_AUDIO_FDMA_INTR 0x0D08 +#define SOLO_AUDIO_FDMA_INTERVAL(n) ((n)<<19) +#define SOLO_AUDIO_INTR_ORDER(n) ((n)<<16) +#define SOLO_AUDIO_FDMA_BASE(n) ((n)<<0) +#define SOLO_AUDIO_EVOL_0 0x0D0C +#define SOLO_AUDIO_EVOL_1 0x0D10 +#define SOLO_AUDIO_EVOL(ch, value) ((value)<<((ch)%10)) +#define SOLO_AUDIO_STA 0x0D14 + + +#define SOLO_WATCHDOG 0x0BE4 +#define WATCHDOG_STAT(status) (status<<8) +#define WATCHDOG_TIME(sec) (sec&0xff) + +#endif /* __SOLO6X10_REGISTERS_H */ diff --git a/drivers/staging/media/solo6x10/solo6x10.h b/drivers/staging/media/solo6x10/solo6x10.h new file mode 100644 index 0000000..abee721 --- /dev/null +++ b/drivers/staging/media/solo6x10/solo6x10.h @@ -0,0 +1,336 @@ +/* + * Copyright (C) 2010 Bluecherry, LLC www.bluecherrydvr.com + * Copyright (C) 2010 Ben Collins <bcollins@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/version.h> +#include <linux/pci.h> +#include <linux/i2c.h> +#include <linux/semaphore.h> +#include <linux/mutex.h> +#include <linux/list.h> +#include <linux/wait.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <asm/io.h> +#include <linux/atomic.h> +#include <linux/videodev2.h> +#include <media/v4l2-dev.h> +#include <media/videobuf-core.h> +#include "registers.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 */ + +#define SOLO6X10_NAME "solo6x10" + +#define SOLO_MAX_CHANNELS 16 + +/* Make sure these two match */ +#define SOLO6X10_VERSION "2.1.0" +#define SOLO6X10_VER_MAJOR 2 +#define SOLO6X10_VER_MINOR 0 +#define SOLO6X10_VER_SUB 0 +#define SOLO6X10_VER_NUM \ + KERNEL_VERSION(SOLO6X10_VER_MAJOR, SOLO6X10_VER_MINOR, SOLO6X10_VER_SUB) + +#define FLAGS_6110 1 + +/* + * 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 +/* MPEG and JPEG share the same interrupt and locks so they must be together + * in the same dma channel. */ +#define SOLO_P2M_DMA_ID_MP4E 0 +#define SOLO_P2M_DMA_ID_JPEG 0 +#define SOLO_P2M_DMA_ID_MP4D 1 +#define SOLO_P2M_DMA_ID_G723D 1 +#define SOLO_P2M_DMA_ID_DISP 2 +#define SOLO_P2M_DMA_ID_OSG 2 +#define SOLO_P2M_DMA_ID_G723E 3 +#define SOLO_P2M_DMA_ID_VIN 3 + +/* Encoder standard modes */ +#define SOLO_ENC_MODE_CIF 2 +#define SOLO_ENC_MODE_HD1 1 +#define SOLO_ENC_MODE_D1 9 + +#define SOLO_DEFAULT_GOP 30 +#define SOLO_DEFAULT_QP 3 + +/* There is 8MB memory available for solo to buffer MPEG4 frames. + * This gives us 512 * 16kbyte queues. */ +#define SOLO_NR_RING_BUFS 512 + +#define SOLO_CLOCK_MHZ 108 + +#ifndef V4L2_BUF_FLAG_MOTION_ON +#define V4L2_BUF_FLAG_MOTION_ON 0x0400 +#define V4L2_BUF_FLAG_MOTION_DETECTED 0x0800 +#endif +#ifndef V4L2_CID_MOTION_ENABLE +#define PRIVATE_CIDS +#define V4L2_CID_MOTION_ENABLE (V4L2_CID_PRIVATE_BASE+0) +#define V4L2_CID_MOTION_THRESHOLD (V4L2_CID_PRIVATE_BASE+1) +#define V4L2_CID_MOTION_TRACE (V4L2_CID_PRIVATE_BASE+2) +#endif + +enum SOLO_I2C_STATE { + IIC_STATE_IDLE, + IIC_STATE_START, + IIC_STATE_READ, + IIC_STATE_WRITE, + IIC_STATE_STOP +}; + +struct p2m_desc { + u32 ctrl; + u32 ext; + u32 ta; + u32 fa; +}; + +struct solo_p2m_dev { + struct mutex mutex; + struct completion completion; + int error; +}; + +#define OSD_TEXT_MAX 30 + +enum solo_enc_types { + SOLO_ENC_TYPE_STD, + SOLO_ENC_TYPE_EXT, +}; + +struct solo_enc_dev { + struct solo_dev *solo_dev; + /* V4L2 Items */ + struct video_device *vfd; + /* General accounting */ + wait_queue_head_t thread_wait; + spinlock_t lock; + atomic_t readers; + u8 ch; + u8 mode, gop, qp, interlaced, interval; + u8 reset_gop; + u8 bw_weight; + u8 motion_detected; + u16 motion_thresh; + u16 width; + u16 height; + char osd_text[OSD_TEXT_MAX + 1]; +}; + +struct solo_enc_buf { + u8 vop; + u8 ch; + enum solo_enc_types type; + u32 off; + u32 size; + u32 jpeg_off; + u32 jpeg_size; + struct timeval ts; +}; + +/* The SOLO6x10 PCI Device */ +struct solo_dev { + /* General stuff */ + struct pci_dev *pdev; + u8 __iomem *reg_base; + int nr_chans; + int nr_ext; + u32 flags; + u32 irq_mask; + u32 motion_mask; + spinlock_t reg_io_lock; + + /* 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]; + + /* V4L2 Display items */ + struct video_device *vfd; + unsigned int erasing; + unsigned int frame_blank; + u8 cur_disp_ch; + wait_queue_head_t disp_thread_wait; + + /* V4L2 Encoder items */ + struct solo_enc_dev *v4l2_enc[SOLO_MAX_CHANNELS]; + u16 enc_bw_remain; + /* IDX into hw mp4 encoder */ + u8 enc_idx; + /* Our software ring of enc buf references */ + u16 enc_wr_idx; + struct solo_enc_buf enc_buf[SOLO_NR_RING_BUFS]; + + /* Current video settings */ + u32 video_type; + u16 video_hsize, video_vsize; + u16 vout_hstart, vout_vstart; + u16 vin_hstart, vin_vstart; + u8 fps; + + /* Audio components */ + struct snd_card *snd_card; + struct snd_pcm *snd_pcm; + atomic_t snd_users; + int g723_hw_idx; +}; + +static inline u32 solo_reg_read(struct 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); +} + +void solo_irq_on(struct solo_dev *solo_dev, u32 mask); +void solo_irq_off(struct solo_dev *solo_dev, u32 mask); + +/* Init/exit routeines 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); +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); +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, u32 status); +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, u8 id, int wr, + dma_addr_t dma_addr, u32 ext_addr, u32 size); +int solo_p2m_dma(struct solo_dev *solo_dev, u8 id, int wr, + void *sys_addr, u32 ext_addr, u32 size); +int solo_p2m_dma_sg(struct solo_dev *solo_dev, u8 id, + struct p2m_desc *pdesc, int wr, + struct scatterlist *sglist, u32 sg_off, + u32 ext_addr, u32 size); +void solo_p2m_push_desc(struct 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, u8 id, + struct p2m_desc *desc, int desc_count); + +/* Set the threshold for motion detection */ +void solo_set_motion_threshold(struct solo_dev *solo_dev, u8 ch, u16 val); +#define SOLO_DEF_MOT_THRESH 0x0300 + +/* Write text on OSD */ +int solo_osd_print(struct solo_enc_dev *solo_enc); + +#endif /* __SOLO6X10_H */ diff --git a/drivers/staging/media/solo6x10/tw28.c b/drivers/staging/media/solo6x10/tw28.c new file mode 100644 index 0000000..db56b42 --- /dev/null +++ b/drivers/staging/media/solo6x10/tw28.c @@ -0,0 +1,821 @@ +/* + * Copyright (C) 2010 Bluecherry, LLC www.bluecherrydvr.com + * Copyright (C) 2010 Ben Collins <bcollins@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 "solo6x10.h" +#include "tw28.h" + +/* XXX: Some of these values are masked into an 8-bit regs, and shifted + * around for other 8-bit regs. What are the magic bits in these values? */ +#define DEFAULT_HDELAY_NTSC (32 - 4) +#define DEFAULT_HACTIVE_NTSC (720 + 16) +#define DEFAULT_VDELAY_NTSC (7 - 2) +#define DEFAULT_VACTIVE_NTSC (240 + 4) + +#define DEFAULT_HDELAY_PAL (32 + 4) +#define DEFAULT_HACTIVE_PAL (864-DEFAULT_HDELAY_PAL) +#define DEFAULT_VDELAY_PAL (6) +#define DEFAULT_VACTIVE_PAL (312-DEFAULT_VDELAY_PAL) + +static u8 tbl_tw2864_template[] = { + 0x00, 0x00, 0x80, 0x10, 0x80, 0x80, 0x00, 0x02, /* 0x00 */ + 0x12, 0xf5, 0x09, 0xd0, 0x00, 0x00, 0x00, 0x7f, + 0x00, 0x00, 0x80, 0x10, 0x80, 0x80, 0x00, 0x02, /* 0x10 */ + 0x12, 0xf5, 0x09, 0xd0, 0x00, 0x00, 0x00, 0x7f, + 0x00, 0x00, 0x80, 0x10, 0x80, 0x80, 0x00, 0x02, /* 0x20 */ + 0x12, 0xf5, 0x09, 0xd0, 0x00, 0x00, 0x00, 0x7f, + 0x00, 0x00, 0x80, 0x10, 0x80, 0x80, 0x00, 0x02, /* 0x30 */ + 0x12, 0xf5, 0x09, 0xd0, 0x00, 0x00, 0x00, 0x7f, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x40 */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x50 */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x60 */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x70 */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA3, 0x00, + 0x00, 0x02, 0x00, 0xcc, 0x00, 0x80, 0x44, 0x50, /* 0x80 */ + 0x22, 0x01, 0xd8, 0xbc, 0xb8, 0x44, 0x38, 0x00, + 0x00, 0x78, 0x72, 0x3e, 0x14, 0xa5, 0xe4, 0x05, /* 0x90 */ + 0x00, 0x28, 0x44, 0x44, 0xa0, 0x88, 0x5a, 0x01, + 0x08, 0x08, 0x08, 0x08, 0x1a, 0x1a, 0x1a, 0x1a, /* 0xa0 */ + 0x00, 0x00, 0x00, 0xf0, 0xf0, 0xf0, 0xf0, 0x44, + 0x44, 0x0a, 0x00, 0xff, 0xef, 0xef, 0xef, 0xef, /* 0xb0 */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0xc0 */ + 0x00, 0x00, 0x55, 0x00, 0xb1, 0xe4, 0x40, 0x00, + 0x77, 0x77, 0x01, 0x13, 0x57, 0x9b, 0xdf, 0x20, /* 0xd0 */ + 0x64, 0xa8, 0xec, 0xd1, 0x0f, 0x11, 0x11, 0x81, + 0x10, 0xe0, 0xbb, 0xbb, 0x00, 0x11, 0x00, 0x00, /* 0xe0 */ + 0x11, 0x00, 0x00, 0x11, 0x00, 0x00, 0x11, 0x00, + 0x83, 0xb5, 0x09, 0x78, 0x85, 0x00, 0x01, 0x20, /* 0xf0 */ + 0x64, 0x11, 0x40, 0xaf, 0xff, 0x00, 0x00, 0x00, +}; + +static 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 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 */ + if (i >= 0xb8 && i <= 0xc1) + continue; + if ((i & ~0x30) == 0x00 || + (i & ~0x30) == 0x0c || + (i & ~0x30) == 0x0d) + continue; + if (i >= 0xc4 && i <= 0xc7) + continue; + if (i == 0xfd) + 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[sizeof(tbl_tw2864_template)]; + int i; + + memcpy(tbl_tw2864_common, tbl_tw2864_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; + } + } + + /* NTSC or PAL */ + if (solo_dev->video_type == SOLO_VO_FMT_TYPE_PAL) { + for (i = 0; i < 4; i++) { + tbl_tw2864_common[0x07 | (i << 4)] |= 0x10; + tbl_tw2864_common[0x08 | (i << 4)] |= 0x06; + tbl_tw2864_common[0x0a | (i << 4)] |= 0x08; + tbl_tw2864_common[0x0b | (i << 4)] |= 0x13; + tbl_tw2864_common[0x0e | (i << 4)] |= 0x01; + } + tbl_tw2864_common[0x9d] = 0x90; + tbl_tw2864_common[0xf3] = 0x00; + tbl_tw2864_common[0xf4] = 0xa0; + } + + for (i = 0; i < 0xff; i++) { + /* Skip read only registers */ + if (i >= 0xb8 && i <= 0xc1) + continue; + if ((i & ~0x30) == 0x00 || + (i & ~0x30) == 0x0c || + (i & ~0x30) == 0x0d) + continue; + if (i == 0x74 || i == 0x77 || i == 0x78 || + i == 0x79 || i == 0x7a) + continue; + if (i == 0xfd) + continue; + + tw_write_and_verify(solo_dev, dev_addr, i, + tbl_tw2864_common[i]); + } + + return 0; +} + +static int tw2815_setup(struct 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 saa7128_setup(struct solo_dev *solo_dev) +{ + int i; + unsigned char regs[128] = { + 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x1C, 0x2B, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, + 0x59, 0x1d, 0x75, 0x3f, 0x06, 0x3f, 0x00, 0x00, + 0x1c, 0x33, 0x00, 0x3f, 0x00, 0x00, 0x3f, 0x00, + 0x1a, 0x1a, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x68, 0x10, 0x97, 0x4c, 0x18, + 0x9b, 0x93, 0x9f, 0xff, 0x7c, 0x34, 0x3f, 0x3f, + 0x3f, 0x83, 0x83, 0x80, 0x0d, 0x0f, 0xc3, 0x06, + 0x02, 0x80, 0x71, 0x77, 0xa7, 0x67, 0x66, 0x2e, + 0x7b, 0x11, 0x4f, 0x1f, 0x7c, 0xf0, 0x21, 0x77, + 0x41, 0x88, 0x41, 0x12, 0xed, 0x10, 0x10, 0x00, + 0x41, 0xc3, 0x00, 0x3e, 0xb8, 0x02, 0x00, 0x00, + 0x00, 0x00, 0x08, 0xff, 0x80, 0x00, 0xff, 0xff, + }; + + regs[0x7A] = FIRST_ACTIVE_LINE & 0xff; + regs[0x7B] = LAST_ACTIVE_LINE & 0xff; + regs[0x7C] = ((1 << 7) | + (((LAST_ACTIVE_LINE >> 8) & 1) << 6) | + (((FIRST_ACTIVE_LINE >> 8) & 1) << 4)); + + /* PAL: XXX: We could do a second set of regs to avoid this */ + if (solo_dev->video_type != SOLO_VO_FMT_TYPE_NTSC) { + regs[0x28] = 0xE1; + + regs[0x5A] = 0x0F; + regs[0x61] = 0x02; + regs[0x62] = 0x35; + regs[0x63] = 0xCB; + regs[0x64] = 0x8A; + regs[0x65] = 0x09; + regs[0x66] = 0x2A; + + regs[0x6C] = 0xf1; + regs[0x6E] = 0x20; + + regs[0x7A] = 0x06 + 12; + regs[0x7b] = 0x24 + 12; + regs[0x7c] |= 1 << 6; + } + + /* First 0x25 bytes are read-only? */ + for (i = 0x26; i < 128; i++) { + if (i == 0x60 || i == 0x7D) + continue; + solo_i2c_writebyte(solo_dev, SOLO_I2C_SAA, 0x46, i, regs[i]); + } + + return; +} + +int solo_tw28_init(struct solo_dev *solo_dev) +{ + int i; + u8 value; + + /* Detect techwell chip type */ + for (i = 0; i < TW_NUM_CHIP; i++) { + value = solo_i2c_readbyte(solo_dev, SOLO_I2C_TW, + TW_CHIP_OFFSET_ADDR(i), 0xFF); + + switch (value >> 3) { + case 0x18: + 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) + return -EINVAL; + + saa7128_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)); + } + + dev_info(&solo_dev->pdev->dev, "Initialized %d tw28xx chip%s:", + solo_dev->tw28_cnt, solo_dev->tw28_cnt == 1 ? "" : "s"); + + if (solo_dev->tw2865) + printk(" tw2865[%d]", hweight32(solo_dev->tw2865)); + if (solo_dev->tw2864) + printk(" tw2864[%d]", hweight32(solo_dev->tw2864)); + if (solo_dev->tw2815) + printk(" tw2815[%d]", hweight32(solo_dev->tw2815)); + printk("\n"); + + return 0; +} + +/* + * We accessed the video status signal in the Techwell chip through + * iic/i2c because the video status reported by register REG_VI_STATUS1 + * (address 0x012C) of the SOLO6010 chip doesn't give the correct video + * status signal values. + */ +int tw28_get_video_status(struct 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 + +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 (val > 0x0f || val < 0) + return -ERANGE; + if (is_tw286x(solo_dev, chip_num)) { + u8 v = solo_i2c_readbyte(solo_dev, SOLO_I2C_TW, + TW_CHIP_OFFSET_ADDR(chip_num), + TW286x_SHARPNESS(chip_num)); + v &= 0xf0; + v |= val; + solo_i2c_writebyte(solo_dev, SOLO_I2C_TW, + TW_CHIP_OFFSET_ADDR(chip_num), + TW286x_SHARPNESS(chip_num), v); + } else if (val != 0) + return -ERANGE; + break; + + case V4L2_CID_HUE: + if (is_tw286x(solo_dev, chip_num)) + sval = val - 128; + else + sval = (char)val; + tw_writebyte(solo_dev, chip_num, TW286x_HUE_ADDR(ch), + TW_HUE_ADDR(ch), sval); + + break; + + case V4L2_CID_SATURATION: + if (is_tw286x(solo_dev, chip_num)) { + solo_i2c_writebyte(solo_dev, SOLO_I2C_TW, + TW_CHIP_OFFSET_ADDR(chip_num), + TW286x_SATURATIONU_ADDR(ch), val); + } + tw_writebyte(solo_dev, chip_num, TW286x_SATURATIONV_ADDR(ch), + TW_SATURATION_ADDR(ch), val); + + break; + + case V4L2_CID_CONTRAST: + tw_writebyte(solo_dev, chip_num, TW286x_CONTRAST_ADDR(ch), + TW_CONTRAST_ADDR(ch), val); + break; + + case V4L2_CID_BRIGHTNESS: + if (is_tw286x(solo_dev, chip_num)) + sval = val - 128; + else + sval = (char)val; + tw_writebyte(solo_dev, chip_num, TW286x_BRIGHTNESS_ADDR(ch), + TW_BRIGHTNESS_ADDR(ch), sval); + + break; + default: + return -EINVAL; + } + + return 0; +} + +int tw28_get_ctrl_val(struct 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/tw28.h b/drivers/staging/media/solo6x10/tw28.h new file mode 100644 index 0000000..a44a03a --- /dev/null +++ b/drivers/staging/media/solo6x10/tw28.h @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2010 Bluecherry, LLC www.bluecherrydvr.com + * Copyright (C) 2010 Ben Collins <bcollins@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); + +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/v4l2-enc.c b/drivers/staging/media/solo6x10/v4l2-enc.c new file mode 100644 index 0000000..bee7280 --- /dev/null +++ b/drivers/staging/media/solo6x10/v4l2-enc.c @@ -0,0 +1,1825 @@ +/* + * Copyright (C) 2010 Bluecherry, LLC www.bluecherrydvr.com + * Copyright (C) 2010 Ben Collins <bcollins@bluecherry.net> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/kthread.h> +#include <linux/freezer.h> +#include <media/v4l2-ioctl.h> +#include <media/v4l2-common.h> +#include <media/videobuf-dma-sg.h> +#include "solo6x10.h" +#include "tw28.h" +#include "jpeg.h" + +#define MIN_VID_BUFFERS 4 +#define FRAME_BUF_SIZE (128 * 1024) +#define MP4_QS 16 + +static int solo_enc_thread(void *data); + +extern unsigned video_nr; + +struct solo_enc_fh { + struct solo_enc_dev *enc; + u32 fmt; + u16 rd_idx; + u8 enc_on; + enum solo_enc_types type; + struct videobuf_queue vidq; + struct list_head vidq_active; + struct task_struct *kthread; + struct p2m_desc desc[SOLO_NR_P2M_DESC]; +}; + +static const u32 solo_user_ctrls[] = { + V4L2_CID_BRIGHTNESS, + V4L2_CID_CONTRAST, + V4L2_CID_SATURATION, + V4L2_CID_HUE, + V4L2_CID_SHARPNESS, + 0 +}; + +static const u32 solo_mpeg_ctrls[] = { + V4L2_CID_MPEG_VIDEO_ENCODING, + V4L2_CID_MPEG_VIDEO_GOP_SIZE, + 0 +}; + +static const u32 solo_private_ctrls[] = { + V4L2_CID_MOTION_ENABLE, + V4L2_CID_MOTION_THRESHOLD, + 0 +}; + +static const u32 solo_fmtx_ctrls[] = { + V4L2_CID_RDS_TX_RADIO_TEXT, + 0 +}; + +static const u32 *solo_ctrl_classes[] = { + solo_user_ctrls, + solo_mpeg_ctrls, + solo_fmtx_ctrls, + solo_private_ctrls, + NULL +}; + +static int solo_is_motion_on(struct solo_enc_dev *solo_enc) +{ + struct solo_dev *solo_dev = solo_enc->solo_dev; + u8 ch = solo_enc->ch; + + if (solo_dev->motion_mask & (1 << ch)) + return 1; + return 0; +} + +static void solo_motion_toggle(struct solo_enc_dev *solo_enc, int on) +{ + struct solo_dev *solo_dev = solo_enc->solo_dev; + u8 ch = solo_enc->ch; + + spin_lock(&solo_enc->lock); + + if (on) + solo_dev->motion_mask |= (1 << ch); + else + solo_dev->motion_mask &= ~(1 << ch); + + /* Do this regardless of if we are turning on or off */ + solo_reg_write(solo_enc->solo_dev, SOLO_VI_MOT_CLEAR, + 1 << solo_enc->ch); + solo_enc->motion_detected = 0; + + solo_reg_write(solo_dev, SOLO_VI_MOT_ADR, + SOLO_VI_MOTION_EN(solo_dev->motion_mask) | + (SOLO_MOTION_EXT_ADDR(solo_dev) >> 16)); + + if (solo_dev->motion_mask) + solo_irq_on(solo_dev, SOLO_IRQ_MOTION); + else + solo_irq_off(solo_dev, SOLO_IRQ_MOTION); + + spin_unlock(&solo_enc->lock); +} + +/* Should be called with solo_enc->lock held */ +static void solo_update_mode(struct solo_enc_dev *solo_enc) +{ + struct solo_dev *solo_dev = solo_enc->solo_dev; + + assert_spin_locked(&solo_enc->lock); + + solo_enc->interlaced = (solo_enc->mode & 0x08) ? 1 : 0; + solo_enc->bw_weight = max(solo_dev->fps / solo_enc->interval, 1); + + switch (solo_enc->mode) { + case SOLO_ENC_MODE_CIF: + solo_enc->width = solo_dev->video_hsize >> 1; + solo_enc->height = solo_dev->video_vsize; + break; + case SOLO_ENC_MODE_D1: + solo_enc->width = solo_dev->video_hsize; + solo_enc->height = solo_dev->video_vsize << 1; + solo_enc->bw_weight <<= 2; + break; + default: + WARN(1, "mode is unknown\n"); + } +} + +/* Should be called with solo_enc->lock held */ +static int solo_enc_on(struct solo_enc_fh *fh) +{ + struct solo_enc_dev *solo_enc = fh->enc; + u8 ch = solo_enc->ch; + struct solo_dev *solo_dev = solo_enc->solo_dev; + u8 interval; + + assert_spin_locked(&solo_enc->lock); + + if (fh->enc_on) + return 0; + + solo_update_mode(solo_enc); + + /* Make sure to bw check on first reader */ + if (!atomic_read(&solo_enc->readers)) { + if (solo_enc->bw_weight > solo_dev->enc_bw_remain) + return -EBUSY; + else + solo_dev->enc_bw_remain -= solo_enc->bw_weight; + } + + fh->enc_on = 1; + fh->rd_idx = solo_enc->solo_dev->enc_wr_idx; + + if (fh->type == SOLO_ENC_TYPE_EXT) + solo_reg_write(solo_dev, SOLO_CAP_CH_COMP_ENA_E(ch), 1); + + if (atomic_inc_return(&solo_enc->readers) > 1) + return 0; + + /* Disable all encoding for this channel */ + solo_reg_write(solo_dev, SOLO_CAP_CH_SCALE(ch), 0); + + /* Common for both std and ext encoding */ + solo_reg_write(solo_dev, SOLO_VE_CH_INTL(ch), + solo_enc->interlaced ? 1 : 0); + + if (solo_enc->interlaced) + interval = solo_enc->interval - 1; + else + interval = solo_enc->interval; + + /* Standard encoding only */ + solo_reg_write(solo_dev, SOLO_VE_CH_GOP(ch), solo_enc->gop); + solo_reg_write(solo_dev, SOLO_VE_CH_QP(ch), solo_enc->qp); + solo_reg_write(solo_dev, SOLO_CAP_CH_INTV(ch), interval); + + /* Extended encoding only */ + solo_reg_write(solo_dev, SOLO_VE_CH_GOP_E(ch), solo_enc->gop); + solo_reg_write(solo_dev, SOLO_VE_CH_QP_E(ch), solo_enc->qp); + solo_reg_write(solo_dev, SOLO_CAP_CH_INTV_E(ch), interval); + + /* Enables the standard encoder */ + solo_reg_write(solo_dev, SOLO_CAP_CH_SCALE(ch), solo_enc->mode); + + /* Settle down Beavis... */ + mdelay(10); + + return 0; +} + +static void solo_enc_off(struct solo_enc_fh *fh) +{ + struct solo_enc_dev *solo_enc = fh->enc; + struct solo_dev *solo_dev = solo_enc->solo_dev; + + if (!fh->enc_on) + return; + + if (fh->kthread) { + kthread_stop(fh->kthread); + fh->kthread = NULL; + } + + solo_dev->enc_bw_remain += solo_enc->bw_weight; + fh->enc_on = 0; + + if (atomic_dec_return(&solo_enc->readers) > 0) + return; + + solo_reg_write(solo_dev, SOLO_CAP_CH_SCALE(solo_enc->ch), 0); + solo_reg_write(solo_dev, SOLO_CAP_CH_COMP_ENA_E(solo_enc->ch), 0); +} + +static int solo_start_fh_thread(struct solo_enc_fh *fh) +{ + struct solo_enc_dev *solo_enc = fh->enc; + + fh->kthread = kthread_run(solo_enc_thread, fh, SOLO6X10_NAME "_enc"); + + /* Oops, we had a problem */ + if (IS_ERR(fh->kthread)) { + spin_lock(&solo_enc->lock); + solo_enc_off(fh); + spin_unlock(&solo_enc->lock); + + return PTR_ERR(fh->kthread); + } + + return 0; +} + +static void enc_reset_gop(struct solo_dev *solo_dev, u8 ch) +{ + BUG_ON(ch >= solo_dev->nr_chans); + solo_reg_write(solo_dev, SOLO_VE_CH_GOP(ch), 1); + solo_dev->v4l2_enc[ch]->reset_gop = 1; +} + +static int enc_gop_reset(struct solo_dev *solo_dev, u8 ch, u8 vop) +{ + BUG_ON(ch >= solo_dev->nr_chans); + if (!solo_dev->v4l2_enc[ch]->reset_gop) + return 0; + if (vop) + return 1; + solo_dev->v4l2_enc[ch]->reset_gop = 0; + solo_reg_write(solo_dev, SOLO_VE_CH_GOP(ch), + solo_dev->v4l2_enc[ch]->gop); + return 0; +} + +static void enc_write_sg(struct scatterlist *sglist, void *buf, int size) +{ + struct scatterlist *sg; + u8 *src = buf; + + for (sg = sglist; sg && size > 0; sg = sg_next(sg)) { + u8 *p = sg_virt(sg); + size_t len = sg_dma_len(sg); + int i; + + for (i = 0; i < len && size; i++) + p[i] = *(src++); + } +} + +static int enc_get_mpeg_dma_sg(struct solo_dev *solo_dev, + struct p2m_desc *desc, + struct scatterlist *sglist, int skip, + unsigned int off, unsigned int size) +{ + int ret; + + if (off > SOLO_MP4E_EXT_SIZE(solo_dev)) + return -EINVAL; + + if (off + size <= SOLO_MP4E_EXT_SIZE(solo_dev)) { + return solo_p2m_dma_sg(solo_dev, SOLO_P2M_DMA_ID_MP4E, + desc, 0, sglist, skip, + SOLO_MP4E_EXT_ADDR(solo_dev) + off, size); + } + + /* Buffer wrap */ + ret = solo_p2m_dma_sg(solo_dev, SOLO_P2M_DMA_ID_MP4E, desc, 0, + sglist, skip, SOLO_MP4E_EXT_ADDR(solo_dev) + off, + SOLO_MP4E_EXT_SIZE(solo_dev) - off); + + ret |= solo_p2m_dma_sg(solo_dev, SOLO_P2M_DMA_ID_MP4E, desc, 0, + sglist, skip + SOLO_MP4E_EXT_SIZE(solo_dev) - off, + SOLO_MP4E_EXT_ADDR(solo_dev), + size + off - SOLO_MP4E_EXT_SIZE(solo_dev)); + + return ret; +} + +static int enc_get_mpeg_dma_t(struct solo_dev *solo_dev, + dma_addr_t buf, unsigned int off, + unsigned int size) +{ + int ret; + + if (off > SOLO_MP4E_EXT_SIZE(solo_dev)) + return -EINVAL; + + if (off + size <= SOLO_MP4E_EXT_SIZE(solo_dev)) { + return solo_p2m_dma_t(solo_dev, SOLO_P2M_DMA_ID_MP4E, 0, buf, + SOLO_MP4E_EXT_ADDR(solo_dev) + off, size); + } + + /* Buffer wrap */ + ret = solo_p2m_dma_t(solo_dev, SOLO_P2M_DMA_ID_MP4E, 0, buf, + SOLO_MP4E_EXT_ADDR(solo_dev) + off, + SOLO_MP4E_EXT_SIZE(solo_dev) - off); + + ret |= solo_p2m_dma_t(solo_dev, SOLO_P2M_DMA_ID_MP4E, 0, + buf + SOLO_MP4E_EXT_SIZE(solo_dev) - off, + SOLO_MP4E_EXT_ADDR(solo_dev), + size + off - SOLO_MP4E_EXT_SIZE(solo_dev)); + + return ret; +} + +static int enc_get_mpeg_dma(struct solo_dev *solo_dev, void *buf, + unsigned int off, unsigned int size) +{ + int ret; + + dma_addr_t dma_addr = pci_map_single(solo_dev->pdev, buf, size, + PCI_DMA_FROMDEVICE); + ret = enc_get_mpeg_dma_t(solo_dev, dma_addr, off, size); + pci_unmap_single(solo_dev->pdev, dma_addr, size, PCI_DMA_FROMDEVICE); + + return ret; +} + +static int enc_get_jpeg_dma_sg(struct solo_dev *solo_dev, + struct p2m_desc *desc, + struct scatterlist *sglist, int skip, + unsigned int off, unsigned int size) +{ + int ret; + + if (off > SOLO_JPEG_EXT_SIZE(solo_dev)) + return -EINVAL; + + if (off + size <= SOLO_JPEG_EXT_SIZE(solo_dev)) { + return solo_p2m_dma_sg(solo_dev, SOLO_P2M_DMA_ID_JPEG, + desc, 0, sglist, skip, + SOLO_JPEG_EXT_ADDR(solo_dev) + off, size); + } + + /* Buffer wrap */ + ret = solo_p2m_dma_sg(solo_dev, SOLO_P2M_DMA_ID_JPEG, desc, 0, + sglist, skip, SOLO_JPEG_EXT_ADDR(solo_dev) + off, + SOLO_JPEG_EXT_SIZE(solo_dev) - off); + + ret |= solo_p2m_dma_sg(solo_dev, SOLO_P2M_DMA_ID_JPEG, desc, 0, + sglist, skip + SOLO_JPEG_EXT_SIZE(solo_dev) - off, + SOLO_JPEG_EXT_ADDR(solo_dev), + size + off - SOLO_JPEG_EXT_SIZE(solo_dev)); + + return ret; +} + +/* Returns true of __chk is within the first __range bytes of __off */ +#define OFF_IN_RANGE(__off, __range, __chk) \ + ((__off <= __chk) && ((__off + __range) >= __chk)) + +static void solo_jpeg_header(struct solo_enc_dev *solo_enc, + struct videobuf_dmabuf *vbuf) +{ + struct scatterlist *sg; + void *src = jpeg_header; + size_t copied = 0; + size_t to_copy = sizeof(jpeg_header); + + for (sg = vbuf->sglist; sg && copied < to_copy; sg = sg_next(sg)) { + size_t this_copy = min(sg_dma_len(sg), + (unsigned int)(to_copy - copied)); + u8 *p = sg_virt(sg); + + memcpy(p, src + copied, this_copy); + + if (OFF_IN_RANGE(copied, this_copy, SOF0_START + 5)) + p[(SOF0_START + 5) - copied] = + 0xff & (solo_enc->height >> 8); + if (OFF_IN_RANGE(copied, this_copy, SOF0_START + 6)) + p[(SOF0_START + 6) - copied] = 0xff & solo_enc->height; + if (OFF_IN_RANGE(copied, this_copy, SOF0_START + 7)) + p[(SOF0_START + 7) - copied] = + 0xff & (solo_enc->width >> 8); + if (OFF_IN_RANGE(copied, this_copy, SOF0_START + 8)) + p[(SOF0_START + 8) - copied] = 0xff & solo_enc->width; + + copied += this_copy; + } +} + +static int solo_fill_jpeg(struct solo_enc_fh *fh, struct solo_enc_buf *enc_buf, + struct videobuf_buffer *vb, + struct videobuf_dmabuf *vbuf) +{ + struct solo_dev *solo_dev = fh->enc->solo_dev; + int size = enc_buf->jpeg_size; + + /* Copy the header first (direct write) */ + solo_jpeg_header(fh->enc, vbuf); + + vb->size = size + sizeof(jpeg_header); + + /* Grab the jpeg frame */ + return enc_get_jpeg_dma_sg(solo_dev, fh->desc, vbuf->sglist, + sizeof(jpeg_header), + enc_buf->jpeg_off, size); +} + +static inline int vop_interlaced(__le32 *vh) +{ + return (__le32_to_cpu(vh[0]) >> 30) & 1; +} + +static inline u32 vop_size(__le32 *vh) +{ + return __le32_to_cpu(vh[0]) & 0xFFFFF; +} + +static inline u8 vop_hsize(__le32 *vh) +{ + return (__le32_to_cpu(vh[1]) >> 8) & 0xFF; +} + +static inline u8 vop_vsize(__le32 *vh) +{ + return __le32_to_cpu(vh[1]) & 0xFF; +} + +/* must be called with *bits % 8 = 0 */ +static void write_bytes(u8 **out, unsigned *bits, const u8 *src, unsigned count) +{ + memcpy(*out, src, count); + *out += count; + *bits += count * 8; +} + +static void write_bits(u8 **out, unsigned *bits, u32 value, unsigned count) +{ + + value <<= 32 - count; // shift to the right + + while (count--) { + **out <<= 1; + **out |= !!(value & (1 << 31)); /* MSB */ + value <<= 1; + if (++(*bits) % 8 == 0) + (*out)++; + } +} + +static void write_ue(u8 **out, unsigned *bits, unsigned value) /* H.264 only */ +{ + uint32_t max = 0, cnt = 0; + + while (value > max) { + max = (max + 2) * 2 - 2; + cnt++; + } + write_bits(out, bits, 1, cnt + 1); + write_bits(out, bits, ~(max - value), cnt); +} + +static void write_se(u8 **out, unsigned *bits, int value) /* H.264 only */ +{ + if (value <= 0) + write_ue(out, bits, -value * 2); + else + write_ue(out, bits, value * 2 - 1); +} + +static void write_mpeg4_end(u8 **out, unsigned *bits) +{ + write_bits(out, bits, 0, 1); + /* align on 32-bit boundary */ + if (*bits % 32) + write_bits(out, bits, 0xFFFFFFFF, 32 - *bits % 32); +} + +static void write_h264_end(u8 **out, unsigned *bits, int align) +{ + write_bits(out, bits, 1, 1); + while ((*bits) % 8) + write_bits(out, bits, 0, 1); + if (align) + while ((*bits) % 32) + write_bits(out, bits, 0, 1); +} + +static void mpeg4_write_vol(u8 **out, struct solo_dev *solo_dev, + __le32 *vh, unsigned fps, unsigned interval) +{ + static const u8 hdr[] = { + 0, 0, 1, 0x00 /* video_object_start_code */, + 0, 0, 1, 0x20 /* video_object_layer_start_code */ + }; + unsigned bits = 0; + unsigned width = vop_hsize(vh) << 4; + unsigned height = vop_vsize(vh) << 4; + unsigned interlaced = vop_interlaced(vh); + + write_bytes(out, &bits, hdr, sizeof(hdr)); + write_bits(out, &bits, 0, 1); /* random_accessible_vol */ + write_bits(out, &bits, 0x04, 8); /* video_object_type_indication: main */ + write_bits(out, &bits, 1, 1); /* is_object_layer_identifier */ + write_bits(out, &bits, 2, 4); /* video_object_layer_verid: table V2-39 */ + write_bits(out, &bits, 0, 3); /* video_object_layer_priority */ + if (solo_dev->video_type == SOLO_VO_FMT_TYPE_NTSC) + write_bits(out, &bits, 3, 4); /* aspect_ratio_info, assuming 4:3 */ + else + write_bits(out, &bits, 2, 4); + write_bits(out, &bits, 1, 1); /* vol_control_parameters */ + write_bits(out, &bits, 1, 2); /* chroma_format: 4:2:0 */ + write_bits(out, &bits, 1, 1); /* low_delay */ + write_bits(out, &bits, 0, 1); /* vbv_parameters */ + write_bits(out, &bits, 0, 2); /* video_object_layer_shape: rectangular */ + write_bits(out, &bits, 1, 1); /* marker_bit */ + write_bits(out, &bits, fps, 16); /* vop_time_increment_resolution */ + write_bits(out, &bits, 1, 1); /* marker_bit */ + write_bits(out, &bits, 1, 1); /* fixed_vop_rate */ + write_bits(out, &bits, interval, 15); /* fixed_vop_time_increment */ + write_bits(out, &bits, 1, 1); /* marker_bit */ + write_bits(out, &bits, width, 13); /* video_object_layer_width */ + write_bits(out, &bits, 1, 1); /* marker_bit */ + write_bits(out, &bits, height, 13); /* video_object_layer_height */ + write_bits(out, &bits, 1, 1); /* marker_bit */ + write_bits(out, &bits, interlaced, 1); /* interlaced */ + write_bits(out, &bits, 1, 1); /* obmc_disable */ + write_bits(out, &bits, 0, 2); /* sprite_enable */ + write_bits(out, &bits, 0, 1); /* not_8_bit */ + write_bits(out, &bits, 1, 0); /* quant_type */ + write_bits(out, &bits, 0, 1); /* load_intra_quant_mat */ + write_bits(out, &bits, 0, 1); /* load_nonintra_quant_mat */ + write_bits(out, &bits, 0, 1); /* quarter_sample */ + write_bits(out, &bits, 1, 1); /* complexity_estimation_disable */ + write_bits(out, &bits, 1, 1); /* resync_marker_disable */ + write_bits(out, &bits, 0, 1); /* data_partitioned */ + write_bits(out, &bits, 0, 1); /* newpred_enable */ + write_bits(out, &bits, 0, 1); /* reduced_resolution_vop_enable */ + write_bits(out, &bits, 0, 1); /* scalability */ + write_mpeg4_end(out, &bits); +} + +static void h264_write_vol(u8 **out, struct solo_dev *solo_dev, __le32 *vh) +{ + static const u8 sps[] = { + 0, 0, 0, 1 /* start code */, 0x67, 66 /* profile_idc */, + 0 /* constraints */, 30 /* level_idc */ + }; + static const u8 pps[] = { + 0, 0, 0, 1 /* start code */, 0x68 + }; + + unsigned bits = 0; + unsigned mbs_w = vop_hsize(vh); + unsigned mbs_h = vop_vsize(vh); + + write_bytes(out, &bits, sps, sizeof(sps)); + write_ue(out, &bits, 0); /* seq_parameter_set_id */ + write_ue(out, &bits, 5); /* log2_max_frame_num_minus4 */ + write_ue(out, &bits, 0); /* pic_order_cnt_type */ + write_ue(out, &bits, 6); /* log2_max_pic_order_cnt_lsb_minus4 */ + write_ue(out, &bits, 1); /* max_num_ref_frames */ + write_bits(out, &bits, 0, 1); /* gaps_in_frame_num_value_allowed_flag */ + write_ue(out, &bits, mbs_w - 1); /* pic_width_in_mbs_minus1 */ + write_ue(out, &bits, mbs_h - 1); /* pic_height_in_map_units_minus1 */ + write_bits(out, &bits, 1, 1); /* frame_mbs_only_flag */ + write_bits(out, &bits, 1, 1); /* direct_8x8_frame_field_flag */ + write_bits(out, &bits, 0, 1); /* frame_cropping_flag */ + write_bits(out, &bits, 0, 1); /* vui_parameters_present_flag */ + write_h264_end(out, &bits, 0); + + write_bytes(out, &bits, pps, sizeof(pps)); + write_ue(out, &bits, 0); /* pic_parameter_set_id */ + write_ue(out, &bits, 0); /* seq_parameter_set_id */ + write_bits(out, &bits, 0, 1); /* entropy_coding_mode_flag */ + write_bits(out, &bits, 0, 1); /* bottom_field_pic_order_in_frame_present_flag */ + write_ue(out, &bits, 0); /* num_slice_groups_minus1 */ + write_ue(out, &bits, 0); /* num_ref_idx_l0_default_active_minus1 */ + write_ue(out, &bits, 0); /* num_ref_idx_l1_default_active_minus1 */ + write_bits(out, &bits, 0, 1); /* weighted_pred_flag */ + write_bits(out, &bits, 0, 2); /* weighted_bipred_idc */ + write_se(out, &bits, 0); /* pic_init_qp_minus26 */ + write_se(out, &bits, 0); /* pic_init_qs_minus26 */ + write_se(out, &bits, 2); /* chroma_qp_index_offset */ + write_bits(out, &bits, 0, 1); /* deblocking_filter_control_present_flag */ + write_bits(out, &bits, 1, 1); /* constrained_intra_pred_flag */ + write_bits(out, &bits, 0, 1); /* redundant_pic_cnt_present_flag */ + write_h264_end(out, &bits, 1); +} + +static int solo_fill_mpeg(struct solo_enc_fh *fh, struct solo_enc_buf *enc_buf, + struct videobuf_buffer *vb, + struct videobuf_dmabuf *vbuf) +{ + struct solo_enc_dev *solo_enc = fh->enc; + struct solo_dev *solo_dev = solo_enc->solo_dev; + +#define VH_WORDS 16 +#define MAX_VOL_HEADER_LENGTH 64 + + __le32 vh[VH_WORDS]; + int ret; + int frame_size, frame_off; + int skip = 0; + + if (WARN_ON_ONCE(enc_buf->size <= sizeof(vh))) + return -EINVAL; + + /* First get the hardware vop header (not real mpeg) */ + ret = enc_get_mpeg_dma(solo_dev, vh, enc_buf->off, sizeof(vh)); + if (WARN_ON_ONCE(ret)) + return ret; + + if (WARN_ON_ONCE(vop_size(vh) > enc_buf->size)) + return -EINVAL; + + vb->width = vop_hsize(vh) << 4; + vb->height = vop_vsize(vh) << 4; + vb->size = vop_size(vh); + + /* If this is a key frame, add extra m4v header */ + if (!enc_buf->vop) { + u8 header[MAX_VOL_HEADER_LENGTH], *out = header; + + if (solo_dev->flags & FLAGS_6110) + h264_write_vol(&out, solo_dev, vh); + else + mpeg4_write_vol(&out, solo_dev, vh, + solo_dev->fps * 1000, + solo_enc->interval * 1000); + skip = out - header; + enc_write_sg(vbuf->sglist, header, skip); + /* Adjust the dma buffer past this header */ + vb->size += skip; + } + + /* Now get the actual mpeg payload */ + frame_off = (enc_buf->off + sizeof(vh)) % SOLO_MP4E_EXT_SIZE(solo_dev); + frame_size = enc_buf->size - sizeof(vh); + + ret = enc_get_mpeg_dma_sg(solo_dev, fh->desc, vbuf->sglist, + skip, frame_off, frame_size); + WARN_ON_ONCE(ret); + + return ret; +} + +static void solo_enc_fillbuf(struct solo_enc_fh *fh, + struct videobuf_buffer *vb) +{ + struct solo_enc_dev *solo_enc = fh->enc; + struct solo_dev *solo_dev = solo_enc->solo_dev; + struct solo_enc_buf *enc_buf = NULL; + struct videobuf_dmabuf *vbuf; + int ret; + int error = 1; + u16 idx = fh->rd_idx; + + while (idx != solo_dev->enc_wr_idx) { + struct solo_enc_buf *ebuf = &solo_dev->enc_buf[idx]; + + idx = (idx + 1) % SOLO_NR_RING_BUFS; + + if (ebuf->ch != solo_enc->ch) + continue; + + if (fh->fmt == V4L2_PIX_FMT_MPEG) { + if (fh->type == ebuf->type) { + enc_buf = ebuf; + break; + } + } else { + /* For mjpeg, keep reading to the newest frame */ + enc_buf = ebuf; + } + } + + fh->rd_idx = idx; + + if (WARN_ON_ONCE(!enc_buf)) + goto buf_err; + + if ((fh->fmt == V4L2_PIX_FMT_MPEG && + vb->bsize < enc_buf->size) || + (fh->fmt == V4L2_PIX_FMT_MJPEG && + vb->bsize < (enc_buf->jpeg_size + sizeof(jpeg_header)))) { + WARN_ON_ONCE(1); + goto buf_err; + } + + vbuf = videobuf_to_dma(vb); + if (WARN_ON_ONCE(!vbuf)) + goto buf_err; + + if (fh->fmt == V4L2_PIX_FMT_MPEG) + ret = solo_fill_mpeg(fh, enc_buf, vb, vbuf); + else + ret = solo_fill_jpeg(fh, enc_buf, vb, vbuf); + + if (!ret) + error = 0; + +buf_err: + if (error) { + vb->state = VIDEOBUF_ERROR; + } else { + vb->field_count++; + vb->ts = enc_buf->ts; + vb->state = VIDEOBUF_DONE; + } + + wake_up(&vb->done); + + return; +} + +static void solo_enc_thread_try(struct solo_enc_fh *fh) +{ + struct solo_enc_dev *solo_enc = fh->enc; + struct solo_dev *solo_dev = solo_enc->solo_dev; + struct videobuf_buffer *vb; + + for (;;) { + spin_lock(&solo_enc->lock); + + if (fh->rd_idx == solo_dev->enc_wr_idx) + break; + + if (list_empty(&fh->vidq_active)) + break; + + vb = list_first_entry(&fh->vidq_active, + struct videobuf_buffer, queue); + + if (!waitqueue_active(&vb->done)) + break; + + list_del(&vb->queue); + + spin_unlock(&solo_enc->lock); + + solo_enc_fillbuf(fh, vb); + } + + assert_spin_locked(&solo_enc->lock); + spin_unlock(&solo_enc->lock); +} + +static int solo_enc_thread(void *data) +{ + struct solo_enc_fh *fh = data; + struct solo_enc_dev *solo_enc = fh->enc; + DECLARE_WAITQUEUE(wait, current); + + set_freezable(); + add_wait_queue(&solo_enc->thread_wait, &wait); + + for (;;) { + long timeout = schedule_timeout_interruptible(HZ); + if (timeout == -ERESTARTSYS || kthread_should_stop()) + break; + solo_enc_thread_try(fh); + try_to_freeze(); + } + + remove_wait_queue(&solo_enc->thread_wait, &wait); + + return 0; +} + +void solo_motion_isr(struct solo_dev *solo_dev) +{ + u32 status; + int i; + + solo_reg_write(solo_dev, SOLO_IRQ_STAT, SOLO_IRQ_MOTION); + + status = solo_reg_read(solo_dev, SOLO_VI_MOT_STATUS); + + for (i = 0; i < solo_dev->nr_chans; i++) { + struct solo_enc_dev *solo_enc = solo_dev->v4l2_enc[i]; + + BUG_ON(solo_enc == NULL); + + if (solo_enc->motion_detected) + continue; + if (!(status & (1 << i))) + continue; + + solo_enc->motion_detected = 1; + } +} + +void solo_enc_v4l2_isr(struct solo_dev *solo_dev) +{ + struct solo_enc_buf *enc_buf; + u32 mpeg_current, mpeg_next, mpeg_size; + u32 jpeg_current, jpeg_next, jpeg_size; + u32 reg_mpeg_size; + u8 cur_q, vop_type; + u8 ch; + enum solo_enc_types enc_type; + + solo_reg_write(solo_dev, SOLO_IRQ_STAT, SOLO_IRQ_ENCODER); + + cur_q = ((solo_reg_read(solo_dev, SOLO_VE_STATE(11)) & 0xF) + 1) % MP4_QS; + + reg_mpeg_size = ((solo_reg_read(solo_dev, SOLO_VE_STATE(0)) & 0xFFFFF) + 64 + 8) & ~7; + + while (solo_dev->enc_idx != cur_q) { + mpeg_current = solo_reg_read(solo_dev, + SOLO_VE_MPEG4_QUE(solo_dev->enc_idx)); + jpeg_current = solo_reg_read(solo_dev, + SOLO_VE_JPEG_QUE(solo_dev->enc_idx)); + solo_dev->enc_idx = (solo_dev->enc_idx + 1) % MP4_QS; + mpeg_next = solo_reg_read(solo_dev, + SOLO_VE_MPEG4_QUE(solo_dev->enc_idx)); + jpeg_next = solo_reg_read(solo_dev, + SOLO_VE_JPEG_QUE(solo_dev->enc_idx)); + + ch = (mpeg_current >> 24) & 0x1f; + if (ch >= SOLO_MAX_CHANNELS) { + ch -= SOLO_MAX_CHANNELS; + enc_type = SOLO_ENC_TYPE_EXT; + } else + enc_type = SOLO_ENC_TYPE_STD; + + vop_type = (mpeg_current >> 29) & 3; + + mpeg_current &= 0x00ffffff; + mpeg_next &= 0x00ffffff; + jpeg_current &= 0x00ffffff; + jpeg_next &= 0x00ffffff; + + mpeg_size = (SOLO_MP4E_EXT_SIZE(solo_dev) + + mpeg_next - mpeg_current) % + SOLO_MP4E_EXT_SIZE(solo_dev); + + jpeg_size = (SOLO_JPEG_EXT_SIZE(solo_dev) + + jpeg_next - jpeg_current) % + SOLO_JPEG_EXT_SIZE(solo_dev); + + /* XXX I think this means we had a ring overflow? */ + if (mpeg_current > mpeg_next && mpeg_size != reg_mpeg_size) { + enc_reset_gop(solo_dev, ch); + continue; + } + + /* When resetting the GOP, skip frames until I-frame */ + if (enc_gop_reset(solo_dev, ch, vop_type)) + continue; + + enc_buf = &solo_dev->enc_buf[solo_dev->enc_wr_idx]; + + enc_buf->vop = vop_type; + enc_buf->ch = ch; + enc_buf->off = mpeg_current; + enc_buf->size = mpeg_size; + enc_buf->jpeg_off = jpeg_current; + enc_buf->jpeg_size = jpeg_size; + enc_buf->type = enc_type; + + do_gettimeofday(&enc_buf->ts); + + solo_dev->enc_wr_idx = (solo_dev->enc_wr_idx + 1) % + SOLO_NR_RING_BUFS; + + wake_up_interruptible(&solo_dev->v4l2_enc[ch]->thread_wait); + } + + return; +} + +static int solo_enc_buf_setup(struct videobuf_queue *vq, unsigned int *count, + unsigned int *size) +{ + *size = FRAME_BUF_SIZE; + + if (*count < MIN_VID_BUFFERS) + *count = MIN_VID_BUFFERS; + + return 0; +} + +static int solo_enc_buf_prepare(struct videobuf_queue *vq, + struct videobuf_buffer *vb, + enum v4l2_field field) +{ + struct solo_enc_fh *fh = vq->priv_data; + struct solo_enc_dev *solo_enc = fh->enc; + + vb->size = FRAME_BUF_SIZE; + if (vb->baddr != 0 && vb->bsize < vb->size) + return -EINVAL; + + /* These properties only change when queue is idle */ + vb->width = solo_enc->width; + vb->height = solo_enc->height; + vb->field = field; + + if (vb->state == VIDEOBUF_NEEDS_INIT) { + int rc = videobuf_iolock(vq, vb, NULL); + if (rc < 0) { + struct videobuf_dmabuf *dma = videobuf_to_dma(vb); + videobuf_dma_unmap(vq->dev, dma); + videobuf_dma_free(dma); + vb->state = VIDEOBUF_NEEDS_INIT; + return rc; + } + } + vb->state = VIDEOBUF_PREPARED; + + return 0; +} + +static void solo_enc_buf_queue(struct videobuf_queue *vq, + struct videobuf_buffer *vb) +{ + struct solo_enc_fh *fh = vq->priv_data; + + vb->state = VIDEOBUF_QUEUED; + list_add_tail(&vb->queue, &fh->vidq_active); + wake_up_interruptible(&fh->enc->thread_wait); +} + +static void solo_enc_buf_release(struct videobuf_queue *vq, + struct videobuf_buffer *vb) +{ + struct videobuf_dmabuf *dma = videobuf_to_dma(vb); + + videobuf_dma_unmap(vq->dev, dma); + videobuf_dma_free(dma); + vb->state = VIDEOBUF_NEEDS_INIT; +} + +static struct videobuf_queue_ops solo_enc_video_qops = { + .buf_setup = solo_enc_buf_setup, + .buf_prepare = solo_enc_buf_prepare, + .buf_queue = solo_enc_buf_queue, + .buf_release = solo_enc_buf_release, +}; + +static unsigned int solo_enc_poll(struct file *file, + struct poll_table_struct *wait) +{ + struct solo_enc_fh *fh = file->private_data; + + return videobuf_poll_stream(file, &fh->vidq, wait); +} + +static int solo_enc_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct solo_enc_fh *fh = file->private_data; + + return videobuf_mmap_mapper(&fh->vidq, vma); +} + +static int solo_enc_open(struct file *file) +{ + struct solo_enc_dev *solo_enc = video_drvdata(file); + struct solo_enc_fh *fh; + + fh = kzalloc(sizeof(*fh), GFP_KERNEL); + if (fh == NULL) + return -ENOMEM; + + fh->enc = solo_enc; + file->private_data = fh; + INIT_LIST_HEAD(&fh->vidq_active); + fh->fmt = V4L2_PIX_FMT_MPEG; + fh->type = SOLO_ENC_TYPE_STD; + + videobuf_queue_sg_init(&fh->vidq, &solo_enc_video_qops, + &solo_enc->solo_dev->pdev->dev, + &solo_enc->lock, + V4L2_BUF_TYPE_VIDEO_CAPTURE, + V4L2_FIELD_INTERLACED, + sizeof(struct videobuf_buffer), fh, NULL); + + return 0; +} + +static ssize_t solo_enc_read(struct file *file, char __user *data, + size_t count, loff_t *ppos) +{ + struct solo_enc_fh *fh = file->private_data; + struct solo_enc_dev *solo_enc = fh->enc; + + /* Make sure the encoder is on */ + if (!fh->enc_on) { + int ret; + + spin_lock(&solo_enc->lock); + ret = solo_enc_on(fh); + spin_unlock(&solo_enc->lock); + if (ret) + return ret; + + ret = solo_start_fh_thread(fh); + if (ret) + return ret; + } + + return videobuf_read_stream(&fh->vidq, data, count, ppos, 0, + file->f_flags & O_NONBLOCK); +} + +static int solo_enc_release(struct file *file) +{ + struct solo_enc_fh *fh = file->private_data; + struct solo_enc_dev *solo_enc = fh->enc; + + videobuf_stop(&fh->vidq); + videobuf_mmap_free(&fh->vidq); + + spin_lock(&solo_enc->lock); + solo_enc_off(fh); + spin_unlock(&solo_enc->lock); + + kfree(fh); + + return 0; +} + +static int solo_enc_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + struct solo_enc_fh *fh = priv; + struct solo_enc_dev *solo_enc = fh->enc; + struct 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->version = SOLO6X10_VER_NUM; + cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | + V4L2_CAP_READWRITE | + V4L2_CAP_STREAMING; + return 0; +} + +static int solo_enc_enum_input(struct file *file, void *priv, + struct v4l2_input *input) +{ + struct solo_enc_fh *fh = priv; + struct solo_enc_dev *solo_enc = fh->enc; + struct 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; + + if (solo_dev->video_type == SOLO_VO_FMT_TYPE_NTSC) + input->std = V4L2_STD_NTSC_M; + else + input->std = V4L2_STD_PAL_B; + + if (!tw28_get_video_status(solo_dev, solo_enc->ch)) + input->status = V4L2_IN_ST_NO_SIGNAL; + + return 0; +} + +static int solo_enc_set_input(struct file *file, void *priv, unsigned int index) +{ + if (index) + return -EINVAL; + + return 0; +} + +static int solo_enc_get_input(struct file *file, void *priv, + unsigned int *index) +{ + *index = 0; + + return 0; +} + +static int solo_enc_enum_fmt_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + switch (f->index) { + case 0: + f->pixelformat = V4L2_PIX_FMT_MPEG; + strcpy(f->description, "MPEG-4 AVC"); + break; + case 1: + f->pixelformat = V4L2_PIX_FMT_MJPEG; + strcpy(f->description, "MJPEG"); + break; + default: + return -EINVAL; + } + + f->flags = V4L2_FMT_FLAG_COMPRESSED; + + return 0; +} + +static int solo_enc_try_fmt_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct solo_enc_fh *fh = priv; + struct solo_enc_dev *solo_enc = fh->enc; + struct solo_dev *solo_dev = solo_enc->solo_dev; + struct v4l2_pix_format *pix = &f->fmt.pix; + + if (pix->pixelformat != V4L2_PIX_FMT_MPEG && + pix->pixelformat != V4L2_PIX_FMT_MJPEG) + return -EINVAL; + + /* We cannot change width/height in mid read */ + if (atomic_read(&solo_enc->readers) > 0) { + if (pix->width != solo_enc->width || + pix->height != solo_enc->height) + return -EBUSY; + } + + 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; + } + + if (pix->field == V4L2_FIELD_ANY) + pix->field = V4L2_FIELD_INTERLACED; + else if (pix->field != V4L2_FIELD_INTERLACED) + pix->field = V4L2_FIELD_INTERLACED; + + /* Just set these */ + pix->colorspace = V4L2_COLORSPACE_SMPTE170M; + pix->sizeimage = FRAME_BUF_SIZE; + + return 0; +} + +static int solo_enc_set_fmt_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct solo_enc_fh *fh = priv; + struct solo_enc_dev *solo_enc = fh->enc; + struct solo_dev *solo_dev = solo_enc->solo_dev; + struct v4l2_pix_format *pix = &f->fmt.pix; + int ret; + + spin_lock(&solo_enc->lock); + + ret = solo_enc_try_fmt_cap(file, priv, f); + if (ret) { + spin_unlock(&solo_enc->lock); + return ret; + } + + if (pix->width == solo_dev->video_hsize) + solo_enc->mode = SOLO_ENC_MODE_D1; + else + solo_enc->mode = SOLO_ENC_MODE_CIF; + + /* This does not change the encoder at all */ + fh->fmt = pix->pixelformat; + + if (pix->priv) + fh->type = SOLO_ENC_TYPE_EXT; + ret = solo_enc_on(fh); + + spin_unlock(&solo_enc->lock); + + if (ret) + return ret; + + return solo_start_fh_thread(fh); +} + +static int solo_enc_get_fmt_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct solo_enc_fh *fh = priv; + struct solo_enc_dev *solo_enc = fh->enc; + struct v4l2_pix_format *pix = &f->fmt.pix; + + pix->width = solo_enc->width; + pix->height = solo_enc->height; + pix->pixelformat = fh->fmt; + pix->field = solo_enc->interlaced ? V4L2_FIELD_INTERLACED : + V4L2_FIELD_NONE; + pix->sizeimage = FRAME_BUF_SIZE; + pix->colorspace = V4L2_COLORSPACE_SMPTE170M; + + return 0; +} + +static int solo_enc_reqbufs(struct file *file, void *priv, + struct v4l2_requestbuffers *req) +{ + struct solo_enc_fh *fh = priv; + + return videobuf_reqbufs(&fh->vidq, req); +} + +static int solo_enc_querybuf(struct file *file, void *priv, + struct v4l2_buffer *buf) +{ + struct solo_enc_fh *fh = priv; + + return videobuf_querybuf(&fh->vidq, buf); +} + +static int solo_enc_qbuf(struct file *file, void *priv, struct v4l2_buffer *buf) +{ + struct solo_enc_fh *fh = priv; + + return videobuf_qbuf(&fh->vidq, buf); +} + +static int solo_enc_dqbuf(struct file *file, void *priv, + struct v4l2_buffer *buf) +{ + struct solo_enc_fh *fh = priv; + struct solo_enc_dev *solo_enc = fh->enc; + int ret; + + /* Make sure the encoder is on */ + if (!fh->enc_on) { + spin_lock(&solo_enc->lock); + ret = solo_enc_on(fh); + spin_unlock(&solo_enc->lock); + if (ret) + return ret; + + ret = solo_start_fh_thread(fh); + if (ret) + return ret; + } + + ret = videobuf_dqbuf(&fh->vidq, buf, file->f_flags & O_NONBLOCK); + if (ret) + return ret; + + /* Signal motion detection */ + if (solo_is_motion_on(solo_enc)) { + buf->flags |= V4L2_BUF_FLAG_MOTION_ON; + if (solo_enc->motion_detected) { + buf->flags |= V4L2_BUF_FLAG_MOTION_DETECTED; + solo_reg_write(solo_enc->solo_dev, SOLO_VI_MOT_CLEAR, + 1 << solo_enc->ch); + solo_enc->motion_detected = 0; + } + } + + /* Check for key frame on mpeg data */ + if (fh->fmt == V4L2_PIX_FMT_MPEG) { + struct videobuf_dmabuf *vbuf = + videobuf_to_dma(fh->vidq.bufs[buf->index]); + + if (vbuf) { + u8 *p = sg_virt(vbuf->sglist); + if (p[3] == 0x00) + buf->flags |= V4L2_BUF_FLAG_KEYFRAME; + else + buf->flags |= V4L2_BUF_FLAG_PFRAME; + } + } + + return 0; +} + +static int solo_enc_streamon(struct file *file, void *priv, + enum v4l2_buf_type i) +{ + struct solo_enc_fh *fh = priv; + + if (i != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + return videobuf_streamon(&fh->vidq); +} + +static int solo_enc_streamoff(struct file *file, void *priv, + enum v4l2_buf_type i) +{ + struct solo_enc_fh *fh = priv; + + if (i != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + return videobuf_streamoff(&fh->vidq); +} + +static int solo_enc_s_std(struct file *file, void *priv, v4l2_std_id *i) +{ + return 0; +} + +static int solo_enum_framesizes(struct file *file, void *priv, + struct v4l2_frmsizeenum *fsize) +{ + struct solo_enc_fh *fh = priv; + struct solo_dev *solo_dev = fh->enc->solo_dev; + + if (fsize->pixel_format != V4L2_PIX_FMT_MPEG) + return -EINVAL; + + switch (fsize->index) { + case 0: + fsize->discrete.width = solo_dev->video_hsize >> 1; + fsize->discrete.height = solo_dev->video_vsize; + break; + case 1: + fsize->discrete.width = solo_dev->video_hsize; + fsize->discrete.height = solo_dev->video_vsize << 1; + break; + default: + return -EINVAL; + } + + fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE; + + return 0; +} + +static int solo_enum_frameintervals(struct file *file, void *priv, + struct v4l2_frmivalenum *fintv) +{ + struct solo_enc_fh *fh = priv; + struct solo_dev *solo_dev = fh->enc->solo_dev; + + if (fintv->pixel_format != V4L2_PIX_FMT_MPEG || fintv->index) + return -EINVAL; + + fintv->type = V4L2_FRMIVAL_TYPE_STEPWISE; + + fintv->stepwise.min.numerator = solo_dev->fps; + fintv->stepwise.min.denominator = 1; + + fintv->stepwise.max.numerator = solo_dev->fps; + fintv->stepwise.max.denominator = 15; + + fintv->stepwise.step.numerator = 1; + fintv->stepwise.step.denominator = 1; + + return 0; +} + +static int solo_g_parm(struct file *file, void *priv, + struct v4l2_streamparm *sp) +{ + struct solo_enc_fh *fh = priv; + struct solo_enc_dev *solo_enc = fh->enc; + struct solo_dev *solo_dev = solo_enc->solo_dev; + struct v4l2_captureparm *cp = &sp->parm.capture; + + cp->capability = V4L2_CAP_TIMEPERFRAME; + cp->timeperframe.numerator = solo_enc->interval; + cp->timeperframe.denominator = solo_dev->fps; + cp->capturemode = 0; + /* XXX: Shouldn't we be able to get/set this from videobuf? */ + cp->readbuffers = 2; + + return 0; +} + +static int solo_s_parm(struct file *file, void *priv, + struct v4l2_streamparm *sp) +{ + struct solo_enc_fh *fh = priv; + struct solo_enc_dev *solo_enc = fh->enc; + struct solo_dev *solo_dev = solo_enc->solo_dev; + struct v4l2_captureparm *cp = &sp->parm.capture; + + spin_lock(&solo_enc->lock); + + if (atomic_read(&solo_enc->readers) > 0) { + spin_unlock(&solo_enc->lock); + return -EBUSY; + } + + if ((cp->timeperframe.numerator == 0) || + (cp->timeperframe.denominator == 0)) { + /* reset framerate */ + cp->timeperframe.numerator = 1; + cp->timeperframe.denominator = solo_dev->fps; + } + + if (cp->timeperframe.denominator != solo_dev->fps) + cp->timeperframe.denominator = solo_dev->fps; + + if (cp->timeperframe.numerator > 15) + cp->timeperframe.numerator = 15; + + solo_enc->interval = cp->timeperframe.numerator; + + cp->capability = V4L2_CAP_TIMEPERFRAME; + + solo_enc->gop = max(solo_dev->fps / solo_enc->interval, 1); + solo_update_mode(solo_enc); + + spin_unlock(&solo_enc->lock); + + return 0; +} + +static int solo_queryctrl(struct file *file, void *priv, + struct v4l2_queryctrl *qc) +{ + struct solo_enc_fh *fh = priv; + struct solo_enc_dev *solo_enc = fh->enc; + struct solo_dev *solo_dev = solo_enc->solo_dev; + + qc->id = v4l2_ctrl_next(solo_ctrl_classes, qc->id); + if (!qc->id) + return -EINVAL; + + switch (qc->id) { + case V4L2_CID_BRIGHTNESS: + case V4L2_CID_CONTRAST: + case V4L2_CID_SATURATION: + case V4L2_CID_HUE: + return v4l2_ctrl_query_fill(qc, 0x00, 0xff, 1, 0x80); + case V4L2_CID_SHARPNESS: + return v4l2_ctrl_query_fill(qc, 0x00, 0x0f, 1, 0x00); + case V4L2_CID_MPEG_VIDEO_ENCODING: + return v4l2_ctrl_query_fill( + qc, V4L2_MPEG_VIDEO_ENCODING_MPEG_1, + V4L2_MPEG_VIDEO_ENCODING_MPEG_4_AVC, 1, + V4L2_MPEG_VIDEO_ENCODING_MPEG_4_AVC); + case V4L2_CID_MPEG_VIDEO_GOP_SIZE: + return v4l2_ctrl_query_fill(qc, 1, 255, 1, solo_dev->fps); +#ifdef PRIVATE_CIDS + case V4L2_CID_MOTION_THRESHOLD: + qc->flags |= V4L2_CTRL_FLAG_SLIDER; + qc->type = V4L2_CTRL_TYPE_INTEGER; + qc->minimum = 0; + qc->maximum = 0xffff; + qc->step = 1; + qc->default_value = SOLO_DEF_MOT_THRESH; + strlcpy(qc->name, "Motion Detection Threshold", + sizeof(qc->name)); + return 0; + case V4L2_CID_MOTION_ENABLE: + qc->type = V4L2_CTRL_TYPE_BOOLEAN; + qc->minimum = 0; + qc->maximum = qc->step = 1; + qc->default_value = 0; + strlcpy(qc->name, "Motion Detection Enable", sizeof(qc->name)); + return 0; +#else + case V4L2_CID_MOTION_THRESHOLD: + return v4l2_ctrl_query_fill(qc, 0, 0xffff, 1, + SOLO_DEF_MOT_THRESH); + case V4L2_CID_MOTION_ENABLE: + return v4l2_ctrl_query_fill(qc, 0, 1, 1, 0); +#endif + case V4L2_CID_RDS_TX_RADIO_TEXT: + qc->type = V4L2_CTRL_TYPE_STRING; + qc->minimum = 0; + qc->maximum = OSD_TEXT_MAX; + qc->step = 1; + qc->default_value = 0; + strlcpy(qc->name, "OSD Text", sizeof(qc->name)); + return 0; + } + + return -EINVAL; +} + +static int solo_querymenu(struct file *file, void *priv, + struct v4l2_querymenu *qmenu) +{ + struct v4l2_queryctrl qctrl; + int err; + + qctrl.id = qmenu->id; + err = solo_queryctrl(file, priv, &qctrl); + if (err) + return err; + + return v4l2_ctrl_query_menu(qmenu, &qctrl, NULL); +} + +static int solo_g_ctrl(struct file *file, void *priv, + struct v4l2_control *ctrl) +{ + struct solo_enc_fh *fh = priv; + struct solo_enc_dev *solo_enc = fh->enc; + struct solo_dev *solo_dev = solo_enc->solo_dev; + + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + case V4L2_CID_CONTRAST: + case V4L2_CID_SATURATION: + case V4L2_CID_HUE: + case V4L2_CID_SHARPNESS: + return tw28_get_ctrl_val(solo_dev, ctrl->id, solo_enc->ch, + &ctrl->value); + case V4L2_CID_MPEG_VIDEO_ENCODING: + ctrl->value = V4L2_MPEG_VIDEO_ENCODING_MPEG_4_AVC; + break; + case V4L2_CID_MPEG_VIDEO_GOP_SIZE: + ctrl->value = solo_enc->gop; + break; + case V4L2_CID_MOTION_THRESHOLD: + ctrl->value = solo_enc->motion_thresh; + break; + case V4L2_CID_MOTION_ENABLE: + ctrl->value = solo_is_motion_on(solo_enc); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int solo_s_ctrl(struct file *file, void *priv, + struct v4l2_control *ctrl) +{ + struct solo_enc_fh *fh = priv; + struct solo_enc_dev *solo_enc = fh->enc; + struct solo_dev *solo_dev = solo_enc->solo_dev; + + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + case V4L2_CID_CONTRAST: + case V4L2_CID_SATURATION: + case V4L2_CID_HUE: + case V4L2_CID_SHARPNESS: + return tw28_set_ctrl_val(solo_dev, ctrl->id, solo_enc->ch, + ctrl->value); + case V4L2_CID_MPEG_VIDEO_ENCODING: + if (ctrl->value != V4L2_MPEG_VIDEO_ENCODING_MPEG_4_AVC) + return -ERANGE; + break; + case V4L2_CID_MPEG_VIDEO_GOP_SIZE: + if (ctrl->value < 1 || ctrl->value > 255) + return -ERANGE; + solo_enc->gop = ctrl->value; + solo_reg_write(solo_dev, SOLO_VE_CH_GOP(solo_enc->ch), + solo_enc->gop); + solo_reg_write(solo_dev, SOLO_VE_CH_GOP_E(solo_enc->ch), + solo_enc->gop); + break; + case V4L2_CID_MOTION_THRESHOLD: + /* TODO accept value on lower 16-bits and use high + * 16-bits to assign the value to a specific block */ + if (ctrl->value < 0 || ctrl->value > 0xffff) + return -ERANGE; + solo_enc->motion_thresh = ctrl->value; + solo_set_motion_threshold(solo_dev, solo_enc->ch, ctrl->value); + break; + case V4L2_CID_MOTION_ENABLE: + solo_motion_toggle(solo_enc, ctrl->value); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int solo_s_ext_ctrls(struct file *file, void *priv, + struct v4l2_ext_controls *ctrls) +{ + struct solo_enc_fh *fh = priv; + struct solo_enc_dev *solo_enc = fh->enc; + int i; + + for (i = 0; i < ctrls->count; i++) { + struct v4l2_ext_control *ctrl = (ctrls->controls + i); + int err; + + switch (ctrl->id) { + case V4L2_CID_RDS_TX_RADIO_TEXT: + if (ctrl->size - 1 > OSD_TEXT_MAX) + err = -ERANGE; + else { + err = copy_from_user(solo_enc->osd_text, + ctrl->string, + OSD_TEXT_MAX); + solo_enc->osd_text[OSD_TEXT_MAX] = '\0'; + if (!err) + err = solo_osd_print(solo_enc); + } + break; + default: + err = -EINVAL; + } + + if (err < 0) { + ctrls->error_idx = i; + return err; + } + } + + return 0; +} + +static int solo_g_ext_ctrls(struct file *file, void *priv, + struct v4l2_ext_controls *ctrls) +{ + struct solo_enc_fh *fh = priv; + struct solo_enc_dev *solo_enc = fh->enc; + int i; + + for (i = 0; i < ctrls->count; i++) { + struct v4l2_ext_control *ctrl = (ctrls->controls + i); + int err; + + switch (ctrl->id) { + case V4L2_CID_RDS_TX_RADIO_TEXT: + if (ctrl->size < OSD_TEXT_MAX) { + ctrl->size = OSD_TEXT_MAX; + err = -ENOSPC; + } else { + err = copy_to_user(ctrl->string, + solo_enc->osd_text, + OSD_TEXT_MAX); + } + break; + default: + err = -EINVAL; + } + + if (err < 0) { + ctrls->error_idx = i; + return err; + } + } + + return 0; +} + +static const struct v4l2_file_operations solo_enc_fops = { + .owner = THIS_MODULE, + .open = solo_enc_open, + .release = solo_enc_release, + .read = solo_enc_read, + .poll = solo_enc_poll, + .mmap = solo_enc_mmap, + .ioctl = video_ioctl2, +}; + +static const struct v4l2_ioctl_ops solo_enc_ioctl_ops = { + .vidioc_querycap = solo_enc_querycap, + .vidioc_s_std = solo_enc_s_std, + /* Input callbacks */ + .vidioc_enum_input = solo_enc_enum_input, + .vidioc_s_input = solo_enc_set_input, + .vidioc_g_input = solo_enc_get_input, + /* Video capture format callbacks */ + .vidioc_enum_fmt_vid_cap = solo_enc_enum_fmt_cap, + .vidioc_try_fmt_vid_cap = solo_enc_try_fmt_cap, + .vidioc_s_fmt_vid_cap = solo_enc_set_fmt_cap, + .vidioc_g_fmt_vid_cap = solo_enc_get_fmt_cap, + /* Streaming I/O */ + .vidioc_reqbufs = solo_enc_reqbufs, + .vidioc_querybuf = solo_enc_querybuf, + .vidioc_qbuf = solo_enc_qbuf, + .vidioc_dqbuf = solo_enc_dqbuf, + .vidioc_streamon = solo_enc_streamon, + .vidioc_streamoff = solo_enc_streamoff, + /* Frame size and interval */ + .vidioc_enum_framesizes = solo_enum_framesizes, + .vidioc_enum_frameintervals = solo_enum_frameintervals, + /* Video capture parameters */ + .vidioc_s_parm = solo_s_parm, + .vidioc_g_parm = solo_g_parm, + /* Controls */ + .vidioc_queryctrl = solo_queryctrl, + .vidioc_querymenu = solo_querymenu, + .vidioc_g_ctrl = solo_g_ctrl, + .vidioc_s_ctrl = solo_s_ctrl, + .vidioc_g_ext_ctrls = solo_g_ext_ctrls, + .vidioc_s_ext_ctrls = solo_s_ext_ctrls, +}; + +static struct video_device solo_enc_template = { + .name = 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_B, + .current_norm = V4L2_STD_NTSC_M, +}; + +static struct solo_enc_dev *solo_enc_alloc(struct solo_dev *solo_dev, u8 ch) +{ + struct solo_enc_dev *solo_enc; + int ret; + + solo_enc = kzalloc(sizeof(*solo_enc), GFP_KERNEL); + if (!solo_enc) + return ERR_PTR(-ENOMEM); + + solo_enc->vfd = video_device_alloc(); + if (!solo_enc->vfd) { + kfree(solo_enc); + return ERR_PTR(-ENOMEM); + } + + solo_enc->solo_dev = solo_dev; + solo_enc->ch = ch; + + *solo_enc->vfd = solo_enc_template; + solo_enc->vfd->parent = &solo_dev->pdev->dev; + ret = video_register_device(solo_enc->vfd, VFL_TYPE_GRABBER, + video_nr); + if (ret < 0) { + video_device_release(solo_enc->vfd); + kfree(solo_enc); + return ERR_PTR(ret); + } + + video_set_drvdata(solo_enc->vfd, solo_enc); + + snprintf(solo_enc->vfd->name, sizeof(solo_enc->vfd->name), + "%s-enc (%i/%i)", SOLO6X10_NAME, solo_dev->vfd->num, + solo_enc->vfd->num); + + if (video_nr != -1) + video_nr++; + + spin_lock_init(&solo_enc->lock); + init_waitqueue_head(&solo_enc->thread_wait); + atomic_set(&solo_enc->readers, 0); + + solo_enc->qp = SOLO_DEFAULT_QP; + solo_enc->gop = solo_dev->fps; + solo_enc->interval = 1; + solo_enc->mode = SOLO_ENC_MODE_CIF; + solo_enc->motion_thresh = SOLO_DEF_MOT_THRESH; + + spin_lock(&solo_enc->lock); + solo_update_mode(solo_enc); + spin_unlock(&solo_enc->lock); + + return solo_enc; +} + +static void solo_enc_free(struct solo_enc_dev *solo_enc) +{ + if (solo_enc == NULL) + return; + + video_unregister_device(solo_enc->vfd); + kfree(solo_enc); +} + +int solo_enc_v4l2_init(struct solo_dev *solo_dev) +{ + int i; + + for (i = 0; i < solo_dev->nr_chans; i++) { + solo_dev->v4l2_enc[i] = solo_enc_alloc(solo_dev, i); + if (IS_ERR(solo_dev->v4l2_enc[i])) + break; + } + + if (i != solo_dev->nr_chans) { + int ret = PTR_ERR(solo_dev->v4l2_enc[i]); + while (i--) + solo_enc_free(solo_dev->v4l2_enc[i]); + return ret; + } + + /* D1@MAX-FPS * 4 */ + solo_dev->enc_bw_remain = solo_dev->fps * 4 * 4; + + dev_info(&solo_dev->pdev->dev, "Encoders as /dev/video%d-%d\n", + solo_dev->v4l2_enc[0]->vfd->num, + solo_dev->v4l2_enc[solo_dev->nr_chans - 1]->vfd->num); + + return 0; +} + +void solo_enc_v4l2_exit(struct solo_dev *solo_dev) +{ + int i; + + solo_irq_off(solo_dev, SOLO_IRQ_MOTION); + + for (i = 0; i < solo_dev->nr_chans; i++) + solo_enc_free(solo_dev->v4l2_enc[i]); +} diff --git a/drivers/staging/media/solo6x10/v4l2.c b/drivers/staging/media/solo6x10/v4l2.c new file mode 100644 index 0000000..571c3a3 --- /dev/null +++ b/drivers/staging/media/solo6x10/v4l2.c @@ -0,0 +1,964 @@ +/* + * Copyright (C) 2010 Bluecherry, LLC www.bluecherrydvr.com + * Copyright (C) 2010 Ben Collins <bcollins@bluecherry.net> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/kthread.h> +#include <linux/freezer.h> +#include <media/v4l2-ioctl.h> +#include <media/v4l2-common.h> +#include <media/videobuf-dma-sg.h> +#include "solo6x10.h" +#include "tw28.h" + +#define SOLO_HW_BPL 2048 +#define SOLO_DISP_PIX_FIELD V4L2_FIELD_INTERLACED + +/* Image size is two fields, SOLO_HW_BPL is one horizontal line */ +#define solo_vlines(__solo) (__solo->video_vsize * 2) +#define solo_image_size(__solo) (solo_bytesperline(__solo) * \ + solo_vlines(__solo)) +#define solo_bytesperline(__solo) (__solo->video_hsize * 2) + +#define MIN_VID_BUFFERS 4 + +/* Simple file handle */ +struct solo_filehandle { + struct solo_dev *solo_dev; + struct videobuf_queue vidq; + struct task_struct *kthread; + spinlock_t slock; + int old_write; + struct list_head vidq_active; + struct p2m_desc desc[SOLO_NR_P2M_DESC]; + int desc_idx; +}; + +unsigned video_nr = -1; +module_param(video_nr, uint, 0644); +MODULE_PARM_DESC(video_nr, "videoX start number, -1 is autodetect (default)"); + +static void erase_on(struct 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 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) +{ + solo_reg_write(solo_dev, SOLO_IRQ_STAT, SOLO_IRQ_VIDEO_IN); + wake_up_interruptible(&solo_dev->disp_thread_wait); +} + +static void solo_win_setup(struct 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 disp_reset_desc(struct solo_filehandle *fh) +{ + /* We use desc mode, which ignores desc 0 */ + memset(fh->desc, 0, sizeof(*fh->desc)); + fh->desc_idx = 1; +} + +static int disp_flush_descs(struct solo_filehandle *fh) +{ + int ret; + + if (!fh->desc_idx) + return 0; + + ret = solo_p2m_dma_desc(fh->solo_dev, SOLO_P2M_DMA_ID_DISP, + fh->desc, fh->desc_idx); + disp_reset_desc(fh); + + return ret; +} + +static int disp_push_desc(struct solo_filehandle *fh, dma_addr_t dma_addr, + u32 ext_addr, int size, int repeat, int ext_size) +{ + if (fh->desc_idx >= SOLO_NR_P2M_DESC) { + int ret = disp_flush_descs(fh); + if (ret) + return ret; + } + + solo_p2m_push_desc(&fh->desc[fh->desc_idx], 0, dma_addr, ext_addr, + size, repeat, ext_size); + fh->desc_idx++; + + return 0; +} + +static void solo_fillbuf(struct solo_filehandle *fh, + struct videobuf_buffer *vb) +{ + struct solo_dev *solo_dev = fh->solo_dev; + struct videobuf_dmabuf *vbuf; + unsigned int fdma_addr; + int error = 1; + int i; + struct scatterlist *sg; + dma_addr_t sg_dma; + int sg_size_left; + + vbuf = videobuf_to_dma(vb); + if (!vbuf) + goto finish_buf; + + if (erase_off(solo_dev)) { + int i; + + /* Just blit to the entire sg list, ignoring size */ + for_each_sg(vbuf->sglist, sg, vbuf->sglen, i) { + void *p = sg_virt(sg); + size_t len = sg_dma_len(sg); + + for (i = 0; i < len; i += 2) { + ((u8 *)p)[i] = 0x80; + ((u8 *)p)[i + 1] = 0x00; + } + } + + error = 0; + goto finish_buf; + } + + disp_reset_desc(fh); + sg = vbuf->sglist; + sg_dma = sg_dma_address(sg); + sg_size_left = sg_dma_len(sg); + + fdma_addr = SOLO_DISP_EXT_ADDR + (fh->old_write * + (SOLO_HW_BPL * solo_vlines(solo_dev))); + + for (i = 0; i < solo_vlines(solo_dev); i++) { + int line_len = solo_bytesperline(solo_dev); + int lines; + + if (!sg_size_left) { + sg = sg_next(sg); + if (sg == NULL) + goto finish_buf; + sg_dma = sg_dma_address(sg); + sg_size_left = sg_dma_len(sg); + } + + /* No room for an entire line, so chunk it up */ + if (sg_size_left < line_len) { + int this_addr = fdma_addr; + + while (line_len > 0) { + int this_write; + + if (!sg_size_left) { + sg = sg_next(sg); + if (sg == NULL) + goto finish_buf; + sg_dma = sg_dma_address(sg); + sg_size_left = sg_dma_len(sg); + } + + this_write = min(sg_size_left, line_len); + + if (disp_push_desc(fh, sg_dma, this_addr, + this_write, 0, 0)) + goto finish_buf; + + line_len -= this_write; + sg_size_left -= this_write; + sg_dma += this_write; + this_addr += this_write; + } + + fdma_addr += SOLO_HW_BPL; + continue; + } + + /* Shove as many lines into a repeating descriptor as possible */ + lines = min(sg_size_left / line_len, + solo_vlines(solo_dev) - i); + + if (disp_push_desc(fh, sg_dma, fdma_addr, line_len, + lines - 1, SOLO_HW_BPL)) + goto finish_buf; + + i += lines - 1; + fdma_addr += SOLO_HW_BPL * lines; + sg_dma += lines * line_len; + sg_size_left -= lines * line_len; + } + + error = disp_flush_descs(fh); + +finish_buf: + if (error) { + vb->state = VIDEOBUF_ERROR; + } else { + vb->size = solo_vlines(solo_dev) * solo_bytesperline(solo_dev); + vb->state = VIDEOBUF_DONE; + vb->field_count++; + do_gettimeofday(&vb->ts); + } + + wake_up(&vb->done); + + return; +} + +static void solo_thread_try(struct solo_filehandle *fh) +{ + struct videobuf_buffer *vb; + unsigned int cur_write; + + for (;;) { + spin_lock(&fh->slock); + + if (list_empty(&fh->vidq_active)) + break; + + vb = list_first_entry(&fh->vidq_active, struct videobuf_buffer, + queue); + + if (!waitqueue_active(&vb->done)) + break; + + cur_write = SOLO_VI_STATUS0_PAGE(solo_reg_read(fh->solo_dev, + SOLO_VI_STATUS0)); + if (cur_write == fh->old_write) + break; + + fh->old_write = cur_write; + list_del(&vb->queue); + + spin_unlock(&fh->slock); + + solo_fillbuf(fh, vb); + } + + assert_spin_locked(&fh->slock); + spin_unlock(&fh->slock); +} + +static int solo_thread(void *data) +{ + struct solo_filehandle *fh = data; + struct solo_dev *solo_dev = fh->solo_dev; + DECLARE_WAITQUEUE(wait, current); + + set_freezable(); + add_wait_queue(&solo_dev->disp_thread_wait, &wait); + + for (;;) { + long timeout = schedule_timeout_interruptible(HZ); + if (timeout == -ERESTARTSYS || kthread_should_stop()) + break; + solo_thread_try(fh); + try_to_freeze(); + } + + remove_wait_queue(&solo_dev->disp_thread_wait, &wait); + + return 0; +} + +static int solo_start_thread(struct solo_filehandle *fh) +{ + fh->kthread = kthread_run(solo_thread, fh, SOLO6X10_NAME "_disp"); + + if (IS_ERR(fh->kthread)) + return PTR_ERR(fh->kthread); + + return 0; +} + +static void solo_stop_thread(struct solo_filehandle *fh) +{ + if (fh->kthread) { + kthread_stop(fh->kthread); + fh->kthread = NULL; + } +} + +static int solo_buf_setup(struct videobuf_queue *vq, unsigned int *count, + unsigned int *size) +{ + struct solo_filehandle *fh = vq->priv_data; + struct solo_dev *solo_dev = fh->solo_dev; + + *size = solo_image_size(solo_dev); + + if (*count < MIN_VID_BUFFERS) + *count = MIN_VID_BUFFERS; + + return 0; +} + +static int solo_buf_prepare(struct videobuf_queue *vq, + struct videobuf_buffer *vb, enum v4l2_field field) +{ + struct solo_filehandle *fh = vq->priv_data; + struct solo_dev *solo_dev = fh->solo_dev; + + vb->size = solo_image_size(solo_dev); + if (vb->baddr != 0 && vb->bsize < vb->size) + return -EINVAL; + + /* XXX: These properties only change when queue is idle */ + vb->width = solo_dev->video_hsize; + vb->height = solo_vlines(solo_dev); + vb->bytesperline = solo_bytesperline(solo_dev); + vb->field = field; + + if (vb->state == VIDEOBUF_NEEDS_INIT) { + int rc = videobuf_iolock(vq, vb, NULL); + if (rc < 0) { + struct videobuf_dmabuf *dma = videobuf_to_dma(vb); + videobuf_dma_unmap(vq->dev, dma); + videobuf_dma_free(dma); + vb->state = VIDEOBUF_NEEDS_INIT; + return rc; + } + } + vb->state = VIDEOBUF_PREPARED; + + return 0; +} + +static void solo_buf_queue(struct videobuf_queue *vq, + struct videobuf_buffer *vb) +{ + struct solo_filehandle *fh = vq->priv_data; + struct solo_dev *solo_dev = fh->solo_dev; + + vb->state = VIDEOBUF_QUEUED; + list_add_tail(&vb->queue, &fh->vidq_active); + wake_up_interruptible(&solo_dev->disp_thread_wait); +} + +static void solo_buf_release(struct videobuf_queue *vq, + struct videobuf_buffer *vb) +{ + struct videobuf_dmabuf *dma = videobuf_to_dma(vb); + + videobuf_dma_unmap(vq->dev, dma); + videobuf_dma_free(dma); + vb->state = VIDEOBUF_NEEDS_INIT; +} + +static struct videobuf_queue_ops solo_video_qops = { + .buf_setup = solo_buf_setup, + .buf_prepare = solo_buf_prepare, + .buf_queue = solo_buf_queue, + .buf_release = solo_buf_release, +}; + +static unsigned int solo_v4l2_poll(struct file *file, + struct poll_table_struct *wait) +{ + struct solo_filehandle *fh = file->private_data; + + return videobuf_poll_stream(file, &fh->vidq, wait); +} + +static int solo_v4l2_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct solo_filehandle *fh = file->private_data; + + return videobuf_mmap_mapper(&fh->vidq, vma); +} + +static int solo_v4l2_open(struct file *file) +{ + struct solo_dev *solo_dev = video_drvdata(file); + struct solo_filehandle *fh; + int ret; + + fh = kzalloc(sizeof(*fh), GFP_KERNEL); + if (fh == NULL) + return -ENOMEM; + + spin_lock_init(&fh->slock); + INIT_LIST_HEAD(&fh->vidq_active); + fh->solo_dev = solo_dev; + file->private_data = fh; + + ret = solo_start_thread(fh); + if (ret) { + kfree(fh); + return ret; + } + + videobuf_queue_sg_init(&fh->vidq, &solo_video_qops, + &solo_dev->pdev->dev, &fh->slock, + V4L2_BUF_TYPE_VIDEO_CAPTURE, + SOLO_DISP_PIX_FIELD, + sizeof(struct videobuf_buffer), fh, NULL); + + return 0; +} + +static ssize_t solo_v4l2_read(struct file *file, char __user *data, + size_t count, loff_t *ppos) +{ + struct solo_filehandle *fh = file->private_data; + + return videobuf_read_stream(&fh->vidq, data, count, ppos, 0, + file->f_flags & O_NONBLOCK); +} + +static int solo_v4l2_release(struct file *file) +{ + struct solo_filehandle *fh = file->private_data; + + videobuf_stop(&fh->vidq); + videobuf_mmap_free(&fh->vidq); + solo_stop_thread(fh); + kfree(fh); + + return 0; +} + +static int solo_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + struct solo_filehandle *fh = priv; + struct solo_dev *solo_dev = fh->solo_dev; + + 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->version = SOLO6X10_VER_NUM; + cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | + V4L2_CAP_READWRITE | + V4L2_CAP_STREAMING; + return 0; +} + +static int solo_enum_ext_input(struct solo_dev *solo_dev, + struct v4l2_input *input) +{ + static const char *dispnames_1[] = { "4UP" }; + static const char *dispnames_2[] = { "4UP-1", "4UP-2" }; + static const char *dispnames_5[] = { + "4UP-1", "4UP-2", "4UP-3", "4UP-4", "16UP" + }; + const char **dispnames; + + if (input->index >= (solo_dev->nr_chans + solo_dev->nr_ext)) + return -EINVAL; + + if (solo_dev->nr_ext == 5) + dispnames = dispnames_5; + else if (solo_dev->nr_ext == 2) + dispnames = dispnames_2; + else + dispnames = dispnames_1; + + snprintf(input->name, sizeof(input->name), "Multi %s", + dispnames[input->index - solo_dev->nr_chans]); + + return 0; +} + +static int solo_enum_input(struct file *file, void *priv, + struct v4l2_input *input) +{ + struct solo_filehandle *fh = priv; + struct solo_dev *solo_dev = fh->solo_dev; + + if (input->index >= solo_dev->nr_chans) { + int ret = solo_enum_ext_input(solo_dev, input); + if (ret < 0) + return ret; + } else { + snprintf(input->name, sizeof(input->name), "Camera %d", + input->index + 1); + + /* We can only check this for normal inputs */ + if (!tw28_get_video_status(solo_dev, input->index)) + input->status = V4L2_IN_ST_NO_SIGNAL; + } + + input->type = V4L2_INPUT_TYPE_CAMERA; + + if (solo_dev->video_type == SOLO_VO_FMT_TYPE_NTSC) + input->std = V4L2_STD_NTSC_M; + else + input->std = V4L2_STD_PAL_B; + + return 0; +} + +static int solo_set_input(struct file *file, void *priv, unsigned int index) +{ + struct solo_filehandle *fh = priv; + + return solo_v4l2_set_ch(fh->solo_dev, index); +} + +static int solo_get_input(struct file *file, void *priv, unsigned int *index) +{ + struct solo_filehandle *fh = priv; + + *index = fh->solo_dev->cur_disp_ch; + + return 0; +} + +static int solo_enum_fmt_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + if (f->index) + return -EINVAL; + + f->pixelformat = V4L2_PIX_FMT_UYVY; + strlcpy(f->description, "UYUV 4:2:2 Packed", sizeof(f->description)); + + return 0; +} + +static int solo_try_fmt_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct solo_filehandle *fh = priv; + struct solo_dev *solo_dev = fh->solo_dev; + struct v4l2_pix_format *pix = &f->fmt.pix; + int image_size = solo_image_size(solo_dev); + + /* Check supported sizes */ + if (pix->width != solo_dev->video_hsize) + pix->width = solo_dev->video_hsize; + if (pix->height != solo_vlines(solo_dev)) + pix->height = solo_vlines(solo_dev); + if (pix->sizeimage != image_size) + pix->sizeimage = image_size; + + /* Check formats */ + if (pix->field == V4L2_FIELD_ANY) + pix->field = SOLO_DISP_PIX_FIELD; + + if (pix->pixelformat != V4L2_PIX_FMT_UYVY || + pix->field != SOLO_DISP_PIX_FIELD || + pix->colorspace != V4L2_COLORSPACE_SMPTE170M) + return -EINVAL; + + return 0; +} + +static int solo_set_fmt_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct solo_filehandle *fh = priv; + + if (videobuf_queue_is_busy(&fh->vidq)) + return -EBUSY; + + /* For right now, if it doesn't match our running config, + * then fail */ + return solo_try_fmt_cap(file, priv, f); +} + +static int solo_get_fmt_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct solo_filehandle *fh = priv; + struct solo_dev *solo_dev = fh->solo_dev; + struct v4l2_pix_format *pix = &f->fmt.pix; + + pix->width = solo_dev->video_hsize; + pix->height = solo_vlines(solo_dev); + pix->pixelformat = V4L2_PIX_FMT_UYVY; + pix->field = SOLO_DISP_PIX_FIELD; + pix->sizeimage = solo_image_size(solo_dev); + pix->colorspace = V4L2_COLORSPACE_SMPTE170M; + pix->bytesperline = solo_bytesperline(solo_dev); + + return 0; +} + +static int solo_reqbufs(struct file *file, void *priv, + struct v4l2_requestbuffers *req) +{ + struct solo_filehandle *fh = priv; + + return videobuf_reqbufs(&fh->vidq, req); +} + +static int solo_querybuf(struct file *file, void *priv, struct v4l2_buffer *buf) +{ + struct solo_filehandle *fh = priv; + + return videobuf_querybuf(&fh->vidq, buf); +} + +static int solo_qbuf(struct file *file, void *priv, struct v4l2_buffer *buf) +{ + struct solo_filehandle *fh = priv; + + return videobuf_qbuf(&fh->vidq, buf); +} + +static int solo_dqbuf(struct file *file, void *priv, struct v4l2_buffer *buf) +{ + struct solo_filehandle *fh = priv; + + return videobuf_dqbuf(&fh->vidq, buf, file->f_flags & O_NONBLOCK); +} + +static int solo_streamon(struct file *file, void *priv, enum v4l2_buf_type i) +{ + struct solo_filehandle *fh = priv; + + if (i != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + return videobuf_streamon(&fh->vidq); +} + +static int solo_streamoff(struct file *file, void *priv, enum v4l2_buf_type i) +{ + struct solo_filehandle *fh = priv; + + if (i != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + return videobuf_streamoff(&fh->vidq); +} + +static int solo_s_std(struct file *file, void *priv, v4l2_std_id *i) +{ + return 0; +} + +static const u32 solo_motion_ctrls[] = { + V4L2_CID_MOTION_TRACE, + 0 +}; + +static const u32 *solo_ctrl_classes[] = { + solo_motion_ctrls, + NULL +}; + +static int solo_disp_queryctrl(struct file *file, void *priv, + struct v4l2_queryctrl *qc) +{ + qc->id = v4l2_ctrl_next(solo_ctrl_classes, qc->id); + if (!qc->id) + return -EINVAL; + + switch (qc->id) { +#ifdef PRIVATE_CIDS + case V4L2_CID_MOTION_TRACE: + qc->type = V4L2_CTRL_TYPE_BOOLEAN; + qc->minimum = 0; + qc->maximum = qc->step = 1; + qc->default_value = 0; + strlcpy(qc->name, "Motion Detection Trace", sizeof(qc->name)); + return 0; +#else + case V4L2_CID_MOTION_TRACE: + return v4l2_ctrl_query_fill(qc, 0, 1, 1, 0); +#endif + } + return -EINVAL; +} + +static int solo_disp_g_ctrl(struct file *file, void *priv, + struct v4l2_control *ctrl) +{ + struct solo_filehandle *fh = priv; + struct solo_dev *solo_dev = fh->solo_dev; + + switch (ctrl->id) { + case V4L2_CID_MOTION_TRACE: + ctrl->value = solo_reg_read(solo_dev, SOLO_VI_MOTION_BAR) + ? 1 : 0; + return 0; + } + return -EINVAL; +} + +static int solo_disp_s_ctrl(struct file *file, void *priv, + struct v4l2_control *ctrl) +{ + struct solo_filehandle *fh = priv; + struct solo_dev *solo_dev = fh->solo_dev; + + switch (ctrl->id) { + case V4L2_CID_MOTION_TRACE: + if (ctrl->value) { + solo_reg_write(solo_dev, SOLO_VI_MOTION_BORDER, + SOLO_VI_MOTION_Y_ADD | + SOLO_VI_MOTION_Y_VALUE(0x20) | + SOLO_VI_MOTION_CB_VALUE(0x10) | + SOLO_VI_MOTION_CR_VALUE(0x10)); + solo_reg_write(solo_dev, SOLO_VI_MOTION_BAR, + SOLO_VI_MOTION_CR_ADD | + SOLO_VI_MOTION_Y_VALUE(0x10) | + SOLO_VI_MOTION_CB_VALUE(0x80) | + SOLO_VI_MOTION_CR_VALUE(0x10)); + } else { + solo_reg_write(solo_dev, SOLO_VI_MOTION_BORDER, 0); + solo_reg_write(solo_dev, SOLO_VI_MOTION_BAR, 0); + } + return 0; + } + return -EINVAL; +} + +static const struct v4l2_file_operations solo_v4l2_fops = { + .owner = THIS_MODULE, + .open = solo_v4l2_open, + .release = solo_v4l2_release, + .read = solo_v4l2_read, + .poll = solo_v4l2_poll, + .mmap = solo_v4l2_mmap, + .ioctl = video_ioctl2, +}; + +static const struct v4l2_ioctl_ops solo_v4l2_ioctl_ops = { + .vidioc_querycap = solo_querycap, + .vidioc_s_std = solo_s_std, + /* Input callbacks */ + .vidioc_enum_input = solo_enum_input, + .vidioc_s_input = solo_set_input, + .vidioc_g_input = solo_get_input, + /* Video capture format callbacks */ + .vidioc_enum_fmt_vid_cap = solo_enum_fmt_cap, + .vidioc_try_fmt_vid_cap = solo_try_fmt_cap, + .vidioc_s_fmt_vid_cap = solo_set_fmt_cap, + .vidioc_g_fmt_vid_cap = solo_get_fmt_cap, + /* Streaming I/O */ + .vidioc_reqbufs = solo_reqbufs, + .vidioc_querybuf = solo_querybuf, + .vidioc_qbuf = solo_qbuf, + .vidioc_dqbuf = solo_dqbuf, + .vidioc_streamon = solo_streamon, + .vidioc_streamoff = solo_streamoff, + /* Controls */ + .vidioc_queryctrl = solo_disp_queryctrl, + .vidioc_g_ctrl = solo_disp_g_ctrl, + .vidioc_s_ctrl = solo_disp_s_ctrl, +}; + +static struct video_device solo_v4l2_template = { + .name = 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_B, + .current_norm = V4L2_STD_NTSC_M, +}; + +int solo_v4l2_init(struct solo_dev *solo_dev) +{ + int ret; + int i; + + init_waitqueue_head(&solo_dev->disp_thread_wait); + + solo_dev->vfd = video_device_alloc(); + if (!solo_dev->vfd) + return -ENOMEM; + + *solo_dev->vfd = solo_v4l2_template; + solo_dev->vfd->parent = &solo_dev->pdev->dev; + + ret = video_register_device(solo_dev->vfd, VFL_TYPE_GRABBER, video_nr); + if (ret < 0) { + video_device_release(solo_dev->vfd); + solo_dev->vfd = NULL; + return ret; + } + + video_set_drvdata(solo_dev->vfd, solo_dev); + + snprintf(solo_dev->vfd->name, sizeof(solo_dev->vfd->name), "%s (%i)", + SOLO6X10_NAME, solo_dev->vfd->num); + + if (video_nr != -1) + video_nr++; + + dev_info(&solo_dev->pdev->dev, "Display as /dev/video%d with " + "%d inputs (%d extended)\n", solo_dev->vfd->num, + solo_dev->nr_chans, solo_dev->nr_ext); + + /* Cycle all the channels and clear */ + for (i = 0; i < solo_dev->nr_chans; i++) { + solo_v4l2_set_ch(solo_dev, i); + while (erase_off(solo_dev)) + ;/* Do nothing */ + } + + /* Set the default display channel */ + solo_v4l2_set_ch(solo_dev, 0); + while (erase_off(solo_dev)) + ;/* Do nothing */ + + solo_irq_on(solo_dev, SOLO_IRQ_VIDEO_IN); + + return 0; +} + +void solo_v4l2_exit(struct solo_dev *solo_dev) +{ + solo_irq_off(solo_dev, SOLO_IRQ_VIDEO_IN); + if (solo_dev->vfd) { + video_unregister_device(solo_dev->vfd); + solo_dev->vfd = NULL; + } +} |