diff options
Diffstat (limited to 'drivers/staging/media')
155 files changed, 70886 insertions, 0 deletions
diff --git a/drivers/staging/media/Kconfig b/drivers/staging/media/Kconfig new file mode 100644 index 00000000000..a9f2e63a7c9 --- /dev/null +++ b/drivers/staging/media/Kconfig @@ -0,0 +1,49 @@ +menuconfig STAGING_MEDIA + bool "Media staging drivers" + default n + ---help--- + This option allows you to select a number of media drivers that + don't have the "normal" Linux kernel quality level. + Most of them don't follow properly the V4L, DVB and/or RC API's, + so, they won't likely work fine with the existing applications. + That also means that, once fixed, their API's will change to match + the existing ones. + + If you wish to work on these drivers, to help improve them, or + to report problems you have with them, please use the + linux-media@vger.kernel.org mailing list. + + If in doubt, say N here. + + +if STAGING_MEDIA + +# Please keep them in alphabetic order +source "drivers/staging/media/as102/Kconfig" + +source "drivers/staging/media/bcm2048/Kconfig" + +source "drivers/staging/media/cxd2099/Kconfig" + +source "drivers/staging/media/davinci_vpfe/Kconfig" + +source "drivers/staging/media/dt3155v4l/Kconfig" + +source "drivers/staging/media/go7007/Kconfig" + +source "drivers/staging/media/msi3101/Kconfig" + +source "drivers/staging/media/omap24xx/Kconfig" + +source "drivers/staging/media/sn9c102/Kconfig" + +source "drivers/staging/media/solo6x10/Kconfig" + +source "drivers/staging/media/omap4iss/Kconfig" + +source "drivers/staging/media/rtl2832u_sdr/Kconfig" + +# Keep LIRC at the end, as it has sub-menus +source "drivers/staging/media/lirc/Kconfig" + +endif diff --git a/drivers/staging/media/Makefile b/drivers/staging/media/Makefile new file mode 100644 index 00000000000..8e2c5d27216 --- /dev/null +++ b/drivers/staging/media/Makefile @@ -0,0 +1,15 @@ +obj-$(CONFIG_DVB_AS102) += as102/ +obj-$(CONFIG_I2C_BCM2048) += bcm2048/ +obj-$(CONFIG_DVB_CXD2099) += cxd2099/ +obj-$(CONFIG_LIRC_STAGING) += lirc/ +obj-$(CONFIG_SOLO6X10) += solo6x10/ +obj-$(CONFIG_VIDEO_DT3155) += dt3155v4l/ +obj-$(CONFIG_VIDEO_GO7007) += go7007/ +obj-$(CONFIG_USB_MSI3101) += msi3101/ +obj-$(CONFIG_VIDEO_DM365_VPFE) += davinci_vpfe/ +obj-$(CONFIG_VIDEO_OMAP4) += omap4iss/ +obj-$(CONFIG_USB_SN9C102) += sn9c102/ +obj-$(CONFIG_VIDEO_OMAP2) += omap24xx/ +obj-$(CONFIG_VIDEO_TCM825X) += omap24xx/ +obj-$(CONFIG_DVB_RTL2832_SDR) += rtl2832u_sdr/ + diff --git a/drivers/staging/media/as102/Kconfig b/drivers/staging/media/as102/Kconfig new file mode 100644 index 00000000000..28aba00dc62 --- /dev/null +++ b/drivers/staging/media/as102/Kconfig @@ -0,0 +1,8 @@ +config DVB_AS102 + tristate "Abilis AS102 DVB receiver" + depends on DVB_CORE && USB && I2C && INPUT + select FW_LOADER + help + Choose Y or M here if you have a device containing an AS102 + + To compile this driver as a module, choose M here diff --git a/drivers/staging/media/as102/Makefile b/drivers/staging/media/as102/Makefile new file mode 100644 index 00000000000..8916d8a909b --- /dev/null +++ b/drivers/staging/media/as102/Makefile @@ -0,0 +1,6 @@ +dvb-as102-objs := as102_drv.o as102_fw.o as10x_cmd.o as10x_cmd_stream.o \ + as102_fe.o as102_usb_drv.o as10x_cmd_cfg.o + +obj-$(CONFIG_DVB_AS102) += dvb-as102.o + +ccflags-y += -Idrivers/media/dvb-core diff --git a/drivers/staging/media/as102/as102_drv.c b/drivers/staging/media/as102/as102_drv.c new file mode 100644 index 00000000000..09d64cd6750 --- /dev/null +++ b/drivers/staging/media/as102/as102_drv.c @@ -0,0 +1,277 @@ +/* + * Abilis Systems Single DVB-T Receiver + * Copyright (C) 2008 Pierrick Hascoet <pierrick.hascoet@abilis.com> + * Copyright (C) 2010 Devin Heitmueller <dheitmueller@kernellabs.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/mm.h> +#include <linux/kref.h> +#include <linux/uaccess.h> +#include <linux/usb.h> + +/* header file for usb device driver*/ +#include "as102_drv.h" +#include "as102_fw.h" +#include "dvbdev.h" + +int as102_debug; +module_param_named(debug, as102_debug, int, 0644); +MODULE_PARM_DESC(debug, "Turn on/off debugging (default: off)"); + +int dual_tuner; +module_param_named(dual_tuner, dual_tuner, int, 0644); +MODULE_PARM_DESC(dual_tuner, "Activate Dual-Tuner config (default: off)"); + +static int fw_upload = 1; +module_param_named(fw_upload, fw_upload, int, 0644); +MODULE_PARM_DESC(fw_upload, "Turn on/off default FW upload (default: on)"); + +static int pid_filtering; +module_param_named(pid_filtering, pid_filtering, int, 0644); +MODULE_PARM_DESC(pid_filtering, "Activate HW PID filtering (default: off)"); + +static int ts_auto_disable; +module_param_named(ts_auto_disable, ts_auto_disable, int, 0644); +MODULE_PARM_DESC(ts_auto_disable, "Stream Auto Enable on FW (default: off)"); + +int elna_enable = 1; +module_param_named(elna_enable, elna_enable, int, 0644); +MODULE_PARM_DESC(elna_enable, "Activate eLNA (default: on)"); + +DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr); + +static void as102_stop_stream(struct as102_dev_t *dev) +{ + struct as10x_bus_adapter_t *bus_adap; + + if (dev != NULL) + bus_adap = &dev->bus_adap; + else + return; + + if (bus_adap->ops->stop_stream != NULL) + bus_adap->ops->stop_stream(dev); + + if (ts_auto_disable) { + if (mutex_lock_interruptible(&dev->bus_adap.lock)) + return; + + if (as10x_cmd_stop_streaming(bus_adap) < 0) + dprintk(debug, "as10x_cmd_stop_streaming failed\n"); + + mutex_unlock(&dev->bus_adap.lock); + } +} + +static int as102_start_stream(struct as102_dev_t *dev) +{ + struct as10x_bus_adapter_t *bus_adap; + int ret = -EFAULT; + + if (dev != NULL) + bus_adap = &dev->bus_adap; + else + return ret; + + if (bus_adap->ops->start_stream != NULL) + ret = bus_adap->ops->start_stream(dev); + + if (ts_auto_disable) { + if (mutex_lock_interruptible(&dev->bus_adap.lock)) + return -EFAULT; + + ret = as10x_cmd_start_streaming(bus_adap); + + mutex_unlock(&dev->bus_adap.lock); + } + + return ret; +} + +static int as10x_pid_filter(struct as102_dev_t *dev, + int index, u16 pid, int onoff) { + + struct as10x_bus_adapter_t *bus_adap = &dev->bus_adap; + int ret = -EFAULT; + + if (mutex_lock_interruptible(&dev->bus_adap.lock)) { + dprintk(debug, "mutex_lock_interruptible(lock) failed !\n"); + return -EBUSY; + } + + switch (onoff) { + case 0: + ret = as10x_cmd_del_PID_filter(bus_adap, (uint16_t) pid); + dprintk(debug, "DEL_PID_FILTER([%02d] 0x%04x) ret = %d\n", + index, pid, ret); + break; + case 1: + { + struct as10x_ts_filter filter; + + filter.type = TS_PID_TYPE_TS; + filter.idx = 0xFF; + filter.pid = pid; + + ret = as10x_cmd_add_PID_filter(bus_adap, &filter); + dprintk(debug, + "ADD_PID_FILTER([%02d -> %02d], 0x%04x) ret = %d\n", + index, filter.idx, filter.pid, ret); + break; + } + } + + mutex_unlock(&dev->bus_adap.lock); + return ret; +} + +static int as102_dvb_dmx_start_feed(struct dvb_demux_feed *dvbdmxfeed) +{ + int ret = 0; + struct dvb_demux *demux = dvbdmxfeed->demux; + struct as102_dev_t *as102_dev = demux->priv; + + if (mutex_lock_interruptible(&as102_dev->sem)) + return -ERESTARTSYS; + + if (pid_filtering) + as10x_pid_filter(as102_dev, dvbdmxfeed->index, + dvbdmxfeed->pid, 1); + + if (as102_dev->streaming++ == 0) + ret = as102_start_stream(as102_dev); + + mutex_unlock(&as102_dev->sem); + return ret; +} + +static int as102_dvb_dmx_stop_feed(struct dvb_demux_feed *dvbdmxfeed) +{ + struct dvb_demux *demux = dvbdmxfeed->demux; + struct as102_dev_t *as102_dev = demux->priv; + + if (mutex_lock_interruptible(&as102_dev->sem)) + return -ERESTARTSYS; + + if (--as102_dev->streaming == 0) + as102_stop_stream(as102_dev); + + if (pid_filtering) + as10x_pid_filter(as102_dev, dvbdmxfeed->index, + dvbdmxfeed->pid, 0); + + mutex_unlock(&as102_dev->sem); + return 0; +} + +int as102_dvb_register(struct as102_dev_t *as102_dev) +{ + struct device *dev = &as102_dev->bus_adap.usb_dev->dev; + int ret; + + ret = dvb_register_adapter(&as102_dev->dvb_adap, + as102_dev->name, THIS_MODULE, + dev, adapter_nr); + if (ret < 0) { + dev_err(dev, "%s: dvb_register_adapter() failed: %d\n", + __func__, ret); + return ret; + } + + as102_dev->dvb_dmx.priv = as102_dev; + as102_dev->dvb_dmx.filternum = pid_filtering ? 16 : 256; + as102_dev->dvb_dmx.feednum = 256; + as102_dev->dvb_dmx.start_feed = as102_dvb_dmx_start_feed; + as102_dev->dvb_dmx.stop_feed = as102_dvb_dmx_stop_feed; + + as102_dev->dvb_dmx.dmx.capabilities = DMX_TS_FILTERING | + DMX_SECTION_FILTERING; + + as102_dev->dvb_dmxdev.filternum = as102_dev->dvb_dmx.filternum; + as102_dev->dvb_dmxdev.demux = &as102_dev->dvb_dmx.dmx; + as102_dev->dvb_dmxdev.capabilities = 0; + + ret = dvb_dmx_init(&as102_dev->dvb_dmx); + if (ret < 0) { + dev_err(dev, "%s: dvb_dmx_init() failed: %d\n", __func__, ret); + goto edmxinit; + } + + ret = dvb_dmxdev_init(&as102_dev->dvb_dmxdev, &as102_dev->dvb_adap); + if (ret < 0) { + dev_err(dev, "%s: dvb_dmxdev_init() failed: %d\n", + __func__, ret); + goto edmxdinit; + } + + ret = as102_dvb_register_fe(as102_dev, &as102_dev->dvb_fe); + if (ret < 0) { + dev_err(dev, "%s: as102_dvb_register_frontend() failed: %d", + __func__, ret); + goto efereg; + } + + /* init bus mutex for token locking */ + mutex_init(&as102_dev->bus_adap.lock); + + /* init start / stop stream mutex */ + mutex_init(&as102_dev->sem); + + /* + * try to load as102 firmware. If firmware upload failed, we'll be + * able to upload it later. + */ + if (fw_upload) + try_then_request_module(as102_fw_upload(&as102_dev->bus_adap), + "firmware_class"); + + pr_info("Registered device %s", as102_dev->name); + return 0; + +efereg: + dvb_dmxdev_release(&as102_dev->dvb_dmxdev); +edmxdinit: + dvb_dmx_release(&as102_dev->dvb_dmx); +edmxinit: + dvb_unregister_adapter(&as102_dev->dvb_adap); + return ret; +} + +void as102_dvb_unregister(struct as102_dev_t *as102_dev) +{ + /* unregister as102 frontend */ + as102_dvb_unregister_fe(&as102_dev->dvb_fe); + + /* unregister demux device */ + dvb_dmxdev_release(&as102_dev->dvb_dmxdev); + dvb_dmx_release(&as102_dev->dvb_dmx); + + /* unregister dvb adapter */ + dvb_unregister_adapter(&as102_dev->dvb_adap); + + pr_info("Unregistered device %s", as102_dev->name); +} + +module_usb_driver(as102_usb_driver); + +/* modinfo details */ +MODULE_DESCRIPTION(DRIVER_FULL_NAME); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Pierrick Hascoet <pierrick.hascoet@abilis.com>"); diff --git a/drivers/staging/media/as102/as102_drv.h b/drivers/staging/media/as102/as102_drv.h new file mode 100644 index 00000000000..a06837dcc05 --- /dev/null +++ b/drivers/staging/media/as102/as102_drv.h @@ -0,0 +1,99 @@ +/* + * Abilis Systems Single DVB-T Receiver + * Copyright (C) 2008 Pierrick Hascoet <pierrick.hascoet@abilis.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/usb.h> +#include <dvb_demux.h> +#include <dvb_frontend.h> +#include <dmxdev.h> +#include "as10x_cmd.h" +#include "as102_usb_drv.h" + +#define DRIVER_FULL_NAME "Abilis Systems as10x usb driver" +#define DRIVER_NAME "as10x_usb" + +extern int as102_debug; +#define debug as102_debug +extern struct usb_driver as102_usb_driver; +extern int elna_enable; + +#define dprintk(debug, args...) \ + do { if (debug) { \ + pr_debug("%s: ", __func__); \ + printk(args); \ + } } while (0) + +#define AS102_DEVICE_MAJOR 192 + +#define AS102_USB_BUF_SIZE 512 +#define MAX_STREAM_URB 32 + +struct as10x_bus_adapter_t { + struct usb_device *usb_dev; + /* bus token lock */ + struct mutex lock; + /* low level interface for bus adapter */ + union as10x_bus_token_t { + /* usb token */ + struct as10x_usb_token_cmd_t usb; + } token; + + /* token cmd xfer id */ + uint16_t cmd_xid; + + /* as10x command and response for dvb interface*/ + struct as10x_cmd_t *cmd, *rsp; + + /* bus adapter private ops callback */ + struct as102_priv_ops_t *ops; +}; + +struct as102_dev_t { + const char *name; + struct as10x_bus_adapter_t bus_adap; + struct list_head device_entry; + struct kref kref; + uint8_t elna_cfg; + + struct dvb_adapter dvb_adap; + struct dvb_frontend dvb_fe; + struct dvb_demux dvb_dmx; + struct dmxdev dvb_dmxdev; + + /* demodulator stats */ + struct as10x_demod_stats demod_stats; + /* signal strength */ + uint16_t signal_strength; + /* bit error rate */ + uint32_t ber; + + /* timer handle to trig ts stream download */ + struct timer_list timer_handle; + + struct mutex sem; + dma_addr_t dma_addr; + void *stream; + int streaming; + struct urb *stream_urb[MAX_STREAM_URB]; +}; + +int as102_dvb_register(struct as102_dev_t *dev); +void as102_dvb_unregister(struct as102_dev_t *dev); + +int as102_dvb_register_fe(struct as102_dev_t *dev, struct dvb_frontend *fe); +int as102_dvb_unregister_fe(struct dvb_frontend *dev); diff --git a/drivers/staging/media/as102/as102_fe.c b/drivers/staging/media/as102/as102_fe.c new file mode 100644 index 00000000000..b686b7617cd --- /dev/null +++ b/drivers/staging/media/as102/as102_fe.c @@ -0,0 +1,571 @@ +/* + * Abilis Systems Single DVB-T Receiver + * Copyright (C) 2008 Pierrick Hascoet <pierrick.hascoet@abilis.com> + * Copyright (C) 2010 Devin Heitmueller <dheitmueller@kernellabs.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +#include "as102_drv.h" +#include "as10x_types.h" +#include "as10x_cmd.h" + +static void as10x_fe_copy_tps_parameters(struct dtv_frontend_properties *dst, + struct as10x_tps *src); + +static void as102_fe_copy_tune_parameters(struct as10x_tune_args *dst, + struct dtv_frontend_properties *src); + +static int as102_fe_set_frontend(struct dvb_frontend *fe) +{ + struct dtv_frontend_properties *p = &fe->dtv_property_cache; + int ret = 0; + struct as102_dev_t *dev; + struct as10x_tune_args tune_args = { 0 }; + + dev = (struct as102_dev_t *) fe->tuner_priv; + if (dev == NULL) + return -ENODEV; + + if (mutex_lock_interruptible(&dev->bus_adap.lock)) + return -EBUSY; + + as102_fe_copy_tune_parameters(&tune_args, p); + + /* send abilis command: SET_TUNE */ + ret = as10x_cmd_set_tune(&dev->bus_adap, &tune_args); + if (ret != 0) + dprintk(debug, "as10x_cmd_set_tune failed. (err = %d)\n", ret); + + mutex_unlock(&dev->bus_adap.lock); + + return (ret < 0) ? -EINVAL : 0; +} + +static int as102_fe_get_frontend(struct dvb_frontend *fe) +{ + struct dtv_frontend_properties *p = &fe->dtv_property_cache; + int ret = 0; + struct as102_dev_t *dev; + struct as10x_tps tps = { 0 }; + + dev = (struct as102_dev_t *) fe->tuner_priv; + if (dev == NULL) + return -EINVAL; + + if (mutex_lock_interruptible(&dev->bus_adap.lock)) + return -EBUSY; + + /* send abilis command: GET_TPS */ + ret = as10x_cmd_get_tps(&dev->bus_adap, &tps); + + if (ret == 0) + as10x_fe_copy_tps_parameters(p, &tps); + + mutex_unlock(&dev->bus_adap.lock); + + return (ret < 0) ? -EINVAL : 0; +} + +static int as102_fe_get_tune_settings(struct dvb_frontend *fe, + struct dvb_frontend_tune_settings *settings) { + +#if 0 + dprintk(debug, "step_size = %d\n", settings->step_size); + dprintk(debug, "max_drift = %d\n", settings->max_drift); + dprintk(debug, "min_delay_ms = %d -> %d\n", settings->min_delay_ms, + 1000); +#endif + + settings->min_delay_ms = 1000; + + return 0; +} + + +static int as102_fe_read_status(struct dvb_frontend *fe, fe_status_t *status) +{ + int ret = 0; + struct as102_dev_t *dev; + struct as10x_tune_status tstate = { 0 }; + + dev = (struct as102_dev_t *) fe->tuner_priv; + if (dev == NULL) + return -ENODEV; + + if (mutex_lock_interruptible(&dev->bus_adap.lock)) + return -EBUSY; + + /* send abilis command: GET_TUNE_STATUS */ + ret = as10x_cmd_get_tune_status(&dev->bus_adap, &tstate); + if (ret < 0) { + dprintk(debug, "as10x_cmd_get_tune_status failed (err = %d)\n", + ret); + goto out; + } + + dev->signal_strength = tstate.signal_strength; + dev->ber = tstate.BER; + + switch (tstate.tune_state) { + case TUNE_STATUS_SIGNAL_DVB_OK: + *status = FE_HAS_SIGNAL | FE_HAS_CARRIER; + break; + case TUNE_STATUS_STREAM_DETECTED: + *status = FE_HAS_SIGNAL | FE_HAS_CARRIER | FE_HAS_SYNC; + break; + case TUNE_STATUS_STREAM_TUNED: + *status = FE_HAS_SIGNAL | FE_HAS_CARRIER | FE_HAS_SYNC | + FE_HAS_LOCK; + break; + default: + *status = TUNE_STATUS_NOT_TUNED; + } + + dprintk(debug, "tuner status: 0x%02x, strength %d, per: %d, ber: %d\n", + tstate.tune_state, tstate.signal_strength, + tstate.PER, tstate.BER); + + if (*status & FE_HAS_LOCK) { + if (as10x_cmd_get_demod_stats(&dev->bus_adap, + (struct as10x_demod_stats *) &dev->demod_stats) < 0) { + memset(&dev->demod_stats, 0, sizeof(dev->demod_stats)); + dprintk(debug, + "as10x_cmd_get_demod_stats failed (probably not tuned)\n"); + } else { + dprintk(debug, + "demod status: fc: 0x%08x, bad fc: 0x%08x, " + "bytes corrected: 0x%08x , MER: 0x%04x\n", + dev->demod_stats.frame_count, + dev->demod_stats.bad_frame_count, + dev->demod_stats.bytes_fixed_by_rs, + dev->demod_stats.mer); + } + } else { + memset(&dev->demod_stats, 0, sizeof(dev->demod_stats)); + } + +out: + mutex_unlock(&dev->bus_adap.lock); + return ret; +} + +/* + * Note: + * - in AS102 SNR=MER + * - the SNR will be returned in linear terms, i.e. not in dB + * - the accuracy equals ±2dB for a SNR range from 4dB to 30dB + * - the accuracy is >2dB for SNR values outside this range + */ +static int as102_fe_read_snr(struct dvb_frontend *fe, u16 *snr) +{ + struct as102_dev_t *dev; + + dev = (struct as102_dev_t *) fe->tuner_priv; + if (dev == NULL) + return -ENODEV; + + *snr = dev->demod_stats.mer; + + return 0; +} + +static int as102_fe_read_ber(struct dvb_frontend *fe, u32 *ber) +{ + struct as102_dev_t *dev; + + dev = (struct as102_dev_t *) fe->tuner_priv; + if (dev == NULL) + return -ENODEV; + + *ber = dev->ber; + + return 0; +} + +static int as102_fe_read_signal_strength(struct dvb_frontend *fe, + u16 *strength) +{ + struct as102_dev_t *dev; + + dev = (struct as102_dev_t *) fe->tuner_priv; + if (dev == NULL) + return -ENODEV; + + *strength = (((0xffff * 400) * dev->signal_strength + 41000) * 2); + + return 0; +} + +static int as102_fe_read_ucblocks(struct dvb_frontend *fe, u32 *ucblocks) +{ + struct as102_dev_t *dev; + + dev = (struct as102_dev_t *) fe->tuner_priv; + if (dev == NULL) + return -ENODEV; + + if (dev->demod_stats.has_started) + *ucblocks = dev->demod_stats.bad_frame_count; + else + *ucblocks = 0; + + return 0; +} + +static int as102_fe_ts_bus_ctrl(struct dvb_frontend *fe, int acquire) +{ + struct as102_dev_t *dev; + int ret; + + dev = (struct as102_dev_t *) fe->tuner_priv; + if (dev == NULL) + return -ENODEV; + + if (mutex_lock_interruptible(&dev->bus_adap.lock)) + return -EBUSY; + + if (acquire) { + if (elna_enable) + as10x_cmd_set_context(&dev->bus_adap, + CONTEXT_LNA, dev->elna_cfg); + + ret = as10x_cmd_turn_on(&dev->bus_adap); + } else { + ret = as10x_cmd_turn_off(&dev->bus_adap); + } + + mutex_unlock(&dev->bus_adap.lock); + + return ret; +} + +static struct dvb_frontend_ops as102_fe_ops = { + .delsys = { SYS_DVBT }, + .info = { + .name = "Unknown AS102 device", + .frequency_min = 174000000, + .frequency_max = 862000000, + .frequency_stepsize = 166667, + .caps = FE_CAN_INVERSION_AUTO + | FE_CAN_FEC_1_2 | FE_CAN_FEC_2_3 | FE_CAN_FEC_3_4 + | FE_CAN_FEC_5_6 | FE_CAN_FEC_7_8 | FE_CAN_FEC_AUTO + | FE_CAN_QAM_16 | FE_CAN_QAM_64 | FE_CAN_QPSK + | FE_CAN_QAM_AUTO + | FE_CAN_TRANSMISSION_MODE_AUTO + | FE_CAN_GUARD_INTERVAL_AUTO + | FE_CAN_HIERARCHY_AUTO + | FE_CAN_RECOVER + | FE_CAN_MUTE_TS + }, + + .set_frontend = as102_fe_set_frontend, + .get_frontend = as102_fe_get_frontend, + .get_tune_settings = as102_fe_get_tune_settings, + + .read_status = as102_fe_read_status, + .read_snr = as102_fe_read_snr, + .read_ber = as102_fe_read_ber, + .read_signal_strength = as102_fe_read_signal_strength, + .read_ucblocks = as102_fe_read_ucblocks, + .ts_bus_ctrl = as102_fe_ts_bus_ctrl, +}; + +int as102_dvb_unregister_fe(struct dvb_frontend *fe) +{ + /* unregister frontend */ + dvb_unregister_frontend(fe); + + /* detach frontend */ + dvb_frontend_detach(fe); + + return 0; +} + +int as102_dvb_register_fe(struct as102_dev_t *as102_dev, + struct dvb_frontend *dvb_fe) +{ + int errno; + struct dvb_adapter *dvb_adap; + + if (as102_dev == NULL) + return -EINVAL; + + /* extract dvb_adapter */ + dvb_adap = &as102_dev->dvb_adap; + + /* init frontend callback ops */ + memcpy(&dvb_fe->ops, &as102_fe_ops, sizeof(struct dvb_frontend_ops)); + strncpy(dvb_fe->ops.info.name, as102_dev->name, + sizeof(dvb_fe->ops.info.name)); + + /* register dvb frontend */ + errno = dvb_register_frontend(dvb_adap, dvb_fe); + if (errno == 0) + dvb_fe->tuner_priv = as102_dev; + + return errno; +} + +static void as10x_fe_copy_tps_parameters(struct dtv_frontend_properties *fe_tps, + struct as10x_tps *as10x_tps) +{ + + /* extract constellation */ + switch (as10x_tps->modulation) { + case CONST_QPSK: + fe_tps->modulation = QPSK; + break; + case CONST_QAM16: + fe_tps->modulation = QAM_16; + break; + case CONST_QAM64: + fe_tps->modulation = QAM_64; + break; + } + + /* extract hierarchy */ + switch (as10x_tps->hierarchy) { + case HIER_NONE: + fe_tps->hierarchy = HIERARCHY_NONE; + break; + case HIER_ALPHA_1: + fe_tps->hierarchy = HIERARCHY_1; + break; + case HIER_ALPHA_2: + fe_tps->hierarchy = HIERARCHY_2; + break; + case HIER_ALPHA_4: + fe_tps->hierarchy = HIERARCHY_4; + break; + } + + /* extract code rate HP */ + switch (as10x_tps->code_rate_HP) { + case CODE_RATE_1_2: + fe_tps->code_rate_HP = FEC_1_2; + break; + case CODE_RATE_2_3: + fe_tps->code_rate_HP = FEC_2_3; + break; + case CODE_RATE_3_4: + fe_tps->code_rate_HP = FEC_3_4; + break; + case CODE_RATE_5_6: + fe_tps->code_rate_HP = FEC_5_6; + break; + case CODE_RATE_7_8: + fe_tps->code_rate_HP = FEC_7_8; + break; + } + + /* extract code rate LP */ + switch (as10x_tps->code_rate_LP) { + case CODE_RATE_1_2: + fe_tps->code_rate_LP = FEC_1_2; + break; + case CODE_RATE_2_3: + fe_tps->code_rate_LP = FEC_2_3; + break; + case CODE_RATE_3_4: + fe_tps->code_rate_LP = FEC_3_4; + break; + case CODE_RATE_5_6: + fe_tps->code_rate_LP = FEC_5_6; + break; + case CODE_RATE_7_8: + fe_tps->code_rate_LP = FEC_7_8; + break; + } + + /* extract guard interval */ + switch (as10x_tps->guard_interval) { + case GUARD_INT_1_32: + fe_tps->guard_interval = GUARD_INTERVAL_1_32; + break; + case GUARD_INT_1_16: + fe_tps->guard_interval = GUARD_INTERVAL_1_16; + break; + case GUARD_INT_1_8: + fe_tps->guard_interval = GUARD_INTERVAL_1_8; + break; + case GUARD_INT_1_4: + fe_tps->guard_interval = GUARD_INTERVAL_1_4; + break; + } + + /* extract transmission mode */ + switch (as10x_tps->transmission_mode) { + case TRANS_MODE_2K: + fe_tps->transmission_mode = TRANSMISSION_MODE_2K; + break; + case TRANS_MODE_8K: + fe_tps->transmission_mode = TRANSMISSION_MODE_8K; + break; + } +} + +static uint8_t as102_fe_get_code_rate(fe_code_rate_t arg) +{ + uint8_t c; + + switch (arg) { + case FEC_1_2: + c = CODE_RATE_1_2; + break; + case FEC_2_3: + c = CODE_RATE_2_3; + break; + case FEC_3_4: + c = CODE_RATE_3_4; + break; + case FEC_5_6: + c = CODE_RATE_5_6; + break; + case FEC_7_8: + c = CODE_RATE_7_8; + break; + default: + c = CODE_RATE_UNKNOWN; + break; + } + + return c; +} + +static void as102_fe_copy_tune_parameters(struct as10x_tune_args *tune_args, + struct dtv_frontend_properties *params) +{ + + /* set frequency */ + tune_args->freq = params->frequency / 1000; + + /* fix interleaving_mode */ + tune_args->interleaving_mode = INTLV_NATIVE; + + switch (params->bandwidth_hz) { + case 8000000: + tune_args->bandwidth = BW_8_MHZ; + break; + case 7000000: + tune_args->bandwidth = BW_7_MHZ; + break; + case 6000000: + tune_args->bandwidth = BW_6_MHZ; + break; + default: + tune_args->bandwidth = BW_8_MHZ; + } + + switch (params->guard_interval) { + case GUARD_INTERVAL_1_32: + tune_args->guard_interval = GUARD_INT_1_32; + break; + case GUARD_INTERVAL_1_16: + tune_args->guard_interval = GUARD_INT_1_16; + break; + case GUARD_INTERVAL_1_8: + tune_args->guard_interval = GUARD_INT_1_8; + break; + case GUARD_INTERVAL_1_4: + tune_args->guard_interval = GUARD_INT_1_4; + break; + case GUARD_INTERVAL_AUTO: + default: + tune_args->guard_interval = GUARD_UNKNOWN; + break; + } + + switch (params->modulation) { + case QPSK: + tune_args->modulation = CONST_QPSK; + break; + case QAM_16: + tune_args->modulation = CONST_QAM16; + break; + case QAM_64: + tune_args->modulation = CONST_QAM64; + break; + default: + tune_args->modulation = CONST_UNKNOWN; + break; + } + + switch (params->transmission_mode) { + case TRANSMISSION_MODE_2K: + tune_args->transmission_mode = TRANS_MODE_2K; + break; + case TRANSMISSION_MODE_8K: + tune_args->transmission_mode = TRANS_MODE_8K; + break; + default: + tune_args->transmission_mode = TRANS_MODE_UNKNOWN; + } + + switch (params->hierarchy) { + case HIERARCHY_NONE: + tune_args->hierarchy = HIER_NONE; + break; + case HIERARCHY_1: + tune_args->hierarchy = HIER_ALPHA_1; + break; + case HIERARCHY_2: + tune_args->hierarchy = HIER_ALPHA_2; + break; + case HIERARCHY_4: + tune_args->hierarchy = HIER_ALPHA_4; + break; + case HIERARCHY_AUTO: + tune_args->hierarchy = HIER_UNKNOWN; + break; + } + + dprintk(debug, "tuner parameters: freq: %d bw: 0x%02x gi: 0x%02x\n", + params->frequency, + tune_args->bandwidth, + tune_args->guard_interval); + + /* + * Detect a hierarchy selection + * if HP/LP are both set to FEC_NONE, HP will be selected. + */ + if ((tune_args->hierarchy != HIER_NONE) && + ((params->code_rate_LP == FEC_NONE) || + (params->code_rate_HP == FEC_NONE))) { + + if (params->code_rate_LP == FEC_NONE) { + tune_args->hier_select = HIER_HIGH_PRIORITY; + tune_args->code_rate = + as102_fe_get_code_rate(params->code_rate_HP); + } + + if (params->code_rate_HP == FEC_NONE) { + tune_args->hier_select = HIER_LOW_PRIORITY; + tune_args->code_rate = + as102_fe_get_code_rate(params->code_rate_LP); + } + + dprintk(debug, + "\thierarchy: 0x%02x selected: %s code_rate_%s: 0x%02x\n", + tune_args->hierarchy, + tune_args->hier_select == HIER_HIGH_PRIORITY ? + "HP" : "LP", + tune_args->hier_select == HIER_HIGH_PRIORITY ? + "HP" : "LP", + tune_args->code_rate); + } else { + tune_args->code_rate = + as102_fe_get_code_rate(params->code_rate_HP); + } +} diff --git a/drivers/staging/media/as102/as102_fw.c b/drivers/staging/media/as102/as102_fw.c new file mode 100644 index 00000000000..f33f752c0aa --- /dev/null +++ b/drivers/staging/media/as102/as102_fw.c @@ -0,0 +1,232 @@ +/* + * Abilis Systems Single DVB-T Receiver + * Copyright (C) 2008 Pierrick Hascoet <pierrick.hascoet@abilis.com> + * Copyright (C) 2010 Devin Heitmueller <dheitmueller@kernellabs.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/ctype.h> +#include <linux/delay.h> +#include <linux/firmware.h> + +#include "as102_drv.h" +#include "as102_fw.h" + +static const char as102_st_fw1[] = "as102_data1_st.hex"; +static const char as102_st_fw2[] = "as102_data2_st.hex"; +static const char as102_dt_fw1[] = "as102_data1_dt.hex"; +static const char as102_dt_fw2[] = "as102_data2_dt.hex"; + +static unsigned char atohx(unsigned char *dst, char *src) +{ + unsigned char value = 0; + + char msb = tolower(*src) - '0'; + char lsb = tolower(*(src + 1)) - '0'; + + if (msb > 9) + msb -= 7; + if (lsb > 9) + lsb -= 7; + + *dst = value = ((msb & 0xF) << 4) | (lsb & 0xF); + return value; +} + +/* + * Parse INTEL HEX firmware file to extract address and data. + */ +static int parse_hex_line(unsigned char *fw_data, unsigned char *addr, + unsigned char *data, int *dataLength, + unsigned char *addr_has_changed) { + + int count = 0; + unsigned char *src, dst; + + if (*fw_data++ != ':') { + pr_err("invalid firmware file\n"); + return -EFAULT; + } + + /* locate end of line */ + for (src = fw_data; *src != '\n'; src += 2) { + atohx(&dst, src); + /* parse line to split addr / data */ + switch (count) { + case 0: + *dataLength = dst; + break; + case 1: + addr[2] = dst; + break; + case 2: + addr[3] = dst; + break; + case 3: + /* check if data is an address */ + if (dst == 0x04) + *addr_has_changed = 1; + else + *addr_has_changed = 0; + break; + case 4: + case 5: + if (*addr_has_changed) + addr[(count - 4)] = dst; + else + data[(count - 4)] = dst; + break; + default: + data[(count - 4)] = dst; + break; + } + count++; + } + + /* return read value + ':' + '\n' */ + return (count * 2) + 2; +} + +static int as102_firmware_upload(struct as10x_bus_adapter_t *bus_adap, + unsigned char *cmd, + const struct firmware *firmware) { + + struct as10x_fw_pkt_t fw_pkt; + int total_read_bytes = 0, errno = 0; + unsigned char addr_has_changed = 0; + + for (total_read_bytes = 0; total_read_bytes < firmware->size; ) { + int read_bytes = 0, data_len = 0; + + /* parse intel hex line */ + read_bytes = parse_hex_line( + (u8 *) (firmware->data + total_read_bytes), + fw_pkt.raw.address, + fw_pkt.raw.data, + &data_len, + &addr_has_changed); + + if (read_bytes <= 0) + goto error; + + /* detect the end of file */ + total_read_bytes += read_bytes; + if (total_read_bytes == firmware->size) { + fw_pkt.u.request[0] = 0x00; + fw_pkt.u.request[1] = 0x03; + + /* send EOF command */ + errno = bus_adap->ops->upload_fw_pkt(bus_adap, + (uint8_t *) + &fw_pkt, 2, 0); + if (errno < 0) + goto error; + } else { + if (!addr_has_changed) { + /* prepare command to send */ + fw_pkt.u.request[0] = 0x00; + fw_pkt.u.request[1] = 0x01; + + data_len += sizeof(fw_pkt.u.request); + data_len += sizeof(fw_pkt.raw.address); + + /* send cmd to device */ + errno = bus_adap->ops->upload_fw_pkt(bus_adap, + (uint8_t *) + &fw_pkt, + data_len, + 0); + if (errno < 0) + goto error; + } + } + } +error: + return (errno == 0) ? total_read_bytes : errno; +} + +int as102_fw_upload(struct as10x_bus_adapter_t *bus_adap) +{ + int errno = -EFAULT; + const struct firmware *firmware = NULL; + unsigned char *cmd_buf = NULL; + const char *fw1, *fw2; + struct usb_device *dev = bus_adap->usb_dev; + + /* select fw file to upload */ + if (dual_tuner) { + fw1 = as102_dt_fw1; + fw2 = as102_dt_fw2; + } else { + fw1 = as102_st_fw1; + fw2 = as102_st_fw2; + } + + /* allocate buffer to store firmware upload command and data */ + cmd_buf = kzalloc(MAX_FW_PKT_SIZE, GFP_KERNEL); + if (cmd_buf == NULL) { + errno = -ENOMEM; + goto error; + } + + /* request kernel to locate firmware file: part1 */ + errno = request_firmware(&firmware, fw1, &dev->dev); + if (errno < 0) { + pr_err("%s: unable to locate firmware file: %s\n", + DRIVER_NAME, fw1); + goto error; + } + + /* initiate firmware upload */ + errno = as102_firmware_upload(bus_adap, cmd_buf, firmware); + if (errno < 0) { + pr_err("%s: error during firmware upload part1\n", + DRIVER_NAME); + goto error; + } + + pr_info("%s: firmware: %s loaded with success\n", + DRIVER_NAME, fw1); + release_firmware(firmware); + + /* wait for boot to complete */ + mdelay(100); + + /* request kernel to locate firmware file: part2 */ + errno = request_firmware(&firmware, fw2, &dev->dev); + if (errno < 0) { + pr_err("%s: unable to locate firmware file: %s\n", + DRIVER_NAME, fw2); + goto error; + } + + /* initiate firmware upload */ + errno = as102_firmware_upload(bus_adap, cmd_buf, firmware); + if (errno < 0) { + pr_err("%s: error during firmware upload part2\n", + DRIVER_NAME); + goto error; + } + + pr_info("%s: firmware: %s loaded with success\n", + DRIVER_NAME, fw2); +error: + kfree(cmd_buf); + release_firmware(firmware); + + return errno; +} diff --git a/drivers/staging/media/as102/as102_fw.h b/drivers/staging/media/as102/as102_fw.h new file mode 100644 index 00000000000..4bfc6849d95 --- /dev/null +++ b/drivers/staging/media/as102/as102_fw.h @@ -0,0 +1,38 @@ +/* + * Abilis Systems Single DVB-T Receiver + * Copyright (C) 2008 Pierrick Hascoet <pierrick.hascoet@abilis.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +#define MAX_FW_PKT_SIZE 64 + +extern int dual_tuner; + +struct as10x_raw_fw_pkt { + unsigned char address[4]; + unsigned char data[MAX_FW_PKT_SIZE - 6]; +} __packed; + +struct as10x_fw_pkt_t { + union { + unsigned char request[2]; + unsigned char length[2]; + } __packed u; + struct as10x_raw_fw_pkt raw; +} __packed; + +#ifdef __KERNEL__ +int as102_fw_upload(struct as10x_bus_adapter_t *bus_adap); +#endif diff --git a/drivers/staging/media/as102/as102_usb_drv.c b/drivers/staging/media/as102/as102_usb_drv.c new file mode 100644 index 00000000000..e6f6278e97d --- /dev/null +++ b/drivers/staging/media/as102/as102_usb_drv.c @@ -0,0 +1,472 @@ +/* + * Abilis Systems Single DVB-T Receiver + * Copyright (C) 2008 Pierrick Hascoet <pierrick.hascoet@abilis.com> + * Copyright (C) 2010 Devin Heitmueller <dheitmueller@kernellabs.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/mm.h> +#include <linux/usb.h> + +#include "as102_drv.h" +#include "as102_usb_drv.h" +#include "as102_fw.h" + +static void as102_usb_disconnect(struct usb_interface *interface); +static int as102_usb_probe(struct usb_interface *interface, + const struct usb_device_id *id); + +static int as102_usb_start_stream(struct as102_dev_t *dev); +static void as102_usb_stop_stream(struct as102_dev_t *dev); + +static int as102_open(struct inode *inode, struct file *file); +static int as102_release(struct inode *inode, struct file *file); + +static struct usb_device_id as102_usb_id_table[] = { + { USB_DEVICE(AS102_USB_DEVICE_VENDOR_ID, AS102_USB_DEVICE_PID_0001) }, + { USB_DEVICE(PCTV_74E_USB_VID, PCTV_74E_USB_PID) }, + { USB_DEVICE(ELGATO_EYETV_DTT_USB_VID, ELGATO_EYETV_DTT_USB_PID) }, + { USB_DEVICE(NBOX_DVBT_DONGLE_USB_VID, NBOX_DVBT_DONGLE_USB_PID) }, + { USB_DEVICE(SKY_IT_DIGITAL_KEY_USB_VID, SKY_IT_DIGITAL_KEY_USB_PID) }, + { } /* Terminating entry */ +}; + +/* Note that this table must always have the same number of entries as the + as102_usb_id_table struct */ +static const char * const as102_device_names[] = { + AS102_REFERENCE_DESIGN, + AS102_PCTV_74E, + AS102_ELGATO_EYETV_DTT_NAME, + AS102_NBOX_DVBT_DONGLE_NAME, + AS102_SKY_IT_DIGITAL_KEY_NAME, + NULL /* Terminating entry */ +}; + +/* eLNA configuration: devices built on the reference design work best + with 0xA0, while custom designs seem to require 0xC0 */ +static uint8_t const as102_elna_cfg[] = { + 0xA0, + 0xC0, + 0xC0, + 0xA0, + 0xA0, + 0x00 /* Terminating entry */ +}; + +struct usb_driver as102_usb_driver = { + .name = DRIVER_FULL_NAME, + .probe = as102_usb_probe, + .disconnect = as102_usb_disconnect, + .id_table = as102_usb_id_table +}; + +static const struct file_operations as102_dev_fops = { + .owner = THIS_MODULE, + .open = as102_open, + .release = as102_release, +}; + +static struct usb_class_driver as102_usb_class_driver = { + .name = "aton2-%d", + .fops = &as102_dev_fops, + .minor_base = AS102_DEVICE_MAJOR, +}; + +static int as102_usb_xfer_cmd(struct as10x_bus_adapter_t *bus_adap, + unsigned char *send_buf, int send_buf_len, + unsigned char *recv_buf, int recv_buf_len) +{ + int ret = 0; + + if (send_buf != NULL) { + ret = usb_control_msg(bus_adap->usb_dev, + usb_sndctrlpipe(bus_adap->usb_dev, 0), + AS102_USB_DEVICE_TX_CTRL_CMD, + USB_DIR_OUT | USB_TYPE_VENDOR | + USB_RECIP_DEVICE, + bus_adap->cmd_xid, /* value */ + 0, /* index */ + send_buf, send_buf_len, + USB_CTRL_SET_TIMEOUT /* 200 */); + if (ret < 0) { + dprintk(debug, "usb_control_msg(send) failed, err %i\n", + ret); + return ret; + } + + if (ret != send_buf_len) { + dprintk(debug, "only wrote %d of %d bytes\n", + ret, send_buf_len); + return -1; + } + } + + if (recv_buf != NULL) { +#ifdef TRACE + dprintk(debug, "want to read: %d bytes\n", recv_buf_len); +#endif + ret = usb_control_msg(bus_adap->usb_dev, + usb_rcvctrlpipe(bus_adap->usb_dev, 0), + AS102_USB_DEVICE_RX_CTRL_CMD, + USB_DIR_IN | USB_TYPE_VENDOR | + USB_RECIP_DEVICE, + bus_adap->cmd_xid, /* value */ + 0, /* index */ + recv_buf, recv_buf_len, + USB_CTRL_GET_TIMEOUT /* 200 */); + if (ret < 0) { + dprintk(debug, "usb_control_msg(recv) failed, err %i\n", + ret); + return ret; + } +#ifdef TRACE + dprintk(debug, "read %d bytes\n", recv_buf_len); +#endif + } + + return ret; +} + +static int as102_send_ep1(struct as10x_bus_adapter_t *bus_adap, + unsigned char *send_buf, + int send_buf_len, + int swap32) +{ + int ret = 0, actual_len; + + ret = usb_bulk_msg(bus_adap->usb_dev, + usb_sndbulkpipe(bus_adap->usb_dev, 1), + send_buf, send_buf_len, &actual_len, 200); + if (ret) { + dprintk(debug, "usb_bulk_msg(send) failed, err %i\n", ret); + return ret; + } + + if (actual_len != send_buf_len) { + dprintk(debug, "only wrote %d of %d bytes\n", + actual_len, send_buf_len); + return -1; + } + return ret ? ret : actual_len; +} + +static int as102_read_ep2(struct as10x_bus_adapter_t *bus_adap, + unsigned char *recv_buf, int recv_buf_len) +{ + int ret = 0, actual_len; + + if (recv_buf == NULL) + return -EINVAL; + + ret = usb_bulk_msg(bus_adap->usb_dev, + usb_rcvbulkpipe(bus_adap->usb_dev, 2), + recv_buf, recv_buf_len, &actual_len, 200); + if (ret) { + dprintk(debug, "usb_bulk_msg(recv) failed, err %i\n", ret); + return ret; + } + + if (actual_len != recv_buf_len) { + dprintk(debug, "only read %d of %d bytes\n", + actual_len, recv_buf_len); + return -1; + } + return ret ? ret : actual_len; +} + +static struct as102_priv_ops_t as102_priv_ops = { + .upload_fw_pkt = as102_send_ep1, + .xfer_cmd = as102_usb_xfer_cmd, + .as102_read_ep2 = as102_read_ep2, + .start_stream = as102_usb_start_stream, + .stop_stream = as102_usb_stop_stream, +}; + +static int as102_submit_urb_stream(struct as102_dev_t *dev, struct urb *urb) +{ + int err; + + usb_fill_bulk_urb(urb, + dev->bus_adap.usb_dev, + usb_rcvbulkpipe(dev->bus_adap.usb_dev, 0x2), + urb->transfer_buffer, + AS102_USB_BUF_SIZE, + as102_urb_stream_irq, + dev); + + err = usb_submit_urb(urb, GFP_ATOMIC); + if (err) + dprintk(debug, "%s: usb_submit_urb failed\n", __func__); + + return err; +} + +void as102_urb_stream_irq(struct urb *urb) +{ + struct as102_dev_t *as102_dev = urb->context; + + if (urb->actual_length > 0) { + dvb_dmx_swfilter(&as102_dev->dvb_dmx, + urb->transfer_buffer, + urb->actual_length); + } else { + if (urb->actual_length == 0) + memset(urb->transfer_buffer, 0, AS102_USB_BUF_SIZE); + } + + /* is not stopped, re-submit urb */ + if (as102_dev->streaming) + as102_submit_urb_stream(as102_dev, urb); +} + +static void as102_free_usb_stream_buffer(struct as102_dev_t *dev) +{ + int i; + + for (i = 0; i < MAX_STREAM_URB; i++) + usb_free_urb(dev->stream_urb[i]); + + usb_free_coherent(dev->bus_adap.usb_dev, + MAX_STREAM_URB * AS102_USB_BUF_SIZE, + dev->stream, + dev->dma_addr); +} + +static int as102_alloc_usb_stream_buffer(struct as102_dev_t *dev) +{ + int i; + + dev->stream = usb_alloc_coherent(dev->bus_adap.usb_dev, + MAX_STREAM_URB * AS102_USB_BUF_SIZE, + GFP_KERNEL, + &dev->dma_addr); + if (!dev->stream) { + dprintk(debug, "%s: usb_buffer_alloc failed\n", __func__); + return -ENOMEM; + } + + memset(dev->stream, 0, MAX_STREAM_URB * AS102_USB_BUF_SIZE); + + /* init urb buffers */ + for (i = 0; i < MAX_STREAM_URB; i++) { + struct urb *urb; + + urb = usb_alloc_urb(0, GFP_ATOMIC); + if (urb == NULL) { + dprintk(debug, "%s: usb_alloc_urb failed\n", __func__); + as102_free_usb_stream_buffer(dev); + return -ENOMEM; + } + + urb->transfer_buffer = dev->stream + (i * AS102_USB_BUF_SIZE); + urb->transfer_dma = dev->dma_addr + (i * AS102_USB_BUF_SIZE); + urb->transfer_flags = URB_NO_TRANSFER_DMA_MAP; + urb->transfer_buffer_length = AS102_USB_BUF_SIZE; + + dev->stream_urb[i] = urb; + } + return 0; +} + +static void as102_usb_stop_stream(struct as102_dev_t *dev) +{ + int i; + + for (i = 0; i < MAX_STREAM_URB; i++) + usb_kill_urb(dev->stream_urb[i]); +} + +static int as102_usb_start_stream(struct as102_dev_t *dev) +{ + int i, ret = 0; + + for (i = 0; i < MAX_STREAM_URB; i++) { + ret = as102_submit_urb_stream(dev, dev->stream_urb[i]); + if (ret) { + as102_usb_stop_stream(dev); + return ret; + } + } + + return 0; +} + +static void as102_usb_release(struct kref *kref) +{ + struct as102_dev_t *as102_dev; + + as102_dev = container_of(kref, struct as102_dev_t, kref); + if (as102_dev != NULL) { + usb_put_dev(as102_dev->bus_adap.usb_dev); + kfree(as102_dev); + } +} + +static void as102_usb_disconnect(struct usb_interface *intf) +{ + struct as102_dev_t *as102_dev; + + /* extract as102_dev_t from usb_device private data */ + as102_dev = usb_get_intfdata(intf); + + /* unregister dvb layer */ + as102_dvb_unregister(as102_dev); + + /* free usb buffers */ + as102_free_usb_stream_buffer(as102_dev); + + usb_set_intfdata(intf, NULL); + + /* usb unregister device */ + usb_deregister_dev(intf, &as102_usb_class_driver); + + /* decrement usage counter */ + kref_put(&as102_dev->kref, as102_usb_release); + + pr_info("%s: device has been disconnected\n", DRIVER_NAME); +} + +static int as102_usb_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + int ret; + struct as102_dev_t *as102_dev; + int i; + + /* This should never actually happen */ + if (ARRAY_SIZE(as102_usb_id_table) != + (sizeof(as102_device_names) / sizeof(const char *))) { + pr_err("Device names table invalid size"); + return -EINVAL; + } + + as102_dev = kzalloc(sizeof(struct as102_dev_t), GFP_KERNEL); + if (as102_dev == NULL) + return -ENOMEM; + + /* Assign the user-friendly device name */ + for (i = 0; i < ARRAY_SIZE(as102_usb_id_table); i++) { + if (id == &as102_usb_id_table[i]) { + as102_dev->name = as102_device_names[i]; + as102_dev->elna_cfg = as102_elna_cfg[i]; + } + } + + if (as102_dev->name == NULL) + as102_dev->name = "Unknown AS102 device"; + + /* set private callback functions */ + as102_dev->bus_adap.ops = &as102_priv_ops; + + /* init cmd token for usb bus */ + as102_dev->bus_adap.cmd = &as102_dev->bus_adap.token.usb.c; + as102_dev->bus_adap.rsp = &as102_dev->bus_adap.token.usb.r; + + /* init kernel device reference */ + kref_init(&as102_dev->kref); + + /* store as102 device to usb_device private data */ + usb_set_intfdata(intf, (void *) as102_dev); + + /* store in as102 device the usb_device pointer */ + as102_dev->bus_adap.usb_dev = usb_get_dev(interface_to_usbdev(intf)); + + /* we can register the device now, as it is ready */ + ret = usb_register_dev(intf, &as102_usb_class_driver); + if (ret < 0) { + /* something prevented us from registering this driver */ + dev_err(&intf->dev, + "%s: usb_register_dev() failed (errno = %d)\n", + __func__, ret); + goto failed; + } + + pr_info("%s: device has been detected\n", DRIVER_NAME); + + /* request buffer allocation for streaming */ + ret = as102_alloc_usb_stream_buffer(as102_dev); + if (ret != 0) + goto failed_stream; + + /* register dvb layer */ + ret = as102_dvb_register(as102_dev); + if (ret != 0) + goto failed_dvb; + + return ret; + +failed_dvb: + as102_free_usb_stream_buffer(as102_dev); +failed_stream: + usb_deregister_dev(intf, &as102_usb_class_driver); +failed: + usb_put_dev(as102_dev->bus_adap.usb_dev); + usb_set_intfdata(intf, NULL); + kfree(as102_dev); + return ret; +} + +static int as102_open(struct inode *inode, struct file *file) +{ + int ret = 0, minor = 0; + struct usb_interface *intf = NULL; + struct as102_dev_t *dev = NULL; + + /* read minor from inode */ + minor = iminor(inode); + + /* fetch device from usb interface */ + intf = usb_find_interface(&as102_usb_driver, minor); + if (intf == NULL) { + pr_err("%s: can't find device for minor %d\n", + __func__, minor); + ret = -ENODEV; + goto exit; + } + + /* get our device */ + dev = usb_get_intfdata(intf); + if (dev == NULL) { + ret = -EFAULT; + goto exit; + } + + /* save our device object in the file's private structure */ + file->private_data = dev; + + /* increment our usage count for the device */ + kref_get(&dev->kref); + +exit: + return ret; +} + +static int as102_release(struct inode *inode, struct file *file) +{ + struct as102_dev_t *dev = NULL; + + dev = file->private_data; + if (dev != NULL) { + /* decrement the count on our device */ + kref_put(&dev->kref, as102_usb_release); + } + + return 0; +} + +MODULE_DEVICE_TABLE(usb, as102_usb_id_table); diff --git a/drivers/staging/media/as102/as102_usb_drv.h b/drivers/staging/media/as102/as102_usb_drv.h new file mode 100644 index 00000000000..1ad1ec52b11 --- /dev/null +++ b/drivers/staging/media/as102/as102_usb_drv.h @@ -0,0 +1,61 @@ +/* + * Abilis Systems Single DVB-T Receiver + * Copyright (C) 2008 Pierrick Hascoet <pierrick.hascoet@abilis.com> + * Copyright (C) 2010 Devin Heitmueller <dheitmueller@kernellabs.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +#ifndef _AS102_USB_DRV_H_ +#define _AS102_USB_DRV_H_ + +#define AS102_USB_DEVICE_TX_CTRL_CMD 0xF1 +#define AS102_USB_DEVICE_RX_CTRL_CMD 0xF2 + +/* define these values to match the supported devices */ + +/* Abilis system: "TITAN" */ +#define AS102_REFERENCE_DESIGN "Abilis Systems DVB-Titan" +#define AS102_USB_DEVICE_VENDOR_ID 0x1BA6 +#define AS102_USB_DEVICE_PID_0001 0x0001 + +/* PCTV Systems: PCTV picoStick (74e) */ +#define AS102_PCTV_74E "PCTV Systems picoStick (74e)" +#define PCTV_74E_USB_VID 0x2013 +#define PCTV_74E_USB_PID 0x0246 + +/* Elgato: EyeTV DTT Deluxe */ +#define AS102_ELGATO_EYETV_DTT_NAME "Elgato EyeTV DTT Deluxe" +#define ELGATO_EYETV_DTT_USB_VID 0x0fd9 +#define ELGATO_EYETV_DTT_USB_PID 0x002c + +/* nBox: nBox DVB-T Dongle */ +#define AS102_NBOX_DVBT_DONGLE_NAME "nBox DVB-T Dongle" +#define NBOX_DVBT_DONGLE_USB_VID 0x0b89 +#define NBOX_DVBT_DONGLE_USB_PID 0x0007 + +/* Sky Italia: Digital Key (green led) */ +#define AS102_SKY_IT_DIGITAL_KEY_NAME "Sky IT Digital Key (green led)" +#define SKY_IT_DIGITAL_KEY_USB_VID 0x2137 +#define SKY_IT_DIGITAL_KEY_USB_PID 0x0001 + +void as102_urb_stream_irq(struct urb *urb); + +struct as10x_usb_token_cmd_t { + /* token cmd */ + struct as10x_cmd_t c; + /* token response */ + struct as10x_cmd_t r; +}; +#endif diff --git a/drivers/staging/media/as102/as10x_cmd.c b/drivers/staging/media/as102/as10x_cmd.c new file mode 100644 index 00000000000..9e49f15a7c9 --- /dev/null +++ b/drivers/staging/media/as102/as10x_cmd.c @@ -0,0 +1,418 @@ +/* + * Abilis Systems Single DVB-T Receiver + * Copyright (C) 2008 Pierrick Hascoet <pierrick.hascoet@abilis.com> + * Copyright (C) 2010 Devin Heitmueller <dheitmueller@kernellabs.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/kernel.h> +#include "as102_drv.h" +#include "as10x_types.h" +#include "as10x_cmd.h" + +/** + * as10x_cmd_turn_on - send turn on command to AS10x + * @adap: pointer to AS10x bus adapter + * + * Return 0 when no error, < 0 in case of error. + */ +int as10x_cmd_turn_on(struct as10x_bus_adapter_t *adap) +{ + int error = AS10X_CMD_ERROR; + struct as10x_cmd_t *pcmd, *prsp; + + pcmd = adap->cmd; + prsp = adap->rsp; + + /* prepare command */ + as10x_cmd_build(pcmd, (++adap->cmd_xid), + sizeof(pcmd->body.turn_on.req)); + + /* fill command */ + pcmd->body.turn_on.req.proc_id = cpu_to_le16(CONTROL_PROC_TURNON); + + /* send command */ + if (adap->ops->xfer_cmd) { + error = adap->ops->xfer_cmd(adap, (uint8_t *) pcmd, + sizeof(pcmd->body.turn_on.req) + + HEADER_SIZE, + (uint8_t *) prsp, + sizeof(prsp->body.turn_on.rsp) + + HEADER_SIZE); + } + + if (error < 0) + goto out; + + /* parse response */ + error = as10x_rsp_parse(prsp, CONTROL_PROC_TURNON_RSP); + +out: + return error; +} + +/** + * as10x_cmd_turn_off - send turn off command to AS10x + * @adap: pointer to AS10x bus adapter + * + * Return 0 on success or negative value in case of error. + */ +int as10x_cmd_turn_off(struct as10x_bus_adapter_t *adap) +{ + int error = AS10X_CMD_ERROR; + struct as10x_cmd_t *pcmd, *prsp; + + pcmd = adap->cmd; + prsp = adap->rsp; + + /* prepare command */ + as10x_cmd_build(pcmd, (++adap->cmd_xid), + sizeof(pcmd->body.turn_off.req)); + + /* fill command */ + pcmd->body.turn_off.req.proc_id = cpu_to_le16(CONTROL_PROC_TURNOFF); + + /* send command */ + if (adap->ops->xfer_cmd) { + error = adap->ops->xfer_cmd( + adap, (uint8_t *) pcmd, + sizeof(pcmd->body.turn_off.req) + HEADER_SIZE, + (uint8_t *) prsp, + sizeof(prsp->body.turn_off.rsp) + HEADER_SIZE); + } + + if (error < 0) + goto out; + + /* parse response */ + error = as10x_rsp_parse(prsp, CONTROL_PROC_TURNOFF_RSP); + +out: + return error; +} + +/** + * as10x_cmd_set_tune - send set tune command to AS10x + * @adap: pointer to AS10x bus adapter + * @ptune: tune parameters + * + * Return 0 on success or negative value in case of error. + */ +int as10x_cmd_set_tune(struct as10x_bus_adapter_t *adap, + struct as10x_tune_args *ptune) +{ + int error = AS10X_CMD_ERROR; + struct as10x_cmd_t *preq, *prsp; + + preq = adap->cmd; + prsp = adap->rsp; + + /* prepare command */ + as10x_cmd_build(preq, (++adap->cmd_xid), + sizeof(preq->body.set_tune.req)); + + /* fill command */ + preq->body.set_tune.req.proc_id = cpu_to_le16(CONTROL_PROC_SETTUNE); + preq->body.set_tune.req.args.freq = cpu_to_le32(ptune->freq); + preq->body.set_tune.req.args.bandwidth = ptune->bandwidth; + preq->body.set_tune.req.args.hier_select = ptune->hier_select; + preq->body.set_tune.req.args.modulation = ptune->modulation; + preq->body.set_tune.req.args.hierarchy = ptune->hierarchy; + preq->body.set_tune.req.args.interleaving_mode = + ptune->interleaving_mode; + preq->body.set_tune.req.args.code_rate = ptune->code_rate; + preq->body.set_tune.req.args.guard_interval = ptune->guard_interval; + preq->body.set_tune.req.args.transmission_mode = + ptune->transmission_mode; + + /* send command */ + if (adap->ops->xfer_cmd) { + error = adap->ops->xfer_cmd(adap, + (uint8_t *) preq, + sizeof(preq->body.set_tune.req) + + HEADER_SIZE, + (uint8_t *) prsp, + sizeof(prsp->body.set_tune.rsp) + + HEADER_SIZE); + } + + if (error < 0) + goto out; + + /* parse response */ + error = as10x_rsp_parse(prsp, CONTROL_PROC_SETTUNE_RSP); + +out: + return error; +} + +/** + * as10x_cmd_get_tune_status - send get tune status command to AS10x + * @adap: pointer to AS10x bus adapter + * @pstatus: pointer to updated status structure of the current tune + * + * Return 0 on success or negative value in case of error. + */ +int as10x_cmd_get_tune_status(struct as10x_bus_adapter_t *adap, + struct as10x_tune_status *pstatus) +{ + int error = AS10X_CMD_ERROR; + struct as10x_cmd_t *preq, *prsp; + + preq = adap->cmd; + prsp = adap->rsp; + + /* prepare command */ + as10x_cmd_build(preq, (++adap->cmd_xid), + sizeof(preq->body.get_tune_status.req)); + + /* fill command */ + preq->body.get_tune_status.req.proc_id = + cpu_to_le16(CONTROL_PROC_GETTUNESTAT); + + /* send command */ + if (adap->ops->xfer_cmd) { + error = adap->ops->xfer_cmd( + adap, + (uint8_t *) preq, + sizeof(preq->body.get_tune_status.req) + HEADER_SIZE, + (uint8_t *) prsp, + sizeof(prsp->body.get_tune_status.rsp) + HEADER_SIZE); + } + + if (error < 0) + goto out; + + /* parse response */ + error = as10x_rsp_parse(prsp, CONTROL_PROC_GETTUNESTAT_RSP); + if (error < 0) + goto out; + + /* Response OK -> get response data */ + pstatus->tune_state = prsp->body.get_tune_status.rsp.sts.tune_state; + pstatus->signal_strength = + le16_to_cpu(prsp->body.get_tune_status.rsp.sts.signal_strength); + pstatus->PER = le16_to_cpu(prsp->body.get_tune_status.rsp.sts.PER); + pstatus->BER = le16_to_cpu(prsp->body.get_tune_status.rsp.sts.BER); + +out: + return error; +} + +/** + * as10x_cmd_get_tps - send get TPS command to AS10x + * @adap: pointer to AS10x handle + * @ptps: pointer to TPS parameters structure + * + * Return 0 on success or negative value in case of error. + */ +int as10x_cmd_get_tps(struct as10x_bus_adapter_t *adap, struct as10x_tps *ptps) +{ + int error = AS10X_CMD_ERROR; + struct as10x_cmd_t *pcmd, *prsp; + + pcmd = adap->cmd; + prsp = adap->rsp; + + /* prepare command */ + as10x_cmd_build(pcmd, (++adap->cmd_xid), + sizeof(pcmd->body.get_tps.req)); + + /* fill command */ + pcmd->body.get_tune_status.req.proc_id = + cpu_to_le16(CONTROL_PROC_GETTPS); + + /* send command */ + if (adap->ops->xfer_cmd) { + error = adap->ops->xfer_cmd(adap, + (uint8_t *) pcmd, + sizeof(pcmd->body.get_tps.req) + + HEADER_SIZE, + (uint8_t *) prsp, + sizeof(prsp->body.get_tps.rsp) + + HEADER_SIZE); + } + + if (error < 0) + goto out; + + /* parse response */ + error = as10x_rsp_parse(prsp, CONTROL_PROC_GETTPS_RSP); + if (error < 0) + goto out; + + /* Response OK -> get response data */ + ptps->modulation = prsp->body.get_tps.rsp.tps.modulation; + ptps->hierarchy = prsp->body.get_tps.rsp.tps.hierarchy; + ptps->interleaving_mode = prsp->body.get_tps.rsp.tps.interleaving_mode; + ptps->code_rate_HP = prsp->body.get_tps.rsp.tps.code_rate_HP; + ptps->code_rate_LP = prsp->body.get_tps.rsp.tps.code_rate_LP; + ptps->guard_interval = prsp->body.get_tps.rsp.tps.guard_interval; + ptps->transmission_mode = prsp->body.get_tps.rsp.tps.transmission_mode; + ptps->DVBH_mask_HP = prsp->body.get_tps.rsp.tps.DVBH_mask_HP; + ptps->DVBH_mask_LP = prsp->body.get_tps.rsp.tps.DVBH_mask_LP; + ptps->cell_ID = le16_to_cpu(prsp->body.get_tps.rsp.tps.cell_ID); + +out: + return error; +} + +/** + * as10x_cmd_get_demod_stats - send get demod stats command to AS10x + * @adap: pointer to AS10x bus adapter + * @pdemod_stats: pointer to demod stats parameters structure + * + * Return 0 on success or negative value in case of error. + */ +int as10x_cmd_get_demod_stats(struct as10x_bus_adapter_t *adap, + struct as10x_demod_stats *pdemod_stats) +{ + int error = AS10X_CMD_ERROR; + struct as10x_cmd_t *pcmd, *prsp; + + pcmd = adap->cmd; + prsp = adap->rsp; + + /* prepare command */ + as10x_cmd_build(pcmd, (++adap->cmd_xid), + sizeof(pcmd->body.get_demod_stats.req)); + + /* fill command */ + pcmd->body.get_demod_stats.req.proc_id = + cpu_to_le16(CONTROL_PROC_GET_DEMOD_STATS); + + /* send command */ + if (adap->ops->xfer_cmd) { + error = adap->ops->xfer_cmd(adap, + (uint8_t *) pcmd, + sizeof(pcmd->body.get_demod_stats.req) + + HEADER_SIZE, + (uint8_t *) prsp, + sizeof(prsp->body.get_demod_stats.rsp) + + HEADER_SIZE); + } + + if (error < 0) + goto out; + + /* parse response */ + error = as10x_rsp_parse(prsp, CONTROL_PROC_GET_DEMOD_STATS_RSP); + if (error < 0) + goto out; + + /* Response OK -> get response data */ + pdemod_stats->frame_count = + le32_to_cpu(prsp->body.get_demod_stats.rsp.stats.frame_count); + pdemod_stats->bad_frame_count = + le32_to_cpu(prsp->body.get_demod_stats.rsp.stats.bad_frame_count); + pdemod_stats->bytes_fixed_by_rs = + le32_to_cpu(prsp->body.get_demod_stats.rsp.stats.bytes_fixed_by_rs); + pdemod_stats->mer = + le16_to_cpu(prsp->body.get_demod_stats.rsp.stats.mer); + pdemod_stats->has_started = + prsp->body.get_demod_stats.rsp.stats.has_started; + +out: + return error; +} + +/** + * as10x_cmd_get_impulse_resp - send get impulse response command to AS10x + * @adap: pointer to AS10x bus adapter + * @is_ready: pointer to value indicating when impulse + * response data is ready + * + * Return 0 on success or negative value in case of error. + */ +int as10x_cmd_get_impulse_resp(struct as10x_bus_adapter_t *adap, + uint8_t *is_ready) +{ + int error = AS10X_CMD_ERROR; + struct as10x_cmd_t *pcmd, *prsp; + + pcmd = adap->cmd; + prsp = adap->rsp; + + /* prepare command */ + as10x_cmd_build(pcmd, (++adap->cmd_xid), + sizeof(pcmd->body.get_impulse_rsp.req)); + + /* fill command */ + pcmd->body.get_impulse_rsp.req.proc_id = + cpu_to_le16(CONTROL_PROC_GET_IMPULSE_RESP); + + /* send command */ + if (adap->ops->xfer_cmd) { + error = adap->ops->xfer_cmd(adap, + (uint8_t *) pcmd, + sizeof(pcmd->body.get_impulse_rsp.req) + + HEADER_SIZE, + (uint8_t *) prsp, + sizeof(prsp->body.get_impulse_rsp.rsp) + + HEADER_SIZE); + } + + if (error < 0) + goto out; + + /* parse response */ + error = as10x_rsp_parse(prsp, CONTROL_PROC_GET_IMPULSE_RESP_RSP); + if (error < 0) + goto out; + + /* Response OK -> get response data */ + *is_ready = prsp->body.get_impulse_rsp.rsp.is_ready; + +out: + return error; +} + +/** + * as10x_cmd_build - build AS10x command header + * @pcmd: pointer to AS10x command buffer + * @xid: sequence id of the command + * @cmd_len: length of the command + */ +void as10x_cmd_build(struct as10x_cmd_t *pcmd, + uint16_t xid, uint16_t cmd_len) +{ + pcmd->header.req_id = cpu_to_le16(xid); + pcmd->header.prog = cpu_to_le16(SERVICE_PROG_ID); + pcmd->header.version = cpu_to_le16(SERVICE_PROG_VERSION); + pcmd->header.data_len = cpu_to_le16(cmd_len); +} + +/** + * as10x_rsp_parse - Parse command response + * @prsp: pointer to AS10x command buffer + * @proc_id: id of the command + * + * Return 0 on success or negative value in case of error. + */ +int as10x_rsp_parse(struct as10x_cmd_t *prsp, uint16_t proc_id) +{ + int error; + + /* extract command error code */ + error = prsp->body.common.rsp.error; + + if ((error == 0) && + (le16_to_cpu(prsp->body.common.rsp.proc_id) == proc_id)) { + return 0; + } + + return AS10X_CMD_ERROR; +} diff --git a/drivers/staging/media/as102/as10x_cmd.h b/drivers/staging/media/as102/as10x_cmd.h new file mode 100644 index 00000000000..e21ec6c702a --- /dev/null +++ b/drivers/staging/media/as102/as10x_cmd.h @@ -0,0 +1,529 @@ +/* + * Abilis Systems Single DVB-T Receiver + * Copyright (C) 2008 Pierrick Hascoet <pierrick.hascoet@abilis.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +#ifndef _AS10X_CMD_H_ +#define _AS10X_CMD_H_ + +#ifdef __KERNEL__ +#include <linux/kernel.h> +#endif + +#include "as10x_types.h" + +/*********************************/ +/* MACRO DEFINITIONS */ +/*********************************/ +#define AS10X_CMD_ERROR -1 + +#define SERVICE_PROG_ID 0x0002 +#define SERVICE_PROG_VERSION 0x0001 + +#define HIER_NONE 0x00 +#define HIER_LOW_PRIORITY 0x01 + +#define HEADER_SIZE (sizeof(struct as10x_cmd_header_t)) + +/* context request types */ +#define GET_CONTEXT_DATA 1 +#define SET_CONTEXT_DATA 2 + +/* ODSP suspend modes */ +#define CFG_MODE_ODSP_RESUME 0 +#define CFG_MODE_ODSP_SUSPEND 1 + +/* Dump memory size */ +#define DUMP_BLOCK_SIZE_MAX 0x20 + +/*********************************/ +/* TYPE DEFINITION */ +/*********************************/ +enum control_proc { + CONTROL_PROC_TURNON = 0x0001, + CONTROL_PROC_TURNON_RSP = 0x0100, + CONTROL_PROC_SET_REGISTER = 0x0002, + CONTROL_PROC_SET_REGISTER_RSP = 0x0200, + CONTROL_PROC_GET_REGISTER = 0x0003, + CONTROL_PROC_GET_REGISTER_RSP = 0x0300, + CONTROL_PROC_SETTUNE = 0x000A, + CONTROL_PROC_SETTUNE_RSP = 0x0A00, + CONTROL_PROC_GETTUNESTAT = 0x000B, + CONTROL_PROC_GETTUNESTAT_RSP = 0x0B00, + CONTROL_PROC_GETTPS = 0x000D, + CONTROL_PROC_GETTPS_RSP = 0x0D00, + CONTROL_PROC_SETFILTER = 0x000E, + CONTROL_PROC_SETFILTER_RSP = 0x0E00, + CONTROL_PROC_REMOVEFILTER = 0x000F, + CONTROL_PROC_REMOVEFILTER_RSP = 0x0F00, + CONTROL_PROC_GET_IMPULSE_RESP = 0x0012, + CONTROL_PROC_GET_IMPULSE_RESP_RSP = 0x1200, + CONTROL_PROC_START_STREAMING = 0x0013, + CONTROL_PROC_START_STREAMING_RSP = 0x1300, + CONTROL_PROC_STOP_STREAMING = 0x0014, + CONTROL_PROC_STOP_STREAMING_RSP = 0x1400, + CONTROL_PROC_GET_DEMOD_STATS = 0x0015, + CONTROL_PROC_GET_DEMOD_STATS_RSP = 0x1500, + CONTROL_PROC_ELNA_CHANGE_MODE = 0x0016, + CONTROL_PROC_ELNA_CHANGE_MODE_RSP = 0x1600, + CONTROL_PROC_ODSP_CHANGE_MODE = 0x0017, + CONTROL_PROC_ODSP_CHANGE_MODE_RSP = 0x1700, + CONTROL_PROC_AGC_CHANGE_MODE = 0x0018, + CONTROL_PROC_AGC_CHANGE_MODE_RSP = 0x1800, + + CONTROL_PROC_CONTEXT = 0x00FC, + CONTROL_PROC_CONTEXT_RSP = 0xFC00, + CONTROL_PROC_DUMP_MEMORY = 0x00FD, + CONTROL_PROC_DUMP_MEMORY_RSP = 0xFD00, + CONTROL_PROC_DUMPLOG_MEMORY = 0x00FE, + CONTROL_PROC_DUMPLOG_MEMORY_RSP = 0xFE00, + CONTROL_PROC_TURNOFF = 0x00FF, + CONTROL_PROC_TURNOFF_RSP = 0xFF00 +}; + +union as10x_turn_on { + /* request */ + struct { + /* request identifier */ + uint16_t proc_id; + } __packed req; + /* response */ + struct { + /* response identifier */ + uint16_t proc_id; + /* error */ + uint8_t error; + } __packed rsp; +} __packed; + +union as10x_turn_off { + /* request */ + struct { + /* request identifier */ + uint16_t proc_id; + } __packed req; + /* response */ + struct { + /* response identifier */ + uint16_t proc_id; + /* error */ + uint8_t err; + } __packed rsp; +} __packed; + +union as10x_set_tune { + /* request */ + struct { + /* request identifier */ + uint16_t proc_id; + /* tune params */ + struct as10x_tune_args args; + } __packed req; + /* response */ + struct { + /* response identifier */ + uint16_t proc_id; + /* response error */ + uint8_t error; + } __packed rsp; +} __packed; + +union as10x_get_tune_status { + /* request */ + struct { + /* request identifier */ + uint16_t proc_id; + } __packed req; + /* response */ + struct { + /* response identifier */ + uint16_t proc_id; + /* response error */ + uint8_t error; + /* tune status */ + struct as10x_tune_status sts; + } __packed rsp; +} __packed; + +union as10x_get_tps { + /* request */ + struct { + /* request identifier */ + uint16_t proc_id; + } __packed req; + /* response */ + struct { + /* response identifier */ + uint16_t proc_id; + /* response error */ + uint8_t error; + /* tps details */ + struct as10x_tps tps; + } __packed rsp; +} __packed; + +union as10x_common { + /* request */ + struct { + /* request identifier */ + uint16_t proc_id; + } __packed req; + /* response */ + struct { + /* response identifier */ + uint16_t proc_id; + /* response error */ + uint8_t error; + } __packed rsp; +} __packed; + +union as10x_add_pid_filter { + /* request */ + struct { + /* request identifier */ + uint16_t proc_id; + /* PID to filter */ + uint16_t pid; + /* stream type (MPE, PSI/SI or PES )*/ + uint8_t stream_type; + /* PID index in filter table */ + uint8_t idx; + } __packed req; + /* response */ + struct { + /* response identifier */ + uint16_t proc_id; + /* response error */ + uint8_t error; + /* Filter id */ + uint8_t filter_id; + } __packed rsp; +} __packed; + +union as10x_del_pid_filter { + /* request */ + struct { + /* request identifier */ + uint16_t proc_id; + /* PID to remove */ + uint16_t pid; + } __packed req; + /* response */ + struct { + /* response identifier */ + uint16_t proc_id; + /* response error */ + uint8_t error; + } __packed rsp; +} __packed; + +union as10x_start_streaming { + /* request */ + struct { + /* request identifier */ + uint16_t proc_id; + } __packed req; + /* response */ + struct { + /* response identifier */ + uint16_t proc_id; + /* error */ + uint8_t error; + } __packed rsp; +} __packed; + +union as10x_stop_streaming { + /* request */ + struct { + /* request identifier */ + uint16_t proc_id; + } __packed req; + /* response */ + struct { + /* response identifier */ + uint16_t proc_id; + /* error */ + uint8_t error; + } __packed rsp; +} __packed; + +union as10x_get_demod_stats { + /* request */ + struct { + /* request identifier */ + uint16_t proc_id; + } __packed req; + /* response */ + struct { + /* response identifier */ + uint16_t proc_id; + /* error */ + uint8_t error; + /* demod stats */ + struct as10x_demod_stats stats; + } __packed rsp; +} __packed; + +union as10x_get_impulse_resp { + /* request */ + struct { + /* request identifier */ + uint16_t proc_id; + } __packed req; + /* response */ + struct { + /* response identifier */ + uint16_t proc_id; + /* error */ + uint8_t error; + /* impulse response ready */ + uint8_t is_ready; + } __packed rsp; +} __packed; + +union as10x_fw_context { + /* request */ + struct { + /* request identifier */ + uint16_t proc_id; + /* value to write (for set context)*/ + struct as10x_register_value reg_val; + /* context tag */ + uint16_t tag; + /* context request type */ + uint16_t type; + } __packed req; + /* response */ + struct { + /* response identifier */ + uint16_t proc_id; + /* value read (for get context) */ + struct as10x_register_value reg_val; + /* context request type */ + uint16_t type; + /* error */ + uint8_t error; + } __packed rsp; +} __packed; + +union as10x_set_register { + /* request */ + struct { + /* response identifier */ + uint16_t proc_id; + /* register description */ + struct as10x_register_addr reg_addr; + /* register content */ + struct as10x_register_value reg_val; + } __packed req; + /* response */ + struct { + /* response identifier */ + uint16_t proc_id; + /* error */ + uint8_t error; + } __packed rsp; +} __packed; + +union as10x_get_register { + /* request */ + struct { + /* response identifier */ + uint16_t proc_id; + /* register description */ + struct as10x_register_addr reg_addr; + } __packed req; + /* response */ + struct { + /* response identifier */ + uint16_t proc_id; + /* error */ + uint8_t error; + /* register content */ + struct as10x_register_value reg_val; + } __packed rsp; +} __packed; + +union as10x_cfg_change_mode { + /* request */ + struct { + /* request identifier */ + uint16_t proc_id; + /* mode */ + uint8_t mode; + } __packed req; + /* response */ + struct { + /* response identifier */ + uint16_t proc_id; + /* error */ + uint8_t error; + } __packed rsp; +} __packed; + +struct as10x_cmd_header_t { + uint16_t req_id; + uint16_t prog; + uint16_t version; + uint16_t data_len; +} __packed; + +#define DUMP_BLOCK_SIZE 16 + +union as10x_dump_memory { + /* request */ + struct { + /* request identifier */ + uint16_t proc_id; + /* dump memory type request */ + uint8_t dump_req; + /* register description */ + struct as10x_register_addr reg_addr; + /* nb blocks to read */ + uint16_t num_blocks; + } __packed req; + /* response */ + struct { + /* response identifier */ + uint16_t proc_id; + /* error */ + uint8_t error; + /* dump response */ + uint8_t dump_rsp; + /* data */ + union { + uint8_t data8[DUMP_BLOCK_SIZE]; + uint16_t data16[DUMP_BLOCK_SIZE / sizeof(uint16_t)]; + uint32_t data32[DUMP_BLOCK_SIZE / sizeof(uint32_t)]; + } __packed u; + } __packed rsp; +} __packed; + +union as10x_dumplog_memory { + struct { + /* request identifier */ + uint16_t proc_id; + /* dump memory type request */ + uint8_t dump_req; + } __packed req; + struct { + /* request identifier */ + uint16_t proc_id; + /* error */ + uint8_t error; + /* dump response */ + uint8_t dump_rsp; + /* dump data */ + uint8_t data[DUMP_BLOCK_SIZE]; + } __packed rsp; +} __packed; + +union as10x_raw_data { + /* request */ + struct { + uint16_t proc_id; + uint8_t data[64 - sizeof(struct as10x_cmd_header_t) + - 2 /* proc_id */]; + } __packed req; + /* response */ + struct { + uint16_t proc_id; + uint8_t error; + uint8_t data[64 - sizeof(struct as10x_cmd_header_t) + - 2 /* proc_id */ - 1 /* rc */]; + } __packed rsp; +} __packed; + +struct as10x_cmd_t { + struct as10x_cmd_header_t header; + union { + union as10x_turn_on turn_on; + union as10x_turn_off turn_off; + union as10x_set_tune set_tune; + union as10x_get_tune_status get_tune_status; + union as10x_get_tps get_tps; + union as10x_common common; + union as10x_add_pid_filter add_pid_filter; + union as10x_del_pid_filter del_pid_filter; + union as10x_start_streaming start_streaming; + union as10x_stop_streaming stop_streaming; + union as10x_get_demod_stats get_demod_stats; + union as10x_get_impulse_resp get_impulse_rsp; + union as10x_fw_context context; + union as10x_set_register set_register; + union as10x_get_register get_register; + union as10x_cfg_change_mode cfg_change_mode; + union as10x_dump_memory dump_memory; + union as10x_dumplog_memory dumplog_memory; + union as10x_raw_data raw_data; + } __packed body; +} __packed; + +struct as10x_token_cmd_t { + /* token cmd */ + struct as10x_cmd_t c; + /* token response */ + struct as10x_cmd_t r; +} __packed; + + +/**************************/ +/* FUNCTION DECLARATION */ +/**************************/ + +void as10x_cmd_build(struct as10x_cmd_t *pcmd, uint16_t proc_id, + uint16_t cmd_len); +int as10x_rsp_parse(struct as10x_cmd_t *r, uint16_t proc_id); + +/* as10x cmd */ +int as10x_cmd_turn_on(struct as10x_bus_adapter_t *adap); +int as10x_cmd_turn_off(struct as10x_bus_adapter_t *adap); + +int as10x_cmd_set_tune(struct as10x_bus_adapter_t *adap, + struct as10x_tune_args *ptune); + +int as10x_cmd_get_tune_status(struct as10x_bus_adapter_t *adap, + struct as10x_tune_status *pstatus); + +int as10x_cmd_get_tps(struct as10x_bus_adapter_t *adap, + struct as10x_tps *ptps); + +int as10x_cmd_get_demod_stats(struct as10x_bus_adapter_t *adap, + struct as10x_demod_stats *pdemod_stats); + +int as10x_cmd_get_impulse_resp(struct as10x_bus_adapter_t *adap, + uint8_t *is_ready); + +/* as10x cmd stream */ +int as10x_cmd_add_PID_filter(struct as10x_bus_adapter_t *adap, + struct as10x_ts_filter *filter); +int as10x_cmd_del_PID_filter(struct as10x_bus_adapter_t *adap, + uint16_t pid_value); + +int as10x_cmd_start_streaming(struct as10x_bus_adapter_t *adap); +int as10x_cmd_stop_streaming(struct as10x_bus_adapter_t *adap); + +/* as10x cmd cfg */ +int as10x_cmd_set_context(struct as10x_bus_adapter_t *adap, + uint16_t tag, + uint32_t value); +int as10x_cmd_get_context(struct as10x_bus_adapter_t *adap, + uint16_t tag, + uint32_t *pvalue); + +int as10x_cmd_eLNA_change_mode(struct as10x_bus_adapter_t *adap, uint8_t mode); +int as10x_context_rsp_parse(struct as10x_cmd_t *prsp, uint16_t proc_id); +#endif diff --git a/drivers/staging/media/as102/as10x_cmd_cfg.c b/drivers/staging/media/as102/as10x_cmd_cfg.c new file mode 100644 index 00000000000..b1e300d8875 --- /dev/null +++ b/drivers/staging/media/as102/as10x_cmd_cfg.c @@ -0,0 +1,206 @@ +/* + * Abilis Systems Single DVB-T Receiver + * Copyright (C) 2008 Pierrick Hascoet <pierrick.hascoet@abilis.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/kernel.h> +#include "as102_drv.h" +#include "as10x_types.h" +#include "as10x_cmd.h" + +/***************************/ +/* FUNCTION DEFINITION */ +/***************************/ + +/** + * as10x_cmd_get_context - Send get context command to AS10x + * @adap: pointer to AS10x bus adapter + * @tag: context tag + * @pvalue: pointer where to store context value read + * + * Return 0 on success or negative value in case of error. + */ +int as10x_cmd_get_context(struct as10x_bus_adapter_t *adap, uint16_t tag, + uint32_t *pvalue) +{ + int error; + struct as10x_cmd_t *pcmd, *prsp; + + pcmd = adap->cmd; + prsp = adap->rsp; + + /* prepare command */ + as10x_cmd_build(pcmd, (++adap->cmd_xid), + sizeof(pcmd->body.context.req)); + + /* fill command */ + pcmd->body.context.req.proc_id = cpu_to_le16(CONTROL_PROC_CONTEXT); + pcmd->body.context.req.tag = cpu_to_le16(tag); + pcmd->body.context.req.type = cpu_to_le16(GET_CONTEXT_DATA); + + /* send command */ + if (adap->ops->xfer_cmd) { + error = adap->ops->xfer_cmd(adap, + (uint8_t *) pcmd, + sizeof(pcmd->body.context.req) + + HEADER_SIZE, + (uint8_t *) prsp, + sizeof(prsp->body.context.rsp) + + HEADER_SIZE); + } else { + error = AS10X_CMD_ERROR; + } + + if (error < 0) + goto out; + + /* parse response: context command do not follow the common response */ + /* structure -> specific handling response parse required */ + error = as10x_context_rsp_parse(prsp, CONTROL_PROC_CONTEXT_RSP); + + if (error == 0) { + /* Response OK -> get response data */ + *pvalue = le32_to_cpu(prsp->body.context.rsp.reg_val.u.value32); + /* value returned is always a 32-bit value */ + } + +out: + return error; +} + +/** + * as10x_cmd_set_context - send set context command to AS10x + * @adap: pointer to AS10x bus adapter + * @tag: context tag + * @value: value to set in context + * + * Return 0 on success or negative value in case of error. + */ +int as10x_cmd_set_context(struct as10x_bus_adapter_t *adap, uint16_t tag, + uint32_t value) +{ + int error; + struct as10x_cmd_t *pcmd, *prsp; + + pcmd = adap->cmd; + prsp = adap->rsp; + + /* prepare command */ + as10x_cmd_build(pcmd, (++adap->cmd_xid), + sizeof(pcmd->body.context.req)); + + /* fill command */ + pcmd->body.context.req.proc_id = cpu_to_le16(CONTROL_PROC_CONTEXT); + /* pcmd->body.context.req.reg_val.mode initialization is not required */ + pcmd->body.context.req.reg_val.u.value32 = cpu_to_le32(value); + pcmd->body.context.req.tag = cpu_to_le16(tag); + pcmd->body.context.req.type = cpu_to_le16(SET_CONTEXT_DATA); + + /* send command */ + if (adap->ops->xfer_cmd) { + error = adap->ops->xfer_cmd(adap, + (uint8_t *) pcmd, + sizeof(pcmd->body.context.req) + + HEADER_SIZE, + (uint8_t *) prsp, + sizeof(prsp->body.context.rsp) + + HEADER_SIZE); + } else { + error = AS10X_CMD_ERROR; + } + + if (error < 0) + goto out; + + /* parse response: context command do not follow the common response */ + /* structure -> specific handling response parse required */ + error = as10x_context_rsp_parse(prsp, CONTROL_PROC_CONTEXT_RSP); + +out: + return error; +} + +/** + * as10x_cmd_eLNA_change_mode - send eLNA change mode command to AS10x + * @adap: pointer to AS10x bus adapter + * @mode: mode selected: + * - ON : 0x0 => eLNA always ON + * - OFF : 0x1 => eLNA always OFF + * - AUTO : 0x2 => eLNA follow hysteresis parameters + * to be ON or OFF + * + * Return 0 on success or negative value in case of error. + */ +int as10x_cmd_eLNA_change_mode(struct as10x_bus_adapter_t *adap, uint8_t mode) +{ + int error; + struct as10x_cmd_t *pcmd, *prsp; + + pcmd = adap->cmd; + prsp = adap->rsp; + + /* prepare command */ + as10x_cmd_build(pcmd, (++adap->cmd_xid), + sizeof(pcmd->body.cfg_change_mode.req)); + + /* fill command */ + pcmd->body.cfg_change_mode.req.proc_id = + cpu_to_le16(CONTROL_PROC_ELNA_CHANGE_MODE); + pcmd->body.cfg_change_mode.req.mode = mode; + + /* send command */ + if (adap->ops->xfer_cmd) { + error = adap->ops->xfer_cmd(adap, (uint8_t *) pcmd, + sizeof(pcmd->body.cfg_change_mode.req) + + HEADER_SIZE, (uint8_t *) prsp, + sizeof(prsp->body.cfg_change_mode.rsp) + + HEADER_SIZE); + } else { + error = AS10X_CMD_ERROR; + } + + if (error < 0) + goto out; + + /* parse response */ + error = as10x_rsp_parse(prsp, CONTROL_PROC_ELNA_CHANGE_MODE_RSP); + +out: + return error; +} + +/** + * as10x_context_rsp_parse - Parse context command response + * @prsp: pointer to AS10x command response buffer + * @proc_id: id of the command + * + * Since the contex command response does not follow the common + * response, a specific parse function is required. + * Return 0 on success or negative value in case of error. + */ +int as10x_context_rsp_parse(struct as10x_cmd_t *prsp, uint16_t proc_id) +{ + int err; + + err = prsp->body.context.rsp.error; + + if ((err == 0) && + (le16_to_cpu(prsp->body.context.rsp.proc_id) == proc_id)) { + return 0; + } + return AS10X_CMD_ERROR; +} diff --git a/drivers/staging/media/as102/as10x_cmd_stream.c b/drivers/staging/media/as102/as10x_cmd_stream.c new file mode 100644 index 00000000000..1088ca1fe92 --- /dev/null +++ b/drivers/staging/media/as102/as10x_cmd_stream.c @@ -0,0 +1,211 @@ +/* + * Abilis Systems Single DVB-T Receiver + * Copyright (C) 2008 Pierrick Hascoet <pierrick.hascoet@abilis.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/kernel.h> +#include "as102_drv.h" +#include "as10x_cmd.h" + +/** + * as10x_cmd_add_PID_filter - send add filter command to AS10x + * @adap: pointer to AS10x bus adapter + * @filter: TSFilter filter for DVB-T + * + * Return 0 on success or negative value in case of error. + */ +int as10x_cmd_add_PID_filter(struct as10x_bus_adapter_t *adap, + struct as10x_ts_filter *filter) +{ + int error; + struct as10x_cmd_t *pcmd, *prsp; + + pcmd = adap->cmd; + prsp = adap->rsp; + + /* prepare command */ + as10x_cmd_build(pcmd, (++adap->cmd_xid), + sizeof(pcmd->body.add_pid_filter.req)); + + /* fill command */ + pcmd->body.add_pid_filter.req.proc_id = + cpu_to_le16(CONTROL_PROC_SETFILTER); + pcmd->body.add_pid_filter.req.pid = cpu_to_le16(filter->pid); + pcmd->body.add_pid_filter.req.stream_type = filter->type; + + if (filter->idx < 16) + pcmd->body.add_pid_filter.req.idx = filter->idx; + else + pcmd->body.add_pid_filter.req.idx = 0xFF; + + /* send command */ + if (adap->ops->xfer_cmd) { + error = adap->ops->xfer_cmd(adap, (uint8_t *) pcmd, + sizeof(pcmd->body.add_pid_filter.req) + + HEADER_SIZE, (uint8_t *) prsp, + sizeof(prsp->body.add_pid_filter.rsp) + + HEADER_SIZE); + } else { + error = AS10X_CMD_ERROR; + } + + if (error < 0) + goto out; + + /* parse response */ + error = as10x_rsp_parse(prsp, CONTROL_PROC_SETFILTER_RSP); + + if (error == 0) { + /* Response OK -> get response data */ + filter->idx = prsp->body.add_pid_filter.rsp.filter_id; + } + +out: + return error; +} + +/** + * as10x_cmd_del_PID_filter - Send delete filter command to AS10x + * @adap: pointer to AS10x bus adapte + * @pid_value: PID to delete + * + * Return 0 on success or negative value in case of error. + */ +int as10x_cmd_del_PID_filter(struct as10x_bus_adapter_t *adap, + uint16_t pid_value) +{ + int error; + struct as10x_cmd_t *pcmd, *prsp; + + pcmd = adap->cmd; + prsp = adap->rsp; + + /* prepare command */ + as10x_cmd_build(pcmd, (++adap->cmd_xid), + sizeof(pcmd->body.del_pid_filter.req)); + + /* fill command */ + pcmd->body.del_pid_filter.req.proc_id = + cpu_to_le16(CONTROL_PROC_REMOVEFILTER); + pcmd->body.del_pid_filter.req.pid = cpu_to_le16(pid_value); + + /* send command */ + if (adap->ops->xfer_cmd) { + error = adap->ops->xfer_cmd(adap, (uint8_t *) pcmd, + sizeof(pcmd->body.del_pid_filter.req) + + HEADER_SIZE, (uint8_t *) prsp, + sizeof(prsp->body.del_pid_filter.rsp) + + HEADER_SIZE); + } else { + error = AS10X_CMD_ERROR; + } + + if (error < 0) + goto out; + + /* parse response */ + error = as10x_rsp_parse(prsp, CONTROL_PROC_REMOVEFILTER_RSP); + +out: + return error; +} + +/** + * as10x_cmd_start_streaming - Send start streaming command to AS10x + * @adap: pointer to AS10x bus adapter + * + * Return 0 on success or negative value in case of error. + */ +int as10x_cmd_start_streaming(struct as10x_bus_adapter_t *adap) +{ + int error; + struct as10x_cmd_t *pcmd, *prsp; + + pcmd = adap->cmd; + prsp = adap->rsp; + + /* prepare command */ + as10x_cmd_build(pcmd, (++adap->cmd_xid), + sizeof(pcmd->body.start_streaming.req)); + + /* fill command */ + pcmd->body.start_streaming.req.proc_id = + cpu_to_le16(CONTROL_PROC_START_STREAMING); + + /* send command */ + if (adap->ops->xfer_cmd) { + error = adap->ops->xfer_cmd(adap, (uint8_t *) pcmd, + sizeof(pcmd->body.start_streaming.req) + + HEADER_SIZE, (uint8_t *) prsp, + sizeof(prsp->body.start_streaming.rsp) + + HEADER_SIZE); + } else { + error = AS10X_CMD_ERROR; + } + + if (error < 0) + goto out; + + /* parse response */ + error = as10x_rsp_parse(prsp, CONTROL_PROC_START_STREAMING_RSP); + +out: + return error; +} + +/** + * as10x_cmd_stop_streaming - Send stop streaming command to AS10x + * @adap: pointer to AS10x bus adapter + * + * Return 0 on success or negative value in case of error. + */ +int as10x_cmd_stop_streaming(struct as10x_bus_adapter_t *adap) +{ + int8_t error; + struct as10x_cmd_t *pcmd, *prsp; + + pcmd = adap->cmd; + prsp = adap->rsp; + + /* prepare command */ + as10x_cmd_build(pcmd, (++adap->cmd_xid), + sizeof(pcmd->body.stop_streaming.req)); + + /* fill command */ + pcmd->body.stop_streaming.req.proc_id = + cpu_to_le16(CONTROL_PROC_STOP_STREAMING); + + /* send command */ + if (adap->ops->xfer_cmd) { + error = adap->ops->xfer_cmd(adap, (uint8_t *) pcmd, + sizeof(pcmd->body.stop_streaming.req) + + HEADER_SIZE, (uint8_t *) prsp, + sizeof(prsp->body.stop_streaming.rsp) + + HEADER_SIZE); + } else { + error = AS10X_CMD_ERROR; + } + + if (error < 0) + goto out; + + /* parse response */ + error = as10x_rsp_parse(prsp, CONTROL_PROC_STOP_STREAMING_RSP); + +out: + return error; +} diff --git a/drivers/staging/media/as102/as10x_handle.h b/drivers/staging/media/as102/as10x_handle.h new file mode 100644 index 00000000000..5638b191b78 --- /dev/null +++ b/drivers/staging/media/as102/as10x_handle.h @@ -0,0 +1,54 @@ +/* + * Abilis Systems Single DVB-T Receiver + * Copyright (C) 2008 Pierrick Hascoet <pierrick.hascoet@abilis.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +#ifdef __KERNEL__ +struct as10x_bus_adapter_t; +struct as102_dev_t; + +#include "as10x_cmd.h" + +/* values for "mode" field */ +#define REGMODE8 8 +#define REGMODE16 16 +#define REGMODE32 32 + +struct as102_priv_ops_t { + int (*upload_fw_pkt)(struct as10x_bus_adapter_t *bus_adap, + unsigned char *buf, int buflen, int swap32); + + int (*send_cmd)(struct as10x_bus_adapter_t *bus_adap, + unsigned char *buf, int buflen); + + int (*xfer_cmd)(struct as10x_bus_adapter_t *bus_adap, + unsigned char *send_buf, int send_buf_len, + unsigned char *recv_buf, int recv_buf_len); + + int (*start_stream)(struct as102_dev_t *dev); + void (*stop_stream)(struct as102_dev_t *dev); + + int (*reset_target)(struct as10x_bus_adapter_t *bus_adap); + + int (*read_write)(struct as10x_bus_adapter_t *bus_adap, uint8_t mode, + uint32_t rd_addr, uint16_t rd_len, + uint32_t wr_addr, uint16_t wr_len); + + int (*as102_read_ep2)(struct as10x_bus_adapter_t *bus_adap, + unsigned char *recv_buf, + int recv_buf_len); +}; +#endif diff --git a/drivers/staging/media/as102/as10x_types.h b/drivers/staging/media/as102/as10x_types.h new file mode 100644 index 00000000000..af26e057d9a --- /dev/null +++ b/drivers/staging/media/as102/as10x_types.h @@ -0,0 +1,194 @@ +/* + * Abilis Systems Single DVB-T Receiver + * Copyright (C) 2008 Pierrick Hascoet <pierrick.hascoet@abilis.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +#ifndef _AS10X_TYPES_H_ +#define _AS10X_TYPES_H_ + +#include "as10x_handle.h" + +/*********************************/ +/* MACRO DEFINITIONS */ +/*********************************/ + +/* bandwidth constant values */ +#define BW_5_MHZ 0x00 +#define BW_6_MHZ 0x01 +#define BW_7_MHZ 0x02 +#define BW_8_MHZ 0x03 + +/* hierarchy priority selection values */ +#define HIER_NO_PRIORITY 0x00 +#define HIER_LOW_PRIORITY 0x01 +#define HIER_HIGH_PRIORITY 0x02 + +/* constellation available values */ +#define CONST_QPSK 0x00 +#define CONST_QAM16 0x01 +#define CONST_QAM64 0x02 +#define CONST_UNKNOWN 0xFF + +/* hierarchy available values */ +#define HIER_NONE 0x00 +#define HIER_ALPHA_1 0x01 +#define HIER_ALPHA_2 0x02 +#define HIER_ALPHA_4 0x03 +#define HIER_UNKNOWN 0xFF + +/* interleaving available values */ +#define INTLV_NATIVE 0x00 +#define INTLV_IN_DEPTH 0x01 +#define INTLV_UNKNOWN 0xFF + +/* code rate available values */ +#define CODE_RATE_1_2 0x00 +#define CODE_RATE_2_3 0x01 +#define CODE_RATE_3_4 0x02 +#define CODE_RATE_5_6 0x03 +#define CODE_RATE_7_8 0x04 +#define CODE_RATE_UNKNOWN 0xFF + +/* guard interval available values */ +#define GUARD_INT_1_32 0x00 +#define GUARD_INT_1_16 0x01 +#define GUARD_INT_1_8 0x02 +#define GUARD_INT_1_4 0x03 +#define GUARD_UNKNOWN 0xFF + +/* transmission mode available values */ +#define TRANS_MODE_2K 0x00 +#define TRANS_MODE_8K 0x01 +#define TRANS_MODE_4K 0x02 +#define TRANS_MODE_UNKNOWN 0xFF + +/* DVBH signalling available values */ +#define TIMESLICING_PRESENT 0x01 +#define MPE_FEC_PRESENT 0x02 + +/* tune state available */ +#define TUNE_STATUS_NOT_TUNED 0x00 +#define TUNE_STATUS_IDLE 0x01 +#define TUNE_STATUS_LOCKING 0x02 +#define TUNE_STATUS_SIGNAL_DVB_OK 0x03 +#define TUNE_STATUS_STREAM_DETECTED 0x04 +#define TUNE_STATUS_STREAM_TUNED 0x05 +#define TUNE_STATUS_ERROR 0xFF + +/* available TS FID filter types */ +#define TS_PID_TYPE_TS 0 +#define TS_PID_TYPE_PSI_SI 1 +#define TS_PID_TYPE_MPE 2 + +/* number of echos available */ +#define MAX_ECHOS 15 + +/* Context types */ +#define CONTEXT_LNA 1010 +#define CONTEXT_ELNA_HYSTERESIS 4003 +#define CONTEXT_ELNA_GAIN 4004 +#define CONTEXT_MER_THRESHOLD 5005 +#define CONTEXT_MER_OFFSET 5006 +#define CONTEXT_IR_STATE 7000 +#define CONTEXT_TSOUT_MSB_FIRST 7004 +#define CONTEXT_TSOUT_FALLING_EDGE 7005 + +/* Configuration modes */ +#define CFG_MODE_ON 0 +#define CFG_MODE_OFF 1 +#define CFG_MODE_AUTO 2 + +struct as10x_tps { + uint8_t modulation; + uint8_t hierarchy; + uint8_t interleaving_mode; + uint8_t code_rate_HP; + uint8_t code_rate_LP; + uint8_t guard_interval; + uint8_t transmission_mode; + uint8_t DVBH_mask_HP; + uint8_t DVBH_mask_LP; + uint16_t cell_ID; +} __packed; + +struct as10x_tune_args { + /* frequency */ + uint32_t freq; + /* bandwidth */ + uint8_t bandwidth; + /* hierarchy selection */ + uint8_t hier_select; + /* constellation */ + uint8_t modulation; + /* hierarchy */ + uint8_t hierarchy; + /* interleaving mode */ + uint8_t interleaving_mode; + /* code rate */ + uint8_t code_rate; + /* guard interval */ + uint8_t guard_interval; + /* transmission mode */ + uint8_t transmission_mode; +} __packed; + +struct as10x_tune_status { + /* tune status */ + uint8_t tune_state; + /* signal strength */ + int16_t signal_strength; + /* packet error rate 10^-4 */ + uint16_t PER; + /* bit error rate 10^-4 */ + uint16_t BER; +} __packed; + +struct as10x_demod_stats { + /* frame counter */ + uint32_t frame_count; + /* Bad frame counter */ + uint32_t bad_frame_count; + /* Number of wrong bytes fixed by Reed-Solomon */ + uint32_t bytes_fixed_by_rs; + /* Averaged MER */ + uint16_t mer; + /* statistics calculation state indicator (started or not) */ + uint8_t has_started; +} __packed; + +struct as10x_ts_filter { + uint16_t pid; /* valid PID value 0x00 : 0x2000 */ + uint8_t type; /* Red TS_PID_TYPE_<N> values */ + uint8_t idx; /* index in filtering table */ +} __packed; + +struct as10x_register_value { + uint8_t mode; + union { + uint8_t value8; /* 8 bit value */ + uint16_t value16; /* 16 bit value */ + uint32_t value32; /* 32 bit value */ + } __packed u; +} __packed; + +struct as10x_register_addr { + /* register addr */ + uint32_t addr; + /* register mode access */ + uint8_t mode; +}; + +#endif diff --git a/drivers/staging/media/bcm2048/Kconfig b/drivers/staging/media/bcm2048/Kconfig new file mode 100644 index 00000000000..a9fc6e18649 --- /dev/null +++ b/drivers/staging/media/bcm2048/Kconfig @@ -0,0 +1,13 @@ +# +# Multimedia Video device configuration +# + +config I2C_BCM2048 + tristate "Broadcom BCM2048 FM Radio Receiver support" + depends on I2C && VIDEO_V4L2 && RADIO_ADAPTERS + ---help--- + Say Y here if you want support to BCM2048 FM Radio Receiver. + This device driver supports only i2c bus. + + To compile this driver as a module, choose M here: the + module will be called radio-bcm2048. diff --git a/drivers/staging/media/bcm2048/Makefile b/drivers/staging/media/bcm2048/Makefile new file mode 100644 index 00000000000..b4f5663d140 --- /dev/null +++ b/drivers/staging/media/bcm2048/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_I2C_BCM2048) += radio-bcm2048.o diff --git a/drivers/staging/media/bcm2048/TODO b/drivers/staging/media/bcm2048/TODO new file mode 100644 index 00000000000..051f85dbe89 --- /dev/null +++ b/drivers/staging/media/bcm2048/TODO @@ -0,0 +1,24 @@ +TODO: + +From the initial code review: + +The main thing you need to do is to implement all the controls using the +control framework (see Documentation/video4linux/v4l2-controls.txt). +Most drivers are by now converted to the control framework, so you will +find many examples of how to do this in drivers/media/radio. + +The sysfs stuff should be replaced by controls as well. A lot of the RDS +support is now available as controls (although there may well be some +missing features, but that is easy enough to add). Since the RDS data is +actually read() from the device I am not sure whether the RDS +properties/controls should be there at all. + +Correct Coding Style, as this driver also violates several Style +rules, and do evil tricks, like returning from a function inside a +macro. + +Finally this driver should probably be split up into two parts: one +v4l2_subdev-based core driver and one platform driver. See e.g. +radio-si4713/si4713-i2c.c as a good example. But I would wait with that +until the rest of the driver is cleaned up. Then I have a better idea of +whether this is necessary or not. diff --git a/drivers/staging/media/bcm2048/radio-bcm2048.c b/drivers/staging/media/bcm2048/radio-bcm2048.c new file mode 100644 index 00000000000..bbf236e842a --- /dev/null +++ b/drivers/staging/media/bcm2048/radio-bcm2048.c @@ -0,0 +1,2744 @@ +/* + * drivers/staging/media/radio-bcm2048.c + * + * Driver for I2C Broadcom BCM2048 FM Radio Receiver: + * + * Copyright (C) Nokia Corporation + * Contact: Eero Nurkkala <ext-eero.nurkkala@nokia.com> + * + * Copyright (C) Nils Faerber <nils.faerber@kernelconcepts.de> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +/* + * History: + * Eero Nurkkala <ext-eero.nurkkala@nokia.com> + * Version 0.0.1 + * - Initial implementation + * 2010-02-21 Nils Faerber <nils.faerber@kernelconcepts.de> + * Version 0.0.2 + * - Add support for interrupt driven rds data reading + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/version.h> +#include <linux/interrupt.h> +#include <linux/sysfs.h> +#include <linux/completion.h> +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/videodev2.h> +#include <linux/mutex.h> +#include <linux/slab.h> +#include <media/v4l2-common.h> +#include <media/v4l2-ioctl.h> +#include "radio-bcm2048.h" + +/* driver definitions */ +#define BCM2048_DRIVER_AUTHOR "Eero Nurkkala <ext-eero.nurkkala@nokia.com>" +#define BCM2048_DRIVER_NAME BCM2048_NAME +#define BCM2048_DRIVER_VERSION KERNEL_VERSION(0, 0, 1) +#define BCM2048_DRIVER_CARD "Broadcom bcm2048 FM Radio Receiver" +#define BCM2048_DRIVER_DESC "I2C driver for BCM2048 FM Radio Receiver" + +/* I2C Control Registers */ +#define BCM2048_I2C_FM_RDS_SYSTEM 0x00 +#define BCM2048_I2C_FM_CTRL 0x01 +#define BCM2048_I2C_RDS_CTRL0 0x02 +#define BCM2048_I2C_RDS_CTRL1 0x03 +#define BCM2048_I2C_FM_AUDIO_PAUSE 0x04 +#define BCM2048_I2C_FM_AUDIO_CTRL0 0x05 +#define BCM2048_I2C_FM_AUDIO_CTRL1 0x06 +#define BCM2048_I2C_FM_SEARCH_CTRL0 0x07 +#define BCM2048_I2C_FM_SEARCH_CTRL1 0x08 +#define BCM2048_I2C_FM_SEARCH_TUNE_MODE 0x09 +#define BCM2048_I2C_FM_FREQ0 0x0a +#define BCM2048_I2C_FM_FREQ1 0x0b +#define BCM2048_I2C_FM_AF_FREQ0 0x0c +#define BCM2048_I2C_FM_AF_FREQ1 0x0d +#define BCM2048_I2C_FM_CARRIER 0x0e +#define BCM2048_I2C_FM_RSSI 0x0f +#define BCM2048_I2C_FM_RDS_MASK0 0x10 +#define BCM2048_I2C_FM_RDS_MASK1 0x11 +#define BCM2048_I2C_FM_RDS_FLAG0 0x12 +#define BCM2048_I2C_FM_RDS_FLAG1 0x13 +#define BCM2048_I2C_RDS_WLINE 0x14 +#define BCM2048_I2C_RDS_BLKB_MATCH0 0x16 +#define BCM2048_I2C_RDS_BLKB_MATCH1 0x17 +#define BCM2048_I2C_RDS_BLKB_MASK0 0x18 +#define BCM2048_I2C_RDS_BLKB_MASK1 0x19 +#define BCM2048_I2C_RDS_PI_MATCH0 0x1a +#define BCM2048_I2C_RDS_PI_MATCH1 0x1b +#define BCM2048_I2C_RDS_PI_MASK0 0x1c +#define BCM2048_I2C_RDS_PI_MASK1 0x1d +#define BCM2048_I2C_SPARE1 0x20 +#define BCM2048_I2C_SPARE2 0x21 +#define BCM2048_I2C_FM_RDS_REV 0x28 +#define BCM2048_I2C_SLAVE_CONFIGURATION 0x29 +#define BCM2048_I2C_RDS_DATA 0x80 +#define BCM2048_I2C_FM_BEST_TUNE_MODE 0x90 + +/* BCM2048_I2C_FM_RDS_SYSTEM */ +#define BCM2048_FM_ON 0x01 +#define BCM2048_RDS_ON 0x02 + +/* BCM2048_I2C_FM_CTRL */ +#define BCM2048_BAND_SELECT 0x01 +#define BCM2048_STEREO_MONO_AUTO_SELECT 0x02 +#define BCM2048_STEREO_MONO_MANUAL_SELECT 0x04 +#define BCM2048_STEREO_MONO_BLEND_SWITCH 0x08 +#define BCM2048_HI_LO_INJECTION 0x10 + +/* BCM2048_I2C_RDS_CTRL0 */ +#define BCM2048_RBDS_RDS_SELECT 0x01 +#define BCM2048_FLUSH_FIFO 0x02 + +/* BCM2048_I2C_FM_AUDIO_PAUSE */ +#define BCM2048_AUDIO_PAUSE_RSSI_TRESH 0x0f +#define BCM2048_AUDIO_PAUSE_DURATION 0xf0 + +/* BCM2048_I2C_FM_AUDIO_CTRL0 */ +#define BCM2048_RF_MUTE 0x01 +#define BCM2048_MANUAL_MUTE 0x02 +#define BCM2048_DAC_OUTPUT_LEFT 0x04 +#define BCM2048_DAC_OUTPUT_RIGHT 0x08 +#define BCM2048_AUDIO_ROUTE_DAC 0x10 +#define BCM2048_AUDIO_ROUTE_I2S 0x20 +#define BCM2048_DE_EMPHASIS_SELECT 0x40 +#define BCM2048_AUDIO_BANDWIDTH_SELECT 0x80 + +/* BCM2048_I2C_FM_SEARCH_CTRL0 */ +#define BCM2048_SEARCH_RSSI_THRESHOLD 0x7f +#define BCM2048_SEARCH_DIRECTION 0x80 + +/* BCM2048_I2C_FM_SEARCH_TUNE_MODE */ +#define BCM2048_FM_AUTO_SEARCH 0x03 + +/* BCM2048_I2C_FM_RSSI */ +#define BCM2048_RSSI_VALUE 0xff + +/* BCM2048_I2C_FM_RDS_MASK0 */ +/* BCM2048_I2C_FM_RDS_MASK1 */ +#define BCM2048_FM_FLAG_SEARCH_TUNE_FINISHED 0x01 +#define BCM2048_FM_FLAG_SEARCH_TUNE_FAIL 0x02 +#define BCM2048_FM_FLAG_RSSI_LOW 0x04 +#define BCM2048_FM_FLAG_CARRIER_ERROR_HIGH 0x08 +#define BCM2048_FM_FLAG_AUDIO_PAUSE_INDICATION 0x10 +#define BCM2048_FLAG_STEREO_DETECTED 0x20 +#define BCM2048_FLAG_STEREO_ACTIVE 0x40 + +/* BCM2048_I2C_RDS_DATA */ +#define BCM2048_SLAVE_ADDRESS 0x3f +#define BCM2048_SLAVE_ENABLE 0x80 + +/* BCM2048_I2C_FM_BEST_TUNE_MODE */ +#define BCM2048_BEST_TUNE_MODE 0x80 + +#define BCM2048_FM_FLAG_SEARCH_TUNE_FINISHED 0x01 +#define BCM2048_FM_FLAG_SEARCH_TUNE_FAIL 0x02 +#define BCM2048_FM_FLAG_RSSI_LOW 0x04 +#define BCM2048_FM_FLAG_CARRIER_ERROR_HIGH 0x08 +#define BCM2048_FM_FLAG_AUDIO_PAUSE_INDICATION 0x10 +#define BCM2048_FLAG_STEREO_DETECTED 0x20 +#define BCM2048_FLAG_STEREO_ACTIVE 0x40 + +#define BCM2048_RDS_FLAG_FIFO_WLINE 0x02 +#define BCM2048_RDS_FLAG_B_BLOCK_MATCH 0x08 +#define BCM2048_RDS_FLAG_SYNC_LOST 0x10 +#define BCM2048_RDS_FLAG_PI_MATCH 0x20 + +#define BCM2048_RDS_MARK_END_BYTE0 0x7C +#define BCM2048_RDS_MARK_END_BYTEN 0xFF + +#define BCM2048_FM_FLAGS_ALL (FM_FLAG_SEARCH_TUNE_FINISHED | \ + FM_FLAG_SEARCH_TUNE_FAIL | \ + FM_FLAG_RSSI_LOW | \ + FM_FLAG_CARRIER_ERROR_HIGH | \ + FM_FLAG_AUDIO_PAUSE_INDICATION | \ + FLAG_STEREO_DETECTED | FLAG_STEREO_ACTIVE) + +#define BCM2048_RDS_FLAGS_ALL (RDS_FLAG_FIFO_WLINE | \ + RDS_FLAG_B_BLOCK_MATCH | \ + RDS_FLAG_SYNC_LOST | RDS_FLAG_PI_MATCH) + +#define BCM2048_DEFAULT_TIMEOUT 1500 +#define BCM2048_AUTO_SEARCH_TIMEOUT 3000 + + +#define BCM2048_FREQDEV_UNIT 10000 +#define BCM2048_FREQV4L2_MULTI 625 +#define dev_to_v4l2(f) ((f * BCM2048_FREQDEV_UNIT) / BCM2048_FREQV4L2_MULTI) +#define v4l2_to_dev(f) ((f * BCM2048_FREQV4L2_MULTI) / BCM2048_FREQDEV_UNIT) + +#define msb(x) ((u8)((u16) x >> 8)) +#define lsb(x) ((u8)((u16) x & 0x00FF)) +#define compose_u16(msb, lsb) (((u16)msb << 8) | lsb) + +#define BCM2048_DEFAULT_POWERING_DELAY 20 +#define BCM2048_DEFAULT_REGION 0x02 +#define BCM2048_DEFAULT_MUTE 0x01 +#define BCM2048_DEFAULT_RSSI_THRESHOLD 0x64 +#define BCM2048_DEFAULT_RDS_WLINE 0x7E + +#define BCM2048_FM_SEARCH_INACTIVE 0x00 +#define BCM2048_FM_PRE_SET_MODE 0x01 +#define BCM2048_FM_AUTO_SEARCH_MODE 0x02 +#define BCM2048_FM_AF_JUMP_MODE 0x03 + +#define BCM2048_FREQUENCY_BASE 64000 + +#define BCM2048_POWER_ON 0x01 +#define BCM2048_POWER_OFF 0x00 + +#define BCM2048_ITEM_ENABLED 0x01 +#define BCM2048_SEARCH_DIRECTION_UP 0x01 + +#define BCM2048_DE_EMPHASIS_75us 75 +#define BCM2048_DE_EMPHASIS_50us 50 + +#define BCM2048_SCAN_FAIL 0x00 +#define BCM2048_SCAN_OK 0x01 + +#define BCM2048_FREQ_ERROR_FLOOR -20 +#define BCM2048_FREQ_ERROR_ROOF 20 + +/* -60 dB is reported as full signal strenght */ +#define BCM2048_RSSI_LEVEL_BASE -60 +#define BCM2048_RSSI_LEVEL_ROOF -100 +#define BCM2048_RSSI_LEVEL_ROOF_NEG 100 +#define BCM2048_SIGNAL_MULTIPLIER (0xFFFF / \ + (BCM2048_RSSI_LEVEL_ROOF_NEG + \ + BCM2048_RSSI_LEVEL_BASE)) + +#define BCM2048_RDS_FIFO_DUPLE_SIZE 0x03 +#define BCM2048_RDS_CRC_MASK 0x0F +#define BCM2048_RDS_CRC_NONE 0x00 +#define BCM2048_RDS_CRC_MAX_2BITS 0x04 +#define BCM2048_RDS_CRC_LEAST_2BITS 0x08 +#define BCM2048_RDS_CRC_UNRECOVARABLE 0x0C + +#define BCM2048_RDS_BLOCK_MASK 0xF0 +#define BCM2048_RDS_BLOCK_A 0x00 +#define BCM2048_RDS_BLOCK_B 0x10 +#define BCM2048_RDS_BLOCK_C 0x20 +#define BCM2048_RDS_BLOCK_D 0x30 +#define BCM2048_RDS_BLOCK_C_SCORED 0x40 +#define BCM2048_RDS_BLOCK_E 0x60 + +#define BCM2048_RDS_RT 0x20 +#define BCM2048_RDS_PS 0x00 + +#define BCM2048_RDS_GROUP_AB_MASK 0x08 +#define BCM2048_RDS_GROUP_A 0x00 +#define BCM2048_RDS_GROUP_B 0x08 + +#define BCM2048_RDS_RT_AB_MASK 0x10 +#define BCM2048_RDS_RT_A 0x00 +#define BCM2048_RDS_RT_B 0x10 +#define BCM2048_RDS_RT_INDEX 0x0F + +#define BCM2048_RDS_PS_INDEX 0x03 + +struct rds_info { + u16 rds_pi; +#define BCM2048_MAX_RDS_RT (64 + 1) + u8 rds_rt[BCM2048_MAX_RDS_RT]; + u8 rds_rt_group_b; + u8 rds_rt_ab; +#define BCM2048_MAX_RDS_PS (8 + 1) + u8 rds_ps[BCM2048_MAX_RDS_PS]; + u8 rds_ps_group; + u8 rds_ps_group_cnt; +#define BCM2048_MAX_RDS_RADIO_TEXT 255 + u8 radio_text[BCM2048_MAX_RDS_RADIO_TEXT + 3]; + u8 text_len; +}; + +struct region_info { + u32 bottom_frequency; + u32 top_frequency; + u8 deemphasis; + u8 channel_spacing; + u8 region; +}; + +struct bcm2048_device { + struct i2c_client *client; + struct video_device *videodev; + struct work_struct work; + struct completion compl; + struct mutex mutex; + struct bcm2048_platform_data *platform_data; + struct rds_info rds_info; + struct region_info region_info; + u16 frequency; + u8 cache_fm_rds_system; + u8 cache_fm_ctrl; + u8 cache_fm_audio_ctrl0; + u8 cache_fm_search_ctrl0; + u8 power_state; + u8 rds_state; + u8 fifo_size; + u8 scan_state; + u8 mute_state; + + /* for rds data device read */ + wait_queue_head_t read_queue; + unsigned int users; + unsigned char rds_data_available; + unsigned int rd_index; +}; + +static int radio_nr = -1; /* radio device minor (-1 ==> auto assign) */ +module_param(radio_nr, int, 0); +MODULE_PARM_DESC(radio_nr, + "Minor number for radio device (-1 ==> auto assign)"); + +static struct region_info region_configs[] = { + /* USA */ + { + .channel_spacing = 20, + .bottom_frequency = 87500, + .top_frequency = 108000, + .deemphasis = 75, + .region = 0, + }, + /* Australia */ + { + .channel_spacing = 20, + .bottom_frequency = 87500, + .top_frequency = 108000, + .deemphasis = 50, + .region = 1, + }, + /* Europe */ + { + .channel_spacing = 10, + .bottom_frequency = 87500, + .top_frequency = 108000, + .deemphasis = 50, + .region = 2, + }, + /* Japan */ + { + .channel_spacing = 10, + .bottom_frequency = 76000, + .top_frequency = 90000, + .deemphasis = 50, + .region = 3, + }, + /* Japan wide band */ + { + .channel_spacing = 10, + .bottom_frequency = 76000, + .top_frequency = 108000, + .deemphasis = 50, + .region = 4, + }, +}; + +/* + * I2C Interface read / write + */ +static int bcm2048_send_command(struct bcm2048_device *bdev, unsigned int reg, + unsigned int value) +{ + struct i2c_client *client = bdev->client; + u8 data[2]; + + if (!bdev->power_state) { + dev_err(&bdev->client->dev, "bcm2048: chip not powered!\n"); + return -EIO; + } + + data[0] = reg & 0xff; + data[1] = value & 0xff; + + if (i2c_master_send(client, data, 2) == 2) { + return 0; + } else { + dev_err(&bdev->client->dev, "BCM I2C error!\n"); + dev_err(&bdev->client->dev, "Is Bluetooth up and running?\n"); + return -EIO; + } +} + +static int bcm2048_recv_command(struct bcm2048_device *bdev, unsigned int reg, + u8 *value) +{ + struct i2c_client *client = bdev->client; + + if (!bdev->power_state) { + dev_err(&bdev->client->dev, "bcm2048: chip not powered!\n"); + return -EIO; + } + + value[0] = i2c_smbus_read_byte_data(client, reg & 0xff); + + return 0; +} + +static int bcm2048_recv_duples(struct bcm2048_device *bdev, unsigned int reg, + u8 *value, u8 duples) +{ + struct i2c_client *client = bdev->client; + struct i2c_adapter *adap = client->adapter; + struct i2c_msg msg[2]; + u8 buf; + + if (!bdev->power_state) { + dev_err(&bdev->client->dev, "bcm2048: chip not powered!\n"); + return -EIO; + } + + buf = reg & 0xff; + + msg[0].addr = client->addr; + msg[0].flags = client->flags & I2C_M_TEN; + msg[0].len = 1; + msg[0].buf = &buf; + + msg[1].addr = client->addr; + msg[1].flags = client->flags & I2C_M_TEN; + msg[1].flags |= I2C_M_RD; + msg[1].len = duples; + msg[1].buf = value; + + return i2c_transfer(adap, msg, 2); +} + +/* + * BCM2048 - I2C register programming helpers + */ +static int bcm2048_set_power_state(struct bcm2048_device *bdev, u8 power) +{ + int err = 0; + + mutex_lock(&bdev->mutex); + + if (power) { + bdev->power_state = BCM2048_POWER_ON; + bdev->cache_fm_rds_system |= BCM2048_FM_ON; + } else { + bdev->cache_fm_rds_system &= ~BCM2048_FM_ON; + } + + /* + * Warning! FM cannot be turned off because then + * the I2C communications get ruined! + * Comment off the "if (power)" when the chip works! + */ + if (power) + err = bcm2048_send_command(bdev, BCM2048_I2C_FM_RDS_SYSTEM, + bdev->cache_fm_rds_system); + msleep(BCM2048_DEFAULT_POWERING_DELAY); + + if (!power) + bdev->power_state = BCM2048_POWER_OFF; + + mutex_unlock(&bdev->mutex); + return err; +} + +static int bcm2048_get_power_state(struct bcm2048_device *bdev) +{ + int err; + u8 value; + + mutex_lock(&bdev->mutex); + + err = bcm2048_recv_command(bdev, BCM2048_I2C_FM_RDS_SYSTEM, &value); + + mutex_unlock(&bdev->mutex); + + if (!err && (value & BCM2048_FM_ON)) + return BCM2048_POWER_ON; + + return err; +} + +static int bcm2048_set_rds_no_lock(struct bcm2048_device *bdev, u8 rds_on) +{ + int err; + u8 flags; + + bdev->cache_fm_rds_system &= ~BCM2048_RDS_ON; + + if (rds_on) { + bdev->cache_fm_rds_system |= BCM2048_RDS_ON; + bdev->rds_state = BCM2048_RDS_ON; + flags = BCM2048_RDS_FLAG_FIFO_WLINE; + err = bcm2048_send_command(bdev, BCM2048_I2C_FM_RDS_MASK1, + flags); + } else { + flags = 0; + bdev->rds_state = 0; + err = bcm2048_send_command(bdev, BCM2048_I2C_FM_RDS_MASK1, + flags); + memset(&bdev->rds_info, 0, sizeof(bdev->rds_info)); + } + + err = bcm2048_send_command(bdev, BCM2048_I2C_FM_RDS_SYSTEM, + bdev->cache_fm_rds_system); + + return err; +} + +static int bcm2048_get_rds_no_lock(struct bcm2048_device *bdev) +{ + int err; + u8 value; + + err = bcm2048_recv_command(bdev, BCM2048_I2C_FM_RDS_SYSTEM, &value); + + if (!err && (value & BCM2048_RDS_ON)) + return BCM2048_ITEM_ENABLED; + + return err; +} + +static int bcm2048_set_rds(struct bcm2048_device *bdev, u8 rds_on) +{ + int err; + + mutex_lock(&bdev->mutex); + + err = bcm2048_set_rds_no_lock(bdev, rds_on); + + mutex_unlock(&bdev->mutex); + return err; +} + +static int bcm2048_get_rds(struct bcm2048_device *bdev) +{ + int err; + + mutex_lock(&bdev->mutex); + + err = bcm2048_get_rds_no_lock(bdev); + + mutex_unlock(&bdev->mutex); + return err; +} + +static int bcm2048_get_rds_pi(struct bcm2048_device *bdev) +{ + return bdev->rds_info.rds_pi; +} + +static int bcm2048_set_fm_automatic_stereo_mono(struct bcm2048_device *bdev, + u8 enabled) +{ + int err; + + mutex_lock(&bdev->mutex); + + bdev->cache_fm_ctrl &= ~BCM2048_STEREO_MONO_AUTO_SELECT; + + if (enabled) + bdev->cache_fm_ctrl |= BCM2048_STEREO_MONO_AUTO_SELECT; + + err = bcm2048_send_command(bdev, BCM2048_I2C_FM_CTRL, + bdev->cache_fm_ctrl); + + mutex_unlock(&bdev->mutex); + return err; +} + +static int bcm2048_set_fm_hi_lo_injection(struct bcm2048_device *bdev, + u8 hi_lo) +{ + int err; + + mutex_lock(&bdev->mutex); + + bdev->cache_fm_ctrl &= ~BCM2048_HI_LO_INJECTION; + + if (hi_lo) + bdev->cache_fm_ctrl |= BCM2048_HI_LO_INJECTION; + + err = bcm2048_send_command(bdev, BCM2048_I2C_FM_CTRL, + bdev->cache_fm_ctrl); + + mutex_unlock(&bdev->mutex); + return err; +} + +static int bcm2048_get_fm_hi_lo_injection(struct bcm2048_device *bdev) +{ + int err; + u8 value; + + mutex_lock(&bdev->mutex); + + err = bcm2048_recv_command(bdev, BCM2048_I2C_FM_CTRL, &value); + + mutex_unlock(&bdev->mutex); + + if (!err && (value & BCM2048_HI_LO_INJECTION)) + return BCM2048_ITEM_ENABLED; + + return err; +} + +static int bcm2048_set_fm_frequency(struct bcm2048_device *bdev, u32 frequency) +{ + int err; + + if (frequency < bdev->region_info.bottom_frequency || + frequency > bdev->region_info.top_frequency) + return -EDOM; + + frequency -= BCM2048_FREQUENCY_BASE; + + mutex_lock(&bdev->mutex); + + err = bcm2048_send_command(bdev, BCM2048_I2C_FM_FREQ0, lsb(frequency)); + err |= bcm2048_send_command(bdev, BCM2048_I2C_FM_FREQ1, + msb(frequency)); + + if (!err) + bdev->frequency = frequency; + + mutex_unlock(&bdev->mutex); + return err; +} + +static int bcm2048_get_fm_frequency(struct bcm2048_device *bdev) +{ + int err; + u8 lsb, msb; + + mutex_lock(&bdev->mutex); + + err = bcm2048_recv_command(bdev, BCM2048_I2C_FM_FREQ0, &lsb); + err |= bcm2048_recv_command(bdev, BCM2048_I2C_FM_FREQ1, &msb); + + mutex_unlock(&bdev->mutex); + + if (err) + return err; + + err = compose_u16(msb, lsb); + err += BCM2048_FREQUENCY_BASE; + + return err; +} + +static int bcm2048_set_fm_af_frequency(struct bcm2048_device *bdev, + u32 frequency) +{ + int err; + + if (frequency < bdev->region_info.bottom_frequency || + frequency > bdev->region_info.top_frequency) + return -EDOM; + + frequency -= BCM2048_FREQUENCY_BASE; + + mutex_lock(&bdev->mutex); + + err = bcm2048_send_command(bdev, BCM2048_I2C_FM_AF_FREQ0, + lsb(frequency)); + err |= bcm2048_send_command(bdev, BCM2048_I2C_FM_AF_FREQ1, + msb(frequency)); + if (!err) + bdev->frequency = frequency; + + mutex_unlock(&bdev->mutex); + return err; +} + +static int bcm2048_get_fm_af_frequency(struct bcm2048_device *bdev) +{ + int err; + u8 lsb, msb; + + mutex_lock(&bdev->mutex); + + err = bcm2048_recv_command(bdev, BCM2048_I2C_FM_AF_FREQ0, &lsb); + err |= bcm2048_recv_command(bdev, BCM2048_I2C_FM_AF_FREQ1, &msb); + + mutex_unlock(&bdev->mutex); + + if (err) + return err; + + err = compose_u16(msb, lsb); + err += BCM2048_FREQUENCY_BASE; + + return err; +} + +static int bcm2048_set_fm_deemphasis(struct bcm2048_device *bdev, int d) +{ + int err; + u8 deemphasis; + + if (d == BCM2048_DE_EMPHASIS_75us) + deemphasis = BCM2048_DE_EMPHASIS_SELECT; + else + deemphasis = 0; + + mutex_lock(&bdev->mutex); + + bdev->cache_fm_audio_ctrl0 &= ~BCM2048_DE_EMPHASIS_SELECT; + bdev->cache_fm_audio_ctrl0 |= deemphasis; + + err = bcm2048_send_command(bdev, BCM2048_I2C_FM_AUDIO_CTRL0, + bdev->cache_fm_audio_ctrl0); + + if (!err) + bdev->region_info.deemphasis = d; + + mutex_unlock(&bdev->mutex); + + return err; +} + +static int bcm2048_get_fm_deemphasis(struct bcm2048_device *bdev) +{ + int err; + u8 value; + + mutex_lock(&bdev->mutex); + + err = bcm2048_recv_command(bdev, BCM2048_I2C_FM_AUDIO_CTRL0, &value); + + mutex_unlock(&bdev->mutex); + + if (!err) { + if (value & BCM2048_DE_EMPHASIS_SELECT) + return BCM2048_DE_EMPHASIS_75us; + else + return BCM2048_DE_EMPHASIS_50us; + } + + return err; +} + +static int bcm2048_set_region(struct bcm2048_device *bdev, u8 region) +{ + int err; + u32 new_frequency = 0; + + if (region >= ARRAY_SIZE(region_configs)) + return -EINVAL; + + mutex_lock(&bdev->mutex); + bdev->region_info = region_configs[region]; + mutex_unlock(&bdev->mutex); + + if (bdev->frequency < region_configs[region].bottom_frequency || + bdev->frequency > region_configs[region].top_frequency) + new_frequency = region_configs[region].bottom_frequency; + + if (new_frequency > 0) { + err = bcm2048_set_fm_frequency(bdev, new_frequency); + + if (err) + goto done; + } + + err = bcm2048_set_fm_deemphasis(bdev, + region_configs[region].deemphasis); + +done: + return err; +} + +static int bcm2048_get_region(struct bcm2048_device *bdev) +{ + int err; + + mutex_lock(&bdev->mutex); + err = bdev->region_info.region; + mutex_unlock(&bdev->mutex); + + return err; +} + +static int bcm2048_set_mute(struct bcm2048_device *bdev, u16 mute) +{ + int err; + + mutex_lock(&bdev->mutex); + + bdev->cache_fm_audio_ctrl0 &= ~(BCM2048_RF_MUTE | BCM2048_MANUAL_MUTE); + + if (mute) + bdev->cache_fm_audio_ctrl0 |= (BCM2048_RF_MUTE | + BCM2048_MANUAL_MUTE); + + err = bcm2048_send_command(bdev, BCM2048_I2C_FM_AUDIO_CTRL0, + bdev->cache_fm_audio_ctrl0); + + if (!err) + bdev->mute_state = mute; + + mutex_unlock(&bdev->mutex); + return err; +} + +static int bcm2048_get_mute(struct bcm2048_device *bdev) +{ + int err; + u8 value; + + mutex_lock(&bdev->mutex); + + if (bdev->power_state) { + err = bcm2048_recv_command(bdev, BCM2048_I2C_FM_AUDIO_CTRL0, + &value); + if (!err) + err = value & (BCM2048_RF_MUTE | BCM2048_MANUAL_MUTE); + } else { + err = bdev->mute_state; + } + + mutex_unlock(&bdev->mutex); + return err; +} + +static int bcm2048_set_audio_route(struct bcm2048_device *bdev, u8 route) +{ + int err; + + mutex_lock(&bdev->mutex); + + route &= (BCM2048_AUDIO_ROUTE_DAC | BCM2048_AUDIO_ROUTE_I2S); + bdev->cache_fm_audio_ctrl0 &= ~(BCM2048_AUDIO_ROUTE_DAC | + BCM2048_AUDIO_ROUTE_I2S); + bdev->cache_fm_audio_ctrl0 |= route; + + err = bcm2048_send_command(bdev, BCM2048_I2C_FM_AUDIO_CTRL0, + bdev->cache_fm_audio_ctrl0); + + mutex_unlock(&bdev->mutex); + return err; +} + +static int bcm2048_get_audio_route(struct bcm2048_device *bdev) +{ + int err; + u8 value; + + mutex_lock(&bdev->mutex); + + err = bcm2048_recv_command(bdev, BCM2048_I2C_FM_AUDIO_CTRL0, &value); + + mutex_unlock(&bdev->mutex); + + if (!err) + return value & (BCM2048_AUDIO_ROUTE_DAC | + BCM2048_AUDIO_ROUTE_I2S); + + return err; +} + +static int bcm2048_set_dac_output(struct bcm2048_device *bdev, u8 channels) +{ + int err; + + mutex_lock(&bdev->mutex); + + bdev->cache_fm_audio_ctrl0 &= ~(BCM2048_DAC_OUTPUT_LEFT | + BCM2048_DAC_OUTPUT_RIGHT); + bdev->cache_fm_audio_ctrl0 |= channels; + + err = bcm2048_send_command(bdev, BCM2048_I2C_FM_AUDIO_CTRL0, + bdev->cache_fm_audio_ctrl0); + + mutex_unlock(&bdev->mutex); + return err; +} + +static int bcm2048_get_dac_output(struct bcm2048_device *bdev) +{ + int err; + u8 value; + + mutex_lock(&bdev->mutex); + + err = bcm2048_recv_command(bdev, BCM2048_I2C_FM_AUDIO_CTRL0, &value); + + mutex_unlock(&bdev->mutex); + + if (!err) + return value & (BCM2048_DAC_OUTPUT_LEFT | + BCM2048_DAC_OUTPUT_RIGHT); + + return err; +} + +static int bcm2048_set_fm_search_rssi_threshold(struct bcm2048_device *bdev, + u8 threshold) +{ + int err; + + mutex_lock(&bdev->mutex); + + threshold &= BCM2048_SEARCH_RSSI_THRESHOLD; + bdev->cache_fm_search_ctrl0 &= ~BCM2048_SEARCH_RSSI_THRESHOLD; + bdev->cache_fm_search_ctrl0 |= threshold; + + err = bcm2048_send_command(bdev, BCM2048_I2C_FM_SEARCH_CTRL0, + bdev->cache_fm_search_ctrl0); + + mutex_unlock(&bdev->mutex); + return err; +} + +static int bcm2048_get_fm_search_rssi_threshold(struct bcm2048_device *bdev) +{ + int err; + u8 value; + + mutex_lock(&bdev->mutex); + + err = bcm2048_recv_command(bdev, BCM2048_I2C_FM_SEARCH_CTRL0, &value); + + mutex_unlock(&bdev->mutex); + + if (!err) + return value & BCM2048_SEARCH_RSSI_THRESHOLD; + + return err; +} + +static int bcm2048_set_fm_search_mode_direction(struct bcm2048_device *bdev, + u8 direction) +{ + int err; + + mutex_lock(&bdev->mutex); + + bdev->cache_fm_search_ctrl0 &= ~BCM2048_SEARCH_DIRECTION; + + if (direction) + bdev->cache_fm_search_ctrl0 |= BCM2048_SEARCH_DIRECTION; + + err = bcm2048_send_command(bdev, BCM2048_I2C_FM_SEARCH_CTRL0, + bdev->cache_fm_search_ctrl0); + + mutex_unlock(&bdev->mutex); + return err; +} + +static int bcm2048_get_fm_search_mode_direction(struct bcm2048_device *bdev) +{ + int err; + u8 value; + + mutex_lock(&bdev->mutex); + + err = bcm2048_recv_command(bdev, BCM2048_I2C_FM_SEARCH_CTRL0, &value); + + mutex_unlock(&bdev->mutex); + + if (!err && (value & BCM2048_SEARCH_DIRECTION)) + return BCM2048_SEARCH_DIRECTION_UP; + + return err; +} + +static int bcm2048_set_fm_search_tune_mode(struct bcm2048_device *bdev, + u8 mode) +{ + int err, timeout, restart_rds = 0; + u8 value, flags; + + value = mode & BCM2048_FM_AUTO_SEARCH; + + flags = BCM2048_FM_FLAG_SEARCH_TUNE_FINISHED | + BCM2048_FM_FLAG_SEARCH_TUNE_FAIL; + + mutex_lock(&bdev->mutex); + + /* + * If RDS is enabled, and frequency is changed, RDS quits working. + * Thus, always restart RDS if it's enabled. Moreover, RDS must + * not be enabled while changing the frequency because it can + * provide a race to the mutex from the workqueue handler if RDS + * IRQ occurs while waiting for frequency changed IRQ. + */ + if (bcm2048_get_rds_no_lock(bdev)) { + err = bcm2048_set_rds_no_lock(bdev, 0); + if (err) + goto unlock; + restart_rds = 1; + } + + err = bcm2048_send_command(bdev, BCM2048_I2C_FM_RDS_MASK0, flags); + + if (err) + goto unlock; + + bcm2048_send_command(bdev, BCM2048_I2C_FM_SEARCH_TUNE_MODE, value); + + if (mode != BCM2048_FM_AUTO_SEARCH_MODE) + timeout = BCM2048_DEFAULT_TIMEOUT; + else + timeout = BCM2048_AUTO_SEARCH_TIMEOUT; + + if (!wait_for_completion_timeout(&bdev->compl, + msecs_to_jiffies(timeout))) + dev_err(&bdev->client->dev, "IRQ timeout.\n"); + + if (value) + if (!bdev->scan_state) + err = -EIO; + +unlock: + if (restart_rds) + err |= bcm2048_set_rds_no_lock(bdev, 1); + + mutex_unlock(&bdev->mutex); + + return err; +} + +static int bcm2048_get_fm_search_tune_mode(struct bcm2048_device *bdev) +{ + int err; + u8 value; + + mutex_lock(&bdev->mutex); + + err = bcm2048_recv_command(bdev, BCM2048_I2C_FM_SEARCH_TUNE_MODE, + &value); + + mutex_unlock(&bdev->mutex); + + if (!err) + return value & BCM2048_FM_AUTO_SEARCH; + + return err; +} + +static int bcm2048_set_rds_b_block_mask(struct bcm2048_device *bdev, u16 mask) +{ + int err; + + mutex_lock(&bdev->mutex); + + err = bcm2048_send_command(bdev, + BCM2048_I2C_RDS_BLKB_MASK0, lsb(mask)); + err |= bcm2048_send_command(bdev, + BCM2048_I2C_RDS_BLKB_MASK1, msb(mask)); + + mutex_unlock(&bdev->mutex); + return err; +} + +static int bcm2048_get_rds_b_block_mask(struct bcm2048_device *bdev) +{ + int err; + u8 lsb, msb; + + mutex_lock(&bdev->mutex); + + err = bcm2048_recv_command(bdev, + BCM2048_I2C_RDS_BLKB_MASK0, &lsb); + err |= bcm2048_recv_command(bdev, + BCM2048_I2C_RDS_BLKB_MASK1, &msb); + + mutex_unlock(&bdev->mutex); + + if (!err) + return compose_u16(msb, lsb); + + return err; +} + +static int bcm2048_set_rds_b_block_match(struct bcm2048_device *bdev, + u16 match) +{ + int err; + + mutex_lock(&bdev->mutex); + + err = bcm2048_send_command(bdev, + BCM2048_I2C_RDS_BLKB_MATCH0, lsb(match)); + err |= bcm2048_send_command(bdev, + BCM2048_I2C_RDS_BLKB_MATCH1, msb(match)); + + mutex_unlock(&bdev->mutex); + return err; +} + +static int bcm2048_get_rds_b_block_match(struct bcm2048_device *bdev) +{ + int err; + u8 lsb, msb; + + mutex_lock(&bdev->mutex); + + err = bcm2048_recv_command(bdev, + BCM2048_I2C_RDS_BLKB_MATCH0, &lsb); + err |= bcm2048_recv_command(bdev, + BCM2048_I2C_RDS_BLKB_MATCH1, &msb); + + mutex_unlock(&bdev->mutex); + + if (!err) + return compose_u16(msb, lsb); + + return err; +} + +static int bcm2048_set_rds_pi_mask(struct bcm2048_device *bdev, u16 mask) +{ + int err; + + mutex_lock(&bdev->mutex); + + err = bcm2048_send_command(bdev, + BCM2048_I2C_RDS_PI_MASK0, lsb(mask)); + err |= bcm2048_send_command(bdev, + BCM2048_I2C_RDS_PI_MASK1, msb(mask)); + + mutex_unlock(&bdev->mutex); + return err; +} + +static int bcm2048_get_rds_pi_mask(struct bcm2048_device *bdev) +{ + int err; + u8 lsb, msb; + + mutex_lock(&bdev->mutex); + + err = bcm2048_recv_command(bdev, + BCM2048_I2C_RDS_PI_MASK0, &lsb); + err |= bcm2048_recv_command(bdev, + BCM2048_I2C_RDS_PI_MASK1, &msb); + + mutex_unlock(&bdev->mutex); + + if (!err) + return compose_u16(msb, lsb); + + return err; +} + +static int bcm2048_set_rds_pi_match(struct bcm2048_device *bdev, u16 match) +{ + int err; + + mutex_lock(&bdev->mutex); + + err = bcm2048_send_command(bdev, + BCM2048_I2C_RDS_PI_MATCH0, lsb(match)); + err |= bcm2048_send_command(bdev, + BCM2048_I2C_RDS_PI_MATCH1, msb(match)); + + mutex_unlock(&bdev->mutex); + return err; +} + +static int bcm2048_get_rds_pi_match(struct bcm2048_device *bdev) +{ + int err; + u8 lsb, msb; + + mutex_lock(&bdev->mutex); + + err = bcm2048_recv_command(bdev, + BCM2048_I2C_RDS_PI_MATCH0, &lsb); + err |= bcm2048_recv_command(bdev, + BCM2048_I2C_RDS_PI_MATCH1, &msb); + + mutex_unlock(&bdev->mutex); + + if (!err) + return compose_u16(msb, lsb); + + return err; +} + +static int bcm2048_set_fm_rds_mask(struct bcm2048_device *bdev, u16 mask) +{ + int err; + + mutex_lock(&bdev->mutex); + + err = bcm2048_send_command(bdev, + BCM2048_I2C_FM_RDS_MASK0, lsb(mask)); + err |= bcm2048_send_command(bdev, + BCM2048_I2C_FM_RDS_MASK1, msb(mask)); + + mutex_unlock(&bdev->mutex); + return err; +} + +static int bcm2048_get_fm_rds_mask(struct bcm2048_device *bdev) +{ + int err; + u8 value0, value1; + + mutex_lock(&bdev->mutex); + + err = bcm2048_recv_command(bdev, BCM2048_I2C_FM_RDS_MASK0, &value0); + err |= bcm2048_recv_command(bdev, BCM2048_I2C_FM_RDS_MASK1, &value1); + + mutex_unlock(&bdev->mutex); + + if (!err) + return compose_u16(value1, value0); + + return err; +} + +static int bcm2048_get_fm_rds_flags(struct bcm2048_device *bdev) +{ + int err; + u8 value0, value1; + + mutex_lock(&bdev->mutex); + + err = bcm2048_recv_command(bdev, BCM2048_I2C_FM_RDS_FLAG0, &value0); + err |= bcm2048_recv_command(bdev, BCM2048_I2C_FM_RDS_FLAG1, &value1); + + mutex_unlock(&bdev->mutex); + + if (!err) + return compose_u16(value1, value0); + + return err; +} + +static int bcm2048_get_region_bottom_frequency(struct bcm2048_device *bdev) +{ + return bdev->region_info.bottom_frequency; +} + +static int bcm2048_get_region_top_frequency(struct bcm2048_device *bdev) +{ + return bdev->region_info.top_frequency; +} + +static int bcm2048_set_fm_best_tune_mode(struct bcm2048_device *bdev, u8 mode) +{ + int err; + u8 value; + + mutex_lock(&bdev->mutex); + + /* Perform read as the manual indicates */ + err = bcm2048_recv_command(bdev, BCM2048_I2C_FM_BEST_TUNE_MODE, + &value); + value &= ~BCM2048_BEST_TUNE_MODE; + + if (mode) + value |= BCM2048_BEST_TUNE_MODE; + err |= bcm2048_send_command(bdev, BCM2048_I2C_FM_BEST_TUNE_MODE, + value); + + mutex_unlock(&bdev->mutex); + return err; +} + +static int bcm2048_get_fm_best_tune_mode(struct bcm2048_device *bdev) +{ + int err; + u8 value; + + mutex_lock(&bdev->mutex); + + err = bcm2048_recv_command(bdev, BCM2048_I2C_FM_BEST_TUNE_MODE, + &value); + + mutex_unlock(&bdev->mutex); + + if (!err && (value & BCM2048_BEST_TUNE_MODE)) + return BCM2048_ITEM_ENABLED; + + return err; +} + +static int bcm2048_get_fm_carrier_error(struct bcm2048_device *bdev) +{ + int err = 0; + s8 value; + + mutex_lock(&bdev->mutex); + err = bcm2048_recv_command(bdev, BCM2048_I2C_FM_CARRIER, &value); + mutex_unlock(&bdev->mutex); + + if (!err) + return value; + + return err; +} + +static int bcm2048_get_fm_rssi(struct bcm2048_device *bdev) +{ + int err; + s8 value; + + mutex_lock(&bdev->mutex); + err = bcm2048_recv_command(bdev, BCM2048_I2C_FM_RSSI, &value); + mutex_unlock(&bdev->mutex); + + if (!err) + return value; + + return err; +} + +static int bcm2048_set_rds_wline(struct bcm2048_device *bdev, u8 wline) +{ + int err; + + mutex_lock(&bdev->mutex); + + err = bcm2048_send_command(bdev, BCM2048_I2C_RDS_WLINE, wline); + + if (!err) + bdev->fifo_size = wline; + + mutex_unlock(&bdev->mutex); + return err; +} + +static int bcm2048_get_rds_wline(struct bcm2048_device *bdev) +{ + int err; + u8 value; + + mutex_lock(&bdev->mutex); + + err = bcm2048_recv_command(bdev, BCM2048_I2C_RDS_WLINE, &value); + + mutex_unlock(&bdev->mutex); + + if (!err) { + bdev->fifo_size = value; + return value; + } + + return err; +} + +static int bcm2048_checkrev(struct bcm2048_device *bdev) +{ + int err; + u8 version; + + mutex_lock(&bdev->mutex); + + err = bcm2048_recv_command(bdev, BCM2048_I2C_FM_RDS_REV, &version); + + mutex_unlock(&bdev->mutex); + + if (!err) { + dev_info(&bdev->client->dev, "BCM2048 Version 0x%x\n", + version); + return version; + } + + return err; +} + +static int bcm2048_get_rds_rt(struct bcm2048_device *bdev, char *data) +{ + int err = 0, i, j = 0, ce = 0, cr = 0; + char data_buffer[BCM2048_MAX_RDS_RT+1]; + + mutex_lock(&bdev->mutex); + + if (!bdev->rds_info.text_len) { + err = -EINVAL; + goto unlock; + } + + for (i = 0; i < BCM2048_MAX_RDS_RT; i++) { + if (bdev->rds_info.rds_rt[i]) { + ce = i; + /* Skip the carriage return */ + if (bdev->rds_info.rds_rt[i] != 0x0d) { + data_buffer[j++] = bdev->rds_info.rds_rt[i]; + } else { + cr = i; + break; + } + } + } + + if (j <= BCM2048_MAX_RDS_RT) + data_buffer[j] = 0; + + for (i = 0; i < BCM2048_MAX_RDS_RT; i++) { + if (!bdev->rds_info.rds_rt[i]) { + if (cr && (i < cr)) { + err = -EBUSY; + goto unlock; + } + if (i < ce) { + if (cr && (i >= cr)) + break; + err = -EBUSY; + goto unlock; + } + } + } + + memcpy(data, data_buffer, sizeof(data_buffer)); + +unlock: + mutex_unlock(&bdev->mutex); + return err; +} + +static int bcm2048_get_rds_ps(struct bcm2048_device *bdev, char *data) +{ + int err = 0, i, j = 0; + char data_buffer[BCM2048_MAX_RDS_PS+1]; + + mutex_lock(&bdev->mutex); + + if (!bdev->rds_info.text_len) { + err = -EINVAL; + goto unlock; + } + + for (i = 0; i < BCM2048_MAX_RDS_PS; i++) { + if (bdev->rds_info.rds_ps[i]) { + data_buffer[j++] = bdev->rds_info.rds_ps[i]; + } else { + if (i < (BCM2048_MAX_RDS_PS - 1)) { + err = -EBUSY; + goto unlock; + } + } + } + + if (j <= BCM2048_MAX_RDS_PS) + data_buffer[j] = 0; + + memcpy(data, data_buffer, sizeof(data_buffer)); + +unlock: + mutex_unlock(&bdev->mutex); + return err; +} + +static void bcm2048_parse_rds_pi(struct bcm2048_device *bdev) +{ + int i, cnt = 0; + u16 pi; + + for (i = 0; i < bdev->fifo_size; i += BCM2048_RDS_FIFO_DUPLE_SIZE) { + + /* Block A match, only data without crc errors taken */ + if (bdev->rds_info.radio_text[i] == BCM2048_RDS_BLOCK_A) { + + pi = ((bdev->rds_info.radio_text[i+1] << 8) + + bdev->rds_info.radio_text[i+2]); + + if (!bdev->rds_info.rds_pi) { + bdev->rds_info.rds_pi = pi; + return; + } + if (pi != bdev->rds_info.rds_pi) { + cnt++; + if (cnt > 3) { + bdev->rds_info.rds_pi = pi; + cnt = 0; + } + } else { + cnt = 0; + } + } + } +} + +static int bcm2048_rds_block_crc(struct bcm2048_device *bdev, int i) +{ + return bdev->rds_info.radio_text[i] & BCM2048_RDS_CRC_MASK; +} + +static void bcm2048_parse_rds_rt_block(struct bcm2048_device *bdev, int i, + int index, int crc) +{ + /* Good data will overwrite poor data */ + if (crc) { + if (!bdev->rds_info.rds_rt[index]) + bdev->rds_info.rds_rt[index] = + bdev->rds_info.radio_text[i+1]; + if (!bdev->rds_info.rds_rt[index+1]) + bdev->rds_info.rds_rt[index+1] = + bdev->rds_info.radio_text[i+2]; + } else { + bdev->rds_info.rds_rt[index] = bdev->rds_info.radio_text[i+1]; + bdev->rds_info.rds_rt[index+1] = + bdev->rds_info.radio_text[i+2]; + } +} + +static int bcm2048_parse_rt_match_b(struct bcm2048_device *bdev, int i) +{ + int crc, rt_id, rt_group_b, rt_ab, index = 0; + + crc = bcm2048_rds_block_crc(bdev, i); + + if (crc == BCM2048_RDS_CRC_UNRECOVARABLE) + return -EIO; + + if ((bdev->rds_info.radio_text[i] & BCM2048_RDS_BLOCK_MASK) == + BCM2048_RDS_BLOCK_B) { + + rt_id = (bdev->rds_info.radio_text[i+1] & + BCM2048_RDS_BLOCK_MASK); + rt_group_b = bdev->rds_info.radio_text[i+1] & + BCM2048_RDS_GROUP_AB_MASK; + rt_ab = bdev->rds_info.radio_text[i+2] & + BCM2048_RDS_RT_AB_MASK; + + if (rt_group_b != bdev->rds_info.rds_rt_group_b) { + memset(bdev->rds_info.rds_rt, 0, + sizeof(bdev->rds_info.rds_rt)); + bdev->rds_info.rds_rt_group_b = rt_group_b; + } + + if (rt_id == BCM2048_RDS_RT) { + /* A to B or (vice versa), means: clear screen */ + if (rt_ab != bdev->rds_info.rds_rt_ab) { + memset(bdev->rds_info.rds_rt, 0, + sizeof(bdev->rds_info.rds_rt)); + bdev->rds_info.rds_rt_ab = rt_ab; + } + + index = bdev->rds_info.radio_text[i+2] & + BCM2048_RDS_RT_INDEX; + + if (bdev->rds_info.rds_rt_group_b) + index <<= 1; + else + index <<= 2; + + return index; + } + } + + return -EIO; +} + +static int bcm2048_parse_rt_match_c(struct bcm2048_device *bdev, int i, + int index) +{ + int crc; + + crc = bcm2048_rds_block_crc(bdev, i); + + if (crc == BCM2048_RDS_CRC_UNRECOVARABLE) + return 0; + + BUG_ON((index+2) >= BCM2048_MAX_RDS_RT); + + if ((bdev->rds_info.radio_text[i] & BCM2048_RDS_BLOCK_MASK) == + BCM2048_RDS_BLOCK_C) { + if (bdev->rds_info.rds_rt_group_b) + return 1; + bcm2048_parse_rds_rt_block(bdev, i, index, crc); + return 1; + } + + return 0; +} + +static void bcm2048_parse_rt_match_d(struct bcm2048_device *bdev, int i, + int index) +{ + int crc; + + crc = bcm2048_rds_block_crc(bdev, i); + + if (crc == BCM2048_RDS_CRC_UNRECOVARABLE) + return; + + BUG_ON((index+4) >= BCM2048_MAX_RDS_RT); + + if ((bdev->rds_info.radio_text[i] & BCM2048_RDS_BLOCK_MASK) == + BCM2048_RDS_BLOCK_D) + bcm2048_parse_rds_rt_block(bdev, i, index+2, crc); +} + +static int bcm2048_parse_rds_rt(struct bcm2048_device *bdev) +{ + int i, index = 0, crc, match_b = 0, match_c = 0, match_d = 0; + + for (i = 0; i < bdev->fifo_size; i += BCM2048_RDS_FIFO_DUPLE_SIZE) { + + if (match_b) { + match_b = 0; + index = bcm2048_parse_rt_match_b(bdev, i); + if (index >= 0 && index <= (BCM2048_MAX_RDS_RT - 5)) + match_c = 1; + continue; + } else if (match_c) { + match_c = 0; + if (bcm2048_parse_rt_match_c(bdev, i, index)) + match_d = 1; + continue; + } else if (match_d) { + match_d = 0; + bcm2048_parse_rt_match_d(bdev, i, index); + continue; + } + + /* Skip erroneous blocks due to messed up A block altogether */ + if ((bdev->rds_info.radio_text[i] & BCM2048_RDS_BLOCK_MASK) + == BCM2048_RDS_BLOCK_A) { + crc = bcm2048_rds_block_crc(bdev, i); + if (crc == BCM2048_RDS_CRC_UNRECOVARABLE) + continue; + /* Syncronize to a good RDS PI */ + if (((bdev->rds_info.radio_text[i+1] << 8) + + bdev->rds_info.radio_text[i+2]) == + bdev->rds_info.rds_pi) + match_b = 1; + } + } + + return 0; +} + +static void bcm2048_parse_rds_ps_block(struct bcm2048_device *bdev, int i, + int index, int crc) +{ + /* Good data will overwrite poor data */ + if (crc) { + if (!bdev->rds_info.rds_ps[index]) + bdev->rds_info.rds_ps[index] = + bdev->rds_info.radio_text[i+1]; + if (!bdev->rds_info.rds_ps[index+1]) + bdev->rds_info.rds_ps[index+1] = + bdev->rds_info.radio_text[i+2]; + } else { + bdev->rds_info.rds_ps[index] = bdev->rds_info.radio_text[i+1]; + bdev->rds_info.rds_ps[index+1] = + bdev->rds_info.radio_text[i+2]; + } +} + +static int bcm2048_parse_ps_match_c(struct bcm2048_device *bdev, int i, + int index) +{ + int crc; + + crc = bcm2048_rds_block_crc(bdev, i); + + if (crc == BCM2048_RDS_CRC_UNRECOVARABLE) + return 0; + + if ((bdev->rds_info.radio_text[i] & BCM2048_RDS_BLOCK_MASK) == + BCM2048_RDS_BLOCK_C) + return 1; + + return 0; +} + +static void bcm2048_parse_ps_match_d(struct bcm2048_device *bdev, int i, + int index) +{ + int crc; + + crc = bcm2048_rds_block_crc(bdev, i); + + if (crc == BCM2048_RDS_CRC_UNRECOVARABLE) + return; + + if ((bdev->rds_info.radio_text[i] & BCM2048_RDS_BLOCK_MASK) == + BCM2048_RDS_BLOCK_D) + bcm2048_parse_rds_ps_block(bdev, i, index, crc); +} + +static int bcm2048_parse_ps_match_b(struct bcm2048_device *bdev, int i) +{ + int crc, index, ps_id, ps_group; + + crc = bcm2048_rds_block_crc(bdev, i); + + if (crc == BCM2048_RDS_CRC_UNRECOVARABLE) + return -EIO; + + /* Block B Radio PS match */ + if ((bdev->rds_info.radio_text[i] & BCM2048_RDS_BLOCK_MASK) == + BCM2048_RDS_BLOCK_B) { + ps_id = bdev->rds_info.radio_text[i+1] & + BCM2048_RDS_BLOCK_MASK; + ps_group = bdev->rds_info.radio_text[i+1] & + BCM2048_RDS_GROUP_AB_MASK; + + /* + * Poor RSSI will lead to RDS data corruption + * So using 3 (same) sequential values to justify major changes + */ + if (ps_group != bdev->rds_info.rds_ps_group) { + if (crc == BCM2048_RDS_CRC_NONE) { + bdev->rds_info.rds_ps_group_cnt++; + if (bdev->rds_info.rds_ps_group_cnt > 2) { + bdev->rds_info.rds_ps_group = ps_group; + bdev->rds_info.rds_ps_group_cnt = 0; + dev_err(&bdev->client->dev, + "RDS PS Group change!\n"); + } else { + return -EIO; + } + } else { + bdev->rds_info.rds_ps_group_cnt = 0; + } + } + + if (ps_id == BCM2048_RDS_PS) { + index = bdev->rds_info.radio_text[i+2] & + BCM2048_RDS_PS_INDEX; + index <<= 1; + return index; + } + } + + return -EIO; +} + +static void bcm2048_parse_rds_ps(struct bcm2048_device *bdev) +{ + int i, index = 0, crc, match_b = 0, match_c = 0, match_d = 0; + + for (i = 0; i < bdev->fifo_size; i += BCM2048_RDS_FIFO_DUPLE_SIZE) { + + if (match_b) { + match_b = 0; + index = bcm2048_parse_ps_match_b(bdev, i); + if (index >= 0 && index < (BCM2048_MAX_RDS_PS - 1)) + match_c = 1; + continue; + } else if (match_c) { + match_c = 0; + if (bcm2048_parse_ps_match_c(bdev, i, index)) + match_d = 1; + continue; + } else if (match_d) { + match_d = 0; + bcm2048_parse_ps_match_d(bdev, i, index); + continue; + } + + /* Skip erroneous blocks due to messed up A block altogether */ + if ((bdev->rds_info.radio_text[i] & BCM2048_RDS_BLOCK_MASK) + == BCM2048_RDS_BLOCK_A) { + crc = bcm2048_rds_block_crc(bdev, i); + if (crc == BCM2048_RDS_CRC_UNRECOVARABLE) + continue; + /* Syncronize to a good RDS PI */ + if (((bdev->rds_info.radio_text[i+1] << 8) + + bdev->rds_info.radio_text[i+2]) == + bdev->rds_info.rds_pi) + match_b = 1; + } + } +} + +static void bcm2048_rds_fifo_receive(struct bcm2048_device *bdev) +{ + int err; + + mutex_lock(&bdev->mutex); + + err = bcm2048_recv_duples(bdev, BCM2048_I2C_RDS_DATA, + bdev->rds_info.radio_text, bdev->fifo_size); + if (err != 2) { + dev_err(&bdev->client->dev, "RDS Read problem\n"); + mutex_unlock(&bdev->mutex); + return; + } + + bdev->rds_info.text_len = bdev->fifo_size; + + bcm2048_parse_rds_pi(bdev); + bcm2048_parse_rds_rt(bdev); + bcm2048_parse_rds_ps(bdev); + + mutex_unlock(&bdev->mutex); + + wake_up_interruptible(&bdev->read_queue); +} + +static int bcm2048_get_rds_data(struct bcm2048_device *bdev, char *data) +{ + int err = 0, i, p = 0; + char *data_buffer; + + mutex_lock(&bdev->mutex); + + if (!bdev->rds_info.text_len) { + err = -EINVAL; + goto unlock; + } + + data_buffer = kzalloc(BCM2048_MAX_RDS_RADIO_TEXT*5, GFP_KERNEL); + if (!data_buffer) { + err = -ENOMEM; + goto unlock; + } + + for (i = 0; i < bdev->rds_info.text_len; i++) { + p += sprintf(data_buffer+p, "%x ", + bdev->rds_info.radio_text[i]); + } + + memcpy(data, data_buffer, p); + kfree(data_buffer); + +unlock: + mutex_unlock(&bdev->mutex); + return err; +} + +/* + * BCM2048 default initialization sequence + */ +static int bcm2048_init(struct bcm2048_device *bdev) +{ + int err; + + err = bcm2048_set_power_state(bdev, BCM2048_POWER_ON); + if (err < 0) + goto exit; + + err = bcm2048_set_audio_route(bdev, BCM2048_AUDIO_ROUTE_DAC); + if (err < 0) + goto exit; + + err = bcm2048_set_dac_output(bdev, BCM2048_DAC_OUTPUT_LEFT | + BCM2048_DAC_OUTPUT_RIGHT); + +exit: + return err; +} + +/* + * BCM2048 default deinitialization sequence + */ +static int bcm2048_deinit(struct bcm2048_device *bdev) +{ + int err; + + err = bcm2048_set_audio_route(bdev, 0); + if (err < 0) + goto exit; + + err = bcm2048_set_dac_output(bdev, 0); + if (err < 0) + goto exit; + + err = bcm2048_set_power_state(bdev, BCM2048_POWER_OFF); + if (err < 0) + goto exit; + +exit: + return err; +} + +/* + * BCM2048 probe sequence + */ +static int bcm2048_probe(struct bcm2048_device *bdev) +{ + int err; + + err = bcm2048_set_power_state(bdev, BCM2048_POWER_ON); + if (err < 0) + goto unlock; + + err = bcm2048_checkrev(bdev); + if (err < 0) + goto unlock; + + err = bcm2048_set_mute(bdev, BCM2048_DEFAULT_MUTE); + if (err < 0) + goto unlock; + + err = bcm2048_set_region(bdev, BCM2048_DEFAULT_REGION); + if (err < 0) + goto unlock; + + err = bcm2048_set_fm_search_rssi_threshold(bdev, + BCM2048_DEFAULT_RSSI_THRESHOLD); + if (err < 0) + goto unlock; + + err = bcm2048_set_fm_automatic_stereo_mono(bdev, BCM2048_ITEM_ENABLED); + if (err < 0) + goto unlock; + + err = bcm2048_get_rds_wline(bdev); + if (err < BCM2048_DEFAULT_RDS_WLINE) + err = bcm2048_set_rds_wline(bdev, BCM2048_DEFAULT_RDS_WLINE); + if (err < 0) + goto unlock; + + err = bcm2048_set_power_state(bdev, BCM2048_POWER_OFF); + + init_waitqueue_head(&bdev->read_queue); + bdev->rds_data_available = 0; + bdev->rd_index = 0; + bdev->users = 0; + +unlock: + return err; +} + +/* + * BCM2048 workqueue handler + */ +static void bcm2048_work(struct work_struct *work) +{ + struct bcm2048_device *bdev; + u8 flag_lsb, flag_msb, flags; + + bdev = container_of(work, struct bcm2048_device, work); + bcm2048_recv_command(bdev, BCM2048_I2C_FM_RDS_FLAG0, &flag_lsb); + bcm2048_recv_command(bdev, BCM2048_I2C_FM_RDS_FLAG1, &flag_msb); + + if (flag_lsb & (BCM2048_FM_FLAG_SEARCH_TUNE_FINISHED | + BCM2048_FM_FLAG_SEARCH_TUNE_FAIL)) { + + if (flag_lsb & BCM2048_FM_FLAG_SEARCH_TUNE_FAIL) + bdev->scan_state = BCM2048_SCAN_FAIL; + else + bdev->scan_state = BCM2048_SCAN_OK; + + complete(&bdev->compl); + } + + if (flag_msb & BCM2048_RDS_FLAG_FIFO_WLINE) { + bcm2048_rds_fifo_receive(bdev); + if (bdev->rds_state) { + flags = BCM2048_RDS_FLAG_FIFO_WLINE; + bcm2048_send_command(bdev, BCM2048_I2C_FM_RDS_MASK1, + flags); + } + bdev->rds_data_available = 1; + bdev->rd_index = 0; /* new data, new start */ + } +} + +/* + * BCM2048 interrupt handler + */ +static irqreturn_t bcm2048_handler(int irq, void *dev) +{ + struct bcm2048_device *bdev = dev; + + dev_dbg(&bdev->client->dev, "IRQ called, queuing work\n"); + if (bdev->power_state) + schedule_work(&bdev->work); + + return IRQ_HANDLED; +} + +/* + * BCM2048 sysfs interface definitions + */ +#define property_write(prop, type, mask, check) \ +static ssize_t bcm2048_##prop##_write(struct device *dev, \ + struct device_attribute *attr, \ + const char *buf, \ + size_t count) \ +{ \ + struct bcm2048_device *bdev = dev_get_drvdata(dev); \ + type value; \ + int err; \ + \ + if (!bdev) \ + return -ENODEV; \ + \ + sscanf(buf, mask, &value); \ + \ + if (check) \ + return -EDOM; \ + \ + err = bcm2048_set_##prop(bdev, value); \ + \ + return err < 0 ? err : count; \ +} + +#define property_read(prop, size, mask) \ +static ssize_t bcm2048_##prop##_read(struct device *dev, \ + struct device_attribute *attr, \ + char *buf) \ +{ \ + struct bcm2048_device *bdev = dev_get_drvdata(dev); \ + int value; \ + \ + if (!bdev) \ + return -ENODEV; \ + \ + value = bcm2048_get_##prop(bdev); \ + \ + if (value >= 0) \ + value = sprintf(buf, mask "\n", value); \ + \ + return value; \ +} + +#define property_signed_read(prop, size, mask) \ +static ssize_t bcm2048_##prop##_read(struct device *dev, \ + struct device_attribute *attr, \ + char *buf) \ +{ \ + struct bcm2048_device *bdev = dev_get_drvdata(dev); \ + size value; \ + \ + if (!bdev) \ + return -ENODEV; \ + \ + value = bcm2048_get_##prop(bdev); \ + \ + value = sprintf(buf, mask "\n", value); \ + \ + return value; \ +} + +#define DEFINE_SYSFS_PROPERTY(prop, signal, size, mask, check) \ +property_write(prop, signal size, mask, check) \ +property_read(prop, size, mask) + +#define property_str_read(prop, size) \ +static ssize_t bcm2048_##prop##_read(struct device *dev, \ + struct device_attribute *attr, \ + char *buf) \ +{ \ + struct bcm2048_device *bdev = dev_get_drvdata(dev); \ + int count; \ + u8 *out; \ + \ + if (!bdev) \ + return -ENODEV; \ + \ + out = kzalloc(size + 1, GFP_KERNEL); \ + if (!out) \ + return -ENOMEM; \ + \ + bcm2048_get_##prop(bdev, out); \ + count = sprintf(buf, "%s\n", out); \ + \ + kfree(out); \ + \ + return count; \ +} + +DEFINE_SYSFS_PROPERTY(power_state, unsigned, int, "%u", 0) +DEFINE_SYSFS_PROPERTY(mute, unsigned, int, "%u", 0) +DEFINE_SYSFS_PROPERTY(audio_route, unsigned, int, "%u", 0) +DEFINE_SYSFS_PROPERTY(dac_output, unsigned, int, "%u", 0) + +DEFINE_SYSFS_PROPERTY(fm_hi_lo_injection, unsigned, int, "%u", 0) +DEFINE_SYSFS_PROPERTY(fm_frequency, unsigned, int, "%u", 0) +DEFINE_SYSFS_PROPERTY(fm_af_frequency, unsigned, int, "%u", 0) +DEFINE_SYSFS_PROPERTY(fm_deemphasis, unsigned, int, "%u", 0) +DEFINE_SYSFS_PROPERTY(fm_rds_mask, unsigned, int, "%u", 0) +DEFINE_SYSFS_PROPERTY(fm_best_tune_mode, unsigned, int, "%u", 0) +DEFINE_SYSFS_PROPERTY(fm_search_rssi_threshold, unsigned, int, "%u", 0) +DEFINE_SYSFS_PROPERTY(fm_search_mode_direction, unsigned, int, "%u", 0) +DEFINE_SYSFS_PROPERTY(fm_search_tune_mode, unsigned, int, "%u", value > 3) + +DEFINE_SYSFS_PROPERTY(rds, unsigned, int, "%u", 0) +DEFINE_SYSFS_PROPERTY(rds_b_block_mask, unsigned, int, "%u", 0) +DEFINE_SYSFS_PROPERTY(rds_b_block_match, unsigned, int, "%u", 0) +DEFINE_SYSFS_PROPERTY(rds_pi_mask, unsigned, int, "%u", 0) +DEFINE_SYSFS_PROPERTY(rds_pi_match, unsigned, int, "%u", 0) +DEFINE_SYSFS_PROPERTY(rds_wline, unsigned, int, "%u", 0) +property_read(rds_pi, unsigned int, "%x") +property_str_read(rds_rt, (BCM2048_MAX_RDS_RT + 1)) +property_str_read(rds_ps, (BCM2048_MAX_RDS_PS + 1)) + +property_read(fm_rds_flags, unsigned int, "%u") +property_str_read(rds_data, BCM2048_MAX_RDS_RADIO_TEXT*5) + +property_read(region_bottom_frequency, unsigned int, "%u") +property_read(region_top_frequency, unsigned int, "%u") +property_signed_read(fm_carrier_error, int, "%d") +property_signed_read(fm_rssi, int, "%d") +DEFINE_SYSFS_PROPERTY(region, unsigned, int, "%u", 0) + +static struct device_attribute attrs[] = { + __ATTR(power_state, S_IRUGO | S_IWUSR, bcm2048_power_state_read, + bcm2048_power_state_write), + __ATTR(mute, S_IRUGO | S_IWUSR, bcm2048_mute_read, + bcm2048_mute_write), + __ATTR(audio_route, S_IRUGO | S_IWUSR, bcm2048_audio_route_read, + bcm2048_audio_route_write), + __ATTR(dac_output, S_IRUGO | S_IWUSR, bcm2048_dac_output_read, + bcm2048_dac_output_write), + __ATTR(fm_hi_lo_injection, S_IRUGO | S_IWUSR, + bcm2048_fm_hi_lo_injection_read, + bcm2048_fm_hi_lo_injection_write), + __ATTR(fm_frequency, S_IRUGO | S_IWUSR, bcm2048_fm_frequency_read, + bcm2048_fm_frequency_write), + __ATTR(fm_af_frequency, S_IRUGO | S_IWUSR, + bcm2048_fm_af_frequency_read, + bcm2048_fm_af_frequency_write), + __ATTR(fm_deemphasis, S_IRUGO | S_IWUSR, bcm2048_fm_deemphasis_read, + bcm2048_fm_deemphasis_write), + __ATTR(fm_rds_mask, S_IRUGO | S_IWUSR, bcm2048_fm_rds_mask_read, + bcm2048_fm_rds_mask_write), + __ATTR(fm_best_tune_mode, S_IRUGO | S_IWUSR, + bcm2048_fm_best_tune_mode_read, + bcm2048_fm_best_tune_mode_write), + __ATTR(fm_search_rssi_threshold, S_IRUGO | S_IWUSR, + bcm2048_fm_search_rssi_threshold_read, + bcm2048_fm_search_rssi_threshold_write), + __ATTR(fm_search_mode_direction, S_IRUGO | S_IWUSR, + bcm2048_fm_search_mode_direction_read, + bcm2048_fm_search_mode_direction_write), + __ATTR(fm_search_tune_mode, S_IRUGO | S_IWUSR, + bcm2048_fm_search_tune_mode_read, + bcm2048_fm_search_tune_mode_write), + __ATTR(rds, S_IRUGO | S_IWUSR, bcm2048_rds_read, + bcm2048_rds_write), + __ATTR(rds_b_block_mask, S_IRUGO | S_IWUSR, + bcm2048_rds_b_block_mask_read, + bcm2048_rds_b_block_mask_write), + __ATTR(rds_b_block_match, S_IRUGO | S_IWUSR, + bcm2048_rds_b_block_match_read, + bcm2048_rds_b_block_match_write), + __ATTR(rds_pi_mask, S_IRUGO | S_IWUSR, bcm2048_rds_pi_mask_read, + bcm2048_rds_pi_mask_write), + __ATTR(rds_pi_match, S_IRUGO | S_IWUSR, bcm2048_rds_pi_match_read, + bcm2048_rds_pi_match_write), + __ATTR(rds_wline, S_IRUGO | S_IWUSR, bcm2048_rds_wline_read, + bcm2048_rds_wline_write), + __ATTR(rds_pi, S_IRUGO, bcm2048_rds_pi_read, NULL), + __ATTR(rds_rt, S_IRUGO, bcm2048_rds_rt_read, NULL), + __ATTR(rds_ps, S_IRUGO, bcm2048_rds_ps_read, NULL), + __ATTR(fm_rds_flags, S_IRUGO, bcm2048_fm_rds_flags_read, NULL), + __ATTR(region_bottom_frequency, S_IRUGO, + bcm2048_region_bottom_frequency_read, NULL), + __ATTR(region_top_frequency, S_IRUGO, + bcm2048_region_top_frequency_read, NULL), + __ATTR(fm_carrier_error, S_IRUGO, + bcm2048_fm_carrier_error_read, NULL), + __ATTR(fm_rssi, S_IRUGO, + bcm2048_fm_rssi_read, NULL), + __ATTR(region, S_IRUGO | S_IWUSR, bcm2048_region_read, + bcm2048_region_write), + __ATTR(rds_data, S_IRUGO, bcm2048_rds_data_read, NULL), +}; + +static int bcm2048_sysfs_unregister_properties(struct bcm2048_device *bdev, + int size) +{ + int i; + + for (i = 0; i < size; i++) + device_remove_file(&bdev->client->dev, &attrs[i]); + + return 0; +} + +static int bcm2048_sysfs_register_properties(struct bcm2048_device *bdev) +{ + int err = 0; + int i; + + for (i = 0; i < ARRAY_SIZE(attrs); i++) { + if (device_create_file(&bdev->client->dev, &attrs[i]) != 0) { + dev_err(&bdev->client->dev, + "could not register sysfs entry\n"); + err = -EBUSY; + bcm2048_sysfs_unregister_properties(bdev, i); + break; + } + } + + return err; +} + + +static int bcm2048_fops_open(struct file *file) +{ + struct bcm2048_device *bdev = video_drvdata(file); + + bdev->users++; + bdev->rd_index = 0; + bdev->rds_data_available = 0; + + return 0; +} + +static int bcm2048_fops_release(struct file *file) +{ + struct bcm2048_device *bdev = video_drvdata(file); + + bdev->users--; + + return 0; +} + +static unsigned int bcm2048_fops_poll(struct file *file, + struct poll_table_struct *pts) +{ + struct bcm2048_device *bdev = video_drvdata(file); + int retval = 0; + + poll_wait(file, &bdev->read_queue, pts); + + if (bdev->rds_data_available) + retval = POLLIN | POLLRDNORM; + + return retval; +} + +static ssize_t bcm2048_fops_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + struct bcm2048_device *bdev = video_drvdata(file); + int i; + int retval = 0; + + /* we return at least 3 bytes, one block */ + count = (count / 3) * 3; /* only multiples of 3 */ + if (count < 3) + return -ENOBUFS; + + while (!bdev->rds_data_available) { + if (file->f_flags & O_NONBLOCK) { + retval = -EWOULDBLOCK; + goto done; + } + /* interruptible_sleep_on(&bdev->read_queue); */ + if (wait_event_interruptible(bdev->read_queue, + bdev->rds_data_available) < 0) { + retval = -EINTR; + goto done; + } + } + + mutex_lock(&bdev->mutex); + /* copy data to userspace */ + i = bdev->fifo_size - bdev->rd_index; + if (count > i) + count = (i / 3) * 3; + + i = 0; + while (i < count) { + unsigned char tmpbuf[3]; + tmpbuf[i] = bdev->rds_info.radio_text[bdev->rd_index+i+2]; + tmpbuf[i+1] = bdev->rds_info.radio_text[bdev->rd_index+i+1]; + tmpbuf[i+2] = ((bdev->rds_info.radio_text[bdev->rd_index+i] + & 0xf0) >> 4); + if ((bdev->rds_info.radio_text[bdev->rd_index+i] & + BCM2048_RDS_CRC_MASK) == BCM2048_RDS_CRC_UNRECOVARABLE) + tmpbuf[i+2] |= 0x80; + if (copy_to_user(buf+i, tmpbuf, 3)) { + retval = -EFAULT; + break; + } + i += 3; + } + + bdev->rd_index += i; + if (bdev->rd_index >= bdev->fifo_size) + bdev->rds_data_available = 0; + + mutex_unlock(&bdev->mutex); + if (retval == 0) + retval = i; + +done: + return retval; +} + +/* + * bcm2048_fops - file operations interface + */ +static const struct v4l2_file_operations bcm2048_fops = { + .owner = THIS_MODULE, + .ioctl = video_ioctl2, + /* for RDS read support */ + .open = bcm2048_fops_open, + .release = bcm2048_fops_release, + .read = bcm2048_fops_read, + .poll = bcm2048_fops_poll +}; + +/* + * Video4Linux Interface + */ +static struct v4l2_queryctrl bcm2048_v4l2_queryctrl[] = { + { + .id = V4L2_CID_AUDIO_VOLUME, + .flags = V4L2_CTRL_FLAG_DISABLED, + }, + { + .id = V4L2_CID_AUDIO_BALANCE, + .flags = V4L2_CTRL_FLAG_DISABLED, + }, + { + .id = V4L2_CID_AUDIO_BASS, + .flags = V4L2_CTRL_FLAG_DISABLED, + }, + { + .id = V4L2_CID_AUDIO_TREBLE, + .flags = V4L2_CTRL_FLAG_DISABLED, + }, + { + .id = V4L2_CID_AUDIO_MUTE, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "Mute", + .minimum = 0, + .maximum = 1, + .step = 1, + .default_value = 1, + }, + { + .id = V4L2_CID_AUDIO_LOUDNESS, + .flags = V4L2_CTRL_FLAG_DISABLED, + }, +}; + +static int bcm2048_vidioc_querycap(struct file *file, void *priv, + struct v4l2_capability *capability) +{ + struct bcm2048_device *bdev = video_get_drvdata(video_devdata(file)); + + strlcpy(capability->driver, BCM2048_DRIVER_NAME, + sizeof(capability->driver)); + strlcpy(capability->card, BCM2048_DRIVER_CARD, + sizeof(capability->card)); + snprintf(capability->bus_info, 32, "I2C: 0x%X", bdev->client->addr); + capability->version = BCM2048_DRIVER_VERSION; + capability->capabilities = V4L2_CAP_TUNER | V4L2_CAP_RADIO | + V4L2_CAP_HW_FREQ_SEEK; + + return 0; +} + +static int bcm2048_vidioc_g_input(struct file *filp, void *priv, + unsigned int *i) +{ + *i = 0; + + return 0; +} + +static int bcm2048_vidioc_s_input(struct file *filp, void *priv, + unsigned int i) +{ + if (i) + return -EINVAL; + + return 0; +} + +static int bcm2048_vidioc_queryctrl(struct file *file, void *priv, + struct v4l2_queryctrl *qc) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(bcm2048_v4l2_queryctrl); i++) { + if (qc->id && qc->id == bcm2048_v4l2_queryctrl[i].id) { + *qc = bcm2048_v4l2_queryctrl[i]; + return 0; + } + } + + return -EINVAL; +} + +static int bcm2048_vidioc_g_ctrl(struct file *file, void *priv, + struct v4l2_control *ctrl) +{ + struct bcm2048_device *bdev = video_get_drvdata(video_devdata(file)); + int err = 0; + + if (!bdev) + return -ENODEV; + + switch (ctrl->id) { + case V4L2_CID_AUDIO_MUTE: + err = bcm2048_get_mute(bdev); + if (err >= 0) + ctrl->value = err; + break; + } + + return err; +} + +static int bcm2048_vidioc_s_ctrl(struct file *file, void *priv, + struct v4l2_control *ctrl) +{ + struct bcm2048_device *bdev = video_get_drvdata(video_devdata(file)); + int err = 0; + + if (!bdev) + return -ENODEV; + + switch (ctrl->id) { + case V4L2_CID_AUDIO_MUTE: + if (ctrl->value) { + if (bdev->power_state) { + err = bcm2048_set_mute(bdev, ctrl->value); + err |= bcm2048_deinit(bdev); + } + } else { + if (!bdev->power_state) { + err = bcm2048_init(bdev); + err |= bcm2048_set_mute(bdev, ctrl->value); + } + } + break; + } + + return err; +} + +static int bcm2048_vidioc_g_audio(struct file *file, void *priv, + struct v4l2_audio *audio) +{ + if (audio->index > 1) + return -EINVAL; + + strncpy(audio->name, "Radio", 32); + audio->capability = V4L2_AUDCAP_STEREO; + + return 0; +} + +static int bcm2048_vidioc_s_audio(struct file *file, void *priv, + const struct v4l2_audio *audio) +{ + if (audio->index != 0) + return -EINVAL; + + return 0; +} + +static int bcm2048_vidioc_g_tuner(struct file *file, void *priv, + struct v4l2_tuner *tuner) +{ + struct bcm2048_device *bdev = video_get_drvdata(video_devdata(file)); + s8 f_error; + s8 rssi; + + if (!bdev) + return -ENODEV; + + if (tuner->index > 0) + return -EINVAL; + + strncpy(tuner->name, "FM Receiver", 32); + tuner->type = V4L2_TUNER_RADIO; + tuner->rangelow = + dev_to_v4l2(bcm2048_get_region_bottom_frequency(bdev)); + tuner->rangehigh = + dev_to_v4l2(bcm2048_get_region_top_frequency(bdev)); + tuner->rxsubchans = V4L2_TUNER_SUB_STEREO; + tuner->capability = V4L2_TUNER_CAP_STEREO | V4L2_TUNER_CAP_LOW; + tuner->audmode = V4L2_TUNER_MODE_STEREO; + tuner->afc = 0; + if (bdev->power_state) { + /* + * Report frequencies with high carrier errors to have zero + * signal level + */ + f_error = bcm2048_get_fm_carrier_error(bdev); + if (f_error < BCM2048_FREQ_ERROR_FLOOR || + f_error > BCM2048_FREQ_ERROR_ROOF) { + tuner->signal = 0; + } else { + /* + * RSSI level -60 dB is defined to report full + * signal strenght + */ + rssi = bcm2048_get_fm_rssi(bdev); + if (rssi >= BCM2048_RSSI_LEVEL_BASE) { + tuner->signal = 0xFFFF; + } else if (rssi > BCM2048_RSSI_LEVEL_ROOF) { + tuner->signal = (rssi + + BCM2048_RSSI_LEVEL_ROOF_NEG) + * BCM2048_SIGNAL_MULTIPLIER; + } else { + tuner->signal = 0; + } + } + } else { + tuner->signal = 0; + } + + return 0; +} + +static int bcm2048_vidioc_s_tuner(struct file *file, void *priv, + const struct v4l2_tuner *tuner) +{ + struct bcm2048_device *bdev = video_get_drvdata(video_devdata(file)); + + if (!bdev) + return -ENODEV; + + if (tuner->index > 0) + return -EINVAL; + + return 0; +} + +static int bcm2048_vidioc_g_frequency(struct file *file, void *priv, + struct v4l2_frequency *freq) +{ + struct bcm2048_device *bdev = video_get_drvdata(video_devdata(file)); + int err = 0; + int f; + + if (!bdev->power_state) + return -ENODEV; + + freq->type = V4L2_TUNER_RADIO; + f = bcm2048_get_fm_frequency(bdev); + + if (f < 0) + err = f; + else + freq->frequency = dev_to_v4l2(f); + + return err; +} + +static int bcm2048_vidioc_s_frequency(struct file *file, void *priv, + const struct v4l2_frequency *freq) +{ + struct bcm2048_device *bdev = video_get_drvdata(video_devdata(file)); + int err; + + if (freq->type != V4L2_TUNER_RADIO) + return -EINVAL; + + if (!bdev->power_state) + return -ENODEV; + + err = bcm2048_set_fm_frequency(bdev, v4l2_to_dev(freq->frequency)); + err |= bcm2048_set_fm_search_tune_mode(bdev, BCM2048_FM_PRE_SET_MODE); + + return err; +} + +static int bcm2048_vidioc_s_hw_freq_seek(struct file *file, void *priv, + const struct v4l2_hw_freq_seek *seek) +{ + struct bcm2048_device *bdev = video_get_drvdata(video_devdata(file)); + int err; + + if (!bdev->power_state) + return -ENODEV; + + if ((seek->tuner != 0) || (seek->type != V4L2_TUNER_RADIO)) + return -EINVAL; + + err = bcm2048_set_fm_search_mode_direction(bdev, seek->seek_upward); + err |= bcm2048_set_fm_search_tune_mode(bdev, + BCM2048_FM_AUTO_SEARCH_MODE); + + return err; +} + +static struct v4l2_ioctl_ops bcm2048_ioctl_ops = { + .vidioc_querycap = bcm2048_vidioc_querycap, + .vidioc_g_input = bcm2048_vidioc_g_input, + .vidioc_s_input = bcm2048_vidioc_s_input, + .vidioc_queryctrl = bcm2048_vidioc_queryctrl, + .vidioc_g_ctrl = bcm2048_vidioc_g_ctrl, + .vidioc_s_ctrl = bcm2048_vidioc_s_ctrl, + .vidioc_g_audio = bcm2048_vidioc_g_audio, + .vidioc_s_audio = bcm2048_vidioc_s_audio, + .vidioc_g_tuner = bcm2048_vidioc_g_tuner, + .vidioc_s_tuner = bcm2048_vidioc_s_tuner, + .vidioc_g_frequency = bcm2048_vidioc_g_frequency, + .vidioc_s_frequency = bcm2048_vidioc_s_frequency, + .vidioc_s_hw_freq_seek = bcm2048_vidioc_s_hw_freq_seek, +}; + +/* + * bcm2048_viddev_template - video device interface + */ +static struct video_device bcm2048_viddev_template = { + .fops = &bcm2048_fops, + .name = BCM2048_DRIVER_NAME, + .release = video_device_release, + .ioctl_ops = &bcm2048_ioctl_ops, +}; + +/* + * I2C driver interface + */ +static int bcm2048_i2c_driver_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct bcm2048_device *bdev; + int err, skip_release = 0; + + bdev = kzalloc(sizeof(*bdev), GFP_KERNEL); + if (!bdev) { + dev_dbg(&client->dev, "Failed to alloc video device.\n"); + err = -ENOMEM; + goto exit; + } + + bdev->videodev = video_device_alloc(); + if (!bdev->videodev) { + dev_dbg(&client->dev, "Failed to alloc video device.\n"); + err = -ENOMEM; + goto free_bdev; + } + + bdev->client = client; + i2c_set_clientdata(client, bdev); + mutex_init(&bdev->mutex); + init_completion(&bdev->compl); + INIT_WORK(&bdev->work, bcm2048_work); + + if (client->irq) { + err = request_irq(client->irq, + bcm2048_handler, IRQF_TRIGGER_FALLING | IRQF_DISABLED, + client->name, bdev); + if (err < 0) { + dev_err(&client->dev, "Could not request IRQ\n"); + goto free_vdev; + } + dev_dbg(&client->dev, "IRQ requested.\n"); + } else { + dev_dbg(&client->dev, "IRQ not configured. Using timeouts.\n"); + } + + *bdev->videodev = bcm2048_viddev_template; + video_set_drvdata(bdev->videodev, bdev); + if (video_register_device(bdev->videodev, VFL_TYPE_RADIO, radio_nr)) { + dev_dbg(&client->dev, "Could not register video device.\n"); + err = -EIO; + goto free_irq; + } + + err = bcm2048_sysfs_register_properties(bdev); + if (err < 0) { + dev_dbg(&client->dev, "Could not register sysfs interface.\n"); + goto free_registration; + } + + err = bcm2048_probe(bdev); + if (err < 0) { + dev_dbg(&client->dev, "Failed to probe device information.\n"); + goto free_sysfs; + } + + return 0; + +free_sysfs: + bcm2048_sysfs_unregister_properties(bdev, ARRAY_SIZE(attrs)); +free_registration: + video_unregister_device(bdev->videodev); + /* video_unregister_device frees bdev->videodev */ + bdev->videodev = NULL; + skip_release = 1; +free_irq: + if (client->irq) + free_irq(client->irq, bdev); +free_vdev: + if (!skip_release) + video_device_release(bdev->videodev); + i2c_set_clientdata(client, NULL); +free_bdev: + kfree(bdev); +exit: + return err; +} + +static int __exit bcm2048_i2c_driver_remove(struct i2c_client *client) +{ + struct bcm2048_device *bdev = i2c_get_clientdata(client); + struct video_device *vd; + + if (!client->adapter) + return -ENODEV; + + if (bdev) { + vd = bdev->videodev; + + bcm2048_sysfs_unregister_properties(bdev, ARRAY_SIZE(attrs)); + + if (vd) + video_unregister_device(vd); + + if (bdev->power_state) + bcm2048_set_power_state(bdev, BCM2048_POWER_OFF); + + if (client->irq > 0) + free_irq(client->irq, bdev); + + cancel_work_sync(&bdev->work); + + kfree(bdev); + } + + i2c_set_clientdata(client, NULL); + + return 0; +} + +/* + * bcm2048_i2c_driver - i2c driver interface + */ +static const struct i2c_device_id bcm2048_id[] = { + { "bcm2048" , 0 }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, bcm2048_id); + +static struct i2c_driver bcm2048_i2c_driver = { + .driver = { + .name = BCM2048_DRIVER_NAME, + }, + .probe = bcm2048_i2c_driver_probe, + .remove = __exit_p(bcm2048_i2c_driver_remove), + .id_table = bcm2048_id, +}; + +/* + * Module Interface + */ +static int __init bcm2048_module_init(void) +{ + pr_info(BCM2048_DRIVER_DESC "\n"); + + return i2c_add_driver(&bcm2048_i2c_driver); +} +module_init(bcm2048_module_init); + +static void __exit bcm2048_module_exit(void) +{ + i2c_del_driver(&bcm2048_i2c_driver); +} +module_exit(bcm2048_module_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR(BCM2048_DRIVER_AUTHOR); +MODULE_DESCRIPTION(BCM2048_DRIVER_DESC); +MODULE_VERSION("0.0.2"); diff --git a/drivers/staging/media/bcm2048/radio-bcm2048.h b/drivers/staging/media/bcm2048/radio-bcm2048.h new file mode 100644 index 00000000000..4c90a32db79 --- /dev/null +++ b/drivers/staging/media/bcm2048/radio-bcm2048.h @@ -0,0 +1,30 @@ +/* + * drivers/staging/media/radio-bcm2048.h + * + * Property and command definitions for bcm2048 radio receiver chip. + * + * Copyright (C) Nokia Corporation + * Contact: Eero Nurkkala <ext-eero.nurkkala@nokia.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#ifndef BCM2048_H +#define BCM2048_H + +#define BCM2048_NAME "bcm2048" +#define BCM2048_I2C_ADDR 0x22 + +#endif /* ifndef BCM2048_H */ diff --git a/drivers/staging/media/cxd2099/Kconfig b/drivers/staging/media/cxd2099/Kconfig new file mode 100644 index 00000000000..b48aefddc84 --- /dev/null +++ b/drivers/staging/media/cxd2099/Kconfig @@ -0,0 +1,12 @@ +config DVB_CXD2099 + tristate "CXD2099AR Common Interface driver" + depends on DVB_CORE && PCI && I2C + ---help--- + Support for the CI module found on cards based on + - Micronas ngene PCIe bridge: cineS2 etc. + - Digital Devices PCIe bridge: Octopus series + + For now, data is passed through '/dev/dvb/adapterX/sec0': + - Encrypted data must be written to 'sec0'. + - Decrypted data can be read from 'sec0'. + - Setup the CAM using device 'ca0'. diff --git a/drivers/staging/media/cxd2099/Makefile b/drivers/staging/media/cxd2099/Makefile new file mode 100644 index 00000000000..b2905e65057 --- /dev/null +++ b/drivers/staging/media/cxd2099/Makefile @@ -0,0 +1,5 @@ +obj-$(CONFIG_DVB_CXD2099) += cxd2099.o + +ccflags-y += -Idrivers/media/dvb-core/ +ccflags-y += -Idrivers/media/dvb-frontends/ +ccflags-y += -Idrivers/media/tuners/ diff --git a/drivers/staging/media/cxd2099/TODO b/drivers/staging/media/cxd2099/TODO new file mode 100644 index 00000000000..375bb6f8ee2 --- /dev/null +++ b/drivers/staging/media/cxd2099/TODO @@ -0,0 +1,12 @@ +For now, data is passed through '/dev/dvb/adapterX/sec0': + - Encrypted data must be written to 'sec0'. + - Decrypted data can be read from 'sec0'. + - Setup the CAM using device 'ca0'. + +But this is wrong. There are some discussions about the proper way for +doing it, as seen at: + http://www.mail-archive.com/linux-media@vger.kernel.org/msg22196.html + +While there's no proper fix for it, the driver should be kept in staging. + +Patches should be submitted to: linux-media@vger.kernel.org. diff --git a/drivers/staging/media/cxd2099/cxd2099.c b/drivers/staging/media/cxd2099/cxd2099.c new file mode 100644 index 00000000000..a2a5182570c --- /dev/null +++ b/drivers/staging/media/cxd2099/cxd2099.c @@ -0,0 +1,719 @@ +/* + * cxd2099.c: Driver for the CXD2099AR Common Interface Controller + * + * Copyright (C) 2010-2011 Digital Devices GmbH + * + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 only, as published by the Free Software Foundation. + * + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA + * Or, point your browser to http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/slab.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/i2c.h> +#include <linux/wait.h> +#include <linux/delay.h> +#include <linux/mutex.h> +#include <linux/io.h> + +#include "cxd2099.h" + +#define MAX_BUFFER_SIZE 248 + +struct cxd { + struct dvb_ca_en50221 en; + + struct i2c_adapter *i2c; + struct cxd2099_cfg cfg; + + u8 regs[0x23]; + u8 lastaddress; + u8 clk_reg_f; + u8 clk_reg_b; + int mode; + int ready; + int dr; + int slot_stat; + + u8 amem[1024]; + int amem_read; + + int cammode; + struct mutex lock; +}; + +static int i2c_write_reg(struct i2c_adapter *adapter, u8 adr, + u8 reg, u8 data) +{ + u8 m[2] = {reg, data}; + struct i2c_msg msg = {.addr = adr, .flags = 0, .buf = m, .len = 2}; + + if (i2c_transfer(adapter, &msg, 1) != 1) { + dev_err(&adapter->dev, + "Failed to write to I2C register %02x@%02x!\n", + reg, adr); + return -1; + } + return 0; +} + +static int i2c_write(struct i2c_adapter *adapter, u8 adr, + u8 *data, u8 len) +{ + struct i2c_msg msg = {.addr = adr, .flags = 0, .buf = data, .len = len}; + + if (i2c_transfer(adapter, &msg, 1) != 1) { + dev_err(&adapter->dev, "Failed to write to I2C!\n"); + return -1; + } + return 0; +} + +static int i2c_read_reg(struct i2c_adapter *adapter, u8 adr, + u8 reg, u8 *val) +{ + struct i2c_msg msgs[2] = {{.addr = adr, .flags = 0, + .buf = ®, .len = 1}, + {.addr = adr, .flags = I2C_M_RD, + .buf = val, .len = 1} }; + + if (i2c_transfer(adapter, msgs, 2) != 2) { + dev_err(&adapter->dev, "error in i2c_read_reg\n"); + return -1; + } + return 0; +} + +static int i2c_read(struct i2c_adapter *adapter, u8 adr, + u8 reg, u8 *data, u8 n) +{ + struct i2c_msg msgs[2] = {{.addr = adr, .flags = 0, + .buf = ®, .len = 1}, + {.addr = adr, .flags = I2C_M_RD, + .buf = data, .len = n} }; + + if (i2c_transfer(adapter, msgs, 2) != 2) { + dev_err(&adapter->dev, "error in i2c_read\n"); + return -1; + } + return 0; +} + +static int read_block(struct cxd *ci, u8 adr, u8 *data, u8 n) +{ + int status; + + status = i2c_write_reg(ci->i2c, ci->cfg.adr, 0, adr); + if (!status) { + ci->lastaddress = adr; + status = i2c_read(ci->i2c, ci->cfg.adr, 1, data, n); + } + return status; +} + +static int read_reg(struct cxd *ci, u8 reg, u8 *val) +{ + return read_block(ci, reg, val, 1); +} + + +static int read_pccard(struct cxd *ci, u16 address, u8 *data, u8 n) +{ + int status; + u8 addr[3] = {2, address & 0xff, address >> 8}; + + status = i2c_write(ci->i2c, ci->cfg.adr, addr, 3); + if (!status) + status = i2c_read(ci->i2c, ci->cfg.adr, 3, data, n); + return status; +} + +static int write_pccard(struct cxd *ci, u16 address, u8 *data, u8 n) +{ + int status; + u8 addr[3] = {2, address & 0xff, address >> 8}; + + status = i2c_write(ci->i2c, ci->cfg.adr, addr, 3); + if (!status) { + u8 buf[256] = {3}; + memcpy(buf+1, data, n); + status = i2c_write(ci->i2c, ci->cfg.adr, buf, n+1); + } + return status; +} + +static int read_io(struct cxd *ci, u16 address, u8 *val) +{ + int status; + u8 addr[3] = {2, address & 0xff, address >> 8}; + + status = i2c_write(ci->i2c, ci->cfg.adr, addr, 3); + if (!status) + status = i2c_read(ci->i2c, ci->cfg.adr, 3, val, 1); + return status; +} + +static int write_io(struct cxd *ci, u16 address, u8 val) +{ + int status; + u8 addr[3] = {2, address & 0xff, address >> 8}; + u8 buf[2] = {3, val}; + + status = i2c_write(ci->i2c, ci->cfg.adr, addr, 3); + if (!status) + status = i2c_write(ci->i2c, ci->cfg.adr, buf, 2); + return status; +} + +#if 0 +static int read_io_data(struct cxd *ci, u8 *data, u8 n) +{ + int status; + u8 addr[3] = { 2, 0, 0 }; + + status = i2c_write(ci->i2c, ci->cfg.adr, addr, 3); + if (!status) + status = i2c_read(ci->i2c, ci->cfg.adr, 3, data, n); + return 0; +} + +static int write_io_data(struct cxd *ci, u8 *data, u8 n) +{ + int status; + u8 addr[3] = {2, 0, 0}; + + status = i2c_write(ci->i2c, ci->cfg.adr, addr, 3); + if (!status) { + u8 buf[256] = {3}; + memcpy(buf+1, data, n); + status = i2c_write(ci->i2c, ci->cfg.adr, buf, n + 1); + } + return 0; +} +#endif + +static int write_regm(struct cxd *ci, u8 reg, u8 val, u8 mask) +{ + int status; + + status = i2c_write_reg(ci->i2c, ci->cfg.adr, 0, reg); + if (!status && reg >= 6 && reg <= 8 && mask != 0xff) + status = i2c_read_reg(ci->i2c, ci->cfg.adr, 1, &ci->regs[reg]); + ci->regs[reg] = (ci->regs[reg] & (~mask)) | val; + if (!status) { + ci->lastaddress = reg; + status = i2c_write_reg(ci->i2c, ci->cfg.adr, 1, ci->regs[reg]); + } + if (reg == 0x20) + ci->regs[reg] &= 0x7f; + return status; +} + +static int write_reg(struct cxd *ci, u8 reg, u8 val) +{ + return write_regm(ci, reg, val, 0xff); +} + +#ifdef BUFFER_MODE +static int write_block(struct cxd *ci, u8 adr, u8 *data, int n) +{ + int status; + u8 buf[256] = {1}; + + status = i2c_write_reg(ci->i2c, ci->cfg.adr, 0, adr); + if (!status) { + ci->lastaddress = adr; + memcpy(buf + 1, data, n); + status = i2c_write(ci->i2c, ci->cfg.adr, buf, n + 1); + } + return status; +} +#endif + +static void set_mode(struct cxd *ci, int mode) +{ + if (mode == ci->mode) + return; + + switch (mode) { + case 0x00: /* IO mem */ + write_regm(ci, 0x06, 0x00, 0x07); + break; + case 0x01: /* ATT mem */ + write_regm(ci, 0x06, 0x02, 0x07); + break; + default: + break; + } + ci->mode = mode; +} + +static void cam_mode(struct cxd *ci, int mode) +{ + if (mode == ci->cammode) + return; + + switch (mode) { + case 0x00: + write_regm(ci, 0x20, 0x80, 0x80); + break; + case 0x01: +#ifdef BUFFER_MODE + if (!ci->en.read_data) + return; + dev_info(&ci->i2c->dev, "enable cam buffer mode\n"); + /* write_reg(ci, 0x0d, 0x00); */ + /* write_reg(ci, 0x0e, 0x01); */ + write_regm(ci, 0x08, 0x40, 0x40); + /* read_reg(ci, 0x12, &dummy); */ + write_regm(ci, 0x08, 0x80, 0x80); +#endif + break; + default: + break; + } + ci->cammode = mode; +} + + + +static int init(struct cxd *ci) +{ + int status; + + mutex_lock(&ci->lock); + ci->mode = -1; + do { + status = write_reg(ci, 0x00, 0x00); + if (status < 0) + break; + status = write_reg(ci, 0x01, 0x00); + if (status < 0) + break; + status = write_reg(ci, 0x02, 0x10); + if (status < 0) + break; + status = write_reg(ci, 0x03, 0x00); + if (status < 0) + break; + status = write_reg(ci, 0x05, 0xFF); + if (status < 0) + break; + status = write_reg(ci, 0x06, 0x1F); + if (status < 0) + break; + status = write_reg(ci, 0x07, 0x1F); + if (status < 0) + break; + status = write_reg(ci, 0x08, 0x28); + if (status < 0) + break; + status = write_reg(ci, 0x14, 0x20); + if (status < 0) + break; + +#if 0 + /* Input Mode C, BYPass Serial, TIVAL = low, MSB */ + status = write_reg(ci, 0x09, 0x4D); + if (status < 0) + break; +#endif + /* TOSTRT = 8, Mode B (gated clock), falling Edge, + * Serial, POL=HIGH, MSB */ + status = write_reg(ci, 0x0A, 0xA7); + if (status < 0) + break; + + status = write_reg(ci, 0x0B, 0x33); + if (status < 0) + break; + status = write_reg(ci, 0x0C, 0x33); + if (status < 0) + break; + + status = write_regm(ci, 0x14, 0x00, 0x0F); + if (status < 0) + break; + status = write_reg(ci, 0x15, ci->clk_reg_b); + if (status < 0) + break; + status = write_regm(ci, 0x16, 0x00, 0x0F); + if (status < 0) + break; + status = write_reg(ci, 0x17, ci->clk_reg_f); + if (status < 0) + break; + + if (ci->cfg.clock_mode) { + if (ci->cfg.polarity) { + status = write_reg(ci, 0x09, 0x6f); + if (status < 0) + break; + } else { + status = write_reg(ci, 0x09, 0x6d); + if (status < 0) + break; + } + status = write_reg(ci, 0x20, 0x68); + if (status < 0) + break; + status = write_reg(ci, 0x21, 0x00); + if (status < 0) + break; + status = write_reg(ci, 0x22, 0x02); + if (status < 0) + break; + } else { + if (ci->cfg.polarity) { + status = write_reg(ci, 0x09, 0x4f); + if (status < 0) + break; + } else { + status = write_reg(ci, 0x09, 0x4d); + if (status < 0) + break; + } + + status = write_reg(ci, 0x20, 0x28); + if (status < 0) + break; + status = write_reg(ci, 0x21, 0x00); + if (status < 0) + break; + status = write_reg(ci, 0x22, 0x07); + if (status < 0) + break; + } + + status = write_regm(ci, 0x20, 0x80, 0x80); + if (status < 0) + break; + status = write_regm(ci, 0x03, 0x02, 0x02); + if (status < 0) + break; + status = write_reg(ci, 0x01, 0x04); + if (status < 0) + break; + status = write_reg(ci, 0x00, 0x31); + if (status < 0) + break; + + /* Put TS in bypass */ + status = write_regm(ci, 0x09, 0x08, 0x08); + if (status < 0) + break; + ci->cammode = -1; + cam_mode(ci, 0); + } while (0); + mutex_unlock(&ci->lock); + + return 0; +} + +static int read_attribute_mem(struct dvb_ca_en50221 *ca, + int slot, int address) +{ + struct cxd *ci = ca->data; +#if 0 + if (ci->amem_read) { + if (address <= 0 || address > 1024) + return -EIO; + return ci->amem[address]; + } + + mutex_lock(&ci->lock); + write_regm(ci, 0x06, 0x00, 0x05); + read_pccard(ci, 0, &ci->amem[0], 128); + read_pccard(ci, 128, &ci->amem[0], 128); + read_pccard(ci, 256, &ci->amem[0], 128); + read_pccard(ci, 384, &ci->amem[0], 128); + write_regm(ci, 0x06, 0x05, 0x05); + mutex_unlock(&ci->lock); + return ci->amem[address]; +#else + u8 val; + mutex_lock(&ci->lock); + set_mode(ci, 1); + read_pccard(ci, address, &val, 1); + mutex_unlock(&ci->lock); + /* printk(KERN_INFO "%02x:%02x\n", address,val); */ + return val; +#endif +} + +static int write_attribute_mem(struct dvb_ca_en50221 *ca, int slot, + int address, u8 value) +{ + struct cxd *ci = ca->data; + + mutex_lock(&ci->lock); + set_mode(ci, 1); + write_pccard(ci, address, &value, 1); + mutex_unlock(&ci->lock); + return 0; +} + +static int read_cam_control(struct dvb_ca_en50221 *ca, + int slot, u8 address) +{ + struct cxd *ci = ca->data; + u8 val; + + mutex_lock(&ci->lock); + set_mode(ci, 0); + read_io(ci, address, &val); + mutex_unlock(&ci->lock); + return val; +} + +static int write_cam_control(struct dvb_ca_en50221 *ca, int slot, + u8 address, u8 value) +{ + struct cxd *ci = ca->data; + + mutex_lock(&ci->lock); + set_mode(ci, 0); + write_io(ci, address, value); + mutex_unlock(&ci->lock); + return 0; +} + +static int slot_reset(struct dvb_ca_en50221 *ca, int slot) +{ + struct cxd *ci = ca->data; + + mutex_lock(&ci->lock); +#if 0 + write_reg(ci, 0x00, 0x21); + write_reg(ci, 0x06, 0x1F); + write_reg(ci, 0x00, 0x31); +#else +#if 0 + write_reg(ci, 0x06, 0x1F); + write_reg(ci, 0x06, 0x2F); +#else + cam_mode(ci, 0); + write_reg(ci, 0x00, 0x21); + write_reg(ci, 0x06, 0x1F); + write_reg(ci, 0x00, 0x31); + write_regm(ci, 0x20, 0x80, 0x80); + write_reg(ci, 0x03, 0x02); + ci->ready = 0; +#endif +#endif + ci->mode = -1; + { + int i; +#if 0 + u8 val; +#endif + for (i = 0; i < 100; i++) { + msleep(10); +#if 0 + read_reg(ci, 0x06, &val); + dev_info(&ci->i2c->dev, "%d:%02x\n", i, val); + if (!(val&0x10)) + break; +#else + if (ci->ready) + break; +#endif + } + } + mutex_unlock(&ci->lock); + /* msleep(500); */ + return 0; +} + +static int slot_shutdown(struct dvb_ca_en50221 *ca, int slot) +{ + struct cxd *ci = ca->data; + + dev_info(&ci->i2c->dev, "slot_shutdown\n"); + mutex_lock(&ci->lock); + write_regm(ci, 0x09, 0x08, 0x08); + write_regm(ci, 0x20, 0x80, 0x80); /* Reset CAM Mode */ + write_regm(ci, 0x06, 0x07, 0x07); /* Clear IO Mode */ + ci->mode = -1; + mutex_unlock(&ci->lock); + return 0; +} + +static int slot_ts_enable(struct dvb_ca_en50221 *ca, int slot) +{ + struct cxd *ci = ca->data; + + mutex_lock(&ci->lock); + write_regm(ci, 0x09, 0x00, 0x08); + set_mode(ci, 0); +#ifdef BUFFER_MODE + cam_mode(ci, 1); +#endif + mutex_unlock(&ci->lock); + return 0; +} + + +static int campoll(struct cxd *ci) +{ + u8 istat; + + read_reg(ci, 0x04, &istat); + if (!istat) + return 0; + write_reg(ci, 0x05, istat); + + if (istat&0x40) { + ci->dr = 1; + dev_info(&ci->i2c->dev, "DR\n"); + } + if (istat&0x20) + dev_info(&ci->i2c->dev, "WC\n"); + + if (istat&2) { + u8 slotstat; + + read_reg(ci, 0x01, &slotstat); + if (!(2&slotstat)) { + if (!ci->slot_stat) { + ci->slot_stat = DVB_CA_EN50221_POLL_CAM_PRESENT; + write_regm(ci, 0x03, 0x08, 0x08); + } + + } else { + if (ci->slot_stat) { + ci->slot_stat = 0; + write_regm(ci, 0x03, 0x00, 0x08); + dev_info(&ci->i2c->dev, "NO CAM\n"); + ci->ready = 0; + } + } + if (istat&8 && + ci->slot_stat == DVB_CA_EN50221_POLL_CAM_PRESENT) { + ci->ready = 1; + ci->slot_stat |= DVB_CA_EN50221_POLL_CAM_READY; + } + } + return 0; +} + + +static int poll_slot_status(struct dvb_ca_en50221 *ca, int slot, int open) +{ + struct cxd *ci = ca->data; + u8 slotstat; + + mutex_lock(&ci->lock); + campoll(ci); + read_reg(ci, 0x01, &slotstat); + mutex_unlock(&ci->lock); + + return ci->slot_stat; +} + +#ifdef BUFFER_MODE +static int read_data(struct dvb_ca_en50221 *ca, int slot, u8 *ebuf, int ecount) +{ + struct cxd *ci = ca->data; + u8 msb, lsb; + u16 len; + + mutex_lock(&ci->lock); + campoll(ci); + mutex_unlock(&ci->lock); + + dev_info(&ci->i2c->dev, "read_data\n"); + if (!ci->dr) + return 0; + + mutex_lock(&ci->lock); + read_reg(ci, 0x0f, &msb); + read_reg(ci, 0x10, &lsb); + len = (msb<<8)|lsb; + read_block(ci, 0x12, ebuf, len); + ci->dr = 0; + mutex_unlock(&ci->lock); + + return len; +} + +static int write_data(struct dvb_ca_en50221 *ca, int slot, u8 *ebuf, int ecount) +{ + struct cxd *ci = ca->data; + + mutex_lock(&ci->lock); + printk(kern_INFO "write_data %d\n", ecount); + write_reg(ci, 0x0d, ecount>>8); + write_reg(ci, 0x0e, ecount&0xff); + write_block(ci, 0x11, ebuf, ecount); + mutex_unlock(&ci->lock); + return ecount; +} +#endif + +static struct dvb_ca_en50221 en_templ = { + .read_attribute_mem = read_attribute_mem, + .write_attribute_mem = write_attribute_mem, + .read_cam_control = read_cam_control, + .write_cam_control = write_cam_control, + .slot_reset = slot_reset, + .slot_shutdown = slot_shutdown, + .slot_ts_enable = slot_ts_enable, + .poll_slot_status = poll_slot_status, +#ifdef BUFFER_MODE + .read_data = read_data, + .write_data = write_data, +#endif + +}; + +struct dvb_ca_en50221 *cxd2099_attach(struct cxd2099_cfg *cfg, + void *priv, + struct i2c_adapter *i2c) +{ + struct cxd *ci; + u8 val; + + if (i2c_read_reg(i2c, cfg->adr, 0, &val) < 0) { + dev_info(&i2c->dev, "No CXD2099 detected at %02x\n", cfg->adr); + return NULL; + } + + ci = kzalloc(sizeof(struct cxd), GFP_KERNEL); + if (!ci) + return NULL; + + mutex_init(&ci->lock); + ci->cfg = *cfg; + ci->i2c = i2c; + ci->lastaddress = 0xff; + ci->clk_reg_b = 0x4a; + ci->clk_reg_f = 0x1b; + + ci->en = en_templ; + ci->en.data = ci; + init(ci); + dev_info(&i2c->dev, "Attached CXD2099AR at %02x\n", ci->cfg.adr); + return &ci->en; +} +EXPORT_SYMBOL(cxd2099_attach); + +MODULE_DESCRIPTION("cxd2099"); +MODULE_AUTHOR("Ralph Metzler"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/media/cxd2099/cxd2099.h b/drivers/staging/media/cxd2099/cxd2099.h new file mode 100644 index 00000000000..0eb607c5b42 --- /dev/null +++ b/drivers/staging/media/cxd2099/cxd2099.h @@ -0,0 +1,51 @@ +/* + * cxd2099.h: Driver for the CXD2099AR Common Interface Controller + * + * Copyright (C) 2010-2011 Digital Devices GmbH + * + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 only, as published by the Free Software Foundation. + * + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA + * Or, point your browser to http://www.gnu.org/copyleft/gpl.html + */ + +#ifndef _CXD2099_H_ +#define _CXD2099_H_ + +#include <dvb_ca_en50221.h> + +struct cxd2099_cfg { + u32 bitrate; + u8 adr; + u8 polarity:1; + u8 clock_mode:1; +}; + +#if defined(CONFIG_DVB_CXD2099) || \ + (defined(CONFIG_DVB_CXD2099_MODULE) && defined(MODULE)) +struct dvb_ca_en50221 *cxd2099_attach(struct cxd2099_cfg *cfg, + void *priv, struct i2c_adapter *i2c); +#else + +static inline struct dvb_ca_en50221 *cxd2099_attach(struct cxd2099_cfg *cfg, + void *priv, struct i2c_adapter *i2c) +{ + dev_warn(&i2c->dev, "%s: driver disabled by Kconfig\n", __func__); + return NULL; +} +#endif + +#endif diff --git a/drivers/staging/media/davinci_vpfe/Kconfig b/drivers/staging/media/davinci_vpfe/Kconfig new file mode 100644 index 00000000000..12f321dd239 --- /dev/null +++ b/drivers/staging/media/davinci_vpfe/Kconfig @@ -0,0 +1,9 @@ +config VIDEO_DM365_VPFE + tristate "DM365 VPFE Media Controller Capture Driver" + depends on VIDEO_V4L2 && ARCH_DAVINCI_DM365 && !VIDEO_DM365_ISIF + select VIDEOBUF2_DMA_CONTIG + help + Support for DM365 VPFE based Media Controller Capture driver. + + To compile this driver as a module, choose M here: the + module will be called vpfe-mc-capture. diff --git a/drivers/staging/media/davinci_vpfe/Makefile b/drivers/staging/media/davinci_vpfe/Makefile new file mode 100644 index 00000000000..c64515c644c --- /dev/null +++ b/drivers/staging/media/davinci_vpfe/Makefile @@ -0,0 +1,3 @@ +obj-$(CONFIG_VIDEO_DM365_VPFE) += \ + dm365_isif.o dm365_ipipe_hw.o dm365_ipipe.o \ + dm365_resizer.o dm365_ipipeif.o vpfe_mc_capture.o vpfe_video.o diff --git a/drivers/staging/media/davinci_vpfe/TODO b/drivers/staging/media/davinci_vpfe/TODO new file mode 100644 index 00000000000..7015ab35ded --- /dev/null +++ b/drivers/staging/media/davinci_vpfe/TODO @@ -0,0 +1,37 @@ +TODO (general): +================================== + +- User space interface refinement + - Controls should be used when possible rather than private ioctl + - No enums should be used + - Use of MC and V4L2 subdev APIs when applicable + - Single interface header might suffice + - Current interface forces to configure everything at once +- Get rid of the dm365_ipipe_hw.[ch] layer +- Active external sub-devices defined by link configuration; no strcmp + needed +- More generic platform data (i2c adapters) +- The driver should have no knowledge of possible external subdevs; see + struct vpfe_subdev_id +- Some of the hardware control should be refactorede +- Check proper serialisation (through mutexes and spinlocks) +- Names that are visible in kernel global namespace should have a common + prefix (or a few) +- While replacing the older driver in media folder, provide a compatibility + layer and compatibility tests that warrants (using the libv4l's LD_PRELOAD + approach) there is no regression for the users using the older driver. + +Building of uImage and Applications: +================================== + +As of now since the interface will undergo few changes all the include +files are present in staging itself, to build for dm365 follow below steps, + +- copy vpfe.h from drivers/staging/media/davinci_vpfe/ to + include/media/davinci/ folder for building the uImage. +- copy davinci_vpfe_user.h from drivers/staging/media/davinci_vpfe/ to + include/uapi/linux/davinci_vpfe.h, and add a entry in Kbuild (required + for building application). +- copy dm365_ipipeif_user.h from drivers/staging/media/davinci_vpfe/ to + include/uapi/linux/dm365_ipipeif.h and a entry in Kbuild (required + for building application). diff --git a/drivers/staging/media/davinci_vpfe/davinci-vpfe-mc.txt b/drivers/staging/media/davinci_vpfe/davinci-vpfe-mc.txt new file mode 100644 index 00000000000..a1e91778aa9 --- /dev/null +++ b/drivers/staging/media/davinci_vpfe/davinci-vpfe-mc.txt @@ -0,0 +1,154 @@ +Davinci Video processing Front End (VPFE) driver + +Copyright (C) 2012 Texas Instruments Inc + +Contacts: Manjunath Hadli <manjunath.hadli@ti.com> + Prabhakar Lad <prabhakar.lad@ti.com> + + +Introduction +============ + +This file documents the Texas Instruments Davinci Video processing Front End +(VPFE) driver located under drivers/media/platform/davinci. The original driver +exists for Davinci VPFE, which is now being changed to Media Controller +Framework. + +Currently the driver has been successfully used on the following +version of Davinci: + + DM365/DM368 + +The driver implements V4L2, Media controller and v4l2_subdev interfaces. Sensor, +lens and flash drivers using the v4l2_subdev interface in the kernel are +supported. + + +Split to subdevs +================ + +The Davinci VPFE is split into V4L2 subdevs, each of the blocks inside the VPFE +having one subdev to represent it. Each of the subdevs provide a V4L2 subdev +interface to userspace. + + DAVINCI ISIF + DAVINCI IPIPEIF + DAVINCI IPIPE + DAVINCI CROP RESIZER + DAVINCI RESIZER A + DAVINCI RESIZER B + +Each possible link in the VPFE is modelled by a link in the Media controller +interface. For an example program see [1]. + + +ISIF, IPIPE, and RESIZER block IOCTLs +====================================== + +The Davinci Video processing Front End (VPFE) driver supports standard V4L2 +IOCTLs and controls where possible and practical. Much of the functions provided +by the VPFE, however, does not fall under the standard IOCTL's. + +In general, there is a private ioctl for configuring each of the blocks +containing hardware-dependent functions. + +The following private IOCTLs are supported: + + VIDIOC_VPFE_ISIF_[S/G]_RAW_PARAMS + VIDIOC_VPFE_IPIPE_[S/G]_CONFIG + VIDIOC_VPFE_RSZ_[S/G]_CONFIG + +The parameter structures used by these ioctl's are described in +include/uapi/linux/davinci_vpfe.h. + +The VIDIOC_VPFE_ISIF_S_RAW_PARAMS, VIDIOC_VPFE_IPIPE_S_CONFIG and +VIDIOC_VPFE_RSZ_S_CONFIG are used to configure, enable and disable functions in +the isif, ipipe and resizer blocks respectively. These IOCTL's control several +functions in the blocks they control. VIDIOC_VPFE_ISIF_S_RAW_PARAMS IOCTL +accepts a pointer to struct vpfe_isif_raw_config as its argument. Similarly +VIDIOC_VPFE_IPIPE_S_CONFIG accepts a pointer to struct vpfe_ipipe_config. And +VIDIOC_VPFE_RSZ_S_CONFIG accepts a pointer to struct vpfe_rsz_config as its +argument. Similarly VIDIOC_VPFE_ISIF_G_RAW_PARAMS, VIDIOC_VPFE_IPIPE_G_CONFIG +and VIDIOC_VPFE_RSZ_G_CONFIG are used to get the current configuration set in +the isif, ipipe and resizer blocks respectively. + +The detailed functions of the VPFE itself related to a given VPFE block is +described in the Technical Reference Manuals (TRMs) --- see the end of the +document for those. + + +IPIPEIF block IOCTLs +====================================== + +The following private IOCTLs are supported: + + VIDIOC_VPFE_IPIPEIF_[S/G]_CONFIG + +The parameter structures used by these ioctl's are described in +include/uapi/linux/dm365_ipipeif.h + +The VIDIOC_VPFE_IPIPEIF_S_CONFIG is used to configure the ipipeif +hardware block. The VIDIOC_VPFE_IPIPEIF_S_CONFIG and +VIDIOC_VPFE_IPIPEIF_G_CONFIG accepts a pointer to struct ipipeif_params +as its argument. + + +VPFE Operating Modes +========================================== + +a: Continuous Modes +------------------------ + +1: tvp514x/tvp7002/mt9p031---> DAVINCI ISIF---> SDRAM + +2: tvp514x/tvp7002/mt9p031---> DAVINCI ISIF---> DAVINCI IPIPEIF--->| + | + <--------------------<----------------<---------------------<---| + | + V + DAVINCI CROP RESIZER--->DAVINCI RESIZER [A/B]---> SDRAM + +3: tvp514x/tvp7002/mt9p031---> DAVINCI ISIF---> DAVINCI IPIPEIF--->| + | + <--------------------<----------------<---------------------<---| + | + V + DAVINCI IPIPE---> DAVINCI CROP RESIZER--->DAVINCI RESIZER [A/B]---> SDRAM + +a: Single Shot Modes +------------------------ + +1: SDRAM---> DAVINCI IPIPEIF---> DAVINCI IPIPE---> DAVINCI CROP RESIZER--->| + | + <----------------<----------------<------------------<---------------<--| + | + V +DAVINCI RESIZER [A/B]---> SDRAM + +2: SDRAM---> DAVINCI IPIPEIF---> DAVINCI CROP RESIZER--->| + | + <----------------<----------------<---------------<---| + | + V +DAVINCI RESIZER [A/B]---> SDRAM + + +Technical reference manuals (TRMs) and other documentation +========================================================== + +Davinci DM365 TRM: +<URL:http://www.ti.com/lit/ds/sprs457e/sprs457e.pdf> +Referenced MARCH 2009-REVISED JUNE 2011 + +Davinci DM368 TRM: +<URL:http://www.ti.com/lit/ds/sprs668c/sprs668c.pdf> +Referenced APRIL 2010-REVISED JUNE 2011 + +Davinci Video Processing Front End (VPFE) DM36x +<URL:http://www.ti.com/lit/ug/sprufg8c/sprufg8c.pdf> + + +References +========== + +[1] http://git.ideasonboard.org/?p=media-ctl.git;a=summary diff --git a/drivers/staging/media/davinci_vpfe/davinci_vpfe_user.h b/drivers/staging/media/davinci_vpfe/davinci_vpfe_user.h new file mode 100644 index 00000000000..7b7e7b26c1e --- /dev/null +++ b/drivers/staging/media/davinci_vpfe/davinci_vpfe_user.h @@ -0,0 +1,1290 @@ +/* + * Copyright (C) 2012 Texas Instruments Inc + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Contributors: + * Manjunath Hadli <manjunath.hadli@ti.com> + * Prabhakar Lad <prabhakar.lad@ti.com> + */ + +#ifndef _DAVINCI_VPFE_USER_H +#define _DAVINCI_VPFE_USER_H + +#include <linux/types.h> +#include <linux/videodev2.h> + +/* + * Private IOCTL + * + * VIDIOC_VPFE_ISIF_S_RAW_PARAMS: Set raw params in isif + * VIDIOC_VPFE_ISIF_G_RAW_PARAMS: Get raw params from isif + * VIDIOC_VPFE_PRV_S_CONFIG: Set ipipe engine configuration + * VIDIOC_VPFE_PRV_G_CONFIG: Get ipipe engine configuration + * VIDIOC_VPFE_RSZ_S_CONFIG: Set resizer engine configuration + * VIDIOC_VPFE_RSZ_G_CONFIG: Get resizer engine configuration + */ + +#define VIDIOC_VPFE_ISIF_S_RAW_PARAMS \ + _IOW('V', BASE_VIDIOC_PRIVATE + 1, struct vpfe_isif_raw_config) +#define VIDIOC_VPFE_ISIF_G_RAW_PARAMS \ + _IOR('V', BASE_VIDIOC_PRIVATE + 2, struct vpfe_isif_raw_config) +#define VIDIOC_VPFE_IPIPE_S_CONFIG \ + _IOWR('P', BASE_VIDIOC_PRIVATE + 3, struct vpfe_ipipe_config) +#define VIDIOC_VPFE_IPIPE_G_CONFIG \ + _IOWR('P', BASE_VIDIOC_PRIVATE + 4, struct vpfe_ipipe_config) +#define VIDIOC_VPFE_RSZ_S_CONFIG \ + _IOWR('R', BASE_VIDIOC_PRIVATE + 5, struct vpfe_rsz_config) +#define VIDIOC_VPFE_RSZ_G_CONFIG \ + _IOWR('R', BASE_VIDIOC_PRIVATE + 6, struct vpfe_rsz_config) + +/* + * Private Control's for ISIF + */ +#define VPFE_ISIF_CID_CRGAIN (V4L2_CID_USER_BASE | 0xa001) +#define VPFE_ISIF_CID_CGRGAIN (V4L2_CID_USER_BASE | 0xa002) +#define VPFE_ISIF_CID_CGBGAIN (V4L2_CID_USER_BASE | 0xa003) +#define VPFE_ISIF_CID_CBGAIN (V4L2_CID_USER_BASE | 0xa004) +#define VPFE_ISIF_CID_GAIN_OFFSET (V4L2_CID_USER_BASE | 0xa005) + +/* + * Private Control's for ISIF and IPIPEIF + */ +#define VPFE_CID_DPCM_PREDICTOR (V4L2_CID_USER_BASE | 0xa006) + +/************************************************************************ + * Vertical Defect Correction parameters + ***********************************************************************/ + +/** + * vertical defect correction methods + */ +enum vpfe_isif_vdfc_corr_mode { + /* Defect level subtraction. Just fed through if saturating */ + VPFE_ISIF_VDFC_NORMAL, + /** + * Defect level subtraction. Horizontal interpolation ((i-2)+(i+2))/2 + * if data saturating + */ + VPFE_ISIF_VDFC_HORZ_INTERPOL_IF_SAT, + /* Horizontal interpolation (((i-2)+(i+2))/2) */ + VPFE_ISIF_VDFC_HORZ_INTERPOL +}; + +/** + * Max Size of the Vertical Defect Correction table + */ +#define VPFE_ISIF_VDFC_TABLE_SIZE 8 + +/** + * Values used for shifting up the vdfc defect level + */ +enum vpfe_isif_vdfc_shift { + /* No Shift */ + VPFE_ISIF_VDFC_NO_SHIFT, + /* Shift by 1 bit */ + VPFE_ISIF_VDFC_SHIFT_1, + /* Shift by 2 bit */ + VPFE_ISIF_VDFC_SHIFT_2, + /* Shift by 3 bit */ + VPFE_ISIF_VDFC_SHIFT_3, + /* Shift by 4 bit */ + VPFE_ISIF_VDFC_SHIFT_4 +}; + +/** + * Defect Correction (DFC) table entry + */ +struct vpfe_isif_vdfc_entry { + /* vertical position of defect */ + unsigned short pos_vert; + /* horizontal position of defect */ + unsigned short pos_horz; + /** + * Defect level of Vertical line defect position. This is subtracted + * from the data at the defect position + */ + unsigned char level_at_pos; + /** + * Defect level of the pixels upper than the vertical line defect. + * This is subtracted from the data + */ + unsigned char level_up_pixels; + /** + * Defect level of the pixels lower than the vertical line defect. + * This is subtracted from the data + */ + unsigned char level_low_pixels; +}; + +/** + * Structure for Defect Correction (DFC) parameter + */ +struct vpfe_isif_dfc { + /* enable vertical defect correction */ + unsigned char en; + /* Correction methods */ + enum vpfe_isif_vdfc_corr_mode corr_mode; + /** + * 0 - whole line corrected, 1 - not + * pixels upper than the defect + */ + unsigned char corr_whole_line; + /** + * defect level shift value. level_at_pos, level_upper_pos, + * and level_lower_pos can be shifted up by this value + */ + enum vpfe_isif_vdfc_shift def_level_shift; + /* defect saturation level */ + unsigned short def_sat_level; + /* number of vertical defects. Max is VPFE_ISIF_VDFC_TABLE_SIZE */ + short num_vdefects; + /* VDFC table ptr */ + struct vpfe_isif_vdfc_entry table[VPFE_ISIF_VDFC_TABLE_SIZE]; +}; + +/************************************************************************ +* Digital/Black clamp or DC Subtract parameters +************************************************************************/ +/** + * Horizontal Black Clamp modes + */ +enum vpfe_isif_horz_bc_mode { + /** + * Horizontal clamp disabled. Only vertical clamp + * value is subtracted + */ + VPFE_ISIF_HORZ_BC_DISABLE, + /** + * Horizontal clamp value is calculated and subtracted + * from image data along with vertical clamp value + */ + VPFE_ISIF_HORZ_BC_CLAMP_CALC_ENABLED, + /** + * Horizontal clamp value calculated from previous image + * is subtracted from image data along with vertical clamp + * value. How the horizontal clamp value for the first image + * is calculated in this case ??? + */ + VPFE_ISIF_HORZ_BC_CLAMP_NOT_UPDATED +}; + +/** + * Base window selection for Horizontal Black Clamp calculations + */ +enum vpfe_isif_horz_bc_base_win_sel { + /* Select Most left window for bc calculation */ + VPFE_ISIF_SEL_MOST_LEFT_WIN, + + /* Select Most right window for bc calculation */ + VPFE_ISIF_SEL_MOST_RIGHT_WIN, +}; + +/* Size of window in horizontal direction for horizontal bc */ +enum vpfe_isif_horz_bc_sz_h { + VPFE_ISIF_HORZ_BC_SZ_H_2PIXELS, + VPFE_ISIF_HORZ_BC_SZ_H_4PIXELS, + VPFE_ISIF_HORZ_BC_SZ_H_8PIXELS, + VPFE_ISIF_HORZ_BC_SZ_H_16PIXELS +}; + +/* Size of window in vertcal direction for vertical bc */ +enum vpfe_isif_horz_bc_sz_v { + VPFE_ISIF_HORZ_BC_SZ_H_32PIXELS, + VPFE_ISIF_HORZ_BC_SZ_H_64PIXELS, + VPFE_ISIF_HORZ_BC_SZ_H_128PIXELS, + VPFE_ISIF_HORZ_BC_SZ_H_256PIXELS +}; + +/** + * Structure for Horizontal Black Clamp config params + */ +struct vpfe_isif_horz_bclamp { + /* horizontal clamp mode */ + enum vpfe_isif_horz_bc_mode mode; + /** + * pixel value limit enable. + * 0 - limit disabled + * 1 - pixel value limited to 1023 + */ + unsigned char clamp_pix_limit; + /** + * Select most left or right window for clamp val + * calculation + */ + enum vpfe_isif_horz_bc_base_win_sel base_win_sel_calc; + /* Window count per color for calculation. range 1-32 */ + unsigned char win_count_calc; + /* Window start position - horizontal for calculation. 0 - 8191 */ + unsigned short win_start_h_calc; + /* Window start position - vertical for calculation 0 - 8191 */ + unsigned short win_start_v_calc; + /* Width of the sample window in pixels for calculation */ + enum vpfe_isif_horz_bc_sz_h win_h_sz_calc; + /* Height of the sample window in pixels for calculation */ + enum vpfe_isif_horz_bc_sz_v win_v_sz_calc; +}; + +/** + * Black Clamp vertical reset values + */ +enum vpfe_isif_vert_bc_reset_val_sel { + /* Reset value used is the clamp value calculated */ + VPFE_ISIF_VERT_BC_USE_HORZ_CLAMP_VAL, + /* Reset value used is reset_clamp_val configured */ + VPFE_ISIF_VERT_BC_USE_CONFIG_CLAMP_VAL, + /* No update, previous image value is used */ + VPFE_ISIF_VERT_BC_NO_UPDATE +}; + +enum vpfe_isif_vert_bc_sz_h { + VPFE_ISIF_VERT_BC_SZ_H_2PIXELS, + VPFE_ISIF_VERT_BC_SZ_H_4PIXELS, + VPFE_ISIF_VERT_BC_SZ_H_8PIXELS, + VPFE_ISIF_VERT_BC_SZ_H_16PIXELS, + VPFE_ISIF_VERT_BC_SZ_H_32PIXELS, + VPFE_ISIF_VERT_BC_SZ_H_64PIXELS +}; + +/** + * Structure for Vertical Black Clamp configuration params + */ +struct vpfe_isif_vert_bclamp { + /* Reset value selection for vertical clamp calculation */ + enum vpfe_isif_vert_bc_reset_val_sel reset_val_sel; + /* U12 value if reset_sel = ISIF_BC_VERT_USE_CONFIG_CLAMP_VAL */ + unsigned short reset_clamp_val; + /** + * U8Q8. Line average coefficient used in vertical clamp + * calculation + */ + unsigned char line_ave_coef; + /* Width in pixels of the optical black region used for calculation. */ + enum vpfe_isif_vert_bc_sz_h ob_h_sz_calc; + /* Height of the optical black region for calculation */ + unsigned short ob_v_sz_calc; + /* Optical black region start position - horizontal. 0 - 8191 */ + unsigned short ob_start_h; + /* Optical black region start position - vertical 0 - 8191 */ + unsigned short ob_start_v; +}; + +/** + * Structure for Black Clamp configuration params + */ +struct vpfe_isif_black_clamp { + /** + * this offset value is added irrespective of the clamp + * enable status. S13 + */ + unsigned short dc_offset; + /** + * Enable black/digital clamp value to be subtracted + * from the image data + */ + unsigned char en; + /** + * black clamp mode. same/separate clamp for 4 colors + * 0 - disable - same clamp value for all colors + * 1 - clamp value calculated separately for all colors + */ + unsigned char bc_mode_color; + /* Vertical start position for bc subtraction */ + unsigned short vert_start_sub; + /* Black clamp for horizontal direction */ + struct vpfe_isif_horz_bclamp horz; + /* Black clamp for vertical direction */ + struct vpfe_isif_vert_bclamp vert; +}; + +/************************************************************************* +** Color Space Conversion (CSC) +*************************************************************************/ +/** + * Number of Coefficient values used for CSC + */ +#define VPFE_ISIF_CSC_NUM_COEFF 16 + +struct float_8_bit { + /* 8 bit integer part */ + __u8 integer; + /* 8 bit decimal part */ + __u8 decimal; +}; + +struct float_16_bit { + /* 16 bit integer part */ + __u16 integer; + /* 16 bit decimal part */ + __u16 decimal; +}; + +/************************************************************************* +** Color Space Conversion parameters +*************************************************************************/ +/** + * Structure used for CSC config params + */ +struct vpfe_isif_color_space_conv { + /* Enable color space conversion */ + unsigned char en; + /** + * csc coefficient table. S8Q5, M00 at index 0, M01 at index 1, and + * so forth + */ + struct float_8_bit coeff[VPFE_ISIF_CSC_NUM_COEFF]; +}; + +enum vpfe_isif_datasft { + /* No Shift */ + VPFE_ISIF_NO_SHIFT, + /* 1 bit Shift */ + VPFE_ISIF_1BIT_SHIFT, + /* 2 bit Shift */ + VPFE_ISIF_2BIT_SHIFT, + /* 3 bit Shift */ + VPFE_ISIF_3BIT_SHIFT, + /* 4 bit Shift */ + VPFE_ISIF_4BIT_SHIFT, + /* 5 bit Shift */ + VPFE_ISIF_5BIT_SHIFT, + /* 6 bit Shift */ + VPFE_ISIF_6BIT_SHIFT +}; + +#define VPFE_ISIF_LINEAR_TAB_SIZE 192 +/************************************************************************* +** Linearization parameters +*************************************************************************/ +/** + * Structure for Sensor data linearization + */ +struct vpfe_isif_linearize { + /* Enable or Disable linearization of data */ + unsigned char en; + /* Shift value applied */ + enum vpfe_isif_datasft corr_shft; + /* scale factor applied U11Q10 */ + struct float_16_bit scale_fact; + /* Size of the linear table */ + unsigned short table[VPFE_ISIF_LINEAR_TAB_SIZE]; +}; + +/************************************************************************* +** ISIF Raw configuration parameters +*************************************************************************/ +enum vpfe_isif_fmt_mode { + VPFE_ISIF_SPLIT, + VPFE_ISIF_COMBINE +}; + +enum vpfe_isif_lnum { + VPFE_ISIF_1LINE, + VPFE_ISIF_2LINES, + VPFE_ISIF_3LINES, + VPFE_ISIF_4LINES +}; + +enum vpfe_isif_line { + VPFE_ISIF_1STLINE, + VPFE_ISIF_2NDLINE, + VPFE_ISIF_3RDLINE, + VPFE_ISIF_4THLINE +}; + +struct vpfe_isif_fmtplen { + /** + * number of program entries for SET0, range 1 - 16 + * when fmtmode is ISIF_SPLIT, 1 - 8 when fmtmode is + * ISIF_COMBINE + */ + unsigned short plen0; + /** + * number of program entries for SET1, range 1 - 16 + * when fmtmode is ISIF_SPLIT, 1 - 8 when fmtmode is + * ISIF_COMBINE + */ + unsigned short plen1; + /** + * number of program entries for SET2, range 1 - 16 + * when fmtmode is ISIF_SPLIT, 1 - 8 when fmtmode is + * ISIF_COMBINE + */ + unsigned short plen2; + /** + * number of program entries for SET3, range 1 - 16 + * when fmtmode is ISIF_SPLIT, 1 - 8 when fmtmode is + * ISIF_COMBINE + */ + unsigned short plen3; +}; + +struct vpfe_isif_fmt_cfg { + /* Split or combine or line alternate */ + enum vpfe_isif_fmt_mode fmtmode; + /* enable or disable line alternating mode */ + unsigned char ln_alter_en; + /* Split/combine line number */ + enum vpfe_isif_lnum lnum; + /* Address increment Range 1 - 16 */ + unsigned int addrinc; +}; + +struct vpfe_isif_fmt_addr_ptr { + /* Initial address */ + unsigned int init_addr; + /* output line number */ + enum vpfe_isif_line out_line; +}; + +struct vpfe_isif_fmtpgm_ap { + /* program address pointer */ + unsigned char pgm_aptr; + /* program address increment or decrement */ + unsigned char pgmupdt; +}; + +struct vpfe_isif_data_formatter { + /* Enable/Disable data formatter */ + unsigned char en; + /* data formatter configuration */ + struct vpfe_isif_fmt_cfg cfg; + /* Formatter program entries length */ + struct vpfe_isif_fmtplen plen; + /* first pixel in a line fed to formatter */ + unsigned short fmtrlen; + /* HD interval for output line. Only valid when split line */ + unsigned short fmthcnt; + /* formatter address pointers */ + struct vpfe_isif_fmt_addr_ptr fmtaddr_ptr[16]; + /* program enable/disable */ + unsigned char pgm_en[32]; + /* program address pointers */ + struct vpfe_isif_fmtpgm_ap fmtpgm_ap[32]; +}; + +struct vpfe_isif_df_csc { + /* Color Space Conversion configuration, 0 - csc, 1 - df */ + unsigned int df_or_csc; + /* csc configuration valid if df_or_csc is 0 */ + struct vpfe_isif_color_space_conv csc; + /* data formatter configuration valid if df_or_csc is 1 */ + struct vpfe_isif_data_formatter df; + /* start pixel in a line at the input */ + unsigned int start_pix; + /* number of pixels in input line */ + unsigned int num_pixels; + /* start line at the input */ + unsigned int start_line; + /* number of lines at the input */ + unsigned int num_lines; +}; + +struct vpfe_isif_gain_offsets_adj { + /* Enable or Disable Gain adjustment for SDRAM data */ + unsigned char gain_sdram_en; + /* Enable or Disable Gain adjustment for IPIPE data */ + unsigned char gain_ipipe_en; + /* Enable or Disable Gain adjustment for H3A data */ + unsigned char gain_h3a_en; + /* Enable or Disable Gain adjustment for SDRAM data */ + unsigned char offset_sdram_en; + /* Enable or Disable Gain adjustment for IPIPE data */ + unsigned char offset_ipipe_en; + /* Enable or Disable Gain adjustment for H3A data */ + unsigned char offset_h3a_en; +}; + +struct vpfe_isif_cul { + /* Horizontal Cull pattern for odd lines */ + unsigned char hcpat_odd; + /* Horizontal Cull pattern for even lines */ + unsigned char hcpat_even; + /* Vertical Cull pattern */ + unsigned char vcpat; + /* Enable or disable lpf. Apply when cull is enabled */ + unsigned char en_lpf; +}; + +/* all the stuff in this struct will be provided by userland */ +struct vpfe_isif_raw_config { + /* Linearization parameters for image sensor data input */ + struct vpfe_isif_linearize linearize; + /* Data formatter or CSC */ + struct vpfe_isif_df_csc df_csc; + /* Defect Pixel Correction (DFC) confguration */ + struct vpfe_isif_dfc dfc; + /* Black/Digital Clamp configuration */ + struct vpfe_isif_black_clamp bclamp; + /* Gain, offset adjustments */ + struct vpfe_isif_gain_offsets_adj gain_offset; + /* Culling */ + struct vpfe_isif_cul culling; + /* horizontal offset for Gain/LSC/DFC */ + unsigned short horz_offset; + /* vertical offset for Gain/LSC/DFC */ + unsigned short vert_offset; +}; + +/********************************************************************** + IPIPE API Structures +**********************************************************************/ + +/* IPIPE module configurations */ + +/* IPIPE input configuration */ +#define VPFE_IPIPE_INPUT_CONFIG (1 << 0) +/* LUT based Defect Pixel Correction */ +#define VPFE_IPIPE_LUTDPC (1 << 1) +/* On the fly (OTF) Defect Pixel Correction */ +#define VPFE_IPIPE_OTFDPC (1 << 2) +/* Noise Filter - 1 */ +#define VPFE_IPIPE_NF1 (1 << 3) +/* Noise Filter - 2 */ +#define VPFE_IPIPE_NF2 (1 << 4) +/* White Balance. Also a control ID */ +#define VPFE_IPIPE_WB (1 << 5) +/* 1st RGB to RBG Blend module */ +#define VPFE_IPIPE_RGB2RGB_1 (1 << 6) +/* 2nd RGB to RBG Blend module */ +#define VPFE_IPIPE_RGB2RGB_2 (1 << 7) +/* Gamma Correction */ +#define VPFE_IPIPE_GAMMA (1 << 8) +/* 3D LUT color conversion */ +#define VPFE_IPIPE_3D_LUT (1 << 9) +/* RGB to YCbCr module */ +#define VPFE_IPIPE_RGB2YUV (1 << 10) +/* YUV 422 conversion module */ +#define VPFE_IPIPE_YUV422_CONV (1 << 11) +/* Edge Enhancement */ +#define VPFE_IPIPE_YEE (1 << 12) +/* Green Imbalance Correction */ +#define VPFE_IPIPE_GIC (1 << 13) +/* CFA Interpolation */ +#define VPFE_IPIPE_CFA (1 << 14) +/* Chroma Artifact Reduction */ +#define VPFE_IPIPE_CAR (1 << 15) +/* Chroma Gain Suppression */ +#define VPFE_IPIPE_CGS (1 << 16) +/* Global brightness and contrast control */ +#define VPFE_IPIPE_GBCE (1 << 17) + +#define VPFE_IPIPE_MAX_MODULES 18 + +struct ipipe_float_u16 { + unsigned short integer; + unsigned short decimal; +}; + +struct ipipe_float_s16 { + short integer; + unsigned short decimal; +}; + +struct ipipe_float_u8 { + unsigned char integer; + unsigned char decimal; +}; + +/* Copy method selection for vertical correction + * Used when ipipe_dfc_corr_meth is IPIPE_DPC_CTORB_AFTER_HINT + */ +enum vpfe_ipipe_dpc_corr_meth { + /* replace by black or white dot specified by repl_white */ + VPFE_IPIPE_DPC_REPL_BY_DOT = 0, + /* Copy from left */ + VPFE_IPIPE_DPC_CL = 1, + /* Copy from right */ + VPFE_IPIPE_DPC_CR = 2, + /* Horizontal interpolation */ + VPFE_IPIPE_DPC_H_INTP = 3, + /* Vertical interpolation */ + VPFE_IPIPE_DPC_V_INTP = 4, + /* Copy from top */ + VPFE_IPIPE_DPC_CT = 5, + /* Copy from bottom */ + VPFE_IPIPE_DPC_CB = 6, + /* 2D interpolation */ + VPFE_IPIPE_DPC_2D_INTP = 7, +}; + +struct vpfe_ipipe_lutdpc_entry { + /* Horizontal position */ + unsigned short horz_pos; + /* vertical position */ + unsigned short vert_pos; + enum vpfe_ipipe_dpc_corr_meth method; +}; + +#define VPFE_IPIPE_MAX_SIZE_DPC 256 + +/* Structure for configuring DPC module */ +struct vpfe_ipipe_lutdpc { + /* 0 - disable, 1 - enable */ + unsigned char en; + /* 0 - replace with black dot, 1 - white dot when correction + * method is IPIPE_DFC_REPL_BY_DOT=0, + */ + unsigned char repl_white; + /* number of entries in the correction table. Currently only + * support up-to 256 entries. infinite mode is not supported + */ + unsigned short dpc_size; + struct vpfe_ipipe_lutdpc_entry table[VPFE_IPIPE_MAX_SIZE_DPC]; +}; + +enum vpfe_ipipe_otfdpc_det_meth { + VPFE_IPIPE_DPC_OTF_MIN_MAX, + VPFE_IPIPE_DPC_OTF_MIN_MAX2 +}; + +struct vpfe_ipipe_otfdpc_thr { + unsigned short r; + unsigned short gr; + unsigned short gb; + unsigned short b; +}; + +enum vpfe_ipipe_otfdpc_alg { + VPFE_IPIPE_OTFDPC_2_0, + VPFE_IPIPE_OTFDPC_3_0 +}; + +struct vpfe_ipipe_otfdpc_2_0_cfg { + /* defect detection threshold for MIN_MAX2 method (DPC 2.0 alg) */ + struct vpfe_ipipe_otfdpc_thr det_thr; + /* defect correction threshold for MIN_MAX2 method (DPC 2.0 alg) or + * maximum value for MIN_MAX method + */ + struct vpfe_ipipe_otfdpc_thr corr_thr; +}; + +struct vpfe_ipipe_otfdpc_3_0_cfg { + /* DPC3.0 activity adj shf. activity = (max2-min2) >> (6 -shf) + */ + unsigned char act_adj_shf; + /* DPC3.0 detection threshold, THR */ + unsigned short det_thr; + /* DPC3.0 detection threshold slope, SLP */ + unsigned short det_slp; + /* DPC3.0 detection threshold min, MIN */ + unsigned short det_thr_min; + /* DPC3.0 detection threshold max, MAX */ + unsigned short det_thr_max; + /* DPC3.0 correction threshold, THR */ + unsigned short corr_thr; + /* DPC3.0 correction threshold slope, SLP */ + unsigned short corr_slp; + /* DPC3.0 correction threshold min, MIN */ + unsigned short corr_thr_min; + /* DPC3.0 correction threshold max, MAX */ + unsigned short corr_thr_max; +}; + +struct vpfe_ipipe_otfdpc { + /* 0 - disable, 1 - enable */ + unsigned char en; + /* defect detection method */ + enum vpfe_ipipe_otfdpc_det_meth det_method; + /* Algorithm used. Applicable only when IPIPE_DPC_OTF_MIN_MAX2 is + * used + */ + enum vpfe_ipipe_otfdpc_alg alg; + union { + /* if alg is IPIPE_OTFDPC_2_0 */ + struct vpfe_ipipe_otfdpc_2_0_cfg dpc_2_0; + /* if alg is IPIPE_OTFDPC_3_0 */ + struct vpfe_ipipe_otfdpc_3_0_cfg dpc_3_0; + } alg_cfg; +}; + +/* Threshold values table size */ +#define VPFE_IPIPE_NF_THR_TABLE_SIZE 8 +/* Intensity values table size */ +#define VPFE_IPIPE_NF_STR_TABLE_SIZE 8 + +/* NF, sampling method for green pixels */ +enum vpfe_ipipe_nf_sampl_meth { + /* Same as R or B */ + VPFE_IPIPE_NF_BOX, + /* Diamond mode */ + VPFE_IPIPE_NF_DIAMOND +}; + +/* Structure for configuring NF module */ +struct vpfe_ipipe_nf { + /* 0 - disable, 1 - enable */ + unsigned char en; + /* Sampling method for green pixels */ + enum vpfe_ipipe_nf_sampl_meth gr_sample_meth; + /* Down shift value in LUT reference address + */ + unsigned char shft_val; + /* Spread value in NF algorithm + */ + unsigned char spread_val; + /* Apply LSC gain to threshold. Enable this only if + * LSC is enabled in ISIF + */ + unsigned char apply_lsc_gain; + /* Threshold values table */ + unsigned short thr[VPFE_IPIPE_NF_THR_TABLE_SIZE]; + /* intensity values table */ + unsigned char str[VPFE_IPIPE_NF_STR_TABLE_SIZE]; + /* Edge detection minimum threshold */ + unsigned short edge_det_min_thr; + /* Edge detection maximum threshold */ + unsigned short edge_det_max_thr; +}; + +enum vpfe_ipipe_gic_alg { + VPFE_IPIPE_GIC_ALG_CONST_GAIN, + VPFE_IPIPE_GIC_ALG_ADAPT_GAIN +}; + +enum vpfe_ipipe_gic_thr_sel { + VPFE_IPIPE_GIC_THR_REG, + VPFE_IPIPE_GIC_THR_NF +}; + +enum vpfe_ipipe_gic_wt_fn_type { + /* Use difference as index */ + VPFE_IPIPE_GIC_WT_FN_TYP_DIF, + /* Use weight function as index */ + VPFE_IPIPE_GIC_WT_FN_TYP_HP_VAL +}; + +/* structure for Green Imbalance Correction */ +struct vpfe_ipipe_gic { + /* 0 - disable, 1 - enable */ + unsigned char en; + /* 0 - Constant gain , 1 - Adaptive gain algorithm */ + enum vpfe_ipipe_gic_alg gic_alg; + /* GIC gain or weight. Used for Constant gain and Adaptive algorithms + */ + unsigned short gain; + /* Threshold selection. GIC register values or NF2 thr table */ + enum vpfe_ipipe_gic_thr_sel thr_sel; + /* thr1. Used when thr_sel is IPIPE_GIC_THR_REG */ + unsigned short thr; + /* this value is used for thr2-thr1, thr3-thr2 or + * thr4-thr3 when wt_fn_type is index. Otherwise it + * is the + */ + unsigned short slope; + /* Apply LSC gain to threshold. Enable this only if + * LSC is enabled in ISIF & thr_sel is IPIPE_GIC_THR_REG + */ + unsigned char apply_lsc_gain; + /* Multiply Nf2 threshold by this gain. Use this when thr_sel + * is IPIPE_GIC_THR_NF + */ + struct ipipe_float_u8 nf2_thr_gain; + /* Weight function uses difference as index or high pass value. + * Used for adaptive gain algorithm + */ + enum vpfe_ipipe_gic_wt_fn_type wt_fn_type; +}; + +/* Structure for configuring WB module */ +struct vpfe_ipipe_wb { + /* Offset (S12) for R */ + short ofst_r; + /* Offset (S12) for Gr */ + short ofst_gr; + /* Offset (S12) for Gb */ + short ofst_gb; + /* Offset (S12) for B */ + short ofst_b; + /* Gain (U13Q9) for Red */ + struct ipipe_float_u16 gain_r; + /* Gain (U13Q9) for Gr */ + struct ipipe_float_u16 gain_gr; + /* Gain (U13Q9) for Gb */ + struct ipipe_float_u16 gain_gb; + /* Gain (U13Q9) for Blue */ + struct ipipe_float_u16 gain_b; +}; + +enum vpfe_ipipe_cfa_alg { + /* Algorithm is 2DirAC */ + VPFE_IPIPE_CFA_ALG_2DIRAC, + /* Algorithm is 2DirAC + Digital Antialiasing (DAA) */ + VPFE_IPIPE_CFA_ALG_2DIRAC_DAA, + /* Algorithm is DAA */ + VPFE_IPIPE_CFA_ALG_DAA +}; + +/* Structure for CFA Interpolation */ +struct vpfe_ipipe_cfa { + /* 2DirAC or 2DirAC + DAA */ + enum vpfe_ipipe_cfa_alg alg; + /* 2Dir CFA HP value Low Threshold */ + unsigned short hpf_thr_2dir; + /* 2Dir CFA HP value slope */ + unsigned short hpf_slp_2dir; + /* 2Dir CFA HP mix threshold */ + unsigned short hp_mix_thr_2dir; + /* 2Dir CFA HP mix slope */ + unsigned short hp_mix_slope_2dir; + /* 2Dir Direction threshold */ + unsigned short dir_thr_2dir; + /* 2Dir Direction slope */ + unsigned short dir_slope_2dir; + /* 2Dir Non Directional Weight */ + unsigned short nd_wt_2dir; + /* DAA Mono Hue Fraction */ + unsigned short hue_fract_daa; + /* DAA Mono Edge threshold */ + unsigned short edge_thr_daa; + /* DAA Mono threshold minimum */ + unsigned short thr_min_daa; + /* DAA Mono threshold slope */ + unsigned short thr_slope_daa; + /* DAA Mono slope minimum */ + unsigned short slope_min_daa; + /* DAA Mono slope slope */ + unsigned short slope_slope_daa; + /* DAA Mono LP wight */ + unsigned short lp_wt_daa; +}; + +/* Struct for configuring RGB2RGB blending module */ +struct vpfe_ipipe_rgb2rgb { + /* Matrix coefficient for RR S12Q8 for ID = 1 and S11Q8 for ID = 2 */ + struct ipipe_float_s16 coef_rr; + /* Matrix coefficient for GR S12Q8/S11Q8 */ + struct ipipe_float_s16 coef_gr; + /* Matrix coefficient for BR S12Q8/S11Q8 */ + struct ipipe_float_s16 coef_br; + /* Matrix coefficient for RG S12Q8/S11Q8 */ + struct ipipe_float_s16 coef_rg; + /* Matrix coefficient for GG S12Q8/S11Q8 */ + struct ipipe_float_s16 coef_gg; + /* Matrix coefficient for BG S12Q8/S11Q8 */ + struct ipipe_float_s16 coef_bg; + /* Matrix coefficient for RB S12Q8/S11Q8 */ + struct ipipe_float_s16 coef_rb; + /* Matrix coefficient for GB S12Q8/S11Q8 */ + struct ipipe_float_s16 coef_gb; + /* Matrix coefficient for BB S12Q8/S11Q8 */ + struct ipipe_float_s16 coef_bb; + /* Output offset for R S13/S11 */ + int out_ofst_r; + /* Output offset for G S13/S11 */ + int out_ofst_g; + /* Output offset for B S13/S11 */ + int out_ofst_b; +}; + +#define VPFE_IPIPE_MAX_SIZE_GAMMA 512 + +enum vpfe_ipipe_gamma_tbl_size { + VPFE_IPIPE_GAMMA_TBL_SZ_64 = 64, + VPFE_IPIPE_GAMMA_TBL_SZ_128 = 128, + VPFE_IPIPE_GAMMA_TBL_SZ_256 = 256, + VPFE_IPIPE_GAMMA_TBL_SZ_512 = 512, +}; + +enum vpfe_ipipe_gamma_tbl_sel { + VPFE_IPIPE_GAMMA_TBL_RAM = 0, + VPFE_IPIPE_GAMMA_TBL_ROM = 1, +}; + +struct vpfe_ipipe_gamma_entry { + /* 10 bit slope */ + short slope; + /* 10 bit offset */ + unsigned short offset; +}; + +/* Structure for configuring Gamma correction module */ +struct vpfe_ipipe_gamma { + /* 0 - Enable Gamma correction for Red + * 1 - bypass Gamma correction. Data is divided by 16 + */ + unsigned char bypass_r; + /* 0 - Enable Gamma correction for Blue + * 1 - bypass Gamma correction. Data is divided by 16 + */ + unsigned char bypass_b; + /* 0 - Enable Gamma correction for Green + * 1 - bypass Gamma correction. Data is divided by 16 + */ + unsigned char bypass_g; + /* IPIPE_GAMMA_TBL_RAM or IPIPE_GAMMA_TBL_ROM */ + enum vpfe_ipipe_gamma_tbl_sel tbl_sel; + /* Table size for RAM gamma table. + */ + enum vpfe_ipipe_gamma_tbl_size tbl_size; + /* R table */ + struct vpfe_ipipe_gamma_entry table_r[VPFE_IPIPE_MAX_SIZE_GAMMA]; + /* Blue table */ + struct vpfe_ipipe_gamma_entry table_b[VPFE_IPIPE_MAX_SIZE_GAMMA]; + /* Green table */ + struct vpfe_ipipe_gamma_entry table_g[VPFE_IPIPE_MAX_SIZE_GAMMA]; +}; + +#define VPFE_IPIPE_MAX_SIZE_3D_LUT 729 + +struct vpfe_ipipe_3d_lut_entry { + /* 10 bit entry for red */ + unsigned short r; + /* 10 bit entry for green */ + unsigned short g; + /* 10 bit entry for blue */ + unsigned short b; +}; + +/* structure for 3D-LUT */ +struct vpfe_ipipe_3d_lut { + /* enable/disable 3D lut */ + unsigned char en; + /* 3D - LUT table entry */ + struct vpfe_ipipe_3d_lut_entry table[VPFE_IPIPE_MAX_SIZE_3D_LUT]; +}; + +/* Struct for configuring rgb2ycbcr module */ +struct vpfe_ipipe_rgb2yuv { + /* Matrix coefficient for RY S12Q8 */ + struct ipipe_float_s16 coef_ry; + /* Matrix coefficient for GY S12Q8 */ + struct ipipe_float_s16 coef_gy; + /* Matrix coefficient for BY S12Q8 */ + struct ipipe_float_s16 coef_by; + /* Matrix coefficient for RCb S12Q8 */ + struct ipipe_float_s16 coef_rcb; + /* Matrix coefficient for GCb S12Q8 */ + struct ipipe_float_s16 coef_gcb; + /* Matrix coefficient for BCb S12Q8 */ + struct ipipe_float_s16 coef_bcb; + /* Matrix coefficient for RCr S12Q8 */ + struct ipipe_float_s16 coef_rcr; + /* Matrix coefficient for GCr S12Q8 */ + struct ipipe_float_s16 coef_gcr; + /* Matrix coefficient for BCr S12Q8 */ + struct ipipe_float_s16 coef_bcr; + /* Output offset for R S11 */ + int out_ofst_y; + /* Output offset for Cb S11 */ + int out_ofst_cb; + /* Output offset for Cr S11 */ + int out_ofst_cr; +}; + +enum vpfe_ipipe_gbce_type { + VPFE_IPIPE_GBCE_Y_VAL_TBL = 0, + VPFE_IPIPE_GBCE_GAIN_TBL = 1, +}; + +#define VPFE_IPIPE_MAX_SIZE_GBCE_LUT 1024 + +/* structure for Global brightness and Contrast */ +struct vpfe_ipipe_gbce { + /* enable/disable GBCE */ + unsigned char en; + /* Y - value table or Gain table */ + enum vpfe_ipipe_gbce_type type; + /* ptr to LUT for GBCE with 1024 entries */ + unsigned short table[VPFE_IPIPE_MAX_SIZE_GBCE_LUT]; +}; + +/* Chrominance position. Applicable only for YCbCr input + * Applied after edge enhancement + */ +enum vpfe_chr_pos { + /* Co-siting, same position with luminance */ + VPFE_IPIPE_YUV422_CHR_POS_COSITE = 0, + /* Centering, In the middle of luminance */ + VPFE_IPIPE_YUV422_CHR_POS_CENTRE = 1, +}; + +/* Structure for configuring yuv422 conversion module */ +struct vpfe_ipipe_yuv422_conv { + /* Max Chrominance value */ + unsigned char en_chrom_lpf; + /* 1 - enable LPF for chrminance, 0 - disable */ + enum vpfe_chr_pos chrom_pos; +}; + +#define VPFE_IPIPE_MAX_SIZE_YEE_LUT 1024 + +enum vpfe_ipipe_yee_merge_meth { + VPFE_IPIPE_YEE_ABS_MAX = 0, + VPFE_IPIPE_YEE_EE_ES = 1, +}; + +/* Structure for configuring YUV Edge Enhancement module */ +struct vpfe_ipipe_yee { + /* 1 - enable enhancement, 0 - disable */ + unsigned char en; + /* enable/disable halo reduction in edge sharpner */ + unsigned char en_halo_red; + /* Merge method between Edge Enhancer and Edge sharpner */ + enum vpfe_ipipe_yee_merge_meth merge_meth; + /* HPF Shift length */ + unsigned char hpf_shft; + /* HPF Coefficient 00, S10 */ + short hpf_coef_00; + /* HPF Coefficient 01, S10 */ + short hpf_coef_01; + /* HPF Coefficient 02, S10 */ + short hpf_coef_02; + /* HPF Coefficient 10, S10 */ + short hpf_coef_10; + /* HPF Coefficient 11, S10 */ + short hpf_coef_11; + /* HPF Coefficient 12, S10 */ + short hpf_coef_12; + /* HPF Coefficient 20, S10 */ + short hpf_coef_20; + /* HPF Coefficient 21, S10 */ + short hpf_coef_21; + /* HPF Coefficient 22, S10 */ + short hpf_coef_22; + /* Lower threshold before referring to LUT */ + unsigned short yee_thr; + /* Edge sharpener Gain */ + unsigned short es_gain; + /* Edge sharpener lower threshold */ + unsigned short es_thr1; + /* Edge sharpener upper threshold */ + unsigned short es_thr2; + /* Edge sharpener gain on gradient */ + unsigned short es_gain_grad; + /* Edge sharpener offset on gradient */ + unsigned short es_ofst_grad; + /* Ptr to EE table. Must have 1024 entries */ + short table[VPFE_IPIPE_MAX_SIZE_YEE_LUT]; +}; + +enum vpfe_ipipe_car_meth { + /* Chromatic Gain Control */ + VPFE_IPIPE_CAR_CHR_GAIN_CTRL = 0, + /* Dynamic switching between CHR_GAIN_CTRL + * and MED_FLTR + */ + VPFE_IPIPE_CAR_DYN_SWITCH = 1, + /* Median Filter */ + VPFE_IPIPE_CAR_MED_FLTR = 2, +}; + +enum vpfe_ipipe_car_hpf_type { + VPFE_IPIPE_CAR_HPF_Y = 0, + VPFE_IPIPE_CAR_HPF_H = 1, + VPFE_IPIPE_CAR_HPF_V = 2, + VPFE_IPIPE_CAR_HPF_2D = 3, + /* 2D HPF from YUV Edge Enhancement */ + VPFE_IPIPE_CAR_HPF_2D_YEE = 4, +}; + +struct vpfe_ipipe_car_gain { + /* csup_gain */ + unsigned char gain; + /* csup_shf. */ + unsigned char shft; + /* gain minimum */ + unsigned short gain_min; +}; + +/* Structure for Chromatic Artifact Reduction */ +struct vpfe_ipipe_car { + /* enable/disable */ + unsigned char en; + /* Gain control or Dynamic switching */ + enum vpfe_ipipe_car_meth meth; + /* Gain1 function configuration for Gain control */ + struct vpfe_ipipe_car_gain gain1; + /* Gain2 function configuration for Gain control */ + struct vpfe_ipipe_car_gain gain2; + /* HPF type used for CAR */ + enum vpfe_ipipe_car_hpf_type hpf; + /* csup_thr: HPF threshold for Gain control */ + unsigned char hpf_thr; + /* Down shift value for hpf. 2 bits */ + unsigned char hpf_shft; + /* switch limit for median filter */ + unsigned char sw0; + /* switch coefficient for Gain control */ + unsigned char sw1; +}; + +/* structure for Chromatic Gain Suppression */ +struct vpfe_ipipe_cgs { + /* enable/disable */ + unsigned char en; + /* gain1 bright side threshold */ + unsigned char h_thr; + /* gain1 bright side slope */ + unsigned char h_slope; + /* gain1 down shift value for bright side */ + unsigned char h_shft; + /* gain1 bright side minimum gain */ + unsigned char h_min; +}; + +/* Max pixels allowed in the input. If above this either decimation + * or frame division mode to be enabled + */ +#define VPFE_IPIPE_MAX_INPUT_WIDTH 2600 + +struct vpfe_ipipe_input_config { + unsigned int vst; + unsigned int hst; +}; + +/** + * struct vpfe_ipipe_config - IPIPE engine configuration (user) + * @input_config: Pointer to structure for ipipe configuration. + * @flag: Specifies which ISP IPIPE functions should be enabled. + * @lutdpc: Pointer to luma enhancement structure. + * @otfdpc: Pointer to structure for defect correction. + * @nf1: Pointer to structure for Noise Filter. + * @nf2: Pointer to structure for Noise Filter. + * @gic: Pointer to structure for Green Imbalance. + * @wbal: Pointer to structure for White Balance. + * @cfa: Pointer to structure containing the CFA interpolation. + * @rgb2rgb1: Pointer to structure for RGB to RGB Blending. + * @rgb2rgb2: Pointer to structure for RGB to RGB Blending. + * @gamma: Pointer to gamma structure. + * @lut: Pointer to structure for 3D LUT. + * @rgb2yuv: Pointer to structure for RGB-YCbCr conversion. + * @gbce: Pointer to structure for Global Brightness,Contrast Control. + * @yuv422_conv: Pointer to structure for YUV 422 conversion. + * @yee: Pointer to structure for Edge Enhancer. + * @car: Pointer to structure for Chromatic Artifact Reduction. + * @cgs: Pointer to structure for Chromatic Gain Suppression. + */ +struct vpfe_ipipe_config { + __u32 flag; + struct vpfe_ipipe_input_config __user *input_config; + struct vpfe_ipipe_lutdpc __user *lutdpc; + struct vpfe_ipipe_otfdpc __user *otfdpc; + struct vpfe_ipipe_nf __user *nf1; + struct vpfe_ipipe_nf __user *nf2; + struct vpfe_ipipe_gic __user *gic; + struct vpfe_ipipe_wb __user *wbal; + struct vpfe_ipipe_cfa __user *cfa; + struct vpfe_ipipe_rgb2rgb __user *rgb2rgb1; + struct vpfe_ipipe_rgb2rgb __user *rgb2rgb2; + struct vpfe_ipipe_gamma __user *gamma; + struct vpfe_ipipe_3d_lut __user *lut; + struct vpfe_ipipe_rgb2yuv __user *rgb2yuv; + struct vpfe_ipipe_gbce __user *gbce; + struct vpfe_ipipe_yuv422_conv __user *yuv422_conv; + struct vpfe_ipipe_yee __user *yee; + struct vpfe_ipipe_car __user *car; + struct vpfe_ipipe_cgs __user *cgs; +}; + +/******************************************************************* +** Resizer API structures +*******************************************************************/ +/* Interpolation types used for horizontal rescale */ +enum vpfe_rsz_intp_t { + VPFE_RSZ_INTP_CUBIC, + VPFE_RSZ_INTP_LINEAR +}; + +/* Horizontal LPF intensity selection */ +enum vpfe_rsz_h_lpf_lse_t { + VPFE_RSZ_H_LPF_LSE_INTERN, + VPFE_RSZ_H_LPF_LSE_USER_VAL +}; + +enum vpfe_rsz_down_scale_ave_sz { + VPFE_IPIPE_DWN_SCALE_1_OVER_2, + VPFE_IPIPE_DWN_SCALE_1_OVER_4, + VPFE_IPIPE_DWN_SCALE_1_OVER_8, + VPFE_IPIPE_DWN_SCALE_1_OVER_16, + VPFE_IPIPE_DWN_SCALE_1_OVER_32, + VPFE_IPIPE_DWN_SCALE_1_OVER_64, + VPFE_IPIPE_DWN_SCALE_1_OVER_128, + VPFE_IPIPE_DWN_SCALE_1_OVER_256 +}; + +struct vpfe_rsz_output_spec { + /* enable horizontal flip */ + unsigned char h_flip; + /* enable vertical flip */ + unsigned char v_flip; + /* line start offset for y. */ + unsigned int vst_y; + /* line start offset for c. Only for 420 */ + unsigned int vst_c; + /* vertical rescale interpolation type, YCbCr or Luminance */ + enum vpfe_rsz_intp_t v_typ_y; + /* vertical rescale interpolation type for Chrominance */ + enum vpfe_rsz_intp_t v_typ_c; + /* vertical lpf intensity - Luminance */ + unsigned char v_lpf_int_y; + /* vertical lpf intensity - Chrominance */ + unsigned char v_lpf_int_c; + /* horizontal rescale interpolation types, YCbCr or Luminance */ + enum vpfe_rsz_intp_t h_typ_y; + /* horizontal rescale interpolation types, Chrominance */ + enum vpfe_rsz_intp_t h_typ_c; + /* horizontal lpf intensity - Luminance */ + unsigned char h_lpf_int_y; + /* horizontal lpf intensity - Chrominance */ + unsigned char h_lpf_int_c; + /* Use down scale mode for scale down */ + unsigned char en_down_scale; + /* if downscale, set the downscale more average size for horizontal + * direction. Used only if output width and height is less than + * input sizes + */ + enum vpfe_rsz_down_scale_ave_sz h_dscale_ave_sz; + /* if downscale, set the downscale more average size for vertical + * direction. Used only if output width and height is less than + * input sizes + */ + enum vpfe_rsz_down_scale_ave_sz v_dscale_ave_sz; + /* Y offset. If set, the offset would be added to the base address + */ + unsigned int user_y_ofst; + /* C offset. If set, the offset would be added to the base address + */ + unsigned int user_c_ofst; +}; + +struct vpfe_rsz_config_params { + unsigned int vst; + /* horizontal start position of the image + * data to IPIPE + */ + unsigned int hst; + /* output spec of the image data coming out of resizer - 0(UYVY). + */ + struct vpfe_rsz_output_spec output1; + /* output spec of the image data coming out of resizer - 1(UYVY). + */ + struct vpfe_rsz_output_spec output2; + /* 0 , chroma sample at odd pixel, 1 - even pixel */ + unsigned char chroma_sample_even; + unsigned char frame_div_mode_en; + unsigned char yuv_y_min; + unsigned char yuv_y_max; + unsigned char yuv_c_min; + unsigned char yuv_c_max; + enum vpfe_chr_pos out_chr_pos; + unsigned char bypass; +}; + +/* Structure for VIDIOC_VPFE_RSZ_[S/G]_CONFIG IOCTLs */ +struct vpfe_rsz_config { + struct vpfe_rsz_config_params *config; +}; + +#endif /* _DAVINCI_VPFE_USER_H */ diff --git a/drivers/staging/media/davinci_vpfe/dm365_ipipe.c b/drivers/staging/media/davinci_vpfe/dm365_ipipe.c new file mode 100644 index 00000000000..b7044a380fe --- /dev/null +++ b/drivers/staging/media/davinci_vpfe/dm365_ipipe.c @@ -0,0 +1,1863 @@ +/* + * Copyright (C) 2012 Texas Instruments Inc + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Contributors: + * Manjunath Hadli <manjunath.hadli@ti.com> + * Prabhakar Lad <prabhakar.lad@ti.com> + * + * + * IPIPE allows fine tuning of the input image using different + * tuning modules in IPIPE. Some examples :- Noise filter, Defect + * pixel correction etc. It essentially operate on Bayer Raw data + * or YUV raw data. To do image tuning, application call, + * + */ + +#include <linux/slab.h> + +#include "dm365_ipipe.h" +#include "dm365_ipipe_hw.h" +#include "vpfe_mc_capture.h" + +#define MIN_OUT_WIDTH 32 +#define MIN_OUT_HEIGHT 32 + +/* ipipe input format's */ +static const unsigned int ipipe_input_fmts[] = { + V4L2_MBUS_FMT_UYVY8_2X8, + V4L2_MBUS_FMT_SGRBG12_1X12, + V4L2_MBUS_FMT_SGRBG10_DPCM8_1X8, + V4L2_MBUS_FMT_SGRBG10_ALAW8_1X8, +}; + +/* ipipe output format's */ +static const unsigned int ipipe_output_fmts[] = { + V4L2_MBUS_FMT_UYVY8_2X8, +}; + +static int ipipe_validate_lutdpc_params(struct vpfe_ipipe_lutdpc *lutdpc) +{ + int i; + + if (lutdpc->en > 1 || lutdpc->repl_white > 1 || + lutdpc->dpc_size > LUT_DPC_MAX_SIZE) + return -EINVAL; + + if (lutdpc->en && !lutdpc->table) + return -EINVAL; + + for (i = 0; i < lutdpc->dpc_size; i++) + if (lutdpc->table[i].horz_pos > LUT_DPC_H_POS_MASK || + lutdpc->table[i].vert_pos > LUT_DPC_V_POS_MASK) + return -EINVAL; + + return 0; +} + +static int ipipe_set_lutdpc_params(struct vpfe_ipipe_device *ipipe, void *param) +{ + struct vpfe_ipipe_lutdpc *lutdpc = &ipipe->config.lutdpc; + struct vpfe_ipipe_lutdpc *dpc_param; + struct device *dev; + + if (!param) { + memset((void *)lutdpc, 0, sizeof(struct vpfe_ipipe_lutdpc)); + goto success; + } + + dev = ipipe->subdev.v4l2_dev->dev; + dpc_param = (struct vpfe_ipipe_lutdpc *)param; + lutdpc->en = dpc_param->en; + lutdpc->repl_white = dpc_param->repl_white; + lutdpc->dpc_size = dpc_param->dpc_size; + memcpy(&lutdpc->table, &dpc_param->table, + (dpc_param->dpc_size * sizeof(struct vpfe_ipipe_lutdpc_entry))); + if (ipipe_validate_lutdpc_params(lutdpc) < 0) + return -EINVAL; + +success: + ipipe_set_lutdpc_regs(ipipe->base_addr, ipipe->isp5_base_addr, lutdpc); + + return 0; +} + +static int ipipe_get_lutdpc_params(struct vpfe_ipipe_device *ipipe, void *param) +{ + struct vpfe_ipipe_lutdpc *lut_param = (struct vpfe_ipipe_lutdpc *)param; + struct vpfe_ipipe_lutdpc *lutdpc = &ipipe->config.lutdpc; + + lut_param->en = lutdpc->en; + lut_param->repl_white = lutdpc->repl_white; + lut_param->dpc_size = lutdpc->dpc_size; + memcpy(&lut_param->table, &lutdpc->table, + (lutdpc->dpc_size * sizeof(struct vpfe_ipipe_lutdpc_entry))); + + return 0; +} + +static int ipipe_set_input_config(struct vpfe_ipipe_device *ipipe, void *param) +{ + struct vpfe_ipipe_input_config *config = &ipipe->config.input_config; + + if (!param) + memset(config, 0, sizeof(struct vpfe_ipipe_input_config)); + else + memcpy(config, param, sizeof(struct vpfe_ipipe_input_config)); + return 0; +} + +static int ipipe_get_input_config(struct vpfe_ipipe_device *ipipe, void *param) +{ + struct vpfe_ipipe_input_config *config = &ipipe->config.input_config; + + if (!param) + return -EINVAL; + + memcpy(param, config, sizeof(struct vpfe_ipipe_input_config)); + + return 0; +} + +static int ipipe_validate_otfdpc_params(struct vpfe_ipipe_otfdpc *dpc_param) +{ + struct vpfe_ipipe_otfdpc_2_0_cfg *dpc_2_0; + struct vpfe_ipipe_otfdpc_3_0_cfg *dpc_3_0; + + if (dpc_param->en > 1) + return -EINVAL; + + if (dpc_param->alg == VPFE_IPIPE_OTFDPC_2_0) { + dpc_2_0 = &dpc_param->alg_cfg.dpc_2_0; + if (dpc_2_0->det_thr.r > OTFDPC_DPC2_THR_MASK || + dpc_2_0->det_thr.gr > OTFDPC_DPC2_THR_MASK || + dpc_2_0->det_thr.gb > OTFDPC_DPC2_THR_MASK || + dpc_2_0->det_thr.b > OTFDPC_DPC2_THR_MASK || + dpc_2_0->corr_thr.r > OTFDPC_DPC2_THR_MASK || + dpc_2_0->corr_thr.gr > OTFDPC_DPC2_THR_MASK || + dpc_2_0->corr_thr.gb > OTFDPC_DPC2_THR_MASK || + dpc_2_0->corr_thr.b > OTFDPC_DPC2_THR_MASK) + return -EINVAL; + return 0; + } + + dpc_3_0 = &dpc_param->alg_cfg.dpc_3_0; + + if (dpc_3_0->act_adj_shf > OTF_DPC3_0_SHF_MASK || + dpc_3_0->det_thr > OTF_DPC3_0_DET_MASK || + dpc_3_0->det_slp > OTF_DPC3_0_SLP_MASK || + dpc_3_0->det_thr_min > OTF_DPC3_0_DET_MASK || + dpc_3_0->det_thr_max > OTF_DPC3_0_DET_MASK || + dpc_3_0->corr_thr > OTF_DPC3_0_CORR_MASK || + dpc_3_0->corr_slp > OTF_DPC3_0_SLP_MASK || + dpc_3_0->corr_thr_min > OTF_DPC3_0_CORR_MASK || + dpc_3_0->corr_thr_max > OTF_DPC3_0_CORR_MASK) + return -EINVAL; + + return 0; +} + +static int ipipe_set_otfdpc_params(struct vpfe_ipipe_device *ipipe, void *param) +{ + struct vpfe_ipipe_otfdpc *dpc_param = (struct vpfe_ipipe_otfdpc *)param; + struct vpfe_ipipe_otfdpc *otfdpc = &ipipe->config.otfdpc; + struct device *dev; + + if (!param) { + memset((void *)otfdpc, 0, sizeof(struct ipipe_otfdpc_2_0)); + goto success; + } + dev = ipipe->subdev.v4l2_dev->dev; + memcpy(otfdpc, dpc_param, sizeof(struct vpfe_ipipe_otfdpc)); + if (ipipe_validate_otfdpc_params(otfdpc) < 0) { + dev_err(dev, "Invalid otfdpc params\n"); + return -EINVAL; + } + +success: + ipipe_set_otfdpc_regs(ipipe->base_addr, otfdpc); + + return 0; +} + +static int ipipe_get_otfdpc_params(struct vpfe_ipipe_device *ipipe, void *param) +{ + struct vpfe_ipipe_otfdpc *dpc_param = (struct vpfe_ipipe_otfdpc *)param; + struct vpfe_ipipe_otfdpc *otfdpc = &ipipe->config.otfdpc; + + memcpy(dpc_param, otfdpc, sizeof(struct vpfe_ipipe_otfdpc)); + return 0; +} + +static int ipipe_validate_nf_params(struct vpfe_ipipe_nf *nf_param) +{ + int i; + + if (nf_param->en > 1 || nf_param->shft_val > D2F_SHFT_VAL_MASK || + nf_param->spread_val > D2F_SPR_VAL_MASK || + nf_param->apply_lsc_gain > 1 || + nf_param->edge_det_min_thr > D2F_EDGE_DET_THR_MASK || + nf_param->edge_det_max_thr > D2F_EDGE_DET_THR_MASK) + return -EINVAL; + + for (i = 0; i < VPFE_IPIPE_NF_THR_TABLE_SIZE; i++) + if (nf_param->thr[i] > D2F_THR_VAL_MASK) + return -EINVAL; + + for (i = 0; i < VPFE_IPIPE_NF_STR_TABLE_SIZE; i++) + if (nf_param->str[i] > D2F_STR_VAL_MASK) + return -EINVAL; + + return 0; +} + +static int ipipe_set_nf_params(struct vpfe_ipipe_device *ipipe, + unsigned int id, void *param) +{ + struct vpfe_ipipe_nf *nf_param = (struct vpfe_ipipe_nf *)param; + struct vpfe_ipipe_nf *nf = &ipipe->config.nf1; + struct device *dev; + + if (id == IPIPE_D2F_2ND) + nf = &ipipe->config.nf2; + + if (!nf_param) { + memset((void *)nf, 0, sizeof(struct vpfe_ipipe_nf)); + goto success; + } + + dev = ipipe->subdev.v4l2_dev->dev; + memcpy(nf, nf_param, sizeof(struct vpfe_ipipe_nf)); + if (ipipe_validate_nf_params(nf) < 0) { + dev_err(dev, "Invalid nf params\n"); + return -EINVAL; + } + +success: + ipipe_set_d2f_regs(ipipe->base_addr, id, nf); + + return 0; +} + +static int ipipe_set_nf1_params(struct vpfe_ipipe_device *ipipe, void *param) +{ + return ipipe_set_nf_params(ipipe, IPIPE_D2F_1ST, param); +} + +static int ipipe_set_nf2_params(struct vpfe_ipipe_device *ipipe, void *param) +{ + return ipipe_set_nf_params(ipipe, IPIPE_D2F_2ND, param); +} + +static int ipipe_get_nf_params(struct vpfe_ipipe_device *ipipe, + unsigned int id, void *param) +{ + struct vpfe_ipipe_nf *nf_param = (struct vpfe_ipipe_nf *)param; + struct vpfe_ipipe_nf *nf = &ipipe->config.nf1; + + if (id == IPIPE_D2F_2ND) + nf = &ipipe->config.nf2; + + memcpy(nf_param, nf, sizeof(struct vpfe_ipipe_nf)); + + return 0; +} + +static int ipipe_get_nf1_params(struct vpfe_ipipe_device *ipipe, void *param) +{ + return ipipe_get_nf_params(ipipe, IPIPE_D2F_1ST, param); +} + +static int ipipe_get_nf2_params(struct vpfe_ipipe_device *ipipe, void *param) +{ + return ipipe_get_nf_params(ipipe, IPIPE_D2F_2ND, param); +} + +static int ipipe_validate_gic_params(struct vpfe_ipipe_gic *gic) +{ + if (gic->en > 1 || gic->gain > GIC_GAIN_MASK || + gic->thr > GIC_THR_MASK || gic->slope > GIC_SLOPE_MASK || + gic->apply_lsc_gain > 1 || + gic->nf2_thr_gain.integer > GIC_NFGAN_INT_MASK || + gic->nf2_thr_gain.decimal > GIC_NFGAN_DECI_MASK) + return -EINVAL; + + return 0; +} + +static int ipipe_set_gic_params(struct vpfe_ipipe_device *ipipe, void *param) +{ + struct vpfe_ipipe_gic *gic_param = (struct vpfe_ipipe_gic *)param; + struct device *dev = ipipe->subdev.v4l2_dev->dev; + struct vpfe_ipipe_gic *gic = &ipipe->config.gic; + + if (!gic_param) { + memset((void *)gic, 0, sizeof(struct vpfe_ipipe_gic)); + goto success; + } + + memcpy(gic, gic_param, sizeof(struct vpfe_ipipe_gic)); + if (ipipe_validate_gic_params(gic) < 0) { + dev_err(dev, "Invalid gic params\n"); + return -EINVAL; + } + +success: + ipipe_set_gic_regs(ipipe->base_addr, gic); + + return 0; +} + +static int ipipe_get_gic_params(struct vpfe_ipipe_device *ipipe, void *param) +{ + struct vpfe_ipipe_gic *gic_param = (struct vpfe_ipipe_gic *)param; + struct vpfe_ipipe_gic *gic = &ipipe->config.gic; + + memcpy(gic_param, gic, sizeof(struct vpfe_ipipe_gic)); + + return 0; +} + +static int ipipe_validate_wb_params(struct vpfe_ipipe_wb *wbal) +{ + if (wbal->ofst_r > WB_OFFSET_MASK || + wbal->ofst_gr > WB_OFFSET_MASK || + wbal->ofst_gb > WB_OFFSET_MASK || + wbal->ofst_b > WB_OFFSET_MASK || + wbal->gain_r.integer > WB_GAIN_INT_MASK || + wbal->gain_r.decimal > WB_GAIN_DECI_MASK || + wbal->gain_gr.integer > WB_GAIN_INT_MASK || + wbal->gain_gr.decimal > WB_GAIN_DECI_MASK || + wbal->gain_gb.integer > WB_GAIN_INT_MASK || + wbal->gain_gb.decimal > WB_GAIN_DECI_MASK || + wbal->gain_b.integer > WB_GAIN_INT_MASK || + wbal->gain_b.decimal > WB_GAIN_DECI_MASK) + return -EINVAL; + + return 0; +} + +static int ipipe_set_wb_params(struct vpfe_ipipe_device *ipipe, void *param) +{ + struct vpfe_ipipe_wb *wb_param = (struct vpfe_ipipe_wb *)param; + struct vpfe_ipipe_wb *wbal = &ipipe->config.wbal; + + if (!wb_param) { + const struct vpfe_ipipe_wb wb_defaults = { + .gain_r = {2, 0x0}, + .gain_gr = {2, 0x0}, + .gain_gb = {2, 0x0}, + .gain_b = {2, 0x0} + }; + memcpy(wbal, &wb_defaults, sizeof(struct vpfe_ipipe_wb)); + goto success; + } + + memcpy(wbal, wb_param, sizeof(struct vpfe_ipipe_wb)); + if (ipipe_validate_wb_params(wbal) < 0) + return -EINVAL; + +success: + ipipe_set_wb_regs(ipipe->base_addr, wbal); + + return 0; +} + +static int ipipe_get_wb_params(struct vpfe_ipipe_device *ipipe, void *param) +{ + struct vpfe_ipipe_wb *wb_param = (struct vpfe_ipipe_wb *)param; + struct vpfe_ipipe_wb *wbal = &ipipe->config.wbal; + + memcpy(wb_param, wbal, sizeof(struct vpfe_ipipe_wb)); + return 0; +} + +static int ipipe_validate_cfa_params(struct vpfe_ipipe_cfa *cfa) +{ + if (cfa->hpf_thr_2dir > CFA_HPF_THR_2DIR_MASK || + cfa->hpf_slp_2dir > CFA_HPF_SLOPE_2DIR_MASK || + cfa->hp_mix_thr_2dir > CFA_HPF_MIX_THR_2DIR_MASK || + cfa->hp_mix_slope_2dir > CFA_HPF_MIX_SLP_2DIR_MASK || + cfa->dir_thr_2dir > CFA_DIR_THR_2DIR_MASK || + cfa->dir_slope_2dir > CFA_DIR_SLP_2DIR_MASK || + cfa->nd_wt_2dir > CFA_ND_WT_2DIR_MASK || + cfa->hue_fract_daa > CFA_DAA_HUE_FRA_MASK || + cfa->edge_thr_daa > CFA_DAA_EDG_THR_MASK || + cfa->thr_min_daa > CFA_DAA_THR_MIN_MASK || + cfa->thr_slope_daa > CFA_DAA_THR_SLP_MASK || + cfa->slope_min_daa > CFA_DAA_SLP_MIN_MASK || + cfa->slope_slope_daa > CFA_DAA_SLP_SLP_MASK || + cfa->lp_wt_daa > CFA_DAA_LP_WT_MASK) + return -EINVAL; + + return 0; +} + +static int ipipe_set_cfa_params(struct vpfe_ipipe_device *ipipe, void *param) +{ + struct vpfe_ipipe_cfa *cfa_param = (struct vpfe_ipipe_cfa *)param; + struct vpfe_ipipe_cfa *cfa = &ipipe->config.cfa; + + if (!cfa_param) { + memset(cfa, 0, sizeof(struct vpfe_ipipe_cfa)); + cfa->alg = VPFE_IPIPE_CFA_ALG_2DIRAC; + goto success; + } + + memcpy(cfa, cfa_param, sizeof(struct vpfe_ipipe_cfa)); + if (ipipe_validate_cfa_params(cfa) < 0) + return -EINVAL; + +success: + ipipe_set_cfa_regs(ipipe->base_addr, cfa); + + return 0; +} + +static int ipipe_get_cfa_params(struct vpfe_ipipe_device *ipipe, void *param) +{ + struct vpfe_ipipe_cfa *cfa_param = (struct vpfe_ipipe_cfa *)param; + struct vpfe_ipipe_cfa *cfa = &ipipe->config.cfa; + + memcpy(cfa_param, cfa, sizeof(struct vpfe_ipipe_cfa)); + return 0; +} + +static int +ipipe_validate_rgb2rgb_params(struct vpfe_ipipe_rgb2rgb *rgb2rgb, + unsigned int id) +{ + u32 gain_int_upper = RGB2RGB_1_GAIN_INT_MASK; + u32 offset_upper = RGB2RGB_1_OFST_MASK; + + if (id == IPIPE_RGB2RGB_2) { + offset_upper = RGB2RGB_2_OFST_MASK; + gain_int_upper = RGB2RGB_2_GAIN_INT_MASK; + } + + if (rgb2rgb->coef_rr.decimal > RGB2RGB_GAIN_DECI_MASK || + rgb2rgb->coef_rr.integer > gain_int_upper) + return -EINVAL; + + if (rgb2rgb->coef_gr.decimal > RGB2RGB_GAIN_DECI_MASK || + rgb2rgb->coef_gr.integer > gain_int_upper) + return -EINVAL; + + if (rgb2rgb->coef_br.decimal > RGB2RGB_GAIN_DECI_MASK || + rgb2rgb->coef_br.integer > gain_int_upper) + return -EINVAL; + + if (rgb2rgb->coef_rg.decimal > RGB2RGB_GAIN_DECI_MASK || + rgb2rgb->coef_rg.integer > gain_int_upper) + return -EINVAL; + + if (rgb2rgb->coef_gg.decimal > RGB2RGB_GAIN_DECI_MASK || + rgb2rgb->coef_gg.integer > gain_int_upper) + return -EINVAL; + + if (rgb2rgb->coef_bg.decimal > RGB2RGB_GAIN_DECI_MASK || + rgb2rgb->coef_bg.integer > gain_int_upper) + return -EINVAL; + + if (rgb2rgb->coef_rb.decimal > RGB2RGB_GAIN_DECI_MASK || + rgb2rgb->coef_rb.integer > gain_int_upper) + return -EINVAL; + + if (rgb2rgb->coef_gb.decimal > RGB2RGB_GAIN_DECI_MASK || + rgb2rgb->coef_gb.integer > gain_int_upper) + return -EINVAL; + + if (rgb2rgb->coef_bb.decimal > RGB2RGB_GAIN_DECI_MASK || + rgb2rgb->coef_bb.integer > gain_int_upper) + return -EINVAL; + + if (rgb2rgb->out_ofst_r > offset_upper || + rgb2rgb->out_ofst_g > offset_upper || + rgb2rgb->out_ofst_b > offset_upper) + return -EINVAL; + + return 0; +} + +static int ipipe_set_rgb2rgb_params(struct vpfe_ipipe_device *ipipe, + unsigned int id, void *param) +{ + struct vpfe_ipipe_rgb2rgb *rgb2rgb = &ipipe->config.rgb2rgb1; + struct device *dev = ipipe->subdev.v4l2_dev->dev; + struct vpfe_ipipe_rgb2rgb *rgb2rgb_param; + + rgb2rgb_param = (struct vpfe_ipipe_rgb2rgb *)param; + + if (id == IPIPE_RGB2RGB_2) + rgb2rgb = &ipipe->config.rgb2rgb2; + + if (!rgb2rgb_param) { + const struct vpfe_ipipe_rgb2rgb rgb2rgb_defaults = { + .coef_rr = {1, 0}, /* 256 */ + .coef_gr = {0, 0}, + .coef_br = {0, 0}, + .coef_rg = {0, 0}, + .coef_gg = {1, 0}, /* 256 */ + .coef_bg = {0, 0}, + .coef_rb = {0, 0}, + .coef_gb = {0, 0}, + .coef_bb = {1, 0}, /* 256 */ + }; + /* Copy defaults for rgb2rgb conversion */ + memcpy(rgb2rgb, &rgb2rgb_defaults, + sizeof(struct vpfe_ipipe_rgb2rgb)); + goto success; + } + + memcpy(rgb2rgb, rgb2rgb_param, sizeof(struct vpfe_ipipe_rgb2rgb)); + if (ipipe_validate_rgb2rgb_params(rgb2rgb, id) < 0) { + dev_err(dev, "Invalid rgb2rgb params\n"); + return -EINVAL; + } + +success: + ipipe_set_rgb2rgb_regs(ipipe->base_addr, id, rgb2rgb); + + return 0; +} + +static int +ipipe_set_rgb2rgb_1_params(struct vpfe_ipipe_device *ipipe, void *param) +{ + return ipipe_set_rgb2rgb_params(ipipe, IPIPE_RGB2RGB_1, param); +} + +static int +ipipe_set_rgb2rgb_2_params(struct vpfe_ipipe_device *ipipe, void *param) +{ + return ipipe_set_rgb2rgb_params(ipipe, IPIPE_RGB2RGB_2, param); +} + +static int ipipe_get_rgb2rgb_params(struct vpfe_ipipe_device *ipipe, + unsigned int id, void *param) +{ + struct vpfe_ipipe_rgb2rgb *rgb2rgb = &ipipe->config.rgb2rgb1; + struct vpfe_ipipe_rgb2rgb *rgb2rgb_param; + + rgb2rgb_param = (struct vpfe_ipipe_rgb2rgb *)param; + + if (id == IPIPE_RGB2RGB_2) + rgb2rgb = &ipipe->config.rgb2rgb2; + + memcpy(rgb2rgb_param, rgb2rgb, sizeof(struct vpfe_ipipe_rgb2rgb)); + + return 0; +} + +static int +ipipe_get_rgb2rgb_1_params(struct vpfe_ipipe_device *ipipe, void *param) +{ + return ipipe_get_rgb2rgb_params(ipipe, IPIPE_RGB2RGB_1, param); +} + +static int +ipipe_get_rgb2rgb_2_params(struct vpfe_ipipe_device *ipipe, void *param) +{ + return ipipe_get_rgb2rgb_params(ipipe, IPIPE_RGB2RGB_2, param); +} + +static int +ipipe_validate_gamma_entry(struct vpfe_ipipe_gamma_entry *table, int size) +{ + int i; + + if (!table) + return -EINVAL; + + for (i = 0; i < size; i++) + if (table[i].slope > GAMMA_MASK || + table[i].offset > GAMMA_MASK) + return -EINVAL; + + return 0; +} + +static int +ipipe_validate_gamma_params(struct vpfe_ipipe_gamma *gamma, struct device *dev) +{ + int table_size; + int err; + + if (gamma->bypass_r > 1 || + gamma->bypass_b > 1 || + gamma->bypass_g > 1) + return -EINVAL; + + if (gamma->tbl_sel != VPFE_IPIPE_GAMMA_TBL_RAM) + return 0; + + table_size = gamma->tbl_size; + if (!gamma->bypass_r) { + err = ipipe_validate_gamma_entry(gamma->table_r, table_size); + if (err) { + dev_err(dev, "GAMMA R - table entry invalid\n"); + return err; + } + } + + if (!gamma->bypass_b) { + err = ipipe_validate_gamma_entry(gamma->table_b, table_size); + if (err) { + dev_err(dev, "GAMMA B - table entry invalid\n"); + return err; + } + } + + if (!gamma->bypass_g) { + err = ipipe_validate_gamma_entry(gamma->table_g, table_size); + if (err) { + dev_err(dev, "GAMMA G - table entry invalid\n"); + return err; + } + } + + return 0; +} + +static int +ipipe_set_gamma_params(struct vpfe_ipipe_device *ipipe, void *param) +{ + struct vpfe_ipipe_gamma *gamma_param = (struct vpfe_ipipe_gamma *)param; + struct vpfe_ipipe_gamma *gamma = &ipipe->config.gamma; + struct device *dev = ipipe->subdev.v4l2_dev->dev; + int table_size; + + if (!gamma_param) { + memset(gamma, 0, sizeof(struct vpfe_ipipe_gamma)); + gamma->tbl_sel = VPFE_IPIPE_GAMMA_TBL_ROM; + goto success; + } + + gamma->bypass_r = gamma_param->bypass_r; + gamma->bypass_b = gamma_param->bypass_b; + gamma->bypass_g = gamma_param->bypass_g; + gamma->tbl_sel = gamma_param->tbl_sel; + gamma->tbl_size = gamma_param->tbl_size; + + if (ipipe_validate_gamma_params(gamma, dev) < 0) + return -EINVAL; + + if (gamma_param->tbl_sel != VPFE_IPIPE_GAMMA_TBL_RAM) + goto success; + + table_size = gamma->tbl_size; + if (!gamma_param->bypass_r) + memcpy(&gamma->table_r, &gamma_param->table_r, + (table_size * sizeof(struct vpfe_ipipe_gamma_entry))); + + if (!gamma_param->bypass_b) + memcpy(&gamma->table_b, &gamma_param->table_b, + (table_size * sizeof(struct vpfe_ipipe_gamma_entry))); + + if (!gamma_param->bypass_g) + memcpy(&gamma->table_g, &gamma_param->table_g, + (table_size * sizeof(struct vpfe_ipipe_gamma_entry))); + +success: + ipipe_set_gamma_regs(ipipe->base_addr, ipipe->isp5_base_addr, gamma); + + return 0; +} + +static int ipipe_get_gamma_params(struct vpfe_ipipe_device *ipipe, void *param) +{ + struct vpfe_ipipe_gamma *gamma_param = (struct vpfe_ipipe_gamma *)param; + struct vpfe_ipipe_gamma *gamma = &ipipe->config.gamma; + struct device *dev = ipipe->subdev.v4l2_dev->dev; + int table_size; + + gamma_param->bypass_r = gamma->bypass_r; + gamma_param->bypass_g = gamma->bypass_g; + gamma_param->bypass_b = gamma->bypass_b; + gamma_param->tbl_sel = gamma->tbl_sel; + gamma_param->tbl_size = gamma->tbl_size; + + if (gamma->tbl_sel != VPFE_IPIPE_GAMMA_TBL_RAM) + return 0; + + table_size = gamma->tbl_size; + + if (!gamma->bypass_r && !gamma_param->table_r) { + dev_err(dev, + "ipipe_get_gamma_params: table ptr empty for R\n"); + return -EINVAL; + } + memcpy(gamma_param->table_r, gamma->table_r, + (table_size * sizeof(struct vpfe_ipipe_gamma_entry))); + + if (!gamma->bypass_g && !gamma_param->table_g) { + dev_err(dev, "ipipe_get_gamma_params: table ptr empty for G\n"); + return -EINVAL; + } + memcpy(gamma_param->table_g, gamma->table_g, + (table_size * sizeof(struct vpfe_ipipe_gamma_entry))); + + if (!gamma->bypass_b && !gamma_param->table_b) { + dev_err(dev, "ipipe_get_gamma_params: table ptr empty for B\n"); + return -EINVAL; + } + memcpy(gamma_param->table_b, gamma->table_b, + (table_size * sizeof(struct vpfe_ipipe_gamma_entry))); + + return 0; +} + +static int ipipe_validate_3d_lut_params(struct vpfe_ipipe_3d_lut *lut) +{ + int i; + + if (!lut->en) + return 0; + + for (i = 0; i < VPFE_IPIPE_MAX_SIZE_3D_LUT; i++) + if (lut->table[i].r > D3_LUT_ENTRY_MASK || + lut->table[i].g > D3_LUT_ENTRY_MASK || + lut->table[i].b > D3_LUT_ENTRY_MASK) + return -EINVAL; + + return 0; +} + +static int ipipe_get_3d_lut_params(struct vpfe_ipipe_device *ipipe, void *param) +{ + struct vpfe_ipipe_3d_lut *lut_param = (struct vpfe_ipipe_3d_lut *)param; + struct vpfe_ipipe_3d_lut *lut = &ipipe->config.lut; + struct device *dev = ipipe->subdev.v4l2_dev->dev; + + lut_param->en = lut->en; + if (!lut_param->table) { + dev_err(dev, "ipipe_get_3d_lut_params: Invalid table ptr\n"); + return -EINVAL; + } + + memcpy(lut_param->table, &lut->table, + (VPFE_IPIPE_MAX_SIZE_3D_LUT * + sizeof(struct vpfe_ipipe_3d_lut_entry))); + + return 0; +} + +static int +ipipe_set_3d_lut_params(struct vpfe_ipipe_device *ipipe, void *param) +{ + struct vpfe_ipipe_3d_lut *lut_param = (struct vpfe_ipipe_3d_lut *)param; + struct vpfe_ipipe_3d_lut *lut = &ipipe->config.lut; + struct device *dev = ipipe->subdev.v4l2_dev->dev; + + if (!lut_param) { + memset(lut, 0, sizeof(struct vpfe_ipipe_3d_lut)); + goto success; + } + + memcpy(lut, lut_param, sizeof(struct vpfe_ipipe_3d_lut)); + if (ipipe_validate_3d_lut_params(lut) < 0) { + dev_err(dev, "Invalid 3D-LUT Params\n"); + return -EINVAL; + } + +success: + ipipe_set_3d_lut_regs(ipipe->base_addr, ipipe->isp5_base_addr, lut); + + return 0; +} + +static int ipipe_validate_rgb2yuv_params(struct vpfe_ipipe_rgb2yuv *rgb2yuv) +{ + if (rgb2yuv->coef_ry.decimal > RGB2YCBCR_COEF_DECI_MASK || + rgb2yuv->coef_ry.integer > RGB2YCBCR_COEF_INT_MASK) + return -EINVAL; + + if (rgb2yuv->coef_gy.decimal > RGB2YCBCR_COEF_DECI_MASK || + rgb2yuv->coef_gy.integer > RGB2YCBCR_COEF_INT_MASK) + return -EINVAL; + + if (rgb2yuv->coef_by.decimal > RGB2YCBCR_COEF_DECI_MASK || + rgb2yuv->coef_by.integer > RGB2YCBCR_COEF_INT_MASK) + return -EINVAL; + + if (rgb2yuv->coef_rcb.decimal > RGB2YCBCR_COEF_DECI_MASK || + rgb2yuv->coef_rcb.integer > RGB2YCBCR_COEF_INT_MASK) + return -EINVAL; + + if (rgb2yuv->coef_gcb.decimal > RGB2YCBCR_COEF_DECI_MASK || + rgb2yuv->coef_gcb.integer > RGB2YCBCR_COEF_INT_MASK) + return -EINVAL; + + if (rgb2yuv->coef_bcb.decimal > RGB2YCBCR_COEF_DECI_MASK || + rgb2yuv->coef_bcb.integer > RGB2YCBCR_COEF_INT_MASK) + return -EINVAL; + + if (rgb2yuv->coef_rcr.decimal > RGB2YCBCR_COEF_DECI_MASK || + rgb2yuv->coef_rcr.integer > RGB2YCBCR_COEF_INT_MASK) + return -EINVAL; + + if (rgb2yuv->coef_gcr.decimal > RGB2YCBCR_COEF_DECI_MASK || + rgb2yuv->coef_gcr.integer > RGB2YCBCR_COEF_INT_MASK) + return -EINVAL; + + if (rgb2yuv->coef_bcr.decimal > RGB2YCBCR_COEF_DECI_MASK || + rgb2yuv->coef_bcr.integer > RGB2YCBCR_COEF_INT_MASK) + return -EINVAL; + + if (rgb2yuv->out_ofst_y > RGB2YCBCR_OFST_MASK || + rgb2yuv->out_ofst_cb > RGB2YCBCR_OFST_MASK || + rgb2yuv->out_ofst_cr > RGB2YCBCR_OFST_MASK) + return -EINVAL; + + return 0; +} + +static int +ipipe_set_rgb2yuv_params(struct vpfe_ipipe_device *ipipe, void *param) +{ + struct vpfe_ipipe_rgb2yuv *rgb2yuv = &ipipe->config.rgb2yuv; + struct device *dev = ipipe->subdev.v4l2_dev->dev; + struct vpfe_ipipe_rgb2yuv *rgb2yuv_param; + + rgb2yuv_param = (struct vpfe_ipipe_rgb2yuv *)param; + if (!rgb2yuv_param) { + /* Defaults for rgb2yuv conversion */ + const struct vpfe_ipipe_rgb2yuv rgb2yuv_defaults = { + .coef_ry = {0, 0x4d}, + .coef_gy = {0, 0x96}, + .coef_by = {0, 0x1d}, + .coef_rcb = {0xf, 0xd5}, + .coef_gcb = {0xf, 0xab}, + .coef_bcb = {0, 0x80}, + .coef_rcr = {0, 0x80}, + .coef_gcr = {0xf, 0x95}, + .coef_bcr = {0xf, 0xeb}, + .out_ofst_cb = 0x80, + .out_ofst_cr = 0x80, + }; + /* Copy defaults for rgb2yuv conversion */ + memcpy(rgb2yuv, &rgb2yuv_defaults, + sizeof(struct vpfe_ipipe_rgb2yuv)); + goto success; + } + + memcpy(rgb2yuv, rgb2yuv_param, sizeof(struct vpfe_ipipe_rgb2yuv)); + if (ipipe_validate_rgb2yuv_params(rgb2yuv) < 0) { + dev_err(dev, "Invalid rgb2yuv params\n"); + return -EINVAL; + } + +success: + ipipe_set_rgb2ycbcr_regs(ipipe->base_addr, rgb2yuv); + + return 0; +} + +static int +ipipe_get_rgb2yuv_params(struct vpfe_ipipe_device *ipipe, void *param) +{ + struct vpfe_ipipe_rgb2yuv *rgb2yuv = &ipipe->config.rgb2yuv; + struct vpfe_ipipe_rgb2yuv *rgb2yuv_param; + + rgb2yuv_param = (struct vpfe_ipipe_rgb2yuv *)param; + memcpy(rgb2yuv_param, rgb2yuv, sizeof(struct vpfe_ipipe_rgb2yuv)); + return 0; +} + +static int ipipe_validate_gbce_params(struct vpfe_ipipe_gbce *gbce) +{ + u32 max = GBCE_Y_VAL_MASK; + int i; + + if (!gbce->en) + return 0; + + if (gbce->type == VPFE_IPIPE_GBCE_GAIN_TBL) + max = GBCE_GAIN_VAL_MASK; + + for (i = 0; i < VPFE_IPIPE_MAX_SIZE_GBCE_LUT; i++) + if (gbce->table[i] > max) + return -EINVAL; + + return 0; +} + +static int ipipe_set_gbce_params(struct vpfe_ipipe_device *ipipe, void *param) +{ + struct vpfe_ipipe_gbce *gbce_param = (struct vpfe_ipipe_gbce *)param; + struct vpfe_ipipe_gbce *gbce = &ipipe->config.gbce; + struct device *dev = ipipe->subdev.v4l2_dev->dev; + + if (!gbce_param) { + memset(gbce, 0 , sizeof(struct vpfe_ipipe_gbce)); + } else { + memcpy(gbce, gbce_param, sizeof(struct vpfe_ipipe_gbce)); + if (ipipe_validate_gbce_params(gbce) < 0) { + dev_err(dev, "Invalid gbce params\n"); + return -EINVAL; + } + } + + ipipe_set_gbce_regs(ipipe->base_addr, ipipe->isp5_base_addr, gbce); + + return 0; +} + +static int ipipe_get_gbce_params(struct vpfe_ipipe_device *ipipe, void *param) +{ + struct vpfe_ipipe_gbce *gbce_param = (struct vpfe_ipipe_gbce *)param; + struct vpfe_ipipe_gbce *gbce = &ipipe->config.gbce; + struct device *dev = ipipe->subdev.v4l2_dev->dev; + + gbce_param->en = gbce->en; + gbce_param->type = gbce->type; + if (!gbce_param->table) { + dev_err(dev, "ipipe_get_gbce_params: Invalid table ptr\n"); + return -EINVAL; + } + + memcpy(gbce_param->table, gbce->table, + (VPFE_IPIPE_MAX_SIZE_GBCE_LUT * sizeof(unsigned short))); + + return 0; +} + +static int +ipipe_validate_yuv422_conv_params(struct vpfe_ipipe_yuv422_conv *yuv422_conv) +{ + if (yuv422_conv->en_chrom_lpf > 1) + return -EINVAL; + + return 0; +} + +static int +ipipe_set_yuv422_conv_params(struct vpfe_ipipe_device *ipipe, void *param) +{ + struct vpfe_ipipe_yuv422_conv *yuv422_conv = &ipipe->config.yuv422_conv; + struct vpfe_ipipe_yuv422_conv *yuv422_conv_param; + struct device *dev = ipipe->subdev.v4l2_dev->dev; + + yuv422_conv_param = (struct vpfe_ipipe_yuv422_conv *)param; + if (!yuv422_conv_param) { + memset(yuv422_conv, 0, sizeof(struct vpfe_ipipe_yuv422_conv)); + yuv422_conv->chrom_pos = VPFE_IPIPE_YUV422_CHR_POS_COSITE; + } else { + memcpy(yuv422_conv, yuv422_conv_param, + sizeof(struct vpfe_ipipe_yuv422_conv)); + if (ipipe_validate_yuv422_conv_params(yuv422_conv) < 0) { + dev_err(dev, "Invalid yuv422 params\n"); + return -EINVAL; + } + } + + ipipe_set_yuv422_conv_regs(ipipe->base_addr, yuv422_conv); + + return 0; +} + +static int +ipipe_get_yuv422_conv_params(struct vpfe_ipipe_device *ipipe, void *param) +{ + struct vpfe_ipipe_yuv422_conv *yuv422_conv = &ipipe->config.yuv422_conv; + struct vpfe_ipipe_yuv422_conv *yuv422_conv_param; + + yuv422_conv_param = (struct vpfe_ipipe_yuv422_conv *)param; + memcpy(yuv422_conv_param, yuv422_conv, + sizeof(struct vpfe_ipipe_yuv422_conv)); + + return 0; +} + +static int ipipe_validate_yee_params(struct vpfe_ipipe_yee *yee) +{ + int i; + + if (yee->en > 1 || + yee->en_halo_red > 1 || + yee->hpf_shft > YEE_HPF_SHIFT_MASK) + return -EINVAL; + + if (yee->hpf_coef_00 > YEE_COEF_MASK || + yee->hpf_coef_01 > YEE_COEF_MASK || + yee->hpf_coef_02 > YEE_COEF_MASK || + yee->hpf_coef_10 > YEE_COEF_MASK || + yee->hpf_coef_11 > YEE_COEF_MASK || + yee->hpf_coef_12 > YEE_COEF_MASK || + yee->hpf_coef_20 > YEE_COEF_MASK || + yee->hpf_coef_21 > YEE_COEF_MASK || + yee->hpf_coef_22 > YEE_COEF_MASK) + return -EINVAL; + + if (yee->yee_thr > YEE_THR_MASK || + yee->es_gain > YEE_ES_GAIN_MASK || + yee->es_thr1 > YEE_ES_THR1_MASK || + yee->es_thr2 > YEE_THR_MASK || + yee->es_gain_grad > YEE_THR_MASK || + yee->es_ofst_grad > YEE_THR_MASK) + return -EINVAL; + + for (i = 0; i < VPFE_IPIPE_MAX_SIZE_YEE_LUT; i++) + if (yee->table[i] > YEE_ENTRY_MASK) + return -EINVAL; + + return 0; +} + +static int ipipe_set_yee_params(struct vpfe_ipipe_device *ipipe, void *param) +{ + struct vpfe_ipipe_yee *yee_param = (struct vpfe_ipipe_yee *)param; + struct device *dev = ipipe->subdev.v4l2_dev->dev; + struct vpfe_ipipe_yee *yee = &ipipe->config.yee; + + if (!yee_param) { + memset(yee, 0, sizeof(struct vpfe_ipipe_yee)); + } else { + memcpy(yee, yee_param, sizeof(struct vpfe_ipipe_yee)); + if (ipipe_validate_yee_params(yee) < 0) { + dev_err(dev, "Invalid yee params\n"); + return -EINVAL; + } + } + + ipipe_set_ee_regs(ipipe->base_addr, ipipe->isp5_base_addr, yee); + + return 0; +} + +static int ipipe_get_yee_params(struct vpfe_ipipe_device *ipipe, void *param) +{ + struct vpfe_ipipe_yee *yee_param = (struct vpfe_ipipe_yee *)param; + struct vpfe_ipipe_yee *yee = &ipipe->config.yee; + + yee_param->en = yee->en; + yee_param->en_halo_red = yee->en_halo_red; + yee_param->merge_meth = yee->merge_meth; + yee_param->hpf_shft = yee->hpf_shft; + yee_param->hpf_coef_00 = yee->hpf_coef_00; + yee_param->hpf_coef_01 = yee->hpf_coef_01; + yee_param->hpf_coef_02 = yee->hpf_coef_02; + yee_param->hpf_coef_10 = yee->hpf_coef_10; + yee_param->hpf_coef_11 = yee->hpf_coef_11; + yee_param->hpf_coef_12 = yee->hpf_coef_12; + yee_param->hpf_coef_20 = yee->hpf_coef_20; + yee_param->hpf_coef_21 = yee->hpf_coef_21; + yee_param->hpf_coef_22 = yee->hpf_coef_22; + yee_param->yee_thr = yee->yee_thr; + yee_param->es_gain = yee->es_gain; + yee_param->es_thr1 = yee->es_thr1; + yee_param->es_thr2 = yee->es_thr2; + yee_param->es_gain_grad = yee->es_gain_grad; + yee_param->es_ofst_grad = yee->es_ofst_grad; + memcpy(yee_param->table, &yee->table, + (VPFE_IPIPE_MAX_SIZE_YEE_LUT * sizeof(short))); + + return 0; +} + +static int ipipe_validate_car_params(struct vpfe_ipipe_car *car) +{ + if (car->en > 1 || car->hpf_shft > CAR_HPF_SHIFT_MASK || + car->gain1.shft > CAR_GAIN1_SHFT_MASK || + car->gain1.gain_min > CAR_GAIN_MIN_MASK || + car->gain2.shft > CAR_GAIN2_SHFT_MASK || + car->gain2.gain_min > CAR_GAIN_MIN_MASK) + return -EINVAL; + + return 0; +} + +static int ipipe_set_car_params(struct vpfe_ipipe_device *ipipe, void *param) +{ + struct vpfe_ipipe_car *car_param = (struct vpfe_ipipe_car *)param; + struct device *dev = ipipe->subdev.v4l2_dev->dev; + struct vpfe_ipipe_car *car = &ipipe->config.car; + + if (!car_param) { + memset(car , 0, sizeof(struct vpfe_ipipe_car)); + } else { + memcpy(car, car_param, sizeof(struct vpfe_ipipe_car)); + if (ipipe_validate_car_params(car) < 0) { + dev_err(dev, "Invalid car params\n"); + return -EINVAL; + } + } + + ipipe_set_car_regs(ipipe->base_addr, car); + + return 0; +} + +static int ipipe_get_car_params(struct vpfe_ipipe_device *ipipe, void *param) +{ + struct vpfe_ipipe_car *car_param = (struct vpfe_ipipe_car *)param; + struct vpfe_ipipe_car *car = &ipipe->config.car; + + memcpy(car_param, car, sizeof(struct vpfe_ipipe_car)); + return 0; +} + +static int ipipe_validate_cgs_params(struct vpfe_ipipe_cgs *cgs) +{ + if (cgs->en > 1 || cgs->h_shft > CAR_SHIFT_MASK) + return -EINVAL; + + return 0; +} + +static int ipipe_set_cgs_params(struct vpfe_ipipe_device *ipipe, void *param) +{ + struct vpfe_ipipe_cgs *cgs_param = (struct vpfe_ipipe_cgs *)param; + struct device *dev = ipipe->subdev.v4l2_dev->dev; + struct vpfe_ipipe_cgs *cgs = &ipipe->config.cgs; + + if (!cgs_param) { + memset(cgs, 0, sizeof(struct vpfe_ipipe_cgs)); + } else { + memcpy(cgs, cgs_param, sizeof(struct vpfe_ipipe_cgs)); + if (ipipe_validate_cgs_params(cgs) < 0) { + dev_err(dev, "Invalid cgs params\n"); + return -EINVAL; + } + } + + ipipe_set_cgs_regs(ipipe->base_addr, cgs); + + return 0; +} + +static int ipipe_get_cgs_params(struct vpfe_ipipe_device *ipipe, void *param) +{ + struct vpfe_ipipe_cgs *cgs_param = (struct vpfe_ipipe_cgs *)param; + struct vpfe_ipipe_cgs *cgs = &ipipe->config.cgs; + + memcpy(cgs_param, cgs, sizeof(struct vpfe_ipipe_cgs)); + + return 0; +} + +static const struct ipipe_module_if ipipe_modules[VPFE_IPIPE_MAX_MODULES] = { + /* VPFE_IPIPE_INPUT_CONFIG */ { + offsetof(struct ipipe_module_params, input_config), + FIELD_SIZEOF(struct ipipe_module_params, input_config), + offsetof(struct vpfe_ipipe_config, input_config), + ipipe_set_input_config, + ipipe_get_input_config, + }, /* VPFE_IPIPE_LUTDPC */ { + offsetof(struct ipipe_module_params, lutdpc), + FIELD_SIZEOF(struct ipipe_module_params, lutdpc), + offsetof(struct vpfe_ipipe_config, lutdpc), + ipipe_set_lutdpc_params, + ipipe_get_lutdpc_params, + }, /* VPFE_IPIPE_OTFDPC */ { + offsetof(struct ipipe_module_params, otfdpc), + FIELD_SIZEOF(struct ipipe_module_params, otfdpc), + offsetof(struct vpfe_ipipe_config, otfdpc), + ipipe_set_otfdpc_params, + ipipe_get_otfdpc_params, + }, /* VPFE_IPIPE_NF1 */ { + offsetof(struct ipipe_module_params, nf1), + FIELD_SIZEOF(struct ipipe_module_params, nf1), + offsetof(struct vpfe_ipipe_config, nf1), + ipipe_set_nf1_params, + ipipe_get_nf1_params, + }, /* VPFE_IPIPE_NF2 */ { + offsetof(struct ipipe_module_params, nf2), + FIELD_SIZEOF(struct ipipe_module_params, nf2), + offsetof(struct vpfe_ipipe_config, nf2), + ipipe_set_nf2_params, + ipipe_get_nf2_params, + }, /* VPFE_IPIPE_WB */ { + offsetof(struct ipipe_module_params, wbal), + FIELD_SIZEOF(struct ipipe_module_params, wbal), + offsetof(struct vpfe_ipipe_config, wbal), + ipipe_set_wb_params, + ipipe_get_wb_params, + }, /* VPFE_IPIPE_RGB2RGB_1 */ { + offsetof(struct ipipe_module_params, rgb2rgb1), + FIELD_SIZEOF(struct ipipe_module_params, rgb2rgb1), + offsetof(struct vpfe_ipipe_config, rgb2rgb1), + ipipe_set_rgb2rgb_1_params, + ipipe_get_rgb2rgb_1_params, + }, /* VPFE_IPIPE_RGB2RGB_2 */ { + offsetof(struct ipipe_module_params, rgb2rgb2), + FIELD_SIZEOF(struct ipipe_module_params, rgb2rgb2), + offsetof(struct vpfe_ipipe_config, rgb2rgb2), + ipipe_set_rgb2rgb_2_params, + ipipe_get_rgb2rgb_2_params, + }, /* VPFE_IPIPE_GAMMA */ { + offsetof(struct ipipe_module_params, gamma), + FIELD_SIZEOF(struct ipipe_module_params, gamma), + offsetof(struct vpfe_ipipe_config, gamma), + ipipe_set_gamma_params, + ipipe_get_gamma_params, + }, /* VPFE_IPIPE_3D_LUT */ { + offsetof(struct ipipe_module_params, lut), + FIELD_SIZEOF(struct ipipe_module_params, lut), + offsetof(struct vpfe_ipipe_config, lut), + ipipe_set_3d_lut_params, + ipipe_get_3d_lut_params, + }, /* VPFE_IPIPE_RGB2YUV */ { + offsetof(struct ipipe_module_params, rgb2yuv), + FIELD_SIZEOF(struct ipipe_module_params, rgb2yuv), + offsetof(struct vpfe_ipipe_config, rgb2yuv), + ipipe_set_rgb2yuv_params, + ipipe_get_rgb2yuv_params, + }, /* VPFE_IPIPE_YUV422_CONV */ { + offsetof(struct ipipe_module_params, yuv422_conv), + FIELD_SIZEOF(struct ipipe_module_params, yuv422_conv), + offsetof(struct vpfe_ipipe_config, yuv422_conv), + ipipe_set_yuv422_conv_params, + ipipe_get_yuv422_conv_params, + }, /* VPFE_IPIPE_YEE */ { + offsetof(struct ipipe_module_params, yee), + FIELD_SIZEOF(struct ipipe_module_params, yee), + offsetof(struct vpfe_ipipe_config, yee), + ipipe_set_yee_params, + ipipe_get_yee_params, + }, /* VPFE_IPIPE_GIC */ { + offsetof(struct ipipe_module_params, gic), + FIELD_SIZEOF(struct ipipe_module_params, gic), + offsetof(struct vpfe_ipipe_config, gic), + ipipe_set_gic_params, + ipipe_get_gic_params, + }, /* VPFE_IPIPE_CFA */ { + offsetof(struct ipipe_module_params, cfa), + FIELD_SIZEOF(struct ipipe_module_params, cfa), + offsetof(struct vpfe_ipipe_config, cfa), + ipipe_set_cfa_params, + ipipe_get_cfa_params, + }, /* VPFE_IPIPE_CAR */ { + offsetof(struct ipipe_module_params, car), + FIELD_SIZEOF(struct ipipe_module_params, car), + offsetof(struct vpfe_ipipe_config, car), + ipipe_set_car_params, + ipipe_get_car_params, + }, /* VPFE_IPIPE_CGS */ { + offsetof(struct ipipe_module_params, cgs), + FIELD_SIZEOF(struct ipipe_module_params, cgs), + offsetof(struct vpfe_ipipe_config, cgs), + ipipe_set_cgs_params, + ipipe_get_cgs_params, + }, /* VPFE_IPIPE_GBCE */ { + offsetof(struct ipipe_module_params, gbce), + FIELD_SIZEOF(struct ipipe_module_params, gbce), + offsetof(struct vpfe_ipipe_config, gbce), + ipipe_set_gbce_params, + ipipe_get_gbce_params, + }, +}; + +static int ipipe_s_config(struct v4l2_subdev *sd, struct vpfe_ipipe_config *cfg) +{ + struct vpfe_ipipe_device *ipipe = v4l2_get_subdevdata(sd); + unsigned int i; + int rval = 0; + + for (i = 0; i < ARRAY_SIZE(ipipe_modules); i++) { + unsigned int bit = 1 << i; + if (cfg->flag & bit) { + const struct ipipe_module_if *module_if = + &ipipe_modules[i]; + struct ipipe_module_params *params; + void __user *from = *(void * __user *) + ((void *)cfg + module_if->config_offset); + size_t size; + void *to; + + params = kmalloc(sizeof(struct ipipe_module_params), + GFP_KERNEL); + to = (void *)params + module_if->param_offset; + size = module_if->param_size; + + if (to && from && size) { + if (copy_from_user(to, from, size)) { + rval = -EFAULT; + break; + } + rval = module_if->set(ipipe, to); + if (rval) + goto error; + } else if (to && !from && size) { + rval = module_if->set(ipipe, NULL); + if (rval) + goto error; + } + kfree(params); + } + } +error: + return rval; +} + +static int ipipe_g_config(struct v4l2_subdev *sd, struct vpfe_ipipe_config *cfg) +{ + struct vpfe_ipipe_device *ipipe = v4l2_get_subdevdata(sd); + unsigned int i; + int rval = 0; + + for (i = 1; i < ARRAY_SIZE(ipipe_modules); i++) { + unsigned int bit = 1 << i; + if (cfg->flag & bit) { + const struct ipipe_module_if *module_if = + &ipipe_modules[i]; + struct ipipe_module_params *params; + void __user *to = *(void * __user *) + ((void *)cfg + module_if->config_offset); + size_t size; + void *from; + + params = kmalloc(sizeof(struct ipipe_module_params), + GFP_KERNEL); + from = (void *)params + module_if->param_offset; + size = module_if->param_size; + + if (to && from && size) { + rval = module_if->get(ipipe, from); + if (rval) + goto error; + if (copy_to_user(to, from, size)) { + rval = -EFAULT; + break; + } + } + kfree(params); + } + } +error: + return rval; +} + +/* + * ipipe_ioctl() - Handle ipipe module private ioctl's + * @sd: pointer to v4l2 subdev structure + * @cmd: configuration command + * @arg: configuration argument + */ +static long ipipe_ioctl(struct v4l2_subdev *sd, unsigned int cmd, void *arg) +{ + int ret = 0; + + switch (cmd) { + case VIDIOC_VPFE_IPIPE_S_CONFIG: + ret = ipipe_s_config(sd, arg); + break; + + case VIDIOC_VPFE_IPIPE_G_CONFIG: + ret = ipipe_g_config(sd, arg); + break; + + default: + ret = -ENOIOCTLCMD; + } + return ret; +} + +void vpfe_ipipe_enable(struct vpfe_device *vpfe_dev, int en) +{ + struct vpfe_ipipeif_device *ipipeif = &vpfe_dev->vpfe_ipipeif; + struct vpfe_ipipe_device *ipipe = &vpfe_dev->vpfe_ipipe; + unsigned char val; + + if (ipipe->input == IPIPE_INPUT_NONE) + return; + + /* ipipe is set to single shot */ + if (ipipeif->input == IPIPEIF_INPUT_MEMORY && en) { + /* for single-shot mode, need to wait for h/w to + * reset many register bits + */ + do { + val = regr_ip(vpfe_dev->vpfe_ipipe.base_addr, + IPIPE_SRC_EN); + } while (val); + } + regw_ip(vpfe_dev->vpfe_ipipe.base_addr, en, IPIPE_SRC_EN); +} + +/* + * ipipe_set_stream() - Enable/Disable streaming on the ipipe subdevice + * @sd: pointer to v4l2 subdev structure + * @enable: 1 == Enable, 0 == Disable + */ +static int ipipe_set_stream(struct v4l2_subdev *sd, int enable) +{ + struct vpfe_ipipe_device *ipipe = v4l2_get_subdevdata(sd); + struct vpfe_device *vpfe_dev = to_vpfe_device(ipipe); + + if (enable && ipipe->input != IPIPE_INPUT_NONE && + ipipe->output != IPIPE_OUTPUT_NONE) { + if (config_ipipe_hw(ipipe) < 0) + return -EINVAL; + } + + vpfe_ipipe_enable(vpfe_dev, enable); + + return 0; +} + +/* + * __ipipe_get_format() - helper function for getting ipipe format + * @ipipe: pointer to ipipe private structure. + * @pad: pad number. + * @fh: V4L2 subdev file handle. + * @which: wanted subdev format. + * + */ +static struct v4l2_mbus_framefmt * +__ipipe_get_format(struct vpfe_ipipe_device *ipipe, + struct v4l2_subdev_fh *fh, unsigned int pad, + enum v4l2_subdev_format_whence which) +{ + if (which == V4L2_SUBDEV_FORMAT_TRY) + return v4l2_subdev_get_try_format(fh, pad); + + return &ipipe->formats[pad]; +} + +/* + * ipipe_try_format() - Handle try format by pad subdev method + * @ipipe: VPFE ipipe device. + * @fh: V4L2 subdev file handle. + * @pad: pad num. + * @fmt: pointer to v4l2 format structure. + * @which : wanted subdev format + */ +static void +ipipe_try_format(struct vpfe_ipipe_device *ipipe, + struct v4l2_subdev_fh *fh, unsigned int pad, + struct v4l2_mbus_framefmt *fmt, + enum v4l2_subdev_format_whence which) +{ + unsigned int max_out_height; + unsigned int max_out_width; + unsigned int i; + + max_out_width = IPIPE_MAX_OUTPUT_WIDTH_A; + max_out_height = IPIPE_MAX_OUTPUT_HEIGHT_A; + + if (pad == IPIPE_PAD_SINK) { + for (i = 0; i < ARRAY_SIZE(ipipe_input_fmts); i++) + if (fmt->code == ipipe_input_fmts[i]) + break; + + /* If not found, use SBGGR10 as default */ + if (i >= ARRAY_SIZE(ipipe_input_fmts)) + fmt->code = V4L2_MBUS_FMT_SGRBG12_1X12; + } else if (pad == IPIPE_PAD_SOURCE) { + for (i = 0; i < ARRAY_SIZE(ipipe_output_fmts); i++) + if (fmt->code == ipipe_output_fmts[i]) + break; + + /* If not found, use UYVY as default */ + if (i >= ARRAY_SIZE(ipipe_output_fmts)) + fmt->code = V4L2_MBUS_FMT_UYVY8_2X8; + } + + fmt->width = clamp_t(u32, fmt->width, MIN_OUT_HEIGHT, max_out_width); + fmt->height = clamp_t(u32, fmt->height, MIN_OUT_WIDTH, max_out_height); +} + +/* + * ipipe_set_format() - Handle set format by pads subdev method + * @sd: pointer to v4l2 subdev structure + * @fh: V4L2 subdev file handle + * @fmt: pointer to v4l2 subdev format structure + * return -EINVAL or zero on success + */ +static int +ipipe_set_format(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh, + struct v4l2_subdev_format *fmt) +{ + struct vpfe_ipipe_device *ipipe = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt *format; + + format = __ipipe_get_format(ipipe, fh, fmt->pad, fmt->which); + if (format == NULL) + return -EINVAL; + + ipipe_try_format(ipipe, fh, fmt->pad, &fmt->format, fmt->which); + *format = fmt->format; + + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) + return 0; + + if (fmt->pad == IPIPE_PAD_SINK && + (ipipe->input == IPIPE_INPUT_CCDC || + ipipe->input == IPIPE_INPUT_MEMORY)) + ipipe->formats[fmt->pad] = fmt->format; + else if (fmt->pad == IPIPE_PAD_SOURCE && + ipipe->output == IPIPE_OUTPUT_RESIZER) + ipipe->formats[fmt->pad] = fmt->format; + else + return -EINVAL; + + return 0; +} + +/* + * ipipe_get_format() - Handle get format by pads subdev method. + * @sd: pointer to v4l2 subdev structure. + * @fh: V4L2 subdev file handle. + * @fmt: pointer to v4l2 subdev format structure. + */ +static int +ipipe_get_format(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh, + struct v4l2_subdev_format *fmt) +{ + struct vpfe_ipipe_device *ipipe = v4l2_get_subdevdata(sd); + + if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE) + fmt->format = ipipe->formats[fmt->pad]; + else + fmt->format = *(v4l2_subdev_get_try_format(fh, fmt->pad)); + + return 0; +} + +/* + * ipipe_enum_frame_size() - enum frame sizes on pads + * @sd: pointer to v4l2 subdev structure. + * @fh: V4L2 subdev file handle. + * @fse: pointer to v4l2_subdev_frame_size_enum structure. + */ +static int +ipipe_enum_frame_size(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh, + struct v4l2_subdev_frame_size_enum *fse) +{ + struct vpfe_ipipe_device *ipipe = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt format; + + if (fse->index != 0) + return -EINVAL; + + format.code = fse->code; + format.width = 1; + format.height = 1; + ipipe_try_format(ipipe, fh, fse->pad, &format, + V4L2_SUBDEV_FORMAT_TRY); + fse->min_width = format.width; + fse->min_height = format.height; + + if (format.code != fse->code) + return -EINVAL; + + format.code = fse->code; + format.width = -1; + format.height = -1; + ipipe_try_format(ipipe, fh, fse->pad, &format, + V4L2_SUBDEV_FORMAT_TRY); + fse->max_width = format.width; + fse->max_height = format.height; + + return 0; +} + +/* + * ipipe_enum_mbus_code() - enum mbus codes for pads + * @sd: pointer to v4l2 subdev structure. + * @fh: V4L2 subdev file handle + * @code: pointer to v4l2_subdev_mbus_code_enum structure + */ +static int +ipipe_enum_mbus_code(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh, + struct v4l2_subdev_mbus_code_enum *code) +{ + switch (code->pad) { + case IPIPE_PAD_SINK: + if (code->index >= ARRAY_SIZE(ipipe_input_fmts)) + return -EINVAL; + code->code = ipipe_input_fmts[code->index]; + break; + + case IPIPE_PAD_SOURCE: + if (code->index >= ARRAY_SIZE(ipipe_output_fmts)) + return -EINVAL; + code->code = ipipe_output_fmts[code->index]; + break; + + default: + return -EINVAL; + } + + return 0; +} + +/* + * ipipe_s_ctrl() - Handle set control subdev method + * @ctrl: pointer to v4l2 control structure + */ +static int ipipe_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct vpfe_ipipe_device *ipipe = + container_of(ctrl->handler, struct vpfe_ipipe_device, ctrls); + struct ipipe_lum_adj *lum_adj = &ipipe->config.lum_adj; + + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + lum_adj->brightness = ctrl->val; + ipipe_set_lum_adj_regs(ipipe->base_addr, lum_adj); + break; + + case V4L2_CID_CONTRAST: + lum_adj->contrast = ctrl->val; + ipipe_set_lum_adj_regs(ipipe->base_addr, lum_adj); + break; + + default: + return -EINVAL; + } + + return 0; +} + +/* + * ipipe_init_formats() - Initialize formats on all pads + * @sd: pointer to v4l2 subdev structure. + * @fh: V4L2 subdev file handle + * + * Initialize all pad formats with default values. If fh is not NULL, try + * formats are initialized on the file handle. Otherwise active formats are + * initialized on the device. + */ +static int +ipipe_init_formats(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + struct v4l2_subdev_format format; + + memset(&format, 0, sizeof(format)); + format.pad = IPIPE_PAD_SINK; + format.which = fh ? V4L2_SUBDEV_FORMAT_TRY : V4L2_SUBDEV_FORMAT_ACTIVE; + format.format.code = V4L2_MBUS_FMT_SGRBG12_1X12; + format.format.width = IPIPE_MAX_OUTPUT_WIDTH_A; + format.format.height = IPIPE_MAX_OUTPUT_HEIGHT_A; + ipipe_set_format(sd, fh, &format); + + memset(&format, 0, sizeof(format)); + format.pad = IPIPE_PAD_SOURCE; + format.which = fh ? V4L2_SUBDEV_FORMAT_TRY : V4L2_SUBDEV_FORMAT_ACTIVE; + format.format.code = V4L2_MBUS_FMT_UYVY8_2X8; + format.format.width = IPIPE_MAX_OUTPUT_WIDTH_A; + format.format.height = IPIPE_MAX_OUTPUT_HEIGHT_A; + ipipe_set_format(sd, fh, &format); + + return 0; +} + +/* subdev core operations */ +static const struct v4l2_subdev_core_ops ipipe_v4l2_core_ops = { + .ioctl = ipipe_ioctl, +}; + +static const struct v4l2_ctrl_ops ipipe_ctrl_ops = { + .s_ctrl = ipipe_s_ctrl, +}; + +/* subdev file operations */ +static const struct v4l2_subdev_internal_ops ipipe_v4l2_internal_ops = { + .open = ipipe_init_formats, +}; + +/* subdev video operations */ +static const struct v4l2_subdev_video_ops ipipe_v4l2_video_ops = { + .s_stream = ipipe_set_stream, +}; + +/* subdev pad operations */ +static const struct v4l2_subdev_pad_ops ipipe_v4l2_pad_ops = { + .enum_mbus_code = ipipe_enum_mbus_code, + .enum_frame_size = ipipe_enum_frame_size, + .get_fmt = ipipe_get_format, + .set_fmt = ipipe_set_format, +}; + +/* v4l2 subdev operation */ +static const struct v4l2_subdev_ops ipipe_v4l2_ops = { + .core = &ipipe_v4l2_core_ops, + .video = &ipipe_v4l2_video_ops, + .pad = &ipipe_v4l2_pad_ops, +}; + +/* + * Media entity operations + */ + +/* + * ipipe_link_setup() - Setup ipipe connections + * @entity: ipipe media entity + * @local: Pad at the local end of the link + * @remote: Pad at the remote end of the link + * @flags: Link flags + * + * return -EINVAL or zero on success + */ +static int +ipipe_link_setup(struct media_entity *entity, const struct media_pad *local, + const struct media_pad *remote, u32 flags) +{ + struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(entity); + struct vpfe_ipipe_device *ipipe = v4l2_get_subdevdata(sd); + struct vpfe_device *vpfe_dev = to_vpfe_device(ipipe); + u16 ipipeif_sink = vpfe_dev->vpfe_ipipeif.input; + + switch (local->index | media_entity_type(remote->entity)) { + case IPIPE_PAD_SINK | MEDIA_ENT_T_V4L2_SUBDEV: + if (!(flags & MEDIA_LNK_FL_ENABLED)) { + ipipe->input = IPIPE_INPUT_NONE; + break; + } + if (ipipe->input != IPIPE_INPUT_NONE) + return -EBUSY; + if (ipipeif_sink == IPIPEIF_INPUT_MEMORY) + ipipe->input = IPIPE_INPUT_MEMORY; + else + ipipe->input = IPIPE_INPUT_CCDC; + break; + + case IPIPE_PAD_SOURCE | MEDIA_ENT_T_V4L2_SUBDEV: + /* out to RESIZER */ + if (flags & MEDIA_LNK_FL_ENABLED) + ipipe->output = IPIPE_OUTPUT_RESIZER; + else + ipipe->output = IPIPE_OUTPUT_NONE; + break; + + default: + return -EINVAL; + } + + return 0; +} + +static const struct media_entity_operations ipipe_media_ops = { + .link_setup = ipipe_link_setup, +}; + +/* + * vpfe_ipipe_unregister_entities() - ipipe unregister entity + * @vpfe_ipipe: pointer to ipipe subdevice structure. + */ +void vpfe_ipipe_unregister_entities(struct vpfe_ipipe_device *vpfe_ipipe) +{ + /* unregister subdev */ + v4l2_device_unregister_subdev(&vpfe_ipipe->subdev); + /* cleanup entity */ + media_entity_cleanup(&vpfe_ipipe->subdev.entity); +} + +/* + * vpfe_ipipe_register_entities() - ipipe register entity + * @ipipe: pointer to ipipe subdevice structure. + * @vdev: pointer to v4l2 device structure. + */ +int +vpfe_ipipe_register_entities(struct vpfe_ipipe_device *ipipe, + struct v4l2_device *vdev) +{ + int ret; + + /* Register the subdev */ + ret = v4l2_device_register_subdev(vdev, &ipipe->subdev); + if (ret) { + pr_err("Failed to register ipipe as v4l2 subdevice\n"); + return ret; + } + + return ret; +} + +#define IPIPE_CONTRAST_HIGH 0xff +#define IPIPE_BRIGHT_HIGH 0xff + +/* + * vpfe_ipipe_init() - ipipe module initialization. + * @ipipe: pointer to ipipe subdevice structure. + * @pdev: platform device pointer. + */ +int +vpfe_ipipe_init(struct vpfe_ipipe_device *ipipe, struct platform_device *pdev) +{ + struct media_pad *pads = &ipipe->pads[0]; + struct v4l2_subdev *sd = &ipipe->subdev; + struct media_entity *me = &sd->entity; + static resource_size_t res_len; + struct resource *res; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 4); + if (!res) + return -ENOENT; + + res_len = resource_size(res); + res = request_mem_region(res->start, res_len, res->name); + if (!res) + return -EBUSY; + ipipe->base_addr = ioremap_nocache(res->start, res_len); + if (!ipipe->base_addr) + return -EBUSY; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 6); + if (!res) + return -ENOENT; + ipipe->isp5_base_addr = ioremap_nocache(res->start, res_len); + if (!ipipe->isp5_base_addr) + return -EBUSY; + + v4l2_subdev_init(sd, &ipipe_v4l2_ops); + sd->internal_ops = &ipipe_v4l2_internal_ops; + strlcpy(sd->name, "DAVINCI IPIPE", sizeof(sd->name)); + sd->grp_id = 1 << 16; /* group ID for davinci subdevs */ + v4l2_set_subdevdata(sd, ipipe); + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + + pads[IPIPE_PAD_SINK].flags = MEDIA_PAD_FL_SINK; + pads[IPIPE_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE; + + ipipe->input = IPIPE_INPUT_NONE; + ipipe->output = IPIPE_OUTPUT_NONE; + + me->ops = &ipipe_media_ops; + v4l2_ctrl_handler_init(&ipipe->ctrls, 2); + v4l2_ctrl_new_std(&ipipe->ctrls, &ipipe_ctrl_ops, + V4L2_CID_BRIGHTNESS, 0, + IPIPE_BRIGHT_HIGH, 1, 16); + v4l2_ctrl_new_std(&ipipe->ctrls, &ipipe_ctrl_ops, + V4L2_CID_CONTRAST, 0, + IPIPE_CONTRAST_HIGH, 1, 16); + + + v4l2_ctrl_handler_setup(&ipipe->ctrls); + sd->ctrl_handler = &ipipe->ctrls; + + return media_entity_init(me, IPIPE_PADS_NUM, pads, 0); +} + +/* + * vpfe_ipipe_cleanup() - ipipe subdevice cleanup. + * @ipipe: pointer to ipipe subdevice + * @dev: pointer to platform device + */ +void vpfe_ipipe_cleanup(struct vpfe_ipipe_device *ipipe, + struct platform_device *pdev) +{ + struct resource *res; + + v4l2_ctrl_handler_free(&ipipe->ctrls); + + iounmap(ipipe->base_addr); + iounmap(ipipe->isp5_base_addr); + res = platform_get_resource(pdev, IORESOURCE_MEM, 4); + if (res) + release_mem_region(res->start, resource_size(res)); +} diff --git a/drivers/staging/media/davinci_vpfe/dm365_ipipe.h b/drivers/staging/media/davinci_vpfe/dm365_ipipe.h new file mode 100644 index 00000000000..cf4204603eb --- /dev/null +++ b/drivers/staging/media/davinci_vpfe/dm365_ipipe.h @@ -0,0 +1,179 @@ +/* + * Copyright (C) 2012 Texas Instruments Inc + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Contributors: + * Manjunath Hadli <manjunath.hadli@ti.com> + * Prabhakar Lad <prabhakar.lad@ti.com> + */ + +#ifndef _DAVINCI_VPFE_DM365_IPIPE_H +#define _DAVINCI_VPFE_DM365_IPIPE_H + +#include <linux/platform_device.h> + +#include <media/v4l2-ctrls.h> +#include <media/v4l2-subdev.h> + +#include "davinci_vpfe_user.h" +#include "vpfe_video.h" + +#define CEIL(a, b) (((a) + (b-1)) / (b)) + +enum ipipe_noise_filter { + IPIPE_D2F_1ST = 0, + IPIPE_D2F_2ND = 1, +}; + +/* Used for driver storage */ +struct ipipe_otfdpc_2_0 { + /* 0 - disable, 1 - enable */ + unsigned char en; + /* defect detection method */ + enum vpfe_ipipe_otfdpc_det_meth det_method; + /* Algorithm used. Applicable only when IPIPE_DPC_OTF_MIN_MAX2 is + * used + */ + enum vpfe_ipipe_otfdpc_alg alg; + struct vpfe_ipipe_otfdpc_2_0_cfg otfdpc_2_0; +}; + +struct ipipe_otfdpc_3_0 { + /* 0 - disable, 1 - enable */ + unsigned char en; + /* defect detection method */ + enum vpfe_ipipe_otfdpc_det_meth det_method; + /* Algorithm used. Applicable only when IPIPE_DPC_OTF_MIN_MAX2 is + * used + */ + enum vpfe_ipipe_otfdpc_alg alg; + struct vpfe_ipipe_otfdpc_3_0_cfg otfdpc_3_0; +}; + +/* Structure for configuring Luminance Adjustment module */ +struct ipipe_lum_adj { + /* Brightness adjustments */ + unsigned char brightness; + /* contrast adjustments */ + unsigned char contrast; +}; + +enum ipipe_rgb2rgb { + IPIPE_RGB2RGB_1 = 0, + IPIPE_RGB2RGB_2 = 1, +}; + +struct ipipe_module_params { + __u32 flag; + struct vpfe_ipipe_input_config input_config; + struct vpfe_ipipe_lutdpc lutdpc; + struct vpfe_ipipe_otfdpc otfdpc; + struct vpfe_ipipe_nf nf1; + struct vpfe_ipipe_nf nf2; + struct vpfe_ipipe_gic gic; + struct vpfe_ipipe_wb wbal; + struct vpfe_ipipe_cfa cfa; + struct vpfe_ipipe_rgb2rgb rgb2rgb1; + struct vpfe_ipipe_rgb2rgb rgb2rgb2; + struct vpfe_ipipe_gamma gamma; + struct vpfe_ipipe_3d_lut lut; + struct vpfe_ipipe_rgb2yuv rgb2yuv; + struct vpfe_ipipe_gbce gbce; + struct vpfe_ipipe_yuv422_conv yuv422_conv; + struct vpfe_ipipe_yee yee; + struct vpfe_ipipe_car car; + struct vpfe_ipipe_cgs cgs; + struct ipipe_lum_adj lum_adj; +}; + +#define IPIPE_PAD_SINK 0 +#define IPIPE_PAD_SOURCE 1 + +#define IPIPE_PADS_NUM 2 + +#define IPIPE_OUTPUT_NONE 0 +#define IPIPE_OUTPUT_RESIZER (1 << 0) + +enum ipipe_input_entity { + IPIPE_INPUT_NONE = 0, + IPIPE_INPUT_MEMORY = 1, + IPIPE_INPUT_CCDC = 2, +}; + + +struct vpfe_ipipe_device { + struct v4l2_subdev subdev; + struct media_pad pads[IPIPE_PADS_NUM]; + struct v4l2_mbus_framefmt formats[IPIPE_PADS_NUM]; + enum ipipe_input_entity input; + unsigned int output; + struct v4l2_ctrl_handler ctrls; + void *__iomem base_addr; + void *__iomem isp5_base_addr; + struct ipipe_module_params config; +}; + +struct ipipe_module_if { + unsigned int param_offset; + unsigned int param_size; + unsigned int config_offset; + int (*set)(struct vpfe_ipipe_device *ipipe, void *param); + int (*get)(struct vpfe_ipipe_device *ipipe, void *param); +}; + +/* data paths */ +enum ipipe_data_paths { + IPIPE_RAW2YUV, + /* Bayer RAW input to YCbCr output */ + IPIPE_RAW2RAW, + /* Bayer Raw to Bayer output */ + IPIPE_RAW2BOX, + /* Bayer Raw to Boxcar output */ + IPIPE_YUV2YUV + /* YUV Raw to YUV Raw output */ +}; + +#define IPIPE_COLPTN_R_Ye 0x0 +#define IPIPE_COLPTN_Gr_Cy 0x1 +#define IPIPE_COLPTN_Gb_G 0x2 +#define IPIPE_COLPTN_B_Mg 0x3 + +#define COLPAT_EE_SHIFT 0 +#define COLPAT_EO_SHIFT 2 +#define COLPAT_OE_SHIFT 4 +#define COLPAT_OO_SHIFT 6 + +#define ipipe_sgrbg_pattern \ + (IPIPE_COLPTN_Gr_Cy << COLPAT_EE_SHIFT | \ + IPIPE_COLPTN_R_Ye << COLPAT_EO_SHIFT | \ + IPIPE_COLPTN_B_Mg << COLPAT_OE_SHIFT | \ + IPIPE_COLPTN_Gb_G << COLPAT_OO_SHIFT) + +#define ipipe_srggb_pattern \ + (IPIPE_COLPTN_R_Ye << COLPAT_EE_SHIFT | \ + IPIPE_COLPTN_Gr_Cy << COLPAT_EO_SHIFT | \ + IPIPE_COLPTN_Gb_G << COLPAT_OE_SHIFT | \ + IPIPE_COLPTN_B_Mg << COLPAT_OO_SHIFT) + +int vpfe_ipipe_register_entities(struct vpfe_ipipe_device *ipipe, + struct v4l2_device *v4l2_dev); +int vpfe_ipipe_init(struct vpfe_ipipe_device *ipipe, + struct platform_device *pdev); +void vpfe_ipipe_unregister_entities(struct vpfe_ipipe_device *ipipe); +void vpfe_ipipe_cleanup(struct vpfe_ipipe_device *ipipe, + struct platform_device *pdev); +void vpfe_ipipe_enable(struct vpfe_device *vpfe_dev, int en); + +#endif /* _DAVINCI_VPFE_DM365_IPIPE_H */ diff --git a/drivers/staging/media/davinci_vpfe/dm365_ipipe_hw.c b/drivers/staging/media/davinci_vpfe/dm365_ipipe_hw.c new file mode 100644 index 00000000000..b2daf5e63f8 --- /dev/null +++ b/drivers/staging/media/davinci_vpfe/dm365_ipipe_hw.c @@ -0,0 +1,1048 @@ +/* + * Copyright (C) 2012 Texas Instruments Inc + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Contributors: + * Manjunath Hadli <manjunath.hadli@ti.com> + * Prabhakar Lad <prabhakar.lad@ti.com> + */ + +#include "dm365_ipipe_hw.h" + +#define IPIPE_MODE_CONTINUOUS 0 +#define IPIPE_MODE_SINGLE_SHOT 1 + +static void ipipe_clock_enable(void *__iomem base_addr) +{ + /* enable IPIPE MMR for register write access */ + regw_ip(base_addr, IPIPE_GCK_MMR_DEFAULT, IPIPE_GCK_MMR); + + /* enable the clock wb,cfa,dfc,d2f,pre modules */ + regw_ip(base_addr, IPIPE_GCK_PIX_DEFAULT, IPIPE_GCK_PIX); +} + +static void +rsz_set_common_params(void *__iomem rsz_base, struct resizer_params *params) +{ + struct rsz_common_params *rsz_common = ¶ms->rsz_common; + u32 val; + + /* Set mode */ + regw_rsz(rsz_base, params->oper_mode, RSZ_SRC_MODE); + + /* data source selection and bypass */ + val = (rsz_common->passthrough << RSZ_BYPASS_SHIFT) | + rsz_common->source; + regw_rsz(rsz_base, val, RSZ_SRC_FMT0); + + /* src image selection */ + val = (rsz_common->raw_flip & 1) | + (rsz_common->src_img_fmt << RSZ_SRC_IMG_FMT_SHIFT) | + ((rsz_common->y_c & 1) << RSZ_SRC_Y_C_SEL_SHIFT); + regw_rsz(rsz_base, val, RSZ_SRC_FMT1); + + regw_rsz(rsz_base, rsz_common->vps & IPIPE_RSZ_VPS_MASK, RSZ_SRC_VPS); + regw_rsz(rsz_base, rsz_common->hps & IPIPE_RSZ_HPS_MASK, RSZ_SRC_HPS); + regw_rsz(rsz_base, rsz_common->vsz & IPIPE_RSZ_VSZ_MASK, RSZ_SRC_VSZ); + regw_rsz(rsz_base, rsz_common->hsz & IPIPE_RSZ_HSZ_MASK, RSZ_SRC_HSZ); + regw_rsz(rsz_base, rsz_common->yuv_y_min, RSZ_YUV_Y_MIN); + regw_rsz(rsz_base, rsz_common->yuv_y_max, RSZ_YUV_Y_MAX); + regw_rsz(rsz_base, rsz_common->yuv_c_min, RSZ_YUV_C_MIN); + regw_rsz(rsz_base, rsz_common->yuv_c_max, RSZ_YUV_C_MAX); + /* chromatic position */ + regw_rsz(rsz_base, rsz_common->out_chr_pos, RSZ_YUV_PHS); +} + +static void +rsz_set_rsz_regs(void *__iomem rsz_base, unsigned int rsz_id, + struct resizer_params *params) +{ + struct resizer_scale_param *rsc_params; + struct rsz_ext_mem_param *ext_mem; + struct resizer_rgb *rgb; + u32 reg_base; + u32 val; + + rsc_params = ¶ms->rsz_rsc_param[rsz_id]; + rgb = ¶ms->rsz2rgb[rsz_id]; + ext_mem = ¶ms->ext_mem_param[rsz_id]; + + if (rsz_id == RSZ_A) { + val = rsc_params->h_flip << RSZA_H_FLIP_SHIFT; + val |= rsc_params->v_flip << RSZA_V_FLIP_SHIFT; + reg_base = RSZ_EN_A; + } else { + val = rsc_params->h_flip << RSZB_H_FLIP_SHIFT; + val |= rsc_params->v_flip << RSZB_V_FLIP_SHIFT; + reg_base = RSZ_EN_B; + } + /* update flip settings */ + regw_rsz(rsz_base, val, RSZ_SEQ); + + regw_rsz(rsz_base, params->oper_mode, reg_base + RSZ_MODE); + + val = (rsc_params->cen << RSZ_CEN_SHIFT) | rsc_params->yen; + regw_rsz(rsz_base, val, reg_base + RSZ_420); + + regw_rsz(rsz_base, rsc_params->i_vps & RSZ_VPS_MASK, + reg_base + RSZ_I_VPS); + regw_rsz(rsz_base, rsc_params->i_hps & RSZ_HPS_MASK, + reg_base + RSZ_I_HPS); + regw_rsz(rsz_base, rsc_params->o_vsz & RSZ_O_VSZ_MASK, + reg_base + RSZ_O_VSZ); + regw_rsz(rsz_base, rsc_params->o_hsz & RSZ_O_HSZ_MASK, + reg_base + RSZ_O_HSZ); + regw_rsz(rsz_base, rsc_params->v_phs_y & RSZ_V_PHS_MASK, + reg_base + RSZ_V_PHS_Y); + regw_rsz(rsz_base, rsc_params->v_phs_c & RSZ_V_PHS_MASK, + reg_base + RSZ_V_PHS_C); + + /* keep this additional adjustment to zero for now */ + regw_rsz(rsz_base, rsc_params->v_dif & RSZ_V_DIF_MASK, + reg_base + RSZ_V_DIF); + + val = (rsc_params->v_typ_y & 1) | + ((rsc_params->v_typ_c & 1) << RSZ_TYP_C_SHIFT); + regw_rsz(rsz_base, val, reg_base + RSZ_V_TYP); + + val = (rsc_params->v_lpf_int_y & RSZ_LPF_INT_MASK) | + ((rsc_params->v_lpf_int_c & RSZ_LPF_INT_MASK) << + RSZ_LPF_INT_C_SHIFT); + regw_rsz(rsz_base, val, reg_base + RSZ_V_LPF); + + regw_rsz(rsz_base, rsc_params->h_phs & + RSZ_H_PHS_MASK, reg_base + RSZ_H_PHS); + + regw_rsz(rsz_base, 0, reg_base + RSZ_H_PHS_ADJ); + regw_rsz(rsz_base, rsc_params->h_dif & + RSZ_H_DIF_MASK, reg_base + RSZ_H_DIF); + + val = (rsc_params->h_typ_y & 1) | + ((rsc_params->h_typ_c & 1) << RSZ_TYP_C_SHIFT); + regw_rsz(rsz_base, val, reg_base + RSZ_H_TYP); + + val = (rsc_params->h_lpf_int_y & RSZ_LPF_INT_MASK) | + ((rsc_params->h_lpf_int_c & RSZ_LPF_INT_MASK) << + RSZ_LPF_INT_C_SHIFT); + regw_rsz(rsz_base, val, reg_base + RSZ_H_LPF); + + regw_rsz(rsz_base, rsc_params->dscale_en & 1, reg_base + RSZ_DWN_EN); + + val = (rsc_params->h_dscale_ave_sz & RSZ_DWN_SCALE_AV_SZ_MASK) | + ((rsc_params->v_dscale_ave_sz & RSZ_DWN_SCALE_AV_SZ_MASK) << + RSZ_DWN_SCALE_AV_SZ_V_SHIFT); + regw_rsz(rsz_base, val, reg_base + RSZ_DWN_AV); + + /* setting rgb conversion parameters */ + regw_rsz(rsz_base, rgb->rgb_en, reg_base + RSZ_RGB_EN); + + val = (rgb->rgb_typ << RSZ_RGB_TYP_SHIFT) | + (rgb->rgb_msk0 << RSZ_RGB_MSK0_SHIFT) | + (rgb->rgb_msk1 << RSZ_RGB_MSK1_SHIFT); + regw_rsz(rsz_base, val, reg_base + RSZ_RGB_TYP); + + regw_rsz(rsz_base, rgb->rgb_alpha_val & RSZ_RGB_ALPHA_MASK, + reg_base + RSZ_RGB_BLD); + + /* setting external memory parameters */ + regw_rsz(rsz_base, ext_mem->rsz_sdr_oft_y, reg_base + RSZ_SDR_Y_OFT); + regw_rsz(rsz_base, ext_mem->rsz_sdr_ptr_s_y, + reg_base + RSZ_SDR_Y_PTR_S); + regw_rsz(rsz_base, ext_mem->rsz_sdr_ptr_e_y, + reg_base + RSZ_SDR_Y_PTR_E); + regw_rsz(rsz_base, ext_mem->rsz_sdr_oft_c, reg_base + RSZ_SDR_C_OFT); + regw_rsz(rsz_base, ext_mem->rsz_sdr_ptr_s_c, + reg_base + RSZ_SDR_C_PTR_S); + regw_rsz(rsz_base, (ext_mem->rsz_sdr_ptr_e_c >> 1), + reg_base + RSZ_SDR_C_PTR_E); +} + +/*set the registers of either RSZ0 or RSZ1 */ +static void +ipipe_setup_resizer(void *__iomem rsz_base, struct resizer_params *params) +{ + /* enable MMR gate to write to Resizer */ + regw_rsz(rsz_base, 1, RSZ_GCK_MMR); + + /* Enable resizer if it is not in bypass mode */ + if (params->rsz_common.passthrough) + regw_rsz(rsz_base, 0, RSZ_GCK_SDR); + else + regw_rsz(rsz_base, 1, RSZ_GCK_SDR); + + rsz_set_common_params(rsz_base, params); + + regw_rsz(rsz_base, params->rsz_en[RSZ_A], RSZ_EN_A); + + if (params->rsz_en[RSZ_A]) + /*setting rescale parameters */ + rsz_set_rsz_regs(rsz_base, RSZ_A, params); + + regw_rsz(rsz_base, params->rsz_en[RSZ_B], RSZ_EN_B); + + if (params->rsz_en[RSZ_B]) + rsz_set_rsz_regs(rsz_base, RSZ_B, params); +} + +static u32 ipipe_get_color_pat(enum v4l2_mbus_pixelcode pix) +{ + switch (pix) { + case V4L2_MBUS_FMT_SGRBG10_ALAW8_1X8: + case V4L2_MBUS_FMT_SGRBG10_DPCM8_1X8: + case V4L2_MBUS_FMT_SGRBG12_1X12: + return ipipe_sgrbg_pattern; + + default: + return ipipe_srggb_pattern; + } +} + +static int ipipe_get_data_path(struct vpfe_ipipe_device *ipipe) +{ + enum v4l2_mbus_pixelcode temp_pix_fmt; + + switch (ipipe->formats[IPIPE_PAD_SINK].code) { + case V4L2_MBUS_FMT_SBGGR8_1X8: + case V4L2_MBUS_FMT_SGRBG10_ALAW8_1X8: + case V4L2_MBUS_FMT_SGRBG10_DPCM8_1X8: + case V4L2_MBUS_FMT_SGRBG12_1X12: + temp_pix_fmt = V4L2_MBUS_FMT_SGRBG12_1X12; + break; + + default: + temp_pix_fmt = V4L2_MBUS_FMT_UYVY8_2X8; + } + + if (temp_pix_fmt == V4L2_MBUS_FMT_SGRBG12_1X12) { + if (ipipe->formats[IPIPE_PAD_SOURCE].code == + V4L2_MBUS_FMT_SGRBG12_1X12) + return IPIPE_RAW2RAW; + return IPIPE_RAW2YUV; + } + + return IPIPE_YUV2YUV; +} + +static int get_ipipe_mode(struct vpfe_ipipe_device *ipipe) +{ + struct vpfe_device *vpfe_dev = to_vpfe_device(ipipe); + u16 ipipeif_sink = vpfe_dev->vpfe_ipipeif.input; + + if (ipipeif_sink == IPIPEIF_INPUT_MEMORY) + return IPIPE_MODE_SINGLE_SHOT; + else if (ipipeif_sink == IPIPEIF_INPUT_ISIF) + return IPIPE_MODE_CONTINUOUS; + + return -EINVAL; +} + +int config_ipipe_hw(struct vpfe_ipipe_device *ipipe) +{ + struct vpfe_ipipe_input_config *config = &ipipe->config.input_config; + void __iomem *ipipe_base = ipipe->base_addr; + struct v4l2_mbus_framefmt *outformat; + u32 color_pat; + u32 ipipe_mode; + u32 data_path; + + /* enable clock to IPIPE */ + vpss_enable_clock(VPSS_IPIPE_CLOCK, 1); + ipipe_clock_enable(ipipe_base); + + if (ipipe->input == IPIPE_INPUT_NONE) { + regw_ip(ipipe_base, 0, IPIPE_SRC_EN); + return 0; + } + + ipipe_mode = get_ipipe_mode(ipipe); + if (ipipe_mode < 0) { + pr_err("Failed to get ipipe mode"); + return -EINVAL; + } + regw_ip(ipipe_base, ipipe_mode, IPIPE_SRC_MODE); + + data_path = ipipe_get_data_path(ipipe); + regw_ip(ipipe_base, data_path, IPIPE_SRC_FMT); + + regw_ip(ipipe_base, config->vst & IPIPE_RSZ_VPS_MASK, IPIPE_SRC_VPS); + regw_ip(ipipe_base, config->hst & IPIPE_RSZ_HPS_MASK, IPIPE_SRC_HPS); + + outformat = &ipipe->formats[IPIPE_PAD_SOURCE]; + regw_ip(ipipe_base, (outformat->height + 1) & IPIPE_RSZ_VSZ_MASK, + IPIPE_SRC_VSZ); + regw_ip(ipipe_base, (outformat->width + 1) & IPIPE_RSZ_HSZ_MASK, + IPIPE_SRC_HSZ); + + if (data_path == IPIPE_RAW2YUV || + data_path == IPIPE_RAW2RAW) { + color_pat = + ipipe_get_color_pat(ipipe->formats[IPIPE_PAD_SINK].code); + regw_ip(ipipe_base, color_pat, IPIPE_SRC_COL); + } + + return 0; +} + +/* + * config_rsz_hw() - Performs hardware setup of resizer. + */ +int config_rsz_hw(struct vpfe_resizer_device *resizer, + struct resizer_params *config) +{ + struct vpfe_device *vpfe_dev = to_vpfe_device(resizer); + void *__iomem ipipe_base = vpfe_dev->vpfe_ipipe.base_addr; + void *__iomem rsz_base = vpfe_dev->vpfe_resizer.base_addr; + + /* enable VPSS clock */ + vpss_enable_clock(VPSS_IPIPE_CLOCK, 1); + ipipe_clock_enable(ipipe_base); + + ipipe_setup_resizer(rsz_base, config); + + return 0; +} + +static void +rsz_set_y_address(void *__iomem rsz_base, unsigned int address, + unsigned int offset) +{ + u32 val; + + val = address & SET_LOW_ADDR; + regw_rsz(rsz_base, val, offset + RSZ_SDR_Y_BAD_L); + regw_rsz(rsz_base, val, offset + RSZ_SDR_Y_SAD_L); + + val = (address & SET_HIGH_ADDR) >> 16; + regw_rsz(rsz_base, val, offset + RSZ_SDR_Y_BAD_H); + regw_rsz(rsz_base, val, offset + RSZ_SDR_Y_SAD_H); +} + +static void +rsz_set_c_address(void *__iomem rsz_base, unsigned int address, + unsigned int offset) +{ + u32 val; + + val = address & SET_LOW_ADDR; + regw_rsz(rsz_base, val, offset + RSZ_SDR_C_BAD_L); + regw_rsz(rsz_base, val, offset + RSZ_SDR_C_SAD_L); + + val = (address & SET_HIGH_ADDR) >> 16; + regw_rsz(rsz_base, val, offset + RSZ_SDR_C_BAD_H); + regw_rsz(rsz_base, val, offset + RSZ_SDR_C_SAD_H); +} + +/* + * resizer_set_outaddr() - set the address for given resize_no + * @rsz_base: resizer base address + * @params: pointer to ipipe_params structure + * @resize_no: 0 - Resizer-A, 1 - Resizer B + * @address: the address to set + */ +int +resizer_set_outaddr(void *__iomem rsz_base, struct resizer_params *params, + int resize_no, unsigned int address) +{ + struct resizer_scale_param *rsc_param; + struct rsz_ext_mem_param *mem_param; + struct rsz_common_params *rsz_common; + unsigned int rsz_start_add; + unsigned int val; + + if (resize_no != RSZ_A && resize_no != RSZ_B) + return -EINVAL; + + mem_param = ¶ms->ext_mem_param[resize_no]; + rsc_param = ¶ms->rsz_rsc_param[resize_no]; + rsz_common = ¶ms->rsz_common; + + if (resize_no == RSZ_A) + rsz_start_add = RSZ_EN_A; + else + rsz_start_add = RSZ_EN_B; + + /* y_c = 0 for y, = 1 for c */ + if (rsz_common->src_img_fmt == RSZ_IMG_420) { + if (rsz_common->y_c) { + /* C channel */ + val = address + mem_param->flip_ofst_c; + rsz_set_c_address(rsz_base, val, rsz_start_add); + } else { + val = address + mem_param->flip_ofst_y; + rsz_set_y_address(rsz_base, val, rsz_start_add); + } + } else { + if (rsc_param->cen && rsc_param->yen) { + /* 420 */ + val = address + mem_param->c_offset + + mem_param->flip_ofst_c + + mem_param->user_y_ofst + + mem_param->user_c_ofst; + if (resize_no == RSZ_B) + val += + params->ext_mem_param[RSZ_A].user_y_ofst + + params->ext_mem_param[RSZ_A].user_c_ofst; + /* set C address */ + rsz_set_c_address(rsz_base, val, rsz_start_add); + } + val = address + mem_param->flip_ofst_y + mem_param->user_y_ofst; + if (resize_no == RSZ_B) + val += params->ext_mem_param[RSZ_A].user_y_ofst + + params->ext_mem_param[RSZ_A].user_c_ofst; + /* set Y address */ + rsz_set_y_address(rsz_base, val, rsz_start_add); + } + /* resizer must be enabled */ + regw_rsz(rsz_base, params->rsz_en[resize_no], rsz_start_add); + + return 0; +} + +void +ipipe_set_lutdpc_regs(void *__iomem base_addr, void *__iomem isp5_base_addr, + struct vpfe_ipipe_lutdpc *dpc) +{ + u32 max_tbl_size = LUT_DPC_MAX_SIZE >> 1; + u32 lut_start_addr = DPC_TB0_START_ADDR; + u32 val; + u32 count; + + ipipe_clock_enable(base_addr); + regw_ip(base_addr, dpc->en, DPC_LUT_EN); + + if (dpc->en != 1) + return; + + val = LUTDPC_TBL_256_EN | (dpc->repl_white & 1); + regw_ip(base_addr, val, DPC_LUT_SEL); + regw_ip(base_addr, LUT_DPC_START_ADDR, DPC_LUT_ADR); + regw_ip(base_addr, dpc->dpc_size, DPC_LUT_SIZ & LUT_DPC_SIZE_MASK); + + if (dpc->table == NULL) + return; + + for (count = 0; count < dpc->dpc_size; count++) { + if (count >= max_tbl_size) + lut_start_addr = DPC_TB1_START_ADDR; + val = (dpc->table[count].horz_pos & LUT_DPC_H_POS_MASK) | + ((dpc->table[count].vert_pos & LUT_DPC_V_POS_MASK) << + LUT_DPC_V_POS_SHIFT) | (dpc->table[count].method << + LUT_DPC_CORR_METH_SHIFT); + w_ip_table(isp5_base_addr, val, (lut_start_addr + + ((count % max_tbl_size) << 2))); + } +} + +static void +set_dpc_thresholds(void *__iomem base_addr, + struct vpfe_ipipe_otfdpc_2_0_cfg *dpc_thr) +{ + regw_ip(base_addr, dpc_thr->corr_thr.r & OTFDPC_DPC2_THR_MASK, + DPC_OTF_2C_THR_R); + regw_ip(base_addr, dpc_thr->corr_thr.gr & OTFDPC_DPC2_THR_MASK, + DPC_OTF_2C_THR_GR); + regw_ip(base_addr, dpc_thr->corr_thr.gb & OTFDPC_DPC2_THR_MASK, + DPC_OTF_2C_THR_GB); + regw_ip(base_addr, dpc_thr->corr_thr.b & OTFDPC_DPC2_THR_MASK, + DPC_OTF_2C_THR_B); + regw_ip(base_addr, dpc_thr->det_thr.r & OTFDPC_DPC2_THR_MASK, + DPC_OTF_2D_THR_R); + regw_ip(base_addr, dpc_thr->det_thr.gr & OTFDPC_DPC2_THR_MASK, + DPC_OTF_2D_THR_GR); + regw_ip(base_addr, dpc_thr->det_thr.gb & OTFDPC_DPC2_THR_MASK, + DPC_OTF_2D_THR_GB); + regw_ip(base_addr, dpc_thr->det_thr.b & OTFDPC_DPC2_THR_MASK, + DPC_OTF_2D_THR_B); +} + +void ipipe_set_otfdpc_regs(void *__iomem base_addr, + struct vpfe_ipipe_otfdpc *otfdpc) +{ + struct vpfe_ipipe_otfdpc_2_0_cfg *dpc_2_0 = &otfdpc->alg_cfg.dpc_2_0; + struct vpfe_ipipe_otfdpc_3_0_cfg *dpc_3_0 = &otfdpc->alg_cfg.dpc_3_0; + u32 val; + + ipipe_clock_enable(base_addr); + + regw_ip(base_addr, (otfdpc->en & 1), DPC_OTF_EN); + if (!otfdpc->en) + return; + + /* dpc enabled */ + val = (otfdpc->det_method << OTF_DET_METHOD_SHIFT) | otfdpc->alg; + regw_ip(base_addr, val, DPC_OTF_TYP); + + if (otfdpc->det_method == VPFE_IPIPE_DPC_OTF_MIN_MAX) { + /* ALG= 0, TYP = 0, DPC_OTF_2D_THR_[x]=0 + * DPC_OTF_2C_THR_[x] = Maximum thresohld + * MinMax method + */ + dpc_2_0->det_thr.r = dpc_2_0->det_thr.gb = + dpc_2_0->det_thr.gr = dpc_2_0->det_thr.b = 0; + set_dpc_thresholds(base_addr, dpc_2_0); + return; + } + /* MinMax2 */ + if (otfdpc->alg == VPFE_IPIPE_OTFDPC_2_0) { + set_dpc_thresholds(base_addr, dpc_2_0); + return; + } + regw_ip(base_addr, dpc_3_0->act_adj_shf & + OTF_DPC3_0_SHF_MASK, DPC_OTF_3_SHF); + /* Detection thresholds */ + regw_ip(base_addr, ((dpc_3_0->det_thr & OTF_DPC3_0_THR_MASK) << + OTF_DPC3_0_THR_SHIFT), DPC_OTF_3D_THR); + regw_ip(base_addr, dpc_3_0->det_slp & + OTF_DPC3_0_SLP_MASK, DPC_OTF_3D_SLP); + regw_ip(base_addr, dpc_3_0->det_thr_min & + OTF_DPC3_0_DET_MASK, DPC_OTF_3D_MIN); + regw_ip(base_addr, dpc_3_0->det_thr_max & + OTF_DPC3_0_DET_MASK, DPC_OTF_3D_MAX); + /* Correction thresholds */ + regw_ip(base_addr, ((dpc_3_0->corr_thr & OTF_DPC3_0_THR_MASK) << + OTF_DPC3_0_THR_SHIFT), DPC_OTF_3C_THR); + regw_ip(base_addr, dpc_3_0->corr_slp & + OTF_DPC3_0_SLP_MASK, DPC_OTF_3C_SLP); + regw_ip(base_addr, dpc_3_0->corr_thr_min & + OTF_DPC3_0_CORR_MASK, DPC_OTF_3C_MIN); + regw_ip(base_addr, dpc_3_0->corr_thr_max & + OTF_DPC3_0_CORR_MASK, DPC_OTF_3C_MAX); +} + +/* 2D Noise filter */ +void +ipipe_set_d2f_regs(void *__iomem base_addr, unsigned int id, + struct vpfe_ipipe_nf *noise_filter) +{ + + u32 offset = D2F_1ST; + int count; + u32 val; + + if (id == IPIPE_D2F_2ND) + offset = D2F_2ND; + + ipipe_clock_enable(base_addr); + regw_ip(base_addr, noise_filter->en & 1, offset + D2F_EN); + if (!noise_filter->en) + return; + + /*noise filter enabled */ + /* Combine all the fields to make D2F_CFG register of IPIPE */ + val = ((noise_filter->spread_val & D2F_SPR_VAL_MASK) << + D2F_SPR_VAL_SHIFT) | ((noise_filter->shft_val & + D2F_SHFT_VAL_MASK) << D2F_SHFT_VAL_SHIFT) | + (noise_filter->gr_sample_meth << D2F_SAMPLE_METH_SHIFT) | + ((noise_filter->apply_lsc_gain & 1) << + D2F_APPLY_LSC_GAIN_SHIFT) | D2F_USE_SPR_REG_VAL; + regw_ip(base_addr, val, offset + D2F_TYP); + + /* edge detection minimum */ + regw_ip(base_addr, noise_filter->edge_det_min_thr & + D2F_EDGE_DET_THR_MASK, offset + D2F_EDG_MIN); + + /* edge detection maximum */ + regw_ip(base_addr, noise_filter->edge_det_max_thr & + D2F_EDGE_DET_THR_MASK, offset + D2F_EDG_MAX); + + for (count = 0; count < VPFE_IPIPE_NF_STR_TABLE_SIZE; count++) + regw_ip(base_addr, + (noise_filter->str[count] & D2F_STR_VAL_MASK), + offset + D2F_STR + count * 4); + + for (count = 0; count < VPFE_IPIPE_NF_THR_TABLE_SIZE; count++) + regw_ip(base_addr, noise_filter->thr[count] & D2F_THR_VAL_MASK, + offset + D2F_THR + count * 4); +} + +#define IPIPE_U8Q5(decimal, integer) \ + (((decimal & 0x1f) | ((integer & 0x7) << 5))) + +/* Green Imbalance Correction */ +void ipipe_set_gic_regs(void *__iomem base_addr, struct vpfe_ipipe_gic *gic) +{ + u32 val; + + ipipe_clock_enable(base_addr); + regw_ip(base_addr, gic->en & 1, GIC_EN); + + if (!gic->en) + return; + + /*gic enabled */ + val = (gic->wt_fn_type << GIC_TYP_SHIFT) | + (gic->thr_sel << GIC_THR_SEL_SHIFT) | + ((gic->apply_lsc_gain & 1) << GIC_APPLY_LSC_GAIN_SHIFT); + regw_ip(base_addr, val, GIC_TYP); + + regw_ip(base_addr, gic->gain & GIC_GAIN_MASK, GIC_GAN); + + if (gic->gic_alg != VPFE_IPIPE_GIC_ALG_ADAPT_GAIN) { + /* Constant Gain. Set threshold to maximum */ + regw_ip(base_addr, GIC_THR_MASK, GIC_THR); + return; + } + + if (gic->thr_sel == VPFE_IPIPE_GIC_THR_REG) { + regw_ip(base_addr, gic->thr & GIC_THR_MASK, GIC_THR); + regw_ip(base_addr, gic->slope & GIC_SLOPE_MASK, GIC_SLP); + } else { + /* Use NF thresholds */ + val = IPIPE_U8Q5(gic->nf2_thr_gain.decimal, + gic->nf2_thr_gain.integer); + regw_ip(base_addr, val, GIC_NFGAN); + } +} + +#define IPIPE_U13Q9(decimal, integer) \ + (((decimal & 0x1ff) | ((integer & 0xf) << 9))) +/* White balance */ +void ipipe_set_wb_regs(void *__iomem base_addr, struct vpfe_ipipe_wb *wb) +{ + u32 val; + + ipipe_clock_enable(base_addr); + /* Ofsets. S12 */ + regw_ip(base_addr, wb->ofst_r & WB_OFFSET_MASK, WB2_OFT_R); + regw_ip(base_addr, wb->ofst_gr & WB_OFFSET_MASK, WB2_OFT_GR); + regw_ip(base_addr, wb->ofst_gb & WB_OFFSET_MASK, WB2_OFT_GB); + regw_ip(base_addr, wb->ofst_b & WB_OFFSET_MASK, WB2_OFT_B); + + /* Gains. U13Q9 */ + val = IPIPE_U13Q9(wb->gain_r.decimal, wb->gain_r.integer); + regw_ip(base_addr, val, WB2_WGN_R); + + val = IPIPE_U13Q9(wb->gain_gr.decimal, wb->gain_gr.integer); + regw_ip(base_addr, val, WB2_WGN_GR); + + val = IPIPE_U13Q9(wb->gain_gb.decimal, wb->gain_gb.integer); + regw_ip(base_addr, val, WB2_WGN_GB); + + val = IPIPE_U13Q9(wb->gain_b.decimal, wb->gain_b.integer); + regw_ip(base_addr, val, WB2_WGN_B); +} + +/* CFA */ +void ipipe_set_cfa_regs(void *__iomem base_addr, struct vpfe_ipipe_cfa *cfa) +{ + ipipe_clock_enable(base_addr); + + regw_ip(base_addr, cfa->alg, CFA_MODE); + regw_ip(base_addr, cfa->hpf_thr_2dir & CFA_HPF_THR_2DIR_MASK, + CFA_2DIR_HPF_THR); + regw_ip(base_addr, cfa->hpf_slp_2dir & CFA_HPF_SLOPE_2DIR_MASK, + CFA_2DIR_HPF_SLP); + regw_ip(base_addr, cfa->hp_mix_thr_2dir & CFA_HPF_MIX_THR_2DIR_MASK, + CFA_2DIR_MIX_THR); + regw_ip(base_addr, cfa->hp_mix_slope_2dir & CFA_HPF_MIX_SLP_2DIR_MASK, + CFA_2DIR_MIX_SLP); + regw_ip(base_addr, cfa->dir_thr_2dir & CFA_DIR_THR_2DIR_MASK, + CFA_2DIR_DIR_THR); + regw_ip(base_addr, cfa->dir_slope_2dir & CFA_DIR_SLP_2DIR_MASK, + CFA_2DIR_DIR_SLP); + regw_ip(base_addr, cfa->nd_wt_2dir & CFA_ND_WT_2DIR_MASK, + CFA_2DIR_NDWT); + regw_ip(base_addr, cfa->hue_fract_daa & CFA_DAA_HUE_FRA_MASK, + CFA_MONO_HUE_FRA); + regw_ip(base_addr, cfa->edge_thr_daa & CFA_DAA_EDG_THR_MASK, + CFA_MONO_EDG_THR); + regw_ip(base_addr, cfa->thr_min_daa & CFA_DAA_THR_MIN_MASK, + CFA_MONO_THR_MIN); + regw_ip(base_addr, cfa->thr_slope_daa & CFA_DAA_THR_SLP_MASK, + CFA_MONO_THR_SLP); + regw_ip(base_addr, cfa->slope_min_daa & CFA_DAA_SLP_MIN_MASK, + CFA_MONO_SLP_MIN); + regw_ip(base_addr, cfa->slope_slope_daa & CFA_DAA_SLP_SLP_MASK, + CFA_MONO_SLP_SLP); + regw_ip(base_addr, cfa->lp_wt_daa & CFA_DAA_LP_WT_MASK, + CFA_MONO_LPWT); +} + +void +ipipe_set_rgb2rgb_regs(void *__iomem base_addr, unsigned int id, + struct vpfe_ipipe_rgb2rgb *rgb) +{ + u32 offset_mask = RGB2RGB_1_OFST_MASK; + u32 offset = RGB1_MUL_BASE; + u32 integ_mask = 0xf; + u32 val; + + ipipe_clock_enable(base_addr); + + if (id == IPIPE_RGB2RGB_2) { + /* For second RGB module, gain integer is 3 bits instead + of 4, offset has 11 bits insread of 13 */ + offset = RGB2_MUL_BASE; + integ_mask = 0x7; + offset_mask = RGB2RGB_2_OFST_MASK; + } + /* Gains */ + val = (rgb->coef_rr.decimal & 0xff) | + ((rgb->coef_rr.integer & integ_mask) << 8); + regw_ip(base_addr, val, offset + RGB_MUL_RR); + val = (rgb->coef_gr.decimal & 0xff) | + ((rgb->coef_gr.integer & integ_mask) << 8); + regw_ip(base_addr, val, offset + RGB_MUL_GR); + val = (rgb->coef_br.decimal & 0xff) | + ((rgb->coef_br.integer & integ_mask) << 8); + regw_ip(base_addr, val, offset + RGB_MUL_BR); + val = (rgb->coef_rg.decimal & 0xff) | + ((rgb->coef_rg.integer & integ_mask) << 8); + regw_ip(base_addr, val, offset + RGB_MUL_RG); + val = (rgb->coef_gg.decimal & 0xff) | + ((rgb->coef_gg.integer & integ_mask) << 8); + regw_ip(base_addr, val, offset + RGB_MUL_GG); + val = (rgb->coef_bg.decimal & 0xff) | + ((rgb->coef_bg.integer & integ_mask) << 8); + regw_ip(base_addr, val, offset + RGB_MUL_BG); + val = (rgb->coef_rb.decimal & 0xff) | + ((rgb->coef_rb.integer & integ_mask) << 8); + regw_ip(base_addr, val, offset + RGB_MUL_RB); + val = (rgb->coef_gb.decimal & 0xff) | + ((rgb->coef_gb.integer & integ_mask) << 8); + regw_ip(base_addr, val, offset + RGB_MUL_GB); + val = (rgb->coef_bb.decimal & 0xff) | + ((rgb->coef_bb.integer & integ_mask) << 8); + regw_ip(base_addr, val, offset + RGB_MUL_BB); + + /* Offsets */ + regw_ip(base_addr, rgb->out_ofst_r & offset_mask, offset + RGB_OFT_OR); + regw_ip(base_addr, rgb->out_ofst_g & offset_mask, offset + RGB_OFT_OG); + regw_ip(base_addr, rgb->out_ofst_b & offset_mask, offset + RGB_OFT_OB); +} + +static void +ipipe_update_gamma_tbl(void *__iomem isp5_base_addr, + struct vpfe_ipipe_gamma_entry *table, int size, u32 addr) +{ + int count; + u32 val; + + for (count = 0; count < size; count++) { + val = table[count].slope & GAMMA_MASK; + val |= (table[count].offset & GAMMA_MASK) << GAMMA_SHIFT; + w_ip_table(isp5_base_addr, val, (addr + (count * 4))); + } +} + +void +ipipe_set_gamma_regs(void *__iomem base_addr, void *__iomem isp5_base_addr, + struct vpfe_ipipe_gamma *gamma) +{ + int table_size; + u32 val; + + ipipe_clock_enable(base_addr); + val = (gamma->bypass_r << GAMMA_BYPR_SHIFT) | + (gamma->bypass_b << GAMMA_BYPG_SHIFT) | + (gamma->bypass_g << GAMMA_BYPB_SHIFT) | + (gamma->tbl_sel << GAMMA_TBL_SEL_SHIFT) | + (gamma->tbl_size << GAMMA_TBL_SIZE_SHIFT); + + regw_ip(base_addr, val, GMM_CFG); + + if (gamma->tbl_sel != VPFE_IPIPE_GAMMA_TBL_RAM) + return; + + table_size = gamma->tbl_size; + + if (!gamma->bypass_r && gamma->table_r != NULL) + ipipe_update_gamma_tbl(isp5_base_addr, gamma->table_r, + table_size, GAMMA_R_START_ADDR); + if (!gamma->bypass_b && gamma->table_b != NULL) + ipipe_update_gamma_tbl(isp5_base_addr, gamma->table_b, + table_size, GAMMA_B_START_ADDR); + if (!gamma->bypass_g && gamma->table_g != NULL) + ipipe_update_gamma_tbl(isp5_base_addr, gamma->table_g, + table_size, GAMMA_G_START_ADDR); +} + +void +ipipe_set_3d_lut_regs(void *__iomem base_addr, void *__iomem isp5_base_addr, + struct vpfe_ipipe_3d_lut *lut_3d) +{ + struct vpfe_ipipe_3d_lut_entry *tbl; + u32 bnk_index; + u32 tbl_index; + u32 val; + u32 i; + + ipipe_clock_enable(base_addr); + regw_ip(base_addr, lut_3d->en, D3LUT_EN); + + if (!lut_3d->en) + return; + + /* lut_3d enabled */ + if (!lut_3d->table) + return; + + /* valied table */ + tbl = lut_3d->table; + for (i = 0; i < VPFE_IPIPE_MAX_SIZE_3D_LUT; i++) { + /* Each entry has 0-9 (B), 10-19 (G) and + 20-29 R values */ + val = tbl[i].b & D3_LUT_ENTRY_MASK; + val |= (tbl[i].g & D3_LUT_ENTRY_MASK) << + D3_LUT_ENTRY_G_SHIFT; + val |= (tbl[i].r & D3_LUT_ENTRY_MASK) << + D3_LUT_ENTRY_R_SHIFT; + bnk_index = i % 4; + tbl_index = i >> 2; + tbl_index <<= 2; + if (bnk_index == 0) + w_ip_table(isp5_base_addr, val, + tbl_index + D3L_TB0_START_ADDR); + else if (bnk_index == 1) + w_ip_table(isp5_base_addr, val, + tbl_index + D3L_TB1_START_ADDR); + else if (bnk_index == 2) + w_ip_table(isp5_base_addr, val, + tbl_index + D3L_TB2_START_ADDR); + else + w_ip_table(isp5_base_addr, val, + tbl_index + D3L_TB3_START_ADDR); + } +} + +/* Lumina adjustments */ +void +ipipe_set_lum_adj_regs(void *__iomem base_addr, struct ipipe_lum_adj *lum_adj) +{ + u32 val; + + ipipe_clock_enable(base_addr); + + /* combine fields of YUV_ADJ to set brightness and contrast */ + val = lum_adj->contrast << LUM_ADJ_CONTR_SHIFT | + lum_adj->brightness << LUM_ADJ_BRIGHT_SHIFT; + regw_ip(base_addr, val, YUV_ADJ); +} + +#define IPIPE_S12Q8(decimal, integer) \ + (((decimal & 0xff) | ((integer & 0xf) << 8))) + +void ipipe_set_rgb2ycbcr_regs(void *__iomem base_addr, + struct vpfe_ipipe_rgb2yuv *yuv) +{ + u32 val; + + /* S10Q8 */ + ipipe_clock_enable(base_addr); + val = IPIPE_S12Q8(yuv->coef_ry.decimal, yuv->coef_ry.integer); + regw_ip(base_addr, val, YUV_MUL_RY); + val = IPIPE_S12Q8(yuv->coef_gy.decimal, yuv->coef_gy.integer); + regw_ip(base_addr, val, YUV_MUL_GY); + val = IPIPE_S12Q8(yuv->coef_by.decimal, yuv->coef_by.integer); + regw_ip(base_addr, val, YUV_MUL_BY); + val = IPIPE_S12Q8(yuv->coef_rcb.decimal, yuv->coef_rcb.integer); + regw_ip(base_addr, val, YUV_MUL_RCB); + val = IPIPE_S12Q8(yuv->coef_gcb.decimal, yuv->coef_gcb.integer); + regw_ip(base_addr, val, YUV_MUL_GCB); + val = IPIPE_S12Q8(yuv->coef_bcb.decimal, yuv->coef_bcb.integer); + regw_ip(base_addr, val, YUV_MUL_BCB); + val = IPIPE_S12Q8(yuv->coef_rcr.decimal, yuv->coef_rcr.integer); + regw_ip(base_addr, val, YUV_MUL_RCR); + val = IPIPE_S12Q8(yuv->coef_gcr.decimal, yuv->coef_gcr.integer); + regw_ip(base_addr, val, YUV_MUL_GCR); + val = IPIPE_S12Q8(yuv->coef_bcr.decimal, yuv->coef_bcr.integer); + regw_ip(base_addr, val, YUV_MUL_BCR); + regw_ip(base_addr, yuv->out_ofst_y & RGB2YCBCR_OFST_MASK, YUV_OFT_Y); + regw_ip(base_addr, yuv->out_ofst_cb & RGB2YCBCR_OFST_MASK, YUV_OFT_CB); + regw_ip(base_addr, yuv->out_ofst_cr & RGB2YCBCR_OFST_MASK, YUV_OFT_CR); +} + +/* YUV 422 conversion */ +void +ipipe_set_yuv422_conv_regs(void *__iomem base_addr, + struct vpfe_ipipe_yuv422_conv *conv) +{ + u32 val; + + ipipe_clock_enable(base_addr); + + /* Combine all the fields to make YUV_PHS register of IPIPE */ + val = (conv->chrom_pos << 0) | (conv->en_chrom_lpf << 1); + regw_ip(base_addr, val, YUV_PHS); +} + +void +ipipe_set_gbce_regs(void *__iomem base_addr, void *__iomem isp5_base_addr, + struct vpfe_ipipe_gbce *gbce) +{ + unsigned int count; + u32 mask = GBCE_Y_VAL_MASK; + + if (gbce->type == VPFE_IPIPE_GBCE_GAIN_TBL) + mask = GBCE_GAIN_VAL_MASK; + + ipipe_clock_enable(base_addr); + regw_ip(base_addr, gbce->en & 1, GBCE_EN); + + if (!gbce->en) + return; + + regw_ip(base_addr, gbce->type, GBCE_TYP); + + if (!gbce->table) + return; + + for (count = 0; count < VPFE_IPIPE_MAX_SIZE_GBCE_LUT; count += 2) + w_ip_table(isp5_base_addr, ((gbce->table[count + 1] & mask) << + GBCE_ENTRY_SHIFT) | (gbce->table[count] & mask), + ((count/2) << 2) + GBCE_TB_START_ADDR); +} + +void +ipipe_set_ee_regs(void *__iomem base_addr, void *__iomem isp5_base_addr, + struct vpfe_ipipe_yee *ee) +{ + unsigned int count; + u32 val; + + ipipe_clock_enable(base_addr); + regw_ip(base_addr, ee->en, YEE_EN); + + if (!ee->en) + return; + + val = ee->en_halo_red & 1; + val |= ee->merge_meth << YEE_HALO_RED_EN_SHIFT; + regw_ip(base_addr, val, YEE_TYP); + + regw_ip(base_addr, ee->hpf_shft, YEE_SHF); + regw_ip(base_addr, ee->hpf_coef_00 & YEE_COEF_MASK, YEE_MUL_00); + regw_ip(base_addr, ee->hpf_coef_01 & YEE_COEF_MASK, YEE_MUL_01); + regw_ip(base_addr, ee->hpf_coef_02 & YEE_COEF_MASK, YEE_MUL_02); + regw_ip(base_addr, ee->hpf_coef_10 & YEE_COEF_MASK, YEE_MUL_10); + regw_ip(base_addr, ee->hpf_coef_11 & YEE_COEF_MASK, YEE_MUL_11); + regw_ip(base_addr, ee->hpf_coef_12 & YEE_COEF_MASK, YEE_MUL_12); + regw_ip(base_addr, ee->hpf_coef_20 & YEE_COEF_MASK, YEE_MUL_20); + regw_ip(base_addr, ee->hpf_coef_21 & YEE_COEF_MASK, YEE_MUL_21); + regw_ip(base_addr, ee->hpf_coef_22 & YEE_COEF_MASK, YEE_MUL_22); + regw_ip(base_addr, ee->yee_thr & YEE_THR_MASK, YEE_THR); + regw_ip(base_addr, ee->es_gain & YEE_ES_GAIN_MASK, YEE_E_GAN); + regw_ip(base_addr, ee->es_thr1 & YEE_ES_THR1_MASK, YEE_E_THR1); + regw_ip(base_addr, ee->es_thr2 & YEE_THR_MASK, YEE_E_THR2); + regw_ip(base_addr, ee->es_gain_grad & YEE_THR_MASK, YEE_G_GAN); + regw_ip(base_addr, ee->es_ofst_grad & YEE_THR_MASK, YEE_G_OFT); + + if (ee->table == NULL) + return; + + for (count = 0; count < VPFE_IPIPE_MAX_SIZE_YEE_LUT; count += 2) + w_ip_table(isp5_base_addr, ((ee->table[count + 1] & + YEE_ENTRY_MASK) << YEE_ENTRY_SHIFT) | + (ee->table[count] & YEE_ENTRY_MASK), + ((count/2) << 2) + YEE_TB_START_ADDR); +} + +/* Chromatic Artifact Correction. CAR */ +static void ipipe_set_mf(void *__iomem base_addr) +{ + /* typ to dynamic switch */ + regw_ip(base_addr, VPFE_IPIPE_CAR_DYN_SWITCH, CAR_TYP); + /* Set SW0 to maximum */ + regw_ip(base_addr, CAR_MF_THR, CAR_SW); +} + +static void +ipipe_set_gain_ctrl(void *__iomem base_addr, struct vpfe_ipipe_car *car) +{ + regw_ip(base_addr, VPFE_IPIPE_CAR_CHR_GAIN_CTRL, CAR_TYP); + regw_ip(base_addr, car->hpf, CAR_HPF_TYP); + regw_ip(base_addr, car->hpf_shft & CAR_HPF_SHIFT_MASK, CAR_HPF_SHF); + regw_ip(base_addr, car->hpf_thr, CAR_HPF_THR); + regw_ip(base_addr, car->gain1.gain, CAR_GN1_GAN); + regw_ip(base_addr, car->gain1.shft & CAR_GAIN1_SHFT_MASK, CAR_GN1_SHF); + regw_ip(base_addr, car->gain1.gain_min & CAR_GAIN_MIN_MASK, + CAR_GN1_MIN); + regw_ip(base_addr, car->gain2.gain, CAR_GN2_GAN); + regw_ip(base_addr, car->gain2.shft & CAR_GAIN2_SHFT_MASK, CAR_GN2_SHF); + regw_ip(base_addr, car->gain2.gain_min & CAR_GAIN_MIN_MASK, + CAR_GN2_MIN); +} + +void ipipe_set_car_regs(void *__iomem base_addr, struct vpfe_ipipe_car *car) +{ + u32 val; + + ipipe_clock_enable(base_addr); + regw_ip(base_addr, car->en, CAR_EN); + + if (!car->en) + return; + + switch (car->meth) { + case VPFE_IPIPE_CAR_MED_FLTR: + ipipe_set_mf(base_addr); + break; + + case VPFE_IPIPE_CAR_CHR_GAIN_CTRL: + ipipe_set_gain_ctrl(base_addr, car); + break; + + default: + /* Dynamic switch between MF and Gain Ctrl. */ + ipipe_set_mf(base_addr); + ipipe_set_gain_ctrl(base_addr, car); + /* Set the threshold for switching between + * the two Here we overwrite the MF SW0 value + */ + regw_ip(base_addr, VPFE_IPIPE_CAR_DYN_SWITCH, CAR_TYP); + val = car->sw1; + val <<= CAR_SW1_SHIFT; + val |= car->sw0; + regw_ip(base_addr, val, CAR_SW); + } +} + +/* Chromatic Gain Suppression */ +void ipipe_set_cgs_regs(void *__iomem base_addr, struct vpfe_ipipe_cgs *cgs) +{ + ipipe_clock_enable(base_addr); + regw_ip(base_addr, cgs->en, CGS_EN); + + if (!cgs->en) + return; + + /* Set the bright side parameters */ + regw_ip(base_addr, cgs->h_thr, CGS_GN1_H_THR); + regw_ip(base_addr, cgs->h_slope, CGS_GN1_H_GAN); + regw_ip(base_addr, cgs->h_shft & CAR_SHIFT_MASK, CGS_GN1_H_SHF); + regw_ip(base_addr, cgs->h_min, CGS_GN1_H_MIN); +} + +void rsz_src_enable(void *__iomem rsz_base, int enable) +{ + regw_rsz(rsz_base, enable, RSZ_SRC_EN); +} + +int rsz_enable(void *__iomem rsz_base, int rsz_id, int enable) +{ + if (rsz_id == RSZ_A) { + regw_rsz(rsz_base, enable, RSZ_EN_A); + /* We always enable RSZ_A. RSZ_B is enable upon request from + * application. So enable RSZ_SRC_EN along with RSZ_A + */ + regw_rsz(rsz_base, enable, RSZ_SRC_EN); + } else if (rsz_id == RSZ_B) { + regw_rsz(rsz_base, enable, RSZ_EN_B); + } else { + BUG(); + } + + return 0; +} diff --git a/drivers/staging/media/davinci_vpfe/dm365_ipipe_hw.h b/drivers/staging/media/davinci_vpfe/dm365_ipipe_hw.h new file mode 100644 index 00000000000..010fdb247fa --- /dev/null +++ b/drivers/staging/media/davinci_vpfe/dm365_ipipe_hw.h @@ -0,0 +1,559 @@ +/* + * Copyright (C) 2012 Texas Instruments Inc + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Contributors: + * Manjunath Hadli <manjunath.hadli@ti.com> + * Prabhakar Lad <prabhakar.lad@ti.com> + */ + +#ifndef _DAVINCI_VPFE_DM365_IPIPE_HW_H +#define _DAVINCI_VPFE_DM365_IPIPE_HW_H + +#include "vpfe_mc_capture.h" + +#define SET_LOW_ADDR 0x0000ffff +#define SET_HIGH_ADDR 0xffff0000 + +/* Below are the internal tables */ +#define DPC_TB0_START_ADDR 0x8000 +#define DPC_TB1_START_ADDR 0x8400 + +#define GAMMA_R_START_ADDR 0xa800 +#define GAMMA_G_START_ADDR 0xb000 +#define GAMMA_B_START_ADDR 0xb800 + +/* RAM table addresses for edge enhancement correction*/ +#define YEE_TB_START_ADDR 0x8800 + +/* RAM table address for GBC LUT */ +#define GBCE_TB_START_ADDR 0x9000 + +/* RAM table for 3D NF LUT */ +#define D3L_TB0_START_ADDR 0x9800 +#define D3L_TB1_START_ADDR 0x9c00 +#define D3L_TB2_START_ADDR 0xa000 +#define D3L_TB3_START_ADDR 0xa400 + +/* IPIPE Register Offsets from the base address */ +#define IPIPE_SRC_EN 0x0000 +#define IPIPE_SRC_MODE 0x0004 +#define IPIPE_SRC_FMT 0x0008 +#define IPIPE_SRC_COL 0x000c +#define IPIPE_SRC_VPS 0x0010 +#define IPIPE_SRC_VSZ 0x0014 +#define IPIPE_SRC_HPS 0x0018 +#define IPIPE_SRC_HSZ 0x001c + +#define IPIPE_SEL_SBU 0x0020 + +#define IPIPE_DMA_STA 0x0024 +#define IPIPE_GCK_MMR 0x0028 +#define IPIPE_GCK_PIX 0x002c +#define IPIPE_RESERVED0 0x0030 + +/* Defect Correction */ +#define DPC_LUT_EN 0x0034 +#define DPC_LUT_SEL 0x0038 +#define DPC_LUT_ADR 0x003c +#define DPC_LUT_SIZ 0x0040 +#define DPC_OTF_EN 0x0044 +#define DPC_OTF_TYP 0x0048 +#define DPC_OTF_2D_THR_R 0x004c +#define DPC_OTF_2D_THR_GR 0x0050 +#define DPC_OTF_2D_THR_GB 0x0054 +#define DPC_OTF_2D_THR_B 0x0058 +#define DPC_OTF_2C_THR_R 0x005c +#define DPC_OTF_2C_THR_GR 0x0060 +#define DPC_OTF_2C_THR_GB 0x0064 +#define DPC_OTF_2C_THR_B 0x0068 +#define DPC_OTF_3_SHF 0x006c +#define DPC_OTF_3D_THR 0x0070 +#define DPC_OTF_3D_SLP 0x0074 +#define DPC_OTF_3D_MIN 0x0078 +#define DPC_OTF_3D_MAX 0x007c +#define DPC_OTF_3C_THR 0x0080 +#define DPC_OTF_3C_SLP 0x0084 +#define DPC_OTF_3C_MIN 0x0088 +#define DPC_OTF_3C_MAX 0x008c + +/* Lense Shading Correction */ +#define LSC_VOFT 0x90 +#define LSC_VA2 0x94 +#define LSC_VA1 0x98 +#define LSC_VS 0x9c +#define LSC_HOFT 0xa0 +#define LSC_HA2 0xa4 +#define LSC_HA1 0xa8 +#define LSC_HS 0xac +#define LSC_GAIN_R 0xb0 +#define LSC_GAIN_GR 0xb4 +#define LSC_GAIN_GB 0xb8 +#define LSC_GAIN_B 0xbc +#define LSC_OFT_R 0xc0 +#define LSC_OFT_GR 0xc4 +#define LSC_OFT_GB 0xc8 +#define LSC_OFT_B 0xcc +#define LSC_SHF 0xd0 +#define LSC_MAX 0xd4 + +/* Noise Filter 1. Ofsets from start address given */ +#define D2F_1ST 0xd8 +#define D2F_EN 0x0 +#define D2F_TYP 0x4 +#define D2F_THR 0x8 +#define D2F_STR 0x28 +#define D2F_SPR 0x48 +#define D2F_EDG_MIN 0x68 +#define D2F_EDG_MAX 0x6c + +/* Noise Filter 2 */ +#define D2F_2ND 0x148 + +/* GIC */ +#define GIC_EN 0x1b8 +#define GIC_TYP 0x1bc +#define GIC_GAN 0x1c0 +#define GIC_NFGAN 0x1c4 +#define GIC_THR 0x1c8 +#define GIC_SLP 0x1cc + +/* White Balance */ +#define WB2_OFT_R 0x1d0 +#define WB2_OFT_GR 0x1d4 +#define WB2_OFT_GB 0x1d8 +#define WB2_OFT_B 0x1dc +#define WB2_WGN_R 0x1e0 +#define WB2_WGN_GR 0x1e4 +#define WB2_WGN_GB 0x1e8 +#define WB2_WGN_B 0x1ec + +/* CFA interpolation */ +#define CFA_MODE 0x1f0 +#define CFA_2DIR_HPF_THR 0x1f4 +#define CFA_2DIR_HPF_SLP 0x1f8 +#define CFA_2DIR_MIX_THR 0x1fc +#define CFA_2DIR_MIX_SLP 0x200 +#define CFA_2DIR_DIR_THR 0x204 +#define CFA_2DIR_DIR_SLP 0x208 +#define CFA_2DIR_NDWT 0x20c +#define CFA_MONO_HUE_FRA 0x210 +#define CFA_MONO_EDG_THR 0x214 +#define CFA_MONO_THR_MIN 0x218 +#define CFA_MONO_THR_SLP 0x21c +#define CFA_MONO_SLP_MIN 0x220 +#define CFA_MONO_SLP_SLP 0x224 +#define CFA_MONO_LPWT 0x228 + +/* RGB to RGB conversiona - 1st */ +#define RGB1_MUL_BASE 0x22c +/* Offsets from base */ +#define RGB_MUL_RR 0x0 +#define RGB_MUL_GR 0x4 +#define RGB_MUL_BR 0x8 +#define RGB_MUL_RG 0xc +#define RGB_MUL_GG 0x10 +#define RGB_MUL_BG 0x14 +#define RGB_MUL_RB 0x18 +#define RGB_MUL_GB 0x1c +#define RGB_MUL_BB 0x20 +#define RGB_OFT_OR 0x24 +#define RGB_OFT_OG 0x28 +#define RGB_OFT_OB 0x2c + +/* Gamma */ +#define GMM_CFG 0x25c + +/* RGB to RGB conversiona - 2nd */ +#define RGB2_MUL_BASE 0x260 + +/* 3D LUT */ +#define D3LUT_EN 0x290 + +/* RGB to YUV(YCbCr) conversion */ +#define YUV_ADJ 0x294 +#define YUV_MUL_RY 0x298 +#define YUV_MUL_GY 0x29c +#define YUV_MUL_BY 0x2a0 +#define YUV_MUL_RCB 0x2a4 +#define YUV_MUL_GCB 0x2a8 +#define YUV_MUL_BCB 0x2ac +#define YUV_MUL_RCR 0x2b0 +#define YUV_MUL_GCR 0x2b4 +#define YUV_MUL_BCR 0x2b8 +#define YUV_OFT_Y 0x2bc +#define YUV_OFT_CB 0x2c0 +#define YUV_OFT_CR 0x2c4 +#define YUV_PHS 0x2c8 + +/* Global Brightness and Contrast */ +#define GBCE_EN 0x2cc +#define GBCE_TYP 0x2d0 + +/* Edge Enhancer */ +#define YEE_EN 0x2d4 +#define YEE_TYP 0x2d8 +#define YEE_SHF 0x2dc +#define YEE_MUL_00 0x2e0 +#define YEE_MUL_01 0x2e4 +#define YEE_MUL_02 0x2e8 +#define YEE_MUL_10 0x2ec +#define YEE_MUL_11 0x2f0 +#define YEE_MUL_12 0x2f4 +#define YEE_MUL_20 0x2f8 +#define YEE_MUL_21 0x2fc +#define YEE_MUL_22 0x300 +#define YEE_THR 0x304 +#define YEE_E_GAN 0x308 +#define YEE_E_THR1 0x30c +#define YEE_E_THR2 0x310 +#define YEE_G_GAN 0x314 +#define YEE_G_OFT 0x318 + +/* Chroma Artifact Reduction */ +#define CAR_EN 0x31c +#define CAR_TYP 0x320 +#define CAR_SW 0x324 +#define CAR_HPF_TYP 0x328 +#define CAR_HPF_SHF 0x32c +#define CAR_HPF_THR 0x330 +#define CAR_GN1_GAN 0x334 +#define CAR_GN1_SHF 0x338 +#define CAR_GN1_MIN 0x33c +#define CAR_GN2_GAN 0x340 +#define CAR_GN2_SHF 0x344 +#define CAR_GN2_MIN 0x348 + +/* Chroma Gain Suppression */ +#define CGS_EN 0x34c +#define CGS_GN1_L_THR 0x350 +#define CGS_GN1_L_GAN 0x354 +#define CGS_GN1_L_SHF 0x358 +#define CGS_GN1_L_MIN 0x35c +#define CGS_GN1_H_THR 0x360 +#define CGS_GN1_H_GAN 0x364 +#define CGS_GN1_H_SHF 0x368 +#define CGS_GN1_H_MIN 0x36c +#define CGS_GN2_L_THR 0x370 +#define CGS_GN2_L_GAN 0x374 +#define CGS_GN2_L_SHF 0x378 +#define CGS_GN2_L_MIN 0x37c + +/* Resizer */ +#define RSZ_SRC_EN 0x0 +#define RSZ_SRC_MODE 0x4 +#define RSZ_SRC_FMT0 0x8 +#define RSZ_SRC_FMT1 0xc +#define RSZ_SRC_VPS 0x10 +#define RSZ_SRC_VSZ 0x14 +#define RSZ_SRC_HPS 0x18 +#define RSZ_SRC_HSZ 0x1c +#define RSZ_DMA_RZA 0x20 +#define RSZ_DMA_RZB 0x24 +#define RSZ_DMA_STA 0x28 +#define RSZ_GCK_MMR 0x2c +#define RSZ_RESERVED0 0x30 +#define RSZ_GCK_SDR 0x34 +#define RSZ_IRQ_RZA 0x38 +#define RSZ_IRQ_RZB 0x3c +#define RSZ_YUV_Y_MIN 0x40 +#define RSZ_YUV_Y_MAX 0x44 +#define RSZ_YUV_C_MIN 0x48 +#define RSZ_YUV_C_MAX 0x4c +#define RSZ_YUV_PHS 0x50 +#define RSZ_SEQ 0x54 + +/* Resizer Rescale Parameters */ +#define RSZ_EN_A 0x58 +#define RSZ_EN_B 0xe8 +/* offset of the registers to be added with base register of + either RSZ0 or RSZ1 +*/ +#define RSZ_MODE 0x4 +#define RSZ_420 0x8 +#define RSZ_I_VPS 0xc +#define RSZ_I_HPS 0x10 +#define RSZ_O_VSZ 0x14 +#define RSZ_O_HSZ 0x18 +#define RSZ_V_PHS_Y 0x1c +#define RSZ_V_PHS_C 0x20 +#define RSZ_V_DIF 0x24 +#define RSZ_V_TYP 0x28 +#define RSZ_V_LPF 0x2c +#define RSZ_H_PHS 0x30 +#define RSZ_H_PHS_ADJ 0x34 +#define RSZ_H_DIF 0x38 +#define RSZ_H_TYP 0x3c +#define RSZ_H_LPF 0x40 +#define RSZ_DWN_EN 0x44 +#define RSZ_DWN_AV 0x48 + +/* Resizer RGB Conversion Parameters */ +#define RSZ_RGB_EN 0x4c +#define RSZ_RGB_TYP 0x50 +#define RSZ_RGB_BLD 0x54 + +/* Resizer External Memory Parameters */ +#define RSZ_SDR_Y_BAD_H 0x58 +#define RSZ_SDR_Y_BAD_L 0x5c +#define RSZ_SDR_Y_SAD_H 0x60 +#define RSZ_SDR_Y_SAD_L 0x64 +#define RSZ_SDR_Y_OFT 0x68 +#define RSZ_SDR_Y_PTR_S 0x6c +#define RSZ_SDR_Y_PTR_E 0x70 +#define RSZ_SDR_C_BAD_H 0x74 +#define RSZ_SDR_C_BAD_L 0x78 +#define RSZ_SDR_C_SAD_H 0x7c +#define RSZ_SDR_C_SAD_L 0x80 +#define RSZ_SDR_C_OFT 0x84 +#define RSZ_SDR_C_PTR_S 0x88 +#define RSZ_SDR_C_PTR_E 0x8c + +/* Macro for resizer */ +#define RSZ_YUV_Y_MIN 0x40 +#define RSZ_YUV_Y_MAX 0x44 +#define RSZ_YUV_C_MIN 0x48 +#define RSZ_YUV_C_MAX 0x4c + +#define IPIPE_GCK_MMR_DEFAULT 1 +#define IPIPE_GCK_PIX_DEFAULT 0xe +#define RSZ_GCK_MMR_DEFAULT 1 +#define RSZ_GCK_SDR_DEFAULT 1 + +/* LUTDPC */ +#define LUTDPC_TBL_256_EN 0 +#define LUTDPC_INF_TBL_EN 1 +#define LUT_DPC_START_ADDR 0 +#define LUT_DPC_H_POS_MASK 0x1fff +#define LUT_DPC_V_POS_MASK 0x1fff +#define LUT_DPC_V_POS_SHIFT 13 +#define LUT_DPC_CORR_METH_SHIFT 26 +#define LUT_DPC_MAX_SIZE 256 +#define LUT_DPC_SIZE_MASK 0x3ff + +/* OTFDPC */ +#define OTFDPC_DPC2_THR_MASK 0xfff +#define OTF_DET_METHOD_SHIFT 1 +#define OTF_DPC3_0_SHF_MASK 3 +#define OTF_DPC3_0_THR_SHIFT 6 +#define OTF_DPC3_0_THR_MASK 0x3f +#define OTF_DPC3_0_SLP_MASK 0x3f +#define OTF_DPC3_0_DET_MASK 0xfff +#define OTF_DPC3_0_CORR_MASK 0xfff + +/* NF (D2F) */ +#define D2F_SPR_VAL_MASK 0x1f +#define D2F_SPR_VAL_SHIFT 0 +#define D2F_SHFT_VAL_MASK 3 +#define D2F_SHFT_VAL_SHIFT 5 +#define D2F_SAMPLE_METH_SHIFT 7 +#define D2F_APPLY_LSC_GAIN_SHIFT 8 +#define D2F_USE_SPR_REG_VAL 0 +#define D2F_STR_VAL_MASK 0x1f +#define D2F_THR_VAL_MASK 0x3ff +#define D2F_EDGE_DET_THR_MASK 0x7ff + +/* Green Imbalance Correction */ +#define GIC_TYP_SHIFT 0 +#define GIC_THR_SEL_SHIFT 1 +#define GIC_APPLY_LSC_GAIN_SHIFT 2 +#define GIC_GAIN_MASK 0xff +#define GIC_THR_MASK 0xfff +#define GIC_SLOPE_MASK 0xfff +#define GIC_NFGAN_INT_MASK 7 +#define GIC_NFGAN_DECI_MASK 0x1f + +/* WB */ +#define WB_OFFSET_MASK 0xfff +#define WB_GAIN_INT_MASK 0xf +#define WB_GAIN_DECI_MASK 0x1ff + +/* CFA */ +#define CFA_HPF_THR_2DIR_MASK 0x1fff +#define CFA_HPF_SLOPE_2DIR_MASK 0x3ff +#define CFA_HPF_MIX_THR_2DIR_MASK 0x1fff +#define CFA_HPF_MIX_SLP_2DIR_MASK 0x3ff +#define CFA_DIR_THR_2DIR_MASK 0x3ff +#define CFA_DIR_SLP_2DIR_MASK 0x7f +#define CFA_ND_WT_2DIR_MASK 0x3f +#define CFA_DAA_HUE_FRA_MASK 0x3f +#define CFA_DAA_EDG_THR_MASK 0xff +#define CFA_DAA_THR_MIN_MASK 0x3ff +#define CFA_DAA_THR_SLP_MASK 0x3ff +#define CFA_DAA_SLP_MIN_MASK 0x3ff +#define CFA_DAA_SLP_SLP_MASK 0x3ff +#define CFA_DAA_LP_WT_MASK 0x3f + +/* RGB2RGB */ +#define RGB2RGB_1_OFST_MASK 0x1fff +#define RGB2RGB_1_GAIN_INT_MASK 0xf +#define RGB2RGB_GAIN_DECI_MASK 0xff +#define RGB2RGB_2_OFST_MASK 0x7ff +#define RGB2RGB_2_GAIN_INT_MASK 0x7 + +/* Gamma */ +#define GAMMA_BYPR_SHIFT 0 +#define GAMMA_BYPG_SHIFT 1 +#define GAMMA_BYPB_SHIFT 2 +#define GAMMA_TBL_SEL_SHIFT 4 +#define GAMMA_TBL_SIZE_SHIFT 5 +#define GAMMA_MASK 0x3ff +#define GAMMA_SHIFT 10 + +/* 3D LUT */ +#define D3_LUT_ENTRY_MASK 0x3ff +#define D3_LUT_ENTRY_R_SHIFT 20 +#define D3_LUT_ENTRY_G_SHIFT 10 +#define D3_LUT_ENTRY_B_SHIFT 0 + +/* Lumina adj */ +#define LUM_ADJ_CONTR_SHIFT 0 +#define LUM_ADJ_BRIGHT_SHIFT 8 + +/* RGB2YCbCr */ +#define RGB2YCBCR_OFST_MASK 0x7ff +#define RGB2YCBCR_COEF_INT_MASK 0xf +#define RGB2YCBCR_COEF_DECI_MASK 0xff + +/* GBCE */ +#define GBCE_Y_VAL_MASK 0xff +#define GBCE_GAIN_VAL_MASK 0x3ff +#define GBCE_ENTRY_SHIFT 10 + +/* Edge Enhancements */ +#define YEE_HALO_RED_EN_SHIFT 1 +#define YEE_HPF_SHIFT_MASK 0xf +#define YEE_COEF_MASK 0x3ff +#define YEE_THR_MASK 0x3f +#define YEE_ES_GAIN_MASK 0xfff +#define YEE_ES_THR1_MASK 0xfff +#define YEE_ENTRY_SHIFT 9 +#define YEE_ENTRY_MASK 0x1ff + +/* CAR */ +#define CAR_MF_THR 0xff +#define CAR_SW1_SHIFT 8 +#define CAR_GAIN1_SHFT_MASK 7 +#define CAR_GAIN_MIN_MASK 0x1ff +#define CAR_GAIN2_SHFT_MASK 0xf +#define CAR_HPF_SHIFT_MASK 3 + +/* CGS */ +#define CAR_SHIFT_MASK 3 + +/* Resizer */ +#define RSZ_BYPASS_SHIFT 1 +#define RSZ_SRC_IMG_FMT_SHIFT 1 +#define RSZ_SRC_Y_C_SEL_SHIFT 2 +#define IPIPE_RSZ_VPS_MASK 0xffff +#define IPIPE_RSZ_HPS_MASK 0xffff +#define IPIPE_RSZ_VSZ_MASK 0x1fff +#define IPIPE_RSZ_HSZ_MASK 0x1fff +#define RSZ_HPS_MASK 0x1fff +#define RSZ_VPS_MASK 0x1fff +#define RSZ_O_HSZ_MASK 0x1fff +#define RSZ_O_VSZ_MASK 0x1fff +#define RSZ_V_PHS_MASK 0x3fff +#define RSZ_V_DIF_MASK 0x3fff + +#define RSZA_H_FLIP_SHIFT 0 +#define RSZA_V_FLIP_SHIFT 1 +#define RSZB_H_FLIP_SHIFT 2 +#define RSZB_V_FLIP_SHIFT 3 +#define RSZ_A 0 +#define RSZ_B 1 +#define RSZ_CEN_SHIFT 1 +#define RSZ_YEN_SHIFT 0 +#define RSZ_TYP_Y_SHIFT 0 +#define RSZ_TYP_C_SHIFT 1 +#define RSZ_LPF_INT_MASK 0x3f +#define RSZ_LPF_INT_MASK 0x3f +#define RSZ_LPF_INT_C_SHIFT 6 +#define RSZ_H_PHS_MASK 0x3fff +#define RSZ_H_DIF_MASK 0x3fff +#define RSZ_DIFF_DOWN_THR 256 +#define RSZ_DWN_SCALE_AV_SZ_V_SHIFT 3 +#define RSZ_DWN_SCALE_AV_SZ_MASK 7 +#define RSZ_RGB_MSK1_SHIFT 2 +#define RSZ_RGB_MSK0_SHIFT 1 +#define RSZ_RGB_TYP_SHIFT 0 +#define RSZ_RGB_ALPHA_MASK 0xff + +static inline u32 regr_ip(void *__iomem addr, u32 offset) +{ + return readl(addr + offset); +} + +static inline void regw_ip(void *__iomem addr, u32 val, u32 offset) +{ + writel(val, addr + offset); +} + +static inline u32 w_ip_table(void *__iomem addr, u32 val, u32 offset) +{ + writel(val, addr + offset); + + return val; +} + +static inline u32 regr_rsz(void *__iomem addr, u32 offset) +{ + return readl(addr + offset); +} + +static inline u32 regw_rsz(void *__iomem addr, u32 val, u32 offset) +{ + writel(val, addr + offset); + + return val; +} + +int config_ipipe_hw(struct vpfe_ipipe_device *ipipe); +int resizer_set_outaddr(void *__iomem rsz_base, struct resizer_params *params, + int resize_no, unsigned int address); +int rsz_enable(void *__iomem rsz_base, int rsz_id, int enable); +void rsz_src_enable(void *__iomem rsz_base, int enable); +void rsz_set_in_pix_format(unsigned char y_c); +int config_rsz_hw(struct vpfe_resizer_device *resizer, + struct resizer_params *config); +void ipipe_set_d2f_regs(void *__iomem base_addr, unsigned int id, + struct vpfe_ipipe_nf *noise_filter); +void ipipe_set_rgb2rgb_regs(void *__iomem base_addr, unsigned int id, + struct vpfe_ipipe_rgb2rgb *rgb); +void ipipe_set_yuv422_conv_regs(void *__iomem base_addr, + struct vpfe_ipipe_yuv422_conv *conv); +void ipipe_set_lum_adj_regs(void *__iomem base_addr, + struct ipipe_lum_adj *lum_adj); +void ipipe_set_rgb2ycbcr_regs(void *__iomem base_addr, + struct vpfe_ipipe_rgb2yuv *yuv); +void ipipe_set_lutdpc_regs(void *__iomem base_addr, + void *__iomem isp5_base_addr, struct vpfe_ipipe_lutdpc *lutdpc); +void ipipe_set_otfdpc_regs(void *__iomem base_addr, + struct vpfe_ipipe_otfdpc *otfdpc); +void ipipe_set_3d_lut_regs(void *__iomem base_addr, + void *__iomem isp5_base_addr, struct vpfe_ipipe_3d_lut *lut_3d); +void ipipe_set_gamma_regs(void *__iomem base_addr, + void *__iomem isp5_base_addr, struct vpfe_ipipe_gamma *gamma); +void ipipe_set_ee_regs(void *__iomem base_addr, + void *__iomem isp5_base_addr, struct vpfe_ipipe_yee *ee); +void ipipe_set_gbce_regs(void *__iomem base_addr, + void *__iomem isp5_base_addr, struct vpfe_ipipe_gbce *gbce); +void ipipe_set_gic_regs(void *__iomem base_addr, struct vpfe_ipipe_gic *gic); +void ipipe_set_cfa_regs(void *__iomem base_addr, struct vpfe_ipipe_cfa *cfa); +void ipipe_set_car_regs(void *__iomem base_addr, struct vpfe_ipipe_car *car); +void ipipe_set_cgs_regs(void *__iomem base_addr, struct vpfe_ipipe_cgs *cgs); +void ipipe_set_wb_regs(void *__iomem base_addr, struct vpfe_ipipe_wb *wb); + +#endif /* _DAVINCI_VPFE_DM365_IPIPE_HW_H */ diff --git a/drivers/staging/media/davinci_vpfe/dm365_ipipeif.c b/drivers/staging/media/davinci_vpfe/dm365_ipipeif.c new file mode 100644 index 00000000000..59540cd4bb9 --- /dev/null +++ b/drivers/staging/media/davinci_vpfe/dm365_ipipeif.c @@ -0,0 +1,1070 @@ +/* + * Copyright (C) 2012 Texas Instruments Inc + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Contributors: + * Manjunath Hadli <manjunath.hadli@ti.com> + * Prabhakar Lad <prabhakar.lad@ti.com> + */ + +#include "dm365_ipipeif.h" +#include "vpfe_mc_capture.h" + +static const unsigned int ipipeif_input_fmts[] = { + V4L2_MBUS_FMT_UYVY8_2X8, + V4L2_MBUS_FMT_SGRBG12_1X12, + V4L2_MBUS_FMT_Y8_1X8, + V4L2_MBUS_FMT_UV8_1X8, + V4L2_MBUS_FMT_YDYUYDYV8_1X16, + V4L2_MBUS_FMT_SBGGR8_1X8, +}; + +static const unsigned int ipipeif_output_fmts[] = { + V4L2_MBUS_FMT_UYVY8_2X8, + V4L2_MBUS_FMT_SGRBG12_1X12, + V4L2_MBUS_FMT_Y8_1X8, + V4L2_MBUS_FMT_UV8_1X8, + V4L2_MBUS_FMT_YDYUYDYV8_1X16, + V4L2_MBUS_FMT_SBGGR8_1X8, + V4L2_MBUS_FMT_SGRBG10_DPCM8_1X8, + V4L2_MBUS_FMT_SGRBG10_ALAW8_1X8, +}; + +static int +ipipeif_get_pack_mode(enum v4l2_mbus_pixelcode in_pix_fmt) +{ + switch (in_pix_fmt) { + case V4L2_MBUS_FMT_SBGGR8_1X8: + case V4L2_MBUS_FMT_Y8_1X8: + case V4L2_MBUS_FMT_SGRBG10_DPCM8_1X8: + case V4L2_MBUS_FMT_UV8_1X8: + return IPIPEIF_5_1_PACK_8_BIT; + + case V4L2_MBUS_FMT_SGRBG10_ALAW8_1X8: + return IPIPEIF_5_1_PACK_8_BIT_A_LAW; + + case V4L2_MBUS_FMT_SGRBG12_1X12: + return IPIPEIF_5_1_PACK_16_BIT; + + case V4L2_MBUS_FMT_SBGGR12_1X12: + return IPIPEIF_5_1_PACK_12_BIT; + + default: + return IPIPEIF_5_1_PACK_16_BIT; + } +} + +static inline u32 ipipeif_read(void *addr, u32 offset) +{ + return readl(addr + offset); +} + +static inline void ipipeif_write(u32 val, void *addr, u32 offset) +{ + writel(val, addr + offset); +} + +static void ipipeif_config_dpc(void *addr, struct ipipeif_dpc *dpc) +{ + u32 val = 0; + + if (dpc->en) { + val = (dpc->en & 1) << IPIPEIF_DPC2_EN_SHIFT; + val |= dpc->thr & IPIPEIF_DPC2_THR_MASK; + } + ipipeif_write(val, addr, IPIPEIF_DPC2); +} + +#define IPIPEIF_MODE_CONTINUOUS 0 +#define IPIPEIF_MODE_ONE_SHOT 1 + +static int get_oneshot_mode(enum ipipeif_input_entity input) +{ + if (input == IPIPEIF_INPUT_MEMORY) + return IPIPEIF_MODE_ONE_SHOT; + else if (input == IPIPEIF_INPUT_ISIF) + return IPIPEIF_MODE_CONTINUOUS; + + return -EINVAL; +} + +static int +ipipeif_get_cfg_src1(struct vpfe_ipipeif_device *ipipeif) +{ + struct v4l2_mbus_framefmt *informat; + + informat = &ipipeif->formats[IPIPEIF_PAD_SINK]; + if (ipipeif->input == IPIPEIF_INPUT_MEMORY && + (informat->code == V4L2_MBUS_FMT_Y8_1X8 || + informat->code == V4L2_MBUS_FMT_UV8_1X8)) + return IPIPEIF_CCDC; + + return IPIPEIF_SRC1_PARALLEL_PORT; +} + +static int +ipipeif_get_data_shift(struct vpfe_ipipeif_device *ipipeif) +{ + struct v4l2_mbus_framefmt *informat; + + informat = &ipipeif->formats[IPIPEIF_PAD_SINK]; + + switch (informat->code) { + case V4L2_MBUS_FMT_SGRBG12_1X12: + return IPIPEIF_5_1_BITS11_0; + + case V4L2_MBUS_FMT_Y8_1X8: + case V4L2_MBUS_FMT_UV8_1X8: + return IPIPEIF_5_1_BITS11_0; + + default: + return IPIPEIF_5_1_BITS7_0; + } +} + +static enum ipipeif_input_source +ipipeif_get_source(struct vpfe_ipipeif_device *ipipeif) +{ + struct v4l2_mbus_framefmt *informat; + + informat = &ipipeif->formats[IPIPEIF_PAD_SINK]; + if (ipipeif->input == IPIPEIF_INPUT_ISIF) + return IPIPEIF_CCDC; + + if (informat->code == V4L2_MBUS_FMT_UYVY8_2X8) + return IPIPEIF_SDRAM_YUV; + + return IPIPEIF_SDRAM_RAW; +} + +void vpfe_ipipeif_ss_buffer_isr(struct vpfe_ipipeif_device *ipipeif) +{ + struct vpfe_video_device *video_in = &ipipeif->video_in; + + if (ipipeif->input != IPIPEIF_INPUT_MEMORY) + return; + + spin_lock(&video_in->dma_queue_lock); + vpfe_video_process_buffer_complete(video_in); + video_in->state = VPFE_VIDEO_BUFFER_NOT_QUEUED; + vpfe_video_schedule_next_buffer(video_in); + spin_unlock(&video_in->dma_queue_lock); +} + +int vpfe_ipipeif_decimation_enabled(struct vpfe_device *vpfe_dev) +{ + struct vpfe_ipipeif_device *ipipeif = &vpfe_dev->vpfe_ipipeif; + + return ipipeif->config.decimation; +} + +int vpfe_ipipeif_get_rsz(struct vpfe_device *vpfe_dev) +{ + struct vpfe_ipipeif_device *ipipeif = &vpfe_dev->vpfe_ipipeif; + + return ipipeif->config.rsz; +} + +#define RD_DATA_15_2 0x7 + +/* + * ipipeif_hw_setup() - This function sets up IPIPEIF + * @sd: pointer to v4l2 subdev structure + * return -EINVAL or zero on success + */ +static int ipipeif_hw_setup(struct v4l2_subdev *sd) +{ + struct vpfe_ipipeif_device *ipipeif = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt *informat, *outformat; + struct ipipeif_params params = ipipeif->config; + enum ipipeif_input_source ipipeif_source; + enum v4l2_mbus_pixelcode isif_port_if; + void *ipipeif_base_addr; + unsigned int val; + int data_shift; + int pack_mode; + int source1; + + ipipeif_base_addr = ipipeif->ipipeif_base_addr; + + /* Enable clock to IPIPEIF and IPIPE */ + vpss_enable_clock(VPSS_IPIPEIF_CLOCK, 1); + + informat = &ipipeif->formats[IPIPEIF_PAD_SINK]; + outformat = &ipipeif->formats[IPIPEIF_PAD_SOURCE]; + + /* Combine all the fields to make CFG1 register of IPIPEIF */ + val = get_oneshot_mode(ipipeif->input); + if (val < 0) { + pr_err("ipipeif: links setup required"); + return -EINVAL; + } + val = val << ONESHOT_SHIFT; + + ipipeif_source = ipipeif_get_source(ipipeif); + val |= ipipeif_source << INPSRC_SHIFT; + + val |= params.clock_select << CLKSEL_SHIFT; + val |= params.avg_filter << AVGFILT_SHIFT; + val |= params.decimation << DECIM_SHIFT; + + pack_mode = ipipeif_get_pack_mode(informat->code); + val |= pack_mode << PACK8IN_SHIFT; + + source1 = ipipeif_get_cfg_src1(ipipeif); + val |= source1 << INPSRC1_SHIFT; + + data_shift = ipipeif_get_data_shift(ipipeif); + if (ipipeif_source != IPIPEIF_SDRAM_YUV) + val |= data_shift << DATASFT_SHIFT; + else + val &= ~(RD_DATA_15_2 << DATASFT_SHIFT); + + ipipeif_write(val, ipipeif_base_addr, IPIPEIF_CFG1); + + switch (ipipeif_source) { + case IPIPEIF_CCDC: + ipipeif_write(ipipeif->gain, ipipeif_base_addr, IPIPEIF_GAIN); + break; + + case IPIPEIF_SDRAM_RAW: + case IPIPEIF_CCDC_DARKFM: + ipipeif_write(ipipeif->gain, ipipeif_base_addr, IPIPEIF_GAIN); + /* fall through */ + case IPIPEIF_SDRAM_YUV: + val |= data_shift << DATASFT_SHIFT; + ipipeif_write(params.ppln, ipipeif_base_addr, IPIPEIF_PPLN); + ipipeif_write(params.lpfr, ipipeif_base_addr, IPIPEIF_LPFR); + ipipeif_write(informat->width, ipipeif_base_addr, IPIPEIF_HNUM); + ipipeif_write(informat->height, + ipipeif_base_addr, IPIPEIF_VNUM); + break; + + default: + return -EINVAL; + } + + /*check if decimation is enable or not */ + if (params.decimation) + ipipeif_write(params.rsz, ipipeif_base_addr, IPIPEIF_RSZ); + + /* Setup sync alignment and initial rsz position */ + val = params.if_5_1.align_sync & 1; + val <<= IPIPEIF_INIRSZ_ALNSYNC_SHIFT; + val |= params.if_5_1.rsz_start & IPIPEIF_INIRSZ_MASK; + ipipeif_write(val, ipipeif_base_addr, IPIPEIF_INIRSZ); + isif_port_if = informat->code; + + if (isif_port_if == V4L2_MBUS_FMT_Y8_1X8) + isif_port_if = V4L2_MBUS_FMT_YUYV8_1X16; + else if (isif_port_if == V4L2_MBUS_FMT_UV8_1X8) + isif_port_if = V4L2_MBUS_FMT_SGRBG12_1X12; + + /* Enable DPCM decompression */ + switch (ipipeif_source) { + case IPIPEIF_SDRAM_RAW: + val = 0; + if (outformat->code == V4L2_MBUS_FMT_SGRBG10_DPCM8_1X8) { + val = 1; + val |= (IPIPEIF_DPCM_8BIT_10BIT & 1) << + IPIPEIF_DPCM_BITS_SHIFT; + val |= (ipipeif->dpcm_predictor & 1) << + IPIPEIF_DPCM_PRED_SHIFT; + } + ipipeif_write(val, ipipeif_base_addr, IPIPEIF_DPCM); + + /* set DPC */ + ipipeif_config_dpc(ipipeif_base_addr, ¶ms.if_5_1.dpc); + + ipipeif_write(params.if_5_1.clip, + ipipeif_base_addr, IPIPEIF_OCLIP); + + /* fall through for SDRAM YUV mode */ + /* configure CFG2 */ + val = ipipeif_read(ipipeif_base_addr, IPIPEIF_CFG2); + switch (isif_port_if) { + case V4L2_MBUS_FMT_YUYV8_1X16: + case V4L2_MBUS_FMT_UYVY8_2X8: + case V4L2_MBUS_FMT_Y8_1X8: + RESETBIT(val, IPIPEIF_CFG2_YUV8_SHIFT); + SETBIT(val, IPIPEIF_CFG2_YUV16_SHIFT); + ipipeif_write(val, ipipeif_base_addr, IPIPEIF_CFG2); + break; + + default: + RESETBIT(val, IPIPEIF_CFG2_YUV8_SHIFT); + RESETBIT(val, IPIPEIF_CFG2_YUV16_SHIFT); + ipipeif_write(val, ipipeif_base_addr, IPIPEIF_CFG2); + break; + } + + case IPIPEIF_SDRAM_YUV: + /* Set clock divider */ + if (params.clock_select == IPIPEIF_SDRAM_CLK) { + val = ipipeif_read(ipipeif_base_addr, IPIPEIF_CLKDIV); + val |= (params.if_5_1.clk_div.m - 1) << + IPIPEIF_CLKDIV_M_SHIFT; + val |= (params.if_5_1.clk_div.n - 1); + ipipeif_write(val, ipipeif_base_addr, IPIPEIF_CLKDIV); + } + break; + + case IPIPEIF_CCDC: + case IPIPEIF_CCDC_DARKFM: + /* set DPC */ + ipipeif_config_dpc(ipipeif_base_addr, ¶ms.if_5_1.dpc); + + /* Set DF gain & threshold control */ + val = 0; + if (params.if_5_1.df_gain_en) { + val = params.if_5_1.df_gain_thr & + IPIPEIF_DF_GAIN_THR_MASK; + ipipeif_write(val, ipipeif_base_addr, IPIPEIF_DFSGTH); + val = (params.if_5_1.df_gain_en & 1) << + IPIPEIF_DF_GAIN_EN_SHIFT; + val |= params.if_5_1.df_gain & + IPIPEIF_DF_GAIN_MASK; + } + ipipeif_write(val, ipipeif_base_addr, IPIPEIF_DFSGVL); + /* configure CFG2 */ + val = VPFE_PINPOL_POSITIVE << IPIPEIF_CFG2_HDPOL_SHIFT; + val |= VPFE_PINPOL_POSITIVE << IPIPEIF_CFG2_VDPOL_SHIFT; + + switch (isif_port_if) { + case V4L2_MBUS_FMT_YUYV8_1X16: + case V4L2_MBUS_FMT_YUYV10_1X20: + RESETBIT(val, IPIPEIF_CFG2_YUV8_SHIFT); + SETBIT(val, IPIPEIF_CFG2_YUV16_SHIFT); + break; + + case V4L2_MBUS_FMT_YUYV8_2X8: + case V4L2_MBUS_FMT_UYVY8_2X8: + case V4L2_MBUS_FMT_Y8_1X8: + case V4L2_MBUS_FMT_YUYV10_2X10: + SETBIT(val, IPIPEIF_CFG2_YUV8_SHIFT); + SETBIT(val, IPIPEIF_CFG2_YUV16_SHIFT); + val |= IPIPEIF_CBCR_Y << IPIPEIF_CFG2_YUV8P_SHIFT; + break; + + default: + /* Bayer */ + ipipeif_write(params.if_5_1.clip, ipipeif_base_addr, + IPIPEIF_OCLIP); + } + ipipeif_write(val, ipipeif_base_addr, IPIPEIF_CFG2); + break; + + default: + return -EINVAL; + } + + return 0; +} + +static int +ipipeif_set_config(struct v4l2_subdev *sd, struct ipipeif_params *config) +{ + struct vpfe_ipipeif_device *ipipeif = v4l2_get_subdevdata(sd); + struct device *dev = ipipeif->subdev.v4l2_dev->dev; + + if (!config) { + dev_err(dev, "Invalid configuration pointer\n"); + return -EINVAL; + } + + ipipeif->config.clock_select = config->clock_select; + ipipeif->config.ppln = config->ppln; + ipipeif->config.lpfr = config->lpfr; + ipipeif->config.rsz = config->rsz; + ipipeif->config.decimation = config->decimation; + if (ipipeif->config.decimation && + (ipipeif->config.rsz < IPIPEIF_RSZ_MIN || + ipipeif->config.rsz > IPIPEIF_RSZ_MAX)) { + dev_err(dev, "rsz range is %d to %d\n", + IPIPEIF_RSZ_MIN, IPIPEIF_RSZ_MAX); + return -EINVAL; + } + + ipipeif->config.avg_filter = config->avg_filter; + + ipipeif->config.if_5_1.df_gain_thr = config->if_5_1.df_gain_thr; + ipipeif->config.if_5_1.df_gain = config->if_5_1.df_gain; + ipipeif->config.if_5_1.df_gain_en = config->if_5_1.df_gain_en; + + ipipeif->config.if_5_1.rsz_start = config->if_5_1.rsz_start; + ipipeif->config.if_5_1.align_sync = config->if_5_1.align_sync; + ipipeif->config.if_5_1.clip = config->if_5_1.clip; + + ipipeif->config.if_5_1.dpc.en = config->if_5_1.dpc.en; + ipipeif->config.if_5_1.dpc.thr = config->if_5_1.dpc.thr; + + ipipeif->config.if_5_1.clk_div.m = config->if_5_1.clk_div.m; + ipipeif->config.if_5_1.clk_div.n = config->if_5_1.clk_div.n; + + return 0; +} + +static int +ipipeif_get_config(struct v4l2_subdev *sd, void __user *arg) +{ + struct vpfe_ipipeif_device *ipipeif = v4l2_get_subdevdata(sd); + struct ipipeif_params *config = (struct ipipeif_params *)arg; + struct device *dev = ipipeif->subdev.v4l2_dev->dev; + + if (!arg) { + dev_err(dev, "Invalid configuration pointer\n"); + return -EINVAL; + } + + config->clock_select = ipipeif->config.clock_select; + config->ppln = ipipeif->config.ppln; + config->lpfr = ipipeif->config.lpfr; + config->rsz = ipipeif->config.rsz; + config->decimation = ipipeif->config.decimation; + config->avg_filter = ipipeif->config.avg_filter; + + config->if_5_1.df_gain_thr = ipipeif->config.if_5_1.df_gain_thr; + config->if_5_1.df_gain = ipipeif->config.if_5_1.df_gain; + config->if_5_1.df_gain_en = ipipeif->config.if_5_1.df_gain_en; + + config->if_5_1.rsz_start = ipipeif->config.if_5_1.rsz_start; + config->if_5_1.align_sync = ipipeif->config.if_5_1.align_sync; + config->if_5_1.clip = ipipeif->config.if_5_1.clip; + + config->if_5_1.dpc.en = ipipeif->config.if_5_1.dpc.en; + config->if_5_1.dpc.thr = ipipeif->config.if_5_1.dpc.thr; + + config->if_5_1.clk_div.m = ipipeif->config.if_5_1.clk_div.m; + config->if_5_1.clk_div.n = ipipeif->config.if_5_1.clk_div.n; + + return 0; +} + +/* + * ipipeif_ioctl() - Handle ipipeif module private ioctl's + * @sd: pointer to v4l2 subdev structure + * @cmd: configuration command + * @arg: configuration argument + */ +static long ipipeif_ioctl(struct v4l2_subdev *sd, + unsigned int cmd, void *arg) +{ + struct ipipeif_params *config = (struct ipipeif_params *)arg; + int ret = -ENOIOCTLCMD; + + switch (cmd) { + case VIDIOC_VPFE_IPIPEIF_S_CONFIG: + ret = ipipeif_set_config(sd, config); + break; + + case VIDIOC_VPFE_IPIPEIF_G_CONFIG: + ret = ipipeif_get_config(sd, arg); + break; + } + return ret; +} + +/* + * ipipeif_s_ctrl() - Handle set control subdev method + * @ctrl: pointer to v4l2 control structure + */ +static int ipipeif_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct vpfe_ipipeif_device *ipipeif = + container_of(ctrl->handler, struct vpfe_ipipeif_device, ctrls); + + switch (ctrl->id) { + case VPFE_CID_DPCM_PREDICTOR: + ipipeif->dpcm_predictor = ctrl->val; + break; + + case V4L2_CID_GAIN: + ipipeif->gain = ctrl->val; + break; + + default: + return -EINVAL; + } + + return 0; +} + +#define ENABLE_IPIPEIF 0x1 + +void vpfe_ipipeif_enable(struct vpfe_device *vpfe_dev) +{ + struct vpfe_ipipeif_device *ipipeif = &vpfe_dev->vpfe_ipipeif; + void *ipipeif_base_addr = ipipeif->ipipeif_base_addr; + unsigned char val; + + if (ipipeif->input != IPIPEIF_INPUT_MEMORY) + return; + + do { + val = ipipeif_read(ipipeif_base_addr, IPIPEIF_ENABLE); + } while (val & 0x1); + + ipipeif_write(ENABLE_IPIPEIF, ipipeif_base_addr, IPIPEIF_ENABLE); +} + +/* + * ipipeif_set_stream() - Enable/Disable streaming on ipipeif subdev + * @sd: pointer to v4l2 subdev structure + * @enable: 1 == Enable, 0 == Disable + */ +static int ipipeif_set_stream(struct v4l2_subdev *sd, int enable) +{ + struct vpfe_ipipeif_device *ipipeif = v4l2_get_subdevdata(sd); + struct vpfe_device *vpfe_dev = to_vpfe_device(ipipeif); + int ret = 0; + + if (!enable) + return ret; + + ret = ipipeif_hw_setup(sd); + if (!ret) + vpfe_ipipeif_enable(vpfe_dev); + + return ret; +} + +/* + * ipipeif_enum_mbus_code() - Handle pixel format enumeration + * @sd: pointer to v4l2 subdev structure + * @fh: V4L2 subdev file handle + * @code: pointer to v4l2_subdev_mbus_code_enum structure + * return -EINVAL or zero on success + */ +static int ipipeif_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_fh *fh, + struct v4l2_subdev_mbus_code_enum *code) +{ + switch (code->pad) { + case IPIPEIF_PAD_SINK: + if (code->index >= ARRAY_SIZE(ipipeif_input_fmts)) + return -EINVAL; + + code->code = ipipeif_input_fmts[code->index]; + break; + + case IPIPEIF_PAD_SOURCE: + if (code->index >= ARRAY_SIZE(ipipeif_output_fmts)) + return -EINVAL; + + code->code = ipipeif_output_fmts[code->index]; + break; + + default: + return -EINVAL; + } + + return 0; +} + +/* + * ipipeif_get_format() - Handle get format by pads subdev method + * @sd: pointer to v4l2 subdev structure + * @fh: V4L2 subdev file handle + * @fmt: pointer to v4l2 subdev format structure + */ +static int +ipipeif_get_format(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh, + struct v4l2_subdev_format *fmt) +{ + struct vpfe_ipipeif_device *ipipeif = v4l2_get_subdevdata(sd); + + if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE) + fmt->format = ipipeif->formats[fmt->pad]; + else + fmt->format = *(v4l2_subdev_get_try_format(fh, fmt->pad)); + + return 0; +} + +#define MIN_OUT_WIDTH 32 +#define MIN_OUT_HEIGHT 32 + +/* + * ipipeif_try_format() - Handle try format by pad subdev method + * @ipipeif: VPFE ipipeif device. + * @fh: V4L2 subdev file handle. + * @pad: pad num. + * @fmt: pointer to v4l2 format structure. + * @which : wanted subdev format + */ +static void +ipipeif_try_format(struct vpfe_ipipeif_device *ipipeif, + struct v4l2_subdev_fh *fh, unsigned int pad, + struct v4l2_mbus_framefmt *fmt, + enum v4l2_subdev_format_whence which) +{ + unsigned int max_out_height; + unsigned int max_out_width; + unsigned int i; + + max_out_width = IPIPE_MAX_OUTPUT_WIDTH_A; + max_out_height = IPIPE_MAX_OUTPUT_HEIGHT_A; + + if (pad == IPIPEIF_PAD_SINK) { + for (i = 0; i < ARRAY_SIZE(ipipeif_input_fmts); i++) + if (fmt->code == ipipeif_input_fmts[i]) + break; + + /* If not found, use SBGGR10 as default */ + if (i >= ARRAY_SIZE(ipipeif_input_fmts)) + fmt->code = V4L2_MBUS_FMT_SGRBG12_1X12; + } else if (pad == IPIPEIF_PAD_SOURCE) { + for (i = 0; i < ARRAY_SIZE(ipipeif_output_fmts); i++) + if (fmt->code == ipipeif_output_fmts[i]) + break; + + /* If not found, use UYVY as default */ + if (i >= ARRAY_SIZE(ipipeif_output_fmts)) + fmt->code = V4L2_MBUS_FMT_UYVY8_2X8; + } + + fmt->width = clamp_t(u32, fmt->width, MIN_OUT_HEIGHT, max_out_width); + fmt->height = clamp_t(u32, fmt->height, MIN_OUT_WIDTH, max_out_height); +} + +static int +ipipeif_enum_frame_size(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh, + struct v4l2_subdev_frame_size_enum *fse) +{ + struct vpfe_ipipeif_device *ipipeif = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt format; + + if (fse->index != 0) + return -EINVAL; + + format.code = fse->code; + format.width = 1; + format.height = 1; + ipipeif_try_format(ipipeif, fh, fse->pad, &format, + V4L2_SUBDEV_FORMAT_TRY); + fse->min_width = format.width; + fse->min_height = format.height; + + if (format.code != fse->code) + return -EINVAL; + + format.code = fse->code; + format.width = -1; + format.height = -1; + ipipeif_try_format(ipipeif, fh, fse->pad, &format, + V4L2_SUBDEV_FORMAT_TRY); + fse->max_width = format.width; + fse->max_height = format.height; + + return 0; +} + +/* + * __ipipeif_get_format() - helper function for getting ipipeif format + * @ipipeif: pointer to ipipeif private structure. + * @pad: pad number. + * @fh: V4L2 subdev file handle. + * @which: wanted subdev format. + * + */ +static struct v4l2_mbus_framefmt * +__ipipeif_get_format(struct vpfe_ipipeif_device *ipipeif, + struct v4l2_subdev_fh *fh, unsigned int pad, + enum v4l2_subdev_format_whence which) +{ + if (which == V4L2_SUBDEV_FORMAT_TRY) + return v4l2_subdev_get_try_format(fh, pad); + + return &ipipeif->formats[pad]; +} + +/* + * ipipeif_set_format() - Handle set format by pads subdev method + * @sd: pointer to v4l2 subdev structure + * @fh: V4L2 subdev file handle + * @fmt: pointer to v4l2 subdev format structure + * return -EINVAL or zero on success + */ +static int +ipipeif_set_format(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh, + struct v4l2_subdev_format *fmt) +{ + struct vpfe_ipipeif_device *ipipeif = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt *format; + + format = __ipipeif_get_format(ipipeif, fh, fmt->pad, fmt->which); + if (format == NULL) + return -EINVAL; + + ipipeif_try_format(ipipeif, fh, fmt->pad, &fmt->format, fmt->which); + *format = fmt->format; + + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) + return 0; + + if (fmt->pad == IPIPEIF_PAD_SINK && + ipipeif->input != IPIPEIF_INPUT_NONE) + ipipeif->formats[fmt->pad] = fmt->format; + else if (fmt->pad == IPIPEIF_PAD_SOURCE && + ipipeif->output != IPIPEIF_OUTPUT_NONE) + ipipeif->formats[fmt->pad] = fmt->format; + else + return -EINVAL; + + return 0; +} + +static void ipipeif_set_default_config(struct vpfe_ipipeif_device *ipipeif) +{ +#define WIDTH_I 640 +#define HEIGHT_I 480 + + const struct ipipeif_params ipipeif_defaults = { + .clock_select = IPIPEIF_SDRAM_CLK, + .ppln = WIDTH_I + 8, + .lpfr = HEIGHT_I + 10, + .rsz = 16, /* resize ratio 16/rsz */ + .decimation = IPIPEIF_DECIMATION_OFF, + .avg_filter = IPIPEIF_AVG_OFF, + .if_5_1 = { + .clk_div = { + .m = 1, /* clock = sdram clock * (m/n) */ + .n = 6 + }, + .clip = 4095, + }, + }; + memset(&ipipeif->config, 0, sizeof(struct ipipeif_params)); + memcpy(&ipipeif->config, &ipipeif_defaults, + sizeof(struct ipipeif_params)); +} + +/* + * ipipeif_init_formats() - Initialize formats on all pads + * @sd: VPFE ipipeif V4L2 subdevice + * @fh: V4L2 subdev file handle + * + * Initialize all pad formats with default values. If fh is not NULL, try + * formats are initialized on the file handle. Otherwise active formats are + * initialized on the device. + */ +static int +ipipeif_init_formats(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + struct vpfe_ipipeif_device *ipipeif = v4l2_get_subdevdata(sd); + struct v4l2_subdev_format format; + + memset(&format, 0, sizeof(format)); + format.pad = IPIPEIF_PAD_SINK; + format.which = fh ? V4L2_SUBDEV_FORMAT_TRY : V4L2_SUBDEV_FORMAT_ACTIVE; + format.format.code = V4L2_MBUS_FMT_SGRBG12_1X12; + format.format.width = IPIPE_MAX_OUTPUT_WIDTH_A; + format.format.height = IPIPE_MAX_OUTPUT_HEIGHT_A; + ipipeif_set_format(sd, fh, &format); + + memset(&format, 0, sizeof(format)); + format.pad = IPIPEIF_PAD_SOURCE; + format.which = fh ? V4L2_SUBDEV_FORMAT_TRY : V4L2_SUBDEV_FORMAT_ACTIVE; + format.format.code = V4L2_MBUS_FMT_UYVY8_2X8; + format.format.width = IPIPE_MAX_OUTPUT_WIDTH_A; + format.format.height = IPIPE_MAX_OUTPUT_HEIGHT_A; + ipipeif_set_format(sd, fh, &format); + + ipipeif_set_default_config(ipipeif); + + return 0; +} + +/* + * ipipeif_video_in_queue() - ipipeif video in queue + * @vpfe_dev: vpfe device pointer + * @addr: buffer address + */ +static int +ipipeif_video_in_queue(struct vpfe_device *vpfe_dev, unsigned long addr) +{ + struct vpfe_ipipeif_device *ipipeif = &vpfe_dev->vpfe_ipipeif; + void *ipipeif_base_addr = ipipeif->ipipeif_base_addr; + unsigned int adofs; + u32 val; + + if (ipipeif->input != IPIPEIF_INPUT_MEMORY) + return -EINVAL; + + switch (ipipeif->formats[IPIPEIF_PAD_SINK].code) { + case V4L2_MBUS_FMT_Y8_1X8: + case V4L2_MBUS_FMT_UV8_1X8: + case V4L2_MBUS_FMT_YDYUYDYV8_1X16: + adofs = ipipeif->formats[IPIPEIF_PAD_SINK].width; + break; + + default: + adofs = ipipeif->formats[IPIPEIF_PAD_SINK].width << 1; + break; + } + + /* adjust the line len to be a multiple of 32 */ + adofs += 31; + adofs &= ~0x1f; + val = (adofs >> 5) & IPIPEIF_ADOFS_LSB_MASK; + ipipeif_write(val, ipipeif_base_addr, IPIPEIF_ADOFS); + + /* lower sixteen bit */ + val = (addr >> IPIPEIF_ADDRL_SHIFT) & IPIPEIF_ADDRL_MASK; + ipipeif_write(val, ipipeif_base_addr, IPIPEIF_ADDRL); + + /* upper next seven bit */ + val = (addr >> IPIPEIF_ADDRU_SHIFT) & IPIPEIF_ADDRU_MASK; + ipipeif_write(val, ipipeif_base_addr, IPIPEIF_ADDRU); + + return 0; +} + +/* subdev core operations */ +static const struct v4l2_subdev_core_ops ipipeif_v4l2_core_ops = { + .ioctl = ipipeif_ioctl, +}; + +static const struct v4l2_ctrl_ops ipipeif_ctrl_ops = { + .s_ctrl = ipipeif_s_ctrl, +}; + +static const struct v4l2_ctrl_config vpfe_ipipeif_dpcm_pred = { + .ops = &ipipeif_ctrl_ops, + .id = VPFE_CID_DPCM_PREDICTOR, + .name = "DPCM Predictor", + .type = V4L2_CTRL_TYPE_INTEGER, + .min = 0, + .max = 1, + .step = 1, + .def = 0, +}; + +/* subdev file operations */ +static const struct v4l2_subdev_internal_ops ipipeif_v4l2_internal_ops = { + .open = ipipeif_init_formats, +}; + +/* subdev video operations */ +static const struct v4l2_subdev_video_ops ipipeif_v4l2_video_ops = { + .s_stream = ipipeif_set_stream, +}; + +/* subdev pad operations */ +static const struct v4l2_subdev_pad_ops ipipeif_v4l2_pad_ops = { + .enum_mbus_code = ipipeif_enum_mbus_code, + .enum_frame_size = ipipeif_enum_frame_size, + .get_fmt = ipipeif_get_format, + .set_fmt = ipipeif_set_format, +}; + +/* subdev operations */ +static const struct v4l2_subdev_ops ipipeif_v4l2_ops = { + .core = &ipipeif_v4l2_core_ops, + .video = &ipipeif_v4l2_video_ops, + .pad = &ipipeif_v4l2_pad_ops, +}; + +static const struct vpfe_video_operations video_in_ops = { + .queue = ipipeif_video_in_queue, +}; + +static int +ipipeif_link_setup(struct media_entity *entity, const struct media_pad *local, + const struct media_pad *remote, u32 flags) +{ + struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(entity); + struct vpfe_ipipeif_device *ipipeif = v4l2_get_subdevdata(sd); + struct vpfe_device *vpfe = to_vpfe_device(ipipeif); + + switch (local->index | media_entity_type(remote->entity)) { + case IPIPEIF_PAD_SINK | MEDIA_ENT_T_DEVNODE: + /* Single shot mode */ + if (!(flags & MEDIA_LNK_FL_ENABLED)) { + ipipeif->input = IPIPEIF_INPUT_NONE; + break; + } + ipipeif->input = IPIPEIF_INPUT_MEMORY; + break; + + case IPIPEIF_PAD_SINK | MEDIA_ENT_T_V4L2_SUBDEV: + /* read from isif */ + if (!(flags & MEDIA_LNK_FL_ENABLED)) { + ipipeif->input = IPIPEIF_INPUT_NONE; + break; + } + if (ipipeif->input != IPIPEIF_INPUT_NONE) + return -EBUSY; + + ipipeif->input = IPIPEIF_INPUT_ISIF; + break; + + case IPIPEIF_PAD_SOURCE | MEDIA_ENT_T_V4L2_SUBDEV: + if (!(flags & MEDIA_LNK_FL_ENABLED)) { + ipipeif->output = IPIPEIF_OUTPUT_NONE; + break; + } + if (remote->entity == &vpfe->vpfe_ipipe.subdev.entity) + /* connencted to ipipe */ + ipipeif->output = IPIPEIF_OUTPUT_IPIPE; + else if (remote->entity == &vpfe->vpfe_resizer. + crop_resizer.subdev.entity) + /* connected to resizer */ + ipipeif->output = IPIPEIF_OUTPUT_RESIZER; + else + return -EINVAL; + break; + + default: + return -EINVAL; + } + + return 0; +} + +static const struct media_entity_operations ipipeif_media_ops = { + .link_setup = ipipeif_link_setup, +}; + +/* + * vpfe_ipipeif_unregister_entities() - Unregister entity + * @ipipeif - pointer to ipipeif subdevice structure. + */ +void vpfe_ipipeif_unregister_entities(struct vpfe_ipipeif_device *ipipeif) +{ + /* unregister video device */ + vpfe_video_unregister(&ipipeif->video_in); + + /* unregister subdev */ + v4l2_device_unregister_subdev(&ipipeif->subdev); + /* cleanup entity */ + media_entity_cleanup(&ipipeif->subdev.entity); +} + +int +vpfe_ipipeif_register_entities(struct vpfe_ipipeif_device *ipipeif, + struct v4l2_device *vdev) +{ + struct vpfe_device *vpfe_dev = to_vpfe_device(ipipeif); + unsigned int flags; + int ret; + + /* Register the subdev */ + ret = v4l2_device_register_subdev(vdev, &ipipeif->subdev); + if (ret < 0) + return ret; + + ret = vpfe_video_register(&ipipeif->video_in, vdev); + if (ret) { + pr_err("Failed to register ipipeif video-in device\n"); + goto fail; + } + ipipeif->video_in.vpfe_dev = vpfe_dev; + + flags = 0; + ret = media_entity_create_link(&ipipeif->video_in.video_dev.entity, 0, + &ipipeif->subdev.entity, 0, flags); + if (ret < 0) + goto fail; + + return 0; +fail: + v4l2_device_unregister_subdev(&ipipeif->subdev); + + return ret; +} + +#define IPIPEIF_GAIN_HIGH 0x3ff +#define IPIPEIF_DEFAULT_GAIN 0x200 + +int vpfe_ipipeif_init(struct vpfe_ipipeif_device *ipipeif, + struct platform_device *pdev) +{ + struct v4l2_subdev *sd = &ipipeif->subdev; + struct media_pad *pads = &ipipeif->pads[0]; + struct media_entity *me = &sd->entity; + static resource_size_t res_len; + struct resource *res; + int ret; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 3); + if (!res) + return -ENOENT; + + res_len = resource_size(res); + res = request_mem_region(res->start, res_len, res->name); + if (!res) + return -EBUSY; + + ipipeif->ipipeif_base_addr = ioremap_nocache(res->start, res_len); + if (!ipipeif->ipipeif_base_addr) { + ret = -EBUSY; + goto fail; + } + + v4l2_subdev_init(sd, &ipipeif_v4l2_ops); + + sd->internal_ops = &ipipeif_v4l2_internal_ops; + strlcpy(sd->name, "DAVINCI IPIPEIF", sizeof(sd->name)); + sd->grp_id = 1 << 16; /* group ID for davinci subdevs */ + + v4l2_set_subdevdata(sd, ipipeif); + + sd->flags |= V4L2_SUBDEV_FL_HAS_EVENTS | V4L2_SUBDEV_FL_HAS_DEVNODE; + pads[IPIPEIF_PAD_SINK].flags = MEDIA_PAD_FL_SINK; + pads[IPIPEIF_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE; + ipipeif->input = IPIPEIF_INPUT_NONE; + ipipeif->output = IPIPEIF_OUTPUT_NONE; + me->ops = &ipipeif_media_ops; + + ret = media_entity_init(me, IPIPEIF_NUM_PADS, pads, 0); + if (ret) + goto fail; + + v4l2_ctrl_handler_init(&ipipeif->ctrls, 2); + v4l2_ctrl_new_std(&ipipeif->ctrls, &ipipeif_ctrl_ops, + V4L2_CID_GAIN, 0, + IPIPEIF_GAIN_HIGH, 1, IPIPEIF_DEFAULT_GAIN); + v4l2_ctrl_new_custom(&ipipeif->ctrls, &vpfe_ipipeif_dpcm_pred, NULL); + v4l2_ctrl_handler_setup(&ipipeif->ctrls); + sd->ctrl_handler = &ipipeif->ctrls; + + ipipeif->video_in.ops = &video_in_ops; + ipipeif->video_in.type = V4L2_BUF_TYPE_VIDEO_OUTPUT; + ret = vpfe_video_init(&ipipeif->video_in, "IPIPEIF"); + if (ret) { + pr_err("Failed to init IPIPEIF video-in device\n"); + goto fail; + } + ipipeif_set_default_config(ipipeif); + return 0; +fail: + release_mem_region(res->start, res_len); + return ret; +} + +void +vpfe_ipipeif_cleanup(struct vpfe_ipipeif_device *ipipeif, + struct platform_device *pdev) +{ + struct resource *res; + + v4l2_ctrl_handler_free(&ipipeif->ctrls); + iounmap(ipipeif->ipipeif_base_addr); + res = platform_get_resource(pdev, IORESOURCE_MEM, 3); + if (res) + release_mem_region(res->start, resource_size(res)); + +} diff --git a/drivers/staging/media/davinci_vpfe/dm365_ipipeif.h b/drivers/staging/media/davinci_vpfe/dm365_ipipeif.h new file mode 100644 index 00000000000..608701fc5fe --- /dev/null +++ b/drivers/staging/media/davinci_vpfe/dm365_ipipeif.h @@ -0,0 +1,233 @@ +/* + * Copyright (C) 2012 Texas Instruments Inc + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Contributors: + * Manjunath Hadli <manjunath.hadli@ti.com> + * Prabhakar Lad <prabhakar.lad@ti.com> + */ + +#ifndef _DAVINCI_VPFE_DM365_IPIPEIF_H +#define _DAVINCI_VPFE_DM365_IPIPEIF_H + +#include <linux/platform_device.h> + +#include <media/davinci/vpss.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-subdev.h> + +#include "dm365_ipipeif_user.h" +#include "vpfe_video.h" + +/* IPIPE base specific types */ +enum ipipeif_data_shift { + IPIPEIF_BITS15_2 = 0, + IPIPEIF_BITS14_1 = 1, + IPIPEIF_BITS13_0 = 2, + IPIPEIF_BITS12_0 = 3, + IPIPEIF_BITS11_0 = 4, + IPIPEIF_BITS10_0 = 5, + IPIPEIF_BITS9_0 = 6, +}; + +enum ipipeif_clkdiv { + IPIPEIF_DIVIDE_HALF = 0, + IPIPEIF_DIVIDE_THIRD = 1, + IPIPEIF_DIVIDE_FOURTH = 2, + IPIPEIF_DIVIDE_FIFTH = 3, + IPIPEIF_DIVIDE_SIXTH = 4, + IPIPEIF_DIVIDE_EIGHTH = 5, + IPIPEIF_DIVIDE_SIXTEENTH = 6, + IPIPEIF_DIVIDE_THIRTY = 7, +}; + +enum ipipeif_pack_mode { + IPIPEIF_PACK_16_BIT = 0, + IPIPEIF_PACK_8_BIT = 1, +}; + +enum ipipeif_5_1_pack_mode { + IPIPEIF_5_1_PACK_16_BIT = 0, + IPIPEIF_5_1_PACK_8_BIT = 1, + IPIPEIF_5_1_PACK_8_BIT_A_LAW = 2, + IPIPEIF_5_1_PACK_12_BIT = 3 +}; + +enum ipipeif_input_source { + IPIPEIF_CCDC = 0, + IPIPEIF_SDRAM_RAW = 1, + IPIPEIF_CCDC_DARKFM = 2, + IPIPEIF_SDRAM_YUV = 3, +}; + +enum ipipeif_ialaw { + IPIPEIF_ALAW_OFF = 0, + IPIPEIF_ALAW_ON = 1, +}; + +enum ipipeif_input_src1 { + IPIPEIF_SRC1_PARALLEL_PORT = 0, + IPIPEIF_SRC1_SDRAM_RAW = 1, + IPIPEIF_SRC1_ISIF_DARKFM = 2, + IPIPEIF_SRC1_SDRAM_YUV = 3, +}; + +enum ipipeif_dfs_dir { + IPIPEIF_PORT_MINUS_SDRAM = 0, + IPIPEIF_SDRAM_MINUS_PORT = 1, +}; + +enum ipipeif_chroma_phase { + IPIPEIF_CBCR_Y = 0, + IPIPEIF_Y_CBCR = 1, +}; + +enum ipipeif_dpcm_type { + IPIPEIF_DPCM_8BIT_10BIT = 0, + IPIPEIF_DPCM_8BIT_12BIT = 1, +}; + +/* data shift for IPIPE 5.1 */ +enum ipipeif_5_1_data_shift { + IPIPEIF_5_1_BITS11_0 = 0, + IPIPEIF_5_1_BITS10_0 = 1, + IPIPEIF_5_1_BITS9_0 = 2, + IPIPEIF_5_1_BITS8_0 = 3, + IPIPEIF_5_1_BITS7_0 = 4, + IPIPEIF_5_1_BITS15_4 = 5, +}; + +#define IPIPEIF_PAD_SINK 0 +#define IPIPEIF_PAD_SOURCE 1 + +#define IPIPEIF_NUM_PADS 2 + +enum ipipeif_input_entity { + IPIPEIF_INPUT_NONE = 0, + IPIPEIF_INPUT_ISIF = 1, + IPIPEIF_INPUT_MEMORY = 2, +}; + +enum ipipeif_output_entity { + IPIPEIF_OUTPUT_NONE = 0, + IPIPEIF_OUTPUT_IPIPE = 1, + IPIPEIF_OUTPUT_RESIZER = 2, +}; + +struct vpfe_ipipeif_device { + struct v4l2_subdev subdev; + struct media_pad pads[IPIPEIF_NUM_PADS]; + struct v4l2_mbus_framefmt formats[IPIPEIF_NUM_PADS]; + enum ipipeif_input_entity input; + unsigned int output; + struct vpfe_video_device video_in; + struct v4l2_ctrl_handler ctrls; + void *__iomem ipipeif_base_addr; + struct ipipeif_params config; + int dpcm_predictor; + int gain; +}; + +/* IPIPEIF Register Offsets from the base address */ +#define IPIPEIF_ENABLE 0x00 +#define IPIPEIF_CFG1 0x04 +#define IPIPEIF_PPLN 0x08 +#define IPIPEIF_LPFR 0x0c +#define IPIPEIF_HNUM 0x10 +#define IPIPEIF_VNUM 0x14 +#define IPIPEIF_ADDRU 0x18 +#define IPIPEIF_ADDRL 0x1c +#define IPIPEIF_ADOFS 0x20 +#define IPIPEIF_RSZ 0x24 +#define IPIPEIF_GAIN 0x28 + +/* Below registers are available only on IPIPE 5.1 */ +#define IPIPEIF_DPCM 0x2c +#define IPIPEIF_CFG2 0x30 +#define IPIPEIF_INIRSZ 0x34 +#define IPIPEIF_OCLIP 0x38 +#define IPIPEIF_DTUDF 0x3c +#define IPIPEIF_CLKDIV 0x40 +#define IPIPEIF_DPC1 0x44 +#define IPIPEIF_DPC2 0x48 +#define IPIPEIF_DFSGVL 0x4c +#define IPIPEIF_DFSGTH 0x50 +#define IPIPEIF_RSZ3A 0x54 +#define IPIPEIF_INIRSZ3A 0x58 +#define IPIPEIF_RSZ_MIN 16 +#define IPIPEIF_RSZ_MAX 112 +#define IPIPEIF_RSZ_CONST 16 +#define SETBIT(reg, bit) (reg = ((reg) | ((0x00000001)<<(bit)))) +#define RESETBIT(reg, bit) (reg = ((reg) & (~(0x00000001<<(bit))))) + +#define IPIPEIF_ADOFS_LSB_MASK 0x1ff +#define IPIPEIF_ADOFS_LSB_SHIFT 5 +#define IPIPEIF_ADOFS_MSB_MASK 0x200 +#define IPIPEIF_ADDRU_MASK 0x7ff +#define IPIPEIF_ADDRL_SHIFT 5 +#define IPIPEIF_ADDRL_MASK 0xffff +#define IPIPEIF_ADDRU_SHIFT 21 +#define IPIPEIF_ADDRMSB_SHIFT 31 +#define IPIPEIF_ADDRMSB_LEFT_SHIFT 10 + +/* CFG1 Masks and shifts */ +#define ONESHOT_SHIFT 0 +#define DECIM_SHIFT 1 +#define INPSRC_SHIFT 2 +#define CLKDIV_SHIFT 4 +#define AVGFILT_SHIFT 7 +#define PACK8IN_SHIFT 8 +#define IALAW_SHIFT 9 +#define CLKSEL_SHIFT 10 +#define DATASFT_SHIFT 11 +#define INPSRC1_SHIFT 14 + +/* DPC2 */ +#define IPIPEIF_DPC2_EN_SHIFT 12 +#define IPIPEIF_DPC2_THR_MASK 0xfff +/* Applicable for IPIPE 5.1 */ +#define IPIPEIF_DF_GAIN_EN_SHIFT 10 +#define IPIPEIF_DF_GAIN_MASK 0x3ff +#define IPIPEIF_DF_GAIN_THR_MASK 0xfff +/* DPCM */ +#define IPIPEIF_DPCM_BITS_SHIFT 2 +#define IPIPEIF_DPCM_PRED_SHIFT 1 +/* CFG2 */ +#define IPIPEIF_CFG2_HDPOL_SHIFT 1 +#define IPIPEIF_CFG2_VDPOL_SHIFT 2 +#define IPIPEIF_CFG2_YUV8_SHIFT 6 +#define IPIPEIF_CFG2_YUV16_SHIFT 3 +#define IPIPEIF_CFG2_YUV8P_SHIFT 7 + +/* INIRSZ */ +#define IPIPEIF_INIRSZ_ALNSYNC_SHIFT 13 +#define IPIPEIF_INIRSZ_MASK 0x1fff + +/* CLKDIV */ +#define IPIPEIF_CLKDIV_M_SHIFT 8 + +void vpfe_ipipeif_enable(struct vpfe_device *vpfe_dev); +void vpfe_ipipeif_ss_buffer_isr(struct vpfe_ipipeif_device *ipipeif); +int vpfe_ipipeif_decimation_enabled(struct vpfe_device *vpfe_dev); +int vpfe_ipipeif_get_rsz(struct vpfe_device *vpfe_dev); +void vpfe_ipipeif_cleanup(struct vpfe_ipipeif_device *ipipeif, + struct platform_device *pdev); +int vpfe_ipipeif_init(struct vpfe_ipipeif_device *ipipeif, + struct platform_device *pdev); +int vpfe_ipipeif_register_entities(struct vpfe_ipipeif_device *ipipeif, + struct v4l2_device *vdev); +void vpfe_ipipeif_unregister_entities(struct vpfe_ipipeif_device *ipipeif); + +#endif /* _DAVINCI_VPFE_DM365_IPIPEIF_H */ diff --git a/drivers/staging/media/davinci_vpfe/dm365_ipipeif_user.h b/drivers/staging/media/davinci_vpfe/dm365_ipipeif_user.h new file mode 100644 index 00000000000..e2a69b59554 --- /dev/null +++ b/drivers/staging/media/davinci_vpfe/dm365_ipipeif_user.h @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2012 Texas Instruments Inc + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Contributors: + * Manjunath Hadli <manjunath.hadli@ti.com> + * Prabhakar Lad <prabhakar.lad@ti.com> + */ + +#ifndef _DAVINCI_VPFE_DM365_IPIPEIF_USER_H +#define _DAVINCI_VPFE_DM365_IPIPEIF_USER_H + +/* clockdiv for IPIPE 5.1 */ +struct ipipeif_5_1_clkdiv { + unsigned char m; + unsigned char n; +}; + +enum ipipeif_decimation { + IPIPEIF_DECIMATION_OFF, + IPIPEIF_DECIMATION_ON +}; + +/* DPC at the if for IPIPE 5.1 */ +struct ipipeif_dpc { + /* 0 - disable, 1 - enable */ + unsigned char en; + /* threshold */ + unsigned short thr; +}; + +enum ipipeif_clock { + IPIPEIF_PIXCEL_CLK, + IPIPEIF_SDRAM_CLK +}; + +enum ipipeif_avg_filter { + IPIPEIF_AVG_OFF, + IPIPEIF_AVG_ON +}; + +struct ipipeif_5_1 { + struct ipipeif_5_1_clkdiv clk_div; + /* Defect pixel correction */ + struct ipipeif_dpc dpc; + /* clipped to this value */ + unsigned short clip; + /* Align HSync and VSync to rsz_start */ + unsigned char align_sync; + /* resizer start position */ + unsigned int rsz_start; + /* DF gain enable */ + unsigned char df_gain_en; + /* DF gain value */ + unsigned short df_gain; + /* DF gain threshold value */ + unsigned short df_gain_thr; +}; + +struct ipipeif_params { + enum ipipeif_clock clock_select; + unsigned int ppln; + unsigned int lpfr; + unsigned char rsz; + enum ipipeif_decimation decimation; + enum ipipeif_avg_filter avg_filter; + /* IPIPE 5.1 */ + struct ipipeif_5_1 if_5_1; +}; + +/* + * Private IOCTL + * VIDIOC_VPFE_IPIPEIF_S_CONFIG: Set IPIEIF configuration + * VIDIOC_VPFE_IPIPEIF_G_CONFIG: Get IPIEIF configuration + */ +#define VIDIOC_VPFE_IPIPEIF_S_CONFIG \ + _IOWR('I', BASE_VIDIOC_PRIVATE + 1, struct ipipeif_params) +#define VIDIOC_VPFE_IPIPEIF_G_CONFIG \ + _IOWR('I', BASE_VIDIOC_PRIVATE + 2, struct ipipeif_params) + +#endif /* _DAVINCI_VPFE_DM365_IPIPEIF_USER_H */ diff --git a/drivers/staging/media/davinci_vpfe/dm365_isif.c b/drivers/staging/media/davinci_vpfe/dm365_isif.c new file mode 100644 index 00000000000..b942bf73c43 --- /dev/null +++ b/drivers/staging/media/davinci_vpfe/dm365_isif.c @@ -0,0 +1,2105 @@ +/* + * Copyright (C) 2012 Texas Instruments Inc + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Contributors: + * Manjunath Hadli <manjunath.hadli@ti.com> + * Prabhakar Lad <prabhakar.lad@ti.com> + */ + +#include <linux/delay.h> +#include "dm365_isif.h" +#include "vpfe_mc_capture.h" + +#define MAX_WIDTH 4096 +#define MAX_HEIGHT 4096 + +static const unsigned int isif_fmts[] = { + V4L2_MBUS_FMT_YUYV8_2X8, + V4L2_MBUS_FMT_UYVY8_2X8, + V4L2_MBUS_FMT_YUYV8_1X16, + V4L2_MBUS_FMT_YUYV10_1X20, + V4L2_MBUS_FMT_SGRBG12_1X12, + V4L2_MBUS_FMT_SGRBG10_ALAW8_1X8, + V4L2_MBUS_FMT_SGRBG10_DPCM8_1X8, +}; + +#define ISIF_COLPTN_R_Ye 0x0 +#define ISIF_COLPTN_Gr_Cy 0x1 +#define ISIF_COLPTN_Gb_G 0x2 +#define ISIF_COLPTN_B_Mg 0x3 + +#define ISIF_CCOLP_CP01_0 0 +#define ISIF_CCOLP_CP03_2 2 +#define ISIF_CCOLP_CP05_4 4 +#define ISIF_CCOLP_CP07_6 6 +#define ISIF_CCOLP_CP11_0 8 +#define ISIF_CCOLP_CP13_2 10 +#define ISIF_CCOLP_CP15_4 12 +#define ISIF_CCOLP_CP17_6 14 + +static const u32 isif_sgrbg_pattern = + ISIF_COLPTN_Gr_Cy << ISIF_CCOLP_CP01_0 | + ISIF_COLPTN_R_Ye << ISIF_CCOLP_CP03_2 | + ISIF_COLPTN_B_Mg << ISIF_CCOLP_CP05_4 | + ISIF_COLPTN_Gb_G << ISIF_CCOLP_CP07_6 | + ISIF_COLPTN_Gr_Cy << ISIF_CCOLP_CP11_0 | + ISIF_COLPTN_R_Ye << ISIF_CCOLP_CP13_2 | + ISIF_COLPTN_B_Mg << ISIF_CCOLP_CP15_4 | + ISIF_COLPTN_Gb_G << ISIF_CCOLP_CP17_6; + +static const u32 isif_srggb_pattern = + ISIF_COLPTN_R_Ye << ISIF_CCOLP_CP01_0 | + ISIF_COLPTN_Gr_Cy << ISIF_CCOLP_CP03_2 | + ISIF_COLPTN_Gb_G << ISIF_CCOLP_CP05_4 | + ISIF_COLPTN_B_Mg << ISIF_CCOLP_CP07_6 | + ISIF_COLPTN_R_Ye << ISIF_CCOLP_CP11_0 | + ISIF_COLPTN_Gr_Cy << ISIF_CCOLP_CP13_2 | + ISIF_COLPTN_Gb_G << ISIF_CCOLP_CP15_4 | + ISIF_COLPTN_B_Mg << ISIF_CCOLP_CP17_6; + +static inline u32 isif_read(void *__iomem base_addr, u32 offset) +{ + return readl(base_addr + offset); +} + +static inline void isif_write(void *__iomem base_addr, u32 val, u32 offset) +{ + writel(val, base_addr + offset); +} + +static inline u32 isif_merge(void *__iomem base_addr, u32 mask, u32 val, + u32 offset) +{ + u32 new_val = (isif_read(base_addr, offset) & ~mask) | (val & mask); + + isif_write(base_addr, new_val, offset); + + return new_val; +} + +static void isif_enable_output_to_sdram(struct vpfe_isif_device *isif, int en) +{ + isif_merge(isif->isif_cfg.base_addr, ISIF_SYNCEN_WEN_MASK, + en << ISIF_SYNCEN_WEN_SHIFT, SYNCEN); +} + +static inline void +isif_regw_lin_tbl(struct vpfe_isif_device *isif, u32 val, u32 offset, int i) +{ + if (!i) + writel(val, isif->isif_cfg.linear_tbl0_addr + offset); + else + writel(val, isif->isif_cfg.linear_tbl1_addr + offset); +} + +static void isif_disable_all_modules(struct vpfe_isif_device *isif) +{ + /* disable BC */ + isif_write(isif->isif_cfg.base_addr, 0, CLAMPCFG); + /* disable vdfc */ + isif_write(isif->isif_cfg.base_addr, 0, DFCCTL); + /* disable CSC */ + isif_write(isif->isif_cfg.base_addr, 0, CSCCTL); + /* disable linearization */ + isif_write(isif->isif_cfg.base_addr, 0, LINCFG0); +} + +static void isif_enable(struct vpfe_isif_device *isif, int en) +{ + if (!en) + /* Before disable isif, disable all ISIF modules */ + isif_disable_all_modules(isif); + + /* + * wait for next VD. Assume lowest scan rate is 12 Hz. So + * 100 msec delay is good enough + */ + msleep(100); + isif_merge(isif->isif_cfg.base_addr, ISIF_SYNCEN_VDHDEN_MASK, + en, SYNCEN); +} + +/* + * ISIF helper functions + */ + +#define DM365_ISIF_MDFS_OFFSET 15 +#define DM365_ISIF_MDFS_MASK 0x1 + +/* get field id in isif hardware */ +enum v4l2_field vpfe_isif_get_fid(struct vpfe_device *vpfe_dev) +{ + struct vpfe_isif_device *isif = &vpfe_dev->vpfe_isif; + u32 field_status; + + field_status = isif_read(isif->isif_cfg.base_addr, MODESET); + field_status = (field_status >> DM365_ISIF_MDFS_OFFSET) & + DM365_ISIF_MDFS_MASK; + return field_status; +} + +static int +isif_set_pixel_format(struct vpfe_isif_device *isif, unsigned int pixfmt) +{ + if (isif->formats[ISIF_PAD_SINK].code == V4L2_MBUS_FMT_SGRBG12_1X12) { + if (pixfmt == V4L2_PIX_FMT_SBGGR16) + isif->isif_cfg.data_pack = ISIF_PACK_16BIT; + else if ((pixfmt == V4L2_PIX_FMT_SGRBG10DPCM8) || + (pixfmt == V4L2_PIX_FMT_SGRBG10ALAW8)) + isif->isif_cfg.data_pack = ISIF_PACK_8BIT; + else + return -EINVAL; + + isif->isif_cfg.bayer.pix_fmt = ISIF_PIXFMT_RAW; + isif->isif_cfg.bayer.v4l2_pix_fmt = pixfmt; + } else { + if (pixfmt == V4L2_PIX_FMT_YUYV) + isif->isif_cfg.ycbcr.pix_order = ISIF_PIXORDER_YCBYCR; + else if (pixfmt == V4L2_PIX_FMT_UYVY) + isif->isif_cfg.ycbcr.pix_order = ISIF_PIXORDER_CBYCRY; + else + return -EINVAL; + + isif->isif_cfg.data_pack = ISIF_PACK_8BIT; + isif->isif_cfg.ycbcr.v4l2_pix_fmt = pixfmt; + } + + return 0; +} + +static int +isif_set_frame_format(struct vpfe_isif_device *isif, + enum isif_frmfmt frm_fmt) +{ + if (isif->formats[ISIF_PAD_SINK].code == V4L2_MBUS_FMT_SGRBG12_1X12) + isif->isif_cfg.bayer.frm_fmt = frm_fmt; + else + isif->isif_cfg.ycbcr.frm_fmt = frm_fmt; + + return 0; +} + +static int isif_set_image_window(struct vpfe_isif_device *isif) +{ + struct v4l2_rect *win = &isif->crop; + + if (isif->formats[ISIF_PAD_SINK].code == V4L2_MBUS_FMT_SGRBG12_1X12) { + isif->isif_cfg.bayer.win.top = win->top; + isif->isif_cfg.bayer.win.left = win->left; + isif->isif_cfg.bayer.win.width = win->width; + isif->isif_cfg.bayer.win.height = win->height; + return 0; + } + isif->isif_cfg.ycbcr.win.top = win->top; + isif->isif_cfg.ycbcr.win.left = win->left; + isif->isif_cfg.ycbcr.win.width = win->width; + isif->isif_cfg.ycbcr.win.height = win->height; + + return 0; +} + +static int +isif_set_buftype(struct vpfe_isif_device *isif, enum isif_buftype buf_type) +{ + if (isif->formats[ISIF_PAD_SINK].code == V4L2_MBUS_FMT_SGRBG12_1X12) + isif->isif_cfg.bayer.buf_type = buf_type; + else + isif->isif_cfg.ycbcr.buf_type = buf_type; + + return 0; +} + +/* configure format in isif hardware */ +static int +isif_config_format(struct vpfe_device *vpfe_dev, unsigned int pad) +{ + struct vpfe_isif_device *vpfe_isif = &vpfe_dev->vpfe_isif; + enum isif_frmfmt frm_fmt = ISIF_FRMFMT_INTERLACED; + struct v4l2_pix_format format; + int ret = 0; + + v4l2_fill_pix_format(&format, &vpfe_dev->vpfe_isif.formats[pad]); + mbus_to_pix(&vpfe_dev->vpfe_isif.formats[pad], &format); + + if (isif_set_pixel_format(vpfe_isif, format.pixelformat) < 0) { + v4l2_err(&vpfe_dev->v4l2_dev, + "Failed to set pixel format in isif\n"); + return -EINVAL; + } + + /* call for s_crop will override these values */ + vpfe_isif->crop.left = 0; + vpfe_isif->crop.top = 0; + vpfe_isif->crop.width = format.width; + vpfe_isif->crop.height = format.height; + + /* configure the image window */ + isif_set_image_window(vpfe_isif); + + switch (vpfe_dev->vpfe_isif.formats[pad].field) { + case V4L2_FIELD_INTERLACED: + /* do nothing, since it is default */ + ret = isif_set_buftype(vpfe_isif, ISIF_BUFTYPE_FLD_INTERLEAVED); + break; + + case V4L2_FIELD_NONE: + frm_fmt = ISIF_FRMFMT_PROGRESSIVE; + /* buffer type only applicable for interlaced scan */ + break; + + case V4L2_FIELD_SEQ_TB: + ret = isif_set_buftype(vpfe_isif, ISIF_BUFTYPE_FLD_SEPARATED); + break; + + default: + return -EINVAL; + } + + /* set the frame format */ + if (!ret) + ret = isif_set_frame_format(vpfe_isif, frm_fmt); + + return ret; +} + +/* + * isif_try_format() - Try video format on a pad + * @isif: VPFE isif device + * @fh: V4L2 subdev file handle + * @fmt: pointer to v4l2 subdev format structure + */ +static void +isif_try_format(struct vpfe_isif_device *isif, struct v4l2_subdev_fh *fh, + struct v4l2_subdev_format *fmt) +{ + unsigned int width = fmt->format.width; + unsigned int height = fmt->format.height; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(isif_fmts); i++) { + if (fmt->format.code == isif_fmts[i]) + break; + } + + /* If not found, use YUYV8_2x8 as default */ + if (i >= ARRAY_SIZE(isif_fmts)) + fmt->format.code = V4L2_MBUS_FMT_YUYV8_2X8; + + /* Clamp the size. */ + fmt->format.width = clamp_t(u32, width, 32, MAX_WIDTH); + fmt->format.height = clamp_t(u32, height, 32, MAX_HEIGHT); + + /* The data formatter truncates the number of horizontal output + * pixels to a multiple of 16. To avoid clipping data, allow + * callers to request an output size bigger than the input size + * up to the nearest multiple of 16. + */ + if (fmt->pad == ISIF_PAD_SOURCE) + fmt->format.width &= ~15; +} + +/* + * vpfe_isif_buffer_isr() - isif module non-progressive buffer scheduling isr + * @isif: Pointer to isif subdevice. + */ +void vpfe_isif_buffer_isr(struct vpfe_isif_device *isif) +{ + struct vpfe_device *vpfe_dev = to_vpfe_device(isif); + struct vpfe_video_device *video = &isif->video_out; + enum v4l2_field field; + int fid; + + if (!video->started) + return; + + field = video->fmt.fmt.pix.field; + + if (field == V4L2_FIELD_NONE) { + /* handle progressive frame capture */ + if (video->cur_frm != video->next_frm) + vpfe_video_process_buffer_complete(video); + return; + } + + /* interlaced or TB capture check which field we + * are in hardware + */ + fid = vpfe_isif_get_fid(vpfe_dev); + + /* switch the software maintained field id */ + video->field_id ^= 1; + if (fid == video->field_id) { + /* we are in-sync here,continue */ + if (fid == 0) { + /* + * One frame is just being captured. If the + * next frame is available, release the current + * frame and move on + */ + if (video->cur_frm != video->next_frm) + vpfe_video_process_buffer_complete(video); + /* + * based on whether the two fields are stored + * interleavely or separately in memory, + * reconfigure the ISIF memory address + */ + if (field == V4L2_FIELD_SEQ_TB) + vpfe_video_schedule_bottom_field(video); + return; + } + /* + * if one field is just being captured configure + * the next frame get the next frame from the + * empty queue if no frame is available hold on + * to the current buffer + */ + spin_lock(&video->dma_queue_lock); + if (!list_empty(&video->dma_queue) && + video->cur_frm == video->next_frm) + vpfe_video_schedule_next_buffer(video); + spin_unlock(&video->dma_queue_lock); + } else if (fid == 0) { + /* + * out of sync. Recover from any hardware out-of-sync. + * May loose one frame + */ + video->field_id = fid; + } +} + +/* + * vpfe_isif_vidint1_isr() - ISIF module progressive buffer scheduling isr + * @isif: Pointer to isif subdevice. + */ +void vpfe_isif_vidint1_isr(struct vpfe_isif_device *isif) +{ + struct vpfe_video_device *video = &isif->video_out; + + if (!video->started) + return; + + spin_lock(&video->dma_queue_lock); + if (video->fmt.fmt.pix.field == V4L2_FIELD_NONE && + !list_empty(&video->dma_queue) && video->cur_frm == video->next_frm) + vpfe_video_schedule_next_buffer(video); + + spin_unlock(&video->dma_queue_lock); +} + +/* + * VPFE video operations + */ + +static int isif_video_queue(struct vpfe_device *vpfe_dev, unsigned long addr) +{ + struct vpfe_isif_device *isif = &vpfe_dev->vpfe_isif; + + isif_write(isif->isif_cfg.base_addr, (addr >> 21) & + ISIF_CADU_BITS, CADU); + isif_write(isif->isif_cfg.base_addr, (addr >> 5) & + ISIF_CADL_BITS, CADL); + + return 0; +} + +static const struct vpfe_video_operations isif_video_ops = { + .queue = isif_video_queue, +}; + +/* + * V4L2 subdev operations + */ + +/* Parameter operations */ +static int isif_get_params(struct v4l2_subdev *sd, void *params) +{ + struct vpfe_isif_device *isif = v4l2_get_subdevdata(sd); + + /* only raw module parameters can be set through the IOCTL */ + if (isif->formats[ISIF_PAD_SINK].code != V4L2_MBUS_FMT_SGRBG12_1X12) + return -EINVAL; + memcpy(params, &isif->isif_cfg.bayer.config_params, + sizeof(isif->isif_cfg.bayer.config_params)); + return 0; +} + +static int isif_validate_df_csc_params(struct vpfe_isif_df_csc *df_csc) +{ + struct vpfe_isif_color_space_conv *csc; + int err = -EINVAL; + int csc_df_en; + int i; + + if (!df_csc->df_or_csc) { + /* csc configuration */ + csc = &df_csc->csc; + if (csc->en) { + csc_df_en = 1; + for (i = 0; i < VPFE_ISIF_CSC_NUM_COEFF; i++) + if (csc->coeff[i].integer > + ISIF_CSC_COEF_INTEG_MASK || + csc->coeff[i].decimal > + ISIF_CSC_COEF_DECIMAL_MASK) { + pr_err("Invalid CSC coefficients\n"); + return err; + } + } + } + if (df_csc->start_pix > ISIF_DF_CSC_SPH_MASK) { + pr_err("Invalid df_csc start pix value\n"); + return err; + } + + if (df_csc->num_pixels > ISIF_DF_NUMPIX) { + pr_err("Invalid df_csc num pixels value\n"); + return err; + } + + if (df_csc->start_line > ISIF_DF_CSC_LNH_MASK) { + pr_err("Invalid df_csc start_line value\n"); + return err; + } + + if (df_csc->num_lines > ISIF_DF_NUMLINES) { + pr_err("Invalid df_csc num_lines value\n"); + return err; + } + + return 0; +} + +#define DM365_ISIF_MAX_VDFLSFT 4 +#define DM365_ISIF_MAX_VDFSLV 4095 +#define DM365_ISIF_MAX_DFCMEM0 0x1fff +#define DM365_ISIF_MAX_DFCMEM1 0x1fff + +static int isif_validate_dfc_params(struct vpfe_isif_dfc *dfc) +{ + int err = -EINVAL; + int i; + + if (!dfc->en) + return 0; + + if (dfc->corr_whole_line > 1) { + pr_err("Invalid corr_whole_line value\n"); + return err; + } + + if (dfc->def_level_shift > DM365_ISIF_MAX_VDFLSFT) { + pr_err("Invalid def_level_shift value\n"); + return err; + } + + if (dfc->def_sat_level > DM365_ISIF_MAX_VDFSLV) { + pr_err("Invalid def_sat_level value\n"); + return err; + } + + if (!dfc->num_vdefects || + dfc->num_vdefects > VPFE_ISIF_VDFC_TABLE_SIZE) { + pr_err("Invalid num_vdefects value\n"); + return err; + } + + for (i = 0; i < VPFE_ISIF_VDFC_TABLE_SIZE; i++) { + if (dfc->table[i].pos_vert > DM365_ISIF_MAX_DFCMEM0) { + pr_err("Invalid pos_vert value\n"); + return err; + } + if (dfc->table[i].pos_horz > DM365_ISIF_MAX_DFCMEM1) { + pr_err("Invalid pos_horz value\n"); + return err; + } + } + + return 0; +} + +#define DM365_ISIF_MAX_CLVRV 0xfff +#define DM365_ISIF_MAX_CLDC 0x1fff +#define DM365_ISIF_MAX_CLHSH 0x1fff +#define DM365_ISIF_MAX_CLHSV 0x1fff +#define DM365_ISIF_MAX_CLVSH 0x1fff +#define DM365_ISIF_MAX_CLVSV 0x1fff +#define DM365_ISIF_MAX_HEIGHT_BLACK_REGION 0x1fff + +static int isif_validate_bclamp_params(struct vpfe_isif_black_clamp *bclamp) +{ + int err = -EINVAL; + + if (bclamp->dc_offset > DM365_ISIF_MAX_CLDC) { + pr_err("Invalid bclamp dc_offset value\n"); + return err; + } + if (!bclamp->en) + return 0; + if (bclamp->horz.clamp_pix_limit > 1) { + pr_err("Invalid bclamp horz clamp_pix_limit value\n"); + return err; + } + if (bclamp->horz.win_count_calc < 1 || + bclamp->horz.win_count_calc > 32) { + pr_err("Invalid bclamp horz win_count_calc value\n"); + return err; + } + if (bclamp->horz.win_start_h_calc > DM365_ISIF_MAX_CLHSH) { + pr_err("Invalid bclamp win_start_v_calc value\n"); + return err; + } + + if (bclamp->horz.win_start_v_calc > DM365_ISIF_MAX_CLHSV) { + pr_err("Invalid bclamp win_start_v_calc value\n"); + return err; + } + if (bclamp->vert.reset_clamp_val > DM365_ISIF_MAX_CLVRV) { + pr_err("Invalid bclamp reset_clamp_val value\n"); + return err; + } + if (bclamp->vert.ob_v_sz_calc > DM365_ISIF_MAX_HEIGHT_BLACK_REGION) { + pr_err("Invalid bclamp ob_v_sz_calc value\n"); + return err; + } + if (bclamp->vert.ob_start_h > DM365_ISIF_MAX_CLVSH) { + pr_err("Invalid bclamp ob_start_h value\n"); + return err; + } + if (bclamp->vert.ob_start_v > DM365_ISIF_MAX_CLVSV) { + pr_err("Invalid bclamp ob_start_h value\n"); + return err; + } + return 0; +} + +static int +isif_validate_raw_params(struct vpfe_isif_raw_config *params) +{ + int ret; + + ret = isif_validate_df_csc_params(¶ms->df_csc); + if (ret) + return ret; + ret = isif_validate_dfc_params(¶ms->dfc); + if (ret) + return ret; + ret = isif_validate_bclamp_params(¶ms->bclamp); + return ret; +} + +static int isif_set_params(struct v4l2_subdev *sd, void *params) +{ + struct vpfe_isif_device *isif = v4l2_get_subdevdata(sd); + struct vpfe_isif_raw_config isif_raw_params; + int ret = -EINVAL; + + /* only raw module parameters can be set through the IOCTL */ + if (isif->formats[ISIF_PAD_SINK].code != V4L2_MBUS_FMT_SGRBG12_1X12) + return ret; + + memcpy(&isif_raw_params, params, sizeof(isif_raw_params)); + if (!isif_validate_raw_params(&isif_raw_params)) { + memcpy(&isif->isif_cfg.bayer.config_params, &isif_raw_params, + sizeof(isif_raw_params)); + ret = 0; + } + return ret; +} +/* + * isif_ioctl() - isif module private ioctl's + * @sd: VPFE isif V4L2 subdevice + * @cmd: ioctl command + * @arg: ioctl argument + * + * Return 0 on success or a negative error code otherwise. + */ +static long isif_ioctl(struct v4l2_subdev *sd, unsigned int cmd, void *arg) +{ + int ret; + + switch (cmd) { + case VIDIOC_VPFE_ISIF_S_RAW_PARAMS: + ret = isif_set_params(sd, arg); + break; + + case VIDIOC_VPFE_ISIF_G_RAW_PARAMS: + ret = isif_get_params(sd, arg); + break; + + default: + ret = -ENOIOCTLCMD; + } + return ret; +} + +static void isif_config_gain_offset(struct vpfe_isif_device *isif) +{ + struct vpfe_isif_gain_offsets_adj *gain_off_ptr = + &isif->isif_cfg.bayer.config_params.gain_offset; + void *__iomem base = isif->isif_cfg.base_addr; + u32 val; + + val = ((gain_off_ptr->gain_sdram_en & 1) << GAIN_SDRAM_EN_SHIFT) | + ((gain_off_ptr->gain_ipipe_en & 1) << GAIN_IPIPE_EN_SHIFT) | + ((gain_off_ptr->gain_h3a_en & 1) << GAIN_H3A_EN_SHIFT) | + ((gain_off_ptr->offset_sdram_en & 1) << OFST_SDRAM_EN_SHIFT) | + ((gain_off_ptr->offset_ipipe_en & 1) << OFST_IPIPE_EN_SHIFT) | + ((gain_off_ptr->offset_h3a_en & 1) << OFST_H3A_EN_SHIFT); + isif_merge(base, GAIN_OFFSET_EN_MASK, val, CGAMMAWD); + + isif_write(base, isif->isif_cfg.isif_gain_params.cr_gain, CRGAIN); + isif_write(base, isif->isif_cfg.isif_gain_params.cgr_gain, CGRGAIN); + isif_write(base, isif->isif_cfg.isif_gain_params.cgb_gain, CGBGAIN); + isif_write(base, isif->isif_cfg.isif_gain_params.cb_gain, CBGAIN); + isif_write(base, isif->isif_cfg.isif_gain_params.offset & OFFSET_MASK, + COFSTA); + +} + +static void isif_config_bclamp(struct vpfe_isif_device *isif, + struct vpfe_isif_black_clamp *bc) +{ + u32 val; + + /** + * DC Offset is always added to image data irrespective of bc enable + * status + */ + val = bc->dc_offset & ISIF_BC_DCOFFSET_MASK; + isif_write(isif->isif_cfg.base_addr, val, CLDCOFST); + + if (!bc->en) + return; + + val = (bc->bc_mode_color & ISIF_BC_MODE_COLOR_MASK) << + ISIF_BC_MODE_COLOR_SHIFT; + + /* Enable BC and horizontal clamp calculation paramaters */ + val = val | 1 | ((bc->horz.mode & ISIF_HORZ_BC_MODE_MASK) << + ISIF_HORZ_BC_MODE_SHIFT); + + isif_write(isif->isif_cfg.base_addr, val, CLAMPCFG); + + if (bc->horz.mode != VPFE_ISIF_HORZ_BC_DISABLE) { + /* + * Window count for calculation + * Base window selection + * pixel limit + * Horizontal size of window + * vertical size of the window + * Horizontal start position of the window + * Vertical start position of the window + */ + val = (bc->horz.win_count_calc & ISIF_HORZ_BC_WIN_COUNT_MASK) | + ((bc->horz.base_win_sel_calc & 1) << + ISIF_HORZ_BC_WIN_SEL_SHIFT) | + ((bc->horz.clamp_pix_limit & 1) << + ISIF_HORZ_BC_PIX_LIMIT_SHIFT) | + ((bc->horz.win_h_sz_calc & + ISIF_HORZ_BC_WIN_H_SIZE_MASK) << + ISIF_HORZ_BC_WIN_H_SIZE_SHIFT) | + ((bc->horz.win_v_sz_calc & + ISIF_HORZ_BC_WIN_V_SIZE_MASK) << + ISIF_HORZ_BC_WIN_V_SIZE_SHIFT); + + isif_write(isif->isif_cfg.base_addr, val, CLHWIN0); + + val = bc->horz.win_start_h_calc & ISIF_HORZ_BC_WIN_START_H_MASK; + isif_write(isif->isif_cfg.base_addr, val, CLHWIN1); + + val = bc->horz.win_start_v_calc & ISIF_HORZ_BC_WIN_START_V_MASK; + isif_write(isif->isif_cfg.base_addr, val, CLHWIN2); + } + + /* vertical clamp calculation paramaters */ + /* OB H Valid */ + val = bc->vert.ob_h_sz_calc & ISIF_VERT_BC_OB_H_SZ_MASK; + + /* Reset clamp value sel for previous line */ + val |= (bc->vert.reset_val_sel & ISIF_VERT_BC_RST_VAL_SEL_MASK) << + ISIF_VERT_BC_RST_VAL_SEL_SHIFT; + + /* Line average coefficient */ + val |= bc->vert.line_ave_coef << ISIF_VERT_BC_LINE_AVE_COEF_SHIFT; + isif_write(isif->isif_cfg.base_addr, val, CLVWIN0); + + /* Configured reset value */ + if (bc->vert.reset_val_sel == VPFE_ISIF_VERT_BC_USE_CONFIG_CLAMP_VAL) { + val = bc->vert.reset_clamp_val & ISIF_VERT_BC_RST_VAL_MASK; + isif_write(isif->isif_cfg.base_addr, val, CLVRV); + } + + /* Optical Black horizontal start position */ + val = bc->vert.ob_start_h & ISIF_VERT_BC_OB_START_HORZ_MASK; + isif_write(isif->isif_cfg.base_addr, val, CLVWIN1); + + /* Optical Black vertical start position */ + val = bc->vert.ob_start_v & ISIF_VERT_BC_OB_START_VERT_MASK; + isif_write(isif->isif_cfg.base_addr, val, CLVWIN2); + + val = bc->vert.ob_v_sz_calc & ISIF_VERT_BC_OB_VERT_SZ_MASK; + isif_write(isif->isif_cfg.base_addr, val, CLVWIN3); + + /* Vertical start position for BC subtraction */ + val = bc->vert_start_sub & ISIF_BC_VERT_START_SUB_V_MASK; + isif_write(isif->isif_cfg.base_addr, val, CLSV); +} + +/* This function will configure the window size to be capture in ISIF reg */ +static void +isif_setwin(struct vpfe_isif_device *isif, struct v4l2_rect *image_win, + enum isif_frmfmt frm_fmt, int ppc, int mode) +{ + int horz_nr_pixels; + int vert_nr_lines; + int horz_start; + int vert_start; + int mid_img; + + /* + * ppc - per pixel count. indicates how many pixels per cell + * output to SDRAM. example, for ycbcr, it is one y and one c, so 2. + * raw capture this is 1 + */ + horz_start = image_win->left << (ppc - 1); + horz_nr_pixels = (image_win->width << (ppc - 1)) - 1; + + /* Writing the horizontal info into the registers */ + isif_write(isif->isif_cfg.base_addr, + horz_start & START_PX_HOR_MASK, SPH); + isif_write(isif->isif_cfg.base_addr, + horz_nr_pixels & NUM_PX_HOR_MASK, LNH); + vert_start = image_win->top; + + if (frm_fmt == ISIF_FRMFMT_INTERLACED) { + vert_nr_lines = (image_win->height >> 1) - 1; + vert_start >>= 1; + /* To account for VD since line 0 doesn't have any data */ + vert_start += 1; + } else { + /* To account for VD since line 0 doesn't have any data */ + vert_start += 1; + vert_nr_lines = image_win->height - 1; + /* configure VDINT0 and VDINT1 */ + mid_img = vert_start + (image_win->height / 2); + isif_write(isif->isif_cfg.base_addr, mid_img, VDINT1); + } + + if (!mode) + isif_write(isif->isif_cfg.base_addr, 0, VDINT0); + else + isif_write(isif->isif_cfg.base_addr, vert_nr_lines, VDINT0); + isif_write(isif->isif_cfg.base_addr, + vert_start & START_VER_ONE_MASK, SLV0); + isif_write(isif->isif_cfg.base_addr, + vert_start & START_VER_TWO_MASK, SLV1); + isif_write(isif->isif_cfg.base_addr, + vert_nr_lines & NUM_LINES_VER, LNV); +} + +#define DM365_ISIF_DFCMWR_MEMORY_WRITE 1 +#define DM365_ISIF_DFCMRD_MEMORY_READ 0x2 + +static void +isif_config_dfc(struct vpfe_isif_device *isif, struct vpfe_isif_dfc *vdfc) +{ +#define DFC_WRITE_WAIT_COUNT 1000 + u32 count = DFC_WRITE_WAIT_COUNT; + u32 val; + int i; + + if (!vdfc->en) + return; + + /* Correction mode */ + val = (vdfc->corr_mode & ISIF_VDFC_CORR_MOD_MASK) << + ISIF_VDFC_CORR_MOD_SHIFT; + + /* Correct whole line or partial */ + if (vdfc->corr_whole_line) + val |= 1 << ISIF_VDFC_CORR_WHOLE_LN_SHIFT; + + /* level shift value */ + val |= (vdfc->def_level_shift & ISIF_VDFC_LEVEL_SHFT_MASK) << + ISIF_VDFC_LEVEL_SHFT_SHIFT; + + isif_write(isif->isif_cfg.base_addr, val, DFCCTL); + + /* Defect saturation level */ + val = vdfc->def_sat_level & ISIF_VDFC_SAT_LEVEL_MASK; + isif_write(isif->isif_cfg.base_addr, val, VDFSATLV); + + isif_write(isif->isif_cfg.base_addr, vdfc->table[0].pos_vert & + ISIF_VDFC_POS_MASK, DFCMEM0); + isif_write(isif->isif_cfg.base_addr, vdfc->table[0].pos_horz & + ISIF_VDFC_POS_MASK, DFCMEM1); + if (vdfc->corr_mode == VPFE_ISIF_VDFC_NORMAL || + vdfc->corr_mode == VPFE_ISIF_VDFC_HORZ_INTERPOL_IF_SAT) { + isif_write(isif->isif_cfg.base_addr, + vdfc->table[0].level_at_pos, DFCMEM2); + isif_write(isif->isif_cfg.base_addr, + vdfc->table[0].level_up_pixels, DFCMEM3); + isif_write(isif->isif_cfg.base_addr, + vdfc->table[0].level_low_pixels, DFCMEM4); + } + + val = isif_read(isif->isif_cfg.base_addr, DFCMEMCTL); + /* set DFCMARST and set DFCMWR */ + val |= 1 << ISIF_DFCMEMCTL_DFCMARST_SHIFT; + val |= 1; + isif_write(isif->isif_cfg.base_addr, val, DFCMEMCTL); + + while (count && (isif_read(isif->isif_cfg.base_addr, DFCMEMCTL) & 0x01)) + count--; + + val = isif_read(isif->isif_cfg.base_addr, DFCMEMCTL); + if (!count) { + pr_debug("defect table write timeout !!\n"); + return; + } + + for (i = 1; i < vdfc->num_vdefects; i++) { + isif_write(isif->isif_cfg.base_addr, vdfc->table[i].pos_vert & + ISIF_VDFC_POS_MASK, DFCMEM0); + + isif_write(isif->isif_cfg.base_addr, vdfc->table[i].pos_horz & + ISIF_VDFC_POS_MASK, DFCMEM1); + + if (vdfc->corr_mode == VPFE_ISIF_VDFC_NORMAL || + vdfc->corr_mode == VPFE_ISIF_VDFC_HORZ_INTERPOL_IF_SAT) { + isif_write(isif->isif_cfg.base_addr, + vdfc->table[i].level_at_pos, DFCMEM2); + isif_write(isif->isif_cfg.base_addr, + vdfc->table[i].level_up_pixels, DFCMEM3); + isif_write(isif->isif_cfg.base_addr, + vdfc->table[i].level_low_pixels, DFCMEM4); + } + val = isif_read(isif->isif_cfg.base_addr, DFCMEMCTL); + /* clear DFCMARST and set DFCMWR */ + val &= ~(1 << ISIF_DFCMEMCTL_DFCMARST_SHIFT); + val |= 1; + isif_write(isif->isif_cfg.base_addr, val, DFCMEMCTL); + + count = DFC_WRITE_WAIT_COUNT; + while (count && (isif_read(isif->isif_cfg.base_addr, + DFCMEMCTL) & 0x01)) + count--; + + val = isif_read(isif->isif_cfg.base_addr, DFCMEMCTL); + if (!count) { + pr_debug("defect table write timeout !!\n"); + return; + } + } + if (vdfc->num_vdefects < VPFE_ISIF_VDFC_TABLE_SIZE) { + /* Extra cycle needed */ + isif_write(isif->isif_cfg.base_addr, 0, DFCMEM0); + isif_write(isif->isif_cfg.base_addr, + DM365_ISIF_MAX_DFCMEM1, DFCMEM1); + isif_write(isif->isif_cfg.base_addr, + DM365_ISIF_DFCMWR_MEMORY_WRITE, DFCMEMCTL); + } + /* enable VDFC */ + isif_merge(isif->isif_cfg.base_addr, (1 << ISIF_VDFC_EN_SHIFT), + (1 << ISIF_VDFC_EN_SHIFT), DFCCTL); + + isif_merge(isif->isif_cfg.base_addr, (1 << ISIF_VDFC_EN_SHIFT), + (0 << ISIF_VDFC_EN_SHIFT), DFCCTL); + + isif_write(isif->isif_cfg.base_addr, 0x6, DFCMEMCTL); + for (i = 0; i < vdfc->num_vdefects; i++) { + count = DFC_WRITE_WAIT_COUNT; + while (count && + (isif_read(isif->isif_cfg.base_addr, DFCMEMCTL) & 0x2)) + count--; + val = isif_read(isif->isif_cfg.base_addr, DFCMEMCTL); + if (!count) { + pr_debug("defect table write timeout !!\n"); + return; + } + isif_write(isif->isif_cfg.base_addr, + DM365_ISIF_DFCMRD_MEMORY_READ, DFCMEMCTL); + } +} + +static void +isif_config_csc(struct vpfe_isif_device *isif, struct vpfe_isif_df_csc *df_csc) +{ + u32 val1; + u32 val2; + u32 i; + + if (!df_csc->csc.en) { + isif_write(isif->isif_cfg.base_addr, 0, CSCCTL); + return; + } + /* initialize all bits to 0 */ + val1 = 0; + for (i = 0; i < VPFE_ISIF_CSC_NUM_COEFF; i++) { + if ((i % 2) == 0) { + /* CSCM - LSB */ + val1 = ((df_csc->csc.coeff[i].integer & + ISIF_CSC_COEF_INTEG_MASK) << + ISIF_CSC_COEF_INTEG_SHIFT) | + ((df_csc->csc.coeff[i].decimal & + ISIF_CSC_COEF_DECIMAL_MASK)); + } else { + + /* CSCM - MSB */ + val2 = ((df_csc->csc.coeff[i].integer & + ISIF_CSC_COEF_INTEG_MASK) << + ISIF_CSC_COEF_INTEG_SHIFT) | + ((df_csc->csc.coeff[i].decimal & + ISIF_CSC_COEF_DECIMAL_MASK)); + val2 <<= ISIF_CSCM_MSB_SHIFT; + val2 |= val1; + isif_write(isif->isif_cfg.base_addr, val2, + (CSCM0 + ((i-1) << 1))); + } + } + /* program the active area */ + isif_write(isif->isif_cfg.base_addr, df_csc->start_pix & + ISIF_DF_CSC_SPH_MASK, FMTSPH); + /* + * one extra pixel as required for CSC. Actually number of + * pixel - 1 should be configured in this register. So we + * need to subtract 1 before writing to FMTSPH, but we will + * not do this since csc requires one extra pixel + */ + isif_write(isif->isif_cfg.base_addr, df_csc->num_pixels & + ISIF_DF_CSC_SPH_MASK, FMTLNH); + isif_write(isif->isif_cfg.base_addr, df_csc->start_line & + ISIF_DF_CSC_SPH_MASK, FMTSLV); + /* + * one extra line as required for CSC. See reason documented for + * num_pixels + */ + isif_write(isif->isif_cfg.base_addr, df_csc->num_lines & + ISIF_DF_CSC_SPH_MASK, FMTLNV); + /* Enable CSC */ + isif_write(isif->isif_cfg.base_addr, 1, CSCCTL); +} + +static void +isif_config_linearization(struct vpfe_isif_device *isif, + struct vpfe_isif_linearize *linearize) +{ + u32 val; + u32 i; + + if (!linearize->en) { + isif_write(isif->isif_cfg.base_addr, 0, LINCFG0); + return; + } + /* shift value for correction */ + val = (linearize->corr_shft & ISIF_LIN_CORRSFT_MASK) << + ISIF_LIN_CORRSFT_SHIFT; + /* enable */ + val |= 1; + isif_write(isif->isif_cfg.base_addr, val, LINCFG0); + /* Scale factor */ + val = (linearize->scale_fact.integer & 1) << + ISIF_LIN_SCALE_FACT_INTEG_SHIFT; + val |= linearize->scale_fact.decimal & ISIF_LIN_SCALE_FACT_DECIMAL_MASK; + isif_write(isif->isif_cfg.base_addr, val, LINCFG1); + + for (i = 0; i < VPFE_ISIF_LINEAR_TAB_SIZE; i++) { + val = linearize->table[i] & ISIF_LIN_ENTRY_MASK; + if (i%2) + isif_regw_lin_tbl(isif, val, ((i >> 1) << 2), 1); + else + isif_regw_lin_tbl(isif, val, ((i >> 1) << 2), 0); + } +} + +static void +isif_config_culling(struct vpfe_isif_device *isif, struct vpfe_isif_cul *cul) +{ + u32 val; + + /* Horizontal pattern */ + val = cul->hcpat_even << CULL_PAT_EVEN_LINE_SHIFT; + val |= cul->hcpat_odd; + isif_write(isif->isif_cfg.base_addr, val, CULH); + /* vertical pattern */ + isif_write(isif->isif_cfg.base_addr, cul->vcpat, CULV); + /* LPF */ + isif_merge(isif->isif_cfg.base_addr, ISIF_LPF_MASK << ISIF_LPF_SHIFT, + cul->en_lpf << ISIF_LPF_SHIFT, MODESET); +} + +static int isif_get_pix_fmt(u32 mbus_code) +{ + switch (mbus_code) { + case V4L2_MBUS_FMT_SGRBG10_ALAW8_1X8: + case V4L2_MBUS_FMT_SGRBG10_DPCM8_1X8: + case V4L2_MBUS_FMT_SGRBG12_1X12: + return ISIF_PIXFMT_RAW; + + case V4L2_MBUS_FMT_YUYV8_2X8: + case V4L2_MBUS_FMT_UYVY8_2X8: + case V4L2_MBUS_FMT_YUYV10_2X10: + case V4L2_MBUS_FMT_Y8_1X8: + return ISIF_PIXFMT_YCBCR_8BIT; + + case V4L2_MBUS_FMT_YUYV8_1X16: + case V4L2_MBUS_FMT_YUYV10_1X20: + return ISIF_PIXFMT_YCBCR_16BIT; + + default: + break; + } + return -EINVAL; +} + +#define ISIF_INTERLACE_INVERSE_MODE 0x4b6d +#define ISIF_INTERLACE_NON_INVERSE_MODE 0x0b6d +#define ISIF_PROGRESSIVE_INVERSE_MODE 0x4000 +#define ISIF_PROGRESSIVE_NON_INVERSE_MODE 0x0000 + +static int isif_config_raw(struct v4l2_subdev *sd, int mode) +{ + struct vpfe_isif_device *isif = v4l2_get_subdevdata(sd); + struct isif_params_raw *params = &isif->isif_cfg.bayer; + struct vpfe_isif_raw_config *module_params = + &isif->isif_cfg.bayer.config_params; + struct v4l2_mbus_framefmt *format; + int pix_fmt; + u32 val; + + format = &isif->formats[ISIF_PAD_SINK]; + + /* In case of user has set BT656IF earlier, it should be reset + * when configuring for raw input. + */ + isif_write(isif->isif_cfg.base_addr, 0, REC656IF); + /* Configure CCDCFG register + * Set CCD Not to swap input since input is RAW data + * Set FID detection function to Latch at V-Sync + * Set WENLOG - isif valid area + * Set TRGSEL + * Set EXTRG + * Packed to 8 or 16 bits + */ + val = ISIF_YCINSWP_RAW | ISIF_CCDCFG_FIDMD_LATCH_VSYNC | + ISIF_CCDCFG_WENLOG_AND | ISIF_CCDCFG_TRGSEL_WEN | + ISIF_CCDCFG_EXTRG_DISABLE | (isif->isif_cfg.data_pack & + ISIF_DATA_PACK_MASK); + isif_write(isif->isif_cfg.base_addr, val, CCDCFG); + + pix_fmt = isif_get_pix_fmt(format->code); + if (pix_fmt < 0) { + pr_debug("Invalid pix_fmt(input mode)\n"); + return -EINVAL; + } + /* + * Configure the vertical sync polarity(MODESET.VDPOL) + * Configure the horizontal sync polarity (MODESET.HDPOL) + * Configure frame id polarity (MODESET.FLDPOL) + * Configure data polarity + * Configure External WEN Selection + * Configure frame format(progressive or interlace) + * Configure pixel format (Input mode) + * Configure the data shift + */ + val = ISIF_VDHDOUT_INPUT | ((params->vd_pol & ISIF_VD_POL_MASK) << + ISIF_VD_POL_SHIFT) | ((params->hd_pol & ISIF_HD_POL_MASK) << + ISIF_HD_POL_SHIFT) | ((params->fid_pol & ISIF_FID_POL_MASK) << + ISIF_FID_POL_SHIFT) | ((ISIF_DATAPOL_NORMAL & + ISIF_DATAPOL_MASK) << ISIF_DATAPOL_SHIFT) | ((ISIF_EXWEN_DISABLE & + ISIF_EXWEN_MASK) << ISIF_EXWEN_SHIFT) | ((params->frm_fmt & + ISIF_FRM_FMT_MASK) << ISIF_FRM_FMT_SHIFT) | ((pix_fmt & + ISIF_INPUT_MASK) << ISIF_INPUT_SHIFT); + + /* currently only V4L2_MBUS_FMT_SGRBG12_1X12 is + * supported. shift appropriately depending on + * different MBUS fmt's added + */ + if (format->code == V4L2_MBUS_FMT_SGRBG12_1X12) + val |= ((VPFE_ISIF_NO_SHIFT & + ISIF_DATASFT_MASK) << ISIF_DATASFT_SHIFT); + + isif_write(isif->isif_cfg.base_addr, val, MODESET); + /* + * Configure GAMMAWD register + * CFA pattern setting + */ + val = (params->cfa_pat & ISIF_GAMMAWD_CFA_MASK) << + ISIF_GAMMAWD_CFA_SHIFT; + /* Gamma msb */ + if (params->v4l2_pix_fmt == V4L2_PIX_FMT_SGRBG10ALAW8) + val = val | ISIF_ALAW_ENABLE; + + val = val | ((params->data_msb & ISIF_ALAW_GAMA_WD_MASK) << + ISIF_ALAW_GAMA_WD_SHIFT); + + isif_write(isif->isif_cfg.base_addr, val, CGAMMAWD); + /* Configure DPCM compression settings */ + if (params->v4l2_pix_fmt == V4L2_PIX_FMT_SGRBG10DPCM8) { + val = 1 << ISIF_DPCM_EN_SHIFT; + val |= (params->dpcm_predictor & + ISIF_DPCM_PREDICTOR_MASK) << ISIF_DPCM_PREDICTOR_SHIFT; + } + isif_write(isif->isif_cfg.base_addr, val, MISC); + /* Configure Gain & Offset */ + isif_config_gain_offset(isif); + /* Configure Color pattern */ + if (format->code == V4L2_MBUS_FMT_SGRBG12_1X12) + val = isif_sgrbg_pattern; + else + /* default set to rggb */ + val = isif_srggb_pattern; + + isif_write(isif->isif_cfg.base_addr, val, CCOLP); + + /* Configure HSIZE register */ + val = (params->horz_flip_en & ISIF_HSIZE_FLIP_MASK) << + ISIF_HSIZE_FLIP_SHIFT; + + /* calculate line offset in 32 bytes based on pack value */ + if (isif->isif_cfg.data_pack == ISIF_PACK_8BIT) + val |= ((params->win.width + 31) >> 5) & ISIF_LINEOFST_MASK; + else if (isif->isif_cfg.data_pack == ISIF_PACK_12BIT) + val |= ((((params->win.width + (params->win.width >> 2)) + + 31) >> 5) & ISIF_LINEOFST_MASK); + else + val |= (((params->win.width * 2) + 31) >> 5) & + ISIF_LINEOFST_MASK; + isif_write(isif->isif_cfg.base_addr, val, HSIZE); + /* Configure SDOFST register */ + if (params->frm_fmt == ISIF_FRMFMT_INTERLACED) { + if (params->image_invert_en) + /* For interlace inverse mode */ + isif_write(isif->isif_cfg.base_addr, + ISIF_INTERLACE_INVERSE_MODE, SDOFST); + else + /* For interlace non inverse mode */ + isif_write(isif->isif_cfg.base_addr, + ISIF_INTERLACE_NON_INVERSE_MODE, SDOFST); + } else if (params->frm_fmt == ISIF_FRMFMT_PROGRESSIVE) { + if (params->image_invert_en) + isif_write(isif->isif_cfg.base_addr, + ISIF_PROGRESSIVE_INVERSE_MODE, SDOFST); + else + /* For progessive non inverse mode */ + isif_write(isif->isif_cfg.base_addr, + ISIF_PROGRESSIVE_NON_INVERSE_MODE, SDOFST); + } + /* Configure video window */ + isif_setwin(isif, ¶ms->win, params->frm_fmt, 1, mode); + /* Configure Black Clamp */ + isif_config_bclamp(isif, &module_params->bclamp); + /* Configure Vertical Defection Pixel Correction */ + isif_config_dfc(isif, &module_params->dfc); + if (!module_params->df_csc.df_or_csc) + /* Configure Color Space Conversion */ + isif_config_csc(isif, &module_params->df_csc); + + isif_config_linearization(isif, &module_params->linearize); + /* Configure Culling */ + isif_config_culling(isif, &module_params->culling); + /* Configure Horizontal and vertical offsets(DFC,LSC,Gain) */ + val = module_params->horz_offset & ISIF_DATA_H_OFFSET_MASK; + isif_write(isif->isif_cfg.base_addr, val, DATAHOFST); + + val = module_params->vert_offset & ISIF_DATA_V_OFFSET_MASK; + isif_write(isif->isif_cfg.base_addr, val, DATAVOFST); + + return 0; +} + +#define DM365_ISIF_HSIZE_MASK 0xffffffe0 +#define DM365_ISIF_SDOFST_2_LINES 0x00000249 + +/* This function will configure ISIF for YCbCr parameters. */ +static int isif_config_ycbcr(struct v4l2_subdev *sd, int mode) +{ + struct vpfe_isif_device *isif = v4l2_get_subdevdata(sd); + struct isif_ycbcr_config *params = &isif->isif_cfg.ycbcr; + struct v4l2_mbus_framefmt *format; + int pix_fmt; + u32 modeset; + u32 ccdcfg; + + format = &isif->formats[ISIF_PAD_SINK]; + /* + * first reset the ISIF + * all registers have default values after reset + * This is important since we assume default values to be set in + * a lot of registers that we didn't touch + */ + /* start with all bits zero */ + ccdcfg = modeset = 0; + pix_fmt = isif_get_pix_fmt(format->code); + if (pix_fmt < 0) { + pr_debug("Invalid pix_fmt(input mode)\n"); + return -EINVAL; + } + /* configure pixel format or input mode */ + modeset = modeset | ((pix_fmt & ISIF_INPUT_MASK) << + ISIF_INPUT_SHIFT) | ((params->frm_fmt & ISIF_FRM_FMT_MASK) << + ISIF_FRM_FMT_SHIFT) | (((params->fid_pol & + ISIF_FID_POL_MASK) << ISIF_FID_POL_SHIFT)) | + (((params->hd_pol & ISIF_HD_POL_MASK) << ISIF_HD_POL_SHIFT)) | + (((params->vd_pol & ISIF_VD_POL_MASK) << ISIF_VD_POL_SHIFT)); + /* pack the data to 8-bit CCDCCFG */ + switch (format->code) { + case V4L2_MBUS_FMT_YUYV8_2X8: + case V4L2_MBUS_FMT_UYVY8_2X8: + if (pix_fmt != ISIF_PIXFMT_YCBCR_8BIT) { + pr_debug("Invalid pix_fmt(input mode)\n"); + return -EINVAL; + } + modeset |= ((VPFE_PINPOL_NEGATIVE & ISIF_VD_POL_MASK) << + ISIF_VD_POL_SHIFT); + isif_write(isif->isif_cfg.base_addr, 3, REC656IF); + ccdcfg = ccdcfg | ISIF_PACK_8BIT | ISIF_YCINSWP_YCBCR; + break; + + case V4L2_MBUS_FMT_YUYV10_2X10: + if (pix_fmt != ISIF_PIXFMT_YCBCR_8BIT) { + pr_debug("Invalid pix_fmt(input mode)\n"); + return -EINVAL; + } + /* setup BT.656, embedded sync */ + isif_write(isif->isif_cfg.base_addr, 3, REC656IF); + /* enable 10 bit mode in ccdcfg */ + ccdcfg = ccdcfg | ISIF_PACK_8BIT | ISIF_YCINSWP_YCBCR | + ISIF_BW656_ENABLE; + break; + + case V4L2_MBUS_FMT_YUYV10_1X20: + if (pix_fmt != ISIF_PIXFMT_YCBCR_16BIT) { + pr_debug("Invalid pix_fmt(input mode)\n"); + return -EINVAL; + } + isif_write(isif->isif_cfg.base_addr, 3, REC656IF); + break; + + case V4L2_MBUS_FMT_Y8_1X8: + ccdcfg |= ISIF_PACK_8BIT; + ccdcfg |= ISIF_YCINSWP_YCBCR; + if (pix_fmt != ISIF_PIXFMT_YCBCR_8BIT) { + pr_debug("Invalid pix_fmt(input mode)\n"); + return -EINVAL; + } + break; + + case V4L2_MBUS_FMT_YUYV8_1X16: + if (pix_fmt != ISIF_PIXFMT_YCBCR_16BIT) { + pr_debug("Invalid pix_fmt(input mode)\n"); + return -EINVAL; + } + break; + + default: + /* should never come here */ + pr_debug("Invalid interface type\n"); + return -EINVAL; + } + isif_write(isif->isif_cfg.base_addr, modeset, MODESET); + /* Set up pix order */ + ccdcfg |= (params->pix_order & ISIF_PIX_ORDER_MASK) << + ISIF_PIX_ORDER_SHIFT; + isif_write(isif->isif_cfg.base_addr, ccdcfg, CCDCFG); + /* configure video window */ + if (format->code == V4L2_MBUS_FMT_YUYV10_1X20 || + format->code == V4L2_MBUS_FMT_YUYV8_1X16) + isif_setwin(isif, ¶ms->win, params->frm_fmt, 1, mode); + else + isif_setwin(isif, ¶ms->win, params->frm_fmt, 2, mode); + + /* + * configure the horizontal line offset + * this is done by rounding up width to a multiple of 16 pixels + * and multiply by two to account for y:cb:cr 4:2:2 data + */ + isif_write(isif->isif_cfg.base_addr, + ((((params->win.width * 2) + 31) & + DM365_ISIF_HSIZE_MASK) >> 5), HSIZE); + + /* configure the memory line offset */ + if (params->frm_fmt == ISIF_FRMFMT_INTERLACED && + params->buf_type == ISIF_BUFTYPE_FLD_INTERLEAVED) + /* two fields are interleaved in memory */ + isif_write(isif->isif_cfg.base_addr, + DM365_ISIF_SDOFST_2_LINES, SDOFST); + return 0; +} + +static int isif_configure(struct v4l2_subdev *sd, int mode) +{ + struct vpfe_isif_device *isif = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt *format; + + format = &isif->formats[ISIF_PAD_SINK]; + + switch (format->code) { + case V4L2_MBUS_FMT_SGRBG10_ALAW8_1X8: + case V4L2_MBUS_FMT_SGRBG10_DPCM8_1X8: + case V4L2_MBUS_FMT_SGRBG12_1X12: + return isif_config_raw(sd, mode); + + case V4L2_MBUS_FMT_YUYV8_2X8: + case V4L2_MBUS_FMT_UYVY8_2X8: + case V4L2_MBUS_FMT_YUYV10_2X10: + case V4L2_MBUS_FMT_Y8_1X8: + case V4L2_MBUS_FMT_YUYV8_1X16: + case V4L2_MBUS_FMT_YUYV10_1X20: + return isif_config_ycbcr(sd, mode); + + default: + break; + } + return -EINVAL; +} + +/* + * isif_set_stream() - Enable/Disable streaming on the ISIF module + * @sd: VPFE ISIF V4L2 subdevice + * @enable: Enable/disable stream + */ +static int isif_set_stream(struct v4l2_subdev *sd, int enable) +{ + struct vpfe_isif_device *isif = v4l2_get_subdevdata(sd); + int ret; + + if (enable) { + ret = isif_configure(sd, + (isif->output == ISIF_OUTPUT_MEMORY) ? 0 : 1); + if (ret) + return ret; + if (isif->output == ISIF_OUTPUT_MEMORY) + isif_enable_output_to_sdram(isif, 1); + isif_enable(isif, 1); + } else { + isif_enable(isif, 0); + isif_enable_output_to_sdram(isif, 0); + } + + return 0; +} + +/* + * __isif_get_format() - helper function for getting isif format + * @isif: pointer to isif private structure. + * @pad: pad number. + * @fh: V4L2 subdev file handle. + * @which: wanted subdev format. + */ +static struct v4l2_mbus_framefmt * +__isif_get_format(struct vpfe_isif_device *isif, struct v4l2_subdev_fh *fh, + unsigned int pad, enum v4l2_subdev_format_whence which) +{ + if (which == V4L2_SUBDEV_FORMAT_TRY) { + struct v4l2_subdev_format fmt; + + fmt.pad = pad; + fmt.which = which; + + return v4l2_subdev_get_try_format(fh, pad); + } + return &isif->formats[pad]; +} + +/* +* isif_set_format() - set format on pad +* @sd : VPFE ISIF device +* @fh : V4L2 subdev file handle +* @fmt : pointer to v4l2 subdev format structure +* +* Return 0 on success or -EINVAL if format or pad is invalid +*/ +static int +isif_set_format(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh, + struct v4l2_subdev_format *fmt) +{ + struct vpfe_isif_device *isif = v4l2_get_subdevdata(sd); + struct vpfe_device *vpfe_dev = to_vpfe_device(isif); + struct v4l2_mbus_framefmt *format; + + format = __isif_get_format(isif, fh, fmt->pad, fmt->which); + if (format == NULL) + return -EINVAL; + + isif_try_format(isif, fh, fmt); + memcpy(format, &fmt->format, sizeof(*format)); + + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) + return 0; + + if (fmt->pad == ISIF_PAD_SOURCE) + return isif_config_format(vpfe_dev, fmt->pad); + + return 0; +} + +/* + * isif_get_format() - Retrieve the video format on a pad + * @sd: VPFE ISIF V4L2 subdevice + * @fh: V4L2 subdev file handle + * @fmt: pointer to v4l2 subdev format structure + * + * Return 0 on success or -EINVAL if the pad is invalid or doesn't correspond + * to the format type. + */ +static int +isif_get_format(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh, + struct v4l2_subdev_format *fmt) +{ + struct vpfe_isif_device *vpfe_isif = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt *format; + + format = __isif_get_format(vpfe_isif, fh, fmt->pad, fmt->which); + if (format == NULL) + return -EINVAL; + + memcpy(&fmt->format, format, sizeof(fmt->format)); + + return 0; +} + +/* + * isif_enum_frame_size() - enum frame sizes on pads + * @sd: VPFE isif V4L2 subdevice + * @fh: V4L2 subdev file handle + * @code: pointer to v4l2_subdev_frame_size_enum structure + */ +static int +isif_enum_frame_size(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh, + struct v4l2_subdev_frame_size_enum *fse) +{ + struct vpfe_isif_device *isif = v4l2_get_subdevdata(sd); + struct v4l2_subdev_format format; + + if (fse->index != 0) + return -EINVAL; + + format.pad = fse->pad; + format.format.code = fse->code; + format.format.width = 1; + format.format.height = 1; + format.which = V4L2_SUBDEV_FORMAT_TRY; + isif_try_format(isif, fh, &format); + fse->min_width = format.format.width; + fse->min_height = format.format.height; + + if (format.format.code != fse->code) + return -EINVAL; + + format.pad = fse->pad; + format.format.code = fse->code; + format.format.width = -1; + format.format.height = -1; + format.which = V4L2_SUBDEV_FORMAT_TRY; + isif_try_format(isif, fh, &format); + fse->max_width = format.format.width; + fse->max_height = format.format.height; + + return 0; +} + +/* + * isif_enum_mbus_code() - enum mbus codes for pads + * @sd: VPFE isif V4L2 subdevice + * @fh: V4L2 subdev file handle + * @code: pointer to v4l2_subdev_mbus_code_enum structure + */ +static int +isif_enum_mbus_code(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh, + struct v4l2_subdev_mbus_code_enum *code) +{ + switch (code->pad) { + case ISIF_PAD_SINK: + case ISIF_PAD_SOURCE: + if (code->index >= ARRAY_SIZE(isif_fmts)) + return -EINVAL; + code->code = isif_fmts[code->index]; + break; + + default: + return -EINVAL; + } + + return 0; +} + +/* + * isif_pad_set_crop() - set crop rectangle on pad + * @sd: VPFE isif V4L2 subdevice + * @fh: V4L2 subdev file handle + * @code: pointer to v4l2_subdev_mbus_code_enum structure + * + * Return 0 on success, -EINVAL if pad is invalid + */ +static int +isif_pad_set_crop(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh, + struct v4l2_subdev_crop *crop) +{ + struct vpfe_isif_device *vpfe_isif = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt *format; + + /* check wether its a valid pad */ + if (crop->pad != ISIF_PAD_SINK) + return -EINVAL; + + format = __isif_get_format(vpfe_isif, fh, crop->pad, crop->which); + if (format == NULL) + return -EINVAL; + + /* check wether crop rect is within limits */ + if (crop->rect.top < 0 || crop->rect.left < 0 || + (crop->rect.left + crop->rect.width > + vpfe_isif->formats[ISIF_PAD_SINK].width) || + (crop->rect.top + crop->rect.height > + vpfe_isif->formats[ISIF_PAD_SINK].height)) { + crop->rect.left = 0; + crop->rect.top = 0; + crop->rect.width = format->width; + crop->rect.height = format->height; + } + /* adjust the width to 16 pixel boundary */ + crop->rect.width = ((crop->rect.width + 15) & ~0xf); + vpfe_isif->crop = crop->rect; + if (crop->which == V4L2_SUBDEV_FORMAT_ACTIVE) { + isif_set_image_window(vpfe_isif); + } else { + struct v4l2_rect *rect; + + rect = v4l2_subdev_get_try_crop(fh, ISIF_PAD_SINK); + memcpy(rect, &vpfe_isif->crop, sizeof(*rect)); + } + return 0; +} + +/* + * isif_pad_get_crop() - get crop rectangle on pad + * @sd: VPFE isif V4L2 subdevice + * @fh: V4L2 subdev file handle + * @code: pointer to v4l2_subdev_mbus_code_enum structure + * + * Return 0 on success, -EINVAL if pad is invalid + */ +static int +isif_pad_get_crop(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh, + struct v4l2_subdev_crop *crop) +{ + struct vpfe_isif_device *vpfe_isif = v4l2_get_subdevdata(sd); + + /* check wether its a valid pad */ + if (crop->pad != ISIF_PAD_SINK) + return -EINVAL; + + if (crop->which == V4L2_SUBDEV_FORMAT_TRY) { + struct v4l2_rect *rect; + rect = v4l2_subdev_get_try_crop(fh, ISIF_PAD_SINK); + memcpy(&crop->rect, rect, sizeof(*rect)); + } else { + crop->rect = vpfe_isif->crop; + } + + return 0; +} + +/* + * isif_init_formats() - Initialize formats on all pads + * @sd: VPFE isif V4L2 subdevice + * @fh: V4L2 subdev file handle + * + * Initialize all pad formats with default values. If fh is not NULL, try + * formats are initialized on the file handle. Otherwise active formats are + * initialized on the device. + */ +static int +isif_init_formats(struct v4l2_subdev *sd, + struct v4l2_subdev_fh *fh) +{ + struct v4l2_subdev_format format; + struct v4l2_subdev_crop crop; + + memset(&format, 0, sizeof(format)); + format.pad = ISIF_PAD_SINK; + format.which = fh ? V4L2_SUBDEV_FORMAT_TRY : V4L2_SUBDEV_FORMAT_ACTIVE; + format.format.code = V4L2_MBUS_FMT_SGRBG12_1X12; + format.format.width = MAX_WIDTH; + format.format.height = MAX_HEIGHT; + isif_set_format(sd, fh, &format); + + memset(&format, 0, sizeof(format)); + format.pad = ISIF_PAD_SOURCE; + format.which = fh ? V4L2_SUBDEV_FORMAT_TRY : V4L2_SUBDEV_FORMAT_ACTIVE; + format.format.code = V4L2_MBUS_FMT_SGRBG12_1X12; + format.format.width = MAX_WIDTH; + format.format.height = MAX_HEIGHT; + isif_set_format(sd, fh, &format); + + memset(&crop, 0, sizeof(crop)); + crop.pad = ISIF_PAD_SINK; + crop.which = fh ? V4L2_SUBDEV_FORMAT_TRY : V4L2_SUBDEV_FORMAT_ACTIVE; + crop.rect.width = MAX_WIDTH; + crop.rect.height = MAX_HEIGHT; + isif_pad_set_crop(sd, fh, &crop); + + return 0; +} + +/* subdev core operations */ +static const struct v4l2_subdev_core_ops isif_v4l2_core_ops = { + .ioctl = isif_ioctl, +}; + +/* subdev file operations */ +static const struct v4l2_subdev_internal_ops isif_v4l2_internal_ops = { + .open = isif_init_formats, +}; + +/* subdev video operations */ +static const struct v4l2_subdev_video_ops isif_v4l2_video_ops = { + .s_stream = isif_set_stream, +}; + +/* subdev pad operations */ +static const struct v4l2_subdev_pad_ops isif_v4l2_pad_ops = { + .enum_mbus_code = isif_enum_mbus_code, + .enum_frame_size = isif_enum_frame_size, + .get_fmt = isif_get_format, + .set_fmt = isif_set_format, + .set_crop = isif_pad_set_crop, + .get_crop = isif_pad_get_crop, +}; + +/* subdev operations */ +static const struct v4l2_subdev_ops isif_v4l2_ops = { + .core = &isif_v4l2_core_ops, + .video = &isif_v4l2_video_ops, + .pad = &isif_v4l2_pad_ops, +}; + +/* + * Media entity operations + */ + +/* + * isif_link_setup() - Setup isif connections + * @entity: isif media entity + * @local: Pad at the local end of the link + * @remote: Pad at the remote end of the link + * @flags: Link flags + * + * return -EINVAL or zero on success + */ +static int +isif_link_setup(struct media_entity *entity, const struct media_pad *local, + const struct media_pad *remote, u32 flags) +{ + struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(entity); + struct vpfe_isif_device *isif = v4l2_get_subdevdata(sd); + + switch (local->index | media_entity_type(remote->entity)) { + case ISIF_PAD_SINK | MEDIA_ENT_T_V4L2_SUBDEV: + /* read from decoder/sensor */ + if (!(flags & MEDIA_LNK_FL_ENABLED)) { + isif->input = ISIF_INPUT_NONE; + break; + } + if (isif->input != ISIF_INPUT_NONE) + return -EBUSY; + isif->input = ISIF_INPUT_PARALLEL; + break; + + case ISIF_PAD_SOURCE | MEDIA_ENT_T_DEVNODE: + /* write to memory */ + if (flags & MEDIA_LNK_FL_ENABLED) + isif->output = ISIF_OUTPUT_MEMORY; + else + isif->output = ISIF_OUTPUT_NONE; + break; + + case ISIF_PAD_SOURCE | MEDIA_ENT_T_V4L2_SUBDEV: + if (flags & MEDIA_LNK_FL_ENABLED) + isif->output = ISIF_OUTPUT_IPIPEIF; + else + isif->output = ISIF_OUTPUT_NONE; + break; + + default: + return -EINVAL; + } + + return 0; +} +static const struct media_entity_operations isif_media_ops = { + .link_setup = isif_link_setup, +}; + +/* + * vpfe_isif_unregister_entities() - isif unregister entity + * @isif - pointer to isif subdevice structure. + */ +void vpfe_isif_unregister_entities(struct vpfe_isif_device *isif) +{ + vpfe_video_unregister(&isif->video_out); + /* unregister subdev */ + v4l2_device_unregister_subdev(&isif->subdev); + /* cleanup entity */ + media_entity_cleanup(&isif->subdev.entity); +} + +static void isif_restore_defaults(struct vpfe_isif_device *isif) +{ + enum vpss_ccdc_source_sel source = VPSS_CCDCIN; + int i; + + memset(&isif->isif_cfg.bayer.config_params, 0, + sizeof(struct vpfe_isif_raw_config)); + + isif->isif_cfg.bayer.config_params.linearize.corr_shft = + VPFE_ISIF_NO_SHIFT; + isif->isif_cfg.bayer.config_params.linearize.scale_fact.integer = 1; + isif->isif_cfg.bayer.config_params.culling.hcpat_odd = + ISIF_CULLING_HCAPT_ODD; + isif->isif_cfg.bayer.config_params.culling.hcpat_even = + ISIF_CULLING_HCAPT_EVEN; + isif->isif_cfg.bayer.config_params.culling.vcpat = ISIF_CULLING_VCAPT; + /* Enable clock to ISIF, IPIPEIF and BL */ + vpss_enable_clock(VPSS_CCDC_CLOCK, 1); + vpss_enable_clock(VPSS_IPIPEIF_CLOCK, 1); + vpss_enable_clock(VPSS_BL_CLOCK, 1); + + /* set all registers to default value */ + for (i = 0; i <= 0x1f8; i += 4) + isif_write(isif->isif_cfg.base_addr, 0, i); + /* no culling support */ + isif_write(isif->isif_cfg.base_addr, 0xffff, CULH); + isif_write(isif->isif_cfg.base_addr, 0xff, CULV); + + /* Set default offset and gain */ + isif_config_gain_offset(isif); + vpss_select_ccdc_source(source); +} + +/* + * vpfe_isif_register_entities() - isif register entity + * @isif - pointer to isif subdevice structure. + * @vdev: pointer to v4l2 device structure. + */ +int vpfe_isif_register_entities(struct vpfe_isif_device *isif, + struct v4l2_device *vdev) +{ + struct vpfe_device *vpfe_dev = to_vpfe_device(isif); + unsigned int flags; + int ret; + + /* Register the subdev */ + ret = v4l2_device_register_subdev(vdev, &isif->subdev); + if (ret < 0) + return ret; + + isif_restore_defaults(isif); + ret = vpfe_video_register(&isif->video_out, vdev); + if (ret) { + pr_err("Failed to register isif video out device\n"); + goto out_video_register; + } + isif->video_out.vpfe_dev = vpfe_dev; + flags = 0; + /* connect isif to video node */ + ret = media_entity_create_link(&isif->subdev.entity, 1, + &isif->video_out.video_dev.entity, + 0, flags); + if (ret < 0) + goto out_create_link; + return 0; +out_create_link: + vpfe_video_unregister(&isif->video_out); +out_video_register: + v4l2_device_unregister_subdev(&isif->subdev); + return ret; +} + +/* ------------------------------------------------------------------- + * V4L2 subdev control operations + */ + +static int vpfe_isif_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct vpfe_isif_device *isif = + container_of(ctrl->handler, struct vpfe_isif_device, ctrls); + struct isif_oper_config *config = &isif->isif_cfg; + + switch (ctrl->id) { + case VPFE_CID_DPCM_PREDICTOR: + config->bayer.dpcm_predictor = ctrl->val; + break; + + case VPFE_ISIF_CID_CRGAIN: + config->isif_gain_params.cr_gain = ctrl->val; + break; + + case VPFE_ISIF_CID_CGRGAIN: + config->isif_gain_params.cgr_gain = ctrl->val; + break; + + case VPFE_ISIF_CID_CGBGAIN: + config->isif_gain_params.cgb_gain = ctrl->val; + break; + + case VPFE_ISIF_CID_CBGAIN: + config->isif_gain_params.cb_gain = ctrl->val; + break; + + case VPFE_ISIF_CID_GAIN_OFFSET: + config->isif_gain_params.offset = ctrl->val; + break; + + default: + return -EINVAL; + } + return 0; +} + +static const struct v4l2_ctrl_ops vpfe_isif_ctrl_ops = { + .s_ctrl = vpfe_isif_s_ctrl, +}; + +static const struct v4l2_ctrl_config vpfe_isif_dpcm_pred = { + .ops = &vpfe_isif_ctrl_ops, + .id = VPFE_CID_DPCM_PREDICTOR, + .name = "DPCM Predictor", + .type = V4L2_CTRL_TYPE_INTEGER, + .min = 0, + .max = 1, + .step = 1, + .def = 0, +}; + +static const struct v4l2_ctrl_config vpfe_isif_crgain = { + .ops = &vpfe_isif_ctrl_ops, + .id = VPFE_ISIF_CID_CRGAIN, + .name = "CRGAIN", + .type = V4L2_CTRL_TYPE_INTEGER, + .min = 0, + .max = (1 << 12) - 1, + .step = 1, + .def = 0, +}; + +static const struct v4l2_ctrl_config vpfe_isif_cgrgain = { + .ops = &vpfe_isif_ctrl_ops, + .id = VPFE_ISIF_CID_CGRGAIN, + .name = "CGRGAIN", + .type = V4L2_CTRL_TYPE_INTEGER, + .min = 0, + .max = (1 << 12) - 1, + .step = 1, + .def = 0, +}; + +static const struct v4l2_ctrl_config vpfe_isif_cgbgain = { + .ops = &vpfe_isif_ctrl_ops, + .id = VPFE_ISIF_CID_CGBGAIN, + .name = "CGBGAIN", + .type = V4L2_CTRL_TYPE_INTEGER, + .min = 0, + .max = (1 << 12) - 1, + .step = 1, + .def = 0, +}; + +static const struct v4l2_ctrl_config vpfe_isif_cbgain = { + .ops = &vpfe_isif_ctrl_ops, + .id = VPFE_ISIF_CID_CBGAIN, + .name = "CBGAIN", + .type = V4L2_CTRL_TYPE_INTEGER, + .min = 0, + .max = (1 << 12) - 1, + .step = 1, + .def = 0, +}; + +static const struct v4l2_ctrl_config vpfe_isif_gain_offset = { + .ops = &vpfe_isif_ctrl_ops, + .id = VPFE_ISIF_CID_GAIN_OFFSET, + .name = "Gain Offset", + .type = V4L2_CTRL_TYPE_INTEGER, + .min = 0, + .max = (1 << 12) - 1, + .step = 1, + .def = 0, +}; + +static void isif_remove(struct vpfe_isif_device *isif, + struct platform_device *pdev) +{ + struct resource *res; + int i = 0; + + iounmap(isif->isif_cfg.base_addr); + iounmap(isif->isif_cfg.linear_tbl0_addr); + iounmap(isif->isif_cfg.linear_tbl1_addr); + + while (i < 3) { + res = platform_get_resource(pdev, IORESOURCE_MEM, i); + if (res) + release_mem_region(res->start, + resource_size(res)); + i++; + } +} + +static void isif_config_defaults(struct vpfe_isif_device *isif) +{ + isif->isif_cfg.ycbcr.v4l2_pix_fmt = V4L2_PIX_FMT_UYVY; + isif->isif_cfg.ycbcr.pix_fmt = ISIF_PIXFMT_YCBCR_8BIT; + isif->isif_cfg.ycbcr.frm_fmt = ISIF_FRMFMT_INTERLACED; + isif->isif_cfg.ycbcr.fid_pol = VPFE_PINPOL_POSITIVE; + isif->isif_cfg.ycbcr.vd_pol = VPFE_PINPOL_POSITIVE; + isif->isif_cfg.ycbcr.hd_pol = VPFE_PINPOL_POSITIVE; + isif->isif_cfg.ycbcr.pix_order = ISIF_PIXORDER_CBYCRY; + isif->isif_cfg.ycbcr.buf_type = ISIF_BUFTYPE_FLD_INTERLEAVED; + + isif->isif_cfg.bayer.v4l2_pix_fmt = V4L2_PIX_FMT_SGRBG10ALAW8; + isif->isif_cfg.bayer.pix_fmt = ISIF_PIXFMT_RAW; + isif->isif_cfg.bayer.frm_fmt = ISIF_FRMFMT_PROGRESSIVE; + isif->isif_cfg.bayer.fid_pol = VPFE_PINPOL_POSITIVE; + isif->isif_cfg.bayer.vd_pol = VPFE_PINPOL_POSITIVE; + isif->isif_cfg.bayer.hd_pol = VPFE_PINPOL_POSITIVE; + isif->isif_cfg.bayer.cfa_pat = ISIF_CFA_PAT_MOSAIC; + isif->isif_cfg.bayer.data_msb = ISIF_BIT_MSB_11; + isif->isif_cfg.data_pack = ISIF_PACK_8BIT; +} +/* + * vpfe_isif_init() - Initialize V4L2 subdev and media entity + * @isif: VPFE isif module + * @pdev: Pointer to platform device structure. + * Return 0 on success and a negative error code on failure. + */ +int vpfe_isif_init(struct vpfe_isif_device *isif, struct platform_device *pdev) +{ + struct v4l2_subdev *sd = &isif->subdev; + struct media_pad *pads = &isif->pads[0]; + struct media_entity *me = &sd->entity; + static resource_size_t res_len; + struct resource *res; + void *__iomem addr; + int status; + int i = 0; + + /* Get the ISIF base address, linearization table0 and table1 addr. */ + while (i < 3) { + res = platform_get_resource(pdev, IORESOURCE_MEM, i); + if (!res) { + status = -ENOENT; + goto fail_nobase_res; + } + res_len = resource_size(res); + res = request_mem_region(res->start, res_len, res->name); + if (!res) { + status = -EBUSY; + goto fail_nobase_res; + } + addr = ioremap_nocache(res->start, res_len); + if (!addr) { + status = -EBUSY; + goto fail_base_iomap; + } + switch (i) { + case 0: + /* ISIF base address */ + isif->isif_cfg.base_addr = addr; + break; + case 1: + /* ISIF linear tbl0 address */ + isif->isif_cfg.linear_tbl0_addr = addr; + break; + default: + /* ISIF linear tbl0 address */ + isif->isif_cfg.linear_tbl1_addr = addr; + break; + } + i++; + } + davinci_cfg_reg(DM365_VIN_CAM_WEN); + davinci_cfg_reg(DM365_VIN_CAM_VD); + davinci_cfg_reg(DM365_VIN_CAM_HD); + davinci_cfg_reg(DM365_VIN_YIN4_7_EN); + davinci_cfg_reg(DM365_VIN_YIN0_3_EN); + + /* queue ops */ + isif->video_out.ops = &isif_video_ops; + v4l2_subdev_init(sd, &isif_v4l2_ops); + sd->internal_ops = &isif_v4l2_internal_ops; + strlcpy(sd->name, "DAVINCI ISIF", sizeof(sd->name)); + sd->grp_id = 1 << 16; /* group ID for davinci subdevs */ + v4l2_set_subdevdata(sd, isif); + sd->flags |= V4L2_SUBDEV_FL_HAS_EVENTS | V4L2_SUBDEV_FL_HAS_DEVNODE; + pads[ISIF_PAD_SINK].flags = MEDIA_PAD_FL_SINK; + pads[ISIF_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE; + + isif->input = ISIF_INPUT_NONE; + isif->output = ISIF_OUTPUT_NONE; + me->ops = &isif_media_ops; + status = media_entity_init(me, ISIF_PADS_NUM, pads, 0); + if (status) + goto isif_fail; + isif->video_out.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + status = vpfe_video_init(&isif->video_out, "ISIF"); + if (status) { + pr_err("Failed to init isif-out video device\n"); + goto isif_fail; + } + v4l2_ctrl_handler_init(&isif->ctrls, 6); + v4l2_ctrl_new_custom(&isif->ctrls, &vpfe_isif_crgain, NULL); + v4l2_ctrl_new_custom(&isif->ctrls, &vpfe_isif_cgrgain, NULL); + v4l2_ctrl_new_custom(&isif->ctrls, &vpfe_isif_cgbgain, NULL); + v4l2_ctrl_new_custom(&isif->ctrls, &vpfe_isif_cbgain, NULL); + v4l2_ctrl_new_custom(&isif->ctrls, &vpfe_isif_gain_offset, NULL); + v4l2_ctrl_new_custom(&isif->ctrls, &vpfe_isif_dpcm_pred, NULL); + + v4l2_ctrl_handler_setup(&isif->ctrls); + sd->ctrl_handler = &isif->ctrls; + isif_config_defaults(isif); + return 0; +fail_base_iomap: + release_mem_region(res->start, res_len); + i--; +fail_nobase_res: + if (isif->isif_cfg.base_addr) + iounmap(isif->isif_cfg.base_addr); + if (isif->isif_cfg.linear_tbl0_addr) + iounmap(isif->isif_cfg.linear_tbl0_addr); + + while (i >= 0) { + res = platform_get_resource(pdev, IORESOURCE_MEM, i); + release_mem_region(res->start, res_len); + i--; + } + return status; +isif_fail: + v4l2_ctrl_handler_free(&isif->ctrls); + isif_remove(isif, pdev); + return status; +} + +/* + * vpfe_isif_cleanup - isif module cleanup + * @isif: pointer to isif subdevice + * @dev: pointer to platform device structure + */ +void +vpfe_isif_cleanup(struct vpfe_isif_device *isif, struct platform_device *pdev) +{ + isif_remove(isif, pdev); +} diff --git a/drivers/staging/media/davinci_vpfe/dm365_isif.h b/drivers/staging/media/davinci_vpfe/dm365_isif.h new file mode 100644 index 00000000000..473fd2cfe35 --- /dev/null +++ b/drivers/staging/media/davinci_vpfe/dm365_isif.h @@ -0,0 +1,203 @@ +/* + * Copyright (C) 2012 Texas Instruments Inc + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Contributors: + * Manjunath Hadli <manjunath.hadli@ti.com> + * Prabhakar Lad <prabhakar.lad@ti.com> + */ + +#ifndef _DAVINCI_VPFE_DM365_ISIF_H +#define _DAVINCI_VPFE_DM365_ISIF_H + +#include <linux/platform_device.h> + +#include <mach/mux.h> + +#include <media/davinci/vpfe_types.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> + +#include "davinci_vpfe_user.h" +#include "dm365_isif_regs.h" +#include "vpfe_video.h" + +#define ISIF_CULLING_HCAPT_ODD 0xff +#define ISIF_CULLING_HCAPT_EVEN 0xff +#define ISIF_CULLING_VCAPT 0xff + +#define ISIF_CADU_BITS 0x07ff +#define ISIF_CADL_BITS 0x0ffff + +enum isif_pixfmt { + ISIF_PIXFMT_RAW = 0, + ISIF_PIXFMT_YCBCR_16BIT = 1, + ISIF_PIXFMT_YCBCR_8BIT = 2, +}; + +enum isif_frmfmt { + ISIF_FRMFMT_PROGRESSIVE = 0, + ISIF_FRMFMT_INTERLACED = 1, +}; + +/* PIXEL ORDER IN MEMORY from LSB to MSB */ +/* only applicable for 8-bit input mode */ +enum isif_pixorder { + ISIF_PIXORDER_YCBYCR = 0, + ISIF_PIXORDER_CBYCRY = 1, +}; + +enum isif_buftype { + ISIF_BUFTYPE_FLD_INTERLEAVED = 0, + ISIF_BUFTYPE_FLD_SEPARATED = 1, +}; + +struct isif_ycbcr_config { + /* v4l2 pixel format */ + unsigned long v4l2_pix_fmt; + /* isif pixel format */ + enum isif_pixfmt pix_fmt; + /* isif frame format */ + enum isif_frmfmt frm_fmt; + /* isif crop window */ + struct v4l2_rect win; + /* field polarity */ + enum vpfe_pin_pol fid_pol; + /* interface VD polarity */ + enum vpfe_pin_pol vd_pol; + /* interface HD polarity */ + enum vpfe_pin_pol hd_pol; + /* isif pix order. Only used for ycbcr capture */ + enum isif_pixorder pix_order; + /* isif buffer type. Only used for ycbcr capture */ + enum isif_buftype buf_type; +}; + +enum isif_cfa_pattern { + ISIF_CFA_PAT_MOSAIC = 0, + ISIF_CFA_PAT_STRIPE = 1, +}; + +enum isif_data_msb { + /* MSB b15 */ + ISIF_BIT_MSB_15 = 0, + /* MSB b14 */ + ISIF_BIT_MSB_14 = 1, + /* MSB b13 */ + ISIF_BIT_MSB_13 = 2, + /* MSB b12 */ + ISIF_BIT_MSB_12 = 3, + /* MSB b11 */ + ISIF_BIT_MSB_11 = 4, + /* MSB b10 */ + ISIF_BIT_MSB_10 = 5, + /* MSB b9 */ + ISIF_BIT_MSB_9 = 6, + /* MSB b8 */ + ISIF_BIT_MSB_8 = 7, + /* MSB b7 */ + ISIF_BIT_MSB_7 = 8, +}; + +struct isif_params_raw { + /* v4l2 pixel format */ + unsigned long v4l2_pix_fmt; + /* isif pixel format */ + enum isif_pixfmt pix_fmt; + /* isif frame format */ + enum isif_frmfmt frm_fmt; + /* video window */ + struct v4l2_rect win; + /* field polarity */ + enum vpfe_pin_pol fid_pol; + /* interface VD polarity */ + enum vpfe_pin_pol vd_pol; + /* interface HD polarity */ + enum vpfe_pin_pol hd_pol; + /* buffer type. Applicable for interlaced mode */ + enum isif_buftype buf_type; + /* cfa pattern */ + enum isif_cfa_pattern cfa_pat; + /* Data MSB position */ + enum isif_data_msb data_msb; + /* Enable horizontal flip */ + unsigned char horz_flip_en; + /* Enable image invert vertically */ + unsigned char image_invert_en; + unsigned char dpcm_predictor; + struct vpfe_isif_raw_config config_params; +}; + +enum isif_data_pack { + ISIF_PACK_16BIT = 0, + ISIF_PACK_12BIT = 1, + ISIF_PACK_8BIT = 2, +}; + +struct isif_gain_values { + unsigned int cr_gain; + unsigned int cgr_gain; + unsigned int cgb_gain; + unsigned int cb_gain; + unsigned int offset; +}; + +struct isif_oper_config { + struct isif_ycbcr_config ycbcr; + struct isif_params_raw bayer; + enum isif_data_pack data_pack; + struct isif_gain_values isif_gain_params; + void *__iomem base_addr; + void *__iomem linear_tbl0_addr; + void *__iomem linear_tbl1_addr; +}; + +#define ISIF_PAD_SINK 0 +#define ISIF_PAD_SOURCE 1 + +#define ISIF_PADS_NUM 2 + +enum isif_input_entity { + ISIF_INPUT_NONE = 0, + ISIF_INPUT_PARALLEL = 1, +}; + +#define ISIF_OUTPUT_NONE (0) +#define ISIF_OUTPUT_MEMORY (1 << 0) +#define ISIF_OUTPUT_IPIPEIF (1 << 1) + +struct vpfe_isif_device { + struct v4l2_subdev subdev; + struct media_pad pads[ISIF_PADS_NUM]; + struct v4l2_mbus_framefmt formats[ISIF_PADS_NUM]; + enum isif_input_entity input; + unsigned int output; + struct v4l2_ctrl_handler ctrls; + struct v4l2_rect crop; + struct isif_oper_config isif_cfg; + struct vpfe_video_device video_out; +}; + +enum v4l2_field vpfe_isif_get_fid(struct vpfe_device *vpfe_dev); +void vpfe_isif_unregister_entities(struct vpfe_isif_device *isif); +int vpfe_isif_register_entities(struct vpfe_isif_device *isif, + struct v4l2_device *dev); +int vpfe_isif_init(struct vpfe_isif_device *isif, struct platform_device *pdev); +void vpfe_isif_cleanup(struct vpfe_isif_device *vpfe_isif, + struct platform_device *pdev); +void vpfe_isif_vidint1_isr(struct vpfe_isif_device *isif); +void vpfe_isif_buffer_isr(struct vpfe_isif_device *isif); + +#endif /* _DAVINCI_VPFE_DM365_ISIF_H */ diff --git a/drivers/staging/media/davinci_vpfe/dm365_isif_regs.h b/drivers/staging/media/davinci_vpfe/dm365_isif_regs.h new file mode 100644 index 00000000000..8aceabb43f8 --- /dev/null +++ b/drivers/staging/media/davinci_vpfe/dm365_isif_regs.h @@ -0,0 +1,294 @@ +/* + * Copyright (C) 2012 Texas Instruments Inc + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Contributors: + * Manjunath Hadli <manjunath.hadli@ti.com> + * Prabhakar Lad <prabhakar.lad@ti.com> + */ + +#ifndef _DAVINCI_VPFE_DM365_ISIF_REGS_H +#define _DAVINCI_VPFE_DM365_ISIF_REGS_H + +/* ISIF registers relative offsets */ +#define SYNCEN 0x00 +#define MODESET 0x04 +#define HDW 0x08 +#define VDW 0x0c +#define PPLN 0x10 +#define LPFR 0x14 +#define SPH 0x18 +#define LNH 0x1c +#define SLV0 0x20 +#define SLV1 0x24 +#define LNV 0x28 +#define CULH 0x2c +#define CULV 0x30 +#define HSIZE 0x34 +#define SDOFST 0x38 +#define CADU 0x3c +#define CADL 0x40 +#define LINCFG0 0x44 +#define LINCFG1 0x48 +#define CCOLP 0x4c +#define CRGAIN 0x50 +#define CGRGAIN 0x54 +#define CGBGAIN 0x58 +#define CBGAIN 0x5c +#define COFSTA 0x60 +#define FLSHCFG0 0x64 +#define FLSHCFG1 0x68 +#define FLSHCFG2 0x6c +#define VDINT0 0x70 +#define VDINT1 0x74 +#define VDINT2 0x78 +#define MISC 0x7c +#define CGAMMAWD 0x80 +#define REC656IF 0x84 +#define CCDCFG 0x88 +/***************************************************** +* Defect Correction registers +*****************************************************/ +#define DFCCTL 0x8c +#define VDFSATLV 0x90 +#define DFCMEMCTL 0x94 +#define DFCMEM0 0x98 +#define DFCMEM1 0x9c +#define DFCMEM2 0xa0 +#define DFCMEM3 0xa4 +#define DFCMEM4 0xa8 +/**************************************************** +* Black Clamp registers +****************************************************/ +#define CLAMPCFG 0xac +#define CLDCOFST 0xb0 +#define CLSV 0xb4 +#define CLHWIN0 0xb8 +#define CLHWIN1 0xbc +#define CLHWIN2 0xc0 +#define CLVRV 0xc4 +#define CLVWIN0 0xc8 +#define CLVWIN1 0xcc +#define CLVWIN2 0xd0 +#define CLVWIN3 0xd4 +/**************************************************** +* Lense Shading Correction +****************************************************/ +#define DATAHOFST 0xd8 +#define DATAVOFST 0xdc +#define LSCHVAL 0xe0 +#define LSCVVAL 0xe4 +#define TWODLSCCFG 0xe8 +#define TWODLSCOFST 0xec +#define TWODLSCINI 0xf0 +#define TWODLSCGRBU 0xf4 +#define TWODLSCGRBL 0xf8 +#define TWODLSCGROF 0xfc +#define TWODLSCORBU 0x100 +#define TWODLSCORBL 0x104 +#define TWODLSCOROF 0x108 +#define TWODLSCIRQEN 0x10c +#define TWODLSCIRQST 0x110 +/**************************************************** +* Data formatter +****************************************************/ +#define FMTCFG 0x114 +#define FMTPLEN 0x118 +#define FMTSPH 0x11c +#define FMTLNH 0x120 +#define FMTSLV 0x124 +#define FMTLNV 0x128 +#define FMTRLEN 0x12c +#define FMTHCNT 0x130 +#define FMTAPTR_BASE 0x134 +/* Below macro for addresses FMTAPTR0 - FMTAPTR15 */ +#define FMTAPTR(i) (FMTAPTR_BASE + (i * 4)) +#define FMTPGMVF0 0x174 +#define FMTPGMVF1 0x178 +#define FMTPGMAPU0 0x17c +#define FMTPGMAPU1 0x180 +#define FMTPGMAPS0 0x184 +#define FMTPGMAPS1 0x188 +#define FMTPGMAPS2 0x18c +#define FMTPGMAPS3 0x190 +#define FMTPGMAPS4 0x194 +#define FMTPGMAPS5 0x198 +#define FMTPGMAPS6 0x19c +#define FMTPGMAPS7 0x1a0 +/************************************************ +* Color Space Converter +************************************************/ +#define CSCCTL 0x1a4 +#define CSCM0 0x1a8 +#define CSCM1 0x1ac +#define CSCM2 0x1b0 +#define CSCM3 0x1b4 +#define CSCM4 0x1b8 +#define CSCM5 0x1bc +#define CSCM6 0x1c0 +#define CSCM7 0x1c4 +#define OBWIN0 0x1c8 +#define OBWIN1 0x1cc +#define OBWIN2 0x1d0 +#define OBWIN3 0x1d4 +#define OBVAL0 0x1d8 +#define OBVAL1 0x1dc +#define OBVAL2 0x1e0 +#define OBVAL3 0x1e4 +#define OBVAL4 0x1e8 +#define OBVAL5 0x1ec +#define OBVAL6 0x1f0 +#define OBVAL7 0x1f4 +#define CLKCTL 0x1f8 + +/* Masks & Shifts below */ +#define START_PX_HOR_MASK 0x7fff +#define NUM_PX_HOR_MASK 0x7fff +#define START_VER_ONE_MASK 0x7fff +#define START_VER_TWO_MASK 0x7fff +#define NUM_LINES_VER 0x7fff + +/* gain - offset masks */ +#define OFFSET_MASK 0xfff +#define GAIN_SDRAM_EN_SHIFT 12 +#define GAIN_IPIPE_EN_SHIFT 13 +#define GAIN_H3A_EN_SHIFT 14 +#define OFST_SDRAM_EN_SHIFT 8 +#define OFST_IPIPE_EN_SHIFT 9 +#define OFST_H3A_EN_SHIFT 10 +#define GAIN_OFFSET_EN_MASK 0x7700 + +/* Culling */ +#define CULL_PAT_EVEN_LINE_SHIFT 8 + +/* CCDCFG register */ +#define ISIF_YCINSWP_RAW (0x00 << 4) +#define ISIF_YCINSWP_YCBCR (0x01 << 4) +#define ISIF_CCDCFG_FIDMD_LATCH_VSYNC (0x00 << 6) +#define ISIF_CCDCFG_WENLOG_AND (0x00 << 8) +#define ISIF_CCDCFG_TRGSEL_WEN (0x00 << 9) +#define ISIF_CCDCFG_EXTRG_DISABLE (0x00 << 10) +#define ISIF_LATCH_ON_VSYNC_DISABLE (0x01 << 15) +#define ISIF_LATCH_ON_VSYNC_ENABLE (0x00 << 15) +#define ISIF_DATA_PACK_MASK 0x03 +#define ISIF_PIX_ORDER_SHIFT 11 +#define ISIF_PIX_ORDER_MASK 0x01 +#define ISIF_BW656_ENABLE (0x01 << 5) + +/* MODESET registers */ +#define ISIF_VDHDOUT_INPUT (0x00 << 0) +#define ISIF_INPUT_MASK 0x03 +#define ISIF_INPUT_SHIFT 12 +#define ISIF_FID_POL_MASK 0x01 +#define ISIF_FID_POL_SHIFT 4 +#define ISIF_HD_POL_MASK 0x01 +#define ISIF_HD_POL_SHIFT 3 +#define ISIF_VD_POL_MASK 0x01 +#define ISIF_VD_POL_SHIFT 2 +#define ISIF_DATAPOL_NORMAL 0x00 +#define ISIF_DATAPOL_MASK 0x01 +#define ISIF_DATAPOL_SHIFT 6 +#define ISIF_EXWEN_DISABLE 0x00 +#define ISIF_EXWEN_MASK 0x01 +#define ISIF_EXWEN_SHIFT 5 +#define ISIF_FRM_FMT_MASK 0x01 +#define ISIF_FRM_FMT_SHIFT 7 +#define ISIF_DATASFT_MASK 0x07 +#define ISIF_DATASFT_SHIFT 8 +#define ISIF_LPF_SHIFT 14 +#define ISIF_LPF_MASK 0x1 + +/* GAMMAWD registers */ +#define ISIF_ALAW_GAMA_WD_MASK 0xf +#define ISIF_ALAW_GAMA_WD_SHIFT 1 +#define ISIF_ALAW_ENABLE 0x01 +#define ISIF_GAMMAWD_CFA_MASK 0x01 +#define ISIF_GAMMAWD_CFA_SHIFT 5 + +/* HSIZE registers */ +#define ISIF_HSIZE_FLIP_MASK 0x01 +#define ISIF_HSIZE_FLIP_SHIFT 12 +#define ISIF_LINEOFST_MASK 0xfff + +/* MISC registers */ +#define ISIF_DPCM_EN_SHIFT 12 +#define ISIF_DPCM_PREDICTOR_SHIFT 13 +#define ISIF_DPCM_PREDICTOR_MASK 1 + +/* Black clamp related */ +#define ISIF_BC_DCOFFSET_MASK 0x1fff +#define ISIF_BC_MODE_COLOR_MASK 1 +#define ISIF_BC_MODE_COLOR_SHIFT 4 +#define ISIF_HORZ_BC_MODE_MASK 3 +#define ISIF_HORZ_BC_MODE_SHIFT 1 +#define ISIF_HORZ_BC_WIN_COUNT_MASK 0x1f +#define ISIF_HORZ_BC_WIN_SEL_SHIFT 5 +#define ISIF_HORZ_BC_PIX_LIMIT_SHIFT 6 +#define ISIF_HORZ_BC_WIN_H_SIZE_MASK 3 +#define ISIF_HORZ_BC_WIN_H_SIZE_SHIFT 8 +#define ISIF_HORZ_BC_WIN_V_SIZE_MASK 3 +#define ISIF_HORZ_BC_WIN_V_SIZE_SHIFT 12 +#define ISIF_HORZ_BC_WIN_START_H_MASK 0x1fff +#define ISIF_HORZ_BC_WIN_START_V_MASK 0x1fff +#define ISIF_VERT_BC_OB_H_SZ_MASK 7 +#define ISIF_VERT_BC_RST_VAL_SEL_MASK 3 +#define ISIF_VERT_BC_RST_VAL_SEL_SHIFT 4 +#define ISIF_VERT_BC_LINE_AVE_COEF_SHIFT 8 +#define ISIF_VERT_BC_OB_START_HORZ_MASK 0x1fff +#define ISIF_VERT_BC_OB_START_VERT_MASK 0x1fff +#define ISIF_VERT_BC_OB_VERT_SZ_MASK 0x1fff +#define ISIF_VERT_BC_RST_VAL_MASK 0xfff +#define ISIF_BC_VERT_START_SUB_V_MASK 0x1fff + +/* VDFC registers */ +#define ISIF_VDFC_EN_SHIFT 4 +#define ISIF_VDFC_CORR_MOD_MASK 3 +#define ISIF_VDFC_CORR_MOD_SHIFT 5 +#define ISIF_VDFC_CORR_WHOLE_LN_SHIFT 7 +#define ISIF_VDFC_LEVEL_SHFT_MASK 7 +#define ISIF_VDFC_LEVEL_SHFT_SHIFT 8 +#define ISIF_VDFC_SAT_LEVEL_MASK 0xfff +#define ISIF_VDFC_POS_MASK 0x1fff +#define ISIF_DFCMEMCTL_DFCMARST_SHIFT 2 + +/* CSC registers */ +#define ISIF_CSC_COEF_INTEG_MASK 7 +#define ISIF_CSC_COEF_DECIMAL_MASK 0x1f +#define ISIF_CSC_COEF_INTEG_SHIFT 5 +#define ISIF_CSCM_MSB_SHIFT 8 +#define ISIF_DF_CSC_SPH_MASK 0x1fff +#define ISIF_DF_CSC_LNH_MASK 0x1fff +#define ISIF_DF_CSC_SLV_MASK 0x1fff +#define ISIF_DF_CSC_LNV_MASK 0x1fff +#define ISIF_DF_NUMLINES 0x7fff +#define ISIF_DF_NUMPIX 0x1fff + +/* Offsets for LSC/DFC/Gain */ +#define ISIF_DATA_H_OFFSET_MASK 0x1fff +#define ISIF_DATA_V_OFFSET_MASK 0x1fff + +/* Linearization */ +#define ISIF_LIN_CORRSFT_MASK 7 +#define ISIF_LIN_CORRSFT_SHIFT 4 +#define ISIF_LIN_SCALE_FACT_INTEG_SHIFT 10 +#define ISIF_LIN_SCALE_FACT_DECIMAL_MASK 0x3ff +#define ISIF_LIN_ENTRY_MASK 0x3ff + +/* masks and shifts*/ +#define ISIF_SYNCEN_VDHDEN_MASK (1 << 0) +#define ISIF_SYNCEN_WEN_MASK (1 << 1) +#define ISIF_SYNCEN_WEN_SHIFT 1 + +#endif /* _DAVINCI_VPFE_DM365_ISIF_REGS_H */ diff --git a/drivers/staging/media/davinci_vpfe/dm365_resizer.c b/drivers/staging/media/davinci_vpfe/dm365_resizer.c new file mode 100644 index 00000000000..8e13bd494c9 --- /dev/null +++ b/drivers/staging/media/davinci_vpfe/dm365_resizer.c @@ -0,0 +1,1999 @@ +/* + * Copyright (C) 2012 Texas Instruments Inc + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Contributors: + * Manjunath Hadli <manjunath.hadli@ti.com> + * Prabhakar Lad <prabhakar.lad@ti.com> + * + * + * Resizer allows upscaling or downscaling a image to a desired + * resolution. There are 2 resizer modules. both operating on the + * same input image, but can have different output resolution. + */ + +#include "dm365_ipipe_hw.h" +#include "dm365_resizer.h" + +#define MIN_IN_WIDTH 32 +#define MIN_IN_HEIGHT 32 +#define MAX_IN_WIDTH 4095 +#define MAX_IN_HEIGHT 4095 +#define MIN_OUT_WIDTH 16 +#define MIN_OUT_HEIGHT 2 + +static const unsigned int resizer_input_formats[] = { + V4L2_MBUS_FMT_UYVY8_2X8, + V4L2_MBUS_FMT_Y8_1X8, + V4L2_MBUS_FMT_UV8_1X8, + V4L2_MBUS_FMT_SGRBG12_1X12, +}; + +static const unsigned int resizer_output_formats[] = { + V4L2_MBUS_FMT_UYVY8_2X8, + V4L2_MBUS_FMT_Y8_1X8, + V4L2_MBUS_FMT_UV8_1X8, + V4L2_MBUS_FMT_YDYUYDYV8_1X16, + V4L2_MBUS_FMT_SGRBG12_1X12, +}; + +/* resizer_calculate_line_length() - This function calculates the line length of + * various image planes at the input and + * output. + */ +static void +resizer_calculate_line_length(enum v4l2_mbus_pixelcode pix, int width, + int height, int *line_len, int *line_len_c) +{ + *line_len = 0; + *line_len_c = 0; + + if (pix == V4L2_MBUS_FMT_UYVY8_2X8 || + pix == V4L2_MBUS_FMT_SGRBG12_1X12) { + *line_len = width << 1; + } else if (pix == V4L2_MBUS_FMT_Y8_1X8 || + pix == V4L2_MBUS_FMT_UV8_1X8) { + *line_len = width; + *line_len_c = width; + } else { + /* YUV 420 */ + /* round width to upper 32 byte boundary */ + *line_len = width; + *line_len_c = width; + } + /* adjust the line len to be a multiple of 32 */ + *line_len += 31; + *line_len &= ~0x1f; + *line_len_c += 31; + *line_len_c &= ~0x1f; +} + +static inline int +resizer_validate_output_image_format(struct device *dev, + struct v4l2_mbus_framefmt *format, + int *in_line_len, int *in_line_len_c) +{ + if (format->code != V4L2_MBUS_FMT_UYVY8_2X8 && + format->code != V4L2_MBUS_FMT_Y8_1X8 && + format->code != V4L2_MBUS_FMT_UV8_1X8 && + format->code != V4L2_MBUS_FMT_YDYUYDYV8_1X16 && + format->code != V4L2_MBUS_FMT_SGRBG12_1X12) { + dev_err(dev, "Invalid Mbus format, %d\n", format->code); + return -EINVAL; + } + if (!format->width || !format->height) { + dev_err(dev, "invalid width or height\n"); + return -EINVAL; + } + resizer_calculate_line_length(format->code, format->width, + format->height, in_line_len, in_line_len_c); + return 0; +} + +static void +resizer_configure_passthru(struct vpfe_resizer_device *resizer, int bypass) +{ + struct resizer_params *param = &resizer->config; + + param->rsz_rsc_param[RSZ_A].cen = DISABLE; + param->rsz_rsc_param[RSZ_A].yen = DISABLE; + param->rsz_rsc_param[RSZ_A].v_phs_y = 0; + param->rsz_rsc_param[RSZ_A].v_phs_c = 0; + param->rsz_rsc_param[RSZ_A].v_dif = 256; + param->rsz_rsc_param[RSZ_A].v_lpf_int_y = 0; + param->rsz_rsc_param[RSZ_A].v_lpf_int_c = 0; + param->rsz_rsc_param[RSZ_A].h_phs = 0; + param->rsz_rsc_param[RSZ_A].h_dif = 256; + param->rsz_rsc_param[RSZ_A].h_lpf_int_y = 0; + param->rsz_rsc_param[RSZ_A].h_lpf_int_c = 0; + param->rsz_rsc_param[RSZ_A].dscale_en = DISABLE; + param->rsz2rgb[RSZ_A].rgb_en = DISABLE; + param->rsz_en[RSZ_A] = ENABLE; + param->rsz_en[RSZ_B] = DISABLE; + if (bypass) { + param->rsz_rsc_param[RSZ_A].i_vps = 0; + param->rsz_rsc_param[RSZ_A].i_hps = 0; + /* Raw Bypass */ + param->rsz_common.passthrough = BYPASS_ON; + } +} + +static void +configure_resizer_out_params(struct vpfe_resizer_device *resizer, int index, + void *output_spec, unsigned char partial, + unsigned flag) +{ + struct resizer_params *param = &resizer->config; + struct v4l2_mbus_framefmt *outformat; + struct vpfe_rsz_output_spec *output; + + if (index == RSZ_A && + resizer->resizer_a.output == RESIZER_OUTPUT_NONE) { + param->rsz_en[index] = DISABLE; + return; + } + if (index == RSZ_B && + resizer->resizer_b.output == RESIZER_OUTPUT_NONE) { + param->rsz_en[index] = DISABLE; + return; + } + output = (struct vpfe_rsz_output_spec *)output_spec; + param->rsz_en[index] = ENABLE; + if (partial) { + param->rsz_rsc_param[index].h_flip = output->h_flip; + param->rsz_rsc_param[index].v_flip = output->v_flip; + param->rsz_rsc_param[index].v_typ_y = output->v_typ_y; + param->rsz_rsc_param[index].v_typ_c = output->v_typ_c; + param->rsz_rsc_param[index].v_lpf_int_y = + output->v_lpf_int_y; + param->rsz_rsc_param[index].v_lpf_int_c = + output->v_lpf_int_c; + param->rsz_rsc_param[index].h_typ_y = output->h_typ_y; + param->rsz_rsc_param[index].h_typ_c = output->h_typ_c; + param->rsz_rsc_param[index].h_lpf_int_y = + output->h_lpf_int_y; + param->rsz_rsc_param[index].h_lpf_int_c = + output->h_lpf_int_c; + param->rsz_rsc_param[index].dscale_en = + output->en_down_scale; + param->rsz_rsc_param[index].h_dscale_ave_sz = + output->h_dscale_ave_sz; + param->rsz_rsc_param[index].v_dscale_ave_sz = + output->v_dscale_ave_sz; + param->ext_mem_param[index].user_y_ofst = + (output->user_y_ofst + 31) & ~0x1f; + param->ext_mem_param[index].user_c_ofst = + (output->user_c_ofst + 31) & ~0x1f; + return; + } + + if (index == RSZ_A) + outformat = &resizer->resizer_a.formats[RESIZER_PAD_SOURCE]; + else + outformat = &resizer->resizer_b.formats[RESIZER_PAD_SOURCE]; + param->rsz_rsc_param[index].o_vsz = outformat->height - 1; + param->rsz_rsc_param[index].o_hsz = outformat->width - 1; + param->ext_mem_param[index].rsz_sdr_ptr_s_y = output->vst_y; + param->ext_mem_param[index].rsz_sdr_ptr_e_y = outformat->height; + param->ext_mem_param[index].rsz_sdr_ptr_s_c = output->vst_c; + param->ext_mem_param[index].rsz_sdr_ptr_e_c = outformat->height; + + if (!flag) + return; + /* update common parameters */ + param->rsz_rsc_param[index].h_flip = output->h_flip; + param->rsz_rsc_param[index].v_flip = output->v_flip; + param->rsz_rsc_param[index].v_typ_y = output->v_typ_y; + param->rsz_rsc_param[index].v_typ_c = output->v_typ_c; + param->rsz_rsc_param[index].v_lpf_int_y = output->v_lpf_int_y; + param->rsz_rsc_param[index].v_lpf_int_c = output->v_lpf_int_c; + param->rsz_rsc_param[index].h_typ_y = output->h_typ_y; + param->rsz_rsc_param[index].h_typ_c = output->h_typ_c; + param->rsz_rsc_param[index].h_lpf_int_y = output->h_lpf_int_y; + param->rsz_rsc_param[index].h_lpf_int_c = output->h_lpf_int_c; + param->rsz_rsc_param[index].dscale_en = output->en_down_scale; + param->rsz_rsc_param[index].h_dscale_ave_sz = output->h_dscale_ave_sz; + param->rsz_rsc_param[index].v_dscale_ave_sz = output->h_dscale_ave_sz; + param->ext_mem_param[index].user_y_ofst = + (output->user_y_ofst + 31) & ~0x1f; + param->ext_mem_param[index].user_c_ofst = + (output->user_c_ofst + 31) & ~0x1f; +} + +/* + * resizer_calculate_resize_ratios() - Calculates resize ratio for resizer + * A or B. This is called after setting + * the input size or output size. + * @resizer: Pointer to VPFE resizer subdevice. + * @index: index RSZ_A-resizer-A RSZ_B-resizer-B. + */ +void +resizer_calculate_resize_ratios(struct vpfe_resizer_device *resizer, int index) +{ + struct resizer_params *param = &resizer->config; + struct v4l2_mbus_framefmt *informat, *outformat; + + informat = &resizer->crop_resizer.formats[RESIZER_CROP_PAD_SINK]; + + if (index == RSZ_A) + outformat = &resizer->resizer_a.formats[RESIZER_PAD_SOURCE]; + else + outformat = &resizer->resizer_b.formats[RESIZER_PAD_SOURCE]; + + if (outformat->field != V4L2_FIELD_INTERLACED) + param->rsz_rsc_param[index].v_dif = + ((informat->height) * 256) / (outformat->height); + else + param->rsz_rsc_param[index].v_dif = + ((informat->height >> 1) * 256) / (outformat->height); + param->rsz_rsc_param[index].h_dif = + ((informat->width) * 256) / (outformat->width); +} + +void +static resizer_enable_422_420_conversion(struct resizer_params *param, + int index, bool en) +{ + param->rsz_rsc_param[index].cen = en; + param->rsz_rsc_param[index].yen = en; +} + +/* resizer_calculate_sdram_offsets() - This function calculates the offsets from + * start of buffer for the C plane when + * output format is YUV420SP. It also + * calculates the offsets from the start of + * the buffer when the image is flipped + * vertically or horizontally for ycbcr/y/c + * planes. + * @resizer: Pointer to resizer subdevice. + * @index: index RSZ_A-resizer-A RSZ_B-resizer-B. + */ +static int +resizer_calculate_sdram_offsets(struct vpfe_resizer_device *resizer, int index) +{ + struct resizer_params *param = &resizer->config; + struct v4l2_mbus_framefmt *outformat; + int bytesperpixel = 2; + int image_height; + int image_width; + int yuv_420 = 0; + int offset = 0; + + if (index == RSZ_A) + outformat = &resizer->resizer_a.formats[RESIZER_PAD_SOURCE]; + else + outformat = &resizer->resizer_b.formats[RESIZER_PAD_SOURCE]; + + image_height = outformat->height + 1; + image_width = outformat->width + 1; + param->ext_mem_param[index].c_offset = 0; + param->ext_mem_param[index].flip_ofst_y = 0; + param->ext_mem_param[index].flip_ofst_c = 0; + if (outformat->code == V4L2_MBUS_FMT_YDYUYDYV8_1X16) { + /* YUV 420 */ + yuv_420 = 1; + bytesperpixel = 1; + } + + if (param->rsz_rsc_param[index].h_flip) + /* width * bytesperpixel - 1 */ + offset = (image_width * bytesperpixel) - 1; + if (param->rsz_rsc_param[index].v_flip) + offset += (image_height - 1) * + param->ext_mem_param[index].rsz_sdr_oft_y; + param->ext_mem_param[index].flip_ofst_y = offset; + if (!yuv_420) + return 0; + offset = 0; + /* half height for c-plane */ + if (param->rsz_rsc_param[index].h_flip) + /* width * bytesperpixel - 1 */ + offset = image_width - 1; + if (param->rsz_rsc_param[index].v_flip) + offset += (((image_height >> 1) - 1) * + param->ext_mem_param[index].rsz_sdr_oft_c); + param->ext_mem_param[index].flip_ofst_c = offset; + param->ext_mem_param[index].c_offset = + param->ext_mem_param[index].rsz_sdr_oft_y * image_height; + return 0; +} + +int resizer_configure_output_win(struct vpfe_resizer_device *resizer) +{ + struct resizer_params *param = &resizer->config; + struct vpfe_rsz_output_spec output_specs; + struct v4l2_mbus_framefmt *outformat; + int line_len_c; + int line_len; + int ret; + + outformat = &resizer->resizer_a.formats[RESIZER_PAD_SOURCE]; + + output_specs.vst_y = param->user_config.vst; + if (outformat->code == V4L2_MBUS_FMT_YDYUYDYV8_1X16) + output_specs.vst_c = param->user_config.vst; + + configure_resizer_out_params(resizer, RSZ_A, &output_specs, 0, 0); + resizer_calculate_line_length(outformat->code, + param->rsz_rsc_param[0].o_hsz + 1, + param->rsz_rsc_param[0].o_vsz + 1, + &line_len, &line_len_c); + param->ext_mem_param[0].rsz_sdr_oft_y = line_len; + param->ext_mem_param[0].rsz_sdr_oft_c = line_len_c; + resizer_calculate_resize_ratios(resizer, RSZ_A); + if (param->rsz_en[RSZ_B]) + resizer_calculate_resize_ratios(resizer, RSZ_B); + + if (outformat->code == V4L2_MBUS_FMT_YDYUYDYV8_1X16) + resizer_enable_422_420_conversion(param, RSZ_A, ENABLE); + else + resizer_enable_422_420_conversion(param, RSZ_A, DISABLE); + + ret = resizer_calculate_sdram_offsets(resizer, RSZ_A); + if (!ret && param->rsz_en[RSZ_B]) + ret = resizer_calculate_sdram_offsets(resizer, RSZ_B); + + if (ret) + pr_err("Error in calculating sdram offsets\n"); + return ret; +} + +static int +resizer_calculate_down_scale_f_div_param(struct device *dev, + int input_width, int output_width, + struct resizer_scale_param *param) +{ + /* rsz = R, input_width = H, output width = h in the equation */ + unsigned int two_power; + unsigned int upper_h1; + unsigned int upper_h2; + unsigned int val1; + unsigned int val; + unsigned int rsz; + unsigned int h1; + unsigned int h2; + unsigned int o; + unsigned int n; + + upper_h1 = input_width >> 1; + n = param->h_dscale_ave_sz; + /* 2 ^ (scale+1) */ + two_power = 1 << (n + 1); + upper_h1 = (upper_h1 >> (n + 1)) << (n + 1); + upper_h2 = input_width - upper_h1; + if (upper_h2 % two_power) { + dev_err(dev, "frame halves to be a multiple of 2 power n+1\n"); + return -EINVAL; + } + two_power = 1 << n; + rsz = (input_width << 8) / output_width; + val = rsz * two_power; + val = ((upper_h1 << 8) / val) + 1; + if (!(val % 2)) { + h1 = val; + } else { + val = upper_h1 << 8; + val >>= n + 1; + val -= rsz >> 1; + val /= rsz << 1; + val <<= 1; + val += 2; + h1 = val; + } + o = 10 + (two_power << 2); + if (((input_width << 7) / rsz) % 2) + o += (((CEIL(rsz, 1024)) << 1) << n); + h2 = output_width - h1; + /* phi */ + val = (h1 * rsz) - (((upper_h1 - (o - 10)) / two_power) << 8); + /* skip */ + val1 = ((val - 1024) >> 9) << 1; + param->f_div.num_passes = MAX_PASSES; + param->f_div.pass[0].o_hsz = h1 - 1; + param->f_div.pass[0].i_hps = 0; + param->f_div.pass[0].h_phs = 0; + param->f_div.pass[0].src_hps = 0; + param->f_div.pass[0].src_hsz = upper_h1 + o; + param->f_div.pass[1].o_hsz = h2 - 1; + param->f_div.pass[1].i_hps = 10 + (val1 * two_power); + param->f_div.pass[1].h_phs = (val - (val1 << 8)); + param->f_div.pass[1].src_hps = upper_h1 - o; + param->f_div.pass[1].src_hsz = upper_h2 + o; + + return 0; +} + +static int +resizer_configure_common_in_params(struct vpfe_resizer_device *resizer) +{ + struct vpfe_device *vpfe_dev = to_vpfe_device(resizer); + struct resizer_params *param = &resizer->config; + struct vpfe_rsz_config_params *user_config; + struct v4l2_mbus_framefmt *informat; + + informat = &resizer->crop_resizer.formats[RESIZER_CROP_PAD_SINK]; + user_config = &resizer->config.user_config; + param->rsz_common.vps = param->user_config.vst; + param->rsz_common.hps = param->user_config.hst; + + if (vpfe_ipipeif_decimation_enabled(vpfe_dev)) + param->rsz_common.hsz = (((informat->width - 1) * + IPIPEIF_RSZ_CONST) / vpfe_ipipeif_get_rsz(vpfe_dev)); + else + param->rsz_common.hsz = informat->width - 1; + + if (informat->field == V4L2_FIELD_INTERLACED) + param->rsz_common.vsz = (informat->height - 1) >> 1; + else + param->rsz_common.vsz = informat->height - 1; + + param->rsz_common.raw_flip = 0; + + if (resizer->crop_resizer.input == RESIZER_CROP_INPUT_IPIPEIF) + param->rsz_common.source = IPIPEIF_DATA; + else + param->rsz_common.source = IPIPE_DATA; + + switch (informat->code) { + case V4L2_MBUS_FMT_UYVY8_2X8: + param->rsz_common.src_img_fmt = RSZ_IMG_422; + param->rsz_common.raw_flip = 0; + break; + + case V4L2_MBUS_FMT_Y8_1X8: + param->rsz_common.src_img_fmt = RSZ_IMG_420; + /* Select y */ + param->rsz_common.y_c = 0; + param->rsz_common.raw_flip = 0; + break; + + case V4L2_MBUS_FMT_UV8_1X8: + param->rsz_common.src_img_fmt = RSZ_IMG_420; + /* Select y */ + param->rsz_common.y_c = 1; + param->rsz_common.raw_flip = 0; + break; + + case V4L2_MBUS_FMT_SGRBG12_1X12: + param->rsz_common.raw_flip = 1; + break; + + default: + param->rsz_common.src_img_fmt = RSZ_IMG_422; + param->rsz_common.source = IPIPE_DATA; + } + + param->rsz_common.yuv_y_min = user_config->yuv_y_min; + param->rsz_common.yuv_y_max = user_config->yuv_y_max; + param->rsz_common.yuv_c_min = user_config->yuv_c_min; + param->rsz_common.yuv_c_max = user_config->yuv_c_max; + param->rsz_common.out_chr_pos = user_config->out_chr_pos; + param->rsz_common.rsz_seq_crv = user_config->chroma_sample_even; + + return 0; +} +static int +resizer_configure_in_continious_mode(struct vpfe_resizer_device *resizer) +{ + struct device *dev = resizer->crop_resizer.subdev.v4l2_dev->dev; + struct resizer_params *param = &resizer->config; + struct vpfe_rsz_config_params *cont_config; + int line_len_c; + int line_len; + int ret; + + if (resizer->resizer_a.output != RESIZER_OUPUT_MEMORY) { + dev_err(dev, "enable resizer - Resizer-A\n"); + return -EINVAL; + } + + cont_config = &resizer->config.user_config; + param->rsz_en[RSZ_A] = ENABLE; + configure_resizer_out_params(resizer, RSZ_A, + &cont_config->output1, 1, 0); + param->rsz_en[RSZ_B] = DISABLE; + param->oper_mode = RESIZER_MODE_CONTINIOUS; + + if (resizer->resizer_b.output == RESIZER_OUPUT_MEMORY) { + struct v4l2_mbus_framefmt *outformat2; + + param->rsz_en[RSZ_B] = ENABLE; + outformat2 = &resizer->resizer_b.formats[RESIZER_PAD_SOURCE]; + ret = resizer_validate_output_image_format(dev, outformat2, + &line_len, &line_len_c); + if (ret) + return ret; + param->ext_mem_param[RSZ_B].rsz_sdr_oft_y = line_len; + param->ext_mem_param[RSZ_B].rsz_sdr_oft_c = line_len_c; + configure_resizer_out_params(resizer, RSZ_B, + &cont_config->output2, 0, 1); + if (outformat2->code == V4L2_MBUS_FMT_YDYUYDYV8_1X16) + resizer_enable_422_420_conversion(param, + RSZ_B, ENABLE); + else + resizer_enable_422_420_conversion(param, + RSZ_B, DISABLE); + } + resizer_configure_common_in_params(resizer); + ret = resizer_configure_output_win(resizer); + if (ret) + return ret; + + param->rsz_common.passthrough = cont_config->bypass; + if (cont_config->bypass) + resizer_configure_passthru(resizer, 1); + + return 0; +} + +static inline int +resizer_validate_input_image_format(struct device *dev, + enum v4l2_mbus_pixelcode pix, + int width, int height, int *line_len) +{ + int val; + + if (pix != V4L2_MBUS_FMT_UYVY8_2X8 && + pix != V4L2_MBUS_FMT_Y8_1X8 && + pix != V4L2_MBUS_FMT_UV8_1X8 && + pix != V4L2_MBUS_FMT_SGRBG12_1X12) { + dev_err(dev, + "resizer validate output: pix format not supported, %d\n", pix); + return -EINVAL; + } + + if (!width || !height) { + dev_err(dev, + "resizer validate input: invalid width or height\n"); + return -EINVAL; + } + + if (pix == V4L2_MBUS_FMT_UV8_1X8) + resizer_calculate_line_length(pix, width, + height, &val, line_len); + else + resizer_calculate_line_length(pix, width, + height, line_len, &val); + + return 0; +} + +static int +resizer_validate_decimation(struct device *dev, enum ipipeif_decimation dec_en, + unsigned char rsz, unsigned char frame_div_mode_en, + int width) +{ + if (dec_en && frame_div_mode_en) { + dev_err(dev, + "dec_en & frame_div_mode_en can not enabled simultaneously\n"); + return -EINVAL; + } + + if (frame_div_mode_en) { + dev_err(dev, "frame_div_mode mode not supported\n"); + return -EINVAL; + } + + if (!dec_en) + return 0; + + if (width <= VPFE_IPIPE_MAX_INPUT_WIDTH) { + dev_err(dev, + "image width to be more than %d for decimation\n", + VPFE_IPIPE_MAX_INPUT_WIDTH); + return -EINVAL; + } + + if (rsz < IPIPEIF_RSZ_MIN || rsz > IPIPEIF_RSZ_MAX) { + dev_err(dev, "rsz range is %d to %d\n", + IPIPEIF_RSZ_MIN, IPIPEIF_RSZ_MAX); + return -EINVAL; + } + + return 0; +} + +/* resizer_calculate_normal_f_div_param() - Algorithm to calculate the frame + * division parameters for resizer. + * in normal mode. + */ +static int +resizer_calculate_normal_f_div_param(struct device *dev, int input_width, + int output_width, struct resizer_scale_param *param) +{ + /* rsz = R, input_width = H, output width = h in the equation */ + unsigned int val1; + unsigned int rsz; + unsigned int val; + unsigned int h1; + unsigned int h2; + unsigned int o; + + if (output_width > input_width) { + dev_err(dev, "frame div mode is used for scale down only\n"); + return -EINVAL; + } + + rsz = (input_width << 8) / output_width; + val = rsz << 1; + val = ((input_width << 8) / val) + 1; + o = 14; + if (!(val % 2)) { + h1 = val; + } else { + val = (input_width << 7); + val -= rsz >> 1; + val /= rsz << 1; + val <<= 1; + val += 2; + o += ((CEIL(rsz, 1024)) << 1); + h1 = val; + } + h2 = output_width - h1; + /* phi */ + val = (h1 * rsz) - (((input_width >> 1) - o) << 8); + /* skip */ + val1 = ((val - 1024) >> 9) << 1; + param->f_div.num_passes = MAX_PASSES; + param->f_div.pass[0].o_hsz = h1 - 1; + param->f_div.pass[0].i_hps = 0; + param->f_div.pass[0].h_phs = 0; + param->f_div.pass[0].src_hps = 0; + param->f_div.pass[0].src_hsz = (input_width >> 2) + o; + param->f_div.pass[1].o_hsz = h2 - 1; + param->f_div.pass[1].i_hps = val1; + param->f_div.pass[1].h_phs = (val - (val1 << 8)); + param->f_div.pass[1].src_hps = (input_width >> 2) - o; + param->f_div.pass[1].src_hsz = (input_width >> 2) + o; + + return 0; +} + +static int +resizer_configure_in_single_shot_mode(struct vpfe_resizer_device *resizer) +{ + struct vpfe_rsz_config_params *config = &resizer->config.user_config; + struct device *dev = resizer->crop_resizer.subdev.v4l2_dev->dev; + struct vpfe_device *vpfe_dev = to_vpfe_device(resizer); + struct v4l2_mbus_framefmt *outformat1, *outformat2; + struct resizer_params *param = &resizer->config; + struct v4l2_mbus_framefmt *informat; + int decimation; + int line_len_c; + int line_len; + int rsz; + int ret; + + informat = &resizer->crop_resizer.formats[RESIZER_CROP_PAD_SINK]; + outformat1 = &resizer->resizer_a.formats[RESIZER_PAD_SOURCE]; + outformat2 = &resizer->resizer_b.formats[RESIZER_PAD_SOURCE]; + + decimation = vpfe_ipipeif_decimation_enabled(vpfe_dev); + rsz = vpfe_ipipeif_get_rsz(vpfe_dev); + if (decimation && param->user_config.frame_div_mode_en) { + dev_err(dev, + "dec_en & frame_div_mode_en cannot enabled simultaneously\n"); + return -EINVAL; + } + + ret = resizer_validate_decimation(dev, decimation, rsz, + param->user_config.frame_div_mode_en, informat->width); + if (ret) + return -EINVAL; + + ret = resizer_validate_input_image_format(dev, informat->code, + informat->width, informat->height, &line_len); + if (ret) + return -EINVAL; + + if (resizer->resizer_a.output != RESIZER_OUTPUT_NONE) { + param->rsz_en[RSZ_A] = ENABLE; + ret = resizer_validate_output_image_format(dev, outformat1, + &line_len, &line_len_c); + if (ret) + return ret; + param->ext_mem_param[RSZ_A].rsz_sdr_oft_y = line_len; + param->ext_mem_param[RSZ_A].rsz_sdr_oft_c = line_len_c; + configure_resizer_out_params(resizer, RSZ_A, + ¶m->user_config.output1, 0, 1); + + if (outformat1->code == V4L2_MBUS_FMT_SGRBG12_1X12) + param->rsz_common.raw_flip = 1; + else + param->rsz_common.raw_flip = 0; + + if (outformat1->code == V4L2_MBUS_FMT_YDYUYDYV8_1X16) + resizer_enable_422_420_conversion(param, + RSZ_A, ENABLE); + else + resizer_enable_422_420_conversion(param, + RSZ_A, DISABLE); + } + + if (resizer->resizer_b.output != RESIZER_OUTPUT_NONE) { + param->rsz_en[RSZ_B] = ENABLE; + ret = resizer_validate_output_image_format(dev, outformat2, + &line_len, &line_len_c); + if (ret) + return ret; + param->ext_mem_param[RSZ_B].rsz_sdr_oft_y = line_len; + param->ext_mem_param[RSZ_B].rsz_sdr_oft_c = line_len_c; + configure_resizer_out_params(resizer, RSZ_B, + ¶m->user_config.output2, 0, 1); + if (outformat2->code == V4L2_MBUS_FMT_YDYUYDYV8_1X16) + resizer_enable_422_420_conversion(param, + RSZ_B, ENABLE); + else + resizer_enable_422_420_conversion(param, + RSZ_B, DISABLE); + } + + resizer_configure_common_in_params(resizer); + if (resizer->resizer_a.output != RESIZER_OUTPUT_NONE) { + resizer_calculate_resize_ratios(resizer, RSZ_A); + resizer_calculate_sdram_offsets(resizer, RSZ_A); + /* Overriding resize ratio calculation */ + if (informat->code == V4L2_MBUS_FMT_UV8_1X8) { + param->rsz_rsc_param[RSZ_A].v_dif = + (((informat->height + 1) * 2) * 256) / + (param->rsz_rsc_param[RSZ_A].o_vsz + 1); + } + } + + if (resizer->resizer_b.output != RESIZER_OUTPUT_NONE) { + resizer_calculate_resize_ratios(resizer, RSZ_B); + resizer_calculate_sdram_offsets(resizer, RSZ_B); + /* Overriding resize ratio calculation */ + if (informat->code == V4L2_MBUS_FMT_UV8_1X8) { + param->rsz_rsc_param[RSZ_B].v_dif = + (((informat->height + 1) * 2) * 256) / + (param->rsz_rsc_param[RSZ_B].o_vsz + 1); + } + } + if (param->user_config.frame_div_mode_en && + param->rsz_en[RSZ_A]) { + if (!param->rsz_rsc_param[RSZ_A].dscale_en) + ret = resizer_calculate_normal_f_div_param(dev, + informat->width, + param->rsz_rsc_param[RSZ_A].o_vsz + 1, + ¶m->rsz_rsc_param[RSZ_A]); + else + ret = resizer_calculate_down_scale_f_div_param(dev, + informat->width, + param->rsz_rsc_param[RSZ_A].o_vsz + 1, + ¶m->rsz_rsc_param[RSZ_A]); + if (ret) + return -EINVAL; + } + if (param->user_config.frame_div_mode_en && + param->rsz_en[RSZ_B]) { + if (!param->rsz_rsc_param[RSZ_B].dscale_en) + ret = resizer_calculate_normal_f_div_param(dev, + informat->width, + param->rsz_rsc_param[RSZ_B].o_vsz + 1, + ¶m->rsz_rsc_param[RSZ_B]); + else + ret = resizer_calculate_down_scale_f_div_param(dev, + informat->width, + param->rsz_rsc_param[RSZ_B].o_vsz + 1, + ¶m->rsz_rsc_param[RSZ_B]); + if (ret) + return -EINVAL; + } + param->rsz_common.passthrough = config->bypass; + if (config->bypass) + resizer_configure_passthru(resizer, 1); + return 0; +} + +static void +resizer_set_defualt_configuration(struct vpfe_resizer_device *resizer) +{ +#define WIDTH_I 640 +#define HEIGHT_I 480 +#define WIDTH_O 640 +#define HEIGHT_O 480 + const struct resizer_params rsz_default_config = { + .oper_mode = RESIZER_MODE_ONE_SHOT, + .rsz_common = { + .vsz = HEIGHT_I - 1, + .hsz = WIDTH_I - 1, + .src_img_fmt = RSZ_IMG_422, + .raw_flip = 1, /* flip preserve Raw format */ + .source = IPIPE_DATA, + .passthrough = BYPASS_OFF, + .yuv_y_max = 255, + .yuv_c_max = 255, + .rsz_seq_crv = DISABLE, + .out_chr_pos = VPFE_IPIPE_YUV422_CHR_POS_COSITE, + }, + .rsz_rsc_param = { + { + .h_flip = DISABLE, + .v_flip = DISABLE, + .cen = DISABLE, + .yen = DISABLE, + .o_vsz = HEIGHT_O - 1, + .o_hsz = WIDTH_O - 1, + .v_dif = 256, + .v_typ_y = VPFE_RSZ_INTP_CUBIC, + .h_typ_c = VPFE_RSZ_INTP_CUBIC, + .h_dif = 256, + .h_typ_y = VPFE_RSZ_INTP_CUBIC, + .h_typ_c = VPFE_RSZ_INTP_CUBIC, + .h_dscale_ave_sz = + VPFE_IPIPE_DWN_SCALE_1_OVER_2, + .v_dscale_ave_sz = + VPFE_IPIPE_DWN_SCALE_1_OVER_2, + }, + { + .h_flip = DISABLE, + .v_flip = DISABLE, + .cen = DISABLE, + .yen = DISABLE, + .o_vsz = HEIGHT_O - 1, + .o_hsz = WIDTH_O - 1, + .v_dif = 256, + .v_typ_y = VPFE_RSZ_INTP_CUBIC, + .h_typ_c = VPFE_RSZ_INTP_CUBIC, + .h_dif = 256, + .h_typ_y = VPFE_RSZ_INTP_CUBIC, + .h_typ_c = VPFE_RSZ_INTP_CUBIC, + .h_dscale_ave_sz = + VPFE_IPIPE_DWN_SCALE_1_OVER_2, + .v_dscale_ave_sz = + VPFE_IPIPE_DWN_SCALE_1_OVER_2, + }, + }, + .rsz2rgb = { + { + .rgb_en = DISABLE + }, + { + .rgb_en = DISABLE + } + }, + .ext_mem_param = { + { + .rsz_sdr_oft_y = WIDTH_O << 1, + .rsz_sdr_ptr_e_y = HEIGHT_O, + .rsz_sdr_oft_c = WIDTH_O, + .rsz_sdr_ptr_e_c = HEIGHT_O >> 1, + }, + { + .rsz_sdr_oft_y = WIDTH_O << 1, + .rsz_sdr_ptr_e_y = HEIGHT_O, + .rsz_sdr_oft_c = WIDTH_O, + .rsz_sdr_ptr_e_c = HEIGHT_O, + }, + }, + .rsz_en[0] = ENABLE, + .rsz_en[1] = DISABLE, + .user_config = { + .output1 = { + .v_typ_y = VPFE_RSZ_INTP_CUBIC, + .v_typ_c = VPFE_RSZ_INTP_CUBIC, + .h_typ_y = VPFE_RSZ_INTP_CUBIC, + .h_typ_c = VPFE_RSZ_INTP_CUBIC, + .h_dscale_ave_sz = + VPFE_IPIPE_DWN_SCALE_1_OVER_2, + .v_dscale_ave_sz = + VPFE_IPIPE_DWN_SCALE_1_OVER_2, + }, + .output2 = { + .v_typ_y = VPFE_RSZ_INTP_CUBIC, + .v_typ_c = VPFE_RSZ_INTP_CUBIC, + .h_typ_y = VPFE_RSZ_INTP_CUBIC, + .h_typ_c = VPFE_RSZ_INTP_CUBIC, + .h_dscale_ave_sz = + VPFE_IPIPE_DWN_SCALE_1_OVER_2, + .v_dscale_ave_sz = + VPFE_IPIPE_DWN_SCALE_1_OVER_2, + }, + .yuv_y_max = 255, + .yuv_c_max = 255, + .out_chr_pos = VPFE_IPIPE_YUV422_CHR_POS_COSITE, + }, + }; + memset(&resizer->config, 0, sizeof(struct resizer_params)); + memcpy(&resizer->config, &rsz_default_config, + sizeof(struct resizer_params)); +} + +/* + * resizer_set_configuration() - set resizer config + * @resizer: vpfe resizer device pointer. + * @chan_config: resizer channel configuration. + */ +static int +resizer_set_configuration(struct vpfe_resizer_device *resizer, + struct vpfe_rsz_config *chan_config) +{ + if (!chan_config->config) + resizer_set_defualt_configuration(resizer); + else + if (copy_from_user(&resizer->config.user_config, + chan_config->config, sizeof(struct vpfe_rsz_config_params))) + return -EFAULT; + + return 0; +} + +/* + * resizer_get_configuration() - get resizer config + * @resizer: vpfe resizer device pointer. + * @channel: image processor logical channel. + * @chan_config: resizer channel configuration. + */ +static int +resizer_get_configuration(struct vpfe_resizer_device *resizer, + struct vpfe_rsz_config *chan_config) +{ + struct device *dev = resizer->crop_resizer.subdev.v4l2_dev->dev; + + if (!chan_config->config) { + dev_err(dev, "Resizer channel invalid pointer\n"); + return -EINVAL; + } + + if (copy_to_user((void *)chan_config->config, + (void *)&resizer->config.user_config, + sizeof(struct vpfe_rsz_config_params))) { + dev_err(dev, "resizer_get_configuration: Error in copy to user\n"); + return -EFAULT; + } + + return 0; +} + +/* + * VPFE video operations + */ + +/* + * resizer_a_video_out_queue() - RESIZER-A video out queue + * @vpfe_dev: vpfe device pointer. + * @addr: buffer address. + */ +static int resizer_a_video_out_queue(struct vpfe_device *vpfe_dev, + unsigned long addr) +{ + struct vpfe_resizer_device *resizer = &vpfe_dev->vpfe_resizer; + + return resizer_set_outaddr(resizer->base_addr, + &resizer->config, RSZ_A, addr); +} + +/* + * resizer_b_video_out_queue() - RESIZER-B video out queue + * @vpfe_dev: vpfe device pointer. + * @addr: buffer address. + */ +static int resizer_b_video_out_queue(struct vpfe_device *vpfe_dev, + unsigned long addr) +{ + struct vpfe_resizer_device *resizer = &vpfe_dev->vpfe_resizer; + + return resizer_set_outaddr(resizer->base_addr, + &resizer->config, RSZ_B, addr); +} + +static const struct vpfe_video_operations resizer_a_video_ops = { + .queue = resizer_a_video_out_queue, +}; + +static const struct vpfe_video_operations resizer_b_video_ops = { + .queue = resizer_b_video_out_queue, +}; + +static void resizer_enable(struct vpfe_resizer_device *resizer, int en) +{ + struct vpfe_device *vpfe_dev = to_vpfe_device(resizer); + u16 ipipeif_sink = vpfe_dev->vpfe_ipipeif.input; + unsigned char val; + + if (resizer->crop_resizer.input == RESIZER_CROP_INPUT_NONE) + return; + + if (resizer->crop_resizer.input == RESIZER_CROP_INPUT_IPIPEIF && + ipipeif_sink == IPIPEIF_INPUT_MEMORY) { + do { + val = regr_rsz(resizer->base_addr, RSZ_SRC_EN); + } while (val); + + if (resizer->resizer_a.output != RESIZER_OUTPUT_NONE) { + do { + val = regr_rsz(resizer->base_addr, RSZ_A); + } while (val); + } + if (resizer->resizer_b.output != RESIZER_OUTPUT_NONE) { + do { + val = regr_rsz(resizer->base_addr, RSZ_B); + } while (val); + } + } + if (resizer->resizer_a.output != RESIZER_OUTPUT_NONE) + rsz_enable(resizer->base_addr, RSZ_A, en); + + if (resizer->resizer_b.output != RESIZER_OUTPUT_NONE) + rsz_enable(resizer->base_addr, RSZ_B, en); +} + + +/* + * resizer_ss_isr() - resizer module single-shot buffer scheduling isr + * @resizer: vpfe resizer device pointer. + */ +static void resizer_ss_isr(struct vpfe_resizer_device *resizer) +{ + struct vpfe_video_device *video_out = &resizer->resizer_a.video_out; + struct vpfe_video_device *video_out2 = &resizer->resizer_b.video_out; + struct vpfe_device *vpfe_dev = to_vpfe_device(resizer); + struct vpfe_pipeline *pipe = &video_out->pipe; + u16 ipipeif_sink = vpfe_dev->vpfe_ipipeif.input; + u32 val; + + if (ipipeif_sink != IPIPEIF_INPUT_MEMORY) + return; + + if (resizer->resizer_a.output == RESIZER_OUPUT_MEMORY) { + val = vpss_dma_complete_interrupt(); + if (val != 0 && val != 2) + return; + } + + if (resizer->resizer_a.output == RESIZER_OUPUT_MEMORY) { + spin_lock(&video_out->dma_queue_lock); + vpfe_video_process_buffer_complete(video_out); + video_out->state = VPFE_VIDEO_BUFFER_NOT_QUEUED; + vpfe_video_schedule_next_buffer(video_out); + spin_unlock(&video_out->dma_queue_lock); + } + + /* If resizer B is enabled */ + if (pipe->output_num > 1 && resizer->resizer_b.output == + RESIZER_OUPUT_MEMORY) { + spin_lock(&video_out->dma_queue_lock); + vpfe_video_process_buffer_complete(video_out2); + video_out2->state = VPFE_VIDEO_BUFFER_NOT_QUEUED; + vpfe_video_schedule_next_buffer(video_out2); + spin_unlock(&video_out2->dma_queue_lock); + } + + /* start HW if buffers are queued */ + if (vpfe_video_is_pipe_ready(pipe) && + resizer->resizer_a.output == RESIZER_OUPUT_MEMORY) { + resizer_enable(resizer, 1); + vpfe_ipipe_enable(vpfe_dev, 1); + vpfe_ipipeif_enable(vpfe_dev); + } +} + +/* + * vpfe_resizer_buffer_isr() - resizer module buffer scheduling isr + * @resizer: vpfe resizer device pointer. + */ +void vpfe_resizer_buffer_isr(struct vpfe_resizer_device *resizer) +{ + struct vpfe_device *vpfe_dev = to_vpfe_device(resizer); + struct vpfe_video_device *video_out = &resizer->resizer_a.video_out; + struct vpfe_video_device *video_out2 = &resizer->resizer_b.video_out; + struct vpfe_pipeline *pipe = &resizer->resizer_a.video_out.pipe; + enum v4l2_field field; + int fid; + + if (!video_out->started) + return; + + if (resizer->crop_resizer.input == RESIZER_CROP_INPUT_NONE) + return; + + field = video_out->fmt.fmt.pix.field; + if (field == V4L2_FIELD_NONE) { + /* handle progressive frame capture */ + if (video_out->cur_frm != video_out->next_frm) { + vpfe_video_process_buffer_complete(video_out); + if (pipe->output_num > 1) + vpfe_video_process_buffer_complete(video_out2); + } + + video_out->skip_frame_count--; + if (!video_out->skip_frame_count) { + video_out->skip_frame_count = + video_out->skip_frame_count_init; + rsz_src_enable(resizer->base_addr, 1); + } else { + rsz_src_enable(resizer->base_addr, 0); + } + return; + } + + /* handle interlaced frame capture */ + fid = vpfe_isif_get_fid(vpfe_dev); + + /* switch the software maintained field id */ + video_out->field_id ^= 1; + if (fid == video_out->field_id) { + /* + * we are in-sync here,continue. + * One frame is just being captured. If the + * next frame is available, release the current + * frame and move on + */ + if (fid == 0 && video_out->cur_frm != video_out->next_frm) { + vpfe_video_process_buffer_complete(video_out); + if (pipe->output_num > 1) + vpfe_video_process_buffer_complete(video_out2); + } + } else if (fid == 0) { + /* + * out of sync. Recover from any hardware out-of-sync. + * May loose one frame + */ + video_out->field_id = fid; + } +} + +/* + * vpfe_resizer_dma_isr() - resizer module dma isr + * @resizer: vpfe resizer device pointer. + */ +void vpfe_resizer_dma_isr(struct vpfe_resizer_device *resizer) +{ + struct vpfe_video_device *video_out2 = &resizer->resizer_b.video_out; + struct vpfe_video_device *video_out = &resizer->resizer_a.video_out; + struct vpfe_device *vpfe_dev = to_vpfe_device(resizer); + struct vpfe_pipeline *pipe = &video_out->pipe; + int schedule_capture = 0; + enum v4l2_field field; + int fid; + + if (!video_out->started) + return; + + if (pipe->state == VPFE_PIPELINE_STREAM_SINGLESHOT) { + resizer_ss_isr(resizer); + return; + } + + field = video_out->fmt.fmt.pix.field; + if (field == V4L2_FIELD_NONE) { + if (!list_empty(&video_out->dma_queue) && + video_out->cur_frm == video_out->next_frm) + schedule_capture = 1; + } else { + fid = vpfe_isif_get_fid(vpfe_dev); + if (fid == video_out->field_id) { + /* we are in-sync here,continue */ + if (fid == 1 && !list_empty(&video_out->dma_queue) && + video_out->cur_frm == video_out->next_frm) + schedule_capture = 1; + } + } + + if (!schedule_capture) + return; + + spin_lock(&video_out->dma_queue_lock); + vpfe_video_schedule_next_buffer(video_out); + spin_unlock(&video_out->dma_queue_lock); + if (pipe->output_num > 1) { + spin_lock(&video_out2->dma_queue_lock); + vpfe_video_schedule_next_buffer(video_out2); + spin_unlock(&video_out2->dma_queue_lock); + } +} + +/* + * V4L2 subdev operations + */ + +/* + * resizer_ioctl() - Handle resizer module private ioctl's + * @sd: pointer to v4l2 subdev structure + * @cmd: configuration command + * @arg: configuration argument + */ +static long resizer_ioctl(struct v4l2_subdev *sd, unsigned int cmd, void *arg) +{ + struct vpfe_resizer_device *resizer = v4l2_get_subdevdata(sd); + struct device *dev = resizer->crop_resizer.subdev.v4l2_dev->dev; + struct vpfe_rsz_config *user_config; + int ret = -ENOIOCTLCMD; + + if (&resizer->crop_resizer.subdev != sd) + return ret; + + switch (cmd) { + case VIDIOC_VPFE_RSZ_S_CONFIG: + user_config = (struct vpfe_rsz_config *)arg; + ret = resizer_set_configuration(resizer, user_config); + break; + + case VIDIOC_VPFE_RSZ_G_CONFIG: + user_config = (struct vpfe_rsz_config *)arg; + if (!user_config->config) { + dev_err(dev, "error in VIDIOC_VPFE_RSZ_G_CONFIG\n"); + return -EINVAL; + } + ret = resizer_get_configuration(resizer, user_config); + break; + } + return ret; +} + +static int resizer_do_hw_setup(struct vpfe_resizer_device *resizer) +{ + struct vpfe_device *vpfe_dev = to_vpfe_device(resizer); + u16 ipipeif_sink = vpfe_dev->vpfe_ipipeif.input; + u16 ipipeif_source = vpfe_dev->vpfe_ipipeif.output; + struct resizer_params *param = &resizer->config; + int ret = 0; + + if (resizer->resizer_a.output == RESIZER_OUPUT_MEMORY || + resizer->resizer_b.output == RESIZER_OUPUT_MEMORY) { + if (ipipeif_sink == IPIPEIF_INPUT_MEMORY && + ipipeif_source == IPIPEIF_OUTPUT_RESIZER) + ret = resizer_configure_in_single_shot_mode(resizer); + else + ret = resizer_configure_in_continious_mode(resizer); + if (ret) + return ret; + ret = config_rsz_hw(resizer, param); + } + return ret; +} + +/* + * resizer_set_stream() - Enable/Disable streaming on resizer subdev + * @sd: pointer to v4l2 subdev structure + * @enable: 1 == Enable, 0 == Disable + */ +static int resizer_set_stream(struct v4l2_subdev *sd, int enable) +{ + struct vpfe_resizer_device *resizer = v4l2_get_subdevdata(sd); + + if (&resizer->crop_resizer.subdev != sd) + return 0; + + if (resizer->resizer_a.output != RESIZER_OUPUT_MEMORY) + return 0; + + switch (enable) { + case 1: + if (resizer_do_hw_setup(resizer) < 0) + return -EINVAL; + resizer_enable(resizer, enable); + break; + + case 0: + resizer_enable(resizer, enable); + break; + } + + return 0; +} + +/* + * __resizer_get_format() - helper function for getting resizer format + * @sd: pointer to subdev. + * @fh: V4L2 subdev file handle. + * @pad: pad number. + * @which: wanted subdev format. + * Retun wanted mbus frame format. + */ +static struct v4l2_mbus_framefmt * +__resizer_get_format(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh, + unsigned int pad, enum v4l2_subdev_format_whence which) +{ + struct vpfe_resizer_device *resizer = v4l2_get_subdevdata(sd); + + if (which == V4L2_SUBDEV_FORMAT_TRY) + return v4l2_subdev_get_try_format(fh, pad); + if (&resizer->crop_resizer.subdev == sd) + return &resizer->crop_resizer.formats[pad]; + if (&resizer->resizer_a.subdev == sd) + return &resizer->resizer_a.formats[pad]; + if (&resizer->resizer_b.subdev == sd) + return &resizer->resizer_b.formats[pad]; + return NULL; +} + +/* + * resizer_try_format() - Handle try format by pad subdev method + * @sd: pointer to subdev. + * @fh: V4L2 subdev file handle. + * @pad: pad num. + * @fmt: pointer to v4l2 format structure. + * @which: wanted subdev format. + */ +static void +resizer_try_format(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh, + unsigned int pad, struct v4l2_mbus_framefmt *fmt, + enum v4l2_subdev_format_whence which) +{ + struct vpfe_resizer_device *resizer = v4l2_get_subdevdata(sd); + unsigned int max_out_height; + unsigned int max_out_width; + unsigned int i; + + if ((&resizer->resizer_a.subdev == sd && pad == RESIZER_PAD_SINK) || + (&resizer->resizer_b.subdev == sd && pad == RESIZER_PAD_SINK) || + (&resizer->crop_resizer.subdev == sd && + (pad == RESIZER_CROP_PAD_SOURCE || + pad == RESIZER_CROP_PAD_SOURCE2 || pad == RESIZER_CROP_PAD_SINK))) { + for (i = 0; i < ARRAY_SIZE(resizer_input_formats); i++) { + if (fmt->code == resizer_input_formats[i]) + break; + } + /* If not found, use UYVY as default */ + if (i >= ARRAY_SIZE(resizer_input_formats)) + fmt->code = V4L2_MBUS_FMT_UYVY8_2X8; + + fmt->width = clamp_t(u32, fmt->width, MIN_IN_WIDTH, + MAX_IN_WIDTH); + fmt->height = clamp_t(u32, fmt->height, MIN_IN_HEIGHT, + MAX_IN_HEIGHT); + } else if (&resizer->resizer_a.subdev == sd && + pad == RESIZER_PAD_SOURCE) { + max_out_width = IPIPE_MAX_OUTPUT_WIDTH_A; + max_out_height = IPIPE_MAX_OUTPUT_HEIGHT_A; + + for (i = 0; i < ARRAY_SIZE(resizer_output_formats); i++) { + if (fmt->code == resizer_output_formats[i]) + break; + } + /* If not found, use UYVY as default */ + if (i >= ARRAY_SIZE(resizer_output_formats)) + fmt->code = V4L2_MBUS_FMT_UYVY8_2X8; + + fmt->width = clamp_t(u32, fmt->width, MIN_OUT_WIDTH, + max_out_width); + fmt->width &= ~15; + fmt->height = clamp_t(u32, fmt->height, MIN_OUT_HEIGHT, + max_out_height); + } else if (&resizer->resizer_b.subdev == sd && + pad == RESIZER_PAD_SOURCE) { + max_out_width = IPIPE_MAX_OUTPUT_WIDTH_B; + max_out_height = IPIPE_MAX_OUTPUT_HEIGHT_B; + + for (i = 0; i < ARRAY_SIZE(resizer_output_formats); i++) { + if (fmt->code == resizer_output_formats[i]) + break; + } + /* If not found, use UYVY as default */ + if (i >= ARRAY_SIZE(resizer_output_formats)) + fmt->code = V4L2_MBUS_FMT_UYVY8_2X8; + + fmt->width = clamp_t(u32, fmt->width, MIN_OUT_WIDTH, + max_out_width); + fmt->width &= ~15; + fmt->height = clamp_t(u32, fmt->height, MIN_OUT_HEIGHT, + max_out_height); + } +} + +/* + * resizer_set_format() - Handle set format by pads subdev method + * @sd: pointer to v4l2 subdev structure + * @fh: V4L2 subdev file handle + * @fmt: pointer to v4l2 subdev format structure + * return -EINVAL or zero on success + */ +static int resizer_set_format(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh, + struct v4l2_subdev_format *fmt) +{ + struct vpfe_resizer_device *resizer = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt *format; + + format = __resizer_get_format(sd, fh, fmt->pad, fmt->which); + if (format == NULL) + return -EINVAL; + + resizer_try_format(sd, fh, fmt->pad, &fmt->format, fmt->which); + *format = fmt->format; + + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) + return 0; + + if (&resizer->crop_resizer.subdev == sd) { + if (fmt->pad == RESIZER_CROP_PAD_SINK) { + resizer->crop_resizer.formats[fmt->pad] = fmt->format; + } else if (fmt->pad == RESIZER_CROP_PAD_SOURCE && + resizer->crop_resizer.output == RESIZER_A) { + resizer->crop_resizer.formats[fmt->pad] = fmt->format; + resizer->crop_resizer. + formats[RESIZER_CROP_PAD_SOURCE2] = fmt->format; + } else if (fmt->pad == RESIZER_CROP_PAD_SOURCE2 && + resizer->crop_resizer.output2 == RESIZER_B) { + resizer->crop_resizer.formats[fmt->pad] = fmt->format; + resizer->crop_resizer. + formats[RESIZER_CROP_PAD_SOURCE] = fmt->format; + } else { + return -EINVAL; + } + } else if (&resizer->resizer_a.subdev == sd) { + if (fmt->pad == RESIZER_PAD_SINK) + resizer->resizer_a.formats[fmt->pad] = fmt->format; + else if (fmt->pad == RESIZER_PAD_SOURCE) + resizer->resizer_a.formats[fmt->pad] = fmt->format; + else + return -EINVAL; + } else if (&resizer->resizer_b.subdev == sd) { + if (fmt->pad == RESIZER_PAD_SINK) + resizer->resizer_b.formats[fmt->pad] = fmt->format; + else if (fmt->pad == RESIZER_PAD_SOURCE) + resizer->resizer_b.formats[fmt->pad] = fmt->format; + else + return -EINVAL; + } else { + return -EINVAL; + } + + return 0; +} + +/* + * resizer_get_format() - Retrieve the video format on a pad + * @sd: pointer to v4l2 subdev structure. + * @fh: V4L2 subdev file handle. + * @fmt: pointer to v4l2 subdev format structure + * return -EINVAL or zero on success + */ +static int resizer_get_format(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh, + struct v4l2_subdev_format *fmt) +{ + struct v4l2_mbus_framefmt *format; + + format = __resizer_get_format(sd, fh, fmt->pad, fmt->which); + if (format == NULL) + return -EINVAL; + + fmt->format = *format; + + return 0; +} + +/* + * resizer_enum_frame_size() - enum frame sizes on pads + * @sd: Pointer to subdevice. + * @fh: V4L2 subdev file handle. + * @code: pointer to v4l2_subdev_frame_size_enum structure. + */ +static int resizer_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_fh *fh, + struct v4l2_subdev_frame_size_enum *fse) +{ + struct v4l2_mbus_framefmt format; + + if (fse->index != 0) + return -EINVAL; + + format.code = fse->code; + format.width = 1; + format.height = 1; + resizer_try_format(sd, fh, fse->pad, &format, + V4L2_SUBDEV_FORMAT_TRY); + fse->min_width = format.width; + fse->min_height = format.height; + + if (format.code != fse->code) + return -EINVAL; + + format.code = fse->code; + format.width = -1; + format.height = -1; + resizer_try_format(sd, fh, fse->pad, &format, + V4L2_SUBDEV_FORMAT_TRY); + fse->max_width = format.width; + fse->max_height = format.height; + + return 0; +} + +/* + * resizer_enum_mbus_code() - enum mbus codes for pads + * @sd: Pointer to subdevice. + * @fh: V4L2 subdev file handle + * @code: pointer to v4l2_subdev_mbus_code_enum structure + */ +static int resizer_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_fh *fh, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->pad == RESIZER_PAD_SINK) { + if (code->index >= ARRAY_SIZE(resizer_input_formats)) + return -EINVAL; + + code->code = resizer_input_formats[code->index]; + } else if (code->pad == RESIZER_PAD_SOURCE) { + if (code->index >= ARRAY_SIZE(resizer_output_formats)) + return -EINVAL; + + code->code = resizer_output_formats[code->index]; + } + + return 0; +} + +/* + * resizer_init_formats() - Initialize formats on all pads + * @sd: Pointer to subdevice. + * @fh: V4L2 subdev file handle. + * + * Initialize all pad formats with default values. If fh is not NULL, try + * formats are initialized on the file handle. Otherwise active formats are + * initialized on the device. + */ +static int resizer_init_formats(struct v4l2_subdev *sd, + struct v4l2_subdev_fh *fh) +{ + __u32 which = fh ? V4L2_SUBDEV_FORMAT_TRY : V4L2_SUBDEV_FORMAT_ACTIVE; + struct vpfe_resizer_device *resizer = v4l2_get_subdevdata(sd); + struct v4l2_subdev_format format; + + if (&resizer->crop_resizer.subdev == sd) { + memset(&format, 0, sizeof(format)); + format.pad = RESIZER_CROP_PAD_SINK; + format.which = which; + format.format.code = V4L2_MBUS_FMT_YUYV8_2X8; + format.format.width = MAX_IN_WIDTH; + format.format.height = MAX_IN_HEIGHT; + resizer_set_format(sd, fh, &format); + + memset(&format, 0, sizeof(format)); + format.pad = RESIZER_CROP_PAD_SOURCE; + format.which = which; + format.format.code = V4L2_MBUS_FMT_UYVY8_2X8; + format.format.width = MAX_IN_WIDTH; + format.format.height = MAX_IN_WIDTH; + resizer_set_format(sd, fh, &format); + + memset(&format, 0, sizeof(format)); + format.pad = RESIZER_CROP_PAD_SOURCE2; + format.which = which; + format.format.code = V4L2_MBUS_FMT_UYVY8_2X8; + format.format.width = MAX_IN_WIDTH; + format.format.height = MAX_IN_WIDTH; + resizer_set_format(sd, fh, &format); + } else if (&resizer->resizer_a.subdev == sd) { + memset(&format, 0, sizeof(format)); + format.pad = RESIZER_PAD_SINK; + format.which = which; + format.format.code = V4L2_MBUS_FMT_YUYV8_2X8; + format.format.width = MAX_IN_WIDTH; + format.format.height = MAX_IN_HEIGHT; + resizer_set_format(sd, fh, &format); + + memset(&format, 0, sizeof(format)); + format.pad = RESIZER_PAD_SOURCE; + format.which = which; + format.format.code = V4L2_MBUS_FMT_UYVY8_2X8; + format.format.width = IPIPE_MAX_OUTPUT_WIDTH_A; + format.format.height = IPIPE_MAX_OUTPUT_HEIGHT_A; + resizer_set_format(sd, fh, &format); + } else if (&resizer->resizer_b.subdev == sd) { + memset(&format, 0, sizeof(format)); + format.pad = RESIZER_PAD_SINK; + format.which = which; + format.format.code = V4L2_MBUS_FMT_YUYV8_2X8; + format.format.width = MAX_IN_WIDTH; + format.format.height = MAX_IN_HEIGHT; + resizer_set_format(sd, fh, &format); + + memset(&format, 0, sizeof(format)); + format.pad = RESIZER_PAD_SOURCE; + format.which = which; + format.format.code = V4L2_MBUS_FMT_UYVY8_2X8; + format.format.width = IPIPE_MAX_OUTPUT_WIDTH_B; + format.format.height = IPIPE_MAX_OUTPUT_HEIGHT_B; + resizer_set_format(sd, fh, &format); + } + + return 0; +} + +/* subdev core operations */ +static const struct v4l2_subdev_core_ops resizer_v4l2_core_ops = { + .ioctl = resizer_ioctl, +}; + +/* subdev internal operations */ +static const struct v4l2_subdev_internal_ops resizer_v4l2_internal_ops = { + .open = resizer_init_formats, +}; + +/* subdev video operations */ +static const struct v4l2_subdev_video_ops resizer_v4l2_video_ops = { + .s_stream = resizer_set_stream, +}; + +/* subdev pad operations */ +static const struct v4l2_subdev_pad_ops resizer_v4l2_pad_ops = { + .enum_mbus_code = resizer_enum_mbus_code, + .enum_frame_size = resizer_enum_frame_size, + .get_fmt = resizer_get_format, + .set_fmt = resizer_set_format, +}; + +/* subdev operations */ +static const struct v4l2_subdev_ops resizer_v4l2_ops = { + .core = &resizer_v4l2_core_ops, + .video = &resizer_v4l2_video_ops, + .pad = &resizer_v4l2_pad_ops, +}; + +/* + * Media entity operations + */ + +/* + * resizer_link_setup() - Setup resizer connections + * @entity: Pointer to media entity structure + * @local: Pointer to local pad array + * @remote: Pointer to remote pad array + * @flags: Link flags + * return -EINVAL or zero on success + */ +static int resizer_link_setup(struct media_entity *entity, + const struct media_pad *local, + const struct media_pad *remote, u32 flags) +{ + struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(entity); + struct vpfe_resizer_device *resizer = v4l2_get_subdevdata(sd); + struct vpfe_device *vpfe_dev = to_vpfe_device(resizer); + u16 ipipeif_source = vpfe_dev->vpfe_ipipeif.output; + u16 ipipe_source = vpfe_dev->vpfe_ipipe.output; + + if (&resizer->crop_resizer.subdev == sd) { + switch (local->index | media_entity_type(remote->entity)) { + case RESIZER_CROP_PAD_SINK | MEDIA_ENT_T_V4L2_SUBDEV: + if (!(flags & MEDIA_LNK_FL_ENABLED)) { + resizer->crop_resizer.input = + RESIZER_CROP_INPUT_NONE; + break; + } + + if (resizer->crop_resizer.input != + RESIZER_CROP_INPUT_NONE) + return -EBUSY; + if (ipipeif_source == IPIPEIF_OUTPUT_RESIZER) + resizer->crop_resizer.input = + RESIZER_CROP_INPUT_IPIPEIF; + else if (ipipe_source == IPIPE_OUTPUT_RESIZER) + resizer->crop_resizer.input = + RESIZER_CROP_INPUT_IPIPE; + else + return -EINVAL; + break; + + case RESIZER_CROP_PAD_SOURCE | MEDIA_ENT_T_V4L2_SUBDEV: + if (!(flags & MEDIA_LNK_FL_ENABLED)) { + resizer->crop_resizer.output = + RESIZER_CROP_OUTPUT_NONE; + break; + } + if (resizer->crop_resizer.output != + RESIZER_CROP_OUTPUT_NONE) + return -EBUSY; + resizer->crop_resizer.output = RESIZER_A; + break; + + case RESIZER_CROP_PAD_SOURCE2 | MEDIA_ENT_T_V4L2_SUBDEV: + if (!(flags & MEDIA_LNK_FL_ENABLED)) { + resizer->crop_resizer.output2 = + RESIZER_CROP_OUTPUT_NONE; + break; + } + if (resizer->crop_resizer.output2 != + RESIZER_CROP_OUTPUT_NONE) + return -EBUSY; + resizer->crop_resizer.output2 = RESIZER_B; + break; + + default: + return -EINVAL; + } + } else if (&resizer->resizer_a.subdev == sd) { + switch (local->index | media_entity_type(remote->entity)) { + case RESIZER_PAD_SINK | MEDIA_ENT_T_V4L2_SUBDEV: + if (!(flags & MEDIA_LNK_FL_ENABLED)) { + resizer->resizer_a.input = RESIZER_INPUT_NONE; + break; + } + if (resizer->resizer_a.input != RESIZER_INPUT_NONE) + return -EBUSY; + resizer->resizer_a.input = RESIZER_INPUT_CROP_RESIZER; + break; + + case RESIZER_PAD_SOURCE | MEDIA_ENT_T_DEVNODE: + if (!(flags & MEDIA_LNK_FL_ENABLED)) { + resizer->resizer_a.output = RESIZER_OUTPUT_NONE; + break; + } + if (resizer->resizer_a.output != RESIZER_OUTPUT_NONE) + return -EBUSY; + resizer->resizer_a.output = RESIZER_OUPUT_MEMORY; + break; + + default: + return -EINVAL; + } + } else if (&resizer->resizer_b.subdev == sd) { + switch (local->index | media_entity_type(remote->entity)) { + case RESIZER_PAD_SINK | MEDIA_ENT_T_V4L2_SUBDEV: + if (!(flags & MEDIA_LNK_FL_ENABLED)) { + resizer->resizer_b.input = RESIZER_INPUT_NONE; + break; + } + if (resizer->resizer_b.input != RESIZER_INPUT_NONE) + return -EBUSY; + resizer->resizer_b.input = RESIZER_INPUT_CROP_RESIZER; + break; + + case RESIZER_PAD_SOURCE | MEDIA_ENT_T_DEVNODE: + if (!(flags & MEDIA_LNK_FL_ENABLED)) { + resizer->resizer_b.output = RESIZER_OUTPUT_NONE; + break; + } + if (resizer->resizer_b.output != RESIZER_OUTPUT_NONE) + return -EBUSY; + resizer->resizer_b.output = RESIZER_OUPUT_MEMORY; + break; + + default: + return -EINVAL; + } + } else { + return -EINVAL; + } + + return 0; +} + +static const struct media_entity_operations resizer_media_ops = { + .link_setup = resizer_link_setup, +}; + +/* + * vpfe_resizer_unregister_entities() - Unregister entity + * @vpfe_rsz - pointer to resizer subdevice structure. + */ +void vpfe_resizer_unregister_entities(struct vpfe_resizer_device *vpfe_rsz) +{ + /* unregister video devices */ + vpfe_video_unregister(&vpfe_rsz->resizer_a.video_out); + vpfe_video_unregister(&vpfe_rsz->resizer_b.video_out); + + /* unregister subdev */ + v4l2_device_unregister_subdev(&vpfe_rsz->crop_resizer.subdev); + v4l2_device_unregister_subdev(&vpfe_rsz->resizer_a.subdev); + v4l2_device_unregister_subdev(&vpfe_rsz->resizer_b.subdev); + /* cleanup entity */ + media_entity_cleanup(&vpfe_rsz->crop_resizer.subdev.entity); + media_entity_cleanup(&vpfe_rsz->resizer_a.subdev.entity); + media_entity_cleanup(&vpfe_rsz->resizer_b.subdev.entity); +} + +/* + * vpfe_resizer_register_entities() - Register entity + * @resizer - pointer to resizer devive. + * @vdev: pointer to v4l2 device structure. + */ +int vpfe_resizer_register_entities(struct vpfe_resizer_device *resizer, + struct v4l2_device *vdev) +{ + struct vpfe_device *vpfe_dev = to_vpfe_device(resizer); + unsigned int flags = 0; + int ret; + + /* Register the crop resizer subdev */ + ret = v4l2_device_register_subdev(vdev, &resizer->crop_resizer.subdev); + if (ret < 0) { + pr_err("Failed to register crop resizer as v4l2-subdev\n"); + return ret; + } + /* Register Resizer-A subdev */ + ret = v4l2_device_register_subdev(vdev, &resizer->resizer_a.subdev); + if (ret < 0) { + pr_err("Failed to register resizer-a as v4l2-subdev\n"); + return ret; + } + /* Register Resizer-B subdev */ + ret = v4l2_device_register_subdev(vdev, &resizer->resizer_b.subdev); + if (ret < 0) { + pr_err("Failed to register resizer-b as v4l2-subdev\n"); + return ret; + } + /* Register video-out device for resizer-a */ + ret = vpfe_video_register(&resizer->resizer_a.video_out, vdev); + if (ret) { + pr_err("Failed to register RSZ-A video-out device\n"); + goto out_video_out2_register; + } + resizer->resizer_a.video_out.vpfe_dev = vpfe_dev; + + /* Register video-out device for resizer-b */ + ret = vpfe_video_register(&resizer->resizer_b.video_out, vdev); + if (ret) { + pr_err("Failed to register RSZ-B video-out device\n"); + goto out_video_out2_register; + } + resizer->resizer_b.video_out.vpfe_dev = vpfe_dev; + + /* create link between Resizer Crop----> Resizer A*/ + ret = media_entity_create_link(&resizer->crop_resizer.subdev.entity, 1, + &resizer->resizer_a.subdev.entity, + 0, flags); + if (ret < 0) + goto out_create_link; + + /* create link between Resizer Crop----> Resizer B*/ + ret = media_entity_create_link(&resizer->crop_resizer.subdev.entity, 2, + &resizer->resizer_b.subdev.entity, + 0, flags); + if (ret < 0) + goto out_create_link; + + /* create link between Resizer A ----> video out */ + ret = media_entity_create_link(&resizer->resizer_a.subdev.entity, 1, + &resizer->resizer_a.video_out.video_dev.entity, 0, flags); + if (ret < 0) + goto out_create_link; + + /* create link between Resizer B ----> video out */ + ret = media_entity_create_link(&resizer->resizer_b.subdev.entity, 1, + &resizer->resizer_b.video_out.video_dev.entity, 0, flags); + if (ret < 0) + goto out_create_link; + + return 0; + +out_create_link: + vpfe_video_unregister(&resizer->resizer_b.video_out); +out_video_out2_register: + vpfe_video_unregister(&resizer->resizer_a.video_out); + v4l2_device_unregister_subdev(&resizer->crop_resizer.subdev); + v4l2_device_unregister_subdev(&resizer->resizer_a.subdev); + v4l2_device_unregister_subdev(&resizer->resizer_b.subdev); + media_entity_cleanup(&resizer->crop_resizer.subdev.entity); + media_entity_cleanup(&resizer->resizer_a.subdev.entity); + media_entity_cleanup(&resizer->resizer_b.subdev.entity); + return ret; +} + +/* + * vpfe_resizer_init() - resizer device initialization. + * @vpfe_rsz - pointer to resizer device + * @pdev: platform device pointer. + */ +int vpfe_resizer_init(struct vpfe_resizer_device *vpfe_rsz, + struct platform_device *pdev) +{ + struct v4l2_subdev *sd = &vpfe_rsz->crop_resizer.subdev; + struct media_pad *pads = &vpfe_rsz->crop_resizer.pads[0]; + struct media_entity *me = &sd->entity; + static resource_size_t res_len; + struct resource *res; + int ret; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 5); + if (!res) + return -ENOENT; + + res_len = resource_size(res); + res = request_mem_region(res->start, res_len, res->name); + if (!res) + return -EBUSY; + + vpfe_rsz->base_addr = ioremap_nocache(res->start, res_len); + if (!vpfe_rsz->base_addr) + return -EBUSY; + + v4l2_subdev_init(sd, &resizer_v4l2_ops); + sd->internal_ops = &resizer_v4l2_internal_ops; + strlcpy(sd->name, "DAVINCI RESIZER CROP", sizeof(sd->name)); + sd->grp_id = 1 << 16; /* group ID for davinci subdevs */ + v4l2_set_subdevdata(sd, vpfe_rsz); + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + + pads[RESIZER_CROP_PAD_SINK].flags = MEDIA_PAD_FL_SINK; + pads[RESIZER_CROP_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE; + pads[RESIZER_CROP_PAD_SOURCE2].flags = MEDIA_PAD_FL_SOURCE; + + vpfe_rsz->crop_resizer.input = RESIZER_CROP_INPUT_NONE; + vpfe_rsz->crop_resizer.output = RESIZER_CROP_OUTPUT_NONE; + vpfe_rsz->crop_resizer.output2 = RESIZER_CROP_OUTPUT_NONE; + vpfe_rsz->crop_resizer.rsz_device = vpfe_rsz; + me->ops = &resizer_media_ops; + ret = media_entity_init(me, RESIZER_CROP_PADS_NUM, pads, 0); + if (ret) + return ret; + + sd = &vpfe_rsz->resizer_a.subdev; + pads = &vpfe_rsz->resizer_a.pads[0]; + me = &sd->entity; + + v4l2_subdev_init(sd, &resizer_v4l2_ops); + sd->internal_ops = &resizer_v4l2_internal_ops; + strlcpy(sd->name, "DAVINCI RESIZER A", sizeof(sd->name)); + sd->grp_id = 1 << 16; /* group ID for davinci subdevs */ + v4l2_set_subdevdata(sd, vpfe_rsz); + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + + pads[RESIZER_PAD_SINK].flags = MEDIA_PAD_FL_SINK; + pads[RESIZER_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE; + + vpfe_rsz->resizer_a.input = RESIZER_INPUT_NONE; + vpfe_rsz->resizer_a.output = RESIZER_OUTPUT_NONE; + vpfe_rsz->resizer_a.rsz_device = vpfe_rsz; + me->ops = &resizer_media_ops; + ret = media_entity_init(me, RESIZER_PADS_NUM, pads, 0); + if (ret) + return ret; + + sd = &vpfe_rsz->resizer_b.subdev; + pads = &vpfe_rsz->resizer_b.pads[0]; + me = &sd->entity; + + v4l2_subdev_init(sd, &resizer_v4l2_ops); + sd->internal_ops = &resizer_v4l2_internal_ops; + strlcpy(sd->name, "DAVINCI RESIZER B", sizeof(sd->name)); + sd->grp_id = 1 << 16; /* group ID for davinci subdevs */ + v4l2_set_subdevdata(sd, vpfe_rsz); + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + + pads[RESIZER_PAD_SINK].flags = MEDIA_PAD_FL_SINK; + pads[RESIZER_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE; + + vpfe_rsz->resizer_b.input = RESIZER_INPUT_NONE; + vpfe_rsz->resizer_b.output = RESIZER_OUTPUT_NONE; + vpfe_rsz->resizer_b.rsz_device = vpfe_rsz; + me->ops = &resizer_media_ops; + ret = media_entity_init(me, RESIZER_PADS_NUM, pads, 0); + if (ret) + return ret; + + vpfe_rsz->resizer_a.video_out.ops = &resizer_a_video_ops; + vpfe_rsz->resizer_a.video_out.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + ret = vpfe_video_init(&vpfe_rsz->resizer_a.video_out, "RSZ-A"); + if (ret) { + pr_err("Failed to init RSZ video-out device\n"); + return ret; + } + vpfe_rsz->resizer_b.video_out.ops = &resizer_b_video_ops; + vpfe_rsz->resizer_b.video_out.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + ret = vpfe_video_init(&vpfe_rsz->resizer_b.video_out, "RSZ-B"); + if (ret) { + pr_err("Failed to init RSZ video-out2 device\n"); + return ret; + } + memset(&vpfe_rsz->config, 0, sizeof(struct resizer_params)); + + return 0; +} + +void +vpfe_resizer_cleanup(struct vpfe_resizer_device *vpfe_rsz, + struct platform_device *pdev) +{ + struct resource *res; + + iounmap(vpfe_rsz->base_addr); + res = platform_get_resource(pdev, IORESOURCE_MEM, 5); + if (res) + release_mem_region(res->start, + resource_size(res)); +} diff --git a/drivers/staging/media/davinci_vpfe/dm365_resizer.h b/drivers/staging/media/davinci_vpfe/dm365_resizer.h new file mode 100644 index 00000000000..59a79422b91 --- /dev/null +++ b/drivers/staging/media/davinci_vpfe/dm365_resizer.h @@ -0,0 +1,244 @@ +/* + * Copyright (C) 2012 Texas Instruments Inc + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Contributors: + * Manjunath Hadli <manjunath.hadli@ti.com> + * Prabhakar Lad <prabhakar.lad@ti.com> + */ + +#ifndef _DAVINCI_VPFE_DM365_RESIZER_H +#define _DAVINCI_VPFE_DM365_RESIZER_H + +enum resizer_oper_mode { + RESIZER_MODE_CONTINIOUS = 0, + RESIZER_MODE_ONE_SHOT = 1, +}; + +struct f_div_pass { + unsigned int o_hsz; + unsigned int i_hps; + unsigned int h_phs; + unsigned int src_hps; + unsigned int src_hsz; +}; + +#define MAX_PASSES 2 + +struct f_div_param { + unsigned char en; + unsigned int num_passes; + struct f_div_pass pass[MAX_PASSES]; +}; + +/* Resizer Rescale Parameters*/ +struct resizer_scale_param { + bool h_flip; + bool v_flip; + bool cen; + bool yen; + unsigned short i_vps; + unsigned short i_hps; + unsigned short o_vsz; + unsigned short o_hsz; + unsigned short v_phs_y; + unsigned short v_phs_c; + unsigned short v_dif; + /* resize method - Luminance */ + enum vpfe_rsz_intp_t v_typ_y; + /* resize method - Chrominance */ + enum vpfe_rsz_intp_t v_typ_c; + /* vertical lpf intensity - Luminance */ + unsigned char v_lpf_int_y; + /* vertical lpf intensity - Chrominance */ + unsigned char v_lpf_int_c; + unsigned short h_phs; + unsigned short h_dif; + /* resize method - Luminance */ + enum vpfe_rsz_intp_t h_typ_y; + /* resize method - Chrominance */ + enum vpfe_rsz_intp_t h_typ_c; + /* horizontal lpf intensity - Luminance */ + unsigned char h_lpf_int_y; + /* horizontal lpf intensity - Chrominance */ + unsigned char h_lpf_int_c; + bool dscale_en; + enum vpfe_rsz_down_scale_ave_sz h_dscale_ave_sz; + enum vpfe_rsz_down_scale_ave_sz v_dscale_ave_sz; + /* store the calculated frame division parameter */ + struct f_div_param f_div; +}; + +enum resizer_rgb_t { + OUTPUT_32BIT, + OUTPUT_16BIT +}; + +enum resizer_rgb_msk_t { + NOMASK = 0, + MASKLAST2 = 1, +}; + +/* Resizer RGB Conversion Parameters */ +struct resizer_rgb { + bool rgb_en; + enum resizer_rgb_t rgb_typ; + enum resizer_rgb_msk_t rgb_msk0; + enum resizer_rgb_msk_t rgb_msk1; + unsigned int rgb_alpha_val; +}; + +/* Resizer External Memory Parameters */ +struct rsz_ext_mem_param { + unsigned int rsz_sdr_oft_y; + unsigned int rsz_sdr_ptr_s_y; + unsigned int rsz_sdr_ptr_e_y; + unsigned int rsz_sdr_oft_c; + unsigned int rsz_sdr_ptr_s_c; + unsigned int rsz_sdr_ptr_e_c; + /* offset to be added to buffer start when flipping for y/ycbcr */ + unsigned int flip_ofst_y; + /* offset to be added to buffer start when flipping for c */ + unsigned int flip_ofst_c; + /* c offset for YUV 420SP */ + unsigned int c_offset; + /* User Defined Y offset for YUV 420SP or YUV420ILE data */ + unsigned int user_y_ofst; + /* User Defined C offset for YUV 420SP data */ + unsigned int user_c_ofst; +}; + +enum rsz_data_source { + IPIPE_DATA, + IPIPEIF_DATA +}; + +enum rsz_src_img_fmt { + RSZ_IMG_422, + RSZ_IMG_420 +}; + +enum rsz_dpaths_bypass_t { + BYPASS_OFF = 0, + BYPASS_ON = 1, +}; + +struct rsz_common_params { + unsigned int vps; + unsigned int vsz; + unsigned int hps; + unsigned int hsz; + /* 420 or 422 */ + enum rsz_src_img_fmt src_img_fmt; + /* Y or C when src_fmt is 420, 0 - y, 1 - c */ + unsigned char y_c; + /* flip raw or ycbcr */ + unsigned char raw_flip; + /* IPIPE or IPIPEIF data */ + enum rsz_data_source source; + enum rsz_dpaths_bypass_t passthrough; + unsigned char yuv_y_min; + unsigned char yuv_y_max; + unsigned char yuv_c_min; + unsigned char yuv_c_max; + bool rsz_seq_crv; + enum vpfe_chr_pos out_chr_pos; +}; + +struct resizer_params { + enum resizer_oper_mode oper_mode; + struct rsz_common_params rsz_common; + struct resizer_scale_param rsz_rsc_param[2]; + struct resizer_rgb rsz2rgb[2]; + struct rsz_ext_mem_param ext_mem_param[2]; + bool rsz_en[2]; + struct vpfe_rsz_config_params user_config; +}; + +#define ENABLE 1 +#define DISABLE (!ENABLE) + +#define RESIZER_CROP_PAD_SINK 0 +#define RESIZER_CROP_PAD_SOURCE 1 +#define RESIZER_CROP_PAD_SOURCE2 2 + +#define RESIZER_CROP_PADS_NUM 3 + +enum resizer_crop_input_entity { + RESIZER_CROP_INPUT_NONE = 0, + RESIZER_CROP_INPUT_IPIPEIF = 1, + RESIZER_CROP_INPUT_IPIPE = 2, +}; + +enum resizer_crop_output_entity { + RESIZER_CROP_OUTPUT_NONE, + RESIZER_A, + RESIZER_B, +}; + +struct dm365_crop_resizer_device { + struct v4l2_subdev subdev; + struct media_pad pads[RESIZER_CROP_PADS_NUM]; + struct v4l2_mbus_framefmt formats[RESIZER_CROP_PADS_NUM]; + enum resizer_crop_input_entity input; + enum resizer_crop_output_entity output; + enum resizer_crop_output_entity output2; + struct vpfe_resizer_device *rsz_device; +}; + +#define RESIZER_PAD_SINK 0 +#define RESIZER_PAD_SOURCE 1 + +#define RESIZER_PADS_NUM 2 + +enum resizer_input_entity { + RESIZER_INPUT_NONE = 0, + RESIZER_INPUT_CROP_RESIZER = 1, +}; + +enum resizer_output_entity { + RESIZER_OUTPUT_NONE = 0, + RESIZER_OUPUT_MEMORY = 1, +}; + +struct dm365_resizer_device { + struct v4l2_subdev subdev; + struct media_pad pads[RESIZER_PADS_NUM]; + struct v4l2_mbus_framefmt formats[RESIZER_PADS_NUM]; + enum resizer_input_entity input; + enum resizer_output_entity output; + struct vpfe_video_device video_out; + struct vpfe_resizer_device *rsz_device; +}; + +struct vpfe_resizer_device { + struct dm365_crop_resizer_device crop_resizer; + struct dm365_resizer_device resizer_a; + struct dm365_resizer_device resizer_b; + struct resizer_params config; + void *__iomem base_addr; +}; + +int vpfe_resizer_init(struct vpfe_resizer_device *vpfe_rsz, + struct platform_device *pdev); +int vpfe_resizer_register_entities(struct vpfe_resizer_device *vpfe_rsz, + struct v4l2_device *v4l2_dev); +void vpfe_resizer_unregister_entities(struct vpfe_resizer_device *vpfe_rsz); +void vpfe_resizer_cleanup(struct vpfe_resizer_device *vpfe_rsz, + struct platform_device *pdev); +void vpfe_resizer_buffer_isr(struct vpfe_resizer_device *resizer); +void vpfe_resizer_dma_isr(struct vpfe_resizer_device *resizer); + +#endif /* _DAVINCI_VPFE_DM365_RESIZER_H */ diff --git a/drivers/staging/media/davinci_vpfe/vpfe.h b/drivers/staging/media/davinci_vpfe/vpfe.h new file mode 100644 index 00000000000..0587bc52a84 --- /dev/null +++ b/drivers/staging/media/davinci_vpfe/vpfe.h @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2012 Texas Instruments Inc + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Contributors: + * Manjunath Hadli <manjunath.hadli@ti.com> + * Prabhakar Lad <prabhakar.lad@ti.com> + */ + +#ifndef _VPFE_H +#define _VPFE_H + +#ifdef __KERNEL__ +#include <linux/v4l2-subdev.h> +#include <linux/clk.h> +#include <linux/i2c.h> + +#include <media/davinci/vpfe_types.h> + +#define CAPTURE_DRV_NAME "vpfe-capture" + +struct vpfe_route { + __u32 input; + __u32 output; +}; + +enum vpfe_subdev_id { + VPFE_SUBDEV_TVP5146 = 1, + VPFE_SUBDEV_MT9T031 = 2, + VPFE_SUBDEV_TVP7002 = 3, + VPFE_SUBDEV_MT9P031 = 4, +}; + +struct vpfe_ext_subdev_info { + /* v4l2 subdev */ + struct v4l2_subdev *subdev; + /* Sub device module name */ + char module_name[32]; + /* Sub device group id */ + int grp_id; + /* Number of inputs supported */ + int num_inputs; + /* inputs available at the sub device */ + struct v4l2_input *inputs; + /* Sub dev routing information for each input */ + struct vpfe_route *routes; + /* ccdc bus/interface configuration */ + struct vpfe_hw_if_param ccdc_if_params; + /* i2c subdevice board info */ + struct i2c_board_info board_info; + /* Is this a camera sub device ? */ + unsigned is_camera:1; + /* check if sub dev supports routing */ + unsigned can_route:1; + /* registered ? */ + unsigned registered:1; +}; + +struct vpfe_config { + /* Number of sub devices connected to vpfe */ + int num_subdevs; + /* information about each subdev */ + struct vpfe_ext_subdev_info *sub_devs; + /* evm card info */ + char *card_name; + /* setup function for the input path */ + int (*setup_input)(enum vpfe_subdev_id id); + /* number of clocks */ + int num_clocks; + /* clocks used for vpfe capture */ + char *clocks[]; +}; +#endif +#endif diff --git a/drivers/staging/media/davinci_vpfe/vpfe_mc_capture.c b/drivers/staging/media/davinci_vpfe/vpfe_mc_capture.c new file mode 100644 index 00000000000..cda8388cbb8 --- /dev/null +++ b/drivers/staging/media/davinci_vpfe/vpfe_mc_capture.c @@ -0,0 +1,724 @@ +/* + * Copyright (C) 2012 Texas Instruments Inc + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Contributors: + * Manjunath Hadli <manjunath.hadli@ti.com> + * Prabhakar Lad <prabhakar.lad@ti.com> + * + * + * Driver name : VPFE Capture driver + * VPFE Capture driver allows applications to capture and stream video + * frames on DaVinci SoCs (DM6446, DM355 etc) from a YUV source such as + * TVP5146 or Raw Bayer RGB image data from an image sensor + * such as Microns' MT9T001, MT9T031 etc. + * + * These SoCs have, in common, a Video Processing Subsystem (VPSS) that + * consists of a Video Processing Front End (VPFE) for capturing + * video/raw image data and Video Processing Back End (VPBE) for displaying + * YUV data through an in-built analog encoder or Digital LCD port. This + * driver is for capture through VPFE. A typical EVM using these SoCs have + * following high level configuration. + * + * decoder(TVP5146/ YUV/ + * MT9T001) --> Raw Bayer RGB ---> MUX -> VPFE (CCDC/ISIF) + * data input | | + * V | + * SDRAM | + * V + * Image Processor + * | + * V + * SDRAM + * The data flow happens from a decoder connected to the VPFE over a + * YUV embedded (BT.656/BT.1120) or separate sync or raw bayer rgb interface + * and to the input of VPFE through an optional MUX (if more inputs are + * to be interfaced on the EVM). The input data is first passed through + * CCDC (CCD Controller, a.k.a Image Sensor Interface, ISIF). The CCDC + * does very little or no processing on YUV data and does pre-process Raw + * Bayer RGB data through modules such as Defect Pixel Correction (DFC) + * Color Space Conversion (CSC), data gain/offset etc. After this, data + * can be written to SDRAM or can be connected to the image processing + * block such as IPIPE (on DM355/DM365 only). + * + * Features supported + * - MMAP IO + * - USERPTR IO + * - Capture using TVP5146 over BT.656 + * - Support for interfacing decoders using sub device model + * - Work with DM365 or DM355 or DM6446 CCDC to do Raw Bayer + * RGB/YUV data capture to SDRAM. + * - Chaining of Image Processor + * - SINGLE-SHOT mode + */ + +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/slab.h> + +#include "vpfe.h" +#include "vpfe_mc_capture.h" + +static bool debug; +static bool interface; + +module_param(interface, bool, S_IRUGO); +module_param(debug, bool, 0644); + +/** + * VPFE capture can be used for capturing video such as from TVP5146 or TVP7002 + * and for capture raw bayer data from camera sensors such as mt9p031. At this + * point there is problem in co-existence of mt9p031 and tvp5146 due to i2c + * address collision. So set the variable below from bootargs to do either video + * capture or camera capture. + * interface = 0 - video capture (from TVP514x or such), + * interface = 1 - Camera capture (from mt9p031 or such) + * Re-visit this when we fix the co-existence issue + */ +MODULE_PARM_DESC(interface, "interface 0-1 (default:0)"); +MODULE_PARM_DESC(debug, "Debug level 0-1"); + +MODULE_DESCRIPTION("VPFE Video for Linux Capture Driver"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Texas Instruments"); + +/* map mbus_fmt to pixelformat */ +void mbus_to_pix(const struct v4l2_mbus_framefmt *mbus, + struct v4l2_pix_format *pix) +{ + switch (mbus->code) { + case V4L2_MBUS_FMT_UYVY8_2X8: + pix->pixelformat = V4L2_PIX_FMT_UYVY; + pix->bytesperline = pix->width * 2; + break; + + case V4L2_MBUS_FMT_YUYV8_2X8: + pix->pixelformat = V4L2_PIX_FMT_YUYV; + pix->bytesperline = pix->width * 2; + break; + + case V4L2_MBUS_FMT_YUYV10_1X20: + pix->pixelformat = V4L2_PIX_FMT_UYVY; + pix->bytesperline = pix->width * 2; + break; + + case V4L2_MBUS_FMT_SGRBG12_1X12: + pix->pixelformat = V4L2_PIX_FMT_SBGGR16; + pix->bytesperline = pix->width * 2; + break; + + case V4L2_MBUS_FMT_SGRBG10_DPCM8_1X8: + pix->pixelformat = V4L2_PIX_FMT_SGRBG10DPCM8; + pix->bytesperline = pix->width; + break; + + case V4L2_MBUS_FMT_SGRBG10_ALAW8_1X8: + pix->pixelformat = V4L2_PIX_FMT_SGRBG10ALAW8; + pix->bytesperline = pix->width; + break; + + case V4L2_MBUS_FMT_YDYUYDYV8_1X16: + pix->pixelformat = V4L2_PIX_FMT_NV12; + pix->bytesperline = pix->width; + break; + + case V4L2_MBUS_FMT_Y8_1X8: + pix->pixelformat = V4L2_PIX_FMT_GREY; + pix->bytesperline = pix->width; + break; + + case V4L2_MBUS_FMT_UV8_1X8: + pix->pixelformat = V4L2_PIX_FMT_UV8; + pix->bytesperline = pix->width; + break; + + default: + pr_err("Invalid mbus code set\n"); + } + /* pitch should be 32 bytes aligned */ + pix->bytesperline = ALIGN(pix->bytesperline, 32); + if (pix->pixelformat == V4L2_PIX_FMT_NV12) + pix->sizeimage = pix->bytesperline * pix->height + + ((pix->bytesperline * pix->height) >> 1); + else + pix->sizeimage = pix->bytesperline * pix->height; +} + +/* ISR for VINT0*/ +static irqreturn_t vpfe_isr(int irq, void *dev_id) +{ + struct vpfe_device *vpfe_dev = dev_id; + + v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_isr\n"); + vpfe_isif_buffer_isr(&vpfe_dev->vpfe_isif); + vpfe_resizer_buffer_isr(&vpfe_dev->vpfe_resizer); + return IRQ_HANDLED; +} + +/* vpfe_vdint1_isr() - isr handler for VINT1 interrupt */ +static irqreturn_t vpfe_vdint1_isr(int irq, void *dev_id) +{ + struct vpfe_device *vpfe_dev = dev_id; + + v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_vdint1_isr\n"); + vpfe_isif_vidint1_isr(&vpfe_dev->vpfe_isif); + return IRQ_HANDLED; +} + +/* vpfe_imp_dma_isr() - ISR for ipipe dma completion */ +static irqreturn_t vpfe_imp_dma_isr(int irq, void *dev_id) +{ + struct vpfe_device *vpfe_dev = dev_id; + + v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_imp_dma_isr\n"); + vpfe_ipipeif_ss_buffer_isr(&vpfe_dev->vpfe_ipipeif); + vpfe_resizer_dma_isr(&vpfe_dev->vpfe_resizer); + return IRQ_HANDLED; +} + +/* + * vpfe_disable_clock() - Disable clocks for vpfe capture driver + * @vpfe_dev - ptr to vpfe capture device + * + * Disables clocks defined in vpfe configuration. The function + * assumes that at least one clock is to be defined which is + * true as of now. + */ +static void vpfe_disable_clock(struct vpfe_device *vpfe_dev) +{ + struct vpfe_config *vpfe_cfg = vpfe_dev->cfg; + int i; + + for (i = 0; i < vpfe_cfg->num_clocks; i++) { + clk_disable_unprepare(vpfe_dev->clks[i]); + clk_put(vpfe_dev->clks[i]); + } + kzfree(vpfe_dev->clks); + v4l2_info(vpfe_dev->pdev->driver, "vpfe capture clocks disabled\n"); +} + +/* + * vpfe_enable_clock() - Enable clocks for vpfe capture driver + * @vpfe_dev - ptr to vpfe capture device + * + * Enables clocks defined in vpfe configuration. The function + * assumes that at least one clock is to be defined which is + * true as of now. + */ +static int vpfe_enable_clock(struct vpfe_device *vpfe_dev) +{ + struct vpfe_config *vpfe_cfg = vpfe_dev->cfg; + int ret = -EFAULT; + int i; + + if (!vpfe_cfg->num_clocks) + return 0; + + vpfe_dev->clks = kzalloc(vpfe_cfg->num_clocks * + sizeof(struct clock *), GFP_KERNEL); + if (vpfe_dev->clks == NULL) { + v4l2_err(vpfe_dev->pdev->driver, "Memory allocation failed\n"); + return -ENOMEM; + } + + for (i = 0; i < vpfe_cfg->num_clocks; i++) { + if (vpfe_cfg->clocks[i] == NULL) { + v4l2_err(vpfe_dev->pdev->driver, + "clock %s is not defined in vpfe config\n", + vpfe_cfg->clocks[i]); + goto out; + } + + vpfe_dev->clks[i] = + clk_get(vpfe_dev->pdev, vpfe_cfg->clocks[i]); + if (IS_ERR(vpfe_dev->clks[i])) { + v4l2_err(vpfe_dev->pdev->driver, + "Failed to get clock %s\n", + vpfe_cfg->clocks[i]); + goto out; + } + + if (clk_prepare_enable(vpfe_dev->clks[i])) { + v4l2_err(vpfe_dev->pdev->driver, + "vpfe clock %s not enabled\n", + vpfe_cfg->clocks[i]); + goto out; + } + + v4l2_info(vpfe_dev->pdev->driver, "vpss clock %s enabled", + vpfe_cfg->clocks[i]); + } + + return 0; +out: + for (i = 0; i < vpfe_cfg->num_clocks; i++) + if (!IS_ERR(vpfe_dev->clks[i])) { + clk_disable_unprepare(vpfe_dev->clks[i]); + clk_put(vpfe_dev->clks[i]); + } + + v4l2_err(vpfe_dev->pdev->driver, "Failed to enable clocks\n"); + kzfree(vpfe_dev->clks); + + return ret; +} + +/* + * vpfe_detach_irq() - Detach IRQs for vpfe capture driver + * @vpfe_dev - ptr to vpfe capture device + * + * Detach all IRQs defined in vpfe configuration. + */ +static void vpfe_detach_irq(struct vpfe_device *vpfe_dev) +{ + free_irq(vpfe_dev->ccdc_irq0, vpfe_dev); + free_irq(vpfe_dev->ccdc_irq1, vpfe_dev); + free_irq(vpfe_dev->imp_dma_irq, vpfe_dev); +} + +/* + * vpfe_attach_irq() - Attach IRQs for vpfe capture driver + * @vpfe_dev - ptr to vpfe capture device + * + * Attach all IRQs defined in vpfe configuration. + */ +static int vpfe_attach_irq(struct vpfe_device *vpfe_dev) +{ + int ret = 0; + + ret = request_irq(vpfe_dev->ccdc_irq0, vpfe_isr, 0, + "vpfe_capture0", vpfe_dev); + if (ret < 0) { + v4l2_err(&vpfe_dev->v4l2_dev, + "Error: requesting VINT0 interrupt\n"); + return ret; + } + + ret = request_irq(vpfe_dev->ccdc_irq1, vpfe_vdint1_isr, 0, + "vpfe_capture1", vpfe_dev); + if (ret < 0) { + v4l2_err(&vpfe_dev->v4l2_dev, + "Error: requesting VINT1 interrupt\n"); + free_irq(vpfe_dev->ccdc_irq0, vpfe_dev); + return ret; + } + + ret = request_irq(vpfe_dev->imp_dma_irq, vpfe_imp_dma_isr, + 0, "Imp_Sdram_Irq", vpfe_dev); + if (ret < 0) { + v4l2_err(&vpfe_dev->v4l2_dev, + "Error: requesting IMP IRQ interrupt\n"); + free_irq(vpfe_dev->ccdc_irq1, vpfe_dev); + free_irq(vpfe_dev->ccdc_irq0, vpfe_dev); + return ret; + } + + return 0; +} + +/* + * register_i2c_devices() - register all i2c v4l2 subdevs + * @vpfe_dev - ptr to vpfe capture device + * + * register all i2c v4l2 subdevs + */ +static int register_i2c_devices(struct vpfe_device *vpfe_dev) +{ + struct vpfe_ext_subdev_info *sdinfo; + struct vpfe_config *vpfe_cfg; + struct i2c_adapter *i2c_adap; + unsigned int num_subdevs; + int ret; + int i; + int k; + + vpfe_cfg = vpfe_dev->cfg; + i2c_adap = i2c_get_adapter(1); + num_subdevs = vpfe_cfg->num_subdevs; + vpfe_dev->sd = + kzalloc(sizeof(struct v4l2_subdev *)*num_subdevs, GFP_KERNEL); + if (vpfe_dev->sd == NULL) { + v4l2_err(&vpfe_dev->v4l2_dev, + "unable to allocate memory for subdevice\n"); + return -ENOMEM; + } + + for (i = 0, k = 0; i < num_subdevs; i++) { + sdinfo = &vpfe_cfg->sub_devs[i]; + /* + * register subdevices based on interface setting. Currently + * tvp5146 and mt9p031 cannot co-exists due to i2c address + * conflicts. So only one of them is registered. Re-visit this + * once we have support for i2c switch handling in i2c driver + * framework + */ + if (interface == sdinfo->is_camera) { + /* setup input path */ + if (vpfe_cfg->setup_input && + vpfe_cfg->setup_input(sdinfo->grp_id) < 0) { + ret = -EFAULT; + v4l2_info(&vpfe_dev->v4l2_dev, + "could not setup input for %s\n", + sdinfo->module_name); + goto probe_sd_out; + } + /* Load up the subdevice */ + vpfe_dev->sd[k] = + v4l2_i2c_new_subdev_board(&vpfe_dev->v4l2_dev, + i2c_adap, &sdinfo->board_info, + NULL); + if (vpfe_dev->sd[k]) { + v4l2_info(&vpfe_dev->v4l2_dev, + "v4l2 sub device %s registered\n", + sdinfo->module_name); + + vpfe_dev->sd[k]->grp_id = sdinfo->grp_id; + k++; + + sdinfo->registered = 1; + } + } else { + v4l2_info(&vpfe_dev->v4l2_dev, + "v4l2 sub device %s is not registered\n", + sdinfo->module_name); + } + } + vpfe_dev->num_ext_subdevs = k; + + return 0; + +probe_sd_out: + kzfree(vpfe_dev->sd); + + return ret; +} + +/* + * vpfe_register_entities() - register all v4l2 subdevs and media entities + * @vpfe_dev - ptr to vpfe capture device + * + * register all v4l2 subdevs, media entities, and creates links + * between entities + */ +static int vpfe_register_entities(struct vpfe_device *vpfe_dev) +{ + unsigned int flags = 0; + int ret; + int i; + + /* register i2c devices first */ + ret = register_i2c_devices(vpfe_dev); + if (ret) + return ret; + + /* register rest of the sub-devs */ + ret = vpfe_isif_register_entities(&vpfe_dev->vpfe_isif, + &vpfe_dev->v4l2_dev); + if (ret) + return ret; + + ret = vpfe_ipipeif_register_entities(&vpfe_dev->vpfe_ipipeif, + &vpfe_dev->v4l2_dev); + if (ret) + goto out_isif_register; + + ret = vpfe_ipipe_register_entities(&vpfe_dev->vpfe_ipipe, + &vpfe_dev->v4l2_dev); + if (ret) + goto out_ipipeif_register; + + ret = vpfe_resizer_register_entities(&vpfe_dev->vpfe_resizer, + &vpfe_dev->v4l2_dev); + if (ret) + goto out_ipipe_register; + + /* create links now, starting with external(i2c) entities */ + for (i = 0; i < vpfe_dev->num_ext_subdevs; i++) + /* if entity has no pads (ex: amplifier), + cant establish link */ + if (vpfe_dev->sd[i]->entity.num_pads) { + ret = media_entity_create_link(&vpfe_dev->sd[i]->entity, + 0, &vpfe_dev->vpfe_isif.subdev.entity, + 0, flags); + if (ret < 0) + goto out_resizer_register; + } + + ret = media_entity_create_link(&vpfe_dev->vpfe_isif.subdev.entity, 1, + &vpfe_dev->vpfe_ipipeif.subdev.entity, + 0, flags); + if (ret < 0) + goto out_resizer_register; + + ret = media_entity_create_link(&vpfe_dev->vpfe_ipipeif.subdev.entity, 1, + &vpfe_dev->vpfe_ipipe.subdev.entity, + 0, flags); + if (ret < 0) + goto out_resizer_register; + + ret = media_entity_create_link(&vpfe_dev->vpfe_ipipe.subdev.entity, + 1, &vpfe_dev->vpfe_resizer.crop_resizer.subdev.entity, + 0, flags); + if (ret < 0) + goto out_resizer_register; + + ret = media_entity_create_link(&vpfe_dev->vpfe_ipipeif.subdev.entity, 1, + &vpfe_dev->vpfe_resizer.crop_resizer.subdev.entity, + 0, flags); + if (ret < 0) + goto out_resizer_register; + + ret = v4l2_device_register_subdev_nodes(&vpfe_dev->v4l2_dev); + if (ret < 0) + goto out_resizer_register; + + return 0; + +out_resizer_register: + vpfe_resizer_unregister_entities(&vpfe_dev->vpfe_resizer); +out_ipipe_register: + vpfe_ipipe_unregister_entities(&vpfe_dev->vpfe_ipipe); +out_ipipeif_register: + vpfe_ipipeif_unregister_entities(&vpfe_dev->vpfe_ipipeif); +out_isif_register: + vpfe_isif_unregister_entities(&vpfe_dev->vpfe_isif); + + return ret; +} + +/* + * vpfe_unregister_entities() - unregister all v4l2 subdevs and media entities + * @vpfe_dev - ptr to vpfe capture device + * + * unregister all v4l2 subdevs and media entities + */ +static void vpfe_unregister_entities(struct vpfe_device *vpfe_dev) +{ + vpfe_isif_unregister_entities(&vpfe_dev->vpfe_isif); + vpfe_ipipeif_unregister_entities(&vpfe_dev->vpfe_ipipeif); + vpfe_ipipe_unregister_entities(&vpfe_dev->vpfe_ipipe); + vpfe_resizer_unregister_entities(&vpfe_dev->vpfe_resizer); +} + +/* + * vpfe_cleanup_modules() - cleanup all non-i2c v4l2 subdevs + * @vpfe_dev - ptr to vpfe capture device + * @pdev - pointer to platform device + * + * cleanup all v4l2 subdevs + */ +static void vpfe_cleanup_modules(struct vpfe_device *vpfe_dev, + struct platform_device *pdev) +{ + vpfe_isif_cleanup(&vpfe_dev->vpfe_isif, pdev); + vpfe_ipipeif_cleanup(&vpfe_dev->vpfe_ipipeif, pdev); + vpfe_ipipe_cleanup(&vpfe_dev->vpfe_ipipe, pdev); + vpfe_resizer_cleanup(&vpfe_dev->vpfe_resizer, pdev); +} + +/* + * vpfe_initialize_modules() - initialize all non-i2c v4l2 subdevs + * @vpfe_dev - ptr to vpfe capture device + * @pdev - pointer to platform device + * + * intialize all v4l2 subdevs and media entities + */ +static int vpfe_initialize_modules(struct vpfe_device *vpfe_dev, + struct platform_device *pdev) +{ + int ret; + + ret = vpfe_isif_init(&vpfe_dev->vpfe_isif, pdev); + if (ret) + return ret; + + ret = vpfe_ipipeif_init(&vpfe_dev->vpfe_ipipeif, pdev); + if (ret) + goto out_isif_init; + + ret = vpfe_ipipe_init(&vpfe_dev->vpfe_ipipe, pdev); + if (ret) + goto out_ipipeif_init; + + ret = vpfe_resizer_init(&vpfe_dev->vpfe_resizer, pdev); + if (ret) + goto out_ipipe_init; + + return 0; + +out_ipipe_init: + vpfe_ipipe_cleanup(&vpfe_dev->vpfe_ipipe, pdev); +out_ipipeif_init: + vpfe_ipipeif_cleanup(&vpfe_dev->vpfe_ipipeif, pdev); +out_isif_init: + vpfe_isif_cleanup(&vpfe_dev->vpfe_isif, pdev); + + return ret; +} + +/* + * vpfe_probe() : vpfe probe function + * @pdev: platform device pointer + * + * This function creates device entries by register itself to the V4L2 driver + * and initializes fields of each device objects + */ +static int vpfe_probe(struct platform_device *pdev) +{ + struct vpfe_device *vpfe_dev; + struct resource *res1; + int ret = -ENOMEM; + + vpfe_dev = kzalloc(sizeof(*vpfe_dev), GFP_KERNEL); + if (!vpfe_dev) { + v4l2_err(pdev->dev.driver, + "Failed to allocate memory for vpfe_dev\n"); + return ret; + } + + if (pdev->dev.platform_data == NULL) { + v4l2_err(pdev->dev.driver, "Unable to get vpfe config\n"); + ret = -ENOENT; + goto probe_free_dev_mem; + } + + vpfe_dev->cfg = pdev->dev.platform_data; + if (vpfe_dev->cfg->card_name == NULL || + vpfe_dev->cfg->sub_devs == NULL) { + v4l2_err(pdev->dev.driver, "null ptr in vpfe_cfg\n"); + ret = -ENOENT; + goto probe_free_dev_mem; + } + + /* Get VINT0 irq resource */ + res1 = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!res1) { + v4l2_err(pdev->dev.driver, + "Unable to get interrupt for VINT0\n"); + ret = -ENOENT; + goto probe_free_dev_mem; + } + vpfe_dev->ccdc_irq0 = res1->start; + + /* Get VINT1 irq resource */ + res1 = platform_get_resource(pdev, IORESOURCE_IRQ, 1); + if (!res1) { + v4l2_err(pdev->dev.driver, + "Unable to get interrupt for VINT1\n"); + ret = -ENOENT; + goto probe_free_dev_mem; + } + vpfe_dev->ccdc_irq1 = res1->start; + + /* Get DMA irq resource */ + res1 = platform_get_resource(pdev, IORESOURCE_IRQ, 2); + if (!res1) { + v4l2_err(pdev->dev.driver, + "Unable to get interrupt for DMA\n"); + ret = -ENOENT; + goto probe_free_dev_mem; + } + vpfe_dev->imp_dma_irq = res1->start; + + vpfe_dev->pdev = &pdev->dev; + + /* enable vpss clocks */ + ret = vpfe_enable_clock(vpfe_dev); + if (ret) + goto probe_free_dev_mem; + + ret = vpfe_initialize_modules(vpfe_dev, pdev); + if (ret) + goto probe_disable_clock; + + vpfe_dev->media_dev.dev = vpfe_dev->pdev; + strcpy((char *)&vpfe_dev->media_dev.model, "davinci-media"); + + ret = media_device_register(&vpfe_dev->media_dev); + if (ret) { + v4l2_err(pdev->dev.driver, + "Unable to register media device.\n"); + goto probe_out_entities_cleanup; + } + + vpfe_dev->v4l2_dev.mdev = &vpfe_dev->media_dev; + ret = v4l2_device_register(&pdev->dev, &vpfe_dev->v4l2_dev); + if (ret) { + v4l2_err(pdev->dev.driver, "Unable to register v4l2 device.\n"); + goto probe_out_media_unregister; + } + + v4l2_info(&vpfe_dev->v4l2_dev, "v4l2 device registered\n"); + /* set the driver data in platform device */ + platform_set_drvdata(pdev, vpfe_dev); + /* register subdevs/entities */ + ret = vpfe_register_entities(vpfe_dev); + if (ret) + goto probe_out_v4l2_unregister; + + ret = vpfe_attach_irq(vpfe_dev); + if (ret) + goto probe_out_entities_unregister; + + return 0; + +probe_out_entities_unregister: + vpfe_unregister_entities(vpfe_dev); + kzfree(vpfe_dev->sd); +probe_out_v4l2_unregister: + v4l2_device_unregister(&vpfe_dev->v4l2_dev); +probe_out_media_unregister: + media_device_unregister(&vpfe_dev->media_dev); +probe_out_entities_cleanup: + vpfe_cleanup_modules(vpfe_dev, pdev); +probe_disable_clock: + vpfe_disable_clock(vpfe_dev); +probe_free_dev_mem: + kzfree(vpfe_dev); + + return ret; +} + +/* + * vpfe_remove : This function un-registers device from V4L2 driver + */ +static int vpfe_remove(struct platform_device *pdev) +{ + struct vpfe_device *vpfe_dev = platform_get_drvdata(pdev); + + v4l2_info(pdev->dev.driver, "vpfe_remove\n"); + + kzfree(vpfe_dev->sd); + vpfe_detach_irq(vpfe_dev); + vpfe_unregister_entities(vpfe_dev); + vpfe_cleanup_modules(vpfe_dev, pdev); + v4l2_device_unregister(&vpfe_dev->v4l2_dev); + media_device_unregister(&vpfe_dev->media_dev); + vpfe_disable_clock(vpfe_dev); + kzfree(vpfe_dev); + + return 0; +} + +static struct platform_driver vpfe_driver = { + .driver = { + .name = CAPTURE_DRV_NAME, + .owner = THIS_MODULE, + }, + .probe = vpfe_probe, + .remove = vpfe_remove, +}; + +module_platform_driver(vpfe_driver); diff --git a/drivers/staging/media/davinci_vpfe/vpfe_mc_capture.h b/drivers/staging/media/davinci_vpfe/vpfe_mc_capture.h new file mode 100644 index 00000000000..2632a806c4a --- /dev/null +++ b/drivers/staging/media/davinci_vpfe/vpfe_mc_capture.h @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2012 Texas Instruments Inc + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Contributors: + * Manjunath Hadli <manjunath.hadli@ti.com> + * Prabhakar Lad <prabhakar.lad@ti.com> + */ + +#ifndef _DAVINCI_VPFE_MC_CAPTURE_H +#define _DAVINCI_VPFE_MC_CAPTURE_H + +#include "dm365_ipipe.h" +#include "dm365_ipipeif.h" +#include "dm365_isif.h" +#include "dm365_resizer.h" +#include "vpfe_video.h" + +#define VPFE_MAJOR_RELEASE 0 +#define VPFE_MINOR_RELEASE 0 +#define VPFE_BUILD 1 +#define VPFE_CAPTURE_VERSION_CODE ((VPFE_MAJOR_RELEASE << 16) | \ + (VPFE_MINOR_RELEASE << 8) | \ + VPFE_BUILD) + +/* IPIPE hardware limits */ +#define IPIPE_MAX_OUTPUT_WIDTH_A 2176 +#define IPIPE_MAX_OUTPUT_WIDTH_B 640 + +/* Based on max resolution supported. QXGA */ +#define IPIPE_MAX_OUTPUT_HEIGHT_A 1536 +/* Based on max resolution supported. VGA */ +#define IPIPE_MAX_OUTPUT_HEIGHT_B 480 + +#define to_vpfe_device(ptr_module) \ + container_of(ptr_module, struct vpfe_device, vpfe_##ptr_module) +#define to_device(ptr_module) \ + (to_vpfe_device(ptr_module)->dev) + +struct vpfe_device { + /* external registered sub devices */ + struct v4l2_subdev **sd; + /* number of registered external subdevs */ + unsigned int num_ext_subdevs; + /* vpfe cfg */ + struct vpfe_config *cfg; + /* clock ptrs for vpfe capture */ + struct clk **clks; + /* V4l2 device */ + struct v4l2_device v4l2_dev; + /* parent device */ + struct device *pdev; + /* IRQ number for DMA transfer completion at the image processor */ + unsigned int imp_dma_irq; + /* CCDC IRQs used when CCDC/ISIF output to SDRAM */ + unsigned int ccdc_irq0; + unsigned int ccdc_irq1; + /* maximum video memory that is available*/ + unsigned int video_limit; + /* media device */ + struct media_device media_dev; + /* ccdc subdevice */ + struct vpfe_isif_device vpfe_isif; + /* ipipeif subdevice */ + struct vpfe_ipipeif_device vpfe_ipipeif; + /* ipipe subdevice */ + struct vpfe_ipipe_device vpfe_ipipe; + /* resizer subdevice */ + struct vpfe_resizer_device vpfe_resizer; +}; + +/* File handle structure */ +struct vpfe_fh { + struct v4l2_fh vfh; + struct vpfe_video_device *video; + /* Indicates whether this file handle is doing IO */ + u8 io_allowed; +}; + +void mbus_to_pix(const struct v4l2_mbus_framefmt *mbus, + struct v4l2_pix_format *pix); + +#endif /* _DAVINCI_VPFE_MC_CAPTURE_H */ diff --git a/drivers/staging/media/davinci_vpfe/vpfe_video.c b/drivers/staging/media/davinci_vpfe/vpfe_video.c new file mode 100644 index 00000000000..d95c427043d --- /dev/null +++ b/drivers/staging/media/davinci_vpfe/vpfe_video.c @@ -0,0 +1,1637 @@ +/* + * Copyright (C) 2012 Texas Instruments Inc + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Contributors: + * Manjunath Hadli <manjunath.hadli@ti.com> + * Prabhakar Lad <prabhakar.lad@ti.com> + */ + +#include <linux/module.h> +#include <linux/slab.h> + +#include <media/v4l2-ioctl.h> + +#include "vpfe.h" +#include "vpfe_mc_capture.h" + +/* minimum number of buffers needed in cont-mode */ +#define MIN_NUM_BUFFERS 3 + +static int debug; + +/* get v4l2 subdev pointer to external subdev which is active */ +static struct media_entity *vpfe_get_input_entity + (struct vpfe_video_device *video) +{ + struct vpfe_device *vpfe_dev = video->vpfe_dev; + struct media_pad *remote; + + remote = media_entity_remote_pad(&vpfe_dev->vpfe_isif.pads[0]); + if (remote == NULL) { + pr_err("Invalid media connection to isif/ccdc\n"); + return NULL; + } + return remote->entity; +} + +/* updates external subdev(sensor/decoder) which is active */ +static int vpfe_update_current_ext_subdev(struct vpfe_video_device *video) +{ + struct vpfe_device *vpfe_dev = video->vpfe_dev; + struct vpfe_config *vpfe_cfg; + struct v4l2_subdev *subdev; + struct media_pad *remote; + int i; + + remote = media_entity_remote_pad(&vpfe_dev->vpfe_isif.pads[0]); + if (remote == NULL) { + pr_err("Invalid media connection to isif/ccdc\n"); + return -EINVAL; + } + + subdev = media_entity_to_v4l2_subdev(remote->entity); + vpfe_cfg = vpfe_dev->pdev->platform_data; + for (i = 0; i < vpfe_cfg->num_subdevs; i++) { + if (!strcmp(vpfe_cfg->sub_devs[i].module_name, subdev->name)) { + video->current_ext_subdev = &vpfe_cfg->sub_devs[i]; + break; + } + } + + /* if user not linked decoder/sensor to isif/ccdc */ + if (i == vpfe_cfg->num_subdevs) { + pr_err("Invalid media chain connection to isif/ccdc\n"); + return -EINVAL; + } + /* find the v4l2 subdev pointer */ + for (i = 0; i < vpfe_dev->num_ext_subdevs; i++) { + if (!strcmp(video->current_ext_subdev->module_name, + vpfe_dev->sd[i]->name)) + video->current_ext_subdev->subdev = vpfe_dev->sd[i]; + } + return 0; +} + +/* get the subdev which is connected to the output video node */ +static struct v4l2_subdev * +vpfe_video_remote_subdev(struct vpfe_video_device *video, u32 *pad) +{ + struct media_pad *remote = media_entity_remote_pad(&video->pad); + + if (remote == NULL || remote->entity->type != MEDIA_ENT_T_V4L2_SUBDEV) + return NULL; + if (pad) + *pad = remote->index; + return media_entity_to_v4l2_subdev(remote->entity); +} + +/* get the format set at output pad of the adjacent subdev */ +static int +__vpfe_video_get_format(struct vpfe_video_device *video, + struct v4l2_format *format) +{ + struct v4l2_subdev_format fmt; + struct v4l2_subdev *subdev; + struct media_pad *remote; + u32 pad; + int ret; + + subdev = vpfe_video_remote_subdev(video, &pad); + if (subdev == NULL) + return -EINVAL; + + fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE; + remote = media_entity_remote_pad(&video->pad); + fmt.pad = remote->index; + + ret = v4l2_subdev_call(subdev, pad, get_fmt, NULL, &fmt); + if (ret == -ENOIOCTLCMD) + return -EINVAL; + + format->type = video->type; + /* convert mbus_format to v4l2_format */ + v4l2_fill_pix_format(&format->fmt.pix, &fmt.format); + mbus_to_pix(&fmt.format, &format->fmt.pix); + + return 0; +} + +/* make a note of pipeline details */ +static void vpfe_prepare_pipeline(struct vpfe_video_device *video) +{ + struct media_entity *entity = &video->video_dev.entity; + struct media_device *mdev = entity->parent; + struct vpfe_pipeline *pipe = &video->pipe; + struct vpfe_video_device *far_end = NULL; + struct media_entity_graph graph; + + pipe->input_num = 0; + pipe->output_num = 0; + + if (video->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) + pipe->inputs[pipe->input_num++] = video; + else + pipe->outputs[pipe->output_num++] = video; + + mutex_lock(&mdev->graph_mutex); + media_entity_graph_walk_start(&graph, entity); + while ((entity = media_entity_graph_walk_next(&graph))) { + if (entity == &video->video_dev.entity) + continue; + if (media_entity_type(entity) != MEDIA_ENT_T_DEVNODE) + continue; + far_end = to_vpfe_video(media_entity_to_video_device(entity)); + if (far_end->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) + pipe->inputs[pipe->input_num++] = far_end; + else + pipe->outputs[pipe->output_num++] = far_end; + } + mutex_unlock(&mdev->graph_mutex); +} + +/* update pipe state selected by user */ +static int vpfe_update_pipe_state(struct vpfe_video_device *video) +{ + struct vpfe_pipeline *pipe = &video->pipe; + int ret; + + vpfe_prepare_pipeline(video); + + /* Find out if there is any input video + if yes, it is single shot. + */ + if (pipe->input_num == 0) { + pipe->state = VPFE_PIPELINE_STREAM_CONTINUOUS; + ret = vpfe_update_current_ext_subdev(video); + if (ret) { + pr_err("Invalid external subdev\n"); + return ret; + } + } else { + pipe->state = VPFE_PIPELINE_STREAM_SINGLESHOT; + } + video->initialized = 1; + video->skip_frame_count = 1; + video->skip_frame_count_init = 1; + return 0; +} + +/* checks wether pipeline is ready for enabling */ +int vpfe_video_is_pipe_ready(struct vpfe_pipeline *pipe) +{ + int i; + + for (i = 0; i < pipe->input_num; i++) + if (!pipe->inputs[i]->started || + pipe->inputs[i]->state != VPFE_VIDEO_BUFFER_QUEUED) + return 0; + for (i = 0; i < pipe->output_num; i++) + if (!pipe->outputs[i]->started || + pipe->outputs[i]->state != VPFE_VIDEO_BUFFER_QUEUED) + return 0; + return 1; +} + +/** + * Validate a pipeline by checking both ends of all links for format + * discrepancies. + * + * Return 0 if all formats match, or -EPIPE if at least one link is found with + * different formats on its two ends. + */ +static int vpfe_video_validate_pipeline(struct vpfe_pipeline *pipe) +{ + struct v4l2_subdev_format fmt_source; + struct v4l2_subdev_format fmt_sink; + struct v4l2_subdev *subdev; + struct media_pad *pad; + int ret; + + /* + * Should not matter if it is output[0] or 1 as + * the general ideas is to traverse backwards and + * the fact that the out video node always has the + * format of the connected pad. + */ + subdev = vpfe_video_remote_subdev(pipe->outputs[0], NULL); + if (subdev == NULL) + return -EPIPE; + + while (1) { + /* Retrieve the sink format */ + pad = &subdev->entity.pads[0]; + if (!(pad->flags & MEDIA_PAD_FL_SINK)) + break; + + fmt_sink.which = V4L2_SUBDEV_FORMAT_ACTIVE; + fmt_sink.pad = pad->index; + ret = v4l2_subdev_call(subdev, pad, get_fmt, NULL, + &fmt_sink); + + if (ret < 0 && ret != -ENOIOCTLCMD) + return -EPIPE; + + /* Retrieve the source format */ + pad = media_entity_remote_pad(pad); + if (pad == NULL || + pad->entity->type != MEDIA_ENT_T_V4L2_SUBDEV) + break; + + subdev = media_entity_to_v4l2_subdev(pad->entity); + + fmt_source.which = V4L2_SUBDEV_FORMAT_ACTIVE; + fmt_source.pad = pad->index; + ret = v4l2_subdev_call(subdev, pad, get_fmt, NULL, &fmt_source); + if (ret < 0 && ret != -ENOIOCTLCMD) + return -EPIPE; + + /* Check if the two ends match */ + if (fmt_source.format.code != fmt_sink.format.code || + fmt_source.format.width != fmt_sink.format.width || + fmt_source.format.height != fmt_sink.format.height) + return -EPIPE; + } + return 0; +} + +/* + * vpfe_pipeline_enable() - Enable streaming on a pipeline + * @vpfe_dev: vpfe device + * @pipe: vpfe pipeline + * + * Walk the entities chain starting at the pipeline output video node and start + * all modules in the chain in the given mode. + * + * Return 0 if successful, or the return value of the failed video::s_stream + * operation otherwise. + */ +static int vpfe_pipeline_enable(struct vpfe_pipeline *pipe) +{ + struct media_entity_graph graph; + struct media_entity *entity; + struct v4l2_subdev *subdev; + struct media_device *mdev; + int ret = 0; + + if (pipe->state == VPFE_PIPELINE_STREAM_CONTINUOUS) + entity = vpfe_get_input_entity(pipe->outputs[0]); + else + entity = &pipe->inputs[0]->video_dev.entity; + + mdev = entity->parent; + mutex_lock(&mdev->graph_mutex); + media_entity_graph_walk_start(&graph, entity); + while ((entity = media_entity_graph_walk_next(&graph))) { + + if (media_entity_type(entity) == MEDIA_ENT_T_DEVNODE) + continue; + subdev = media_entity_to_v4l2_subdev(entity); + ret = v4l2_subdev_call(subdev, video, s_stream, 1); + if (ret < 0 && ret != -ENOIOCTLCMD) + break; + } + mutex_unlock(&mdev->graph_mutex); + return ret; +} + +/* + * vpfe_pipeline_disable() - Disable streaming on a pipeline + * @vpfe_dev: vpfe device + * @pipe: VPFE pipeline + * + * Walk the entities chain starting at the pipeline output video node and stop + * all modules in the chain. + * + * Return 0 if all modules have been properly stopped, or -ETIMEDOUT if a module + * can't be stopped. + */ +static int vpfe_pipeline_disable(struct vpfe_pipeline *pipe) +{ + struct media_entity_graph graph; + struct media_entity *entity; + struct v4l2_subdev *subdev; + struct media_device *mdev; + int ret = 0; + + if (pipe->state == VPFE_PIPELINE_STREAM_CONTINUOUS) + entity = vpfe_get_input_entity(pipe->outputs[0]); + else + entity = &pipe->inputs[0]->video_dev.entity; + + mdev = entity->parent; + mutex_lock(&mdev->graph_mutex); + media_entity_graph_walk_start(&graph, entity); + + while ((entity = media_entity_graph_walk_next(&graph))) { + + if (media_entity_type(entity) == MEDIA_ENT_T_DEVNODE) + continue; + subdev = media_entity_to_v4l2_subdev(entity); + ret = v4l2_subdev_call(subdev, video, s_stream, 0); + if (ret < 0 && ret != -ENOIOCTLCMD) + break; + } + mutex_unlock(&mdev->graph_mutex); + + return ret ? -ETIMEDOUT : 0; +} + +/* + * vpfe_pipeline_set_stream() - Enable/disable streaming on a pipeline + * @vpfe_dev: VPFE device + * @pipe: VPFE pipeline + * @state: Stream state (stopped or active) + * + * Set the pipeline to the given stream state. + * + * Return 0 if successful, or the return value of the failed video::s_stream + * operation otherwise. + */ +static int vpfe_pipeline_set_stream(struct vpfe_pipeline *pipe, + enum vpfe_pipeline_stream_state state) +{ + if (state == VPFE_PIPELINE_STREAM_STOPPED) + return vpfe_pipeline_disable(pipe); + + return vpfe_pipeline_enable(pipe); +} + +static int all_videos_stopped(struct vpfe_video_device *video) +{ + struct vpfe_pipeline *pipe = &video->pipe; + int i; + + for (i = 0; i < pipe->input_num; i++) + if (pipe->inputs[i]->started) + return 0; + for (i = 0; i < pipe->output_num; i++) + if (pipe->outputs[i]->started) + return 0; + return 1; +} + +/* + * vpfe_open() - open video device + * @file: file pointer + * + * initialize media pipeline state, allocate memory for file handle + * + * Return 0 if successful, or the return -ENODEV otherwise. + */ +static int vpfe_open(struct file *file) +{ + struct vpfe_video_device *video = video_drvdata(file); + struct vpfe_fh *handle; + + /* Allocate memory for the file handle object */ + handle = kzalloc(sizeof(struct vpfe_fh), GFP_KERNEL); + + if (handle == NULL) + return -ENOMEM; + + v4l2_fh_init(&handle->vfh, &video->video_dev); + v4l2_fh_add(&handle->vfh); + + mutex_lock(&video->lock); + /* If decoder is not initialized. initialize it */ + if (!video->initialized && vpfe_update_pipe_state(video)) { + mutex_unlock(&video->lock); + return -ENODEV; + } + /* Increment device users counter */ + video->usrs++; + /* Set io_allowed member to false */ + handle->io_allowed = 0; + handle->video = video; + file->private_data = &handle->vfh; + mutex_unlock(&video->lock); + + return 0; +} + +/* get the next buffer available from dma queue */ +static unsigned long +vpfe_video_get_next_buffer(struct vpfe_video_device *video) +{ + video->cur_frm = video->next_frm = + list_entry(video->dma_queue.next, + struct vpfe_cap_buffer, list); + + list_del(&video->next_frm->list); + video->next_frm->vb.state = VB2_BUF_STATE_ACTIVE; + return vb2_dma_contig_plane_dma_addr(&video->next_frm->vb, 0); +} + +/* schedule the next buffer which is available on dma queue */ +void vpfe_video_schedule_next_buffer(struct vpfe_video_device *video) +{ + struct vpfe_device *vpfe_dev = video->vpfe_dev; + unsigned long addr; + + if (list_empty(&video->dma_queue)) + return; + + video->next_frm = list_entry(video->dma_queue.next, + struct vpfe_cap_buffer, list); + + if (VPFE_PIPELINE_STREAM_SINGLESHOT == video->pipe.state) + video->cur_frm = video->next_frm; + + list_del(&video->next_frm->list); + video->next_frm->vb.state = VB2_BUF_STATE_ACTIVE; + addr = vb2_dma_contig_plane_dma_addr(&video->next_frm->vb, 0); + video->ops->queue(vpfe_dev, addr); + video->state = VPFE_VIDEO_BUFFER_QUEUED; +} + +/* schedule the buffer for capturing bottom field */ +void vpfe_video_schedule_bottom_field(struct vpfe_video_device *video) +{ + struct vpfe_device *vpfe_dev = video->vpfe_dev; + unsigned long addr; + + addr = vb2_dma_contig_plane_dma_addr(&video->cur_frm->vb, 0); + addr += video->field_off; + video->ops->queue(vpfe_dev, addr); +} + +/* make buffer available for dequeue */ +void vpfe_video_process_buffer_complete(struct vpfe_video_device *video) +{ + struct vpfe_pipeline *pipe = &video->pipe; + + do_gettimeofday(&video->cur_frm->vb.v4l2_buf.timestamp); + vb2_buffer_done(&video->cur_frm->vb, VB2_BUF_STATE_DONE); + if (pipe->state == VPFE_PIPELINE_STREAM_CONTINUOUS) + video->cur_frm = video->next_frm; +} + +/* vpfe_stop_capture() - stop streaming */ +static void vpfe_stop_capture(struct vpfe_video_device *video) +{ + struct vpfe_pipeline *pipe = &video->pipe; + + video->started = 0; + + if (video->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) + return; + if (all_videos_stopped(video)) + vpfe_pipeline_set_stream(pipe, + VPFE_PIPELINE_STREAM_STOPPED); +} + +/* + * vpfe_release() - release video device + * @file: file pointer + * + * deletes buffer queue, frees the buffers and the vpfe file handle + * + * Return 0 + */ +static int vpfe_release(struct file *file) +{ + struct vpfe_video_device *video = video_drvdata(file); + struct v4l2_fh *vfh = file->private_data; + struct vpfe_device *vpfe_dev = video->vpfe_dev; + struct vpfe_fh *fh = container_of(vfh, struct vpfe_fh, vfh); + + v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_release\n"); + + /* Get the device lock */ + mutex_lock(&video->lock); + /* if this instance is doing IO */ + if (fh->io_allowed) { + if (video->started) { + vpfe_stop_capture(video); + /* mark pipe state as stopped in vpfe_release(), + as app might call streamon() after streamoff() + in which case driver has to start streaming. + */ + video->pipe.state = VPFE_PIPELINE_STREAM_STOPPED; + vb2_streamoff(&video->buffer_queue, + video->buffer_queue.type); + } + video->io_usrs = 0; + /* Free buffers allocated */ + vb2_queue_release(&video->buffer_queue); + vb2_dma_contig_cleanup_ctx(video->alloc_ctx); + } + /* Decrement device users counter */ + video->usrs--; + v4l2_fh_del(&fh->vfh); + v4l2_fh_exit(&fh->vfh); + /* If this is the last file handle */ + if (!video->usrs) + video->initialized = 0; + mutex_unlock(&video->lock); + file->private_data = NULL; + /* Free memory allocated to file handle object */ + v4l2_fh_del(vfh); + kzfree(fh); + return 0; +} + +/* + * vpfe_mmap() - It is used to map kernel space buffers + * into user spaces + */ +static int vpfe_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct vpfe_video_device *video = video_drvdata(file); + struct vpfe_device *vpfe_dev = video->vpfe_dev; + + v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_mmap\n"); + return vb2_mmap(&video->buffer_queue, vma); +} + +/* + * vpfe_poll() - It is used for select/poll system call + */ +static unsigned int vpfe_poll(struct file *file, poll_table *wait) +{ + struct vpfe_video_device *video = video_drvdata(file); + struct vpfe_device *vpfe_dev = video->vpfe_dev; + + v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_poll\n"); + if (video->started) + return vb2_poll(&video->buffer_queue, file, wait); + return 0; +} + +/* vpfe capture driver file operations */ +static const struct v4l2_file_operations vpfe_fops = { + .owner = THIS_MODULE, + .open = vpfe_open, + .release = vpfe_release, + .unlocked_ioctl = video_ioctl2, + .mmap = vpfe_mmap, + .poll = vpfe_poll +}; + +/* + * vpfe_querycap() - query capabilities of video device + * @file: file pointer + * @priv: void pointer + * @cap: pointer to v4l2_capability structure + * + * fills v4l2 capabilities structure + * + * Return 0 + */ +static int vpfe_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + struct vpfe_video_device *video = video_drvdata(file); + struct vpfe_device *vpfe_dev = video->vpfe_dev; + + v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_querycap\n"); + + if (video->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) + cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING; + else + cap->capabilities = V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_STREAMING; + cap->device_caps = cap->capabilities; + cap->version = VPFE_CAPTURE_VERSION_CODE; + strlcpy(cap->driver, CAPTURE_DRV_NAME, sizeof(cap->driver)); + strlcpy(cap->bus_info, "VPFE", sizeof(cap->bus_info)); + strlcpy(cap->card, vpfe_dev->cfg->card_name, sizeof(cap->card)); + + return 0; +} + +/* + * vpfe_g_fmt() - get the format which is active on video device + * @file: file pointer + * @priv: void pointer + * @fmt: pointer to v4l2_format structure + * + * fills v4l2 format structure with active format + * + * Return 0 + */ +static int vpfe_g_fmt(struct file *file, void *priv, + struct v4l2_format *fmt) +{ + struct vpfe_video_device *video = video_drvdata(file); + struct vpfe_device *vpfe_dev = video->vpfe_dev; + + v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_g_fmt\n"); + /* Fill in the information about format */ + *fmt = video->fmt; + return 0; +} + +/* + * vpfe_enum_fmt() - enum formats supported on media chain + * @file: file pointer + * @priv: void pointer + * @fmt: pointer to v4l2_fmtdesc structure + * + * fills v4l2_fmtdesc structure with output format set on adjacent subdev, + * only one format is enumearted as subdevs are already configured + * + * Return 0 if successful, error code otherwise + */ +static int vpfe_enum_fmt(struct file *file, void *priv, + struct v4l2_fmtdesc *fmt) +{ + struct vpfe_video_device *video = video_drvdata(file); + struct vpfe_device *vpfe_dev = video->vpfe_dev; + struct v4l2_subdev_format sd_fmt; + struct v4l2_mbus_framefmt mbus; + struct v4l2_subdev *subdev; + struct v4l2_format format; + struct media_pad *remote; + int ret; + + v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_enum_fmt\n"); + + /* since already subdev pad format is set, + only one pixel format is available */ + if (fmt->index > 0) { + v4l2_err(&vpfe_dev->v4l2_dev, "Invalid index\n"); + return -EINVAL; + } + /* get the remote pad */ + remote = media_entity_remote_pad(&video->pad); + if (remote == NULL) { + v4l2_err(&vpfe_dev->v4l2_dev, + "invalid remote pad for video node\n"); + return -EINVAL; + } + /* get the remote subdev */ + subdev = vpfe_video_remote_subdev(video, NULL); + if (subdev == NULL) { + v4l2_err(&vpfe_dev->v4l2_dev, + "invalid remote subdev for video node\n"); + return -EINVAL; + } + sd_fmt.pad = remote->index; + sd_fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE; + /* get output format of remote subdev */ + ret = v4l2_subdev_call(subdev, pad, get_fmt, NULL, &sd_fmt); + if (ret) { + v4l2_err(&vpfe_dev->v4l2_dev, + "invalid remote subdev for video node\n"); + return ret; + } + /* convert to pix format */ + mbus.code = sd_fmt.format.code; + mbus_to_pix(&mbus, &format.fmt.pix); + /* copy the result */ + fmt->pixelformat = format.fmt.pix.pixelformat; + + return 0; +} + +/* + * vpfe_s_fmt() - set the format on video device + * @file: file pointer + * @priv: void pointer + * @fmt: pointer to v4l2_format structure + * + * validate and set the format on video device + * + * Return 0 on success, error code otherwise + */ +static int vpfe_s_fmt(struct file *file, void *priv, + struct v4l2_format *fmt) +{ + struct vpfe_video_device *video = video_drvdata(file); + struct vpfe_device *vpfe_dev = video->vpfe_dev; + struct v4l2_format format; + int ret; + + v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_s_fmt\n"); + /* If streaming is started, return error */ + if (video->started) { + v4l2_err(&vpfe_dev->v4l2_dev, "Streaming is started\n"); + return -EBUSY; + } + /* get adjacent subdev's output pad format */ + ret = __vpfe_video_get_format(video, &format); + if (ret) + return ret; + *fmt = format; + video->fmt = *fmt; + return 0; +} + +/* + * vpfe_try_fmt() - try the format on video device + * @file: file pointer + * @priv: void pointer + * @fmt: pointer to v4l2_format structure + * + * validate the format, update with correct format + * based on output format set on adjacent subdev + * + * Return 0 on success, error code otherwise + */ +static int vpfe_try_fmt(struct file *file, void *priv, + struct v4l2_format *fmt) +{ + struct vpfe_video_device *video = video_drvdata(file); + struct vpfe_device *vpfe_dev = video->vpfe_dev; + struct v4l2_format format; + int ret; + + v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_try_fmt\n"); + /* get adjacent subdev's output pad format */ + ret = __vpfe_video_get_format(video, &format); + if (ret) + return ret; + + *fmt = format; + return 0; +} + +/* + * vpfe_enum_input() - enum inputs supported on media chain + * @file: file pointer + * @priv: void pointer + * @fmt: pointer to v4l2_fmtdesc structure + * + * fills v4l2_input structure with input available on media chain, + * only one input is enumearted as media chain is setup by this time + * + * Return 0 if successful, -EINVAL is media chain is invalid + */ +static int vpfe_enum_input(struct file *file, void *priv, + struct v4l2_input *inp) +{ + struct vpfe_video_device *video = video_drvdata(file); + struct vpfe_ext_subdev_info *sdinfo = video->current_ext_subdev; + struct vpfe_device *vpfe_dev = video->vpfe_dev; + + v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_enum_input\n"); + /* enumerate from the subdev user has chosen through mc */ + if (inp->index < sdinfo->num_inputs) { + memcpy(inp, &sdinfo->inputs[inp->index], + sizeof(struct v4l2_input)); + return 0; + } + return -EINVAL; +} + +/* + * vpfe_g_input() - get index of the input which is active + * @file: file pointer + * @priv: void pointer + * @index: pointer to unsigned int + * + * set index with input index which is active + */ +static int vpfe_g_input(struct file *file, void *priv, unsigned int *index) +{ + struct vpfe_video_device *video = video_drvdata(file); + struct vpfe_device *vpfe_dev = video->vpfe_dev; + + v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_g_input\n"); + + *index = video->current_input; + return 0; +} + +/* + * vpfe_s_input() - set input which is pointed by input index + * @file: file pointer + * @priv: void pointer + * @index: pointer to unsigned int + * + * set input on external subdev + * + * Return 0 on success, error code otherwise + */ +static int vpfe_s_input(struct file *file, void *priv, unsigned int index) +{ + struct vpfe_video_device *video = video_drvdata(file); + struct vpfe_device *vpfe_dev = video->vpfe_dev; + struct vpfe_ext_subdev_info *sdinfo; + struct vpfe_route *route; + struct v4l2_input *inps; + u32 output; + u32 input; + int ret; + int i; + + v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_s_input\n"); + + ret = mutex_lock_interruptible(&video->lock); + if (ret) + return ret; + /* + * If streaming is started return device busy + * error + */ + if (video->started) { + v4l2_err(&vpfe_dev->v4l2_dev, "Streaming is on\n"); + ret = -EBUSY; + goto unlock_out; + } + + sdinfo = video->current_ext_subdev; + if (!sdinfo->registered) { + ret = -EINVAL; + goto unlock_out; + } + if (vpfe_dev->cfg->setup_input && + vpfe_dev->cfg->setup_input(sdinfo->grp_id) < 0) { + ret = -EFAULT; + v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, + "couldn't setup input for %s\n", + sdinfo->module_name); + goto unlock_out; + } + route = &sdinfo->routes[index]; + if (route && sdinfo->can_route) { + input = route->input; + output = route->output; + ret = v4l2_device_call_until_err(&vpfe_dev->v4l2_dev, + sdinfo->grp_id, video, + s_routing, input, output, 0); + if (ret) { + v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, + "s_input:error in setting input in decoder\n"); + ret = -EINVAL; + goto unlock_out; + } + } + /* set standards set by subdev in video device */ + for (i = 0; i < sdinfo->num_inputs; i++) { + inps = &sdinfo->inputs[i]; + video->video_dev.tvnorms |= inps->std; + } + video->current_input = index; +unlock_out: + mutex_unlock(&video->lock); + return ret; +} + +/* + * vpfe_querystd() - query std which is being input on external subdev + * @file: file pointer + * @priv: void pointer + * @std_id: pointer to v4l2_std_id structure + * + * call external subdev through v4l2_device_call_until_err to + * get the std that is being active. + * + * Return 0 on success, error code otherwise + */ +static int vpfe_querystd(struct file *file, void *priv, v4l2_std_id *std_id) +{ + struct vpfe_video_device *video = video_drvdata(file); + struct vpfe_device *vpfe_dev = video->vpfe_dev; + struct vpfe_ext_subdev_info *sdinfo; + int ret; + + v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_querystd\n"); + + ret = mutex_lock_interruptible(&video->lock); + sdinfo = video->current_ext_subdev; + if (ret) + return ret; + /* Call querystd function of decoder device */ + ret = v4l2_device_call_until_err(&vpfe_dev->v4l2_dev, sdinfo->grp_id, + video, querystd, std_id); + mutex_unlock(&video->lock); + return ret; +} + +/* + * vpfe_s_std() - set std on external subdev + * @file: file pointer + * @priv: void pointer + * @std_id: pointer to v4l2_std_id structure + * + * set std pointed by std_id on external subdev by calling it using + * v4l2_device_call_until_err + * + * Return 0 on success, error code otherwise + */ +static int vpfe_s_std(struct file *file, void *priv, v4l2_std_id std_id) +{ + struct vpfe_video_device *video = video_drvdata(file); + struct vpfe_device *vpfe_dev = video->vpfe_dev; + struct vpfe_ext_subdev_info *sdinfo; + int ret; + + v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_s_std\n"); + + /* Call decoder driver function to set the standard */ + ret = mutex_lock_interruptible(&video->lock); + if (ret) + return ret; + sdinfo = video->current_ext_subdev; + /* If streaming is started, return device busy error */ + if (video->started) { + v4l2_err(&vpfe_dev->v4l2_dev, "streaming is started\n"); + ret = -EBUSY; + goto unlock_out; + } + ret = v4l2_device_call_until_err(&vpfe_dev->v4l2_dev, sdinfo->grp_id, + video, s_std, std_id); + if (ret < 0) { + v4l2_err(&vpfe_dev->v4l2_dev, "Failed to set standard\n"); + video->stdid = V4L2_STD_UNKNOWN; + goto unlock_out; + } + video->stdid = std_id; +unlock_out: + mutex_unlock(&video->lock); + return ret; +} + +static int vpfe_g_std(struct file *file, void *priv, v4l2_std_id *tvnorm) +{ + struct vpfe_video_device *video = video_drvdata(file); + struct vpfe_device *vpfe_dev = video->vpfe_dev; + + v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_g_std\n"); + *tvnorm = video->stdid; + return 0; +} + +/* + * vpfe_enum_dv_timings() - enumerate dv_timings which are supported by + * to external subdev + * @file: file pointer + * @priv: void pointer + * @timings: pointer to v4l2_enum_dv_timings structure + * + * enum dv_timings's which are supported by external subdev through + * v4l2_subdev_call + * + * Return 0 on success, error code otherwise + */ +static int +vpfe_enum_dv_timings(struct file *file, void *fh, + struct v4l2_enum_dv_timings *timings) +{ + struct vpfe_video_device *video = video_drvdata(file); + struct vpfe_device *vpfe_dev = video->vpfe_dev; + struct v4l2_subdev *subdev = video->current_ext_subdev->subdev; + + timings->pad = 0; + + v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_enum_dv_timings\n"); + return v4l2_subdev_call(subdev, pad, enum_dv_timings, timings); +} + +/* + * vpfe_query_dv_timings() - query the dv_timings which is being input + * to external subdev + * @file: file pointer + * @priv: void pointer + * @timings: pointer to v4l2_dv_timings structure + * + * get dv_timings which is being input on external subdev through + * v4l2_subdev_call + * + * Return 0 on success, error code otherwise + */ +static int +vpfe_query_dv_timings(struct file *file, void *fh, + struct v4l2_dv_timings *timings) +{ + struct vpfe_video_device *video = video_drvdata(file); + struct vpfe_device *vpfe_dev = video->vpfe_dev; + struct v4l2_subdev *subdev = video->current_ext_subdev->subdev; + + v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_query_dv_timings\n"); + return v4l2_subdev_call(subdev, video, query_dv_timings, timings); +} + +/* + * vpfe_s_dv_timings() - set dv_timings on external subdev + * @file: file pointer + * @priv: void pointer + * @timings: pointer to v4l2_dv_timings structure + * + * set dv_timings pointed by timings on external subdev through + * v4l2_device_call_until_err, this configures amplifier also + * + * Return 0 on success, error code otherwise + */ +static int +vpfe_s_dv_timings(struct file *file, void *fh, + struct v4l2_dv_timings *timings) +{ + struct vpfe_video_device *video = video_drvdata(file); + struct vpfe_device *vpfe_dev = video->vpfe_dev; + + v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_s_dv_timings\n"); + + video->stdid = V4L2_STD_UNKNOWN; + return v4l2_device_call_until_err(&vpfe_dev->v4l2_dev, + video->current_ext_subdev->grp_id, + video, s_dv_timings, timings); +} + +/* + * vpfe_g_dv_timings() - get dv_timings which is set on external subdev + * @file: file pointer + * @priv: void pointer + * @timings: pointer to v4l2_dv_timings structure + * + * get dv_timings which is set on external subdev through + * v4l2_subdev_call + * + * Return 0 on success, error code otherwise + */ +static int +vpfe_g_dv_timings(struct file *file, void *fh, + struct v4l2_dv_timings *timings) +{ + struct vpfe_video_device *video = video_drvdata(file); + struct vpfe_device *vpfe_dev = video->vpfe_dev; + struct v4l2_subdev *subdev = video->current_ext_subdev->subdev; + + v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_g_dv_timings\n"); + return v4l2_subdev_call(subdev, video, g_dv_timings, timings); +} + +/* + * Videobuf operations + */ +/* + * vpfe_buffer_queue_setup : Callback function for buffer setup. + * @vq: vb2_queue ptr + * @fmt: v4l2 format + * @nbuffers: ptr to number of buffers requested by application + * @nplanes:: contains number of distinct video planes needed to hold a frame + * @sizes[]: contains the size (in bytes) of each plane. + * @alloc_ctxs: ptr to allocation context + * + * This callback function is called when reqbuf() is called to adjust + * the buffer nbuffers and buffer size + */ +static int +vpfe_buffer_queue_setup(struct vb2_queue *vq, const struct v4l2_format *fmt, + unsigned int *nbuffers, unsigned int *nplanes, + unsigned int sizes[], void *alloc_ctxs[]) +{ + struct vpfe_fh *fh = vb2_get_drv_priv(vq); + struct vpfe_video_device *video = fh->video; + struct vpfe_device *vpfe_dev = video->vpfe_dev; + struct vpfe_pipeline *pipe = &video->pipe; + unsigned long size; + + v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_buffer_queue_setup\n"); + size = video->fmt.fmt.pix.sizeimage; + + if (vpfe_dev->video_limit) { + while (size * *nbuffers > vpfe_dev->video_limit) + (*nbuffers)--; + } + if (pipe->state == VPFE_PIPELINE_STREAM_CONTINUOUS) { + if (*nbuffers < MIN_NUM_BUFFERS) + *nbuffers = MIN_NUM_BUFFERS; + } + *nplanes = 1; + sizes[0] = size; + alloc_ctxs[0] = video->alloc_ctx; + v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, + "nbuffers=%d, size=%lu\n", *nbuffers, size); + return 0; +} + +/* + * vpfe_buffer_prepare : callback function for buffer prepare + * @vb: ptr to vb2_buffer + * + * This is the callback function for buffer prepare when vb2_qbuf() + * function is called. The buffer is prepared and user space virtual address + * or user address is converted into physical address + */ +static int vpfe_buffer_prepare(struct vb2_buffer *vb) +{ + struct vpfe_fh *fh = vb2_get_drv_priv(vb->vb2_queue); + struct vpfe_video_device *video = fh->video; + struct vpfe_device *vpfe_dev = video->vpfe_dev; + unsigned long addr; + + v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_buffer_prepare\n"); + + if (vb->state != VB2_BUF_STATE_ACTIVE && + vb->state != VB2_BUF_STATE_PREPARED) + return 0; + + /* Initialize buffer */ + vb2_set_plane_payload(vb, 0, video->fmt.fmt.pix.sizeimage); + if (vb2_plane_vaddr(vb, 0) && + vb2_get_plane_payload(vb, 0) > vb2_plane_size(vb, 0)) + return -EINVAL; + + addr = vb2_dma_contig_plane_dma_addr(vb, 0); + /* Make sure user addresses are aligned to 32 bytes */ + if (!ALIGN(addr, 32)) + return -EINVAL; + + return 0; +} + +static void vpfe_buffer_queue(struct vb2_buffer *vb) +{ + /* Get the file handle object and device object */ + struct vpfe_fh *fh = vb2_get_drv_priv(vb->vb2_queue); + struct vpfe_video_device *video = fh->video; + struct vpfe_device *vpfe_dev = video->vpfe_dev; + struct vpfe_pipeline *pipe = &video->pipe; + struct vpfe_cap_buffer *buf = container_of(vb, + struct vpfe_cap_buffer, vb); + unsigned long flags; + unsigned long empty; + unsigned long addr; + + spin_lock_irqsave(&video->dma_queue_lock, flags); + empty = list_empty(&video->dma_queue); + /* add the buffer to the DMA queue */ + list_add_tail(&buf->list, &video->dma_queue); + spin_unlock_irqrestore(&video->dma_queue_lock, flags); + /* this case happens in case of single shot */ + if (empty && video->started && pipe->state == + VPFE_PIPELINE_STREAM_SINGLESHOT && + video->state == VPFE_VIDEO_BUFFER_NOT_QUEUED) { + spin_lock(&video->dma_queue_lock); + addr = vpfe_video_get_next_buffer(video); + video->ops->queue(vpfe_dev, addr); + + video->state = VPFE_VIDEO_BUFFER_QUEUED; + spin_unlock(&video->dma_queue_lock); + + /* enable h/w each time in single shot */ + if (vpfe_video_is_pipe_ready(pipe)) + vpfe_pipeline_set_stream(pipe, + VPFE_PIPELINE_STREAM_SINGLESHOT); + } +} + +/* vpfe_start_capture() - start streaming on all the subdevs */ +static int vpfe_start_capture(struct vpfe_video_device *video) +{ + struct vpfe_pipeline *pipe = &video->pipe; + int ret = 0; + + video->started = 1; + if (vpfe_video_is_pipe_ready(pipe)) + ret = vpfe_pipeline_set_stream(pipe, pipe->state); + + return ret; +} + +static int vpfe_start_streaming(struct vb2_queue *vq, unsigned int count) +{ + struct vpfe_fh *fh = vb2_get_drv_priv(vq); + struct vpfe_video_device *video = fh->video; + struct vpfe_device *vpfe_dev = video->vpfe_dev; + unsigned long addr; + int ret; + + ret = mutex_lock_interruptible(&video->lock); + if (ret) + goto streamoff; + + /* Get the next frame from the buffer queue */ + video->cur_frm = video->next_frm = + list_entry(video->dma_queue.next, struct vpfe_cap_buffer, list); + /* Remove buffer from the buffer queue */ + list_del(&video->cur_frm->list); + /* Mark state of the current frame to active */ + video->cur_frm->vb.state = VB2_BUF_STATE_ACTIVE; + /* Initialize field_id and started member */ + video->field_id = 0; + addr = vb2_dma_contig_plane_dma_addr(&video->cur_frm->vb, 0); + video->ops->queue(vpfe_dev, addr); + video->state = VPFE_VIDEO_BUFFER_QUEUED; + + ret = vpfe_start_capture(video); + if (ret) { + struct vpfe_cap_buffer *buf, *tmp; + + vb2_buffer_done(&video->cur_frm->vb, VB2_BUF_STATE_QUEUED); + list_for_each_entry_safe(buf, tmp, &video->dma_queue, list) { + list_del(&buf->list); + vb2_buffer_done(&buf->vb, VB2_BUF_STATE_QUEUED); + } + goto unlock_out; + } + + mutex_unlock(&video->lock); + + return ret; +unlock_out: + mutex_unlock(&video->lock); +streamoff: + ret = vb2_streamoff(&video->buffer_queue, video->buffer_queue.type); + return 0; +} + +static int vpfe_buffer_init(struct vb2_buffer *vb) +{ + struct vpfe_cap_buffer *buf = container_of(vb, + struct vpfe_cap_buffer, vb); + + INIT_LIST_HEAD(&buf->list); + return 0; +} + +/* abort streaming and wait for last buffer */ +static void vpfe_stop_streaming(struct vb2_queue *vq) +{ + struct vpfe_fh *fh = vb2_get_drv_priv(vq); + struct vpfe_video_device *video = fh->video; + + /* release all active buffers */ + if (video->cur_frm == video->next_frm) { + vb2_buffer_done(&video->cur_frm->vb, VB2_BUF_STATE_ERROR); + } else { + if (video->cur_frm != NULL) + vb2_buffer_done(&video->cur_frm->vb, + VB2_BUF_STATE_ERROR); + if (video->next_frm != NULL) + vb2_buffer_done(&video->next_frm->vb, + VB2_BUF_STATE_ERROR); + } + + while (!list_empty(&video->dma_queue)) { + video->next_frm = list_entry(video->dma_queue.next, + struct vpfe_cap_buffer, list); + list_del(&video->next_frm->list); + vb2_buffer_done(&video->next_frm->vb, VB2_BUF_STATE_ERROR); + } +} + +static void vpfe_buf_cleanup(struct vb2_buffer *vb) +{ + struct vpfe_fh *fh = vb2_get_drv_priv(vb->vb2_queue); + struct vpfe_video_device *video = fh->video; + struct vpfe_device *vpfe_dev = video->vpfe_dev; + struct vpfe_cap_buffer *buf = container_of(vb, + struct vpfe_cap_buffer, vb); + + v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_buf_cleanup\n"); + if (vb->state == VB2_BUF_STATE_ACTIVE) + list_del_init(&buf->list); +} + +static struct vb2_ops video_qops = { + .queue_setup = vpfe_buffer_queue_setup, + .buf_init = vpfe_buffer_init, + .buf_prepare = vpfe_buffer_prepare, + .start_streaming = vpfe_start_streaming, + .stop_streaming = vpfe_stop_streaming, + .buf_cleanup = vpfe_buf_cleanup, + .buf_queue = vpfe_buffer_queue, +}; + +/* + * vpfe_reqbufs() - supported REQBUF only once opening + * the device. + */ +static int vpfe_reqbufs(struct file *file, void *priv, + struct v4l2_requestbuffers *req_buf) +{ + struct vpfe_video_device *video = video_drvdata(file); + struct vpfe_device *vpfe_dev = video->vpfe_dev; + struct vpfe_fh *fh = file->private_data; + struct vb2_queue *q; + int ret; + + v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_reqbufs\n"); + + if (V4L2_BUF_TYPE_VIDEO_CAPTURE != req_buf->type && + V4L2_BUF_TYPE_VIDEO_OUTPUT != req_buf->type) { + v4l2_err(&vpfe_dev->v4l2_dev, "Invalid buffer type\n"); + return -EINVAL; + } + + ret = mutex_lock_interruptible(&video->lock); + if (ret) + return ret; + + if (video->io_usrs != 0) { + v4l2_err(&vpfe_dev->v4l2_dev, "Only one IO user allowed\n"); + ret = -EBUSY; + goto unlock_out; + } + video->memory = req_buf->memory; + + /* Initialize videobuf2 queue as per the buffer type */ + video->alloc_ctx = vb2_dma_contig_init_ctx(vpfe_dev->pdev); + if (IS_ERR(video->alloc_ctx)) { + v4l2_err(&vpfe_dev->v4l2_dev, "Failed to get the context\n"); + return PTR_ERR(video->alloc_ctx); + } + + q = &video->buffer_queue; + q->type = req_buf->type; + q->io_modes = VB2_MMAP | VB2_USERPTR; + q->drv_priv = fh; + q->min_buffers_needed = 1; + q->ops = &video_qops; + q->mem_ops = &vb2_dma_contig_memops; + q->buf_struct_size = sizeof(struct vpfe_cap_buffer); + + ret = vb2_queue_init(q); + if (ret) { + v4l2_err(&vpfe_dev->v4l2_dev, "vb2_queue_init() failed\n"); + vb2_dma_contig_cleanup_ctx(vpfe_dev->pdev); + return ret; + } + + fh->io_allowed = 1; + video->io_usrs = 1; + INIT_LIST_HEAD(&video->dma_queue); + ret = vb2_reqbufs(&video->buffer_queue, req_buf); + +unlock_out: + mutex_unlock(&video->lock); + return ret; +} + +/* + * vpfe_querybuf() - query buffers for exchange + */ +static int vpfe_querybuf(struct file *file, void *priv, + struct v4l2_buffer *buf) +{ + struct vpfe_video_device *video = video_drvdata(file); + struct vpfe_device *vpfe_dev = video->vpfe_dev; + + v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_querybuf\n"); + + if (V4L2_BUF_TYPE_VIDEO_CAPTURE != buf->type && + V4L2_BUF_TYPE_VIDEO_OUTPUT != buf->type) { + v4l2_err(&vpfe_dev->v4l2_dev, "Invalid buf type\n"); + return -EINVAL; + } + + if (video->memory != V4L2_MEMORY_MMAP) { + v4l2_err(&vpfe_dev->v4l2_dev, "Invalid memory\n"); + return -EINVAL; + } + + /* Call vb2_querybuf to get information */ + return vb2_querybuf(&video->buffer_queue, buf); +} + +/* + * vpfe_qbuf() - queue buffers for capture or processing + */ +static int vpfe_qbuf(struct file *file, void *priv, + struct v4l2_buffer *p) +{ + struct vpfe_video_device *video = video_drvdata(file); + struct vpfe_device *vpfe_dev = video->vpfe_dev; + struct vpfe_fh *fh = file->private_data; + + v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_qbuf\n"); + + if (V4L2_BUF_TYPE_VIDEO_CAPTURE != p->type && + V4L2_BUF_TYPE_VIDEO_OUTPUT != p->type) { + v4l2_err(&vpfe_dev->v4l2_dev, "Invalid buf type\n"); + return -EINVAL; + } + /* + * If this file handle is not allowed to do IO, + * return error + */ + if (!fh->io_allowed) { + v4l2_err(&vpfe_dev->v4l2_dev, "fh->io_allowed\n"); + return -EACCES; + } + + return vb2_qbuf(&video->buffer_queue, p); +} + +/* + * vpfe_dqbuf() - deque buffer which is done with processing + */ +static int vpfe_dqbuf(struct file *file, void *priv, + struct v4l2_buffer *buf) +{ + struct vpfe_video_device *video = video_drvdata(file); + struct vpfe_device *vpfe_dev = video->vpfe_dev; + + v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_dqbuf\n"); + + if (V4L2_BUF_TYPE_VIDEO_CAPTURE != buf->type && + V4L2_BUF_TYPE_VIDEO_OUTPUT != buf->type) { + v4l2_err(&vpfe_dev->v4l2_dev, "Invalid buf type\n"); + return -EINVAL; + } + + return vb2_dqbuf(&video->buffer_queue, + buf, (file->f_flags & O_NONBLOCK)); +} + +/* + * vpfe_streamon() - start streaming + * @file: file pointer + * @priv: void pointer + * @buf_type: enum v4l2_buf_type + * + * queue buffer onto hardware for capture/processing and + * start all the subdevs which are in media chain + * + * Return 0 on success, error code otherwise + */ +static int vpfe_streamon(struct file *file, void *priv, + enum v4l2_buf_type buf_type) +{ + struct vpfe_video_device *video = video_drvdata(file); + struct vpfe_device *vpfe_dev = video->vpfe_dev; + struct vpfe_pipeline *pipe = &video->pipe; + struct vpfe_fh *fh = file->private_data; + struct vpfe_ext_subdev_info *sdinfo; + int ret = -EINVAL; + + v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_streamon\n"); + + if (V4L2_BUF_TYPE_VIDEO_CAPTURE != buf_type && + V4L2_BUF_TYPE_VIDEO_OUTPUT != buf_type) { + v4l2_err(&vpfe_dev->v4l2_dev, "Invalid buf type\n"); + return ret; + } + /* If file handle is not allowed IO, return error */ + if (!fh->io_allowed) { + v4l2_err(&vpfe_dev->v4l2_dev, "fh->io_allowed\n"); + return -EACCES; + } + sdinfo = video->current_ext_subdev; + /* If buffer queue is empty, return error */ + if (list_empty(&video->buffer_queue.queued_list)) { + v4l2_err(&vpfe_dev->v4l2_dev, "buffer queue is empty\n"); + return -EIO; + } + /* Validate the pipeline */ + if (V4L2_BUF_TYPE_VIDEO_CAPTURE == buf_type) { + ret = vpfe_video_validate_pipeline(pipe); + if (ret < 0) + return ret; + } + /* Call vb2_streamon to start streaming */ + return vb2_streamon(&video->buffer_queue, buf_type); +} + +/* + * vpfe_streamoff() - stop streaming + * @file: file pointer + * @priv: void pointer + * @buf_type: enum v4l2_buf_type + * + * stop all the subdevs which are in media chain + * + * Return 0 on success, error code otherwise + */ +static int vpfe_streamoff(struct file *file, void *priv, + enum v4l2_buf_type buf_type) +{ + struct vpfe_video_device *video = video_drvdata(file); + struct vpfe_device *vpfe_dev = video->vpfe_dev; + struct vpfe_fh *fh = file->private_data; + int ret = 0; + + v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_streamoff\n"); + + if (buf_type != V4L2_BUF_TYPE_VIDEO_CAPTURE && + buf_type != V4L2_BUF_TYPE_VIDEO_OUTPUT) { + v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "Invalid buf type\n"); + return -EINVAL; + } + + /* If io is allowed for this file handle, return error */ + if (!fh->io_allowed) { + v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "fh->io_allowed\n"); + return -EACCES; + } + + /* If streaming is not started, return error */ + if (!video->started) { + v4l2_err(&vpfe_dev->v4l2_dev, "device is not started\n"); + return -EINVAL; + } + + ret = mutex_lock_interruptible(&video->lock); + if (ret) + return ret; + + vpfe_stop_capture(video); + ret = vb2_streamoff(&video->buffer_queue, buf_type); + mutex_unlock(&video->lock); + + return ret; +} + +/* vpfe capture ioctl operations */ +static const struct v4l2_ioctl_ops vpfe_ioctl_ops = { + .vidioc_querycap = vpfe_querycap, + .vidioc_g_fmt_vid_cap = vpfe_g_fmt, + .vidioc_s_fmt_vid_cap = vpfe_s_fmt, + .vidioc_try_fmt_vid_cap = vpfe_try_fmt, + .vidioc_enum_fmt_vid_cap = vpfe_enum_fmt, + .vidioc_g_fmt_vid_out = vpfe_g_fmt, + .vidioc_s_fmt_vid_out = vpfe_s_fmt, + .vidioc_try_fmt_vid_out = vpfe_try_fmt, + .vidioc_enum_fmt_vid_out = vpfe_enum_fmt, + .vidioc_enum_input = vpfe_enum_input, + .vidioc_g_input = vpfe_g_input, + .vidioc_s_input = vpfe_s_input, + .vidioc_querystd = vpfe_querystd, + .vidioc_s_std = vpfe_s_std, + .vidioc_g_std = vpfe_g_std, + .vidioc_enum_dv_timings = vpfe_enum_dv_timings, + .vidioc_query_dv_timings = vpfe_query_dv_timings, + .vidioc_s_dv_timings = vpfe_s_dv_timings, + .vidioc_g_dv_timings = vpfe_g_dv_timings, + .vidioc_reqbufs = vpfe_reqbufs, + .vidioc_querybuf = vpfe_querybuf, + .vidioc_qbuf = vpfe_qbuf, + .vidioc_dqbuf = vpfe_dqbuf, + .vidioc_streamon = vpfe_streamon, + .vidioc_streamoff = vpfe_streamoff, +}; + +/* VPFE video init function */ +int vpfe_video_init(struct vpfe_video_device *video, const char *name) +{ + const char *direction; + int ret; + + switch (video->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + direction = "output"; + video->pad.flags = MEDIA_PAD_FL_SINK; + video->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + break; + + case V4L2_BUF_TYPE_VIDEO_OUTPUT: + direction = "input"; + video->pad.flags = MEDIA_PAD_FL_SOURCE; + video->type = V4L2_BUF_TYPE_VIDEO_OUTPUT; + break; + + default: + return -EINVAL; + } + /* Initialize field of video device */ + video->video_dev.release = video_device_release; + video->video_dev.fops = &vpfe_fops; + video->video_dev.ioctl_ops = &vpfe_ioctl_ops; + video->video_dev.minor = -1; + video->video_dev.tvnorms = 0; + snprintf(video->video_dev.name, sizeof(video->video_dev.name), + "DAVINCI VIDEO %s %s", name, direction); + + spin_lock_init(&video->irqlock); + spin_lock_init(&video->dma_queue_lock); + mutex_init(&video->lock); + ret = media_entity_init(&video->video_dev.entity, + 1, &video->pad, 0); + if (ret < 0) + return ret; + + set_bit(V4L2_FL_USE_FH_PRIO, &video->video_dev.flags); + video_set_drvdata(&video->video_dev, video); + + return 0; +} + +/* vpfe video device register function */ +int vpfe_video_register(struct vpfe_video_device *video, + struct v4l2_device *vdev) +{ + int ret; + + video->video_dev.v4l2_dev = vdev; + + ret = video_register_device(&video->video_dev, VFL_TYPE_GRABBER, -1); + if (ret < 0) + pr_err("%s: could not register video device (%d)\n", + __func__, ret); + return ret; +} + +/* vpfe video device unregister function */ +void vpfe_video_unregister(struct vpfe_video_device *video) +{ + if (video_is_registered(&video->video_dev)) { + video_unregister_device(&video->video_dev); + media_entity_cleanup(&video->video_dev.entity); + } +} diff --git a/drivers/staging/media/davinci_vpfe/vpfe_video.h b/drivers/staging/media/davinci_vpfe/vpfe_video.h new file mode 100644 index 00000000000..1b1b6c4a56b --- /dev/null +++ b/drivers/staging/media/davinci_vpfe/vpfe_video.h @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2012 Texas Instruments Inc + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Contributors: + * Manjunath Hadli <manjunath.hadli@ti.com> + * Prabhakar Lad <prabhakar.lad@ti.com> + */ + +#ifndef _DAVINCI_VPFE_VIDEO_H +#define _DAVINCI_VPFE_VIDEO_H + +#include <media/videobuf2-dma-contig.h> + +struct vpfe_device; + +/* + * struct vpfe_video_operations - VPFE video operations + * @queue: Resume streaming when a buffer is queued. Called on VIDIOC_QBUF + * if there was no buffer previously queued. + */ +struct vpfe_video_operations { + int (*queue)(struct vpfe_device *vpfe_dev, unsigned long addr); +}; + +enum vpfe_pipeline_stream_state { + VPFE_PIPELINE_STREAM_STOPPED = 0, + VPFE_PIPELINE_STREAM_CONTINUOUS = 1, + VPFE_PIPELINE_STREAM_SINGLESHOT = 2, +}; + +enum vpfe_video_state { + /* indicates that buffer is not queued */ + VPFE_VIDEO_BUFFER_NOT_QUEUED = 0, + /* indicates that buffer is queued */ + VPFE_VIDEO_BUFFER_QUEUED = 1, +}; + +struct vpfe_pipeline { + /* media pipeline */ + struct media_pipeline *pipe; + /* state of the pipeline, continuous, + * single-shot or stopped + */ + enum vpfe_pipeline_stream_state state; + /* number of active input video entities */ + unsigned int input_num; + /* number of active output video entities */ + unsigned int output_num; + /* input video nodes in case of single-shot mode */ + struct vpfe_video_device *inputs[10]; + /* capturing video nodes */ + struct vpfe_video_device *outputs[10]; +}; + +#define to_vpfe_pipeline(__e) \ + container_of((__e)->pipe, struct vpfe_pipeline, pipe) + +#define to_vpfe_video(vdev) \ + container_of(vdev, struct vpfe_video_device, video_dev) + +struct vpfe_cap_buffer { + struct vb2_buffer vb; + struct list_head list; +}; + +struct vpfe_video_device { + /* vpfe device */ + struct vpfe_device *vpfe_dev; + /* video dev */ + struct video_device video_dev; + /* media pad of video entity */ + struct media_pad pad; + /* video operations supported by video device */ + const struct vpfe_video_operations *ops; + /* type of the video buffers used by user */ + enum v4l2_buf_type type; + /* Indicates id of the field which is being captured */ + u32 field_id; + /* pipeline for which video device is part of */ + struct vpfe_pipeline pipe; + /* Indicates whether streaming started */ + u8 started; + /* Indicates state of the stream */ + unsigned int state; + /* current input at the sub device */ + int current_input; + /* + * This field keeps track of type of buffer exchange mechanism + * user has selected + */ + enum v4l2_memory memory; + /* number of open instances of the channel */ + u32 usrs; + /* flag to indicate whether decoder is initialized */ + u8 initialized; + /* skip frame count */ + u8 skip_frame_count; + /* skip frame count init value */ + u8 skip_frame_count_init; + /* time per frame for skipping */ + struct v4l2_fract timeperframe; + /* ptr to currently selected sub device */ + struct vpfe_ext_subdev_info *current_ext_subdev; + /* Pointer pointing to current vpfe_cap_buffer */ + struct vpfe_cap_buffer *cur_frm; + /* Pointer pointing to next vpfe_cap_buffer */ + struct vpfe_cap_buffer *next_frm; + /* Used to store pixel format */ + struct v4l2_format fmt; + struct vb2_queue buffer_queue; + /* allocator-specific contexts for each plane */ + struct vb2_alloc_ctx *alloc_ctx; + /* Queue of filled frames */ + struct list_head dma_queue; + spinlock_t irqlock; + /* IRQ lock for DMA queue */ + spinlock_t dma_queue_lock; + /* lock used to access this structure */ + struct mutex lock; + /* number of users performing IO */ + u32 io_usrs; + /* Currently selected or default standard */ + v4l2_std_id stdid; + /* + * offset where second field starts from the starting of the + * buffer for field separated YCbCr formats + */ + u32 field_off; +}; + +int vpfe_video_is_pipe_ready(struct vpfe_pipeline *pipe); +void vpfe_video_unregister(struct vpfe_video_device *video); +int vpfe_video_register(struct vpfe_video_device *video, + struct v4l2_device *vdev); +int vpfe_video_init(struct vpfe_video_device *video, const char *name); +void vpfe_video_process_buffer_complete(struct vpfe_video_device *video); +void vpfe_video_schedule_bottom_field(struct vpfe_video_device *video); +void vpfe_video_schedule_next_buffer(struct vpfe_video_device *video); + +#endif /* _DAVINCI_VPFE_VIDEO_H */ diff --git a/drivers/staging/media/dt3155v4l/Kconfig b/drivers/staging/media/dt3155v4l/Kconfig new file mode 100644 index 00000000000..226a1ca90b3 --- /dev/null +++ b/drivers/staging/media/dt3155v4l/Kconfig @@ -0,0 +1,28 @@ +config VIDEO_DT3155 + tristate "DT3155 frame grabber, Video4Linux interface" + depends on PCI && VIDEO_DEV && VIDEO_V4L2 + select VIDEOBUF2_DMA_CONTIG + default n + ---help--- + Enables dt3155 device driver for the DataTranslation DT3155 frame grabber. + Say Y here if you have this hardware. + In doubt, say N. + + To compile this driver as a module, choose M here: the + module will be called dt3155v4l. + +config DT3155_CCIR + bool "Selects CCIR/50Hz vertical refresh" + depends on VIDEO_DT3155 + default y + ---help--- + Select it for CCIR/50Hz (European region), + or leave it unselected for RS-170/60Hz (North America). + +config DT3155_STREAMING + bool "Selects streaming capture method" + depends on VIDEO_DT3155 + default y + ---help--- + Select it if you want to use streaming of memory mapped buffers + or leave it unselected if you want to use read method (one copy more). diff --git a/drivers/staging/media/dt3155v4l/Makefile b/drivers/staging/media/dt3155v4l/Makefile new file mode 100644 index 00000000000..ce7a3ec2faf --- /dev/null +++ b/drivers/staging/media/dt3155v4l/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_VIDEO_DT3155) += dt3155v4l.o diff --git a/drivers/staging/media/dt3155v4l/dt3155v4l.c b/drivers/staging/media/dt3155v4l/dt3155v4l.c new file mode 100644 index 00000000000..40580228a6c --- /dev/null +++ b/drivers/staging/media/dt3155v4l/dt3155v4l.c @@ -0,0 +1,995 @@ +/*************************************************************************** + * Copyright (C) 2006-2010 by Marin Mitov * + * mitov@issp.bas.bg * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#include <linux/module.h> +#include <linux/version.h> +#include <linux/stringify.h> +#include <linux/delay.h> +#include <linux/kthread.h> +#include <linux/slab.h> +#include <media/v4l2-dev.h> +#include <media/v4l2-ioctl.h> +#include <media/v4l2-common.h> +#include <media/videobuf2-dma-contig.h> + +#include "dt3155v4l.h" + +#define DT3155_DEVICE_ID 0x1223 + +/* DT3155_CHUNK_SIZE is 4M (2^22) 8 full size buffers */ +#define DT3155_CHUNK_SIZE (1U << 22) + +#define DT3155_COH_FLAGS (GFP_KERNEL | GFP_DMA32 | __GFP_COLD | __GFP_NOWARN) + +#define DT3155_BUF_SIZE (768 * 576) + +#ifdef CONFIG_DT3155_STREAMING +#define DT3155_CAPTURE_METHOD V4L2_CAP_STREAMING +#else +#define DT3155_CAPTURE_METHOD V4L2_CAP_READWRITE +#endif + +/* global initializers (for all boards) */ +#ifdef CONFIG_DT3155_CCIR +static const u8 csr2_init = VT_50HZ; +#define DT3155_CURRENT_NORM V4L2_STD_625_50 +static const unsigned int img_width = 768; +static const unsigned int img_height = 576; +static const unsigned int frames_per_sec = 25; +static const struct v4l2_fmtdesc frame_std[] = { + { + .index = 0, + .type = V4L2_BUF_TYPE_VIDEO_CAPTURE, + .flags = 0, + .description = "CCIR/50Hz 8 bits gray", + .pixelformat = V4L2_PIX_FMT_GREY, + }, +}; +#else +static const u8 csr2_init = VT_60HZ; +#define DT3155_CURRENT_NORM V4L2_STD_525_60 +static const unsigned int img_width = 640; +static const unsigned int img_height = 480; +static const unsigned int frames_per_sec = 30; +static const struct v4l2_fmtdesc frame_std[] = { + { + .index = 0, + .type = V4L2_BUF_TYPE_VIDEO_CAPTURE, + .flags = 0, + .description = "RS-170/60Hz 8 bits gray", + .pixelformat = V4L2_PIX_FMT_GREY, + }, +}; +#endif + +#define NUM_OF_FORMATS ARRAY_SIZE(frame_std) + +static u8 config_init = ACQ_MODE_EVEN; + +/** + * read_i2c_reg - reads an internal i2c register + * + * @addr: dt3155 mmio base address + * @index: index (internal address) of register to read + * @data: pointer to byte the read data will be placed in + * + * returns: zero on success or error code + * + * This function starts reading the specified (by index) register + * and busy waits for the process to finish. The result is placed + * in a byte pointed by data. + */ +static int +read_i2c_reg(void __iomem *addr, u8 index, u8 *data) +{ + u32 tmp = index; + + iowrite32((tmp<<17) | IIC_READ, addr + IIC_CSR2); + mmiowb(); + udelay(45); /* wait at least 43 usec for NEW_CYCLE to clear */ + if (ioread32(addr + IIC_CSR2) & NEW_CYCLE) + return -EIO; /* error: NEW_CYCLE not cleared */ + tmp = ioread32(addr + IIC_CSR1); + if (tmp & DIRECT_ABORT) { + /* reset DIRECT_ABORT bit */ + iowrite32(DIRECT_ABORT, addr + IIC_CSR1); + return -EIO; /* error: DIRECT_ABORT set */ + } + *data = tmp>>24; + return 0; +} + +/** + * write_i2c_reg - writes to an internal i2c register + * + * @addr: dt3155 mmio base address + * @index: index (internal address) of register to read + * @data: data to be written + * + * returns: zero on success or error code + * + * This function starts writting the specified (by index) register + * and busy waits for the process to finish. + */ +static int +write_i2c_reg(void __iomem *addr, u8 index, u8 data) +{ + u32 tmp = index; + + iowrite32((tmp<<17) | IIC_WRITE | data, addr + IIC_CSR2); + mmiowb(); + udelay(65); /* wait at least 63 usec for NEW_CYCLE to clear */ + if (ioread32(addr + IIC_CSR2) & NEW_CYCLE) + return -EIO; /* error: NEW_CYCLE not cleared */ + if (ioread32(addr + IIC_CSR1) & DIRECT_ABORT) { + /* reset DIRECT_ABORT bit */ + iowrite32(DIRECT_ABORT, addr + IIC_CSR1); + return -EIO; /* error: DIRECT_ABORT set */ + } + return 0; +} + +/** + * write_i2c_reg_nowait - writes to an internal i2c register + * + * @addr: dt3155 mmio base address + * @index: index (internal address) of register to read + * @data: data to be written + * + * This function starts writting the specified (by index) register + * and then returns. + */ +static void write_i2c_reg_nowait(void __iomem *addr, u8 index, u8 data) +{ + u32 tmp = index; + + iowrite32((tmp<<17) | IIC_WRITE | data, addr + IIC_CSR2); + mmiowb(); +} + +/** + * wait_i2c_reg - waits the read/write to finish + * + * @addr: dt3155 mmio base address + * + * returns: zero on success or error code + * + * This function waits reading/writting to finish. + */ +static int wait_i2c_reg(void __iomem *addr) +{ + if (ioread32(addr + IIC_CSR2) & NEW_CYCLE) + udelay(65); /* wait at least 63 usec for NEW_CYCLE to clear */ + if (ioread32(addr + IIC_CSR2) & NEW_CYCLE) + return -EIO; /* error: NEW_CYCLE not cleared */ + if (ioread32(addr + IIC_CSR1) & DIRECT_ABORT) { + /* reset DIRECT_ABORT bit */ + iowrite32(DIRECT_ABORT, addr + IIC_CSR1); + return -EIO; /* error: DIRECT_ABORT set */ + } + return 0; +} + +static int +dt3155_start_acq(struct dt3155_priv *pd) +{ + struct vb2_buffer *vb = pd->curr_buf; + dma_addr_t dma_addr; + + dma_addr = vb2_dma_contig_plane_dma_addr(vb, 0); + iowrite32(dma_addr, pd->regs + EVEN_DMA_START); + iowrite32(dma_addr + img_width, pd->regs + ODD_DMA_START); + iowrite32(img_width, pd->regs + EVEN_DMA_STRIDE); + iowrite32(img_width, pd->regs + ODD_DMA_STRIDE); + /* enable interrupts, clear all irq flags */ + iowrite32(FLD_START_EN | FLD_END_ODD_EN | FLD_START | + FLD_END_EVEN | FLD_END_ODD, pd->regs + INT_CSR); + iowrite32(FIFO_EN | SRST | FLD_CRPT_ODD | FLD_CRPT_EVEN | + FLD_DN_ODD | FLD_DN_EVEN | CAP_CONT_EVEN | CAP_CONT_ODD, + pd->regs + CSR1); + wait_i2c_reg(pd->regs); + write_i2c_reg(pd->regs, CONFIG, pd->config); + write_i2c_reg(pd->regs, EVEN_CSR, CSR_ERROR | CSR_DONE); + write_i2c_reg(pd->regs, ODD_CSR, CSR_ERROR | CSR_DONE); + + /* start the board */ + write_i2c_reg(pd->regs, CSR2, pd->csr2 | BUSY_EVEN | BUSY_ODD); + return 0; /* success */ +} + +/* + * driver-specific callbacks (vb2_ops) + */ +static int +dt3155_queue_setup(struct vb2_queue *q, const struct v4l2_format *fmt, + unsigned int *num_buffers, unsigned int *num_planes, + unsigned int sizes[], void *alloc_ctxs[]) + +{ + struct dt3155_priv *pd = vb2_get_drv_priv(q); + void *ret; + + if (*num_buffers == 0) + *num_buffers = 1; + *num_planes = 1; + sizes[0] = img_width * img_height; + if (pd->q->alloc_ctx[0]) + return 0; + ret = vb2_dma_contig_init_ctx(&pd->pdev->dev); + if (IS_ERR(ret)) + return PTR_ERR(ret); + pd->q->alloc_ctx[0] = ret; + return 0; +} + +static void +dt3155_wait_prepare(struct vb2_queue *q) +{ + struct dt3155_priv *pd = vb2_get_drv_priv(q); + + mutex_unlock(pd->vdev->lock); +} + +static void +dt3155_wait_finish(struct vb2_queue *q) +{ + struct dt3155_priv *pd = vb2_get_drv_priv(q); + + mutex_lock(pd->vdev->lock); +} + +static int +dt3155_buf_prepare(struct vb2_buffer *vb) +{ + vb2_set_plane_payload(vb, 0, img_width * img_height); + return 0; +} + +static void +dt3155_stop_streaming(struct vb2_queue *q) +{ + struct dt3155_priv *pd = vb2_get_drv_priv(q); + struct vb2_buffer *vb; + + spin_lock_irq(&pd->lock); + while (!list_empty(&pd->dmaq)) { + vb = list_first_entry(&pd->dmaq, typeof(*vb), done_entry); + list_del(&vb->done_entry); + vb2_buffer_done(vb, VB2_BUF_STATE_ERROR); + } + spin_unlock_irq(&pd->lock); + msleep(45); /* irq hendler will stop the hardware */ +} + +static void +dt3155_buf_queue(struct vb2_buffer *vb) +{ + struct dt3155_priv *pd = vb2_get_drv_priv(vb->vb2_queue); + + /* pd->q->streaming = 1 when dt3155_buf_queue() is invoked */ + spin_lock_irq(&pd->lock); + if (pd->curr_buf) + list_add_tail(&vb->done_entry, &pd->dmaq); + else { + pd->curr_buf = vb; + dt3155_start_acq(pd); + } + spin_unlock_irq(&pd->lock); +} +/* + * end driver-specific callbacks + */ + +static const struct vb2_ops q_ops = { + .queue_setup = dt3155_queue_setup, + .wait_prepare = dt3155_wait_prepare, + .wait_finish = dt3155_wait_finish, + .buf_prepare = dt3155_buf_prepare, + .stop_streaming = dt3155_stop_streaming, + .buf_queue = dt3155_buf_queue, +}; + +static irqreturn_t +dt3155_irq_handler_even(int irq, void *dev_id) +{ + struct dt3155_priv *ipd = dev_id; + struct vb2_buffer *ivb; + dma_addr_t dma_addr; + u32 tmp; + + tmp = ioread32(ipd->regs + INT_CSR) & (FLD_START | FLD_END_ODD); + if (!tmp) + return IRQ_NONE; /* not our irq */ + if ((tmp & FLD_START) && !(tmp & FLD_END_ODD)) { + iowrite32(FLD_START_EN | FLD_END_ODD_EN | FLD_START, + ipd->regs + INT_CSR); + ipd->field_count++; + return IRQ_HANDLED; /* start of field irq */ + } + if ((tmp & FLD_START) && (tmp & FLD_END_ODD)) + ipd->stats.start_before_end++; + /* check for corrupted fields */ +/* write_i2c_reg(ipd->regs, EVEN_CSR, CSR_ERROR | CSR_DONE); */ +/* write_i2c_reg(ipd->regs, ODD_CSR, CSR_ERROR | CSR_DONE); */ + tmp = ioread32(ipd->regs + CSR1) & (FLD_CRPT_EVEN | FLD_CRPT_ODD); + if (tmp) { + ipd->stats.corrupted_fields++; + iowrite32(FIFO_EN | SRST | FLD_CRPT_ODD | FLD_CRPT_EVEN | + FLD_DN_ODD | FLD_DN_EVEN | + CAP_CONT_EVEN | CAP_CONT_ODD, + ipd->regs + CSR1); + mmiowb(); + } + + spin_lock(&ipd->lock); + if (ipd->curr_buf) { + v4l2_get_timestamp(&ipd->curr_buf->v4l2_buf.timestamp); + ipd->curr_buf->v4l2_buf.sequence = (ipd->field_count) >> 1; + vb2_buffer_done(ipd->curr_buf, VB2_BUF_STATE_DONE); + } + + if (!ipd->q->streaming || list_empty(&ipd->dmaq)) + goto stop_dma; + ivb = list_first_entry(&ipd->dmaq, typeof(*ivb), done_entry); + list_del(&ivb->done_entry); + ipd->curr_buf = ivb; + dma_addr = vb2_dma_contig_plane_dma_addr(ivb, 0); + iowrite32(dma_addr, ipd->regs + EVEN_DMA_START); + iowrite32(dma_addr + img_width, ipd->regs + ODD_DMA_START); + iowrite32(img_width, ipd->regs + EVEN_DMA_STRIDE); + iowrite32(img_width, ipd->regs + ODD_DMA_STRIDE); + mmiowb(); + /* enable interrupts, clear all irq flags */ + iowrite32(FLD_START_EN | FLD_END_ODD_EN | FLD_START | + FLD_END_EVEN | FLD_END_ODD, ipd->regs + INT_CSR); + spin_unlock(&ipd->lock); + return IRQ_HANDLED; + +stop_dma: + ipd->curr_buf = NULL; + /* stop the board */ + write_i2c_reg_nowait(ipd->regs, CSR2, ipd->csr2); + iowrite32(FIFO_EN | SRST | FLD_CRPT_ODD | FLD_CRPT_EVEN | + FLD_DN_ODD | FLD_DN_EVEN, ipd->regs + CSR1); + /* disable interrupts, clear all irq flags */ + iowrite32(FLD_START | FLD_END_EVEN | FLD_END_ODD, ipd->regs + INT_CSR); + spin_unlock(&ipd->lock); + return IRQ_HANDLED; +} + +static int +dt3155_open(struct file *filp) +{ + int ret = 0; + struct dt3155_priv *pd = video_drvdata(filp); + + if (mutex_lock_interruptible(&pd->mux)) + return -ERESTARTSYS; + if (!pd->users) { + pd->q = kzalloc(sizeof(*pd->q), GFP_KERNEL); + if (!pd->q) { + ret = -ENOMEM; + goto err_alloc_queue; + } + pd->q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + pd->q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + pd->q->io_modes = VB2_READ | VB2_MMAP; + pd->q->ops = &q_ops; + pd->q->mem_ops = &vb2_dma_contig_memops; + pd->q->drv_priv = pd; + pd->curr_buf = NULL; + pd->field_count = 0; + ret = vb2_queue_init(pd->q); + if (ret < 0) + goto err_request_irq; + INIT_LIST_HEAD(&pd->dmaq); + spin_lock_init(&pd->lock); + /* disable all irqs, clear all irq flags */ + iowrite32(FLD_START | FLD_END_EVEN | FLD_END_ODD, + pd->regs + INT_CSR); + ret = request_irq(pd->pdev->irq, dt3155_irq_handler_even, + IRQF_SHARED, DT3155_NAME, pd); + if (ret) + goto err_request_irq; + } + pd->users++; + mutex_unlock(&pd->mux); + return 0; /* success */ +err_request_irq: + kfree(pd->q); + pd->q = NULL; +err_alloc_queue: + mutex_unlock(&pd->mux); + return ret; +} + +static int +dt3155_release(struct file *filp) +{ + struct dt3155_priv *pd = video_drvdata(filp); + + mutex_lock(&pd->mux); + pd->users--; + BUG_ON(pd->users < 0); + if (!pd->users) { + vb2_queue_release(pd->q); + free_irq(pd->pdev->irq, pd); + if (pd->q->alloc_ctx[0]) + vb2_dma_contig_cleanup_ctx(pd->q->alloc_ctx[0]); + kfree(pd->q); + pd->q = NULL; + } + mutex_unlock(&pd->mux); + return 0; +} + +static ssize_t +dt3155_read(struct file *filp, char __user *user, size_t size, loff_t *loff) +{ + struct dt3155_priv *pd = video_drvdata(filp); + ssize_t res; + + if (mutex_lock_interruptible(&pd->mux)) + return -ERESTARTSYS; + res = vb2_read(pd->q, user, size, loff, filp->f_flags & O_NONBLOCK); + mutex_unlock(&pd->mux); + return res; +} + +static unsigned int +dt3155_poll(struct file *filp, struct poll_table_struct *polltbl) +{ + struct dt3155_priv *pd = video_drvdata(filp); + unsigned int res; + + mutex_lock(&pd->mux); + res = vb2_poll(pd->q, filp, polltbl); + mutex_unlock(&pd->mux); + return res; +} + +static int +dt3155_mmap(struct file *filp, struct vm_area_struct *vma) +{ + struct dt3155_priv *pd = video_drvdata(filp); + int res; + + if (mutex_lock_interruptible(&pd->mux)) + return -ERESTARTSYS; + res = vb2_mmap(pd->q, vma); + mutex_unlock(&pd->mux); + return res; +} + +static const struct v4l2_file_operations dt3155_fops = { + .owner = THIS_MODULE, + .open = dt3155_open, + .release = dt3155_release, + .read = dt3155_read, + .poll = dt3155_poll, + .unlocked_ioctl = video_ioctl2, /* V4L2 ioctl handler */ + .mmap = dt3155_mmap, +}; + +static int +dt3155_ioc_streamon(struct file *filp, void *p, enum v4l2_buf_type type) +{ + struct dt3155_priv *pd = video_drvdata(filp); + + return vb2_streamon(pd->q, type); +} + +static int +dt3155_ioc_streamoff(struct file *filp, void *p, enum v4l2_buf_type type) +{ + struct dt3155_priv *pd = video_drvdata(filp); + + return vb2_streamoff(pd->q, type); +} + +static int +dt3155_ioc_querycap(struct file *filp, void *p, struct v4l2_capability *cap) +{ + struct dt3155_priv *pd = video_drvdata(filp); + + strcpy(cap->driver, DT3155_NAME); + strcpy(cap->card, DT3155_NAME " frame grabber"); + sprintf(cap->bus_info, "PCI:%s", pci_name(pd->pdev)); + cap->version = + KERNEL_VERSION(DT3155_VER_MAJ, DT3155_VER_MIN, DT3155_VER_EXT); + cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | + DT3155_CAPTURE_METHOD; + return 0; +} + +static int +dt3155_ioc_enum_fmt_vid_cap(struct file *filp, void *p, struct v4l2_fmtdesc *f) +{ + if (f->index >= NUM_OF_FORMATS) + return -EINVAL; + *f = frame_std[f->index]; + return 0; +} + +static int +dt3155_ioc_g_fmt_vid_cap(struct file *filp, void *p, struct v4l2_format *f) +{ + if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + f->fmt.pix.width = img_width; + f->fmt.pix.height = img_height; + f->fmt.pix.pixelformat = V4L2_PIX_FMT_GREY; + f->fmt.pix.field = V4L2_FIELD_NONE; + f->fmt.pix.bytesperline = f->fmt.pix.width; + f->fmt.pix.sizeimage = f->fmt.pix.width * f->fmt.pix.height; + f->fmt.pix.colorspace = 0; + f->fmt.pix.priv = 0; + return 0; +} + +static int +dt3155_ioc_try_fmt_vid_cap(struct file *filp, void *p, struct v4l2_format *f) +{ + if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + if (f->fmt.pix.width == img_width && + f->fmt.pix.height == img_height && + f->fmt.pix.pixelformat == V4L2_PIX_FMT_GREY && + f->fmt.pix.field == V4L2_FIELD_NONE && + f->fmt.pix.bytesperline == f->fmt.pix.width && + f->fmt.pix.sizeimage == f->fmt.pix.width * f->fmt.pix.height) + return 0; + else + return -EINVAL; +} + +static int +dt3155_ioc_s_fmt_vid_cap(struct file *filp, void *p, struct v4l2_format *f) +{ + return dt3155_ioc_g_fmt_vid_cap(filp, p, f); +} + +static int +dt3155_ioc_reqbufs(struct file *filp, void *p, struct v4l2_requestbuffers *b) +{ + struct dt3155_priv *pd = video_drvdata(filp); + + return vb2_reqbufs(pd->q, b); +} + +static int +dt3155_ioc_querybuf(struct file *filp, void *p, struct v4l2_buffer *b) +{ + struct dt3155_priv *pd = video_drvdata(filp); + + return vb2_querybuf(pd->q, b); +} + +static int +dt3155_ioc_qbuf(struct file *filp, void *p, struct v4l2_buffer *b) +{ + struct dt3155_priv *pd = video_drvdata(filp); + + return vb2_qbuf(pd->q, b); +} + +static int +dt3155_ioc_dqbuf(struct file *filp, void *p, struct v4l2_buffer *b) +{ + struct dt3155_priv *pd = video_drvdata(filp); + + return vb2_dqbuf(pd->q, b, filp->f_flags & O_NONBLOCK); +} + +static int +dt3155_ioc_querystd(struct file *filp, void *p, v4l2_std_id *norm) +{ + *norm = DT3155_CURRENT_NORM; + return 0; +} + +static int +dt3155_ioc_g_std(struct file *filp, void *p, v4l2_std_id *norm) +{ + *norm = DT3155_CURRENT_NORM; + return 0; +} + +static int +dt3155_ioc_s_std(struct file *filp, void *p, v4l2_std_id norm) +{ + if (norm & DT3155_CURRENT_NORM) + return 0; + return -EINVAL; +} + +static int +dt3155_ioc_enum_input(struct file *filp, void *p, struct v4l2_input *input) +{ + if (input->index) + return -EINVAL; + strcpy(input->name, "Coax in"); + input->type = V4L2_INPUT_TYPE_CAMERA; + /* + * FIXME: input->std = 0 according to v4l2 API + * VIDIOC_G_STD, VIDIOC_S_STD, VIDIOC_QUERYSTD and VIDIOC_ENUMSTD + * should return -EINVAL + */ + input->std = DT3155_CURRENT_NORM; + input->status = 0;/* FIXME: add sync detection & V4L2_IN_ST_NO_H_LOCK */ + return 0; +} + +static int +dt3155_ioc_g_input(struct file *filp, void *p, unsigned int *i) +{ + *i = 0; + return 0; +} + +static int +dt3155_ioc_s_input(struct file *filp, void *p, unsigned int i) +{ + if (i) + return -EINVAL; + return 0; +} + +static int +dt3155_ioc_g_parm(struct file *filp, void *p, struct v4l2_streamparm *parms) +{ + if (parms->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + parms->parm.capture.capability = V4L2_CAP_TIMEPERFRAME; + parms->parm.capture.capturemode = 0; + parms->parm.capture.timeperframe.numerator = 1001; + parms->parm.capture.timeperframe.denominator = frames_per_sec * 1000; + parms->parm.capture.extendedmode = 0; + parms->parm.capture.readbuffers = 1; /* FIXME: 2 buffers? */ + return 0; +} + +static int +dt3155_ioc_s_parm(struct file *filp, void *p, struct v4l2_streamparm *parms) +{ + if (parms->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + parms->parm.capture.capability = V4L2_CAP_TIMEPERFRAME; + parms->parm.capture.capturemode = 0; + parms->parm.capture.timeperframe.numerator = 1001; + parms->parm.capture.timeperframe.denominator = frames_per_sec * 1000; + parms->parm.capture.extendedmode = 0; + parms->parm.capture.readbuffers = 1; /* FIXME: 2 buffers? */ + return 0; +} + +static const struct v4l2_ioctl_ops dt3155_ioctl_ops = { + .vidioc_streamon = dt3155_ioc_streamon, + .vidioc_streamoff = dt3155_ioc_streamoff, + .vidioc_querycap = dt3155_ioc_querycap, +/* + .vidioc_g_priority = dt3155_ioc_g_priority, + .vidioc_s_priority = dt3155_ioc_s_priority, +*/ + .vidioc_enum_fmt_vid_cap = dt3155_ioc_enum_fmt_vid_cap, + .vidioc_try_fmt_vid_cap = dt3155_ioc_try_fmt_vid_cap, + .vidioc_g_fmt_vid_cap = dt3155_ioc_g_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = dt3155_ioc_s_fmt_vid_cap, + .vidioc_reqbufs = dt3155_ioc_reqbufs, + .vidioc_querybuf = dt3155_ioc_querybuf, + .vidioc_qbuf = dt3155_ioc_qbuf, + .vidioc_dqbuf = dt3155_ioc_dqbuf, + .vidioc_querystd = dt3155_ioc_querystd, + .vidioc_g_std = dt3155_ioc_g_std, + .vidioc_s_std = dt3155_ioc_s_std, + .vidioc_enum_input = dt3155_ioc_enum_input, + .vidioc_g_input = dt3155_ioc_g_input, + .vidioc_s_input = dt3155_ioc_s_input, +/* + .vidioc_queryctrl = dt3155_ioc_queryctrl, + .vidioc_g_ctrl = dt3155_ioc_g_ctrl, + .vidioc_s_ctrl = dt3155_ioc_s_ctrl, + .vidioc_querymenu = dt3155_ioc_querymenu, + .vidioc_g_ext_ctrls = dt3155_ioc_g_ext_ctrls, + .vidioc_s_ext_ctrls = dt3155_ioc_s_ext_ctrls, +*/ + .vidioc_g_parm = dt3155_ioc_g_parm, + .vidioc_s_parm = dt3155_ioc_s_parm, +/* + .vidioc_cropcap = dt3155_ioc_cropcap, + .vidioc_g_crop = dt3155_ioc_g_crop, + .vidioc_s_crop = dt3155_ioc_s_crop, + .vidioc_enum_framesizes = dt3155_ioc_enum_framesizes, + .vidioc_enum_frameintervals = dt3155_ioc_enum_frameintervals, +*/ +}; + +static int +dt3155_init_board(struct pci_dev *pdev) +{ + struct dt3155_priv *pd = pci_get_drvdata(pdev); + void *buf_cpu; + dma_addr_t buf_dma; + int i; + u8 tmp; + + pci_set_master(pdev); /* dt3155 needs it */ + + /* resetting the adapter */ + iowrite32(FLD_CRPT_ODD | FLD_CRPT_EVEN | FLD_DN_ODD | FLD_DN_EVEN, + pd->regs + CSR1); + mmiowb(); + msleep(20); + + /* initializing adaper registers */ + iowrite32(FIFO_EN | SRST, pd->regs + CSR1); + mmiowb(); + iowrite32(0xEEEEEE01, pd->regs + EVEN_PIXEL_FMT); + iowrite32(0xEEEEEE01, pd->regs + ODD_PIXEL_FMT); + iowrite32(0x00000020, pd->regs + FIFO_TRIGER); + iowrite32(0x00000103, pd->regs + XFER_MODE); + iowrite32(0, pd->regs + RETRY_WAIT_CNT); + iowrite32(0, pd->regs + INT_CSR); + iowrite32(1, pd->regs + EVEN_FLD_MASK); + iowrite32(1, pd->regs + ODD_FLD_MASK); + iowrite32(0, pd->regs + MASK_LENGTH); + iowrite32(0x0005007C, pd->regs + FIFO_FLAG_CNT); + iowrite32(0x01010101, pd->regs + IIC_CLK_DUR); + mmiowb(); + + /* verifying that we have a DT3155 board (not just a SAA7116 chip) */ + read_i2c_reg(pd->regs, DT_ID, &tmp); + if (tmp != DT3155_ID) + return -ENODEV; + + /* initialize AD LUT */ + write_i2c_reg(pd->regs, AD_ADDR, 0); + for (i = 0; i < 256; i++) + write_i2c_reg(pd->regs, AD_LUT, i); + + /* initialize ADC references */ + /* FIXME: pos_ref & neg_ref depend on VT_50HZ */ + write_i2c_reg(pd->regs, AD_ADDR, AD_CMD_REG); + write_i2c_reg(pd->regs, AD_CMD, VIDEO_CNL_1 | SYNC_CNL_1 | SYNC_LVL_3); + write_i2c_reg(pd->regs, AD_ADDR, AD_POS_REF); + write_i2c_reg(pd->regs, AD_CMD, 34); + write_i2c_reg(pd->regs, AD_ADDR, AD_NEG_REF); + write_i2c_reg(pd->regs, AD_CMD, 0); + + /* initialize PM LUT */ + write_i2c_reg(pd->regs, CONFIG, pd->config | PM_LUT_PGM); + for (i = 0; i < 256; i++) { + write_i2c_reg(pd->regs, PM_LUT_ADDR, i); + write_i2c_reg(pd->regs, PM_LUT_DATA, i); + } + write_i2c_reg(pd->regs, CONFIG, pd->config | PM_LUT_PGM | PM_LUT_SEL); + for (i = 0; i < 256; i++) { + write_i2c_reg(pd->regs, PM_LUT_ADDR, i); + write_i2c_reg(pd->regs, PM_LUT_DATA, i); + } + write_i2c_reg(pd->regs, CONFIG, pd->config); /* ACQ_MODE_EVEN */ + + /* select channel 1 for input and set sync level */ + write_i2c_reg(pd->regs, AD_ADDR, AD_CMD_REG); + write_i2c_reg(pd->regs, AD_CMD, VIDEO_CNL_1 | SYNC_CNL_1 | SYNC_LVL_3); + + /* allocate memory, and initialize the DMA machine */ + buf_cpu = dma_alloc_coherent(&pdev->dev, DT3155_BUF_SIZE, &buf_dma, + GFP_KERNEL); + if (!buf_cpu) + return -ENOMEM; + iowrite32(buf_dma, pd->regs + EVEN_DMA_START); + iowrite32(buf_dma, pd->regs + ODD_DMA_START); + iowrite32(0, pd->regs + EVEN_DMA_STRIDE); + iowrite32(0, pd->regs + ODD_DMA_STRIDE); + + /* Perform a pseudo even field acquire */ + iowrite32(FIFO_EN | SRST | CAP_CONT_ODD, pd->regs + CSR1); + write_i2c_reg(pd->regs, CSR2, pd->csr2 | SYNC_SNTL); + write_i2c_reg(pd->regs, CONFIG, pd->config); + write_i2c_reg(pd->regs, EVEN_CSR, CSR_SNGL); + write_i2c_reg(pd->regs, CSR2, pd->csr2 | BUSY_EVEN | SYNC_SNTL); + msleep(100); + read_i2c_reg(pd->regs, CSR2, &tmp); + write_i2c_reg(pd->regs, EVEN_CSR, CSR_ERROR | CSR_SNGL | CSR_DONE); + write_i2c_reg(pd->regs, ODD_CSR, CSR_ERROR | CSR_SNGL | CSR_DONE); + write_i2c_reg(pd->regs, CSR2, pd->csr2); + iowrite32(FIFO_EN | SRST | FLD_DN_EVEN | FLD_DN_ODD, pd->regs + CSR1); + + /* deallocate memory */ + dma_free_coherent(&pdev->dev, DT3155_BUF_SIZE, buf_cpu, buf_dma); + if (tmp & BUSY_EVEN) + return -EIO; + return 0; +} + +static struct video_device dt3155_vdev = { + .name = DT3155_NAME, + .fops = &dt3155_fops, + .ioctl_ops = &dt3155_ioctl_ops, + .minor = -1, + .release = video_device_release, + .tvnorms = DT3155_CURRENT_NORM, +}; + +/* same as in drivers/base/dma-coherent.c */ +struct dma_coherent_mem { + void *virt_base; + dma_addr_t device_base; + int size; + int flags; + unsigned long *bitmap; +}; + +static int +dt3155_alloc_coherent(struct device *dev, size_t size, int flags) +{ + struct dma_coherent_mem *mem; + dma_addr_t dev_base; + int pages = size >> PAGE_SHIFT; + int bitmap_size = BITS_TO_LONGS(pages) * sizeof(long); + + if ((flags & DMA_MEMORY_MAP) == 0) + goto out; + if (!size) + goto out; + if (dev->dma_mem) + goto out; + + mem = kzalloc(sizeof(*mem), GFP_KERNEL); + if (!mem) + goto out; + mem->virt_base = dma_alloc_coherent(dev, size, &dev_base, + DT3155_COH_FLAGS); + if (!mem->virt_base) + goto err_alloc_coherent; + mem->bitmap = kzalloc(bitmap_size, GFP_KERNEL); + if (!mem->bitmap) + goto err_bitmap; + + /* coherent_dma_mask is already set to 32 bits */ + mem->device_base = dev_base; + mem->size = pages; + mem->flags = flags; + dev->dma_mem = mem; + return DMA_MEMORY_MAP; + +err_bitmap: + dma_free_coherent(dev, size, mem->virt_base, dev_base); +err_alloc_coherent: + kfree(mem); +out: + return 0; +} + +static void +dt3155_free_coherent(struct device *dev) +{ + struct dma_coherent_mem *mem = dev->dma_mem; + + if (!mem) + return; + dev->dma_mem = NULL; + dma_free_coherent(dev, mem->size << PAGE_SHIFT, + mem->virt_base, mem->device_base); + kfree(mem->bitmap); + kfree(mem); +} + +static int +dt3155_probe(struct pci_dev *pdev, const struct pci_device_id *id) +{ + int err; + struct dt3155_priv *pd; + + err = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32)); + if (err) + return -ENODEV; + pd = kzalloc(sizeof(*pd), GFP_KERNEL); + if (!pd) + return -ENOMEM; + pd->vdev = video_device_alloc(); + if (!pd->vdev) { + err = -ENOMEM; + goto err_video_device_alloc; + } + *pd->vdev = dt3155_vdev; + pci_set_drvdata(pdev, pd); /* for use in dt3155_remove() */ + video_set_drvdata(pd->vdev, pd); /* for use in video_fops */ + pd->users = 0; + pd->pdev = pdev; + INIT_LIST_HEAD(&pd->dmaq); + mutex_init(&pd->mux); + pd->vdev->lock = &pd->mux; /* for locking v4l2_file_operations */ + spin_lock_init(&pd->lock); + pd->csr2 = csr2_init; + pd->config = config_init; + err = pci_enable_device(pdev); + if (err) + goto err_enable_dev; + err = pci_request_region(pdev, 0, pci_name(pdev)); + if (err) + goto err_req_region; + pd->regs = pci_iomap(pdev, 0, pci_resource_len(pd->pdev, 0)); + if (!pd->regs) { + err = -ENOMEM; + goto err_pci_iomap; + } + err = dt3155_init_board(pdev); + if (err) + goto err_init_board; + err = video_register_device(pd->vdev, VFL_TYPE_GRABBER, -1); + if (err) + goto err_init_board; + if (dt3155_alloc_coherent(&pdev->dev, DT3155_CHUNK_SIZE, + DMA_MEMORY_MAP)) + dev_info(&pdev->dev, "preallocated 8 buffers\n"); + dev_info(&pdev->dev, "/dev/video%i is ready\n", pd->vdev->minor); + return 0; /* success */ + +err_init_board: + pci_iounmap(pdev, pd->regs); +err_pci_iomap: + pci_release_region(pdev, 0); +err_req_region: + pci_disable_device(pdev); +err_enable_dev: + video_device_release(pd->vdev); +err_video_device_alloc: + kfree(pd); + return err; +} + +static void +dt3155_remove(struct pci_dev *pdev) +{ + struct dt3155_priv *pd = pci_get_drvdata(pdev); + + dt3155_free_coherent(&pdev->dev); + video_unregister_device(pd->vdev); + pci_iounmap(pdev, pd->regs); + pci_release_region(pdev, 0); + pci_disable_device(pdev); + /* + * video_device_release() is invoked automatically + * see: struct video_device dt3155_vdev + */ + kfree(pd); +} + +static const struct pci_device_id pci_ids[] = { + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, DT3155_DEVICE_ID) }, + { 0, /* zero marks the end */ }, +}; +MODULE_DEVICE_TABLE(pci, pci_ids); + +static struct pci_driver pci_driver = { + .name = DT3155_NAME, + .id_table = pci_ids, + .probe = dt3155_probe, + .remove = dt3155_remove, +}; + +module_pci_driver(pci_driver); + +MODULE_DESCRIPTION("video4linux pci-driver for dt3155 frame grabber"); +MODULE_AUTHOR("Marin Mitov <mitov@issp.bas.bg>"); +MODULE_VERSION(DT3155_VERSION); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/media/dt3155v4l/dt3155v4l.h b/drivers/staging/media/dt3155v4l/dt3155v4l.h new file mode 100644 index 00000000000..2e4f89d402e --- /dev/null +++ b/drivers/staging/media/dt3155v4l/dt3155v4l.h @@ -0,0 +1,212 @@ +/*************************************************************************** + * Copyright (C) 2006-2010 by Marin Mitov * + * mitov@issp.bas.bg * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +/* DT3155 header file */ +#ifndef _DT3155_H_ +#define _DT3155_H_ + +#ifdef __KERNEL__ + +#include <linux/pci.h> +#include <linux/interrupt.h> + +#define DT3155_NAME "dt3155" +#define DT3155_VER_MAJ 1 +#define DT3155_VER_MIN 1 +#define DT3155_VER_EXT 0 +#define DT3155_VERSION __stringify(DT3155_VER_MAJ) "." \ + __stringify(DT3155_VER_MIN) "." \ + __stringify(DT3155_VER_EXT) + +/* DT3155 Base Register offsets (memory mapped) */ +#define EVEN_DMA_START 0x00 +#define ODD_DMA_START 0x0C +#define EVEN_DMA_STRIDE 0x18 +#define ODD_DMA_STRIDE 0x24 +#define EVEN_PIXEL_FMT 0x30 +#define ODD_PIXEL_FMT 0x34 +#define FIFO_TRIGER 0x38 +#define XFER_MODE 0x3C +#define CSR1 0x40 +#define RETRY_WAIT_CNT 0x44 +#define INT_CSR 0x48 +#define EVEN_FLD_MASK 0x4C +#define ODD_FLD_MASK 0x50 +#define MASK_LENGTH 0x54 +#define FIFO_FLAG_CNT 0x58 +#define IIC_CLK_DUR 0x5C +#define IIC_CSR1 0x60 +#define IIC_CSR2 0x64 + +/* DT3155 Internal Registers indexes (i2c/IIC mapped) */ +#define CSR2 0x10 +#define EVEN_CSR 0x11 +#define ODD_CSR 0x12 +#define CONFIG 0x13 +#define DT_ID 0x1F +#define X_CLIP_START 0x20 +#define Y_CLIP_START 0x22 +#define X_CLIP_END 0x24 +#define Y_CLIP_END 0x26 +#define AD_ADDR 0x30 +#define AD_LUT 0x31 +#define AD_CMD 0x32 +#define DIG_OUT 0x40 +#define PM_LUT_ADDR 0x50 +#define PM_LUT_DATA 0x51 + +/* AD command register values */ +#define AD_CMD_REG 0x00 +#define AD_POS_REF 0x01 +#define AD_NEG_REF 0x02 + +/* CSR1 bit masks */ +#define CRPT_DIS 0x00004000 +#define FLD_CRPT_ODD 0x00000200 +#define FLD_CRPT_EVEN 0x00000100 +#define FIFO_EN 0x00000080 +#define SRST 0x00000040 +#define FLD_DN_ODD 0x00000020 +#define FLD_DN_EVEN 0x00000010 +/* These should not be used. + * Use CAP_CONT_ODD/EVEN instead +#define CAP_SNGL_ODD 0x00000008 +#define CAP_SNGL_EVEN 0x00000004 +*/ +#define CAP_CONT_ODD 0x00000002 +#define CAP_CONT_EVEN 0x00000001 + +/* INT_CSR bit masks */ +#define FLD_START_EN 0x00000400 +#define FLD_END_ODD_EN 0x00000200 +#define FLD_END_EVEN_EN 0x00000100 +#define FLD_START 0x00000004 +#define FLD_END_ODD 0x00000002 +#define FLD_END_EVEN 0x00000001 + +/* IIC_CSR1 bit masks */ +#define DIRECT_ABORT 0x00000200 + +/* IIC_CSR2 bit masks */ +#define NEW_CYCLE 0x01000000 +#define DIR_RD 0x00010000 +#define IIC_READ 0x01010000 +#define IIC_WRITE 0x01000000 + +/* CSR2 bit masks */ +#define DISP_PASS 0x40 +#define BUSY_ODD 0x20 +#define BUSY_EVEN 0x10 +#define SYNC_PRESENT 0x08 +#define VT_50HZ 0x04 +#define SYNC_SNTL 0x02 +#define CHROM_FILT 0x01 +#define VT_60HZ 0x00 + +/* CSR_EVEN/ODD bit masks */ +#define CSR_ERROR 0x04 +#define CSR_SNGL 0x02 +#define CSR_DONE 0x01 + +/* CONFIG bit masks */ +#define PM_LUT_PGM 0x80 +#define PM_LUT_SEL 0x40 +#define CLIP_EN 0x20 +#define HSCALE_EN 0x10 +#define EXT_TRIG_UP 0x0C +#define EXT_TRIG_DOWN 0x04 +#define ACQ_MODE_NEXT 0x02 +#define ACQ_MODE_ODD 0x01 +#define ACQ_MODE_EVEN 0x00 + +/* AD_CMD bit masks */ +#define VIDEO_CNL_1 0x00 +#define VIDEO_CNL_2 0x40 +#define VIDEO_CNL_3 0x80 +#define VIDEO_CNL_4 0xC0 +#define SYNC_CNL_1 0x00 +#define SYNC_CNL_2 0x10 +#define SYNC_CNL_3 0x20 +#define SYNC_CNL_4 0x30 +#define SYNC_LVL_1 0x00 +#define SYNC_LVL_2 0x04 +#define SYNC_LVL_3 0x08 +#define SYNC_LVL_4 0x0C + +/* DT3155 identificator */ +#define DT3155_ID 0x20 + +#ifdef CONFIG_DT3155_CCIR +#define DMA_STRIDE 768 +#else +#define DMA_STRIDE 640 +#endif + +/** + * struct dt3155_stats - statistics structure + * + * @free_bufs_empty: no free image buffers + * @corrupted_fields: corrupted fields + * @dma_map_failed: dma mapping failed + * @start_before_end: new started before old ended + */ +struct dt3155_stats { + int free_bufs_empty; + int corrupted_fields; + int dma_map_failed; + int start_before_end; +}; + +/* per board private data structure */ +/** + * struct dt3155_priv - private data structure + * + * @vdev: pointer to video_device structure + * @pdev: pointer to pci_dev structure + * @q pointer to vb2_queue structure + * @curr_buf: pointer to curren buffer + * @mux: mutex to protect the instance + * @dmaq queue for dma buffers + * @lock spinlock for dma queue + * @field_count fields counter + * @stats: statistics structure + * @users open count + * @regs: local copy of mmio base register + * @csr2: local copy of csr2 register + * @config: local copy of config register + */ +struct dt3155_priv { + struct video_device *vdev; + struct pci_dev *pdev; + struct vb2_queue *q; + struct vb2_buffer *curr_buf; + struct mutex mux; + struct list_head dmaq; + spinlock_t lock; + unsigned int field_count; + struct dt3155_stats stats; + void __iomem *regs; + int users; + u8 csr2, config; +}; + +#endif /* __KERNEL__ */ + +#endif /* _DT3155_H_ */ diff --git a/drivers/staging/media/go7007/Kconfig b/drivers/staging/media/go7007/Kconfig new file mode 100644 index 00000000000..95a3af644a9 --- /dev/null +++ b/drivers/staging/media/go7007/Kconfig @@ -0,0 +1,51 @@ +config VIDEO_GO7007 + tristate "WIS GO7007 MPEG encoder support" + depends on VIDEO_DEV && I2C + depends on SND && USB + select VIDEOBUF2_VMALLOC + select VIDEO_TUNER + select CYPRESS_FIRMWARE + select SND_PCM + select VIDEO_SONY_BTF_MPX if MEDIA_SUBDRV_AUTOSELECT + select VIDEO_SAA711X if MEDIA_SUBDRV_AUTOSELECT + select VIDEO_TW2804 if MEDIA_SUBDRV_AUTOSELECT + select VIDEO_TW9903 if MEDIA_SUBDRV_AUTOSELECT + select VIDEO_TW9906 if MEDIA_SUBDRV_AUTOSELECT + select VIDEO_OV7640 if MEDIA_SUBDRV_AUTOSELECT + select VIDEO_UDA1342 if MEDIA_SUBDRV_AUTOSELECT + ---help--- + This is a video4linux driver for the WIS GO7007 MPEG + encoder chip. + + To compile this driver as a module, choose M here: the + module will be called go7007. + +config VIDEO_GO7007_USB + tristate "WIS GO7007 USB support" + depends on VIDEO_GO7007 && USB + ---help--- + This is a video4linux driver for the WIS GO7007 MPEG + encoder chip over USB. + + To compile this driver as a module, choose M here: the + module will be called go7007-usb. + +config VIDEO_GO7007_LOADER + tristate "WIS GO7007 Loader support" + depends on VIDEO_GO7007 + default y + ---help--- + This is a go7007 firmware loader driver for the WIS GO7007 + MPEG encoder chip over USB. + + To compile this driver as a module, choose M here: the + module will be called go7007-loader. + +config VIDEO_GO7007_USB_S2250_BOARD + tristate "Sensoray 2250/2251 support" + depends on VIDEO_GO7007_USB && USB + ---help--- + This is a video4linux driver for the Sensoray 2250/2251 device. + + To compile this driver as a module, choose M here: the + module will be called s2250. diff --git a/drivers/staging/media/go7007/Makefile b/drivers/staging/media/go7007/Makefile new file mode 100644 index 00000000000..9c6ad4a263e --- /dev/null +++ b/drivers/staging/media/go7007/Makefile @@ -0,0 +1,15 @@ +obj-$(CONFIG_VIDEO_GO7007) += go7007.o +obj-$(CONFIG_VIDEO_GO7007_USB) += go7007-usb.o +obj-$(CONFIG_VIDEO_GO7007_LOADER) += go7007-loader.o +obj-$(CONFIG_VIDEO_GO7007_USB_S2250_BOARD) += s2250.o + +go7007-y := go7007-v4l2.o go7007-driver.o go7007-i2c.o go7007-fw.o \ + snd-go7007.o + +s2250-y := s2250-board.o + +# Uncomment when the saa7134 patches get into upstream +#obj-$(CONFIG_VIDEO_SAA7134) += saa7134-go7007.o +#ccflags-$(CONFIG_VIDEO_SAA7134:m=y) += -Idrivers/media/pci/saa7134 + +ccflags-$(CONFIG_VIDEO_GO7007_LOADER:m=y) += -Idrivers/media/common diff --git a/drivers/staging/media/go7007/README b/drivers/staging/media/go7007/README new file mode 100644 index 00000000000..3af0d906281 --- /dev/null +++ b/drivers/staging/media/go7007/README @@ -0,0 +1,137 @@ +Todo: + - create an API for motion detection + - let s2250-board use i2c subdevs as well instead of hardcoding + support for the i2c devices. + - when the driver is moved out of staging, support for saa7134-go7007 + should be added to the saa7134 driver. The patch for that is + included below. + +Patch for saa7134: + +diff --git a/drivers/media/pci/saa7134/saa7134-cards.c b/drivers/media/pci/saa7134/saa7134-cards.c +index dc68cf1..9a53794 100644 +--- a/drivers/media/pci/saa7134/saa7134-cards.c ++++ b/drivers/media/pci/saa7134/saa7134-cards.c +@@ -5790,6 +5790,29 @@ struct saa7134_board saa7134_boards[] = { + .gpio = 0x6010000, + } }, + }, ++ [SAA7134_BOARD_WIS_VOYAGER] = { ++ .name = "WIS Voyager or compatible", ++ .audio_clock = 0x00200000, ++ .tuner_type = TUNER_PHILIPS_TDA8290, ++ .radio_type = UNSET, ++ .tuner_addr = ADDR_UNSET, ++ .radio_addr = ADDR_UNSET, ++ .mpeg = SAA7134_MPEG_GO7007, ++ .inputs = { { ++ .name = name_comp1, ++ .vmux = 0, ++ .amux = LINE2, ++ }, { ++ .name = name_tv, ++ .vmux = 3, ++ .amux = TV, ++ .tv = 1, ++ }, { ++ .name = name_svideo, ++ .vmux = 6, ++ .amux = LINE1, ++ } }, ++ }, + + }; + +@@ -7037,6 +7060,12 @@ struct pci_device_id saa7134_pci_tbl[] = { + .subdevice = 0x0911, + .driver_data = SAA7134_BOARD_SENSORAY811_911, + }, { ++ .vendor = PCI_VENDOR_ID_PHILIPS, ++ .device = PCI_DEVICE_ID_PHILIPS_SAA7133, ++ .subvendor = 0x1905, /* WIS */ ++ .subdevice = 0x7007, ++ .driver_data = SAA7134_BOARD_WIS_VOYAGER, ++ }, { + /* --- boards without eeprom + subsystem ID --- */ + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7134, +diff --git a/drivers/media/pci/saa7134/saa7134-core.c b/drivers/media/pci/saa7134/saa7134-core.c +index 8fd24e7..0a849ea 100644 +--- a/drivers/media/pci/saa7134/saa7134-core.c ++++ b/drivers/media/pci/saa7134/saa7134-core.c +@@ -156,6 +156,8 @@ static void request_module_async(struct work_struct *work){ + request_module("saa7134-empress"); + if (card_is_dvb(dev)) + request_module("saa7134-dvb"); ++ if (card_is_go7007(dev)) ++ request_module("saa7134-go7007"); + if (alsa) { + if (dev->pci->device != PCI_DEVICE_ID_PHILIPS_SAA7130) + request_module("saa7134-alsa"); +@@ -557,8 +559,12 @@ static irqreturn_t saa7134_irq(int irq, void *dev_id) + saa7134_irq_vbi_done(dev,status); + + if ((report & SAA7134_IRQ_REPORT_DONE_RA2) && +- card_has_mpeg(dev)) +- saa7134_irq_ts_done(dev,status); ++ card_has_mpeg(dev)) { ++ if (dev->mops->irq_ts_done != NULL) ++ dev->mops->irq_ts_done(dev, status); ++ else ++ saa7134_irq_ts_done(dev, status); ++ } + + if (report & SAA7134_IRQ_REPORT_GPIO16) { + switch (dev->has_remote) { +diff --git a/drivers/media/pci/saa7134/saa7134.h b/drivers/media/pci/saa7134/saa7134.h +index 62169dd..5fad39a 100644 +--- a/drivers/media/pci/saa7134/saa7134.h ++++ b/drivers/media/pci/saa7134/saa7134.h +@@ -334,6 +334,7 @@ struct saa7134_card_ir { + #define SAA7134_BOARD_KWORLD_PC150U 189 + #define SAA7134_BOARD_ASUSTeK_PS3_100 190 + #define SAA7134_BOARD_HAWELL_HW_9004V1 191 ++#define SAA7134_BOARD_WIS_VOYAGER 192 + + #define SAA7134_MAXBOARDS 32 + #define SAA7134_INPUT_MAX 8 +@@ -364,6 +365,7 @@ enum saa7134_mpeg_type { + SAA7134_MPEG_UNUSED, + SAA7134_MPEG_EMPRESS, + SAA7134_MPEG_DVB, ++ SAA7134_MPEG_GO7007, + }; + + enum saa7134_mpeg_ts_type { +@@ -403,6 +405,7 @@ struct saa7134_board { + #define card_has_radio(dev) (NULL != saa7134_boards[dev->board].radio.name) + #define card_is_empress(dev) (SAA7134_MPEG_EMPRESS == saa7134_boards[dev->board].mpeg) + #define card_is_dvb(dev) (SAA7134_MPEG_DVB == saa7134_boards[dev->board].mpeg) ++#define card_is_go7007(dev) (SAA7134_MPEG_GO7007 == saa7134_boards[dev->board].mpeg) + #define card_has_mpeg(dev) (SAA7134_MPEG_UNUSED != saa7134_boards[dev->board].mpeg) + #define card(dev) (saa7134_boards[dev->board]) + #define card_in(dev,n) (saa7134_boards[dev->board].inputs[n]) +@@ -535,6 +538,8 @@ struct saa7134_mpeg_ops { + int (*init)(struct saa7134_dev *dev); + int (*fini)(struct saa7134_dev *dev); + void (*signal_change)(struct saa7134_dev *dev); ++ void (*irq_ts_done)(struct saa7134_dev *dev, ++ unsigned long status); + }; + + /* global device status */ +diff --git a/drivers/staging/media/go7007/Makefile b/drivers/staging/media/go7007/Makefile +index 9c6ad4a..1b23689 100644 +--- a/drivers/staging/media/go7007/Makefile ++++ b/drivers/staging/media/go7007/Makefile +@@ -8,8 +8,7 @@ go7007-y := go7007-v4l2.o go7007-driver.o go7007-i2c.o go7007-fw.o \ + + s2250-y := s2250-board.o + +-# Uncomment when the saa7134 patches get into upstream +-#obj-$(CONFIG_VIDEO_SAA7134) += saa7134-go7007.o +-#ccflags-$(CONFIG_VIDEO_SAA7134:m=y) += -Idrivers/media/pci/saa7134 ++obj-$(CONFIG_VIDEO_SAA7134) += saa7134-go7007.o ++ccflags-$(CONFIG_VIDEO_SAA7134:m=y) += -Idrivers/media/pci/saa7134 + + ccflags-$(CONFIG_VIDEO_GO7007_LOADER:m=y) += -Idrivers/media/common diff --git a/drivers/staging/media/go7007/go7007-driver.c b/drivers/staging/media/go7007/go7007-driver.c new file mode 100644 index 00000000000..6f1beca86b2 --- /dev/null +++ b/drivers/staging/media/go7007/go7007-driver.c @@ -0,0 +1,711 @@ +/* + * Copyright (C) 2005-2006 Micronas USA Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License (Version 2) as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA. + */ + +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/sched.h> +#include <linux/spinlock.h> +#include <linux/unistd.h> +#include <linux/time.h> +#include <linux/mm.h> +#include <linux/vmalloc.h> +#include <linux/device.h> +#include <linux/i2c.h> +#include <linux/firmware.h> +#include <linux/mutex.h> +#include <linux/uaccess.h> +#include <linux/slab.h> +#include <linux/videodev2.h> +#include <media/tuner.h> +#include <media/v4l2-common.h> + +#include "go7007-priv.h" + +/* + * Wait for an interrupt to be delivered from the GO7007SB and return + * the associated value and data. + * + * Must be called with the hw_lock held. + */ +int go7007_read_interrupt(struct go7007 *go, u16 *value, u16 *data) +{ + go->interrupt_available = 0; + go->hpi_ops->read_interrupt(go); + if (wait_event_timeout(go->interrupt_waitq, + go->interrupt_available, 5*HZ) < 0) { + v4l2_err(&go->v4l2_dev, "timeout waiting for read interrupt\n"); + return -1; + } + if (!go->interrupt_available) + return -1; + go->interrupt_available = 0; + *value = go->interrupt_value & 0xfffe; + *data = go->interrupt_data; + return 0; +} +EXPORT_SYMBOL(go7007_read_interrupt); + +/* + * Read a register/address on the GO7007SB. + * + * Must be called with the hw_lock held. + */ +int go7007_read_addr(struct go7007 *go, u16 addr, u16 *data) +{ + int count = 100; + u16 value; + + if (go7007_write_interrupt(go, 0x0010, addr) < 0) + return -EIO; + while (count-- > 0) { + if (go7007_read_interrupt(go, &value, data) == 0 && + value == 0xa000) + return 0; + } + return -EIO; +} +EXPORT_SYMBOL(go7007_read_addr); + +/* + * Send the boot firmware to the encoder, which just wakes it up and lets + * us talk to the GPIO pins and on-board I2C adapter. + * + * Must be called with the hw_lock held. + */ +static int go7007_load_encoder(struct go7007 *go) +{ + const struct firmware *fw_entry; + char fw_name[] = "go7007/go7007fw.bin"; + void *bounce; + int fw_len, rv = 0; + u16 intr_val, intr_data; + + if (go->boot_fw == NULL) { + if (request_firmware(&fw_entry, fw_name, go->dev)) { + v4l2_err(go, "unable to load firmware from file \"%s\"\n", fw_name); + return -1; + } + if (fw_entry->size < 16 || memcmp(fw_entry->data, "WISGO7007FW", 11)) { + v4l2_err(go, "file \"%s\" does not appear to be go7007 firmware\n", fw_name); + release_firmware(fw_entry); + return -1; + } + fw_len = fw_entry->size - 16; + bounce = kmemdup(fw_entry->data + 16, fw_len, GFP_KERNEL); + if (bounce == NULL) { + v4l2_err(go, "unable to allocate %d bytes for firmware transfer\n", fw_len); + release_firmware(fw_entry); + return -1; + } + release_firmware(fw_entry); + go->boot_fw_len = fw_len; + go->boot_fw = bounce; + } + if (go7007_interface_reset(go) < 0 || + go7007_send_firmware(go, go->boot_fw, go->boot_fw_len) < 0 || + go7007_read_interrupt(go, &intr_val, &intr_data) < 0 || + (intr_val & ~0x1) != 0x5a5a) { + v4l2_err(go, "error transferring firmware\n"); + rv = -1; + } + return rv; +} + +MODULE_FIRMWARE("go7007/go7007fw.bin"); + +/* + * Boot the encoder and register the I2C adapter if requested. Do the + * minimum initialization necessary, since the board-specific code may + * still need to probe the board ID. + * + * Must NOT be called with the hw_lock held. + */ +int go7007_boot_encoder(struct go7007 *go, int init_i2c) +{ + int ret; + + mutex_lock(&go->hw_lock); + ret = go7007_load_encoder(go); + mutex_unlock(&go->hw_lock); + if (ret < 0) + return -1; + if (!init_i2c) + return 0; + if (go7007_i2c_init(go) < 0) + return -1; + go->i2c_adapter_online = 1; + return 0; +} +EXPORT_SYMBOL(go7007_boot_encoder); + +/* + * Configure any hardware-related registers in the GO7007, such as GPIO + * pins and bus parameters, which are board-specific. This assumes + * the boot firmware has already been downloaded. + * + * Must be called with the hw_lock held. + */ +static int go7007_init_encoder(struct go7007 *go) +{ + if (go->board_info->audio_flags & GO7007_AUDIO_I2S_MASTER) { + go7007_write_addr(go, 0x1000, 0x0811); + go7007_write_addr(go, 0x1000, 0x0c11); + } + switch (go->board_id) { + case GO7007_BOARDID_MATRIX_REV: + /* Set GPIO pin 0 to be an output (audio clock control) */ + go7007_write_addr(go, 0x3c82, 0x0001); + go7007_write_addr(go, 0x3c80, 0x00fe); + break; + case GO7007_BOARDID_ADLINK_MPG24: + /* set GPIO5 to be an output, currently low */ + go7007_write_addr(go, 0x3c82, 0x0000); + go7007_write_addr(go, 0x3c80, 0x00df); + break; + case GO7007_BOARDID_ADS_USBAV_709: + /* GPIO pin 0: audio clock control */ + /* pin 2: TW9906 reset */ + /* pin 3: capture LED */ + go7007_write_addr(go, 0x3c82, 0x000d); + go7007_write_addr(go, 0x3c80, 0x00f2); + break; + } + return 0; +} + +/* + * Send the boot firmware to the GO7007 and configure the registers. This + * is the only way to stop the encoder once it has started streaming video. + * + * Must be called with the hw_lock held. + */ +int go7007_reset_encoder(struct go7007 *go) +{ + if (go7007_load_encoder(go) < 0) + return -1; + return go7007_init_encoder(go); +} + +/* + * Attempt to instantiate an I2C client by ID, probably loading a module. + */ +static int init_i2c_module(struct i2c_adapter *adapter, const struct go_i2c *const i2c) +{ + struct go7007 *go = i2c_get_adapdata(adapter); + struct v4l2_device *v4l2_dev = &go->v4l2_dev; + struct v4l2_subdev *sd; + struct i2c_board_info info; + + memset(&info, 0, sizeof(info)); + strlcpy(info.type, i2c->type, sizeof(info.type)); + info.addr = i2c->addr; + info.flags = i2c->flags; + + sd = v4l2_i2c_new_subdev_board(v4l2_dev, adapter, &info, NULL); + if (sd) { + if (i2c->is_video) + go->sd_video = sd; + if (i2c->is_audio) + go->sd_audio = sd; + return 0; + } + + printk(KERN_INFO "go7007: probing for module i2c:%s failed\n", i2c->type); + return -EINVAL; +} + +/* + * Detach and unregister the encoder. The go7007 struct won't be freed + * until v4l2 finishes releasing its resources and all associated fds are + * closed by applications. + */ +static void go7007_remove(struct v4l2_device *v4l2_dev) +{ + struct go7007 *go = container_of(v4l2_dev, struct go7007, v4l2_dev); + + v4l2_device_unregister(v4l2_dev); + if (go->hpi_ops->release) + go->hpi_ops->release(go); + if (go->i2c_adapter_online) { + i2c_del_adapter(&go->i2c_adapter); + go->i2c_adapter_online = 0; + } + + kfree(go->boot_fw); + go7007_v4l2_remove(go); + kfree(go); +} + +/* + * Finalize the GO7007 hardware setup, register the on-board I2C adapter + * (if used on this board), load the I2C client driver for the sensor + * (SAA7115 or whatever) and other devices, and register the ALSA and V4L2 + * interfaces. + * + * Must NOT be called with the hw_lock held. + */ +int go7007_register_encoder(struct go7007 *go, unsigned num_i2c_devs) +{ + int i, ret; + + dev_info(go->dev, "go7007: registering new %s\n", go->name); + + go->v4l2_dev.release = go7007_remove; + ret = v4l2_device_register(go->dev, &go->v4l2_dev); + if (ret < 0) + return ret; + + mutex_lock(&go->hw_lock); + ret = go7007_init_encoder(go); + mutex_unlock(&go->hw_lock); + if (ret < 0) + return ret; + + ret = go7007_v4l2_ctrl_init(go); + if (ret < 0) + return ret; + + if (!go->i2c_adapter_online && + go->board_info->flags & GO7007_BOARD_USE_ONBOARD_I2C) { + ret = go7007_i2c_init(go); + if (ret < 0) + return ret; + go->i2c_adapter_online = 1; + } + if (go->i2c_adapter_online) { + if (go->board_id == GO7007_BOARDID_ADS_USBAV_709) { + /* Reset the TW9906 */ + go7007_write_addr(go, 0x3c82, 0x0009); + msleep(50); + go7007_write_addr(go, 0x3c82, 0x000d); + } + for (i = 0; i < num_i2c_devs; ++i) + init_i2c_module(&go->i2c_adapter, &go->board_info->i2c_devs[i]); + + if (go->tuner_type >= 0) { + struct tuner_setup setup = { + .addr = ADDR_UNSET, + .type = go->tuner_type, + .mode_mask = T_ANALOG_TV, + }; + + v4l2_device_call_all(&go->v4l2_dev, 0, tuner, + s_type_addr, &setup); + } + if (go->board_id == GO7007_BOARDID_ADLINK_MPG24) + v4l2_subdev_call(go->sd_video, video, s_routing, + 0, 0, go->channel_number + 1); + } + + ret = go7007_v4l2_init(go); + if (ret < 0) + return ret; + + if (go->board_info->flags & GO7007_BOARD_HAS_AUDIO) { + go->audio_enabled = 1; + go7007_snd_init(go); + } + return 0; +} +EXPORT_SYMBOL(go7007_register_encoder); + +/* + * Send the encode firmware to the encoder, which will cause it + * to immediately start delivering the video and audio streams. + * + * Must be called with the hw_lock held. + */ +int go7007_start_encoder(struct go7007 *go) +{ + u8 *fw; + int fw_len, rv = 0, i; + u16 intr_val, intr_data; + + go->modet_enable = 0; + if (!go->dvd_mode) + for (i = 0; i < 4; ++i) { + if (go->modet[i].enable) { + go->modet_enable = 1; + continue; + } + go->modet[i].pixel_threshold = 32767; + go->modet[i].motion_threshold = 32767; + go->modet[i].mb_threshold = 32767; + } + + if (go7007_construct_fw_image(go, &fw, &fw_len) < 0) + return -1; + + if (go7007_send_firmware(go, fw, fw_len) < 0 || + go7007_read_interrupt(go, &intr_val, &intr_data) < 0) { + v4l2_err(&go->v4l2_dev, "error transferring firmware\n"); + rv = -1; + goto start_error; + } + + go->state = STATE_DATA; + go->parse_length = 0; + go->seen_frame = 0; + if (go7007_stream_start(go) < 0) { + v4l2_err(&go->v4l2_dev, "error starting stream transfer\n"); + rv = -1; + goto start_error; + } + +start_error: + kfree(fw); + return rv; +} + +/* + * Store a byte in the current video buffer, if there is one. + */ +static inline void store_byte(struct go7007_buffer *vb, u8 byte) +{ + if (vb && vb->vb.v4l2_planes[0].bytesused < GO7007_BUF_SIZE) { + u8 *ptr = vb2_plane_vaddr(&vb->vb, 0); + + ptr[vb->vb.v4l2_planes[0].bytesused++] = byte; + } +} + +/* + * Deliver the last video buffer and get a new one to start writing to. + */ +static struct go7007_buffer *frame_boundary(struct go7007 *go, struct go7007_buffer *vb) +{ + struct go7007_buffer *vb_tmp = NULL; + u32 *bytesused = &vb->vb.v4l2_planes[0].bytesused; + int i; + + if (vb) { + if (vb->modet_active) { + if (*bytesused + 216 < GO7007_BUF_SIZE) { + for (i = 0; i < 216; ++i) + store_byte(vb, go->active_map[i]); + *bytesused -= 216; + } else + vb->modet_active = 0; + } + vb->vb.v4l2_buf.sequence = go->next_seq++; + v4l2_get_timestamp(&vb->vb.v4l2_buf.timestamp); + vb_tmp = vb; + spin_lock(&go->spinlock); + list_del(&vb->list); + if (list_empty(&go->vidq_active)) + vb = NULL; + else + vb = list_first_entry(&go->vidq_active, struct go7007_buffer, list); + go->active_buf = vb; + spin_unlock(&go->spinlock); + vb2_buffer_done(&vb_tmp->vb, VB2_BUF_STATE_DONE); + return vb; + } + spin_lock(&go->spinlock); + if (!list_empty(&go->vidq_active)) + vb = go->active_buf = + list_first_entry(&go->vidq_active, struct go7007_buffer, list); + spin_unlock(&go->spinlock); + go->next_seq++; + return vb; +} + +static void write_bitmap_word(struct go7007 *go) +{ + int x, y, i, stride = ((go->width >> 4) + 7) >> 3; + + for (i = 0; i < 16; ++i) { + y = (((go->parse_length - 1) << 3) + i) / (go->width >> 4); + x = (((go->parse_length - 1) << 3) + i) % (go->width >> 4); + if (stride * y + (x >> 3) < sizeof(go->active_map)) + go->active_map[stride * y + (x >> 3)] |= + (go->modet_word & 1) << (x & 0x7); + go->modet_word >>= 1; + } +} + +/* + * Parse a chunk of the video stream into frames. The frames are not + * delimited by the hardware, so we have to parse the frame boundaries + * based on the type of video stream we're receiving. + */ +void go7007_parse_video_stream(struct go7007 *go, u8 *buf, int length) +{ + struct go7007_buffer *vb = go->active_buf; + int i, seq_start_code = -1, gop_start_code = -1, frame_start_code = -1; + + switch (go->format) { + case V4L2_PIX_FMT_MPEG4: + seq_start_code = 0xB0; + gop_start_code = 0xB3; + frame_start_code = 0xB6; + break; + case V4L2_PIX_FMT_MPEG1: + case V4L2_PIX_FMT_MPEG2: + seq_start_code = 0xB3; + gop_start_code = 0xB8; + frame_start_code = 0x00; + break; + } + + for (i = 0; i < length; ++i) { + if (vb && vb->vb.v4l2_planes[0].bytesused >= GO7007_BUF_SIZE - 3) { + v4l2_info(&go->v4l2_dev, "dropping oversized frame\n"); + vb->vb.v4l2_planes[0].bytesused = 0; + vb->frame_offset = 0; + vb->modet_active = 0; + vb = go->active_buf = NULL; + } + + switch (go->state) { + case STATE_DATA: + switch (buf[i]) { + case 0x00: + go->state = STATE_00; + break; + case 0xFF: + go->state = STATE_FF; + break; + default: + store_byte(vb, buf[i]); + break; + } + break; + case STATE_00: + switch (buf[i]) { + case 0x00: + go->state = STATE_00_00; + break; + case 0xFF: + store_byte(vb, 0x00); + go->state = STATE_FF; + break; + default: + store_byte(vb, 0x00); + store_byte(vb, buf[i]); + go->state = STATE_DATA; + break; + } + break; + case STATE_00_00: + switch (buf[i]) { + case 0x00: + store_byte(vb, 0x00); + /* go->state remains STATE_00_00 */ + break; + case 0x01: + go->state = STATE_00_00_01; + break; + case 0xFF: + store_byte(vb, 0x00); + store_byte(vb, 0x00); + go->state = STATE_FF; + break; + default: + store_byte(vb, 0x00); + store_byte(vb, 0x00); + store_byte(vb, buf[i]); + go->state = STATE_DATA; + break; + } + break; + case STATE_00_00_01: + if (buf[i] == 0xF8 && go->modet_enable == 0) { + /* MODET start code, but MODET not enabled */ + store_byte(vb, 0x00); + store_byte(vb, 0x00); + store_byte(vb, 0x01); + store_byte(vb, 0xF8); + go->state = STATE_DATA; + break; + } + /* If this is the start of a new MPEG frame, + * get a new buffer */ + if ((go->format == V4L2_PIX_FMT_MPEG1 || + go->format == V4L2_PIX_FMT_MPEG2 || + go->format == V4L2_PIX_FMT_MPEG4) && + (buf[i] == seq_start_code || + buf[i] == gop_start_code || + buf[i] == frame_start_code)) { + if (vb == NULL || go->seen_frame) + vb = frame_boundary(go, vb); + go->seen_frame = buf[i] == frame_start_code; + if (vb && go->seen_frame) + vb->frame_offset = vb->vb.v4l2_planes[0].bytesused; + } + /* Handle any special chunk types, or just write the + * start code to the (potentially new) buffer */ + switch (buf[i]) { + case 0xF5: /* timestamp */ + go->parse_length = 12; + go->state = STATE_UNPARSED; + break; + case 0xF6: /* vbi */ + go->state = STATE_VBI_LEN_A; + break; + case 0xF8: /* MD map */ + go->parse_length = 0; + memset(go->active_map, 0, + sizeof(go->active_map)); + go->state = STATE_MODET_MAP; + break; + case 0xFF: /* Potential JPEG start code */ + store_byte(vb, 0x00); + store_byte(vb, 0x00); + store_byte(vb, 0x01); + go->state = STATE_FF; + break; + default: + store_byte(vb, 0x00); + store_byte(vb, 0x00); + store_byte(vb, 0x01); + store_byte(vb, buf[i]); + go->state = STATE_DATA; + break; + } + break; + case STATE_FF: + switch (buf[i]) { + case 0x00: + store_byte(vb, 0xFF); + go->state = STATE_00; + break; + case 0xFF: + store_byte(vb, 0xFF); + /* go->state remains STATE_FF */ + break; + case 0xD8: + if (go->format == V4L2_PIX_FMT_MJPEG) + vb = frame_boundary(go, vb); + /* fall through */ + default: + store_byte(vb, 0xFF); + store_byte(vb, buf[i]); + go->state = STATE_DATA; + break; + } + break; + case STATE_VBI_LEN_A: + go->parse_length = buf[i] << 8; + go->state = STATE_VBI_LEN_B; + break; + case STATE_VBI_LEN_B: + go->parse_length |= buf[i]; + if (go->parse_length > 0) + go->state = STATE_UNPARSED; + else + go->state = STATE_DATA; + break; + case STATE_MODET_MAP: + if (go->parse_length < 204) { + if (go->parse_length & 1) { + go->modet_word |= buf[i]; + write_bitmap_word(go); + } else + go->modet_word = buf[i] << 8; + } else if (go->parse_length == 207 && vb) { + vb->modet_active = buf[i]; + } + if (++go->parse_length == 208) + go->state = STATE_DATA; + break; + case STATE_UNPARSED: + if (--go->parse_length == 0) + go->state = STATE_DATA; + break; + } + } +} +EXPORT_SYMBOL(go7007_parse_video_stream); + +/* + * Allocate a new go7007 struct. Used by the hardware-specific probe. + */ +struct go7007 *go7007_alloc(const struct go7007_board_info *board, + struct device *dev) +{ + struct go7007 *go; + int i; + + go = kzalloc(sizeof(struct go7007), GFP_KERNEL); + if (go == NULL) + return NULL; + go->dev = dev; + go->board_info = board; + go->board_id = 0; + go->tuner_type = -1; + go->channel_number = 0; + go->name[0] = 0; + mutex_init(&go->hw_lock); + init_waitqueue_head(&go->frame_waitq); + spin_lock_init(&go->spinlock); + go->status = STATUS_INIT; + memset(&go->i2c_adapter, 0, sizeof(go->i2c_adapter)); + go->i2c_adapter_online = 0; + go->interrupt_available = 0; + init_waitqueue_head(&go->interrupt_waitq); + go->input = 0; + go7007_update_board(go); + go->encoder_h_halve = 0; + go->encoder_v_halve = 0; + go->encoder_subsample = 0; + go->format = V4L2_PIX_FMT_MJPEG; + go->bitrate = 1500000; + go->fps_scale = 1; + go->pali = 0; + go->aspect_ratio = GO7007_RATIO_1_1; + go->gop_size = 0; + go->ipb = 0; + go->closed_gop = 0; + go->repeat_seqhead = 0; + go->seq_header_enable = 0; + go->gop_header_enable = 0; + go->dvd_mode = 0; + go->interlace_coding = 0; + for (i = 0; i < 4; ++i) + go->modet[i].enable = 0; + for (i = 0; i < 1624; ++i) + go->modet_map[i] = 0; + go->audio_deliver = NULL; + go->audio_enabled = 0; + + return go; +} +EXPORT_SYMBOL(go7007_alloc); + +void go7007_update_board(struct go7007 *go) +{ + const struct go7007_board_info *board = go->board_info; + + if (board->sensor_flags & GO7007_SENSOR_TV) { + go->standard = GO7007_STD_NTSC; + go->std = V4L2_STD_NTSC_M; + go->width = 720; + go->height = 480; + go->sensor_framerate = 30000; + } else { + go->standard = GO7007_STD_OTHER; + go->width = board->sensor_width; + go->height = board->sensor_height; + go->sensor_framerate = board->sensor_framerate; + } + go->encoder_v_offset = board->sensor_v_offset; + go->encoder_h_offset = board->sensor_h_offset; +} +EXPORT_SYMBOL(go7007_update_board); + +MODULE_LICENSE("GPL v2"); diff --git a/drivers/staging/media/go7007/go7007-fw.c b/drivers/staging/media/go7007/go7007-fw.c new file mode 100644 index 00000000000..814ce08bc44 --- /dev/null +++ b/drivers/staging/media/go7007/go7007-fw.c @@ -0,0 +1,1628 @@ +/* + * Copyright (C) 2005-2006 Micronas USA Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License (Version 2) as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA. + */ + +/* + * This file contains code to generate a firmware image for the GO7007SB + * encoder. Much of the firmware is read verbatim from a file, but some of + * it concerning bitrate control and other things that can be configured at + * run-time are generated dynamically. Note that the format headers + * generated here do not affect the functioning of the encoder; they are + * merely parroted back to the host at the start of each frame. + */ + +#include <linux/module.h> +#include <linux/time.h> +#include <linux/mm.h> +#include <linux/device.h> +#include <linux/i2c.h> +#include <linux/firmware.h> +#include <linux/slab.h> +#include <asm/byteorder.h> + +#include "go7007-priv.h" + +#define GO7007_FW_NAME "go7007/go7007tv.bin" + +/* Constants used in the source firmware image to describe code segments */ + +#define FLAG_MODE_MJPEG (1) +#define FLAG_MODE_MPEG1 (1<<1) +#define FLAG_MODE_MPEG2 (1<<2) +#define FLAG_MODE_MPEG4 (1<<3) +#define FLAG_MODE_H263 (1<<4) +#define FLAG_MODE_ALL (FLAG_MODE_MJPEG | FLAG_MODE_MPEG1 | \ + FLAG_MODE_MPEG2 | FLAG_MODE_MPEG4 | \ + FLAG_MODE_H263) +#define FLAG_SPECIAL (1<<8) + +#define SPECIAL_FRM_HEAD 0 +#define SPECIAL_BRC_CTRL 1 +#define SPECIAL_CONFIG 2 +#define SPECIAL_SEQHEAD 3 +#define SPECIAL_AV_SYNC 4 +#define SPECIAL_FINAL 5 +#define SPECIAL_AUDIO 6 +#define SPECIAL_MODET 7 + +/* Little data class for creating MPEG headers bit-by-bit */ + +struct code_gen { + unsigned char *p; /* destination */ + u32 a; /* collects bits at the top of the variable */ + int b; /* bit position of most recently-written bit */ + int len; /* written out so far */ +}; + +#define CODE_GEN(name, dest) struct code_gen name = { dest, 0, 32, 0 } + +#define CODE_ADD(name, val, length) do { \ + name.b -= (length); \ + name.a |= (val) << name.b; \ + while (name.b <= 24) { \ + *name.p = name.a >> 24; \ + ++name.p; \ + name.a <<= 8; \ + name.b += 8; \ + name.len += 8; \ + } \ +} while (0) + +#define CODE_LENGTH(name) (name.len + (32 - name.b)) + +/* Tables for creating the bitrate control data */ + +static const s16 converge_speed_ip[101] = { + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, + 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, + 5, 5, 5, 6, 6, 6, 7, 7, 8, 8, + 9, 10, 10, 11, 12, 13, 14, 15, 16, 17, + 19, 20, 22, 23, 25, 27, 30, 32, 35, 38, + 41, 45, 49, 53, 58, 63, 69, 76, 83, 91, + 100 +}; + +static const s16 converge_speed_ipb[101] = { + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 5, 5, 5, 5, 5, 6, + 6, 6, 6, 7, 7, 7, 7, 8, 8, 9, + 9, 9, 10, 10, 11, 12, 12, 13, 14, 14, + 15, 16, 17, 18, 19, 20, 22, 23, 25, 26, + 28, 30, 32, 34, 37, 40, 42, 46, 49, 53, + 57, 61, 66, 71, 77, 83, 90, 97, 106, 115, + 125, 135, 147, 161, 175, 191, 209, 228, 249, 273, + 300 +}; + +static const s16 LAMBDA_table[4][101] = { + { 16, 16, 16, 16, 17, 17, 17, 18, 18, 18, + 19, 19, 19, 20, 20, 20, 21, 21, 22, 22, + 22, 23, 23, 24, 24, 25, 25, 25, 26, 26, + 27, 27, 28, 28, 29, 29, 30, 31, 31, 32, + 32, 33, 33, 34, 35, 35, 36, 37, 37, 38, + 39, 39, 40, 41, 42, 42, 43, 44, 45, 46, + 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, + 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, + 67, 68, 69, 70, 72, 73, 74, 76, 77, 78, + 80, 81, 83, 84, 86, 87, 89, 90, 92, 94, + 96 + }, + { + 20, 20, 20, 21, 21, 21, 22, 22, 23, 23, + 23, 24, 24, 25, 25, 26, 26, 27, 27, 28, + 28, 29, 29, 30, 30, 31, 31, 32, 33, 33, + 34, 34, 35, 36, 36, 37, 38, 38, 39, 40, + 40, 41, 42, 43, 43, 44, 45, 46, 47, 48, + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, + 58, 59, 60, 61, 62, 64, 65, 66, 67, 68, + 70, 71, 72, 73, 75, 76, 78, 79, 80, 82, + 83, 85, 86, 88, 90, 91, 93, 95, 96, 98, + 100, 102, 103, 105, 107, 109, 111, 113, 115, 117, + 120 + }, + { + 24, 24, 24, 25, 25, 26, 26, 27, 27, 28, + 28, 29, 29, 30, 30, 31, 31, 32, 33, 33, + 34, 34, 35, 36, 36, 37, 38, 38, 39, 40, + 41, 41, 42, 43, 44, 44, 45, 46, 47, 48, + 49, 50, 50, 51, 52, 53, 54, 55, 56, 57, + 58, 59, 60, 62, 63, 64, 65, 66, 67, 69, + 70, 71, 72, 74, 75, 76, 78, 79, 81, 82, + 84, 85, 87, 88, 90, 92, 93, 95, 97, 98, + 100, 102, 104, 106, 108, 110, 112, 114, 116, 118, + 120, 122, 124, 127, 129, 131, 134, 136, 138, 141, + 144 + }, + { + 32, 32, 33, 33, 34, 34, 35, 36, 36, 37, + 38, 38, 39, 40, 41, 41, 42, 43, 44, 44, + 45, 46, 47, 48, 49, 50, 50, 51, 52, 53, + 54, 55, 56, 57, 58, 59, 60, 62, 63, 64, + 65, 66, 67, 69, 70, 71, 72, 74, 75, 76, + 78, 79, 81, 82, 84, 85, 87, 88, 90, 92, + 93, 95, 97, 98, 100, 102, 104, 106, 108, 110, + 112, 114, 116, 118, 120, 122, 124, 127, 129, 131, + 134, 136, 139, 141, 144, 146, 149, 152, 154, 157, + 160, 163, 166, 169, 172, 175, 178, 181, 185, 188, + 192 + } +}; + +/* MPEG blank frame generation tables */ + +enum mpeg_frame_type { + PFRAME, + BFRAME_PRE, + BFRAME_POST, + BFRAME_BIDIR, + BFRAME_EMPTY +}; + +static const u32 addrinctab[33][2] = { + { 0x01, 1 }, { 0x03, 3 }, { 0x02, 3 }, { 0x03, 4 }, + { 0x02, 4 }, { 0x03, 5 }, { 0x02, 5 }, { 0x07, 7 }, + { 0x06, 7 }, { 0x0b, 8 }, { 0x0a, 8 }, { 0x09, 8 }, + { 0x08, 8 }, { 0x07, 8 }, { 0x06, 8 }, { 0x17, 10 }, + { 0x16, 10 }, { 0x15, 10 }, { 0x14, 10 }, { 0x13, 10 }, + { 0x12, 10 }, { 0x23, 11 }, { 0x22, 11 }, { 0x21, 11 }, + { 0x20, 11 }, { 0x1f, 11 }, { 0x1e, 11 }, { 0x1d, 11 }, + { 0x1c, 11 }, { 0x1b, 11 }, { 0x1a, 11 }, { 0x19, 11 }, + { 0x18, 11 } +}; + +/* Standard JPEG tables */ + +static const u8 default_intra_quant_table[] = { + 8, 16, 19, 22, 26, 27, 29, 34, + 16, 16, 22, 24, 27, 29, 34, 37, + 19, 22, 26, 27, 29, 34, 34, 38, + 22, 22, 26, 27, 29, 34, 37, 40, + 22, 26, 27, 29, 32, 35, 40, 48, + 26, 27, 29, 32, 35, 40, 48, 58, + 26, 27, 29, 34, 38, 46, 56, 69, + 27, 29, 35, 38, 46, 56, 69, 83 +}; + +static const u8 bits_dc_luminance[] = { + 0, 0, 1, 5, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0 +}; + +static const u8 val_dc_luminance[] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 +}; + +static const u8 bits_dc_chrominance[] = { + 0, 0, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0 +}; + +static const u8 val_dc_chrominance[] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 +}; + +static const u8 bits_ac_luminance[] = { + 0, 0, 2, 1, 3, 3, 2, 4, 3, 5, 5, 4, 4, 0, 0, 1, 0x7d +}; + +static const u8 val_ac_luminance[] = { + 0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, + 0x21, 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07, + 0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xa1, 0x08, + 0x23, 0x42, 0xb1, 0xc1, 0x15, 0x52, 0xd1, 0xf0, + 0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0a, 0x16, + 0x17, 0x18, 0x19, 0x1a, 0x25, 0x26, 0x27, 0x28, + 0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, + 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, + 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, + 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, + 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, + 0x7a, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, + 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, + 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, + 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, + 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, + 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, + 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe1, 0xe2, + 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, + 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, + 0xf9, 0xfa +}; + +static const u8 bits_ac_chrominance[] = { + 0, 0, 2, 1, 2, 4, 4, 3, 4, 7, 5, 4, 4, 0, 1, 2, 0x77 +}; + +static const u8 val_ac_chrominance[] = { + 0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21, + 0x31, 0x06, 0x12, 0x41, 0x51, 0x07, 0x61, 0x71, + 0x13, 0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91, + 0xa1, 0xb1, 0xc1, 0x09, 0x23, 0x33, 0x52, 0xf0, + 0x15, 0x62, 0x72, 0xd1, 0x0a, 0x16, 0x24, 0x34, + 0xe1, 0x25, 0xf1, 0x17, 0x18, 0x19, 0x1a, 0x26, + 0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, 0x37, 0x38, + 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, + 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, + 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, + 0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, + 0x79, 0x7a, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, + 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, + 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, + 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, + 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, + 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, + 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, + 0xea, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, + 0xf9, 0xfa +}; + +/* Zig-zag mapping for quant table + * + * OK, let's do this mapping on the actual table above so it doesn't have + * to be done on the fly. + */ +static const int zz[64] = { + 0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, + 12, 19, 26, 33, 40, 48, 41, 34, 27, 20, 13, 6, 7, 14, 21, 28, + 35, 42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23, 30, 37, 44, 51, + 58, 59, 52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63 +}; + +static int copy_packages(__le16 *dest, u16 *src, int pkg_cnt, int space) +{ + int i, cnt = pkg_cnt * 32; + + if (space < cnt) + return -1; + + for (i = 0; i < cnt; ++i) + dest[i] = cpu_to_le16p(src + i); + + return cnt; +} + +static int mjpeg_frame_header(struct go7007 *go, unsigned char *buf, int q) +{ + int i, p = 0; + + buf[p++] = 0xff; + buf[p++] = 0xd8; + buf[p++] = 0xff; + buf[p++] = 0xdb; + buf[p++] = 0; + buf[p++] = 2 + 65; + buf[p++] = 0; + buf[p++] = default_intra_quant_table[0]; + for (i = 1; i < 64; ++i) + /* buf[p++] = (default_intra_quant_table[i] * q) >> 3; */ + buf[p++] = (default_intra_quant_table[zz[i]] * q) >> 3; + buf[p++] = 0xff; + buf[p++] = 0xc0; + buf[p++] = 0; + buf[p++] = 17; + buf[p++] = 8; + buf[p++] = go->height >> 8; + buf[p++] = go->height & 0xff; + buf[p++] = go->width >> 8; + buf[p++] = go->width & 0xff; + buf[p++] = 3; + buf[p++] = 1; + buf[p++] = 0x22; + buf[p++] = 0; + buf[p++] = 2; + buf[p++] = 0x11; + buf[p++] = 0; + buf[p++] = 3; + buf[p++] = 0x11; + buf[p++] = 0; + buf[p++] = 0xff; + buf[p++] = 0xc4; + buf[p++] = 418 >> 8; + buf[p++] = 418 & 0xff; + buf[p++] = 0x00; + memcpy(buf + p, bits_dc_luminance + 1, 16); + p += 16; + memcpy(buf + p, val_dc_luminance, sizeof(val_dc_luminance)); + p += sizeof(val_dc_luminance); + buf[p++] = 0x01; + memcpy(buf + p, bits_dc_chrominance + 1, 16); + p += 16; + memcpy(buf + p, val_dc_chrominance, sizeof(val_dc_chrominance)); + p += sizeof(val_dc_chrominance); + buf[p++] = 0x10; + memcpy(buf + p, bits_ac_luminance + 1, 16); + p += 16; + memcpy(buf + p, val_ac_luminance, sizeof(val_ac_luminance)); + p += sizeof(val_ac_luminance); + buf[p++] = 0x11; + memcpy(buf + p, bits_ac_chrominance + 1, 16); + p += 16; + memcpy(buf + p, val_ac_chrominance, sizeof(val_ac_chrominance)); + p += sizeof(val_ac_chrominance); + buf[p++] = 0xff; + buf[p++] = 0xda; + buf[p++] = 0; + buf[p++] = 12; + buf[p++] = 3; + buf[p++] = 1; + buf[p++] = 0x00; + buf[p++] = 2; + buf[p++] = 0x11; + buf[p++] = 3; + buf[p++] = 0x11; + buf[p++] = 0; + buf[p++] = 63; + buf[p++] = 0; + return p; +} + +static int gen_mjpeghdr_to_package(struct go7007 *go, __le16 *code, int space) +{ + u8 *buf; + u16 mem = 0x3e00; + unsigned int addr = 0x19; + int size = 0, i, off = 0, chunk; + + buf = kzalloc(4096, GFP_KERNEL); + if (buf == NULL) + return -1; + + for (i = 1; i < 32; ++i) { + mjpeg_frame_header(go, buf + size, i); + size += 80; + } + chunk = mjpeg_frame_header(go, buf + size, 1); + memmove(buf + size, buf + size + 80, chunk - 80); + size += chunk - 80; + + for (i = 0; i < size; i += chunk * 2) { + if (space - off < 32) { + off = -1; + goto done; + } + + code[off + 1] = __cpu_to_le16(0x8000 | mem); + + chunk = 28; + if (mem + chunk > 0x4000) + chunk = 0x4000 - mem; + if (i + 2 * chunk > size) + chunk = (size - i) / 2; + + if (chunk < 28) { + code[off] = __cpu_to_le16(0x4000 | chunk); + code[off + 31] = __cpu_to_le16(addr++); + mem = 0x3e00; + } else { + code[off] = __cpu_to_le16(0x1000 | 28); + code[off + 31] = 0; + mem += 28; + } + + memcpy(&code[off + 2], buf + i, chunk * 2); + off += 32; + } +done: + kfree(buf); + return off; +} + +static int mpeg1_frame_header(struct go7007 *go, unsigned char *buf, + int modulo, int pict_struct, enum mpeg_frame_type frame) +{ + int i, j, mb_code, mb_len; + int rows = go->interlace_coding ? go->height / 32 : go->height / 16; + CODE_GEN(c, buf + 6); + + switch (frame) { + case PFRAME: + mb_code = 0x1; + mb_len = 3; + break; + case BFRAME_PRE: + mb_code = 0x2; + mb_len = 4; + break; + case BFRAME_POST: + mb_code = 0x2; + mb_len = 3; + break; + case BFRAME_BIDIR: + mb_code = 0x2; + mb_len = 2; + break; + default: /* keep the compiler happy */ + mb_code = mb_len = 0; + break; + } + + CODE_ADD(c, frame == PFRAME ? 0x2 : 0x3, 13); + CODE_ADD(c, 0xffff, 16); + CODE_ADD(c, go->format == V4L2_PIX_FMT_MPEG2 ? 0x7 : 0x4, 4); + if (frame != PFRAME) + CODE_ADD(c, go->format == V4L2_PIX_FMT_MPEG2 ? 0x7 : 0x4, 4); + else + CODE_ADD(c, 0, 4); /* Is this supposed to be here?? */ + CODE_ADD(c, 0, 3); /* What is this?? */ + /* Byte-align with zeros */ + j = 8 - (CODE_LENGTH(c) % 8); + if (j != 8) + CODE_ADD(c, 0, j); + + if (go->format == V4L2_PIX_FMT_MPEG2) { + CODE_ADD(c, 0x1, 24); + CODE_ADD(c, 0xb5, 8); + CODE_ADD(c, 0x844, 12); + CODE_ADD(c, frame == PFRAME ? 0xff : 0x44, 8); + if (go->interlace_coding) { + CODE_ADD(c, pict_struct, 4); + if (go->dvd_mode) + CODE_ADD(c, 0x000, 11); + else + CODE_ADD(c, 0x200, 11); + } else { + CODE_ADD(c, 0x3, 4); + CODE_ADD(c, 0x20c, 11); + } + /* Byte-align with zeros */ + j = 8 - (CODE_LENGTH(c) % 8); + if (j != 8) + CODE_ADD(c, 0, j); + } + + for (i = 0; i < rows; ++i) { + CODE_ADD(c, 1, 24); + CODE_ADD(c, i + 1, 8); + CODE_ADD(c, 0x2, 6); + CODE_ADD(c, 0x1, 1); + CODE_ADD(c, mb_code, mb_len); + if (go->interlace_coding) { + CODE_ADD(c, 0x1, 2); + CODE_ADD(c, pict_struct == 1 ? 0x0 : 0x1, 1); + } + if (frame == BFRAME_BIDIR) { + CODE_ADD(c, 0x3, 2); + if (go->interlace_coding) + CODE_ADD(c, pict_struct == 1 ? 0x0 : 0x1, 1); + } + CODE_ADD(c, 0x3, 2); + for (j = (go->width >> 4) - 2; j >= 33; j -= 33) + CODE_ADD(c, 0x8, 11); + CODE_ADD(c, addrinctab[j][0], addrinctab[j][1]); + CODE_ADD(c, mb_code, mb_len); + if (go->interlace_coding) { + CODE_ADD(c, 0x1, 2); + CODE_ADD(c, pict_struct == 1 ? 0x0 : 0x1, 1); + } + if (frame == BFRAME_BIDIR) { + CODE_ADD(c, 0x3, 2); + if (go->interlace_coding) + CODE_ADD(c, pict_struct == 1 ? 0x0 : 0x1, 1); + } + CODE_ADD(c, 0x3, 2); + + /* Byte-align with zeros */ + j = 8 - (CODE_LENGTH(c) % 8); + if (j != 8) + CODE_ADD(c, 0, j); + } + + i = CODE_LENGTH(c) + 4 * 8; + buf[2] = 0x00; + buf[3] = 0x00; + buf[4] = 0x01; + buf[5] = 0x00; + return i; +} + +static int mpeg1_sequence_header(struct go7007 *go, unsigned char *buf, int ext) +{ + int i, aspect_ratio, picture_rate; + CODE_GEN(c, buf + 6); + + if (go->format == V4L2_PIX_FMT_MPEG1) { + switch (go->aspect_ratio) { + case GO7007_RATIO_4_3: + aspect_ratio = go->standard == GO7007_STD_NTSC ? 3 : 2; + break; + case GO7007_RATIO_16_9: + aspect_ratio = go->standard == GO7007_STD_NTSC ? 5 : 4; + break; + default: + aspect_ratio = 1; + break; + } + } else { + switch (go->aspect_ratio) { + case GO7007_RATIO_4_3: + aspect_ratio = 2; + break; + case GO7007_RATIO_16_9: + aspect_ratio = 3; + break; + default: + aspect_ratio = 1; + break; + } + } + switch (go->sensor_framerate) { + case 24000: + picture_rate = 1; + break; + case 24024: + picture_rate = 2; + break; + case 25025: + picture_rate = go->interlace_coding ? 6 : 3; + break; + case 30000: + picture_rate = go->interlace_coding ? 7 : 4; + break; + case 30030: + picture_rate = go->interlace_coding ? 8 : 5; + break; + default: + picture_rate = 5; /* 30 fps seems like a reasonable default */ + break; + } + + CODE_ADD(c, go->width, 12); + CODE_ADD(c, go->height, 12); + CODE_ADD(c, aspect_ratio, 4); + CODE_ADD(c, picture_rate, 4); + CODE_ADD(c, go->format == V4L2_PIX_FMT_MPEG2 ? 20000 : 0x3ffff, 18); + CODE_ADD(c, 1, 1); + CODE_ADD(c, go->format == V4L2_PIX_FMT_MPEG2 ? 112 : 20, 10); + CODE_ADD(c, 0, 3); + + /* Byte-align with zeros */ + i = 8 - (CODE_LENGTH(c) % 8); + if (i != 8) + CODE_ADD(c, 0, i); + + if (go->format == V4L2_PIX_FMT_MPEG2) { + CODE_ADD(c, 0x1, 24); + CODE_ADD(c, 0xb5, 8); + CODE_ADD(c, 0x148, 12); + if (go->interlace_coding) + CODE_ADD(c, 0x20001, 20); + else + CODE_ADD(c, 0xa0001, 20); + CODE_ADD(c, 0, 16); + + /* Byte-align with zeros */ + i = 8 - (CODE_LENGTH(c) % 8); + if (i != 8) + CODE_ADD(c, 0, i); + + if (ext) { + CODE_ADD(c, 0x1, 24); + CODE_ADD(c, 0xb52, 12); + CODE_ADD(c, go->standard == GO7007_STD_NTSC ? 2 : 1, 3); + CODE_ADD(c, 0x105, 9); + CODE_ADD(c, 0x505, 16); + CODE_ADD(c, go->width, 14); + CODE_ADD(c, 1, 1); + CODE_ADD(c, go->height, 14); + + /* Byte-align with zeros */ + i = 8 - (CODE_LENGTH(c) % 8); + if (i != 8) + CODE_ADD(c, 0, i); + } + } + + i = CODE_LENGTH(c) + 4 * 8; + buf[0] = i & 0xff; + buf[1] = i >> 8; + buf[2] = 0x00; + buf[3] = 0x00; + buf[4] = 0x01; + buf[5] = 0xb3; + return i; +} + +static int gen_mpeg1hdr_to_package(struct go7007 *go, + __le16 *code, int space, int *framelen) +{ + u8 *buf; + u16 mem = 0x3e00; + unsigned int addr = 0x19; + int i, off = 0, chunk; + + buf = kzalloc(5120, GFP_KERNEL); + if (buf == NULL) + return -1; + + framelen[0] = mpeg1_frame_header(go, buf, 0, 1, PFRAME); + if (go->interlace_coding) + framelen[0] += mpeg1_frame_header(go, buf + framelen[0] / 8, + 0, 2, PFRAME); + buf[0] = framelen[0] & 0xff; + buf[1] = framelen[0] >> 8; + i = 368; + framelen[1] = mpeg1_frame_header(go, buf + i, 0, 1, BFRAME_PRE); + if (go->interlace_coding) + framelen[1] += mpeg1_frame_header(go, buf + i + framelen[1] / 8, + 0, 2, BFRAME_PRE); + buf[i] = framelen[1] & 0xff; + buf[i + 1] = framelen[1] >> 8; + i += 1632; + framelen[2] = mpeg1_frame_header(go, buf + i, 0, 1, BFRAME_POST); + if (go->interlace_coding) + framelen[2] += mpeg1_frame_header(go, buf + i + framelen[2] / 8, + 0, 2, BFRAME_POST); + buf[i] = framelen[2] & 0xff; + buf[i + 1] = framelen[2] >> 8; + i += 1432; + framelen[3] = mpeg1_frame_header(go, buf + i, 0, 1, BFRAME_BIDIR); + if (go->interlace_coding) + framelen[3] += mpeg1_frame_header(go, buf + i + framelen[3] / 8, + 0, 2, BFRAME_BIDIR); + buf[i] = framelen[3] & 0xff; + buf[i + 1] = framelen[3] >> 8; + i += 1632 + 16; + mpeg1_sequence_header(go, buf + i, 0); + i += 40; + for (i = 0; i < 5120; i += chunk * 2) { + if (space - off < 32) { + off = -1; + goto done; + } + + code[off + 1] = __cpu_to_le16(0x8000 | mem); + + chunk = 28; + if (mem + chunk > 0x4000) + chunk = 0x4000 - mem; + if (i + 2 * chunk > 5120) + chunk = (5120 - i) / 2; + + if (chunk < 28) { + code[off] = __cpu_to_le16(0x4000 | chunk); + code[off + 31] = __cpu_to_le16(addr); + if (mem + chunk == 0x4000) { + mem = 0x3e00; + ++addr; + } + } else { + code[off] = __cpu_to_le16(0x1000 | 28); + code[off + 31] = 0; + mem += 28; + } + + memcpy(&code[off + 2], buf + i, chunk * 2); + off += 32; + } +done: + kfree(buf); + return off; +} + +static int vti_bitlen(struct go7007 *go) +{ + unsigned int i, max_time_incr = go->sensor_framerate / go->fps_scale; + + for (i = 31; (max_time_incr & ((1 << i) - 1)) == max_time_incr; --i) + ; + return i + 1; +} + +static int mpeg4_frame_header(struct go7007 *go, unsigned char *buf, + int modulo, enum mpeg_frame_type frame) +{ + int i; + CODE_GEN(c, buf + 6); + int mb_count = (go->width >> 4) * (go->height >> 4); + + CODE_ADD(c, frame == PFRAME ? 0x1 : 0x2, 2); + if (modulo) + CODE_ADD(c, 0x1, 1); + CODE_ADD(c, 0x1, 2); + CODE_ADD(c, 0, vti_bitlen(go)); + CODE_ADD(c, 0x3, 2); + if (frame == PFRAME) + CODE_ADD(c, 0, 1); + CODE_ADD(c, 0xc, 11); + if (frame != PFRAME) + CODE_ADD(c, 0x4, 3); + if (frame != BFRAME_EMPTY) { + for (i = 0; i < mb_count; ++i) { + switch (frame) { + case PFRAME: + CODE_ADD(c, 0x1, 1); + break; + case BFRAME_PRE: + CODE_ADD(c, 0x47, 8); + break; + case BFRAME_POST: + CODE_ADD(c, 0x27, 7); + break; + case BFRAME_BIDIR: + CODE_ADD(c, 0x5f, 8); + break; + case BFRAME_EMPTY: /* keep compiler quiet */ + break; + } + } + } + + /* Byte-align with a zero followed by ones */ + i = 8 - (CODE_LENGTH(c) % 8); + CODE_ADD(c, 0, 1); + CODE_ADD(c, (1 << (i - 1)) - 1, i - 1); + + i = CODE_LENGTH(c) + 4 * 8; + buf[0] = i & 0xff; + buf[1] = i >> 8; + buf[2] = 0x00; + buf[3] = 0x00; + buf[4] = 0x01; + buf[5] = 0xb6; + return i; +} + +static int mpeg4_sequence_header(struct go7007 *go, unsigned char *buf, int ext) +{ + const unsigned char head[] = { 0x00, 0x00, 0x01, 0xb0, go->pali, + 0x00, 0x00, 0x01, 0xb5, 0x09, + 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x01, 0x20, }; + int i, aspect_ratio; + int fps = go->sensor_framerate / go->fps_scale; + CODE_GEN(c, buf + 2 + sizeof(head)); + + switch (go->aspect_ratio) { + case GO7007_RATIO_4_3: + aspect_ratio = go->standard == GO7007_STD_NTSC ? 3 : 2; + break; + case GO7007_RATIO_16_9: + aspect_ratio = go->standard == GO7007_STD_NTSC ? 5 : 4; + break; + default: + aspect_ratio = 1; + break; + } + + memcpy(buf + 2, head, sizeof(head)); + CODE_ADD(c, 0x191, 17); + CODE_ADD(c, aspect_ratio, 4); + CODE_ADD(c, 0x1, 4); + CODE_ADD(c, fps, 16); + CODE_ADD(c, 0x3, 2); + CODE_ADD(c, 1001, vti_bitlen(go)); + CODE_ADD(c, 1, 1); + CODE_ADD(c, go->width, 13); + CODE_ADD(c, 1, 1); + CODE_ADD(c, go->height, 13); + CODE_ADD(c, 0x2830, 14); + + /* Byte-align */ + i = 8 - (CODE_LENGTH(c) % 8); + CODE_ADD(c, 0, 1); + CODE_ADD(c, (1 << (i - 1)) - 1, i - 1); + + i = CODE_LENGTH(c) + sizeof(head) * 8; + buf[0] = i & 0xff; + buf[1] = i >> 8; + return i; +} + +static int gen_mpeg4hdr_to_package(struct go7007 *go, + __le16 *code, int space, int *framelen) +{ + u8 *buf; + u16 mem = 0x3e00; + unsigned int addr = 0x19; + int i, off = 0, chunk; + + buf = kzalloc(5120, GFP_KERNEL); + if (buf == NULL) + return -1; + + framelen[0] = mpeg4_frame_header(go, buf, 0, PFRAME); + i = 368; + framelen[1] = mpeg4_frame_header(go, buf + i, 0, BFRAME_PRE); + i += 1632; + framelen[2] = mpeg4_frame_header(go, buf + i, 0, BFRAME_POST); + i += 1432; + framelen[3] = mpeg4_frame_header(go, buf + i, 0, BFRAME_BIDIR); + i += 1632; + mpeg4_frame_header(go, buf + i, 0, BFRAME_EMPTY); + i += 16; + mpeg4_sequence_header(go, buf + i, 0); + i += 40; + for (i = 0; i < 5120; i += chunk * 2) { + if (space - off < 32) { + off = -1; + goto done; + } + + code[off + 1] = __cpu_to_le16(0x8000 | mem); + + chunk = 28; + if (mem + chunk > 0x4000) + chunk = 0x4000 - mem; + if (i + 2 * chunk > 5120) + chunk = (5120 - i) / 2; + + if (chunk < 28) { + code[off] = __cpu_to_le16(0x4000 | chunk); + code[off + 31] = __cpu_to_le16(addr); + if (mem + chunk == 0x4000) { + mem = 0x3e00; + ++addr; + } + } else { + code[off] = __cpu_to_le16(0x1000 | 28); + code[off + 31] = 0; + mem += 28; + } + + memcpy(&code[off + 2], buf + i, chunk * 2); + off += 32; + } + mem = 0x3e00; + addr = go->ipb ? 0x14f9 : 0x0af9; + memset(buf, 0, 5120); + framelen[4] = mpeg4_frame_header(go, buf, 1, PFRAME); + i = 368; + framelen[5] = mpeg4_frame_header(go, buf + i, 1, BFRAME_PRE); + i += 1632; + framelen[6] = mpeg4_frame_header(go, buf + i, 1, BFRAME_POST); + i += 1432; + framelen[7] = mpeg4_frame_header(go, buf + i, 1, BFRAME_BIDIR); + i += 1632; + mpeg4_frame_header(go, buf + i, 1, BFRAME_EMPTY); + i += 16; + for (i = 0; i < 5120; i += chunk * 2) { + if (space - off < 32) { + off = -1; + goto done; + } + + code[off + 1] = __cpu_to_le16(0x8000 | mem); + + chunk = 28; + if (mem + chunk > 0x4000) + chunk = 0x4000 - mem; + if (i + 2 * chunk > 5120) + chunk = (5120 - i) / 2; + + if (chunk < 28) { + code[off] = __cpu_to_le16(0x4000 | chunk); + code[off + 31] = __cpu_to_le16(addr); + if (mem + chunk == 0x4000) { + mem = 0x3e00; + ++addr; + } + } else { + code[off] = __cpu_to_le16(0x1000 | 28); + code[off + 31] = 0; + mem += 28; + } + + memcpy(&code[off + 2], buf + i, chunk * 2); + off += 32; + } +done: + kfree(buf); + return off; +} + +static int brctrl_to_package(struct go7007 *go, + __le16 *code, int space, int *framelen) +{ + int converge_speed = 0; + int lambda = (go->format == V4L2_PIX_FMT_MJPEG || go->dvd_mode) ? + 100 : 0; + int peak_rate = 6 * go->bitrate / 5; + int vbv_buffer = go->format == V4L2_PIX_FMT_MJPEG ? + go->bitrate : + (go->dvd_mode ? 900000 : peak_rate); + int fps = go->sensor_framerate / go->fps_scale; + int q = 0; + /* Bizarre math below depends on rounding errors in division */ + u32 sgop_expt_addr = go->bitrate / 32 * (go->ipb ? 3 : 1) * 1001 / fps; + u32 sgop_peak_addr = peak_rate / 32 * 1001 / fps; + u32 total_expt_addr = go->bitrate / 32 * 1000 / fps * (fps / 1000); + u32 vbv_alert_addr = vbv_buffer * 3 / (4 * 32); + u32 cplx[] = { + q > 0 ? sgop_expt_addr * q : + 2 * go->width * go->height * (go->ipb ? 6 : 4) / 32, + q > 0 ? sgop_expt_addr * q : + 2 * go->width * go->height * (go->ipb ? 6 : 4) / 32, + q > 0 ? sgop_expt_addr * q : + 2 * go->width * go->height * (go->ipb ? 6 : 4) / 32, + q > 0 ? sgop_expt_addr * q : + 2 * go->width * go->height * (go->ipb ? 6 : 4) / 32, + }; + u32 calc_q = q > 0 ? q : cplx[0] / sgop_expt_addr; + u16 pack[] = { + 0x200e, 0x0000, + 0xBF20, go->ipb ? converge_speed_ipb[converge_speed] + : converge_speed_ip[converge_speed], + 0xBF21, go->ipb ? 2 : 0, + 0xBF22, go->ipb ? LAMBDA_table[0][lambda / 2 + 50] + : 32767, + 0xBF23, go->ipb ? LAMBDA_table[1][lambda] : 32767, + 0xBF24, 32767, + 0xBF25, lambda > 99 ? 32767 : LAMBDA_table[3][lambda], + 0xBF26, sgop_expt_addr & 0x0000FFFF, + 0xBF27, sgop_expt_addr >> 16, + 0xBF28, sgop_peak_addr & 0x0000FFFF, + 0xBF29, sgop_peak_addr >> 16, + 0xBF2A, vbv_alert_addr & 0x0000FFFF, + 0xBF2B, vbv_alert_addr >> 16, + 0xBF2C, 0, + 0xBF2D, 0, + 0, 0, + + 0x200e, 0x0000, + 0xBF2E, vbv_alert_addr & 0x0000FFFF, + 0xBF2F, vbv_alert_addr >> 16, + 0xBF30, cplx[0] & 0x0000FFFF, + 0xBF31, cplx[0] >> 16, + 0xBF32, cplx[1] & 0x0000FFFF, + 0xBF33, cplx[1] >> 16, + 0xBF34, cplx[2] & 0x0000FFFF, + 0xBF35, cplx[2] >> 16, + 0xBF36, cplx[3] & 0x0000FFFF, + 0xBF37, cplx[3] >> 16, + 0xBF38, 0, + 0xBF39, 0, + 0xBF3A, total_expt_addr & 0x0000FFFF, + 0xBF3B, total_expt_addr >> 16, + 0, 0, + + 0x200e, 0x0000, + 0xBF3C, total_expt_addr & 0x0000FFFF, + 0xBF3D, total_expt_addr >> 16, + 0xBF3E, 0, + 0xBF3F, 0, + 0xBF48, 0, + 0xBF49, 0, + 0xBF4A, calc_q < 4 ? 4 : (calc_q > 124 ? 124 : calc_q), + 0xBF4B, 4, + 0xBF4C, 0, + 0xBF4D, 0, + 0xBF4E, 0, + 0xBF4F, 0, + 0xBF50, 0, + 0xBF51, 0, + 0, 0, + + 0x200e, 0x0000, + 0xBF40, sgop_expt_addr & 0x0000FFFF, + 0xBF41, sgop_expt_addr >> 16, + 0xBF42, 0, + 0xBF43, 0, + 0xBF44, 0, + 0xBF45, 0, + 0xBF46, (go->width >> 4) * (go->height >> 4), + 0xBF47, 0, + 0xBF64, 0, + 0xBF65, 0, + 0xBF18, framelen[4], + 0xBF19, framelen[5], + 0xBF1A, framelen[6], + 0xBF1B, framelen[7], + 0, 0, + +#if 0 + /* Remove once we don't care about matching */ + 0x200e, 0x0000, + 0xBF56, 4, + 0xBF57, 0, + 0xBF58, 5, + 0xBF59, 0, + 0xBF5A, 6, + 0xBF5B, 0, + 0xBF5C, 8, + 0xBF5D, 0, + 0xBF5E, 1, + 0xBF5F, 0, + 0xBF60, 1, + 0xBF61, 0, + 0xBF62, 0, + 0xBF63, 0, + 0, 0, +#else + 0x2008, 0x0000, + 0xBF56, 4, + 0xBF57, 0, + 0xBF58, 5, + 0xBF59, 0, + 0xBF5A, 6, + 0xBF5B, 0, + 0xBF5C, 8, + 0xBF5D, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, +#endif + + 0x200e, 0x0000, + 0xBF10, 0, + 0xBF11, 0, + 0xBF12, 0, + 0xBF13, 0, + 0xBF14, 0, + 0xBF15, 0, + 0xBF16, 0, + 0xBF17, 0, + 0xBF7E, 0, + 0xBF7F, 1, + 0xBF52, framelen[0], + 0xBF53, framelen[1], + 0xBF54, framelen[2], + 0xBF55, framelen[3], + 0, 0, + }; + + return copy_packages(code, pack, 6, space); +} + +static int config_package(struct go7007 *go, __le16 *code, int space) +{ + int fps = go->sensor_framerate / go->fps_scale / 1000; + int rows = go->interlace_coding ? go->height / 32 : go->height / 16; + int brc_window_size = fps; + int q_min = 2, q_max = 31; + int THACCoeffSet0 = 0; + u16 pack[] = { + 0x200e, 0x0000, + 0xc002, 0x14b4, + 0xc003, 0x28b4, + 0xc004, 0x3c5a, + 0xdc05, 0x2a77, + 0xc6c3, go->format == V4L2_PIX_FMT_MPEG4 ? 0 : + (go->format == V4L2_PIX_FMT_H263 ? 0 : 1), + 0xc680, go->format == V4L2_PIX_FMT_MPEG4 ? 0xf1 : + (go->format == V4L2_PIX_FMT_H263 ? 0x61 : + 0xd3), + 0xc780, 0x0140, + 0xe009, 0x0001, + 0xc60f, 0x0008, + 0xd4ff, 0x0002, + 0xe403, 2340, + 0xe406, 75, + 0xd411, 0x0001, + 0xd410, 0xa1d6, + 0x0001, 0x2801, + + 0x200d, 0x0000, + 0xe402, 0x018b, + 0xe401, 0x8b01, + 0xd472, (go->board_info->sensor_flags & + GO7007_SENSOR_TV) && + (!go->interlace_coding) ? + 0x01b0 : 0x0170, + 0xd475, (go->board_info->sensor_flags & + GO7007_SENSOR_TV) && + (!go->interlace_coding) ? + 0x0008 : 0x0009, + 0xc404, go->interlace_coding ? 0x44 : + (go->format == V4L2_PIX_FMT_MPEG4 ? 0x11 : + (go->format == V4L2_PIX_FMT_MPEG1 ? 0x02 : + (go->format == V4L2_PIX_FMT_MPEG2 ? 0x04 : + (go->format == V4L2_PIX_FMT_H263 ? 0x08 : + 0x20)))), + 0xbf0a, (go->format == V4L2_PIX_FMT_MPEG4 ? 8 : + (go->format == V4L2_PIX_FMT_MPEG1 ? 1 : + (go->format == V4L2_PIX_FMT_MPEG2 ? 2 : + (go->format == V4L2_PIX_FMT_H263 ? 4 : 16)))) | + ((go->repeat_seqhead ? 1 : 0) << 6) | + ((go->dvd_mode ? 1 : 0) << 9) | + ((go->gop_header_enable ? 1 : 0) << 10), + 0xbf0b, 0, + 0xdd5a, go->ipb ? 0x14 : 0x0a, + 0xbf0c, 0, + 0xbf0d, 0, + 0xc683, THACCoeffSet0, + 0xc40a, (go->width << 4) | rows, + 0xe01a, go->board_info->hpi_buffer_cap, + 0, 0, + 0, 0, + + 0x2008, 0, + 0xe402, 0x88, + 0xe401, 0x8f01, + 0xbf6a, 0, + 0xbf6b, 0, + 0xbf6c, 0, + 0xbf6d, 0, + 0xbf6e, 0, + 0xbf6f, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + + 0x200e, 0, + 0xbf66, brc_window_size, + 0xbf67, 0, + 0xbf68, q_min, + 0xbf69, q_max, + 0xbfe0, 0, + 0xbfe1, 0, + 0xbfe2, 0, + 0xbfe3, go->ipb ? 3 : 1, + 0xc031, go->board_info->sensor_flags & + GO7007_SENSOR_VBI ? 1 : 0, + 0xc01c, 0x1f, + 0xdd8c, 0x15, + 0xdd94, 0x15, + 0xdd88, go->ipb ? 0x1401 : 0x0a01, + 0xdd90, go->ipb ? 0x1401 : 0x0a01, + 0, 0, + + 0x200e, 0, + 0xbfe4, 0, + 0xbfe5, 0, + 0xbfe6, 0, + 0xbfe7, fps << 8, + 0xbfe8, 0x3a00, + 0xbfe9, 0, + 0xbfea, 0, + 0xbfeb, 0, + 0xbfec, (go->interlace_coding ? 1 << 15 : 0) | + (go->modet_enable ? 0xa : 0) | + (go->board_info->sensor_flags & + GO7007_SENSOR_VBI ? 1 : 0), + 0xbfed, 0, + 0xbfee, 0, + 0xbfef, 0, + 0xbff0, go->board_info->sensor_flags & + GO7007_SENSOR_TV ? 0xf060 : 0xb060, + 0xbff1, 0, + 0, 0, + }; + + return copy_packages(code, pack, 5, space); +} + +static int seqhead_to_package(struct go7007 *go, __le16 *code, int space, + int (*sequence_header_func)(struct go7007 *go, + unsigned char *buf, int ext)) +{ + int vop_time_increment_bitlength = vti_bitlen(go); + int fps = go->sensor_framerate / go->fps_scale * + (go->interlace_coding ? 2 : 1); + unsigned char buf[40] = { }; + int len = sequence_header_func(go, buf, 1); + u16 pack[] = { + 0x2006, 0, + 0xbf08, fps, + 0xbf09, 0, + 0xbff2, vop_time_increment_bitlength, + 0xbff3, (1 << vop_time_increment_bitlength) - 1, + 0xbfe6, 0, + 0xbfe7, (fps / 1000) << 8, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + + 0x2007, 0, + 0xc800, buf[2] << 8 | buf[3], + 0xc801, buf[4] << 8 | buf[5], + 0xc802, buf[6] << 8 | buf[7], + 0xc803, buf[8] << 8 | buf[9], + 0xc406, 64, + 0xc407, len - 64, + 0xc61b, 1, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + + 0x200e, 0, + 0xc808, buf[10] << 8 | buf[11], + 0xc809, buf[12] << 8 | buf[13], + 0xc80a, buf[14] << 8 | buf[15], + 0xc80b, buf[16] << 8 | buf[17], + 0xc80c, buf[18] << 8 | buf[19], + 0xc80d, buf[20] << 8 | buf[21], + 0xc80e, buf[22] << 8 | buf[23], + 0xc80f, buf[24] << 8 | buf[25], + 0xc810, buf[26] << 8 | buf[27], + 0xc811, buf[28] << 8 | buf[29], + 0xc812, buf[30] << 8 | buf[31], + 0xc813, buf[32] << 8 | buf[33], + 0xc814, buf[34] << 8 | buf[35], + 0xc815, buf[36] << 8 | buf[37], + 0, 0, + 0, 0, + 0, 0, + }; + + return copy_packages(code, pack, 3, space); +} + +static int relative_prime(int big, int little) +{ + int remainder; + + while (little != 0) { + remainder = big % little; + big = little; + little = remainder; + } + return big; +} + +static int avsync_to_package(struct go7007 *go, __le16 *code, int space) +{ + int arate = go->board_info->audio_rate * 1001 * go->fps_scale; + int ratio = arate / go->sensor_framerate; + int adjratio = ratio * 215 / 100; + int rprime = relative_prime(go->sensor_framerate, + arate % go->sensor_framerate); + int f1 = (arate % go->sensor_framerate) / rprime; + int f2 = (go->sensor_framerate - arate % go->sensor_framerate) / rprime; + u16 pack[] = { + 0x200e, 0, + 0xbf98, (u16)((-adjratio) & 0xffff), + 0xbf99, (u16)((-adjratio) >> 16), + 0xbf92, 0, + 0xbf93, 0, + 0xbff4, f1 > f2 ? f1 : f2, + 0xbff5, f1 < f2 ? f1 : f2, + 0xbff6, f1 < f2 ? ratio : ratio + 1, + 0xbff7, f1 > f2 ? ratio : ratio + 1, + 0xbff8, 0, + 0xbff9, 0, + 0xbffa, adjratio & 0xffff, + 0xbffb, adjratio >> 16, + 0xbf94, 0, + 0xbf95, 0, + 0, 0, + }; + + return copy_packages(code, pack, 1, space); +} + +static int final_package(struct go7007 *go, __le16 *code, int space) +{ + int rows = go->interlace_coding ? go->height / 32 : go->height / 16; + u16 pack[] = { + 0x8000, + 0, + 0, + 0, + 0, + 0, + 0, + 2, + ((go->board_info->sensor_flags & GO7007_SENSOR_TV) && + (!go->interlace_coding) ? + (1 << 14) | (1 << 9) : 0) | + ((go->encoder_subsample ? 1 : 0) << 8) | + (go->board_info->sensor_flags & + GO7007_SENSOR_CONFIG_MASK), + ((go->encoder_v_halve ? 1 : 0) << 14) | + (go->encoder_v_halve ? rows << 9 : rows << 8) | + (go->encoder_h_halve ? 1 << 6 : 0) | + (go->encoder_h_halve ? go->width >> 3 : go->width >> 4), + (1 << 15) | (go->encoder_v_offset << 6) | + (1 << 7) | (go->encoder_h_offset >> 2), + (1 << 6), + 0, + 0, + ((go->fps_scale - 1) << 8) | + (go->board_info->sensor_flags & GO7007_SENSOR_TV ? + (1 << 7) : 0) | + 0x41, + go->ipb ? 0xd4c : 0x36b, + (rows << 8) | (go->width >> 4), + go->format == V4L2_PIX_FMT_MPEG4 ? 0x0404 : 0, + (1 << 15) | ((go->interlace_coding ? 1 : 0) << 13) | + ((go->closed_gop ? 1 : 0) << 12) | + ((go->format == V4L2_PIX_FMT_MPEG4 ? 1 : 0) << 11) | + /* (1 << 9) | */ + ((go->ipb ? 3 : 0) << 7) | + ((go->modet_enable ? 1 : 0) << 2) | + ((go->dvd_mode ? 1 : 0) << 1) | 1, + (go->format == V4L2_PIX_FMT_MPEG1 ? 0x89a0 : + (go->format == V4L2_PIX_FMT_MPEG2 ? 0x89a0 : + (go->format == V4L2_PIX_FMT_MJPEG ? 0x89a0 : + (go->format == V4L2_PIX_FMT_MPEG4 ? 0x8920 : + (go->format == V4L2_PIX_FMT_H263 ? 0x8920 : 0))))), + go->ipb ? 0x1f15 : 0x1f0b, + go->ipb ? 0x0015 : 0x000b, + go->ipb ? 0xa800 : 0x5800, + 0xffff, + 0x0020 + 0x034b * 0, + 0x0020 + 0x034b * 1, + 0x0020 + 0x034b * 2, + 0x0020 + 0x034b * 3, + 0x0020 + 0x034b * 4, + 0x0020 + 0x034b * 5, + go->ipb ? (go->gop_size / 3) : go->gop_size, + (go->height >> 4) * (go->width >> 4) * 110 / 100, + }; + + return copy_packages(code, pack, 1, space); +} + +static int audio_to_package(struct go7007 *go, __le16 *code, int space) +{ + int clock_config = ((go->board_info->audio_flags & + GO7007_AUDIO_I2S_MASTER ? 1 : 0) << 11) | + ((go->board_info->audio_flags & + GO7007_AUDIO_OKI_MODE ? 1 : 0) << 8) | + (((go->board_info->audio_bclk_div / 4) - 1) << 4) | + (go->board_info->audio_main_div - 1); + u16 pack[] = { + 0x200d, 0, + 0x9002, 0, + 0x9002, 0, + 0x9031, 0, + 0x9032, 0, + 0x9033, 0, + 0x9034, 0, + 0x9035, 0, + 0x9036, 0, + 0x9037, 0, + 0x9040, 0, + 0x9000, clock_config, + 0x9001, (go->board_info->audio_flags & 0xffff) | + (1 << 9), + 0x9000, ((go->board_info->audio_flags & + GO7007_AUDIO_I2S_MASTER ? + 1 : 0) << 10) | + clock_config, + 0, 0, + 0, 0, + 0x2005, 0, + 0x9041, 0, + 0x9042, 256, + 0x9043, 0, + 0x9044, 16, + 0x9045, 16, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + }; + + return copy_packages(code, pack, 2, space); +} + +static int modet_to_package(struct go7007 *go, __le16 *code, int space) +{ + int ret, mb, i, addr, cnt = 0; + u16 pack[32]; + u16 thresholds[] = { + 0x200e, 0, + 0xbf82, go->modet[0].pixel_threshold, + 0xbf83, go->modet[1].pixel_threshold, + 0xbf84, go->modet[2].pixel_threshold, + 0xbf85, go->modet[3].pixel_threshold, + 0xbf86, go->modet[0].motion_threshold, + 0xbf87, go->modet[1].motion_threshold, + 0xbf88, go->modet[2].motion_threshold, + 0xbf89, go->modet[3].motion_threshold, + 0xbf8a, go->modet[0].mb_threshold, + 0xbf8b, go->modet[1].mb_threshold, + 0xbf8c, go->modet[2].mb_threshold, + 0xbf8d, go->modet[3].mb_threshold, + 0xbf8e, 0, + 0xbf8f, 0, + 0, 0, + }; + + ret = copy_packages(code, thresholds, 1, space); + if (ret < 0) + return -1; + cnt += ret; + + addr = 0xbac0; + memset(pack, 0, 64); + i = 0; + for (mb = 0; mb < 1624; ++mb) { + pack[i * 2 + 3] <<= 2; + pack[i * 2 + 3] |= go->modet_map[mb]; + if (mb % 8 != 7) + continue; + pack[i * 2 + 2] = addr++; + ++i; + if (i == 10 || mb == 1623) { + pack[0] = 0x2000 | i; + ret = copy_packages(code + cnt, pack, 1, space - cnt); + if (ret < 0) + return -1; + cnt += ret; + i = 0; + memset(pack, 0, 64); + } + pack[i * 2 + 3] = 0; + } + + memset(pack, 0, 64); + i = 0; + for (addr = 0xbb90; addr < 0xbbfa; ++addr) { + pack[i * 2 + 2] = addr; + pack[i * 2 + 3] = 0; + ++i; + if (i == 10 || addr == 0xbbf9) { + pack[0] = 0x2000 | i; + ret = copy_packages(code + cnt, pack, 1, space - cnt); + if (ret < 0) + return -1; + cnt += ret; + i = 0; + memset(pack, 0, 64); + } + } + return cnt; +} + +static int do_special(struct go7007 *go, u16 type, __le16 *code, int space, + int *framelen) +{ + switch (type) { + case SPECIAL_FRM_HEAD: + switch (go->format) { + case V4L2_PIX_FMT_MJPEG: + return gen_mjpeghdr_to_package(go, code, space); + case V4L2_PIX_FMT_MPEG1: + case V4L2_PIX_FMT_MPEG2: + return gen_mpeg1hdr_to_package(go, code, space, + framelen); + case V4L2_PIX_FMT_MPEG4: + return gen_mpeg4hdr_to_package(go, code, space, + framelen); + } + case SPECIAL_BRC_CTRL: + return brctrl_to_package(go, code, space, framelen); + case SPECIAL_CONFIG: + return config_package(go, code, space); + case SPECIAL_SEQHEAD: + switch (go->format) { + case V4L2_PIX_FMT_MPEG1: + case V4L2_PIX_FMT_MPEG2: + return seqhead_to_package(go, code, space, + mpeg1_sequence_header); + case V4L2_PIX_FMT_MPEG4: + return seqhead_to_package(go, code, space, + mpeg4_sequence_header); + default: + return 0; + } + case SPECIAL_AV_SYNC: + return avsync_to_package(go, code, space); + case SPECIAL_FINAL: + return final_package(go, code, space); + case SPECIAL_AUDIO: + return audio_to_package(go, code, space); + case SPECIAL_MODET: + return modet_to_package(go, code, space); + } + dev_err(go->dev, + "firmware file contains unsupported feature %04x\n", type); + return -1; +} + +int go7007_construct_fw_image(struct go7007 *go, u8 **fw, int *fwlen) +{ + const struct firmware *fw_entry; + __le16 *code, *src; + int framelen[8] = { }; /* holds the lengths of empty frame templates */ + int codespace = 64 * 1024, i = 0, srclen, chunk_len, chunk_flags; + int mode_flag; + int ret; + + switch (go->format) { + case V4L2_PIX_FMT_MJPEG: + mode_flag = FLAG_MODE_MJPEG; + break; + case V4L2_PIX_FMT_MPEG1: + mode_flag = FLAG_MODE_MPEG1; + break; + case V4L2_PIX_FMT_MPEG2: + mode_flag = FLAG_MODE_MPEG2; + break; + case V4L2_PIX_FMT_MPEG4: + mode_flag = FLAG_MODE_MPEG4; + break; + default: + return -1; + } + if (request_firmware(&fw_entry, GO7007_FW_NAME, go->dev)) { + dev_err(go->dev, + "unable to load firmware from file \"%s\"\n", + GO7007_FW_NAME); + return -1; + } + code = kzalloc(codespace * 2, GFP_KERNEL); + if (code == NULL) + goto fw_failed; + + src = (__le16 *)fw_entry->data; + srclen = fw_entry->size / 2; + while (srclen >= 2) { + chunk_flags = __le16_to_cpu(src[0]); + chunk_len = __le16_to_cpu(src[1]); + if (chunk_len + 2 > srclen) { + dev_err(go->dev, + "firmware file \"%s\" appears to be corrupted\n", + GO7007_FW_NAME); + goto fw_failed; + } + if (chunk_flags & mode_flag) { + if (chunk_flags & FLAG_SPECIAL) { + ret = do_special(go, __le16_to_cpu(src[2]), + &code[i], codespace - i, framelen); + if (ret < 0) { + dev_err(go->dev, + "insufficient memory for firmware construction\n"); + goto fw_failed; + } + i += ret; + } else { + if (codespace - i < chunk_len) { + dev_err(go->dev, + "insufficient memory for firmware construction\n"); + goto fw_failed; + } + memcpy(&code[i], &src[2], chunk_len * 2); + i += chunk_len; + } + } + srclen -= chunk_len + 2; + src += chunk_len + 2; + } + release_firmware(fw_entry); + *fw = (u8 *)code; + *fwlen = i * 2; + return 0; + +fw_failed: + kfree(code); + release_firmware(fw_entry); + return -1; +} + +MODULE_FIRMWARE(GO7007_FW_NAME); diff --git a/drivers/staging/media/go7007/go7007-i2c.c b/drivers/staging/media/go7007/go7007-i2c.c new file mode 100644 index 00000000000..4cf4c0d6508 --- /dev/null +++ b/drivers/staging/media/go7007/go7007-i2c.c @@ -0,0 +1,222 @@ +/* + * Copyright (C) 2005-2006 Micronas USA Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License (Version 2) as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA. + */ + +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/sched.h> +#include <linux/list.h> +#include <linux/unistd.h> +#include <linux/time.h> +#include <linux/device.h> +#include <linux/i2c.h> +#include <linux/mutex.h> +#include <linux/uaccess.h> + +#include "go7007-priv.h" + +/********************* Driver for on-board I2C adapter *********************/ + +/* #define GO7007_I2C_DEBUG */ + +#define SPI_I2C_ADDR_BASE 0x1400 +#define STATUS_REG_ADDR (SPI_I2C_ADDR_BASE + 0x2) +#define I2C_CTRL_REG_ADDR (SPI_I2C_ADDR_BASE + 0x6) +#define I2C_DEV_UP_ADDR_REG_ADDR (SPI_I2C_ADDR_BASE + 0x7) +#define I2C_LO_ADDR_REG_ADDR (SPI_I2C_ADDR_BASE + 0x8) +#define I2C_DATA_REG_ADDR (SPI_I2C_ADDR_BASE + 0x9) +#define I2C_CLKFREQ_REG_ADDR (SPI_I2C_ADDR_BASE + 0xa) + +#define I2C_STATE_MASK 0x0007 +#define I2C_READ_READY_MASK 0x0008 + +/* There is only one I2C port on the TW2804 that feeds all four GO7007 VIPs + * on the Adlink PCI-MPG24, so access is shared between all of them. */ +static DEFINE_MUTEX(adlink_mpg24_i2c_lock); + +static int go7007_i2c_xfer(struct go7007 *go, u16 addr, int read, + u16 command, int flags, u8 *data) +{ + int i, ret = -EIO; + u16 val; + + if (go->status == STATUS_SHUTDOWN) + return -ENODEV; + +#ifdef GO7007_I2C_DEBUG + if (read) + dev_dbg(go->dev, "go7007-i2c: reading 0x%02x on 0x%02x\n", + command, addr); + else + dev_dbg(go->dev, + "go7007-i2c: writing 0x%02x to 0x%02x on 0x%02x\n", + *data, command, addr); +#endif + + mutex_lock(&go->hw_lock); + + if (go->board_id == GO7007_BOARDID_ADLINK_MPG24) { + /* Bridge the I2C port on this GO7007 to the shared bus */ + mutex_lock(&adlink_mpg24_i2c_lock); + go7007_write_addr(go, 0x3c82, 0x0020); + } + + /* Wait for I2C adapter to be ready */ + for (i = 0; i < 10; ++i) { + if (go7007_read_addr(go, STATUS_REG_ADDR, &val) < 0) + goto i2c_done; + if (!(val & I2C_STATE_MASK)) + break; + msleep(100); + } + if (i == 10) { + dev_err(go->dev, "go7007-i2c: I2C adapter is hung\n"); + goto i2c_done; + } + + /* Set target register (command) */ + go7007_write_addr(go, I2C_CTRL_REG_ADDR, flags); + go7007_write_addr(go, I2C_LO_ADDR_REG_ADDR, command); + + /* If we're writing, send the data and target address and we're done */ + if (!read) { + go7007_write_addr(go, I2C_DATA_REG_ADDR, *data); + go7007_write_addr(go, I2C_DEV_UP_ADDR_REG_ADDR, + (addr << 9) | (command >> 8)); + ret = 0; + goto i2c_done; + } + + /* Otherwise, we're reading. First clear i2c_rx_data_rdy. */ + if (go7007_read_addr(go, I2C_DATA_REG_ADDR, &val) < 0) + goto i2c_done; + + /* Send the target address plus read flag */ + go7007_write_addr(go, I2C_DEV_UP_ADDR_REG_ADDR, + (addr << 9) | 0x0100 | (command >> 8)); + + /* Wait for i2c_rx_data_rdy */ + for (i = 0; i < 10; ++i) { + if (go7007_read_addr(go, STATUS_REG_ADDR, &val) < 0) + goto i2c_done; + if (val & I2C_READ_READY_MASK) + break; + msleep(100); + } + if (i == 10) { + dev_err(go->dev, "go7007-i2c: I2C adapter is hung\n"); + goto i2c_done; + } + + /* Retrieve the read byte */ + if (go7007_read_addr(go, I2C_DATA_REG_ADDR, &val) < 0) + goto i2c_done; + *data = val; + ret = 0; + +i2c_done: + if (go->board_id == GO7007_BOARDID_ADLINK_MPG24) { + /* Isolate the I2C port on this GO7007 from the shared bus */ + go7007_write_addr(go, 0x3c82, 0x0000); + mutex_unlock(&adlink_mpg24_i2c_lock); + } + mutex_unlock(&go->hw_lock); + return ret; +} + +static int go7007_smbus_xfer(struct i2c_adapter *adapter, u16 addr, + unsigned short flags, char read_write, + u8 command, int size, union i2c_smbus_data *data) +{ + struct go7007 *go = i2c_get_adapdata(adapter); + + if (size != I2C_SMBUS_BYTE_DATA) + return -EIO; + return go7007_i2c_xfer(go, addr, read_write == I2C_SMBUS_READ, command, + flags & I2C_CLIENT_SCCB ? 0x10 : 0x00, &data->byte); +} + +/* VERY LIMITED I2C master xfer function -- only needed because the + * SMBus functions only support 8-bit commands and the SAA7135 uses + * 16-bit commands. The I2C interface on the GO7007, as limited as + * it is, does support this mode. */ + +static int go7007_i2c_master_xfer(struct i2c_adapter *adapter, + struct i2c_msg msgs[], int num) +{ + struct go7007 *go = i2c_get_adapdata(adapter); + int i; + + for (i = 0; i < num; ++i) { + /* We can only do two things here -- write three bytes, or + * write two bytes and read one byte. */ + if (msgs[i].len == 2) { + if (i + 1 == num || msgs[i].addr != msgs[i + 1].addr || + (msgs[i].flags & I2C_M_RD) || + !(msgs[i + 1].flags & I2C_M_RD) || + msgs[i + 1].len != 1) + return -EIO; + if (go7007_i2c_xfer(go, msgs[i].addr, 1, + (msgs[i].buf[0] << 8) | msgs[i].buf[1], + 0x01, &msgs[i + 1].buf[0]) < 0) + return -EIO; + ++i; + } else if (msgs[i].len == 3) { + if (msgs[i].flags & I2C_M_RD) + return -EIO; + if (msgs[i].len != 3) + return -EIO; + if (go7007_i2c_xfer(go, msgs[i].addr, 0, + (msgs[i].buf[0] << 8) | msgs[i].buf[1], + 0x01, &msgs[i].buf[2]) < 0) + return -EIO; + } else + return -EIO; + } + + return num; +} + +static u32 go7007_functionality(struct i2c_adapter *adapter) +{ + return I2C_FUNC_SMBUS_BYTE_DATA; +} + +static struct i2c_algorithm go7007_algo = { + .smbus_xfer = go7007_smbus_xfer, + .master_xfer = go7007_i2c_master_xfer, + .functionality = go7007_functionality, +}; + +static struct i2c_adapter go7007_adap_templ = { + .owner = THIS_MODULE, + .name = "WIS GO7007SB", + .algo = &go7007_algo, +}; + +int go7007_i2c_init(struct go7007 *go) +{ + memcpy(&go->i2c_adapter, &go7007_adap_templ, + sizeof(go7007_adap_templ)); + go->i2c_adapter.dev.parent = go->dev; + i2c_set_adapdata(&go->i2c_adapter, go); + if (i2c_add_adapter(&go->i2c_adapter) < 0) { + dev_err(go->dev, + "go7007-i2c: error: i2c_add_adapter failed\n"); + return -1; + } + return 0; +} diff --git a/drivers/staging/media/go7007/go7007-loader.c b/drivers/staging/media/go7007/go7007-loader.c new file mode 100644 index 00000000000..491d0e697f5 --- /dev/null +++ b/drivers/staging/media/go7007/go7007-loader.c @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2008 Sensoray Company Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License (Version 2) as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA. + */ + +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/usb.h> +#include <linux/firmware.h> +#include <cypress_firmware.h> + +struct fw_config { + u16 vendor; + u16 product; + const char * const fw_name1; + const char * const fw_name2; +}; + +static struct fw_config fw_configs[] = { + { 0x1943, 0xa250, "go7007/s2250-1.fw", "go7007/s2250-2.fw" }, + { 0x093b, 0xa002, "go7007/px-m402u.fw", NULL }, + { 0x093b, 0xa004, "go7007/px-tv402u.fw", NULL }, + { 0x0eb1, 0x6666, "go7007/lr192.fw", NULL }, + { 0x0eb1, 0x6668, "go7007/wis-startrek.fw", NULL }, + { 0, 0, NULL, NULL } +}; +MODULE_FIRMWARE("go7007/s2250-1.fw"); +MODULE_FIRMWARE("go7007/s2250-2.fw"); +MODULE_FIRMWARE("go7007/px-m402u.fw"); +MODULE_FIRMWARE("go7007/px-tv402u.fw"); +MODULE_FIRMWARE("go7007/lr192.fw"); +MODULE_FIRMWARE("go7007/wis-startrek.fw"); + +static int go7007_loader_probe(struct usb_interface *interface, + const struct usb_device_id *id) +{ + struct usb_device *usbdev; + const struct firmware *fw; + u16 vendor, product; + const char *fw1, *fw2; + int ret; + int i; + + usbdev = usb_get_dev(interface_to_usbdev(interface)); + if (!usbdev) + goto failed2; + + if (usbdev->descriptor.bNumConfigurations != 1) { + dev_err(&interface->dev, "can't handle multiple config\n"); + goto failed2; + } + + vendor = le16_to_cpu(usbdev->descriptor.idVendor); + product = le16_to_cpu(usbdev->descriptor.idProduct); + + for (i = 0; fw_configs[i].fw_name1; i++) + if (fw_configs[i].vendor == vendor && + fw_configs[i].product == product) + break; + + /* Should never happen */ + if (fw_configs[i].fw_name1 == NULL) + goto failed2; + + fw1 = fw_configs[i].fw_name1; + fw2 = fw_configs[i].fw_name2; + + dev_info(&interface->dev, "loading firmware %s\n", fw1); + + if (request_firmware(&fw, fw1, &usbdev->dev)) { + dev_err(&interface->dev, + "unable to load firmware from file \"%s\"\n", fw1); + goto failed2; + } + ret = cypress_load_firmware(usbdev, fw, CYPRESS_FX2); + release_firmware(fw); + if (0 != ret) { + dev_err(&interface->dev, "loader download failed\n"); + goto failed2; + } + + if (fw2 == NULL) + return 0; + + if (request_firmware(&fw, fw2, &usbdev->dev)) { + dev_err(&interface->dev, + "unable to load firmware from file \"%s\"\n", fw2); + goto failed2; + } + ret = cypress_load_firmware(usbdev, fw, CYPRESS_FX2); + release_firmware(fw); + if (0 != ret) { + dev_err(&interface->dev, "firmware download failed\n"); + goto failed2; + } + return 0; + +failed2: + usb_put_dev(usbdev); + dev_err(&interface->dev, "probe failed\n"); + return -ENODEV; +} + +static void go7007_loader_disconnect(struct usb_interface *interface) +{ + dev_info(&interface->dev, "disconnect\n"); + usb_put_dev(interface_to_usbdev(interface)); + usb_set_intfdata(interface, NULL); +} + +static const struct usb_device_id go7007_loader_ids[] = { + { USB_DEVICE(0x1943, 0xa250) }, + { USB_DEVICE(0x093b, 0xa002) }, + { USB_DEVICE(0x093b, 0xa004) }, + { USB_DEVICE(0x0eb1, 0x6666) }, + { USB_DEVICE(0x0eb1, 0x6668) }, + {} /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE(usb, go7007_loader_ids); + +static struct usb_driver go7007_loader_driver = { + .name = "go7007-loader", + .probe = go7007_loader_probe, + .disconnect = go7007_loader_disconnect, + .id_table = go7007_loader_ids, +}; + +module_usb_driver(go7007_loader_driver); + +MODULE_AUTHOR(""); +MODULE_DESCRIPTION("firmware loader for go7007-usb"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/staging/media/go7007/go7007-priv.h b/drivers/staging/media/go7007/go7007-priv.h new file mode 100644 index 00000000000..6e16af72050 --- /dev/null +++ b/drivers/staging/media/go7007/go7007-priv.h @@ -0,0 +1,294 @@ +/* + * Copyright (C) 2005-2006 Micronas USA Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License (Version 2) as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA. + */ + +/* + * This is the private include file for the go7007 driver. It should not + * be included by anybody but the driver itself, and especially not by + * user-space applications. + */ + +#include <media/v4l2-device.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-fh.h> +#include <media/videobuf2-core.h> + +struct go7007; + +/* IDs to activate board-specific support code */ +#define GO7007_BOARDID_MATRIX_II 0 +#define GO7007_BOARDID_MATRIX_RELOAD 1 +#define GO7007_BOARDID_STAR_TREK 2 +#define GO7007_BOARDID_PCI_VOYAGER 3 +#define GO7007_BOARDID_XMEN 4 +#define GO7007_BOARDID_XMEN_II 5 +#define GO7007_BOARDID_XMEN_III 6 +#define GO7007_BOARDID_MATRIX_REV 7 +#define GO7007_BOARDID_PX_M402U 8 +#define GO7007_BOARDID_PX_TV402U 9 +#define GO7007_BOARDID_LIFEVIEW_LR192 10 /* TV Walker Ultra */ +#define GO7007_BOARDID_ENDURA 11 +#define GO7007_BOARDID_ADLINK_MPG24 12 +#define GO7007_BOARDID_SENSORAY_2250 13 /* Sensoray 2250/2251 */ +#define GO7007_BOARDID_ADS_USBAV_709 14 + +/* Various characteristics of each board */ +#define GO7007_BOARD_HAS_AUDIO (1<<0) +#define GO7007_BOARD_USE_ONBOARD_I2C (1<<1) +#define GO7007_BOARD_HAS_TUNER (1<<2) + +/* Characteristics of sensor devices */ +#define GO7007_SENSOR_VALID_POLAR (1<<0) +#define GO7007_SENSOR_HREF_POLAR (1<<1) +#define GO7007_SENSOR_VREF_POLAR (1<<2) +#define GO7007_SENSOR_FIELD_ID_POLAR (1<<3) +#define GO7007_SENSOR_BIT_WIDTH (1<<4) +#define GO7007_SENSOR_VALID_ENABLE (1<<5) +#define GO7007_SENSOR_656 (1<<6) +#define GO7007_SENSOR_CONFIG_MASK 0x7f +#define GO7007_SENSOR_TV (1<<7) +#define GO7007_SENSOR_VBI (1<<8) +#define GO7007_SENSOR_SCALING (1<<9) +#define GO7007_SENSOR_SAA7115 (1<<10) + +/* Characteristics of audio sensor devices */ +#define GO7007_AUDIO_I2S_MODE_1 (1) +#define GO7007_AUDIO_I2S_MODE_2 (2) +#define GO7007_AUDIO_I2S_MODE_3 (3) +#define GO7007_AUDIO_BCLK_POLAR (1<<2) +#define GO7007_AUDIO_WORD_14 (14<<4) +#define GO7007_AUDIO_WORD_16 (16<<4) +#define GO7007_AUDIO_ONE_CHANNEL (1<<11) +#define GO7007_AUDIO_I2S_MASTER (1<<16) +#define GO7007_AUDIO_OKI_MODE (1<<17) + +struct go7007_board_info { + unsigned int flags; + int hpi_buffer_cap; + unsigned int sensor_flags; + int sensor_width; + int sensor_height; + int sensor_framerate; + int sensor_h_offset; + int sensor_v_offset; + unsigned int audio_flags; + int audio_rate; + int audio_bclk_div; + int audio_main_div; + int num_i2c_devs; + struct go_i2c { + const char *type; + unsigned int is_video:1; + unsigned int is_audio:1; + int addr; + u32 flags; + } i2c_devs[5]; + int num_inputs; + struct { + int video_input; + int audio_index; + char *name; + } inputs[4]; + int video_config; + int num_aud_inputs; + struct { + int audio_input; + char *name; + } aud_inputs[3]; +}; + +struct go7007_hpi_ops { + int (*interface_reset)(struct go7007 *go); + int (*write_interrupt)(struct go7007 *go, int addr, int data); + int (*read_interrupt)(struct go7007 *go); + int (*stream_start)(struct go7007 *go); + int (*stream_stop)(struct go7007 *go); + int (*send_firmware)(struct go7007 *go, u8 *data, int len); + int (*send_command)(struct go7007 *go, unsigned int cmd, void *arg); + void (*release)(struct go7007 *go); +}; + +/* The video buffer size must be a multiple of PAGE_SIZE */ +#define GO7007_BUF_PAGES (128 * 1024 / PAGE_SIZE) +#define GO7007_BUF_SIZE (GO7007_BUF_PAGES << PAGE_SHIFT) + +struct go7007_buffer { + struct vb2_buffer vb; + struct list_head list; + unsigned int frame_offset; + u32 modet_active; +}; + +#define GO7007_RATIO_1_1 0 +#define GO7007_RATIO_4_3 1 +#define GO7007_RATIO_16_9 2 + +enum go7007_parser_state { + STATE_DATA, + STATE_00, + STATE_00_00, + STATE_00_00_01, + STATE_FF, + STATE_VBI_LEN_A, + STATE_VBI_LEN_B, + STATE_MODET_MAP, + STATE_UNPARSED, +}; + +struct go7007 { + struct device *dev; + u8 bus_info[32]; + const struct go7007_board_info *board_info; + unsigned int board_id; + int tuner_type; + int channel_number; /* for multi-channel boards like Adlink PCI-MPG24 */ + char name[64]; + struct video_device vdev; + void *boot_fw; + unsigned boot_fw_len; + struct v4l2_device v4l2_dev; + struct v4l2_ctrl_handler hdl; + struct v4l2_ctrl *mpeg_video_encoding; + struct v4l2_ctrl *mpeg_video_gop_size; + struct v4l2_ctrl *mpeg_video_gop_closure; + struct v4l2_ctrl *mpeg_video_bitrate; + struct v4l2_ctrl *mpeg_video_aspect_ratio; + struct v4l2_ctrl *mpeg_video_b_frames; + struct v4l2_ctrl *mpeg_video_rep_seqheader; + enum { STATUS_INIT, STATUS_ONLINE, STATUS_SHUTDOWN } status; + spinlock_t spinlock; + struct mutex hw_lock; + struct mutex serialize_lock; + int audio_enabled; + struct v4l2_subdev *sd_video; + struct v4l2_subdev *sd_audio; + u8 usb_buf[16]; + + /* Video input */ + int input; + int aud_input; + enum { GO7007_STD_NTSC, GO7007_STD_PAL, GO7007_STD_OTHER } standard; + v4l2_std_id std; + int sensor_framerate; + int width; + int height; + int encoder_h_offset; + int encoder_v_offset; + unsigned int encoder_h_halve:1; + unsigned int encoder_v_halve:1; + unsigned int encoder_subsample:1; + + /* Encoder config */ + u32 format; + int bitrate; + int fps_scale; + int pali; + int aspect_ratio; + int gop_size; + unsigned int ipb:1; + unsigned int closed_gop:1; + unsigned int repeat_seqhead:1; + unsigned int seq_header_enable:1; + unsigned int gop_header_enable:1; + unsigned int dvd_mode:1; + unsigned int interlace_coding:1; + + /* Motion detection */ + unsigned int modet_enable:1; + struct { + unsigned int enable:1; + int pixel_threshold; + int motion_threshold; + int mb_threshold; + } modet[4]; + unsigned char modet_map[1624]; + unsigned char active_map[216]; + + /* Video streaming */ + struct mutex queue_lock; + struct vb2_queue vidq; + enum go7007_parser_state state; + int parse_length; + u16 modet_word; + int seen_frame; + u32 next_seq; + struct list_head vidq_active; + wait_queue_head_t frame_waitq; + struct go7007_buffer *active_buf; + + /* Audio streaming */ + void (*audio_deliver)(struct go7007 *go, u8 *buf, int length); + void *snd_context; + + /* I2C */ + int i2c_adapter_online; + struct i2c_adapter i2c_adapter; + + /* HPI driver */ + struct go7007_hpi_ops *hpi_ops; + void *hpi_context; + int interrupt_available; + wait_queue_head_t interrupt_waitq; + unsigned short interrupt_value; + unsigned short interrupt_data; +}; + +static inline struct go7007 *to_go7007(struct v4l2_device *v4l2_dev) +{ + return container_of(v4l2_dev, struct go7007, v4l2_dev); +} + +/* All of these must be called with the hpi_lock mutex held! */ +#define go7007_interface_reset(go) \ + ((go)->hpi_ops->interface_reset(go)) +#define go7007_write_interrupt(go, x, y) \ + ((go)->hpi_ops->write_interrupt)((go), (x), (y)) +#define go7007_stream_start(go) \ + ((go)->hpi_ops->stream_start(go)) +#define go7007_stream_stop(go) \ + ((go)->hpi_ops->stream_stop(go)) +#define go7007_send_firmware(go, x, y) \ + ((go)->hpi_ops->send_firmware)((go), (x), (y)) +#define go7007_write_addr(go, x, y) \ + ((go)->hpi_ops->write_interrupt)((go), (x)|0x8000, (y)) + +/* go7007-driver.c */ +int go7007_read_addr(struct go7007 *go, u16 addr, u16 *data); +int go7007_read_interrupt(struct go7007 *go, u16 *value, u16 *data); +int go7007_boot_encoder(struct go7007 *go, int init_i2c); +int go7007_reset_encoder(struct go7007 *go); +int go7007_register_encoder(struct go7007 *go, unsigned num_i2c_devs); +int go7007_start_encoder(struct go7007 *go); +void go7007_parse_video_stream(struct go7007 *go, u8 *buf, int length); +struct go7007 *go7007_alloc(const struct go7007_board_info *board, + struct device *dev); +void go7007_update_board(struct go7007 *go); + +/* go7007-fw.c */ +int go7007_construct_fw_image(struct go7007 *go, u8 **fw, int *fwlen); + +/* go7007-i2c.c */ +int go7007_i2c_init(struct go7007 *go); +int go7007_i2c_remove(struct go7007 *go); + +/* go7007-v4l2.c */ +int go7007_v4l2_init(struct go7007 *go); +int go7007_v4l2_ctrl_init(struct go7007 *go); +void go7007_v4l2_remove(struct go7007 *go); + +/* snd-go7007.c */ +int go7007_snd_init(struct go7007 *go); +int go7007_snd_remove(struct go7007 *go); diff --git a/drivers/staging/media/go7007/go7007-usb.c b/drivers/staging/media/go7007/go7007-usb.c new file mode 100644 index 00000000000..2f62be905cd --- /dev/null +++ b/drivers/staging/media/go7007/go7007-usb.c @@ -0,0 +1,1349 @@ +/* + * Copyright (C) 2005-2006 Micronas USA Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License (Version 2) as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/wait.h> +#include <linux/list.h> +#include <linux/slab.h> +#include <linux/time.h> +#include <linux/mm.h> +#include <linux/usb.h> +#include <linux/i2c.h> +#include <asm/byteorder.h> +#include <media/saa7115.h> +#include <media/tuner.h> +#include <media/uda1342.h> + +#include "go7007-priv.h" + +static unsigned int assume_endura; +module_param(assume_endura, int, 0644); +MODULE_PARM_DESC(assume_endura, + "when probing fails, hardware is a Pelco Endura"); + +/* #define GO7007_I2C_DEBUG */ /* for debugging the EZ-USB I2C adapter */ + +#define HPI_STATUS_ADDR 0xFFF4 +#define INT_PARAM_ADDR 0xFFF6 +#define INT_INDEX_ADDR 0xFFF8 + +/* + * Pipes on EZ-USB interface: + * 0 snd - Control + * 0 rcv - Control + * 2 snd - Download firmware (control) + * 4 rcv - Read Interrupt (interrupt) + * 6 rcv - Read Video (bulk) + * 8 rcv - Read Audio (bulk) + */ + +#define GO7007_USB_EZUSB (1<<0) +#define GO7007_USB_EZUSB_I2C (1<<1) + +struct go7007_usb_board { + unsigned int flags; + struct go7007_board_info main_info; +}; + +struct go7007_usb { + const struct go7007_usb_board *board; + struct mutex i2c_lock; + struct usb_device *usbdev; + struct urb *video_urbs[8]; + struct urb *audio_urbs[8]; + struct urb *intr_urb; +}; + +/*********************** Product specification data ***********************/ + +static const struct go7007_usb_board board_matrix_ii = { + .flags = GO7007_USB_EZUSB, + .main_info = { + .flags = GO7007_BOARD_HAS_AUDIO | + GO7007_BOARD_USE_ONBOARD_I2C, + .audio_flags = GO7007_AUDIO_I2S_MODE_1 | + GO7007_AUDIO_WORD_16, + .audio_rate = 48000, + .audio_bclk_div = 8, + .audio_main_div = 2, + .hpi_buffer_cap = 7, + .sensor_flags = GO7007_SENSOR_656 | + GO7007_SENSOR_VALID_ENABLE | + GO7007_SENSOR_TV | + GO7007_SENSOR_SAA7115 | + GO7007_SENSOR_VBI | + GO7007_SENSOR_SCALING, + .num_i2c_devs = 1, + .i2c_devs = { + { + .type = "saa7115", + .addr = 0x20, + .is_video = 1, + }, + }, + .num_inputs = 2, + .inputs = { + { + .video_input = 0, + .name = "Composite", + }, + { + .video_input = 9, + .name = "S-Video", + }, + }, + .video_config = SAA7115_IDQ_IS_DEFAULT, + }, +}; + +static const struct go7007_usb_board board_matrix_reload = { + .flags = GO7007_USB_EZUSB, + .main_info = { + .flags = GO7007_BOARD_HAS_AUDIO | + GO7007_BOARD_USE_ONBOARD_I2C, + .audio_flags = GO7007_AUDIO_I2S_MODE_1 | + GO7007_AUDIO_I2S_MASTER | + GO7007_AUDIO_WORD_16, + .audio_rate = 48000, + .audio_bclk_div = 8, + .audio_main_div = 2, + .hpi_buffer_cap = 7, + .sensor_flags = GO7007_SENSOR_656 | + GO7007_SENSOR_TV, + .num_i2c_devs = 1, + .i2c_devs = { + { + .type = "saa7113", + .addr = 0x25, + .is_video = 1, + }, + }, + .num_inputs = 2, + .inputs = { + { + .video_input = 0, + .name = "Composite", + }, + { + .video_input = 9, + .name = "S-Video", + }, + }, + .video_config = SAA7115_IDQ_IS_DEFAULT, + }, +}; + +static const struct go7007_usb_board board_star_trek = { + .flags = GO7007_USB_EZUSB | GO7007_USB_EZUSB_I2C, + .main_info = { + .flags = GO7007_BOARD_HAS_AUDIO, /* | + GO7007_BOARD_HAS_TUNER, */ + .sensor_flags = GO7007_SENSOR_656 | + GO7007_SENSOR_VALID_ENABLE | + GO7007_SENSOR_TV | + GO7007_SENSOR_SAA7115 | + GO7007_SENSOR_VBI | + GO7007_SENSOR_SCALING, + .audio_flags = GO7007_AUDIO_I2S_MODE_1 | + GO7007_AUDIO_WORD_16, + .audio_bclk_div = 8, + .audio_main_div = 2, + .hpi_buffer_cap = 7, + .num_i2c_devs = 1, + .i2c_devs = { + { + .type = "saa7115", + .addr = 0x20, + .is_video = 1, + }, + }, + .num_inputs = 2, + .inputs = { + /* { + * .video_input = 3, + * .audio_index = AUDIO_TUNER, + * .name = "Tuner", + * }, + */ + { + .video_input = 1, + /* .audio_index = AUDIO_EXTERN, */ + .name = "Composite", + }, + { + .video_input = 8, + /* .audio_index = AUDIO_EXTERN, */ + .name = "S-Video", + }, + }, + .video_config = SAA7115_IDQ_IS_DEFAULT, + }, +}; + +static const struct go7007_usb_board board_px_tv402u = { + .flags = GO7007_USB_EZUSB | GO7007_USB_EZUSB_I2C, + .main_info = { + .flags = GO7007_BOARD_HAS_AUDIO | + GO7007_BOARD_HAS_TUNER, + .sensor_flags = GO7007_SENSOR_656 | + GO7007_SENSOR_VALID_ENABLE | + GO7007_SENSOR_TV | + GO7007_SENSOR_SAA7115 | + GO7007_SENSOR_VBI | + GO7007_SENSOR_SCALING, + .audio_flags = GO7007_AUDIO_I2S_MODE_1 | + GO7007_AUDIO_WORD_16, + .audio_bclk_div = 8, + .audio_main_div = 2, + .hpi_buffer_cap = 7, + .num_i2c_devs = 5, + .i2c_devs = { + { + .type = "saa7115", + .addr = 0x20, + .is_video = 1, + }, + { + .type = "uda1342", + .addr = 0x1a, + .is_audio = 1, + }, + { + .type = "tuner", + .addr = 0x60, + }, + { + .type = "tuner", + .addr = 0x43, + }, + { + .type = "sony-btf-mpx", + .addr = 0x44, + }, + }, + .num_inputs = 3, + .inputs = { + { + .video_input = 3, + .audio_index = 0, + .name = "Tuner", + }, + { + .video_input = 1, + .audio_index = 1, + .name = "Composite", + }, + { + .video_input = 8, + .audio_index = 1, + .name = "S-Video", + }, + }, + .video_config = SAA7115_IDQ_IS_DEFAULT, + .num_aud_inputs = 2, + .aud_inputs = { + { + .audio_input = UDA1342_IN2, + .name = "Tuner", + }, + { + .audio_input = UDA1342_IN1, + .name = "Line In", + }, + }, + }, +}; + +static const struct go7007_usb_board board_xmen = { + .flags = 0, + .main_info = { + .flags = GO7007_BOARD_USE_ONBOARD_I2C, + .hpi_buffer_cap = 0, + .sensor_flags = GO7007_SENSOR_VREF_POLAR, + .sensor_width = 320, + .sensor_height = 240, + .sensor_framerate = 30030, + .audio_flags = GO7007_AUDIO_ONE_CHANNEL | + GO7007_AUDIO_I2S_MODE_3 | + GO7007_AUDIO_WORD_14 | + GO7007_AUDIO_I2S_MASTER | + GO7007_AUDIO_BCLK_POLAR | + GO7007_AUDIO_OKI_MODE, + .audio_rate = 8000, + .audio_bclk_div = 48, + .audio_main_div = 1, + .num_i2c_devs = 1, + .i2c_devs = { + { + .type = "ov7640", + .addr = 0x21, + }, + }, + .num_inputs = 1, + .inputs = { + { + .name = "Camera", + }, + }, + }, +}; + +static const struct go7007_usb_board board_matrix_revolution = { + .flags = GO7007_USB_EZUSB, + .main_info = { + .flags = GO7007_BOARD_HAS_AUDIO | + GO7007_BOARD_USE_ONBOARD_I2C, + .audio_flags = GO7007_AUDIO_I2S_MODE_1 | + GO7007_AUDIO_I2S_MASTER | + GO7007_AUDIO_WORD_16, + .audio_rate = 48000, + .audio_bclk_div = 8, + .audio_main_div = 2, + .hpi_buffer_cap = 7, + .sensor_flags = GO7007_SENSOR_656 | + GO7007_SENSOR_TV | + GO7007_SENSOR_VBI, + .num_i2c_devs = 1, + .i2c_devs = { + { + .type = "tw9903", + .is_video = 1, + .addr = 0x44, + }, + }, + .num_inputs = 2, + .inputs = { + { + .video_input = 2, + .name = "Composite", + }, + { + .video_input = 8, + .name = "S-Video", + }, + }, + }, +}; + +static const struct go7007_usb_board board_lifeview_lr192 = { + .flags = GO7007_USB_EZUSB, + .main_info = { + .flags = GO7007_BOARD_HAS_AUDIO | + GO7007_BOARD_USE_ONBOARD_I2C, + .audio_flags = GO7007_AUDIO_I2S_MODE_1 | + GO7007_AUDIO_WORD_16, + .audio_rate = 48000, + .audio_bclk_div = 8, + .audio_main_div = 2, + .hpi_buffer_cap = 7, + .sensor_flags = GO7007_SENSOR_656 | + GO7007_SENSOR_VALID_ENABLE | + GO7007_SENSOR_TV | + GO7007_SENSOR_VBI | + GO7007_SENSOR_SCALING, + .num_i2c_devs = 0, + .num_inputs = 1, + .inputs = { + { + .video_input = 0, + .name = "Composite", + }, + }, + }, +}; + +static const struct go7007_usb_board board_endura = { + .flags = 0, + .main_info = { + .flags = 0, + .audio_flags = GO7007_AUDIO_I2S_MODE_1 | + GO7007_AUDIO_I2S_MASTER | + GO7007_AUDIO_WORD_16, + .audio_rate = 8000, + .audio_bclk_div = 48, + .audio_main_div = 8, + .hpi_buffer_cap = 0, + .sensor_flags = GO7007_SENSOR_656 | + GO7007_SENSOR_TV, + .sensor_h_offset = 8, + .num_i2c_devs = 0, + .num_inputs = 1, + .inputs = { + { + .name = "Camera", + }, + }, + }, +}; + +static const struct go7007_usb_board board_adlink_mpg24 = { + .flags = 0, + .main_info = { + .flags = GO7007_BOARD_USE_ONBOARD_I2C, + .audio_flags = GO7007_AUDIO_I2S_MODE_1 | + GO7007_AUDIO_I2S_MASTER | + GO7007_AUDIO_WORD_16, + .audio_rate = 48000, + .audio_bclk_div = 8, + .audio_main_div = 2, + .hpi_buffer_cap = 0, + .sensor_flags = GO7007_SENSOR_656 | + GO7007_SENSOR_TV | + GO7007_SENSOR_VBI, + .num_i2c_devs = 1, + .i2c_devs = { + { + .type = "tw2804", + .addr = 0x00, /* yes, really */ + .flags = I2C_CLIENT_TEN, + .is_video = 1, + }, + }, + .num_inputs = 1, + .inputs = { + { + .name = "Composite", + }, + }, + }, +}; + +static const struct go7007_usb_board board_sensoray_2250 = { + .flags = GO7007_USB_EZUSB | GO7007_USB_EZUSB_I2C, + .main_info = { + .audio_flags = GO7007_AUDIO_I2S_MODE_1 | + GO7007_AUDIO_I2S_MASTER | + GO7007_AUDIO_WORD_16, + .flags = GO7007_BOARD_HAS_AUDIO, + .audio_rate = 48000, + .audio_bclk_div = 8, + .audio_main_div = 2, + .hpi_buffer_cap = 7, + .sensor_flags = GO7007_SENSOR_656 | + GO7007_SENSOR_TV, + .num_i2c_devs = 1, + .i2c_devs = { + { + .type = "s2250", + .addr = 0x43, + .is_video = 1, + .is_audio = 1, + }, + }, + .num_inputs = 2, + .inputs = { + { + .video_input = 0, + .name = "Composite", + }, + { + .video_input = 1, + .name = "S-Video", + }, + }, + .num_aud_inputs = 3, + .aud_inputs = { + { + .audio_input = 0, + .name = "Line In", + }, + { + .audio_input = 1, + .name = "Mic", + }, + { + .audio_input = 2, + .name = "Mic Boost", + }, + }, + }, +}; + +static const struct go7007_usb_board board_ads_usbav_709 = { + .flags = GO7007_USB_EZUSB, + .main_info = { + .flags = GO7007_BOARD_HAS_AUDIO | + GO7007_BOARD_USE_ONBOARD_I2C, + .audio_flags = GO7007_AUDIO_I2S_MODE_1 | + GO7007_AUDIO_I2S_MASTER | + GO7007_AUDIO_WORD_16, + .audio_rate = 48000, + .audio_bclk_div = 8, + .audio_main_div = 2, + .hpi_buffer_cap = 7, + .sensor_flags = GO7007_SENSOR_656 | + GO7007_SENSOR_TV | + GO7007_SENSOR_VBI, + .num_i2c_devs = 1, + .i2c_devs = { + { + .type = "tw9906", + .is_video = 1, + .addr = 0x44, + }, + }, + .num_inputs = 2, + .inputs = { + { + .video_input = 0, + .name = "Composite", + }, + { + .video_input = 10, + .name = "S-Video", + }, + }, + }, +}; + +static const struct usb_device_id go7007_usb_id_table[] = { + { + .match_flags = USB_DEVICE_ID_MATCH_DEVICE_AND_VERSION | + USB_DEVICE_ID_MATCH_INT_INFO, + .idVendor = 0x0eb1, /* Vendor ID of WIS Technologies */ + .idProduct = 0x7007, /* Product ID of GO7007SB chip */ + .bcdDevice_lo = 0x200, /* Revision number of XMen */ + .bcdDevice_hi = 0x200, + .bInterfaceClass = 255, + .bInterfaceSubClass = 0, + .bInterfaceProtocol = 255, + .driver_info = (kernel_ulong_t)GO7007_BOARDID_XMEN, + }, + { + .match_flags = USB_DEVICE_ID_MATCH_DEVICE_AND_VERSION, + .idVendor = 0x0eb1, /* Vendor ID of WIS Technologies */ + .idProduct = 0x7007, /* Product ID of GO7007SB chip */ + .bcdDevice_lo = 0x202, /* Revision number of Matrix II */ + .bcdDevice_hi = 0x202, + .driver_info = (kernel_ulong_t)GO7007_BOARDID_MATRIX_II, + }, + { + .match_flags = USB_DEVICE_ID_MATCH_DEVICE_AND_VERSION, + .idVendor = 0x0eb1, /* Vendor ID of WIS Technologies */ + .idProduct = 0x7007, /* Product ID of GO7007SB chip */ + .bcdDevice_lo = 0x204, /* Revision number of Matrix */ + .bcdDevice_hi = 0x204, /* Reloaded */ + .driver_info = (kernel_ulong_t)GO7007_BOARDID_MATRIX_RELOAD, + }, + { + .match_flags = USB_DEVICE_ID_MATCH_DEVICE_AND_VERSION | + USB_DEVICE_ID_MATCH_INT_INFO, + .idVendor = 0x0eb1, /* Vendor ID of WIS Technologies */ + .idProduct = 0x7007, /* Product ID of GO7007SB chip */ + .bcdDevice_lo = 0x205, /* Revision number of XMen-II */ + .bcdDevice_hi = 0x205, + .bInterfaceClass = 255, + .bInterfaceSubClass = 0, + .bInterfaceProtocol = 255, + .driver_info = (kernel_ulong_t)GO7007_BOARDID_XMEN_II, + }, + { + .match_flags = USB_DEVICE_ID_MATCH_DEVICE_AND_VERSION, + .idVendor = 0x0eb1, /* Vendor ID of WIS Technologies */ + .idProduct = 0x7007, /* Product ID of GO7007SB chip */ + .bcdDevice_lo = 0x208, /* Revision number of Star Trek */ + .bcdDevice_hi = 0x208, + .driver_info = (kernel_ulong_t)GO7007_BOARDID_STAR_TREK, + }, + { + .match_flags = USB_DEVICE_ID_MATCH_DEVICE_AND_VERSION | + USB_DEVICE_ID_MATCH_INT_INFO, + .idVendor = 0x0eb1, /* Vendor ID of WIS Technologies */ + .idProduct = 0x7007, /* Product ID of GO7007SB chip */ + .bcdDevice_lo = 0x209, /* Revision number of XMen-III */ + .bcdDevice_hi = 0x209, + .bInterfaceClass = 255, + .bInterfaceSubClass = 0, + .bInterfaceProtocol = 255, + .driver_info = (kernel_ulong_t)GO7007_BOARDID_XMEN_III, + }, + { + .match_flags = USB_DEVICE_ID_MATCH_DEVICE_AND_VERSION, + .idVendor = 0x0eb1, /* Vendor ID of WIS Technologies */ + .idProduct = 0x7007, /* Product ID of GO7007SB chip */ + .bcdDevice_lo = 0x210, /* Revision number of Matrix */ + .bcdDevice_hi = 0x210, /* Revolution */ + .driver_info = (kernel_ulong_t)GO7007_BOARDID_MATRIX_REV, + }, + { + .match_flags = USB_DEVICE_ID_MATCH_DEVICE_AND_VERSION, + .idVendor = 0x093b, /* Vendor ID of Plextor */ + .idProduct = 0xa102, /* Product ID of M402U */ + .bcdDevice_lo = 0x1, /* revision number of Blueberry */ + .bcdDevice_hi = 0x1, + .driver_info = (kernel_ulong_t)GO7007_BOARDID_PX_M402U, + }, + { + .match_flags = USB_DEVICE_ID_MATCH_DEVICE_AND_VERSION, + .idVendor = 0x093b, /* Vendor ID of Plextor */ + .idProduct = 0xa104, /* Product ID of TV402U */ + .bcdDevice_lo = 0x1, + .bcdDevice_hi = 0x1, + .driver_info = (kernel_ulong_t)GO7007_BOARDID_PX_TV402U, + }, + { + .match_flags = USB_DEVICE_ID_MATCH_DEVICE_AND_VERSION, + .idVendor = 0x10fd, /* Vendor ID of Anubis Electronics */ + .idProduct = 0xde00, /* Product ID of Lifeview LR192 */ + .bcdDevice_lo = 0x1, + .bcdDevice_hi = 0x1, + .driver_info = (kernel_ulong_t)GO7007_BOARDID_LIFEVIEW_LR192, + }, + { + .match_flags = USB_DEVICE_ID_MATCH_DEVICE_AND_VERSION, + .idVendor = 0x1943, /* Vendor ID Sensoray */ + .idProduct = 0x2250, /* Product ID of 2250/2251 */ + .bcdDevice_lo = 0x1, + .bcdDevice_hi = 0x1, + .driver_info = (kernel_ulong_t)GO7007_BOARDID_SENSORAY_2250, + }, + { + .match_flags = USB_DEVICE_ID_MATCH_DEVICE_AND_VERSION, + .idVendor = 0x06e1, /* Vendor ID of ADS Technologies */ + .idProduct = 0x0709, /* Product ID of DVD Xpress DX2 */ + .bcdDevice_lo = 0x204, + .bcdDevice_hi = 0x204, + .driver_info = (kernel_ulong_t)GO7007_BOARDID_ADS_USBAV_709, + }, + { } /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE(usb, go7007_usb_id_table); + +/********************* Driver for EZ-USB HPI interface *********************/ + +static int go7007_usb_vendor_request(struct go7007 *go, int request, + int value, int index, void *transfer_buffer, int length, int in) +{ + struct go7007_usb *usb = go->hpi_context; + int timeout = 5000; + + if (in) { + return usb_control_msg(usb->usbdev, + usb_rcvctrlpipe(usb->usbdev, 0), request, + USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN, + value, index, transfer_buffer, length, timeout); + } else { + return usb_control_msg(usb->usbdev, + usb_sndctrlpipe(usb->usbdev, 0), request, + USB_TYPE_VENDOR | USB_RECIP_DEVICE, + value, index, transfer_buffer, length, timeout); + } +} + +static int go7007_usb_interface_reset(struct go7007 *go) +{ + struct go7007_usb *usb = go->hpi_context; + u16 intr_val, intr_data; + + if (go->status == STATUS_SHUTDOWN) + return -1; + /* Reset encoder */ + if (go7007_write_interrupt(go, 0x0001, 0x0001) < 0) + return -1; + msleep(100); + + if (usb->board->flags & GO7007_USB_EZUSB) { + /* Reset buffer in EZ-USB */ + pr_debug("resetting EZ-USB buffers\n"); + if (go7007_usb_vendor_request(go, 0x10, 0, 0, NULL, 0, 0) < 0 || + go7007_usb_vendor_request(go, 0x10, 0, 0, NULL, 0, 0) < 0) + return -1; + + /* Reset encoder again */ + if (go7007_write_interrupt(go, 0x0001, 0x0001) < 0) + return -1; + msleep(100); + } + + /* Wait for an interrupt to indicate successful hardware reset */ + if (go7007_read_interrupt(go, &intr_val, &intr_data) < 0 || + (intr_val & ~0x1) != 0x55aa) { + dev_err(go->dev, "unable to reset the USB interface\n"); + return -1; + } + return 0; +} + +static int go7007_usb_ezusb_write_interrupt(struct go7007 *go, + int addr, int data) +{ + struct go7007_usb *usb = go->hpi_context; + int i, r; + u16 status_reg = 0; + int timeout = 500; + + pr_debug("WriteInterrupt: %04x %04x\n", addr, data); + + for (i = 0; i < 100; ++i) { + r = usb_control_msg(usb->usbdev, + usb_rcvctrlpipe(usb->usbdev, 0), 0x14, + USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN, + 0, HPI_STATUS_ADDR, go->usb_buf, + sizeof(status_reg), timeout); + if (r < 0) + break; + status_reg = le16_to_cpu(*((u16 *)go->usb_buf)); + if (!(status_reg & 0x0010)) + break; + msleep(10); + } + if (r < 0) + goto write_int_error; + if (i == 100) { + dev_err(go->dev, "device is hung, status reg = 0x%04x\n", status_reg); + return -1; + } + r = usb_control_msg(usb->usbdev, usb_sndctrlpipe(usb->usbdev, 0), 0x12, + USB_TYPE_VENDOR | USB_RECIP_DEVICE, data, + INT_PARAM_ADDR, NULL, 0, timeout); + if (r < 0) + goto write_int_error; + r = usb_control_msg(usb->usbdev, usb_sndctrlpipe(usb->usbdev, 0), + 0x12, USB_TYPE_VENDOR | USB_RECIP_DEVICE, addr, + INT_INDEX_ADDR, NULL, 0, timeout); + if (r < 0) + goto write_int_error; + return 0; + +write_int_error: + dev_err(go->dev, "error in WriteInterrupt: %d\n", r); + return r; +} + +static int go7007_usb_onboard_write_interrupt(struct go7007 *go, + int addr, int data) +{ + struct go7007_usb *usb = go->hpi_context; + int r; + int timeout = 500; + + pr_debug("WriteInterrupt: %04x %04x\n", addr, data); + + go->usb_buf[0] = data & 0xff; + go->usb_buf[1] = data >> 8; + go->usb_buf[2] = addr & 0xff; + go->usb_buf[3] = addr >> 8; + go->usb_buf[4] = go->usb_buf[5] = go->usb_buf[6] = go->usb_buf[7] = 0; + r = usb_control_msg(usb->usbdev, usb_sndctrlpipe(usb->usbdev, 2), 0x00, + USB_TYPE_VENDOR | USB_RECIP_ENDPOINT, 0x55aa, + 0xf0f0, go->usb_buf, 8, timeout); + if (r < 0) { + dev_err(go->dev, "error in WriteInterrupt: %d\n", r); + return r; + } + return 0; +} + +static void go7007_usb_readinterrupt_complete(struct urb *urb) +{ + struct go7007 *go = (struct go7007 *)urb->context; + u16 *regs = (u16 *)urb->transfer_buffer; + int status = urb->status; + + if (status) { + if (status != -ESHUTDOWN && + go->status != STATUS_SHUTDOWN) { + dev_err(go->dev, "error in read interrupt: %d\n", urb->status); + } else { + wake_up(&go->interrupt_waitq); + return; + } + } else if (urb->actual_length != urb->transfer_buffer_length) { + dev_err(go->dev, "short read in interrupt pipe!\n"); + } else { + go->interrupt_available = 1; + go->interrupt_data = __le16_to_cpu(regs[0]); + go->interrupt_value = __le16_to_cpu(regs[1]); + pr_debug("ReadInterrupt: %04x %04x\n", + go->interrupt_value, go->interrupt_data); + } + + wake_up(&go->interrupt_waitq); +} + +static int go7007_usb_read_interrupt(struct go7007 *go) +{ + struct go7007_usb *usb = go->hpi_context; + int r; + + r = usb_submit_urb(usb->intr_urb, GFP_KERNEL); + if (r < 0) { + dev_err(go->dev, "unable to submit interrupt urb: %d\n", r); + return r; + } + return 0; +} + +static void go7007_usb_read_video_pipe_complete(struct urb *urb) +{ + struct go7007 *go = (struct go7007 *)urb->context; + int r, status = urb->status; + + if (!vb2_is_streaming(&go->vidq)) { + wake_up_interruptible(&go->frame_waitq); + return; + } + if (status) { + dev_err(go->dev, "error in video pipe: %d\n", status); + return; + } + if (urb->actual_length != urb->transfer_buffer_length) { + dev_err(go->dev, "short read in video pipe!\n"); + return; + } + go7007_parse_video_stream(go, urb->transfer_buffer, urb->actual_length); + r = usb_submit_urb(urb, GFP_ATOMIC); + if (r < 0) + dev_err(go->dev, "error in video pipe: %d\n", r); +} + +static void go7007_usb_read_audio_pipe_complete(struct urb *urb) +{ + struct go7007 *go = (struct go7007 *)urb->context; + int r, status = urb->status; + + if (!vb2_is_streaming(&go->vidq)) + return; + if (status) { + dev_err(go->dev, "error in audio pipe: %d\n", + status); + return; + } + if (urb->actual_length != urb->transfer_buffer_length) { + dev_err(go->dev, "short read in audio pipe!\n"); + return; + } + if (go->audio_deliver != NULL) + go->audio_deliver(go, urb->transfer_buffer, urb->actual_length); + r = usb_submit_urb(urb, GFP_ATOMIC); + if (r < 0) + dev_err(go->dev, "error in audio pipe: %d\n", r); +} + +static int go7007_usb_stream_start(struct go7007 *go) +{ + struct go7007_usb *usb = go->hpi_context; + int i, r; + + for (i = 0; i < 8; ++i) { + r = usb_submit_urb(usb->video_urbs[i], GFP_KERNEL); + if (r < 0) { + dev_err(go->dev, "error submitting video urb %d: %d\n", i, r); + goto video_submit_failed; + } + } + if (!go->audio_enabled) + return 0; + + for (i = 0; i < 8; ++i) { + r = usb_submit_urb(usb->audio_urbs[i], GFP_KERNEL); + if (r < 0) { + dev_err(go->dev, "error submitting audio urb %d: %d\n", i, r); + goto audio_submit_failed; + } + } + return 0; + +audio_submit_failed: + for (i = 0; i < 7; ++i) + usb_kill_urb(usb->audio_urbs[i]); +video_submit_failed: + for (i = 0; i < 8; ++i) + usb_kill_urb(usb->video_urbs[i]); + return -1; +} + +static int go7007_usb_stream_stop(struct go7007 *go) +{ + struct go7007_usb *usb = go->hpi_context; + int i; + + if (go->status == STATUS_SHUTDOWN) + return 0; + for (i = 0; i < 8; ++i) + usb_kill_urb(usb->video_urbs[i]); + if (go->audio_enabled) + for (i = 0; i < 8; ++i) + usb_kill_urb(usb->audio_urbs[i]); + return 0; +} + +static int go7007_usb_send_firmware(struct go7007 *go, u8 *data, int len) +{ + struct go7007_usb *usb = go->hpi_context; + int transferred, pipe; + int timeout = 500; + + pr_debug("DownloadBuffer sending %d bytes\n", len); + + if (usb->board->flags & GO7007_USB_EZUSB) + pipe = usb_sndbulkpipe(usb->usbdev, 2); + else + pipe = usb_sndbulkpipe(usb->usbdev, 3); + + return usb_bulk_msg(usb->usbdev, pipe, data, len, + &transferred, timeout); +} + +static void go7007_usb_release(struct go7007 *go) +{ + struct go7007_usb *usb = go->hpi_context; + struct urb *vurb, *aurb; + int i; + + if (usb->intr_urb) { + usb_kill_urb(usb->intr_urb); + kfree(usb->intr_urb->transfer_buffer); + usb_free_urb(usb->intr_urb); + } + + /* Free USB-related structs */ + for (i = 0; i < 8; ++i) { + vurb = usb->video_urbs[i]; + if (vurb) { + usb_kill_urb(vurb); + kfree(vurb->transfer_buffer); + usb_free_urb(vurb); + } + aurb = usb->audio_urbs[i]; + if (aurb) { + usb_kill_urb(aurb); + kfree(aurb->transfer_buffer); + usb_free_urb(aurb); + } + } + + kfree(go->hpi_context); +} + +static struct go7007_hpi_ops go7007_usb_ezusb_hpi_ops = { + .interface_reset = go7007_usb_interface_reset, + .write_interrupt = go7007_usb_ezusb_write_interrupt, + .read_interrupt = go7007_usb_read_interrupt, + .stream_start = go7007_usb_stream_start, + .stream_stop = go7007_usb_stream_stop, + .send_firmware = go7007_usb_send_firmware, + .release = go7007_usb_release, +}; + +static struct go7007_hpi_ops go7007_usb_onboard_hpi_ops = { + .interface_reset = go7007_usb_interface_reset, + .write_interrupt = go7007_usb_onboard_write_interrupt, + .read_interrupt = go7007_usb_read_interrupt, + .stream_start = go7007_usb_stream_start, + .stream_stop = go7007_usb_stream_stop, + .send_firmware = go7007_usb_send_firmware, + .release = go7007_usb_release, +}; + +/********************* Driver for EZ-USB I2C adapter *********************/ + +static int go7007_usb_i2c_master_xfer(struct i2c_adapter *adapter, + struct i2c_msg msgs[], int num) +{ + struct go7007 *go = i2c_get_adapdata(adapter); + struct go7007_usb *usb = go->hpi_context; + u8 *buf = go->usb_buf; + int buf_len, i; + int ret = -EIO; + + if (go->status == STATUS_SHUTDOWN) + return -ENODEV; + + mutex_lock(&usb->i2c_lock); + + for (i = 0; i < num; ++i) { + /* The hardware command is "write some bytes then read some + * bytes", so we try to coalesce a write followed by a read + * into a single USB transaction */ + if (i + 1 < num && msgs[i].addr == msgs[i + 1].addr && + !(msgs[i].flags & I2C_M_RD) && + (msgs[i + 1].flags & I2C_M_RD)) { +#ifdef GO7007_I2C_DEBUG + pr_debug("i2c write/read %d/%d bytes on %02x\n", + msgs[i].len, msgs[i + 1].len, msgs[i].addr); +#endif + buf[0] = 0x01; + buf[1] = msgs[i].len + 1; + buf[2] = msgs[i].addr << 1; + memcpy(&buf[3], msgs[i].buf, msgs[i].len); + buf_len = msgs[i].len + 3; + buf[buf_len++] = msgs[++i].len; + } else if (msgs[i].flags & I2C_M_RD) { +#ifdef GO7007_I2C_DEBUG + pr_debug("i2c read %d bytes on %02x\n", + msgs[i].len, msgs[i].addr); +#endif + buf[0] = 0x01; + buf[1] = 1; + buf[2] = msgs[i].addr << 1; + buf[3] = msgs[i].len; + buf_len = 4; + } else { +#ifdef GO7007_I2C_DEBUG + pr_debug("i2c write %d bytes on %02x\n", + msgs[i].len, msgs[i].addr); +#endif + buf[0] = 0x00; + buf[1] = msgs[i].len + 1; + buf[2] = msgs[i].addr << 1; + memcpy(&buf[3], msgs[i].buf, msgs[i].len); + buf_len = msgs[i].len + 3; + buf[buf_len++] = 0; + } + if (go7007_usb_vendor_request(go, 0x24, 0, 0, + buf, buf_len, 0) < 0) + goto i2c_done; + if (msgs[i].flags & I2C_M_RD) { + memset(buf, 0, msgs[i].len + 1); + if (go7007_usb_vendor_request(go, 0x25, 0, 0, buf, + msgs[i].len + 1, 1) < 0) + goto i2c_done; + memcpy(msgs[i].buf, buf + 1, msgs[i].len); + } + } + ret = num; + +i2c_done: + mutex_unlock(&usb->i2c_lock); + return ret; +} + +static u32 go7007_usb_functionality(struct i2c_adapter *adapter) +{ + /* No errors are reported by the hardware, so we don't bother + * supporting quick writes to avoid confusing probing */ + return (I2C_FUNC_SMBUS_EMUL) & ~I2C_FUNC_SMBUS_QUICK; +} + +static struct i2c_algorithm go7007_usb_algo = { + .master_xfer = go7007_usb_i2c_master_xfer, + .functionality = go7007_usb_functionality, +}; + +static struct i2c_adapter go7007_usb_adap_templ = { + .owner = THIS_MODULE, + .name = "WIS GO7007SB EZ-USB", + .algo = &go7007_usb_algo, +}; + +/********************* USB add/remove functions *********************/ + +static int go7007_usb_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct go7007 *go; + struct go7007_usb *usb; + const struct go7007_usb_board *board; + struct usb_device *usbdev = interface_to_usbdev(intf); + unsigned num_i2c_devs; + char *name; + int video_pipe, i, v_urb_len; + + pr_debug("probing new GO7007 USB board\n"); + + switch (id->driver_info) { + case GO7007_BOARDID_MATRIX_II: + name = "WIS Matrix II or compatible"; + board = &board_matrix_ii; + break; + case GO7007_BOARDID_MATRIX_RELOAD: + name = "WIS Matrix Reloaded or compatible"; + board = &board_matrix_reload; + break; + case GO7007_BOARDID_MATRIX_REV: + name = "WIS Matrix Revolution or compatible"; + board = &board_matrix_revolution; + break; + case GO7007_BOARDID_STAR_TREK: + name = "WIS Star Trek or compatible"; + board = &board_star_trek; + break; + case GO7007_BOARDID_XMEN: + name = "WIS XMen or compatible"; + board = &board_xmen; + break; + case GO7007_BOARDID_XMEN_II: + name = "WIS XMen II or compatible"; + board = &board_xmen; + break; + case GO7007_BOARDID_XMEN_III: + name = "WIS XMen III or compatible"; + board = &board_xmen; + break; + case GO7007_BOARDID_PX_M402U: + name = "Plextor PX-M402U"; + board = &board_matrix_ii; + break; + case GO7007_BOARDID_PX_TV402U: + name = "Plextor PX-TV402U (unknown tuner)"; + board = &board_px_tv402u; + break; + case GO7007_BOARDID_LIFEVIEW_LR192: + dev_err(&intf->dev, "The Lifeview TV Walker Ultra is not supported. Sorry!\n"); + return -ENODEV; + name = "Lifeview TV Walker Ultra"; + board = &board_lifeview_lr192; + break; + case GO7007_BOARDID_SENSORAY_2250: + dev_info(&intf->dev, "Sensoray 2250 found\n"); + name = "Sensoray 2250/2251"; + board = &board_sensoray_2250; + break; + case GO7007_BOARDID_ADS_USBAV_709: + name = "ADS Tech DVD Xpress DX2"; + board = &board_ads_usbav_709; + break; + default: + dev_err(&intf->dev, "unknown board ID %d!\n", + (unsigned int)id->driver_info); + return -ENODEV; + } + + go = go7007_alloc(&board->main_info, &intf->dev); + if (go == NULL) + return -ENOMEM; + + usb = kzalloc(sizeof(struct go7007_usb), GFP_KERNEL); + if (usb == NULL) { + kfree(go); + return -ENOMEM; + } + + usb->board = board; + usb->usbdev = usbdev; + usb_make_path(usbdev, go->bus_info, sizeof(go->bus_info)); + go->board_id = id->driver_info; + strncpy(go->name, name, sizeof(go->name)); + if (board->flags & GO7007_USB_EZUSB) + go->hpi_ops = &go7007_usb_ezusb_hpi_ops; + else + go->hpi_ops = &go7007_usb_onboard_hpi_ops; + go->hpi_context = usb; + + /* Allocate the URB and buffer for receiving incoming interrupts */ + usb->intr_urb = usb_alloc_urb(0, GFP_KERNEL); + if (usb->intr_urb == NULL) + goto allocfail; + usb->intr_urb->transfer_buffer = kmalloc(2*sizeof(u16), GFP_KERNEL); + if (usb->intr_urb->transfer_buffer == NULL) + goto allocfail; + + if (go->board_id == GO7007_BOARDID_SENSORAY_2250) + usb_fill_bulk_urb(usb->intr_urb, usb->usbdev, + usb_rcvbulkpipe(usb->usbdev, 4), + usb->intr_urb->transfer_buffer, 2*sizeof(u16), + go7007_usb_readinterrupt_complete, go); + else + usb_fill_int_urb(usb->intr_urb, usb->usbdev, + usb_rcvintpipe(usb->usbdev, 4), + usb->intr_urb->transfer_buffer, 2*sizeof(u16), + go7007_usb_readinterrupt_complete, go, 8); + usb_set_intfdata(intf, &go->v4l2_dev); + + /* Boot the GO7007 */ + if (go7007_boot_encoder(go, go->board_info->flags & + GO7007_BOARD_USE_ONBOARD_I2C) < 0) + goto allocfail; + + /* Register the EZ-USB I2C adapter, if we're using it */ + if (board->flags & GO7007_USB_EZUSB_I2C) { + memcpy(&go->i2c_adapter, &go7007_usb_adap_templ, + sizeof(go7007_usb_adap_templ)); + mutex_init(&usb->i2c_lock); + go->i2c_adapter.dev.parent = go->dev; + i2c_set_adapdata(&go->i2c_adapter, go); + if (i2c_add_adapter(&go->i2c_adapter) < 0) { + dev_err(go->dev, "error: i2c_add_adapter failed\n"); + goto allocfail; + } + go->i2c_adapter_online = 1; + } + + /* Pelco and Adlink reused the XMen and XMen-III vendor and product + * IDs for their own incompatible designs. We can detect XMen boards + * by probing the sensor, but there is no way to probe the sensors on + * the Pelco and Adlink designs so we default to the Adlink. If it + * is actually a Pelco, the user must set the assume_endura module + * parameter. */ + if ((go->board_id == GO7007_BOARDID_XMEN || + go->board_id == GO7007_BOARDID_XMEN_III) && + go->i2c_adapter_online) { + union i2c_smbus_data data; + + /* Check to see if register 0x0A is 0x76 */ + i2c_smbus_xfer(&go->i2c_adapter, 0x21, I2C_CLIENT_SCCB, + I2C_SMBUS_READ, 0x0A, I2C_SMBUS_BYTE_DATA, &data); + if (data.byte != 0x76) { + if (assume_endura) { + go->board_id = GO7007_BOARDID_ENDURA; + usb->board = board = &board_endura; + go->board_info = &board->main_info; + strncpy(go->name, "Pelco Endura", + sizeof(go->name)); + } else { + u16 channel; + + /* read channel number from GPIO[1:0] */ + go7007_read_addr(go, 0x3c81, &channel); + channel &= 0x3; + go->board_id = GO7007_BOARDID_ADLINK_MPG24; + usb->board = board = &board_adlink_mpg24; + go->board_info = &board->main_info; + go->channel_number = channel; + snprintf(go->name, sizeof(go->name), + "Adlink PCI-MPG24, channel #%d", + channel); + } + go7007_update_board(go); + } + } + + num_i2c_devs = go->board_info->num_i2c_devs; + + /* Probe the tuner model on the TV402U */ + if (go->board_id == GO7007_BOARDID_PX_TV402U) { + /* Board strapping indicates tuner model */ + if (go7007_usb_vendor_request(go, 0x41, 0, 0, go->usb_buf, 3, + 1) < 0) { + dev_err(go->dev, "GPIO read failed!\n"); + goto allocfail; + } + switch (go->usb_buf[0] >> 6) { + case 1: + go->tuner_type = TUNER_SONY_BTF_PG472Z; + go->std = V4L2_STD_PAL; + strncpy(go->name, "Plextor PX-TV402U-EU", + sizeof(go->name)); + break; + case 2: + go->tuner_type = TUNER_SONY_BTF_PK467Z; + go->std = V4L2_STD_NTSC_M_JP; + num_i2c_devs -= 2; + strncpy(go->name, "Plextor PX-TV402U-JP", + sizeof(go->name)); + break; + case 3: + go->tuner_type = TUNER_SONY_BTF_PB463Z; + num_i2c_devs -= 2; + strncpy(go->name, "Plextor PX-TV402U-NA", + sizeof(go->name)); + break; + default: + pr_debug("unable to detect tuner type!\n"); + break; + } + /* Configure tuner mode selection inputs connected + * to the EZ-USB GPIO output pins */ + if (go7007_usb_vendor_request(go, 0x40, 0x7f02, 0, + NULL, 0, 0) < 0) { + dev_err(go->dev, "GPIO write failed!\n"); + goto allocfail; + } + } + + /* Print a nasty message if the user attempts to use a USB2.0 device in + * a USB1.1 port. There will be silent corruption of the stream. */ + if ((board->flags & GO7007_USB_EZUSB) && + usbdev->speed != USB_SPEED_HIGH) + dev_err(go->dev, "*** WARNING *** This device must be connected to a USB 2.0 port! Attempting to capture video through a USB 1.1 port will result in stream corruption, even at low bitrates!\n"); + + /* Allocate the URBs and buffers for receiving the video stream */ + if (board->flags & GO7007_USB_EZUSB) { + v_urb_len = 1024; + video_pipe = usb_rcvbulkpipe(usb->usbdev, 6); + } else { + v_urb_len = 512; + video_pipe = usb_rcvbulkpipe(usb->usbdev, 1); + } + for (i = 0; i < 8; ++i) { + usb->video_urbs[i] = usb_alloc_urb(0, GFP_KERNEL); + if (usb->video_urbs[i] == NULL) + goto allocfail; + usb->video_urbs[i]->transfer_buffer = + kmalloc(v_urb_len, GFP_KERNEL); + if (usb->video_urbs[i]->transfer_buffer == NULL) + goto allocfail; + usb_fill_bulk_urb(usb->video_urbs[i], usb->usbdev, video_pipe, + usb->video_urbs[i]->transfer_buffer, v_urb_len, + go7007_usb_read_video_pipe_complete, go); + } + + /* Allocate the URBs and buffers for receiving the audio stream */ + if ((board->flags & GO7007_USB_EZUSB) && + (board->flags & GO7007_BOARD_HAS_AUDIO)) { + for (i = 0; i < 8; ++i) { + usb->audio_urbs[i] = usb_alloc_urb(0, GFP_KERNEL); + if (usb->audio_urbs[i] == NULL) + goto allocfail; + usb->audio_urbs[i]->transfer_buffer = kmalloc(4096, + GFP_KERNEL); + if (usb->audio_urbs[i]->transfer_buffer == NULL) + goto allocfail; + usb_fill_bulk_urb(usb->audio_urbs[i], usb->usbdev, + usb_rcvbulkpipe(usb->usbdev, 8), + usb->audio_urbs[i]->transfer_buffer, 4096, + go7007_usb_read_audio_pipe_complete, go); + } + } + + /* Do any final GO7007 initialization, then register the + * V4L2 and ALSA interfaces */ + if (go7007_register_encoder(go, num_i2c_devs) < 0) + goto allocfail; + + go->status = STATUS_ONLINE; + return 0; + +allocfail: + go7007_usb_release(go); + kfree(go); + return -ENOMEM; +} + +static void go7007_usb_disconnect(struct usb_interface *intf) +{ + struct go7007 *go = to_go7007(usb_get_intfdata(intf)); + + mutex_lock(&go->queue_lock); + mutex_lock(&go->serialize_lock); + + if (go->audio_enabled) + go7007_snd_remove(go); + + go->status = STATUS_SHUTDOWN; + v4l2_device_disconnect(&go->v4l2_dev); + video_unregister_device(&go->vdev); + mutex_unlock(&go->serialize_lock); + mutex_unlock(&go->queue_lock); + + v4l2_device_put(&go->v4l2_dev); +} + +static struct usb_driver go7007_usb_driver = { + .name = "go7007", + .probe = go7007_usb_probe, + .disconnect = go7007_usb_disconnect, + .id_table = go7007_usb_id_table, +}; + +module_usb_driver(go7007_usb_driver); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/staging/media/go7007/go7007-v4l2.c b/drivers/staging/media/go7007/go7007-v4l2.c new file mode 100644 index 00000000000..da7b5493e13 --- /dev/null +++ b/drivers/staging/media/go7007/go7007-v4l2.c @@ -0,0 +1,1055 @@ +/* + * Copyright (C) 2005-2006 Micronas USA Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License (Version 2) as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA. + */ + +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/sched.h> +#include <linux/spinlock.h> +#include <linux/slab.h> +#include <linux/fs.h> +#include <linux/unistd.h> +#include <linux/time.h> +#include <linux/vmalloc.h> +#include <linux/pagemap.h> +#include <linux/i2c.h> +#include <linux/mutex.h> +#include <linux/uaccess.h> +#include <linux/videodev2.h> +#include <media/v4l2-common.h> +#include <media/v4l2-ioctl.h> +#include <media/v4l2-subdev.h> +#include <media/v4l2-event.h> +#include <media/videobuf2-vmalloc.h> +#include <media/saa7115.h> + +#include "go7007.h" +#include "go7007-priv.h" + +#define call_all(dev, o, f, args...) \ + v4l2_device_call_until_err(dev, 0, o, f, ##args) + +static bool valid_pixelformat(u32 pixelformat) +{ + switch (pixelformat) { + case V4L2_PIX_FMT_MJPEG: + case V4L2_PIX_FMT_MPEG1: + case V4L2_PIX_FMT_MPEG2: + case V4L2_PIX_FMT_MPEG4: + return true; + default: + return false; + } +} + +static u32 get_frame_type_flag(struct go7007_buffer *vb, int format) +{ + u8 *ptr = vb2_plane_vaddr(&vb->vb, 0); + + switch (format) { + case V4L2_PIX_FMT_MJPEG: + return V4L2_BUF_FLAG_KEYFRAME; + case V4L2_PIX_FMT_MPEG4: + switch ((ptr[vb->frame_offset + 4] >> 6) & 0x3) { + case 0: + return V4L2_BUF_FLAG_KEYFRAME; + case 1: + return V4L2_BUF_FLAG_PFRAME; + case 2: + return V4L2_BUF_FLAG_BFRAME; + default: + return 0; + } + case V4L2_PIX_FMT_MPEG1: + case V4L2_PIX_FMT_MPEG2: + switch ((ptr[vb->frame_offset + 5] >> 3) & 0x7) { + case 1: + return V4L2_BUF_FLAG_KEYFRAME; + case 2: + return V4L2_BUF_FLAG_PFRAME; + case 3: + return V4L2_BUF_FLAG_BFRAME; + default: + return 0; + } + } + + return 0; +} + +static void get_resolution(struct go7007 *go, int *width, int *height) +{ + switch (go->standard) { + case GO7007_STD_NTSC: + *width = 720; + *height = 480; + break; + case GO7007_STD_PAL: + *width = 720; + *height = 576; + break; + case GO7007_STD_OTHER: + default: + *width = go->board_info->sensor_width; + *height = go->board_info->sensor_height; + break; + } +} + +static void set_formatting(struct go7007 *go) +{ + if (go->format == V4L2_PIX_FMT_MJPEG) { + go->pali = 0; + go->aspect_ratio = GO7007_RATIO_1_1; + go->gop_size = 0; + go->ipb = 0; + go->closed_gop = 0; + go->repeat_seqhead = 0; + go->seq_header_enable = 0; + go->gop_header_enable = 0; + go->dvd_mode = 0; + return; + } + + switch (go->format) { + case V4L2_PIX_FMT_MPEG1: + go->pali = 0; + break; + default: + case V4L2_PIX_FMT_MPEG2: + go->pali = 0x48; + break; + case V4L2_PIX_FMT_MPEG4: + /* For future reference: this is the list of MPEG4 + * profiles that are available, although they are + * untested: + * + * Profile pali + * -------------- ---- + * PROFILE_S_L0 0x08 + * PROFILE_S_L1 0x01 + * PROFILE_S_L2 0x02 + * PROFILE_S_L3 0x03 + * PROFILE_ARTS_L1 0x91 + * PROFILE_ARTS_L2 0x92 + * PROFILE_ARTS_L3 0x93 + * PROFILE_ARTS_L4 0x94 + * PROFILE_AS_L0 0xf0 + * PROFILE_AS_L1 0xf1 + * PROFILE_AS_L2 0xf2 + * PROFILE_AS_L3 0xf3 + * PROFILE_AS_L4 0xf4 + * PROFILE_AS_L5 0xf5 + */ + go->pali = 0xf5; + break; + } + go->gop_size = v4l2_ctrl_g_ctrl(go->mpeg_video_gop_size); + go->closed_gop = v4l2_ctrl_g_ctrl(go->mpeg_video_gop_closure); + go->ipb = v4l2_ctrl_g_ctrl(go->mpeg_video_b_frames) != 0; + go->bitrate = v4l2_ctrl_g_ctrl(go->mpeg_video_bitrate); + go->repeat_seqhead = v4l2_ctrl_g_ctrl(go->mpeg_video_rep_seqheader); + go->gop_header_enable = 1; + go->dvd_mode = 0; + if (go->format == V4L2_PIX_FMT_MPEG2) + go->dvd_mode = + go->bitrate == 9800000 && + go->gop_size == 15 && + go->ipb == 0 && + go->repeat_seqhead == 1 && + go->closed_gop; + + switch (v4l2_ctrl_g_ctrl(go->mpeg_video_aspect_ratio)) { + default: + case V4L2_MPEG_VIDEO_ASPECT_1x1: + go->aspect_ratio = GO7007_RATIO_1_1; + break; + case V4L2_MPEG_VIDEO_ASPECT_4x3: + go->aspect_ratio = GO7007_RATIO_4_3; + break; + case V4L2_MPEG_VIDEO_ASPECT_16x9: + go->aspect_ratio = GO7007_RATIO_16_9; + break; + } +} + +static int set_capture_size(struct go7007 *go, struct v4l2_format *fmt, int try) +{ + int sensor_height = 0, sensor_width = 0; + int width, height, i; + + if (fmt != NULL && !valid_pixelformat(fmt->fmt.pix.pixelformat)) + return -EINVAL; + + get_resolution(go, &sensor_width, &sensor_height); + + if (fmt == NULL) { + width = sensor_width; + height = sensor_height; + } else if (go->board_info->sensor_flags & GO7007_SENSOR_SCALING) { + if (fmt->fmt.pix.width > sensor_width) + width = sensor_width; + else if (fmt->fmt.pix.width < 144) + width = 144; + else + width = fmt->fmt.pix.width & ~0x0f; + + if (fmt->fmt.pix.height > sensor_height) + height = sensor_height; + else if (fmt->fmt.pix.height < 96) + height = 96; + else + height = fmt->fmt.pix.height & ~0x0f; + } else { + width = fmt->fmt.pix.width; + + if (width <= sensor_width / 4) { + width = sensor_width / 4; + height = sensor_height / 4; + } else if (width <= sensor_width / 2) { + width = sensor_width / 2; + height = sensor_height / 2; + } else { + width = sensor_width; + height = sensor_height; + } + width &= ~0xf; + height &= ~0xf; + } + + if (fmt != NULL) { + u32 pixelformat = fmt->fmt.pix.pixelformat; + + memset(fmt, 0, sizeof(*fmt)); + fmt->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + fmt->fmt.pix.width = width; + fmt->fmt.pix.height = height; + fmt->fmt.pix.pixelformat = pixelformat; + fmt->fmt.pix.field = V4L2_FIELD_NONE; + fmt->fmt.pix.bytesperline = 0; + fmt->fmt.pix.sizeimage = GO7007_BUF_SIZE; + fmt->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M; + } + + if (try) + return 0; + + if (fmt) + go->format = fmt->fmt.pix.pixelformat; + go->width = width; + go->height = height; + go->encoder_h_offset = go->board_info->sensor_h_offset; + go->encoder_v_offset = go->board_info->sensor_v_offset; + for (i = 0; i < 4; ++i) + go->modet[i].enable = 0; + for (i = 0; i < 1624; ++i) + go->modet_map[i] = 0; + + if (go->board_info->sensor_flags & GO7007_SENSOR_SCALING) { + struct v4l2_mbus_framefmt mbus_fmt; + + mbus_fmt.code = V4L2_MBUS_FMT_FIXED; + mbus_fmt.width = fmt ? fmt->fmt.pix.width : width; + mbus_fmt.height = height; + go->encoder_h_halve = 0; + go->encoder_v_halve = 0; + go->encoder_subsample = 0; + call_all(&go->v4l2_dev, video, s_mbus_fmt, &mbus_fmt); + } else { + if (width <= sensor_width / 4) { + go->encoder_h_halve = 1; + go->encoder_v_halve = 1; + go->encoder_subsample = 1; + } else if (width <= sensor_width / 2) { + go->encoder_h_halve = 1; + go->encoder_v_halve = 1; + go->encoder_subsample = 0; + } else { + go->encoder_h_halve = 0; + go->encoder_v_halve = 0; + go->encoder_subsample = 0; + } + } + return 0; +} + +#if 0 +static int clip_to_modet_map(struct go7007 *go, int region, + struct v4l2_clip *clip_list) +{ + struct v4l2_clip clip, *clip_ptr; + int x, y, mbnum; + + /* Check if coordinates are OK and if any macroblocks are already + * used by other regions (besides 0) */ + clip_ptr = clip_list; + while (clip_ptr) { + if (copy_from_user(&clip, clip_ptr, sizeof(clip))) + return -EFAULT; + if (clip.c.left < 0 || (clip.c.left & 0xF) || + clip.c.width <= 0 || (clip.c.width & 0xF)) + return -EINVAL; + if (clip.c.left + clip.c.width > go->width) + return -EINVAL; + if (clip.c.top < 0 || (clip.c.top & 0xF) || + clip.c.height <= 0 || (clip.c.height & 0xF)) + return -EINVAL; + if (clip.c.top + clip.c.height > go->height) + return -EINVAL; + for (y = 0; y < clip.c.height; y += 16) + for (x = 0; x < clip.c.width; x += 16) { + mbnum = (go->width >> 4) * + ((clip.c.top + y) >> 4) + + ((clip.c.left + x) >> 4); + if (go->modet_map[mbnum] != 0 && + go->modet_map[mbnum] != region) + return -EBUSY; + } + clip_ptr = clip.next; + } + + /* Clear old region macroblocks */ + for (mbnum = 0; mbnum < 1624; ++mbnum) + if (go->modet_map[mbnum] == region) + go->modet_map[mbnum] = 0; + + /* Claim macroblocks in this list */ + clip_ptr = clip_list; + while (clip_ptr) { + if (copy_from_user(&clip, clip_ptr, sizeof(clip))) + return -EFAULT; + for (y = 0; y < clip.c.height; y += 16) + for (x = 0; x < clip.c.width; x += 16) { + mbnum = (go->width >> 4) * + ((clip.c.top + y) >> 4) + + ((clip.c.left + x) >> 4); + go->modet_map[mbnum] = region; + } + clip_ptr = clip.next; + } + return 0; +} +#endif + +static int vidioc_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + struct go7007 *go = video_drvdata(file); + + strlcpy(cap->driver, "go7007", sizeof(cap->driver)); + strlcpy(cap->card, go->name, sizeof(cap->card)); + strlcpy(cap->bus_info, go->bus_info, sizeof(cap->bus_info)); + + cap->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_READWRITE | + V4L2_CAP_STREAMING; + + if (go->board_info->num_aud_inputs) + cap->device_caps |= V4L2_CAP_AUDIO; + if (go->board_info->flags & GO7007_BOARD_HAS_TUNER) + cap->device_caps |= V4L2_CAP_TUNER; + cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS; + return 0; +} + +static int vidioc_enum_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *fmt) +{ + char *desc = NULL; + + switch (fmt->index) { + case 0: + fmt->pixelformat = V4L2_PIX_FMT_MJPEG; + desc = "Motion JPEG"; + break; + case 1: + fmt->pixelformat = V4L2_PIX_FMT_MPEG1; + desc = "MPEG-1 ES"; + break; + case 2: + fmt->pixelformat = V4L2_PIX_FMT_MPEG2; + desc = "MPEG-2 ES"; + break; + case 3: + fmt->pixelformat = V4L2_PIX_FMT_MPEG4; + desc = "MPEG-4 ES"; + break; + default: + return -EINVAL; + } + fmt->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + fmt->flags = V4L2_FMT_FLAG_COMPRESSED; + + strncpy(fmt->description, desc, sizeof(fmt->description)); + + return 0; +} + +static int vidioc_g_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *fmt) +{ + struct go7007 *go = video_drvdata(file); + + fmt->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + fmt->fmt.pix.width = go->width; + fmt->fmt.pix.height = go->height; + fmt->fmt.pix.pixelformat = go->format; + fmt->fmt.pix.field = V4L2_FIELD_NONE; + fmt->fmt.pix.bytesperline = 0; + fmt->fmt.pix.sizeimage = GO7007_BUF_SIZE; + fmt->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M; + + return 0; +} + +static int vidioc_try_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *fmt) +{ + struct go7007 *go = video_drvdata(file); + + return set_capture_size(go, fmt, 1); +} + +static int vidioc_s_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *fmt) +{ + struct go7007 *go = video_drvdata(file); + + if (vb2_is_busy(&go->vidq)) + return -EBUSY; + + return set_capture_size(go, fmt, 0); +} + +static int go7007_queue_setup(struct vb2_queue *q, + const struct v4l2_format *fmt, + unsigned int *num_buffers, unsigned int *num_planes, + unsigned int sizes[], void *alloc_ctxs[]) +{ + sizes[0] = GO7007_BUF_SIZE; + *num_planes = 1; + + if (*num_buffers < 2) + *num_buffers = 2; + + return 0; +} + +static void go7007_buf_queue(struct vb2_buffer *vb) +{ + struct vb2_queue *vq = vb->vb2_queue; + struct go7007 *go = vb2_get_drv_priv(vq); + struct go7007_buffer *go7007_vb = + container_of(vb, struct go7007_buffer, vb); + unsigned long flags; + + spin_lock_irqsave(&go->spinlock, flags); + list_add_tail(&go7007_vb->list, &go->vidq_active); + spin_unlock_irqrestore(&go->spinlock, flags); +} + +static int go7007_buf_prepare(struct vb2_buffer *vb) +{ + struct go7007_buffer *go7007_vb = + container_of(vb, struct go7007_buffer, vb); + + go7007_vb->modet_active = 0; + go7007_vb->frame_offset = 0; + vb->v4l2_planes[0].bytesused = 0; + return 0; +} + +static void go7007_buf_finish(struct vb2_buffer *vb) +{ + struct vb2_queue *vq = vb->vb2_queue; + struct go7007 *go = vb2_get_drv_priv(vq); + struct go7007_buffer *go7007_vb = + container_of(vb, struct go7007_buffer, vb); + u32 frame_type_flag = get_frame_type_flag(go7007_vb, go->format); + struct v4l2_buffer *buf = &vb->v4l2_buf; + + buf->flags &= ~(V4L2_BUF_FLAG_KEYFRAME | V4L2_BUF_FLAG_BFRAME | + V4L2_BUF_FLAG_PFRAME); + buf->flags |= frame_type_flag; + buf->field = V4L2_FIELD_NONE; +} + +static int go7007_start_streaming(struct vb2_queue *q, unsigned int count) +{ + struct go7007 *go = vb2_get_drv_priv(q); + int ret; + + set_formatting(go); + mutex_lock(&go->hw_lock); + go->next_seq = 0; + go->active_buf = NULL; + q->streaming = 1; + if (go7007_start_encoder(go) < 0) + ret = -EIO; + else + ret = 0; + mutex_unlock(&go->hw_lock); + if (ret) { + q->streaming = 0; + return ret; + } + call_all(&go->v4l2_dev, video, s_stream, 1); + v4l2_ctrl_grab(go->mpeg_video_gop_size, true); + v4l2_ctrl_grab(go->mpeg_video_gop_closure, true); + v4l2_ctrl_grab(go->mpeg_video_bitrate, true); + v4l2_ctrl_grab(go->mpeg_video_aspect_ratio, true); + /* Turn on Capture LED */ + if (go->board_id == GO7007_BOARDID_ADS_USBAV_709) + go7007_write_addr(go, 0x3c82, 0x0005); + return ret; +} + +static void go7007_stop_streaming(struct vb2_queue *q) +{ + struct go7007 *go = vb2_get_drv_priv(q); + unsigned long flags; + + q->streaming = 0; + go7007_stream_stop(go); + mutex_lock(&go->hw_lock); + go7007_reset_encoder(go); + mutex_unlock(&go->hw_lock); + call_all(&go->v4l2_dev, video, s_stream, 0); + + spin_lock_irqsave(&go->spinlock, flags); + INIT_LIST_HEAD(&go->vidq_active); + spin_unlock_irqrestore(&go->spinlock, flags); + v4l2_ctrl_grab(go->mpeg_video_gop_size, false); + v4l2_ctrl_grab(go->mpeg_video_gop_closure, false); + v4l2_ctrl_grab(go->mpeg_video_bitrate, false); + v4l2_ctrl_grab(go->mpeg_video_aspect_ratio, false); + /* Turn on Capture LED */ + if (go->board_id == GO7007_BOARDID_ADS_USBAV_709) + go7007_write_addr(go, 0x3c82, 0x000d); +} + +static struct vb2_ops go7007_video_qops = { + .queue_setup = go7007_queue_setup, + .buf_queue = go7007_buf_queue, + .buf_prepare = go7007_buf_prepare, + .buf_finish = go7007_buf_finish, + .start_streaming = go7007_start_streaming, + .stop_streaming = go7007_stop_streaming, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, +}; + +static int vidioc_g_parm(struct file *filp, void *priv, + struct v4l2_streamparm *parm) +{ + struct go7007 *go = video_drvdata(filp); + struct v4l2_fract timeperframe = { + .numerator = 1001 * go->fps_scale, + .denominator = go->sensor_framerate, + }; + + if (parm->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + parm->parm.capture.readbuffers = 2; + parm->parm.capture.capability = V4L2_CAP_TIMEPERFRAME; + parm->parm.capture.timeperframe = timeperframe; + + return 0; +} + +static int vidioc_s_parm(struct file *filp, void *priv, + struct v4l2_streamparm *parm) +{ + struct go7007 *go = video_drvdata(filp); + unsigned int n, d; + + if (parm->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + n = go->sensor_framerate * + parm->parm.capture.timeperframe.numerator; + d = 1001 * parm->parm.capture.timeperframe.denominator; + if (n != 0 && d != 0 && n > d) + go->fps_scale = (n + d/2) / d; + else + go->fps_scale = 1; + + return vidioc_g_parm(filp, priv, parm); +} + +/* VIDIOC_ENUMSTD on go7007 were used for enumerating the supported fps and + its resolution, when the device is not connected to TV. + This is were an API abuse, probably used by the lack of specific IOCTL's to + enumerate it, by the time the driver was written. + + However, since kernel 2.6.19, two new ioctls (VIDIOC_ENUM_FRAMEINTERVALS + and VIDIOC_ENUM_FRAMESIZES) were added for this purpose. + + The two functions below implement the newer ioctls +*/ +static int vidioc_enum_framesizes(struct file *filp, void *priv, + struct v4l2_frmsizeenum *fsize) +{ + struct go7007 *go = video_drvdata(filp); + int width, height; + + if (fsize->index > 2) + return -EINVAL; + + if (!valid_pixelformat(fsize->pixel_format)) + return -EINVAL; + + get_resolution(go, &width, &height); + fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE; + fsize->discrete.width = (width >> fsize->index) & ~0xf; + fsize->discrete.height = (height >> fsize->index) & ~0xf; + return 0; +} + +static int vidioc_enum_frameintervals(struct file *filp, void *priv, + struct v4l2_frmivalenum *fival) +{ + struct go7007 *go = video_drvdata(filp); + int width, height; + int i; + + if (fival->index > 4) + return -EINVAL; + + if (!valid_pixelformat(fival->pixel_format)) + return -EINVAL; + + if (!(go->board_info->sensor_flags & GO7007_SENSOR_SCALING)) { + get_resolution(go, &width, &height); + for (i = 0; i <= 2; i++) + if (fival->width == ((width >> i) & ~0xf) && + fival->height == ((height >> i) & ~0xf)) + break; + if (i > 2) + return -EINVAL; + } + fival->type = V4L2_FRMIVAL_TYPE_DISCRETE; + fival->discrete.numerator = 1001 * (fival->index + 1); + fival->discrete.denominator = go->sensor_framerate; + return 0; +} + +static int vidioc_g_std(struct file *file, void *priv, v4l2_std_id *std) +{ + struct go7007 *go = video_drvdata(file); + + *std = go->std; + return 0; +} + +static int go7007_s_std(struct go7007 *go) +{ + if (go->std & V4L2_STD_625_50) { + go->standard = GO7007_STD_PAL; + go->sensor_framerate = 25025; + } else { + go->standard = GO7007_STD_NTSC; + go->sensor_framerate = 30000; + } + + call_all(&go->v4l2_dev, video, s_std, go->std); + set_capture_size(go, NULL, 0); + return 0; +} + +static int vidioc_s_std(struct file *file, void *priv, v4l2_std_id std) +{ + struct go7007 *go = video_drvdata(file); + + if (vb2_is_busy(&go->vidq)) + return -EBUSY; + + go->std = std; + + return go7007_s_std(go); +} + +static int vidioc_querystd(struct file *file, void *priv, v4l2_std_id *std) +{ + struct go7007 *go = video_drvdata(file); + + return call_all(&go->v4l2_dev, video, querystd, std); +} + +static int vidioc_enum_input(struct file *file, void *priv, + struct v4l2_input *inp) +{ + struct go7007 *go = video_drvdata(file); + + if (inp->index >= go->board_info->num_inputs) + return -EINVAL; + + strncpy(inp->name, go->board_info->inputs[inp->index].name, + sizeof(inp->name)); + + /* If this board has a tuner, it will be the first input */ + if ((go->board_info->flags & GO7007_BOARD_HAS_TUNER) && + inp->index == 0) + inp->type = V4L2_INPUT_TYPE_TUNER; + else + inp->type = V4L2_INPUT_TYPE_CAMERA; + + if (go->board_info->num_aud_inputs) + inp->audioset = (1 << go->board_info->num_aud_inputs) - 1; + else + inp->audioset = 0; + inp->tuner = 0; + if (go->board_info->sensor_flags & GO7007_SENSOR_TV) + inp->std = video_devdata(file)->tvnorms; + else + inp->std = 0; + + return 0; +} + + +static int vidioc_g_input(struct file *file, void *priv, unsigned int *input) +{ + struct go7007 *go = video_drvdata(file); + + *input = go->input; + + return 0; +} + +static int vidioc_enumaudio(struct file *file, void *fh, struct v4l2_audio *a) +{ + struct go7007 *go = video_drvdata(file); + + if (a->index >= go->board_info->num_aud_inputs) + return -EINVAL; + strlcpy(a->name, go->board_info->aud_inputs[a->index].name, + sizeof(a->name)); + a->capability = V4L2_AUDCAP_STEREO; + return 0; +} + +static int vidioc_g_audio(struct file *file, void *fh, struct v4l2_audio *a) +{ + struct go7007 *go = video_drvdata(file); + + a->index = go->aud_input; + strlcpy(a->name, go->board_info->aud_inputs[go->aud_input].name, + sizeof(a->name)); + a->capability = V4L2_AUDCAP_STEREO; + return 0; +} + +static int vidioc_s_audio(struct file *file, void *fh, + const struct v4l2_audio *a) +{ + struct go7007 *go = video_drvdata(file); + + if (a->index >= go->board_info->num_aud_inputs) + return -EINVAL; + go->aud_input = a->index; + v4l2_subdev_call(go->sd_audio, audio, s_routing, + go->board_info->aud_inputs[go->aud_input].audio_input, 0, 0); + return 0; +} + +static void go7007_s_input(struct go7007 *go) +{ + unsigned int input = go->input; + + v4l2_subdev_call(go->sd_video, video, s_routing, + go->board_info->inputs[input].video_input, 0, + go->board_info->video_config); + if (go->board_info->num_aud_inputs) { + int aud_input = go->board_info->inputs[input].audio_index; + + v4l2_subdev_call(go->sd_audio, audio, s_routing, + go->board_info->aud_inputs[aud_input].audio_input, 0, 0); + go->aud_input = aud_input; + } +} + +static int vidioc_s_input(struct file *file, void *priv, unsigned int input) +{ + struct go7007 *go = video_drvdata(file); + + if (input >= go->board_info->num_inputs) + return -EINVAL; + if (vb2_is_busy(&go->vidq)) + return -EBUSY; + + go->input = input; + go7007_s_input(go); + + return 0; +} + +static int vidioc_g_tuner(struct file *file, void *priv, + struct v4l2_tuner *t) +{ + struct go7007 *go = video_drvdata(file); + + if (t->index != 0) + return -EINVAL; + + strlcpy(t->name, "Tuner", sizeof(t->name)); + return call_all(&go->v4l2_dev, tuner, g_tuner, t); +} + +static int vidioc_s_tuner(struct file *file, void *priv, + const struct v4l2_tuner *t) +{ + struct go7007 *go = video_drvdata(file); + + if (t->index != 0) + return -EINVAL; + + return call_all(&go->v4l2_dev, tuner, s_tuner, t); +} + +static int vidioc_g_frequency(struct file *file, void *priv, + struct v4l2_frequency *f) +{ + struct go7007 *go = video_drvdata(file); + + if (f->tuner) + return -EINVAL; + + return call_all(&go->v4l2_dev, tuner, g_frequency, f); +} + +static int vidioc_s_frequency(struct file *file, void *priv, + const struct v4l2_frequency *f) +{ + struct go7007 *go = video_drvdata(file); + + if (f->tuner) + return -EINVAL; + + return call_all(&go->v4l2_dev, tuner, s_frequency, f); +} + +static int vidioc_log_status(struct file *file, void *priv) +{ + struct go7007 *go = video_drvdata(file); + + v4l2_ctrl_log_status(file, priv); + return call_all(&go->v4l2_dev, core, log_status); +} + +/* FIXME: + Those ioctls are private, and not needed, since several standard + extended controls already provide streaming control. + So, those ioctls should be converted into vidioc_g_ext_ctrls() + and vidioc_s_ext_ctrls() + */ + +#if 0 + case GO7007IOC_S_MD_PARAMS: + { + struct go7007_md_params *mdp = arg; + + if (mdp->region > 3) + return -EINVAL; + if (mdp->trigger > 0) { + go->modet[mdp->region].pixel_threshold = + mdp->pixel_threshold >> 1; + go->modet[mdp->region].motion_threshold = + mdp->motion_threshold >> 1; + go->modet[mdp->region].mb_threshold = + mdp->trigger >> 1; + go->modet[mdp->region].enable = 1; + } else + go->modet[mdp->region].enable = 0; + /* fall-through */ + } + case GO7007IOC_S_MD_REGION: + { + struct go7007_md_region *region = arg; + + if (region->region < 1 || region->region > 3) + return -EINVAL; + return clip_to_modet_map(go, region->region, region->clips); + } +#endif + +static struct v4l2_file_operations go7007_fops = { + .owner = THIS_MODULE, + .open = v4l2_fh_open, + .release = vb2_fop_release, + .unlocked_ioctl = video_ioctl2, + .read = vb2_fop_read, + .mmap = vb2_fop_mmap, + .poll = vb2_fop_poll, +}; + +static const struct v4l2_ioctl_ops video_ioctl_ops = { + .vidioc_querycap = vidioc_querycap, + .vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_vid_cap, + .vidioc_g_fmt_vid_cap = vidioc_g_fmt_vid_cap, + .vidioc_try_fmt_vid_cap = vidioc_try_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = vidioc_s_fmt_vid_cap, + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_g_std = vidioc_g_std, + .vidioc_s_std = vidioc_s_std, + .vidioc_querystd = vidioc_querystd, + .vidioc_enum_input = vidioc_enum_input, + .vidioc_g_input = vidioc_g_input, + .vidioc_s_input = vidioc_s_input, + .vidioc_enumaudio = vidioc_enumaudio, + .vidioc_g_audio = vidioc_g_audio, + .vidioc_s_audio = vidioc_s_audio, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, + .vidioc_g_tuner = vidioc_g_tuner, + .vidioc_s_tuner = vidioc_s_tuner, + .vidioc_g_frequency = vidioc_g_frequency, + .vidioc_s_frequency = vidioc_s_frequency, + .vidioc_g_parm = vidioc_g_parm, + .vidioc_s_parm = vidioc_s_parm, + .vidioc_enum_framesizes = vidioc_enum_framesizes, + .vidioc_enum_frameintervals = vidioc_enum_frameintervals, + .vidioc_log_status = vidioc_log_status, + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +static struct video_device go7007_template = { + .name = "go7007", + .fops = &go7007_fops, + .release = video_device_release_empty, + .ioctl_ops = &video_ioctl_ops, + .tvnorms = V4L2_STD_ALL, +}; + +int go7007_v4l2_ctrl_init(struct go7007 *go) +{ + struct v4l2_ctrl_handler *hdl = &go->hdl; + struct v4l2_ctrl *ctrl; + + v4l2_ctrl_handler_init(hdl, 13); + go->mpeg_video_gop_size = v4l2_ctrl_new_std(hdl, NULL, + V4L2_CID_MPEG_VIDEO_GOP_SIZE, 0, 34, 1, 15); + go->mpeg_video_gop_closure = v4l2_ctrl_new_std(hdl, NULL, + V4L2_CID_MPEG_VIDEO_GOP_CLOSURE, 0, 1, 1, 1); + go->mpeg_video_bitrate = v4l2_ctrl_new_std(hdl, NULL, + V4L2_CID_MPEG_VIDEO_BITRATE, + 64000, 10000000, 1, 9800000); + go->mpeg_video_b_frames = v4l2_ctrl_new_std(hdl, NULL, + V4L2_CID_MPEG_VIDEO_B_FRAMES, 0, 2, 2, 0); + go->mpeg_video_rep_seqheader = v4l2_ctrl_new_std(hdl, NULL, + V4L2_CID_MPEG_VIDEO_REPEAT_SEQ_HEADER, 0, 1, 1, 1); + + go->mpeg_video_aspect_ratio = v4l2_ctrl_new_std_menu(hdl, NULL, + V4L2_CID_MPEG_VIDEO_ASPECT, + V4L2_MPEG_VIDEO_ASPECT_16x9, 0, + V4L2_MPEG_VIDEO_ASPECT_1x1); + ctrl = v4l2_ctrl_new_std(hdl, NULL, + V4L2_CID_JPEG_ACTIVE_MARKER, 0, + V4L2_JPEG_ACTIVE_MARKER_DQT | + V4L2_JPEG_ACTIVE_MARKER_DHT, 0, + V4L2_JPEG_ACTIVE_MARKER_DQT | + V4L2_JPEG_ACTIVE_MARKER_DHT); + if (ctrl) + ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY; + if (hdl->error) { + int rv = hdl->error; + + v4l2_err(&go->v4l2_dev, "Could not register controls\n"); + return rv; + } + go->v4l2_dev.ctrl_handler = hdl; + return 0; +} + +int go7007_v4l2_init(struct go7007 *go) +{ + struct video_device *vdev = &go->vdev; + int rv; + + mutex_init(&go->serialize_lock); + mutex_init(&go->queue_lock); + + INIT_LIST_HEAD(&go->vidq_active); + go->vidq.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + go->vidq.io_modes = VB2_MMAP | VB2_USERPTR | VB2_READ; + go->vidq.ops = &go7007_video_qops; + go->vidq.mem_ops = &vb2_vmalloc_memops; + go->vidq.drv_priv = go; + go->vidq.buf_struct_size = sizeof(struct go7007_buffer); + go->vidq.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + go->vidq.lock = &go->queue_lock; + rv = vb2_queue_init(&go->vidq); + if (rv) + return rv; + *vdev = go7007_template; + vdev->lock = &go->serialize_lock; + vdev->queue = &go->vidq; + set_bit(V4L2_FL_USE_FH_PRIO, &vdev->flags); + video_set_drvdata(vdev, go); + vdev->v4l2_dev = &go->v4l2_dev; + if (!v4l2_device_has_op(&go->v4l2_dev, video, querystd)) + v4l2_disable_ioctl(vdev, VIDIOC_QUERYSTD); + if (!(go->board_info->flags & GO7007_BOARD_HAS_TUNER)) { + v4l2_disable_ioctl(vdev, VIDIOC_S_FREQUENCY); + v4l2_disable_ioctl(vdev, VIDIOC_G_FREQUENCY); + v4l2_disable_ioctl(vdev, VIDIOC_S_TUNER); + v4l2_disable_ioctl(vdev, VIDIOC_G_TUNER); + } else { + struct v4l2_frequency f = { + .type = V4L2_TUNER_ANALOG_TV, + .frequency = 980, + }; + + call_all(&go->v4l2_dev, tuner, s_frequency, &f); + } + if (!(go->board_info->sensor_flags & GO7007_SENSOR_TV)) { + v4l2_disable_ioctl(vdev, VIDIOC_G_STD); + v4l2_disable_ioctl(vdev, VIDIOC_S_STD); + vdev->tvnorms = 0; + } + if (go->board_info->sensor_flags & GO7007_SENSOR_SCALING) + v4l2_disable_ioctl(vdev, VIDIOC_ENUM_FRAMESIZES); + if (go->board_info->num_aud_inputs == 0) { + v4l2_disable_ioctl(vdev, VIDIOC_G_AUDIO); + v4l2_disable_ioctl(vdev, VIDIOC_S_AUDIO); + v4l2_disable_ioctl(vdev, VIDIOC_ENUMAUDIO); + } + /* Setup correct crystal frequency on this board */ + if (go->board_info->sensor_flags & GO7007_SENSOR_SAA7115) + v4l2_subdev_call(go->sd_video, video, s_crystal_freq, + SAA7115_FREQ_24_576_MHZ, + SAA7115_FREQ_FL_APLL | SAA7115_FREQ_FL_UCGC | + SAA7115_FREQ_FL_DOUBLE_ASCLK); + go7007_s_input(go); + if (go->board_info->sensor_flags & GO7007_SENSOR_TV) + go7007_s_std(go); + rv = video_register_device(vdev, VFL_TYPE_GRABBER, -1); + if (rv < 0) + return rv; + dev_info(go->dev, "registered device %s [v4l2]\n", + video_device_node_name(vdev)); + + return 0; +} + +void go7007_v4l2_remove(struct go7007 *go) +{ + v4l2_ctrl_handler_free(&go->hdl); +} diff --git a/drivers/staging/media/go7007/go7007.h b/drivers/staging/media/go7007/go7007.h new file mode 100644 index 00000000000..54b98973898 --- /dev/null +++ b/drivers/staging/media/go7007/go7007.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2005-2006 Micronas USA Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and the associated README documentation file (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +struct go7007_md_params { + __u16 region; + __u16 trigger; + __u16 pixel_threshold; + __u16 motion_threshold; + __u32 reserved[8]; +}; + +struct go7007_md_region { + __u16 region; + __u16 flags; + struct v4l2_clip *clips; + __u32 reserved[8]; +}; + +#define GO7007IOC_S_MD_PARAMS _IOWR('V', BASE_VIDIOC_PRIVATE + 6, \ + struct go7007_md_params) +#define GO7007IOC_G_MD_PARAMS _IOR('V', BASE_VIDIOC_PRIVATE + 7, \ + struct go7007_md_params) +#define GO7007IOC_S_MD_REGION _IOW('V', BASE_VIDIOC_PRIVATE + 8, \ + struct go7007_md_region) diff --git a/drivers/staging/media/go7007/go7007.txt b/drivers/staging/media/go7007/go7007.txt new file mode 100644 index 00000000000..c8e5eb09d38 --- /dev/null +++ b/drivers/staging/media/go7007/go7007.txt @@ -0,0 +1,478 @@ +This is a driver for the WIS GO7007SB multi-format video encoder. + +Pete Eberlein <pete@sensoray.com> + +The driver was originally released under the GPL and is currently hosted at: +http://nikosapi.org/wiki/index.php/WIS_Go7007_Linux_driver +The go7007 firmware can be acquired from the package on the site above. + +I've modified the driver to support the following Video4Linux2 MPEG +controls, with acceptable values: + +V4L2_CID_MPEG_STREAM_TYPE V4L2_MPEG_STREAM_TYPE_MPEG2_DVD + V4L2_MPEG_STREAM_TYPE_MPEG_ELEM +V4L2_CID_MPEG_VIDEO_ENCODING V4L2_MPEG_VIDEO_ENCODING_MPEG_1 + V4L2_MPEG_VIDEO_ENCODING_MPEG_2 + V4L2_MPEG_VIDEO_ENCODING_MPEG_4 +V4L2_CID_MPEG_VIDEO_ASPECT V4L2_MPEG_VIDEO_ASPECT_1x1 + V4L2_MPEG_VIDEO_ASPECT_4x3 + V4L2_MPEG_VIDEO_ASPECT_16x9 +V4L2_CID_MPEG_VIDEO_GOP_SIZE integer +V4L2_CID_MPEG_VIDEO_BITRATE 64000 .. 10000000 + +These should be used instead of the non-standard GO7007 ioctls described +below. + + +The README files from the orignal package appear below: + +--------------------------------------------------------------------------- + WIS GO7007SB Public Linux Driver +--------------------------------------------------------------------------- + + +*** Please see the file RELEASE-NOTES for important last-minute updates *** + + + 0. OVERVIEW AND LICENSING/DISCLAIMER + + +This driver kit contains Linux drivers for the WIS GO7007SB multi-format +video encoder. Only kernel version 2.6.x is supported. The video stream +is available through the Video4Linux2 API and the audio stream is available +through the ALSA API (or the OSS emulation layer of the ALSA system). + +The files in kernel/ and hotplug/ are licensed under the GNU General Public +License Version 2 from the Free Software Foundation. A copy of the license +is included in the file COPYING. + +The example applications in apps/ and C header files in include/ are +licensed under a permissive license included in the source files which +allows copying, modification and redistribution for any purpose without +attribution. + +The firmware files included in the firmware/ directory may be freely +redistributed only in conjunction with this document; but modification, +tampering and reverse engineering are prohibited. + +MICRONAS USA, INC., MAKES NO WARRANTIES TO ANY PERSON OR ENTITY WITH +RESPECT TO THE SOFTWARE OR ANY DERIVATIVES THEREOF OR ANY SERVICES OR +LICENSES AND DISCLAIMS ALL IMPLIED WARRANTIES, INCLUDING WITHOUT LIMITATION +WARRANTIES OF MERCHANTABILITY, SUPPORT, AND FITNESS FOR A PARTICULAR +PURPOSE AND NON-INFRINGEMENT. + + + 1. SYSTEM REQUIREMENTS + + +This driver requires Linux kernel 2.6. Kernel 2.4 is not supported. Using +kernel 2.6.10 or later is recommended, as earlier kernels are known to have +unstable USB 2.0 support. + +A fully built kernel source tree must be available. Typically this will be +linked from "/lib/modules/<KERNEL VERSION>/build" for convenience. If this +link does not exist, an extra parameter will need to be passed to the +`make` command. + +All vendor-built kernels should already be configured properly. However, +for custom-built kernels, the following options need to be enabled in the +kernel as built-in or modules: + + CONFIG_MODULES - Enable loadable module support + CONFIG_FW_LOADER - Hotplug firmware loading support + CONFIG_I2C - I2C support + CONFIG_VIDEO_DEV - Video For Linux + CONFIG_SOUND - Sound card support + CONFIG_SND - Advanced Linux Sound Architecture + CONFIG_USB - Support for Host-side USB + CONFIG_USB_EHCI_HCD - EHCI HCD (USB 2.0) support + +Additionally, to use the example application, the following options need to +be enabled in the ALSA section: + + CONFIG_SND_MIXER_OSS - OSS Mixer API + CONFIG_SND_PCM_OSS - OSS PCM (digital audio) API + +The hotplug scripts, along with the fxload utility, must also be installed. +These scripts can be obtained from <http://linux-hotplug.sourceforge.net/>. +Hotplugging is used for loading firmware into the Cypruss EZ-USB chip using +fxload and for loading firmware into the driver using the firmware agent. + + + 2. COMPILING AND INSTALLING THE DRIVER + + +Most users should be able to compile the driver by simply running: + + $ make + +in the top-level directory of the driver kit. First the kernel modules +will be built, followed by the example applications. + +If the build system is unable to locate the kernel source tree for the +currently-running kernel, or if the module should be built for a kernel +other than the currently-running kernel, an additional parameter will need +to be passed to make to specify the appropriate kernel source directory: + + $ make KERNELSRC=/usr/src/linux-2.6.10-custom3 + +Once the compile completes, the driver and firmware files should be +installed by running: + + $ make install + +The kernel modules will be placed in "/lib/modules/<KERNEL VERSION>/extra" +and the firmware files will be placed in the appropriate hotplug firmware +directory, usually /lib/firmware. In addition, USB maps and scripts will +be placed in /etc/hotplug/usb to enable fxload to initialize the EZ-USB +control chip when the device is connected. + + + 3. PAL/SECAM TUNER CONFIGURATION (TV402U-EU only) + + +The PAL model of the Plextor ConvertX TV402U may require additional +configuration to correctly select the appropriate TV frequency band and +audio subchannel. + +Users with a device other than the Plextor ConvertX TV402U-EU should skip +this section. + +The wide variety of PAL TV systems used in Europe requires that additional +information about the local TV standards be passed to the driver in order +to properly tune TV channels. The two necessary parameters are (a) the PAL +TV band, and (b) the audio subchannel format in use. + +In many cases, the appropriate TV band selection is passed to the driver +from applications. However, in some cases, the application only specifies +that the driver should use PAL but not the specific information about the +appropriate TV band. To work around this issue, the correct TV band may be +specified in the "force_band" parameter to the wis-sony-tuner module: + + TV band force_band + ------- ---------- + PAL B/G B + PAL I I + PAL D/K D + SECAM L L + +If the "force_band" parameter is specified, the driver will ignore any TV +band specified by applications and will always use the band provided in the +module parameter. + +The other parameter that can be specified is the audio subchannel format. +There are several stereo audio carrier systems in use, including NICAM and +three varieties of A2. To receive audio broadcast on one of these stereo +carriers, the "force_mpx_mode" parameter must be specified to the +wis-sony-tuner module. + + TV band Audio subcarrier force_mpx_mode + ------- ---------------- -------------- + PAL B/G Mono (default) 1 + PAL B/G A2 2 + PAL B/G NICAM 3 + PAL I Mono (default) 4 + PAL I NICAM 5 + PAL D/K Mono (default) 6 + PAL D/K A2 (1) 7 + PAL D/K A2 (2) 8 + PAL D/K A2 (3) 9 + PAL D/K NICAM 10 + SECAM L Mono (default) 11 + SECAM L NICAM 12 + +If the "force_mpx_mode" parameter is not specified, the correct mono-only +mode will be chosen based on the TV band. However, the tuner will not +receive stereo audio or bilingual broadcasts correctly. + +To pass the "force_band" or "force_mpx_mode" parameters to the +wis-sony-tuner module, the following line must be added to the modprobe +configuration file, which varies from one Linux distribution to another. + + options wis-sony-tuner force_band=B force_mpx_mode=2 + +The above example would force the tuner to the PAL B/G TV band and receive +stereo audio broadcasts on the A2 carrier. + +To verify that the configuration has been placed in the correct location, +execute: + + $ modprobe -c | grep wis-sony-tuner + +If the configuration line appears, then modprobe will pass the parameters +correctly the next time the wis-sony-tuner module is loaded into the +kernel. + + + 4. TESTING THE DRIVER + + +Because few Linux applications are able to correctly capture from +Video4Linux2 devices with only compressed formats supported, the new driver +should be tested with the "gorecord" application in the apps/ directory. + +First connect a video source to the device, such as a DVD player or VCR. +This will be captured to a file for testing the driver. If an input source +is unavailable, a test file can still be captured, but the video will be +black and the audio will be silent. + +This application will auto-detect the V4L2 and ALSA/OSS device names of the +hardware and will record video and audio to an AVI file for a specified +number of seconds. For example: + + $ apps/gorecord -duration 60 capture.avi + +If this application does not successfully record an AVI file, the error +messages produced by gorecord and recorded in the system log (usually in +/var/log/messages) should provide information to help resolve the problem. + +Supplying no parameters to gorecord will cause it to probe the available +devices and exit. Use the -help flag for usage information. + + + 5. USING THE DRIVER + + +The V4L2 device implemented by the driver provides a standard compressed +format API, within the following criteria: + + * Applications that only support the original Video4Linux1 API will not + be able to communicate with this driver at all. + + * No raw video modes are supported, so applications like xawtv that + expect only uncompressed video will not function. + + * Supported compression formats are: Motion-JPEG, MPEG1, MPEG2 and MPEG4. + + * MPEG video formats are delivered as Video Elementary Streams only. + Program Stream (PS), Transport Stream (TS) and Packetized Elementary + Stream (PES) formats are not supported. + + * Video parameters such as format and input port may not be changed while + the encoder is active. + + * The audio capture device only functions when the video encoder is + actively capturing video. Attempts to read from the audio device when + the encoder is inactive will result in an I/O error. + + * The native format of the audio device is 48Khz 2-channel 16-bit + little-endian PCM, delivered through the ALSA system. No audio + compression is implemented in the hardware. ALSA may convert to other + uncompressed formats on the fly. + +The include/ directory contains a C header file describing non-standard +features of the GO7007SB encoder, which are described below: + + + GO7007IOC_S_COMP_PARAMS, GO7007IOC_G_COMP_PARAMS + + These ioctls are used to negotiate general compression parameters. + + To query the current parameters, call the GO7007IOC_G_COMP_PARAMS ioctl + with a pointer to a struct go7007_comp_params. If the driver is not + set to MPEG format, the EINVAL error code will be returned. + + To change the current parameters, initialize all fields of a struct + go7007_comp_params and call the GO7007_IOC_S_COMP_PARAMS ioctl with a + pointer to this structure. The driver will return the current + parameters with any necessary changes to conform to the limitations of + the hardware or current compression mode. Any or all fields can be set + to zero to request a reasonable default value. If the driver is not + set to MPEG format, the EINVAL error code will be returned. When I/O + is in progress, the EBUSY error code will be returned. + + Fields in struct go7007_comp_params: + + __u32 The maximum number of frames in each + gop_size Group Of Pictures; i.e. the maximum + number of frames minus one between + each key frame. + + __u32 The maximum number of sequential + max_b_frames bidirectionally-predicted frames. + (B-frames are not yet supported.) + + enum go7007_aspect_ratio The aspect ratio to be encoded in the + aspect_ratio meta-data of the compressed format. + + Choices are: + GO7007_ASPECT_RATIO_1_1 + GO7007_ASPECT_RATIO_4_3_NTSC + GO7007_ASPECT_RATIO_4_3_PAL + GO7007_ASPECT_RATIO_16_9_NTSC + GO7007_ASPECT_RATIO_16_9_PAL + + __u32 Bit-wise OR of control flags (below) + flags + + Flags in struct go7007_comp_params: + + GO7007_COMP_CLOSED_GOP Only produce self-contained GOPs, used + to produce streams appropriate for + random seeking. + + GO7007_COMP_OMIT_SEQ_HEADER Omit the stream sequence header. + + + GO7007IOC_S_MPEG_PARAMS, GO7007IOC_G_MPEG_PARAMS + + These ioctls are used to negotiate MPEG-specific stream parameters when + the pixelformat has been set to V4L2_PIX_FMT_MPEG. + + To query the current parameters, call the GO7007IOC_G_MPEG_PARAMS ioctl + with a pointer to a struct go7007_mpeg_params. If the driver is not + set to MPEG format, the EINVAL error code will be returned. + + To change the current parameters, initialize all fields of a struct + go7007_mpeg_params and call the GO7007_IOC_S_MPEG_PARAMS ioctl with a + pointer to this structure. The driver will return the current + parameters with any necessary changes to conform to the limitations of + the hardware or selected MPEG mode. Any or all fields can be set to + zero to request a reasonable default value. If the driver is not set + to MPEG format, the EINVAL error code will be returned. When I/O is in + progress, the EBUSY error code will be returned. + + Fields in struct go7007_mpeg_params: + + enum go7007_mpeg_video_standard + mpeg_video_standard The MPEG video standard in which to + compress the video. + + Choices are: + GO7007_MPEG_VIDEO_MPEG1 + GO7007_MPEG_VIDEO_MPEG2 + GO7007_MPEG_VIDEO_MPEG4 + + __u32 Bit-wise OR of control flags (below) + flags + + __u32 The profile and level indication to be + pali stored in the sequence header. This + is only used as an indicator to the + decoder, and does not affect the MPEG + features used in the video stream. + Not valid for MPEG1. + + Choices for MPEG2 are: + GO7007_MPEG2_PROFILE_MAIN_MAIN + + Choices for MPEG4 are: + GO7007_MPEG4_PROFILE_S_L0 + GO7007_MPEG4_PROFILE_S_L1 + GO7007_MPEG4_PROFILE_S_L2 + GO7007_MPEG4_PROFILE_S_L3 + GO7007_MPEG4_PROFILE_ARTS_L1 + GO7007_MPEG4_PROFILE_ARTS_L2 + GO7007_MPEG4_PROFILE_ARTS_L3 + GO7007_MPEG4_PROFILE_ARTS_L4 + GO7007_MPEG4_PROFILE_AS_L0 + GO7007_MPEG4_PROFILE_AS_L1 + GO7007_MPEG4_PROFILE_AS_L2 + GO7007_MPEG4_PROFILE_AS_L3 + GO7007_MPEG4_PROFILE_AS_L4 + GO7007_MPEG4_PROFILE_AS_L5 + + Flags in struct go7007_mpeg_params: + + GO7007_MPEG_FORCE_DVD_MODE Force all compression parameters and + bitrate control settings to comply + with DVD MPEG2 stream requirements. + This overrides most compression and + bitrate settings! + + GO7007_MPEG_OMIT_GOP_HEADER Omit the GOP header. + + GO7007_MPEG_REPEAT_SEQHEADER Repeat the MPEG sequence header at + the start of each GOP. + + + GO7007IOC_S_BITRATE, GO7007IOC_G_BITRATE + + These ioctls are used to set and query the target bitrate value for the + compressed video stream. The bitrate may be selected by storing the + target bits per second in an int and calling GO7007IOC_S_BITRATE with a + pointer to the int. The bitrate may be queried by calling + GO7007IOC_G_BITRATE with a pointer to an int where the current bitrate + will be stored. + + Note that this is the primary means of controlling the video quality + for all compression modes, including V4L2_PIX_FMT_MJPEG. The + VIDIOC_S_JPEGCOMP ioctl is not supported. + + +---------------------------------------------------------------------------- + Installing the WIS PCI Voyager Driver +--------------------------------------------------------------------------- + +The WIS PCI Voyager driver requires several patches to the Linux 2.6.11.x +kernel source tree before compiling the driver. These patches update the +in-kernel SAA7134 driver to the newest development version and patch bugs +in the TDA8290/TDA8275 tuner driver. + +The following patches must be downloaded from Gerd Knorr's website and +applied in the order listed: + + http://dl.bytesex.org/patches/2.6.11-2/i2c-tuner + http://dl.bytesex.org/patches/2.6.11-2/i2c-tuner2 + http://dl.bytesex.org/patches/2.6.11-2/v4l2-api-mpeg + http://dl.bytesex.org/patches/2.6.11-2/saa7134-update + +The following patches are included with this SDK and can be applied in any +order: + + patches/2.6.11/saa7134-voyager.diff + patches/2.6.11/tda8275-newaddr.diff + patches/2.6.11/tda8290-ntsc.diff + +Check to make sure the CONFIG_VIDEO_SAA7134 option is enabled in the kernel +configuration, and build and install the kernel. + +After rebooting into the new kernel, the GO7007 driver can be compiled and +installed: + + $ make SAA7134_BUILD=y + $ make install + $ modprobe saa7134-go7007 + +There will be two V4L video devices associated with the PCI Voyager. The +first device (most likely /dev/video0) provides access to the raw video +capture mode of the SAA7133 device and is used to configure the source +video parameters and tune the TV tuner. This device can be used with xawtv +or other V4L(2) video software as a standard uncompressed device. + +The second device (most likely /dev/video1) provides access to the +compression functions of the GO7007. It can be tested using the gorecord +application in the apps/ directory of this SDK: + + $ apps/gorecord -vdevice /dev/video1 -noaudio test.avi + +Currently the frame resolution is fixed at 720x480 (NTSC) or 720x576 (PAL), +and the video standard must be specified to both the raw and the compressed +video devices (xawtv and gorecord, for example). + + +-------------------------------------------------------------------------- +RELEASE NOTES FOR WIS GO7007SB LINUX DRIVER +--------------------------------------------------------------------------- + +Last updated: 5 November 2005 + + - Release 0.9.7 includes new support for using udev to run fxload. The + install script should automatically detect whether the old hotplug + scripts or the new udev rules should be used. To force the use of + hotplug, run "make install USE_UDEV=n". To force the use of udev, run + "make install USE_UDEV=y". + + - Motion detection is supported but undocumented. Try the `modet` app + for a demonstration of how to use the facility. + + - Using USB2.0 devices such as the TV402U with USB1.1 HCDs or hubs can + cause buffer overruns and frame drops, even at low framerates, due to + inconsistency in the bitrate control mechanism. + + - On devices with an SAA7115, including the Plextor ConvertX, video height + values of 96, 128, 160, 192, 256, 320, and 384 do not work in NTSC mode. + All valid heights up to 512 work correctly in PAL mode. + + - The WIS Star Trek and PCI Voyager boards have no support yet for audio + or the TV tuner. diff --git a/drivers/staging/media/go7007/s2250-board.c b/drivers/staging/media/go7007/s2250-board.c new file mode 100644 index 00000000000..eaa2b0990a1 --- /dev/null +++ b/drivers/staging/media/go7007/s2250-board.c @@ -0,0 +1,631 @@ +/* + * Copyright (C) 2008 Sensoray Company Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License (Version 2) as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA. + */ + +#include <linux/module.h> +#include <linux/usb.h> +#include <linux/i2c.h> +#include <linux/videodev2.h> +#include <linux/slab.h> +#include <media/v4l2-device.h> +#include <media/v4l2-common.h> +#include <media/v4l2-subdev.h> +#include "go7007-priv.h" + +MODULE_DESCRIPTION("Sensoray 2250/2251 i2c v4l2 subdev driver"); +MODULE_LICENSE("GPL v2"); + +/* + * Note: this board has two i2c devices: a vpx3226f and a tlv320aic23b. + * Due to the unusual way these are accessed on this device we do not + * reuse the i2c drivers, but instead they are implemented in this + * driver. It would be nice to improve on this, though. + */ + +#define TLV320_ADDRESS 0x34 +#define VPX322_ADDR_ANALOGCONTROL1 0x02 +#define VPX322_ADDR_BRIGHTNESS0 0x0127 +#define VPX322_ADDR_BRIGHTNESS1 0x0131 +#define VPX322_ADDR_CONTRAST0 0x0128 +#define VPX322_ADDR_CONTRAST1 0x0132 +#define VPX322_ADDR_HUE 0x00dc +#define VPX322_ADDR_SAT 0x0030 + +struct go7007_usb_board { + unsigned int flags; + struct go7007_board_info main_info; +}; + +struct go7007_usb { + struct go7007_usb_board *board; + struct mutex i2c_lock; + struct usb_device *usbdev; + struct urb *video_urbs[8]; + struct urb *audio_urbs[8]; + struct urb *intr_urb; +}; + +static unsigned char aud_regs[] = { + 0x1e, 0x00, + 0x00, 0x17, + 0x02, 0x17, + 0x04, 0xf9, + 0x06, 0xf9, + 0x08, 0x02, + 0x0a, 0x00, + 0x0c, 0x00, + 0x0a, 0x00, + 0x0c, 0x00, + 0x0e, 0x02, + 0x10, 0x00, + 0x12, 0x01, + 0x00, 0x00, +}; + + +static unsigned char vid_regs[] = { + 0xF2, 0x0f, + 0xAA, 0x00, + 0xF8, 0xff, + 0x00, 0x00, +}; + +static u16 vid_regs_fp[] = { + 0x028, 0x067, + 0x120, 0x016, + 0x121, 0xcF2, + 0x122, 0x0F2, + 0x123, 0x00c, + 0x124, 0x2d0, + 0x125, 0x2e0, + 0x126, 0x004, + 0x128, 0x1E0, + 0x12A, 0x016, + 0x12B, 0x0F2, + 0x12C, 0x0F2, + 0x12D, 0x00c, + 0x12E, 0x2d0, + 0x12F, 0x2e0, + 0x130, 0x004, + 0x132, 0x1E0, + 0x140, 0x060, + 0x153, 0x00C, + 0x154, 0x200, + 0x150, 0x801, + 0x000, 0x000 +}; + +/* PAL specific values */ +static u16 vid_regs_fp_pal[] = { + 0x120, 0x017, + 0x121, 0xd22, + 0x122, 0x122, + 0x12A, 0x017, + 0x12B, 0x122, + 0x12C, 0x122, + 0x140, 0x060, + 0x000, 0x000, +}; + +struct s2250 { + struct v4l2_subdev sd; + struct v4l2_ctrl_handler hdl; + v4l2_std_id std; + int input; + int brightness; + int contrast; + int saturation; + int hue; + int reg12b_val; + int audio_input; + struct i2c_client *audio; +}; + +static inline struct s2250 *to_state(struct v4l2_subdev *sd) +{ + return container_of(sd, struct s2250, sd); +} + +/* from go7007-usb.c which is Copyright (C) 2005-2006 Micronas USA Inc.*/ +static int go7007_usb_vendor_request(struct go7007 *go, u16 request, + u16 value, u16 index, void *transfer_buffer, int length, int in) +{ + struct go7007_usb *usb = go->hpi_context; + int timeout = 5000; + + if (in) { + return usb_control_msg(usb->usbdev, + usb_rcvctrlpipe(usb->usbdev, 0), request, + USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN, + value, index, transfer_buffer, length, timeout); + } else { + return usb_control_msg(usb->usbdev, + usb_sndctrlpipe(usb->usbdev, 0), request, + USB_TYPE_VENDOR | USB_RECIP_DEVICE, + value, index, transfer_buffer, length, timeout); + } +} +/* end from go7007-usb.c which is Copyright (C) 2005-2006 Micronas USA Inc.*/ + +static int write_reg(struct i2c_client *client, u8 reg, u8 value) +{ + struct go7007 *go = i2c_get_adapdata(client->adapter); + struct go7007_usb *usb; + int rc; + int dev_addr = client->addr << 1; /* firmware wants 8-bit address */ + u8 *buf; + + if (go == NULL) + return -ENODEV; + + if (go->status == STATUS_SHUTDOWN) + return -EBUSY; + + buf = kzalloc(16, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + usb = go->hpi_context; + if (mutex_lock_interruptible(&usb->i2c_lock) != 0) { + dev_info(&client->dev, "i2c lock failed\n"); + kfree(buf); + return -EINTR; + } + rc = go7007_usb_vendor_request(go, 0x55, dev_addr, + (reg<<8 | value), + buf, + 16, 1); + + mutex_unlock(&usb->i2c_lock); + kfree(buf); + return rc; +} + +static int write_reg_fp(struct i2c_client *client, u16 addr, u16 val) +{ + struct go7007 *go = i2c_get_adapdata(client->adapter); + struct go7007_usb *usb; + int rc; + u8 *buf; + struct s2250 *dec = i2c_get_clientdata(client); + + if (go == NULL) + return -ENODEV; + + if (go->status == STATUS_SHUTDOWN) + return -EBUSY; + + buf = kzalloc(16, GFP_KERNEL); + + if (buf == NULL) + return -ENOMEM; + + + + memset(buf, 0xcd, 6); + + usb = go->hpi_context; + if (mutex_lock_interruptible(&usb->i2c_lock) != 0) { + dev_info(&client->dev, "i2c lock failed\n"); + kfree(buf); + return -EINTR; + } + rc = go7007_usb_vendor_request(go, 0x57, addr, val, buf, 16, 1); + mutex_unlock(&usb->i2c_lock); + if (rc < 0) { + kfree(buf); + return rc; + } + + if (buf[0] == 0) { + unsigned int subaddr, val_read; + + subaddr = (buf[4] << 8) + buf[5]; + val_read = (buf[2] << 8) + buf[3]; + kfree(buf); + if (val_read != val) { + dev_info(&client->dev, "invalid fp write %x %x\n", + val_read, val); + return -EFAULT; + } + if (subaddr != addr) { + dev_info(&client->dev, "invalid fp write addr %x %x\n", + subaddr, addr); + return -EFAULT; + } + } else { + kfree(buf); + return -EFAULT; + } + + /* save last 12b value */ + if (addr == 0x12b) + dec->reg12b_val = val; + + return 0; +} + +static int read_reg_fp(struct i2c_client *client, u16 addr, u16 *val) +{ + struct go7007 *go = i2c_get_adapdata(client->adapter); + struct go7007_usb *usb; + int rc; + u8 *buf; + + if (go == NULL) + return -ENODEV; + + if (go->status == STATUS_SHUTDOWN) + return -EBUSY; + + buf = kzalloc(16, GFP_KERNEL); + + if (buf == NULL) + return -ENOMEM; + + + + memset(buf, 0xcd, 6); + usb = go->hpi_context; + if (mutex_lock_interruptible(&usb->i2c_lock) != 0) { + dev_info(&client->dev, "i2c lock failed\n"); + kfree(buf); + return -EINTR; + } + rc = go7007_usb_vendor_request(go, 0x58, addr, 0, buf, 16, 1); + mutex_unlock(&usb->i2c_lock); + if (rc < 0) { + kfree(buf); + return rc; + } + + *val = (buf[0] << 8) | buf[1]; + kfree(buf); + + return 0; +} + + +static int write_regs(struct i2c_client *client, u8 *regs) +{ + int i; + + for (i = 0; !((regs[i] == 0x00) && (regs[i+1] == 0x00)); i += 2) { + if (write_reg(client, regs[i], regs[i+1]) < 0) { + dev_info(&client->dev, "failed\n"); + return -1; + } + } + return 0; +} + +static int write_regs_fp(struct i2c_client *client, u16 *regs) +{ + int i; + + for (i = 0; !((regs[i] == 0x00) && (regs[i+1] == 0x00)); i += 2) { + if (write_reg_fp(client, regs[i], regs[i+1]) < 0) { + dev_info(&client->dev, "failed fp\n"); + return -1; + } + } + return 0; +} + + +/* ------------------------------------------------------------------------- */ + +static int s2250_s_video_routing(struct v4l2_subdev *sd, u32 input, u32 output, + u32 config) +{ + struct s2250 *state = to_state(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + int vidsys; + + vidsys = (state->std == V4L2_STD_NTSC) ? 0x01 : 0x00; + if (input == 0) { + /* composite */ + write_reg_fp(client, 0x20, 0x020 | vidsys); + write_reg_fp(client, 0x21, 0x662); + write_reg_fp(client, 0x140, 0x060); + } else if (input == 1) { + /* S-Video */ + write_reg_fp(client, 0x20, 0x040 | vidsys); + write_reg_fp(client, 0x21, 0x666); + write_reg_fp(client, 0x140, 0x060); + } else { + return -EINVAL; + } + state->input = input; + return 0; +} + +static int s2250_s_std(struct v4l2_subdev *sd, v4l2_std_id norm) +{ + struct s2250 *state = to_state(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + u16 vidsource; + + vidsource = (state->input == 1) ? 0x040 : 0x020; + if (norm & V4L2_STD_625_50) { + write_regs_fp(client, vid_regs_fp); + write_regs_fp(client, vid_regs_fp_pal); + write_reg_fp(client, 0x20, vidsource); + } else { + write_regs_fp(client, vid_regs_fp); + write_reg_fp(client, 0x20, vidsource | 1); + } + state->std = norm; + return 0; +} + +static int s2250_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct s2250 *state = container_of(ctrl->handler, struct s2250, hdl); + struct i2c_client *client = v4l2_get_subdevdata(&state->sd); + u16 oldvalue; + + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + read_reg_fp(client, VPX322_ADDR_BRIGHTNESS0, &oldvalue); + write_reg_fp(client, VPX322_ADDR_BRIGHTNESS0, + ctrl->val | (oldvalue & ~0xff)); + read_reg_fp(client, VPX322_ADDR_BRIGHTNESS1, &oldvalue); + write_reg_fp(client, VPX322_ADDR_BRIGHTNESS1, + ctrl->val | (oldvalue & ~0xff)); + write_reg_fp(client, 0x140, 0x60); + break; + case V4L2_CID_CONTRAST: + read_reg_fp(client, VPX322_ADDR_CONTRAST0, &oldvalue); + write_reg_fp(client, VPX322_ADDR_CONTRAST0, + ctrl->val | (oldvalue & ~0x3f)); + read_reg_fp(client, VPX322_ADDR_CONTRAST1, &oldvalue); + write_reg_fp(client, VPX322_ADDR_CONTRAST1, + ctrl->val | (oldvalue & ~0x3f)); + write_reg_fp(client, 0x140, 0x60); + break; + case V4L2_CID_SATURATION: + write_reg_fp(client, VPX322_ADDR_SAT, ctrl->val); + break; + case V4L2_CID_HUE: + write_reg_fp(client, VPX322_ADDR_HUE, ctrl->val); + break; + default: + return -EINVAL; + } + return 0; +} + +static int s2250_s_mbus_fmt(struct v4l2_subdev *sd, + struct v4l2_mbus_framefmt *fmt) +{ + struct s2250 *state = to_state(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + + if (fmt->height < 640) { + write_reg_fp(client, 0x12b, state->reg12b_val | 0x400); + write_reg_fp(client, 0x140, 0x060); + } else { + write_reg_fp(client, 0x12b, state->reg12b_val & ~0x400); + write_reg_fp(client, 0x140, 0x060); + } + return 0; +} + +static int s2250_s_audio_routing(struct v4l2_subdev *sd, u32 input, u32 output, + u32 config) +{ + struct s2250 *state = to_state(sd); + + switch (input) { + case 0: + write_reg(state->audio, 0x08, 0x02); /* Line In */ + break; + case 1: + write_reg(state->audio, 0x08, 0x04); /* Mic */ + break; + case 2: + write_reg(state->audio, 0x08, 0x05); /* Mic Boost */ + break; + default: + return -EINVAL; + } + state->audio_input = input; + return 0; +} + + +static int s2250_log_status(struct v4l2_subdev *sd) +{ + struct s2250 *state = to_state(sd); + + v4l2_info(sd, "Standard: %s\n", state->std == V4L2_STD_NTSC ? "NTSC" : + state->std == V4L2_STD_PAL ? "PAL" : + state->std == V4L2_STD_SECAM ? "SECAM" : + "unknown"); + v4l2_info(sd, "Input: %s\n", state->input == 0 ? "Composite" : + state->input == 1 ? "S-video" : + "error"); + v4l2_info(sd, "Audio input: %s\n", state->audio_input == 0 ? "Line In" : + state->audio_input == 1 ? "Mic" : + state->audio_input == 2 ? "Mic Boost" : + "error"); + return v4l2_ctrl_subdev_log_status(sd); +} + +/* --------------------------------------------------------------------------*/ + +static const struct v4l2_ctrl_ops s2250_ctrl_ops = { + .s_ctrl = s2250_s_ctrl, +}; + +static const struct v4l2_subdev_core_ops s2250_core_ops = { + .log_status = s2250_log_status, +}; + +static const struct v4l2_subdev_audio_ops s2250_audio_ops = { + .s_routing = s2250_s_audio_routing, +}; + +static const struct v4l2_subdev_video_ops s2250_video_ops = { + .s_std = s2250_s_std, + .s_routing = s2250_s_video_routing, + .s_mbus_fmt = s2250_s_mbus_fmt, +}; + +static const struct v4l2_subdev_ops s2250_ops = { + .core = &s2250_core_ops, + .audio = &s2250_audio_ops, + .video = &s2250_video_ops, +}; + +/* --------------------------------------------------------------------------*/ + +static int s2250_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct i2c_client *audio; + struct i2c_adapter *adapter = client->adapter; + struct s2250 *state; + struct v4l2_subdev *sd; + u8 *data; + struct go7007 *go = i2c_get_adapdata(adapter); + struct go7007_usb *usb = go->hpi_context; + + audio = i2c_new_dummy(adapter, TLV320_ADDRESS >> 1); + if (audio == NULL) + return -ENOMEM; + + state = kzalloc(sizeof(struct s2250), GFP_KERNEL); + if (state == NULL) { + i2c_unregister_device(audio); + return -ENOMEM; + } + + sd = &state->sd; + v4l2_i2c_subdev_init(sd, client, &s2250_ops); + + v4l2_info(sd, "initializing %s at address 0x%x on %s\n", + "Sensoray 2250/2251", client->addr, client->adapter->name); + + v4l2_ctrl_handler_init(&state->hdl, 4); + v4l2_ctrl_new_std(&state->hdl, &s2250_ctrl_ops, + V4L2_CID_BRIGHTNESS, -128, 127, 1, 0); + v4l2_ctrl_new_std(&state->hdl, &s2250_ctrl_ops, + V4L2_CID_CONTRAST, 0, 0x3f, 1, 0x32); + v4l2_ctrl_new_std(&state->hdl, &s2250_ctrl_ops, + V4L2_CID_SATURATION, 0, 4094, 1, 2070); + v4l2_ctrl_new_std(&state->hdl, &s2250_ctrl_ops, + V4L2_CID_HUE, -512, 511, 1, 0); + sd->ctrl_handler = &state->hdl; + if (state->hdl.error) { + int err = state->hdl.error; + + v4l2_ctrl_handler_free(&state->hdl); + kfree(state); + return err; + } + + state->std = V4L2_STD_NTSC; + state->brightness = 50; + state->contrast = 50; + state->saturation = 50; + state->hue = 0; + state->audio = audio; + + /* initialize the audio */ + if (write_regs(audio, aud_regs) < 0) { + dev_err(&client->dev, "error initializing audio\n"); + goto fail; + } + + if (write_regs(client, vid_regs) < 0) { + dev_err(&client->dev, "error initializing decoder\n"); + goto fail; + } + if (write_regs_fp(client, vid_regs_fp) < 0) { + dev_err(&client->dev, "error initializing decoder\n"); + goto fail; + } + /* set default channel */ + /* composite */ + write_reg_fp(client, 0x20, 0x020 | 1); + write_reg_fp(client, 0x21, 0x662); + write_reg_fp(client, 0x140, 0x060); + + /* set default audio input */ + state->audio_input = 0; + write_reg(client, 0x08, 0x02); /* Line In */ + + if (mutex_lock_interruptible(&usb->i2c_lock) == 0) { + data = kzalloc(16, GFP_KERNEL); + if (data != NULL) { + int rc; + rc = go7007_usb_vendor_request(go, 0x41, 0, 0, + data, 16, 1); + if (rc > 0) { + u8 mask; + data[0] = 0; + mask = 1<<5; + data[0] &= ~mask; + data[1] |= mask; + go7007_usb_vendor_request(go, 0x40, 0, + (data[1]<<8) + + data[1], + data, 16, 0); + } + kfree(data); + } + mutex_unlock(&usb->i2c_lock); + } + + v4l2_info(sd, "initialized successfully\n"); + return 0; + +fail: + i2c_unregister_device(audio); + v4l2_ctrl_handler_free(&state->hdl); + kfree(state); + return -EIO; +} + +static int s2250_remove(struct i2c_client *client) +{ + struct s2250 *state = to_state(i2c_get_clientdata(client)); + + v4l2_device_unregister_subdev(&state->sd); + v4l2_ctrl_handler_free(&state->hdl); + kfree(state); + return 0; +} + +static const struct i2c_device_id s2250_id[] = { + { "s2250", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, s2250_id); + +static struct i2c_driver s2250_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "s2250", + }, + .probe = s2250_probe, + .remove = s2250_remove, + .id_table = s2250_id, +}; + +module_i2c_driver(s2250_driver); diff --git a/drivers/staging/media/go7007/saa7134-go7007.c b/drivers/staging/media/go7007/saa7134-go7007.c new file mode 100644 index 00000000000..e40f7fbfc0a --- /dev/null +++ b/drivers/staging/media/go7007/saa7134-go7007.c @@ -0,0 +1,567 @@ +/* + * Copyright (C) 2005-2006 Micronas USA Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License (Version 2) as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA. + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/spinlock.h> +#include <linux/wait.h> +#include <linux/list.h> +#include <linux/slab.h> +#include <linux/time.h> +#include <linux/mm.h> +#include <linux/usb.h> +#include <linux/i2c.h> +#include <asm/byteorder.h> +#include <media/v4l2-common.h> +#include <media/v4l2-device.h> +#include <media/v4l2-subdev.h> + +#include "saa7134.h" +#include "saa7134-reg.h" +#include "go7007.h" +#include "go7007-priv.h" + +/*#define GO7007_HPI_DEBUG*/ + +enum hpi_address { + HPI_ADDR_VIDEO_BUFFER = 0xe4, + HPI_ADDR_INIT_BUFFER = 0xea, + HPI_ADDR_INTR_RET_VALUE = 0xee, + HPI_ADDR_INTR_RET_DATA = 0xec, + HPI_ADDR_INTR_STATUS = 0xf4, + HPI_ADDR_INTR_WR_PARAM = 0xf6, + HPI_ADDR_INTR_WR_INDEX = 0xf8, +}; + +enum gpio_command { + GPIO_COMMAND_RESET = 0x00, /* 000b */ + GPIO_COMMAND_REQ1 = 0x04, /* 001b */ + GPIO_COMMAND_WRITE = 0x20, /* 010b */ + GPIO_COMMAND_REQ2 = 0x24, /* 011b */ + GPIO_COMMAND_READ = 0x80, /* 100b */ + GPIO_COMMAND_VIDEO = 0x84, /* 101b */ + GPIO_COMMAND_IDLE = 0xA0, /* 110b */ + GPIO_COMMAND_ADDR = 0xA4, /* 111b */ +}; + +struct saa7134_go7007 { + struct v4l2_subdev sd; + struct saa7134_dev *dev; + u8 *top; + u8 *bottom; + dma_addr_t top_dma; + dma_addr_t bottom_dma; +}; + +static inline struct saa7134_go7007 *to_state(struct v4l2_subdev *sd) +{ + return container_of(sd, struct saa7134_go7007, sd); +} + +static const struct go7007_board_info board_voyager = { + .flags = 0, + .sensor_flags = GO7007_SENSOR_656 | + GO7007_SENSOR_VALID_ENABLE | + GO7007_SENSOR_TV | + GO7007_SENSOR_VBI, + .audio_flags = GO7007_AUDIO_I2S_MODE_1 | + GO7007_AUDIO_WORD_16, + .audio_rate = 48000, + .audio_bclk_div = 8, + .audio_main_div = 2, + .hpi_buffer_cap = 7, + .num_inputs = 1, + .inputs = { + { + .name = "SAA7134", + }, + }, +}; + +/********************* Driver for GPIO HPI interface *********************/ + +static int gpio_write(struct saa7134_dev *dev, u8 addr, u16 data) +{ + saa_writeb(SAA7134_GPIO_GPMODE0, 0xff); + + /* Write HPI address */ + saa_writeb(SAA7134_GPIO_GPSTATUS0, addr); + saa_writeb(SAA7134_GPIO_GPSTATUS2, GPIO_COMMAND_ADDR); + saa_writeb(SAA7134_GPIO_GPSTATUS2, GPIO_COMMAND_IDLE); + + /* Write low byte */ + saa_writeb(SAA7134_GPIO_GPSTATUS0, data & 0xff); + saa_writeb(SAA7134_GPIO_GPSTATUS2, GPIO_COMMAND_WRITE); + saa_writeb(SAA7134_GPIO_GPSTATUS2, GPIO_COMMAND_IDLE); + + /* Write high byte */ + saa_writeb(SAA7134_GPIO_GPSTATUS0, data >> 8); + saa_writeb(SAA7134_GPIO_GPSTATUS2, GPIO_COMMAND_WRITE); + saa_writeb(SAA7134_GPIO_GPSTATUS2, GPIO_COMMAND_IDLE); + + return 0; +} + +static int gpio_read(struct saa7134_dev *dev, u8 addr, u16 *data) +{ + saa_writeb(SAA7134_GPIO_GPMODE0, 0xff); + + /* Write HPI address */ + saa_writeb(SAA7134_GPIO_GPSTATUS0, addr); + saa_writeb(SAA7134_GPIO_GPSTATUS2, GPIO_COMMAND_ADDR); + saa_writeb(SAA7134_GPIO_GPSTATUS2, GPIO_COMMAND_IDLE); + + saa_writeb(SAA7134_GPIO_GPMODE0, 0x00); + + /* Read low byte */ + saa_writeb(SAA7134_GPIO_GPSTATUS2, GPIO_COMMAND_READ); + saa_clearb(SAA7134_GPIO_GPMODE3, SAA7134_GPIO_GPRESCAN); + saa_setb(SAA7134_GPIO_GPMODE3, SAA7134_GPIO_GPRESCAN); + *data = saa_readb(SAA7134_GPIO_GPSTATUS0); + saa_writeb(SAA7134_GPIO_GPSTATUS2, GPIO_COMMAND_IDLE); + + /* Read high byte */ + saa_writeb(SAA7134_GPIO_GPSTATUS2, GPIO_COMMAND_READ); + saa_clearb(SAA7134_GPIO_GPMODE3, SAA7134_GPIO_GPRESCAN); + saa_setb(SAA7134_GPIO_GPMODE3, SAA7134_GPIO_GPRESCAN); + *data |= saa_readb(SAA7134_GPIO_GPSTATUS0) << 8; + saa_writeb(SAA7134_GPIO_GPSTATUS2, GPIO_COMMAND_IDLE); + + return 0; +} + +static int saa7134_go7007_interface_reset(struct go7007 *go) +{ + struct saa7134_go7007 *saa = go->hpi_context; + struct saa7134_dev *dev = saa->dev; + u32 status; + u16 intr_val, intr_data; + int count = 20; + + saa_clearb(SAA7134_TS_PARALLEL, 0x80); /* Disable TS interface */ + saa_writeb(SAA7134_GPIO_GPMODE2, 0xa4); + saa_writeb(SAA7134_GPIO_GPMODE0, 0xff); + + saa_writeb(SAA7134_GPIO_GPSTATUS2, GPIO_COMMAND_REQ1); + saa_writeb(SAA7134_GPIO_GPSTATUS2, GPIO_COMMAND_RESET); + msleep(1); + saa_writeb(SAA7134_GPIO_GPSTATUS2, GPIO_COMMAND_REQ1); + saa_writeb(SAA7134_GPIO_GPSTATUS2, GPIO_COMMAND_REQ2); + msleep(10); + + saa_clearb(SAA7134_GPIO_GPMODE3, SAA7134_GPIO_GPRESCAN); + saa_setb(SAA7134_GPIO_GPMODE3, SAA7134_GPIO_GPRESCAN); + + status = saa_readb(SAA7134_GPIO_GPSTATUS2); + /*printk(KERN_DEBUG "status is %s\n", status & 0x40 ? "OK" : "not OK"); */ + + /* enter command mode...(?) */ + saa_writeb(SAA7134_GPIO_GPSTATUS2, GPIO_COMMAND_REQ1); + saa_writeb(SAA7134_GPIO_GPSTATUS2, GPIO_COMMAND_REQ2); + + do { + saa_clearb(SAA7134_GPIO_GPMODE3, SAA7134_GPIO_GPRESCAN); + saa_setb(SAA7134_GPIO_GPMODE3, SAA7134_GPIO_GPRESCAN); + status = saa_readb(SAA7134_GPIO_GPSTATUS2); + /*printk(KERN_INFO "gpio is %08x\n", saa_readl(SAA7134_GPIO_GPSTATUS0 >> 2)); */ + } while (--count > 0); + + /* Wait for an interrupt to indicate successful hardware reset */ + if (go7007_read_interrupt(go, &intr_val, &intr_data) < 0 || + (intr_val & ~0x1) != 0x55aa) { + printk(KERN_ERR + "saa7134-go7007: unable to reset the GO7007\n"); + return -1; + } + return 0; +} + +static int saa7134_go7007_write_interrupt(struct go7007 *go, int addr, int data) +{ + struct saa7134_go7007 *saa = go->hpi_context; + struct saa7134_dev *dev = saa->dev; + int i; + u16 status_reg; + +#ifdef GO7007_HPI_DEBUG + printk(KERN_DEBUG + "saa7134-go7007: WriteInterrupt: %04x %04x\n", addr, data); +#endif + + for (i = 0; i < 100; ++i) { + gpio_read(dev, HPI_ADDR_INTR_STATUS, &status_reg); + if (!(status_reg & 0x0010)) + break; + msleep(10); + } + if (i == 100) { + printk(KERN_ERR + "saa7134-go7007: device is hung, status reg = 0x%04x\n", + status_reg); + return -1; + } + gpio_write(dev, HPI_ADDR_INTR_WR_PARAM, data); + gpio_write(dev, HPI_ADDR_INTR_WR_INDEX, addr); + + return 0; +} + +static int saa7134_go7007_read_interrupt(struct go7007 *go) +{ + struct saa7134_go7007 *saa = go->hpi_context; + struct saa7134_dev *dev = saa->dev; + + /* XXX we need to wait if there is no interrupt available */ + go->interrupt_available = 1; + gpio_read(dev, HPI_ADDR_INTR_RET_VALUE, &go->interrupt_value); + gpio_read(dev, HPI_ADDR_INTR_RET_DATA, &go->interrupt_data); +#ifdef GO7007_HPI_DEBUG + printk(KERN_DEBUG "saa7134-go7007: ReadInterrupt: %04x %04x\n", + go->interrupt_value, go->interrupt_data); +#endif + return 0; +} + +static void saa7134_go7007_irq_ts_done(struct saa7134_dev *dev, + unsigned long status) +{ + struct go7007 *go = video_get_drvdata(dev->empress_dev); + struct saa7134_go7007 *saa = go->hpi_context; + + if (!vb2_is_streaming(&go->vidq)) + return; + if (0 != (status & 0x000f0000)) + printk(KERN_DEBUG "saa7134-go7007: irq: lost %ld\n", + (status >> 16) & 0x0f); + if (status & 0x100000) { + dma_sync_single_for_cpu(&dev->pci->dev, + saa->bottom_dma, PAGE_SIZE, DMA_FROM_DEVICE); + go7007_parse_video_stream(go, saa->bottom, PAGE_SIZE); + saa_writel(SAA7134_RS_BA2(5), cpu_to_le32(saa->bottom_dma)); + } else { + dma_sync_single_for_cpu(&dev->pci->dev, + saa->top_dma, PAGE_SIZE, DMA_FROM_DEVICE); + go7007_parse_video_stream(go, saa->top, PAGE_SIZE); + saa_writel(SAA7134_RS_BA1(5), cpu_to_le32(saa->top_dma)); + } +} + +static int saa7134_go7007_stream_start(struct go7007 *go) +{ + struct saa7134_go7007 *saa = go->hpi_context; + struct saa7134_dev *dev = saa->dev; + + saa->top_dma = dma_map_page(&dev->pci->dev, virt_to_page(saa->top), + 0, PAGE_SIZE, DMA_FROM_DEVICE); + if (dma_mapping_error(&dev->pci->dev, saa->top_dma)) + return -ENOMEM; + saa->bottom_dma = dma_map_page(&dev->pci->dev, + virt_to_page(saa->bottom), + 0, PAGE_SIZE, DMA_FROM_DEVICE); + if (dma_mapping_error(&dev->pci->dev, saa->bottom_dma)) { + dma_unmap_page(&dev->pci->dev, saa->top_dma, PAGE_SIZE, + DMA_FROM_DEVICE); + return -ENOMEM; + } + + saa_writel(SAA7134_VIDEO_PORT_CTRL0 >> 2, 0xA300B000); + saa_writel(SAA7134_VIDEO_PORT_CTRL4 >> 2, 0x40000200); + + /* Set HPI interface for video */ + saa_writeb(SAA7134_GPIO_GPMODE0, 0xff); + saa_writeb(SAA7134_GPIO_GPSTATUS0, HPI_ADDR_VIDEO_BUFFER); + saa_writeb(SAA7134_GPIO_GPSTATUS2, GPIO_COMMAND_ADDR); + saa_writeb(SAA7134_GPIO_GPMODE0, 0x00); + + /* Enable TS interface */ + saa_writeb(SAA7134_TS_PARALLEL, 0xe6); + + /* Reset TS interface */ + saa_setb(SAA7134_TS_SERIAL1, 0x01); + saa_clearb(SAA7134_TS_SERIAL1, 0x01); + + /* Set up transfer block size */ + saa_writeb(SAA7134_TS_PARALLEL_SERIAL, 128 - 1); + saa_writeb(SAA7134_TS_DMA0, (PAGE_SIZE >> 7) - 1); + saa_writeb(SAA7134_TS_DMA1, 0); + saa_writeb(SAA7134_TS_DMA2, 0); + + /* Enable video streaming mode */ + saa_writeb(SAA7134_GPIO_GPSTATUS2, GPIO_COMMAND_VIDEO); + + saa_writel(SAA7134_RS_BA1(5), cpu_to_le32(saa->top_dma)); + saa_writel(SAA7134_RS_BA2(5), cpu_to_le32(saa->bottom_dma)); + saa_writel(SAA7134_RS_PITCH(5), 128); + saa_writel(SAA7134_RS_CONTROL(5), SAA7134_RS_CONTROL_BURST_MAX); + + /* Enable TS FIFO */ + saa_setl(SAA7134_MAIN_CTRL, SAA7134_MAIN_CTRL_TE5); + + /* Enable DMA IRQ */ + saa_setl(SAA7134_IRQ1, + SAA7134_IRQ1_INTE_RA2_1 | SAA7134_IRQ1_INTE_RA2_0); + + return 0; +} + +static int saa7134_go7007_stream_stop(struct go7007 *go) +{ + struct saa7134_go7007 *saa = go->hpi_context; + struct saa7134_dev *dev; + + if (!saa) + return -EINVAL; + dev = saa->dev; + if (!dev) + return -EINVAL; + + /* Shut down TS FIFO */ + saa_clearl(SAA7134_MAIN_CTRL, SAA7134_MAIN_CTRL_TE5); + + /* Disable DMA IRQ */ + saa_clearl(SAA7134_IRQ1, + SAA7134_IRQ1_INTE_RA2_1 | SAA7134_IRQ1_INTE_RA2_0); + + /* Disable TS interface */ + saa_clearb(SAA7134_TS_PARALLEL, 0x80); + + dma_unmap_page(&dev->pci->dev, saa->top_dma, PAGE_SIZE, + DMA_FROM_DEVICE); + dma_unmap_page(&dev->pci->dev, saa->bottom_dma, PAGE_SIZE, + DMA_FROM_DEVICE); + + return 0; +} + +static int saa7134_go7007_send_firmware(struct go7007 *go, u8 *data, int len) +{ + struct saa7134_go7007 *saa = go->hpi_context; + struct saa7134_dev *dev = saa->dev; + u16 status_reg; + int i; + +#ifdef GO7007_HPI_DEBUG + printk(KERN_DEBUG "saa7134-go7007: DownloadBuffer " + "sending %d bytes\n", len); +#endif + + while (len > 0) { + i = len > 64 ? 64 : len; + saa_writeb(SAA7134_GPIO_GPMODE0, 0xff); + saa_writeb(SAA7134_GPIO_GPSTATUS0, HPI_ADDR_INIT_BUFFER); + saa_writeb(SAA7134_GPIO_GPSTATUS2, GPIO_COMMAND_ADDR); + saa_writeb(SAA7134_GPIO_GPSTATUS2, GPIO_COMMAND_IDLE); + while (i-- > 0) { + saa_writeb(SAA7134_GPIO_GPSTATUS0, *data); + saa_writeb(SAA7134_GPIO_GPSTATUS2, GPIO_COMMAND_WRITE); + saa_writeb(SAA7134_GPIO_GPSTATUS2, GPIO_COMMAND_IDLE); + ++data; + --len; + } + for (i = 0; i < 100; ++i) { + gpio_read(dev, HPI_ADDR_INTR_STATUS, &status_reg); + if (!(status_reg & 0x0002)) + break; + } + if (i == 100) { + printk(KERN_ERR "saa7134-go7007: device is hung, " + "status reg = 0x%04x\n", status_reg); + return -1; + } + } + return 0; +} + +static struct go7007_hpi_ops saa7134_go7007_hpi_ops = { + .interface_reset = saa7134_go7007_interface_reset, + .write_interrupt = saa7134_go7007_write_interrupt, + .read_interrupt = saa7134_go7007_read_interrupt, + .stream_start = saa7134_go7007_stream_start, + .stream_stop = saa7134_go7007_stream_stop, + .send_firmware = saa7134_go7007_send_firmware, +}; +MODULE_FIRMWARE("go7007/go7007tv.bin"); + +/* --------------------------------------------------------------------------*/ + +static int saa7134_go7007_s_std(struct v4l2_subdev *sd, v4l2_std_id norm) +{ + struct saa7134_go7007 *saa = to_state(sd); + struct saa7134_dev *dev = saa->dev; + + return saa7134_s_std_internal(dev, NULL, norm); +} + +static int saa7134_go7007_queryctrl(struct v4l2_subdev *sd, + struct v4l2_queryctrl *query) +{ + return saa7134_queryctrl(NULL, NULL, query); +} +static int saa7134_go7007_s_ctrl(struct v4l2_subdev *sd, + struct v4l2_control *ctrl) +{ + struct saa7134_go7007 *saa = to_state(sd); + struct saa7134_dev *dev = saa->dev; + return saa7134_s_ctrl_internal(dev, NULL, ctrl); +} + +static int saa7134_go7007_g_ctrl(struct v4l2_subdev *sd, + struct v4l2_control *ctrl) +{ + struct saa7134_go7007 *saa = to_state(sd); + struct saa7134_dev *dev = saa->dev; + return saa7134_g_ctrl_internal(dev, NULL, ctrl); +} + +/* --------------------------------------------------------------------------*/ + +static const struct v4l2_subdev_core_ops saa7134_go7007_core_ops = { + .g_ctrl = saa7134_go7007_g_ctrl, + .s_ctrl = saa7134_go7007_s_ctrl, + .queryctrl = saa7134_go7007_queryctrl, +}; + +static const struct v4l2_subdev_video_ops saa7134_go7007_video_ops = { + .s_std = saa7134_go7007_s_std, +}; + +static const struct v4l2_subdev_ops saa7134_go7007_sd_ops = { + .core = &saa7134_go7007_core_ops, + .video = &saa7134_go7007_video_ops, +}; + +/* --------------------------------------------------------------------------*/ + + +/********************* Add/remove functions *********************/ + +static int saa7134_go7007_init(struct saa7134_dev *dev) +{ + struct go7007 *go; + struct saa7134_go7007 *saa; + struct v4l2_subdev *sd; + + printk(KERN_DEBUG "saa7134-go7007: probing new SAA713X board\n"); + + go = go7007_alloc(&board_voyager, &dev->pci->dev); + if (go == NULL) + return -ENOMEM; + + saa = kzalloc(sizeof(struct saa7134_go7007), GFP_KERNEL); + if (saa == NULL) { + kfree(go); + return -ENOMEM; + } + + go->board_id = GO7007_BOARDID_PCI_VOYAGER; + snprintf(go->bus_info, sizeof(go->bus_info), "PCI:%s", pci_name(dev->pci)); + strlcpy(go->name, saa7134_boards[dev->board].name, sizeof(go->name)); + go->hpi_ops = &saa7134_go7007_hpi_ops; + go->hpi_context = saa; + saa->dev = dev; + + /* Init the subdevice interface */ + sd = &saa->sd; + v4l2_subdev_init(sd, &saa7134_go7007_sd_ops); + v4l2_set_subdevdata(sd, saa); + strncpy(sd->name, "saa7134-go7007", sizeof(sd->name)); + + /* Allocate a couple pages for receiving the compressed stream */ + saa->top = (u8 *)get_zeroed_page(GFP_KERNEL); + if (!saa->top) + goto allocfail; + saa->bottom = (u8 *)get_zeroed_page(GFP_KERNEL); + if (!saa->bottom) + goto allocfail; + + /* Boot the GO7007 */ + if (go7007_boot_encoder(go, go->board_info->flags & + GO7007_BOARD_USE_ONBOARD_I2C) < 0) + goto allocfail; + + /* Do any final GO7007 initialization, then register the + * V4L2 and ALSA interfaces */ + if (go7007_register_encoder(go, go->board_info->num_i2c_devs) < 0) + goto allocfail; + + /* Register the subdevice interface with the go7007 device */ + if (v4l2_device_register_subdev(&go->v4l2_dev, sd) < 0) + printk(KERN_INFO "saa7134-go7007: register subdev failed\n"); + + dev->empress_dev = &go->vdev; + + go->status = STATUS_ONLINE; + return 0; + +allocfail: + if (saa->top) + free_page((unsigned long)saa->top); + if (saa->bottom) + free_page((unsigned long)saa->bottom); + kfree(saa); + kfree(go); + return -ENOMEM; +} + +static int saa7134_go7007_fini(struct saa7134_dev *dev) +{ + struct go7007 *go; + struct saa7134_go7007 *saa; + + if (NULL == dev->empress_dev) + return 0; + + go = video_get_drvdata(dev->empress_dev); + if (go->audio_enabled) + go7007_snd_remove(go); + + saa = go->hpi_context; + go->status = STATUS_SHUTDOWN; + free_page((unsigned long)saa->top); + free_page((unsigned long)saa->bottom); + v4l2_device_unregister_subdev(&saa->sd); + kfree(saa); + video_unregister_device(&go->vdev); + + v4l2_device_put(&go->v4l2_dev); + dev->empress_dev = NULL; + + return 0; +} + +static struct saa7134_mpeg_ops saa7134_go7007_ops = { + .type = SAA7134_MPEG_GO7007, + .init = saa7134_go7007_init, + .fini = saa7134_go7007_fini, + .irq_ts_done = saa7134_go7007_irq_ts_done, +}; + +static int __init saa7134_go7007_mod_init(void) +{ + return saa7134_ts_register(&saa7134_go7007_ops); +} + +static void __exit saa7134_go7007_mod_cleanup(void) +{ + saa7134_ts_unregister(&saa7134_go7007_ops); +} + +module_init(saa7134_go7007_mod_init); +module_exit(saa7134_go7007_mod_cleanup); + +MODULE_LICENSE("GPL v2"); diff --git a/drivers/staging/media/go7007/snd-go7007.c b/drivers/staging/media/go7007/snd-go7007.c new file mode 100644 index 00000000000..9eb2a20ae40 --- /dev/null +++ b/drivers/staging/media/go7007/snd-go7007.c @@ -0,0 +1,302 @@ +/* + * Copyright (C) 2005-2006 Micronas USA Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License (Version 2) as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/spinlock.h> +#include <linux/delay.h> +#include <linux/sched.h> +#include <linux/vmalloc.h> +#include <linux/time.h> +#include <linux/mm.h> +#include <linux/i2c.h> +#include <linux/mutex.h> +#include <linux/uaccess.h> +#include <linux/slab.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/initval.h> + +#include "go7007-priv.h" + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; +static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; + +module_param_array(index, int, NULL, 0444); +module_param_array(id, charp, NULL, 0444); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for the go7007 audio driver"); +MODULE_PARM_DESC(id, "ID string for the go7007 audio driver"); +MODULE_PARM_DESC(enable, "Enable for the go7007 audio driver"); + +struct go7007_snd { + struct snd_card *card; + struct snd_pcm *pcm; + struct snd_pcm_substream *substream; + spinlock_t lock; + int w_idx; + int hw_ptr; + int avail; + int capturing; +}; + +static struct snd_pcm_hardware go7007_snd_capture_hw = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = (128*1024), + .period_bytes_min = 4096, + .period_bytes_max = (128*1024), + .periods_min = 1, + .periods_max = 32, +}; + +static void parse_audio_stream_data(struct go7007 *go, u8 *buf, int length) +{ + struct go7007_snd *gosnd = go->snd_context; + struct snd_pcm_runtime *runtime = gosnd->substream->runtime; + int frames = bytes_to_frames(runtime, length); + + spin_lock(&gosnd->lock); + gosnd->hw_ptr += frames; + if (gosnd->hw_ptr >= runtime->buffer_size) + gosnd->hw_ptr -= runtime->buffer_size; + gosnd->avail += frames; + spin_unlock(&gosnd->lock); + if (gosnd->w_idx + length > runtime->dma_bytes) { + int cpy = runtime->dma_bytes - gosnd->w_idx; + + memcpy(runtime->dma_area + gosnd->w_idx, buf, cpy); + length -= cpy; + buf += cpy; + gosnd->w_idx = 0; + } + memcpy(runtime->dma_area + gosnd->w_idx, buf, length); + gosnd->w_idx += length; + spin_lock(&gosnd->lock); + if (gosnd->avail < runtime->period_size) { + spin_unlock(&gosnd->lock); + return; + } + gosnd->avail -= runtime->period_size; + spin_unlock(&gosnd->lock); + if (gosnd->capturing) + snd_pcm_period_elapsed(gosnd->substream); +} + +static int go7007_snd_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct go7007 *go = snd_pcm_substream_chip(substream); + unsigned int bytes; + + bytes = params_buffer_bytes(hw_params); + if (substream->runtime->dma_bytes > 0) + vfree(substream->runtime->dma_area); + substream->runtime->dma_bytes = 0; + substream->runtime->dma_area = vmalloc(bytes); + if (substream->runtime->dma_area == NULL) + return -ENOMEM; + substream->runtime->dma_bytes = bytes; + go->audio_deliver = parse_audio_stream_data; + return 0; +} + +static int go7007_snd_hw_free(struct snd_pcm_substream *substream) +{ + struct go7007 *go = snd_pcm_substream_chip(substream); + + go->audio_deliver = NULL; + if (substream->runtime->dma_bytes > 0) + vfree(substream->runtime->dma_area); + substream->runtime->dma_bytes = 0; + return 0; +} + +static int go7007_snd_capture_open(struct snd_pcm_substream *substream) +{ + struct go7007 *go = snd_pcm_substream_chip(substream); + struct go7007_snd *gosnd = go->snd_context; + unsigned long flags; + int r; + + spin_lock_irqsave(&gosnd->lock, flags); + if (gosnd->substream == NULL) { + gosnd->substream = substream; + substream->runtime->hw = go7007_snd_capture_hw; + r = 0; + } else + r = -EBUSY; + spin_unlock_irqrestore(&gosnd->lock, flags); + return r; +} + +static int go7007_snd_capture_close(struct snd_pcm_substream *substream) +{ + struct go7007 *go = snd_pcm_substream_chip(substream); + struct go7007_snd *gosnd = go->snd_context; + + gosnd->substream = NULL; + return 0; +} + +static int go7007_snd_pcm_prepare(struct snd_pcm_substream *substream) +{ + return 0; +} + +static int go7007_snd_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct go7007 *go = snd_pcm_substream_chip(substream); + struct go7007_snd *gosnd = go->snd_context; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + /* Just set a flag to indicate we should signal ALSA when + * sound comes in */ + gosnd->capturing = 1; + return 0; + case SNDRV_PCM_TRIGGER_STOP: + gosnd->hw_ptr = gosnd->w_idx = gosnd->avail = 0; + gosnd->capturing = 0; + return 0; + default: + return -EINVAL; + } +} + +static snd_pcm_uframes_t go7007_snd_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct go7007 *go = snd_pcm_substream_chip(substream); + struct go7007_snd *gosnd = go->snd_context; + + return gosnd->hw_ptr; +} + +static struct page *go7007_snd_pcm_page(struct snd_pcm_substream *substream, + unsigned long offset) +{ + return vmalloc_to_page(substream->runtime->dma_area + offset); +} + +static struct snd_pcm_ops go7007_snd_capture_ops = { + .open = go7007_snd_capture_open, + .close = go7007_snd_capture_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = go7007_snd_hw_params, + .hw_free = go7007_snd_hw_free, + .prepare = go7007_snd_pcm_prepare, + .trigger = go7007_snd_pcm_trigger, + .pointer = go7007_snd_pcm_pointer, + .page = go7007_snd_pcm_page, +}; + +static int go7007_snd_free(struct snd_device *device) +{ + struct go7007 *go = device->device_data; + + kfree(go->snd_context); + go->snd_context = NULL; + return 0; +} + +static struct snd_device_ops go7007_snd_device_ops = { + .dev_free = go7007_snd_free, +}; + +int go7007_snd_init(struct go7007 *go) +{ + static int dev; + struct go7007_snd *gosnd; + int ret = 0; + + if (dev >= SNDRV_CARDS) + return -ENODEV; + if (!enable[dev]) { + dev++; + return -ENOENT; + } + gosnd = kmalloc(sizeof(struct go7007_snd), GFP_KERNEL); + if (gosnd == NULL) + return -ENOMEM; + spin_lock_init(&gosnd->lock); + gosnd->hw_ptr = gosnd->w_idx = gosnd->avail = 0; + gosnd->capturing = 0; + ret = snd_card_new(go->dev, index[dev], id[dev], THIS_MODULE, 0, + &gosnd->card); + if (ret < 0) { + kfree(gosnd); + return ret; + } + ret = snd_device_new(gosnd->card, SNDRV_DEV_LOWLEVEL, go, + &go7007_snd_device_ops); + if (ret < 0) { + kfree(gosnd); + return ret; + } + ret = snd_pcm_new(gosnd->card, "go7007", 0, 0, 1, &gosnd->pcm); + if (ret < 0) { + snd_card_free(gosnd->card); + kfree(gosnd); + return ret; + } + strlcpy(gosnd->card->driver, "go7007", sizeof(gosnd->card->driver)); + strlcpy(gosnd->card->shortname, go->name, sizeof(gosnd->card->driver)); + strlcpy(gosnd->card->longname, gosnd->card->shortname, + sizeof(gosnd->card->longname)); + + gosnd->pcm->private_data = go; + snd_pcm_set_ops(gosnd->pcm, SNDRV_PCM_STREAM_CAPTURE, + &go7007_snd_capture_ops); + + ret = snd_card_register(gosnd->card); + if (ret < 0) { + snd_card_free(gosnd->card); + kfree(gosnd); + return ret; + } + + gosnd->substream = NULL; + go->snd_context = gosnd; + v4l2_device_get(&go->v4l2_dev); + ++dev; + + return 0; +} +EXPORT_SYMBOL(go7007_snd_init); + +int go7007_snd_remove(struct go7007 *go) +{ + struct go7007_snd *gosnd = go->snd_context; + + snd_card_disconnect(gosnd->card); + snd_card_free_when_closed(gosnd->card); + v4l2_device_put(&go->v4l2_dev); + return 0; +} +EXPORT_SYMBOL(go7007_snd_remove); + +MODULE_LICENSE("GPL v2"); diff --git a/drivers/staging/media/lirc/Kconfig b/drivers/staging/media/lirc/Kconfig new file mode 100644 index 00000000000..e60a59fc252 --- /dev/null +++ b/drivers/staging/media/lirc/Kconfig @@ -0,0 +1,72 @@ +# +# LIRC driver(s) configuration +# +menuconfig LIRC_STAGING + bool "Linux Infrared Remote Control IR receiver/transmitter drivers" + depends on LIRC + help + Say Y here, and all supported Linux Infrared Remote Control IR and + RF receiver and transmitter drivers will be displayed. When paired + with a remote control and the lirc daemon, the receiver drivers + allow control of your Linux system via remote control. + +if LIRC_STAGING + +config LIRC_BT829 + tristate "BT829 based hardware" + depends on LIRC && PCI + help + Driver for the IR interface on BT829-based hardware + +config LIRC_IGORPLUGUSB + tristate "Igor Cesko's USB IR Receiver" + depends on LIRC && USB + help + Driver for Igor Cesko's USB IR Receiver + +config LIRC_IMON + tristate "Legacy SoundGraph iMON Receiver and Display" + depends on LIRC && USB + help + Driver for the original SoundGraph iMON IR Receiver and Display + + Current generation iMON devices use the input layer imon driver. + +config LIRC_PARALLEL + tristate "Homebrew Parallel Port Receiver" + depends on LIRC && PARPORT + help + Driver for Homebrew Parallel Port Receivers + +config LIRC_SASEM + tristate "Sasem USB IR Remote" + depends on LIRC && USB + help + Driver for the Sasem OnAir Remocon-V or Dign HV5 HTPC IR/VFD Module + +config LIRC_SERIAL + tristate "Homebrew Serial Port Receiver" + depends on LIRC + help + Driver for Homebrew Serial Port Receivers + +config LIRC_SERIAL_TRANSMITTER + bool "Serial Port Transmitter" + default y + depends on LIRC_SERIAL + help + Serial Port Transmitter support + +config LIRC_SIR + tristate "Built-in SIR IrDA port" + depends on LIRC + help + Driver for the SIR IrDA port + +config LIRC_ZILOG + tristate "Zilog/Hauppauge IR Transmitter" + depends on LIRC && I2C + help + Driver for the Zilog/Hauppauge IR Transmitter, found on + PVR-150/500, HVR-1200/1250/1700/1800, HD-PVR and other cards +endif diff --git a/drivers/staging/media/lirc/Makefile b/drivers/staging/media/lirc/Makefile new file mode 100644 index 00000000000..b90fcabddab --- /dev/null +++ b/drivers/staging/media/lirc/Makefile @@ -0,0 +1,13 @@ +# Makefile for the lirc drivers. +# + +# Each configuration option enables a list of files. + +obj-$(CONFIG_LIRC_BT829) += lirc_bt829.o +obj-$(CONFIG_LIRC_IGORPLUGUSB) += lirc_igorplugusb.o +obj-$(CONFIG_LIRC_IMON) += lirc_imon.o +obj-$(CONFIG_LIRC_PARALLEL) += lirc_parallel.o +obj-$(CONFIG_LIRC_SASEM) += lirc_sasem.o +obj-$(CONFIG_LIRC_SERIAL) += lirc_serial.o +obj-$(CONFIG_LIRC_SIR) += lirc_sir.o +obj-$(CONFIG_LIRC_ZILOG) += lirc_zilog.o diff --git a/drivers/staging/media/lirc/TODO b/drivers/staging/media/lirc/TODO new file mode 100644 index 00000000000..cbea5d84fed --- /dev/null +++ b/drivers/staging/media/lirc/TODO @@ -0,0 +1,13 @@ +- All drivers should either be ported to ir-core, or dropped entirely + (see drivers/media/IR/mceusb.c vs. lirc_mceusb.c in lirc cvs for an + example of a previously completed port). + +- lirc_bt829 uses registers on a Mach64 VT, which has a separate kernel + framebuffer driver (atyfb) and userland X driver (mach64). It can't + simply be converted to a normal PCI driver, but ideally it should be + coordinated with the other drivers. + +Please send patches to: +Jarod Wilson <jarod@wilsonet.com> +Greg Kroah-Hartman <greg@kroah.com> + diff --git a/drivers/staging/media/lirc/TODO.lirc_zilog b/drivers/staging/media/lirc/TODO.lirc_zilog new file mode 100644 index 00000000000..a97800a8e12 --- /dev/null +++ b/drivers/staging/media/lirc/TODO.lirc_zilog @@ -0,0 +1,36 @@ +1. Both ir-kbd-i2c and lirc_zilog provide support for RX events for +the chips supported by lirc_zilog. Before moving lirc_zilog out of staging: + +a. ir-kbd-i2c needs a module parameter added to allow the user to tell + ir-kbd-i2c to ignore Z8 IR units. + +b. lirc_zilog should provide Rx key presses to the rc core like ir-kbd-i2c + does. + + +2. lirc_zilog module ref-counting need examination. It has not been +verified that cdev and lirc_dev will take the proper module references on +lirc_zilog to prevent removal of lirc_zilog when the /dev/lircN device node +is open. + +(The good news is ref-counting of lirc_zilog internal structures appears to be +complete. Testing has shown the cx18 module can be unloaded out from under +irw + lircd + lirc_dev, with the /dev/lirc0 device node open, with no adverse +effects. The cx18 module could then be reloaded and irw properly began +receiving button presses again and ir_send worked without error.) + + +3. Bridge drivers, if able, should provide a chip reset() callback +to lirc_zilog via struct IR_i2c_init_data. cx18 and ivtv already have routines +to perform Z8 chip resets via GPIO manipulations. This would allow lirc_zilog +to bring the chip back to normal when it hangs, in the same places the +original lirc_pvr150 driver code does. This is not strictly needed, so it +is not required to move lirc_zilog out of staging. + +Note: Both lirc_zilog and ir-kbd-i2c support the Zilog Z8 for IR, as programmed +and installed on Hauppauge products. When working on either module, developers +must consider at least the following bridge drivers which mention an IR Rx unit +at address 0x71 (indicative of a Z8): + + ivtv cx18 hdpvr pvrusb2 bt8xx cx88 saa7134 + diff --git a/drivers/staging/media/lirc/lirc_bt829.c b/drivers/staging/media/lirc/lirc_bt829.c new file mode 100644 index 00000000000..fe010542ab4 --- /dev/null +++ b/drivers/staging/media/lirc/lirc_bt829.c @@ -0,0 +1,405 @@ +/* + * Remote control driver for the TV-card based on bt829 + * + * by Leonid Froenchenko <lfroen@galileo.co.il> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/threads.h> +#include <linux/sched.h> +#include <linux/ioport.h> +#include <linux/pci.h> +#include <linux/delay.h> + +#include <media/lirc_dev.h> + +static int poll_main(void); +static int atir_init_start(void); + +static void write_index(unsigned char index, unsigned int value); +static unsigned int read_index(unsigned char index); + +static void do_i2c_start(void); +static void do_i2c_stop(void); + +static void seems_wr_byte(unsigned char al); +static unsigned char seems_rd_byte(void); + +static unsigned int read_index(unsigned char al); +static void write_index(unsigned char ah, unsigned int edx); + +static void cycle_delay(int cycle); + +static void do_set_bits(unsigned char bl); +static unsigned char do_get_bits(void); + +#define DATA_PCI_OFF 0x7FFC00 +#define WAIT_CYCLE 20 + +#define DRIVER_NAME "lirc_bt829" + +static bool debug; +#define dprintk(fmt, args...) \ + do { \ + if (debug) \ + printk(KERN_DEBUG DRIVER_NAME ": "fmt, ## args); \ + } while (0) + +static int atir_minor; +static phys_addr_t pci_addr_phys; +static unsigned char __iomem *pci_addr_lin; + +static struct lirc_driver atir_driver; + +static struct pci_dev *do_pci_probe(void) +{ + struct pci_dev *my_dev; + my_dev = pci_get_device(PCI_VENDOR_ID_ATI, + PCI_DEVICE_ID_ATI_264VT, NULL); + if (my_dev) { + pr_err("Using device: %s\n", pci_name(my_dev)); + pci_addr_phys = 0; + if (my_dev->resource[0].flags & IORESOURCE_MEM) { + pci_addr_phys = my_dev->resource[0].start; + pr_info("memory at %pa\n", &pci_addr_phys); + } + if (pci_addr_phys == 0) { + pr_err("no memory resource ?\n"); + pci_dev_put(my_dev); + return NULL; + } + } else { + pr_err("pci_probe failed\n"); + return NULL; + } + return my_dev; +} + +static int atir_add_to_buf(void *data, struct lirc_buffer *buf) +{ + unsigned char key; + int status; + status = poll_main(); + key = (status >> 8) & 0xFF; + if (status & 0xFF) { + dprintk("reading key %02X\n", key); + lirc_buffer_write(buf, &key); + return 0; + } + return -ENODATA; +} + +static int atir_set_use_inc(void *data) +{ + dprintk("driver is opened\n"); + return 0; +} + +static void atir_set_use_dec(void *data) +{ + dprintk("driver is closed\n"); +} + +int init_module(void) +{ + struct pci_dev *pdev; + int rc; + + pdev = do_pci_probe(); + if (pdev == NULL) + return -ENODEV; + + rc = pci_enable_device(pdev); + if (rc) + goto err_put_dev; + + if (!atir_init_start()) { + rc = -ENODEV; + goto err_disable; + } + + strcpy(atir_driver.name, "ATIR"); + atir_driver.minor = -1; + atir_driver.code_length = 8; + atir_driver.sample_rate = 10; + atir_driver.data = NULL; + atir_driver.add_to_buf = atir_add_to_buf; + atir_driver.set_use_inc = atir_set_use_inc; + atir_driver.set_use_dec = atir_set_use_dec; + atir_driver.dev = &pdev->dev; + atir_driver.owner = THIS_MODULE; + + atir_minor = lirc_register_driver(&atir_driver); + if (atir_minor < 0) { + pr_err("failed to register driver!\n"); + rc = atir_minor; + goto err_unmap; + } + dprintk("driver is registered on minor %d\n", atir_minor); + + return 0; + +err_unmap: + iounmap(pci_addr_lin); +err_disable: + pci_disable_device(pdev); +err_put_dev: + pci_dev_put(pdev); + return rc; +} + + +void cleanup_module(void) +{ + struct pci_dev *pdev = to_pci_dev(atir_driver.dev); + + lirc_unregister_driver(atir_minor); + iounmap(pci_addr_lin); + pci_disable_device(pdev); + pci_dev_put(pdev); +} + + +static int atir_init_start(void) +{ + pci_addr_lin = ioremap(pci_addr_phys + DATA_PCI_OFF, 0x400); + if (!pci_addr_lin) { + pr_info("pci mem must be mapped\n"); + return 0; + } + return 1; +} + +static void cycle_delay(int cycle) +{ + udelay(WAIT_CYCLE*cycle); +} + + +static int poll_main(void) +{ + unsigned char status_high, status_low; + + do_i2c_start(); + + seems_wr_byte(0xAA); + seems_wr_byte(0x01); + + do_i2c_start(); + + seems_wr_byte(0xAB); + + status_low = seems_rd_byte(); + status_high = seems_rd_byte(); + + do_i2c_stop(); + + return (status_high << 8) | status_low; +} + +static void do_i2c_start(void) +{ + do_set_bits(3); + cycle_delay(4); + + do_set_bits(1); + cycle_delay(7); + + do_set_bits(0); + cycle_delay(2); +} + +static void do_i2c_stop(void) +{ + unsigned char bits; + bits = do_get_bits() & 0xFD; + do_set_bits(bits); + cycle_delay(1); + + bits |= 1; + do_set_bits(bits); + cycle_delay(2); + + bits |= 2; + do_set_bits(bits); + bits = 3; + do_set_bits(bits); + cycle_delay(2); +} + +static void seems_wr_byte(unsigned char value) +{ + int i; + unsigned char reg; + + reg = do_get_bits(); + for (i = 0; i < 8; i++) { + if (value & 0x80) + reg |= 0x02; + else + reg &= 0xFD; + + do_set_bits(reg); + cycle_delay(1); + + reg |= 1; + do_set_bits(reg); + cycle_delay(1); + + reg &= 0xFE; + do_set_bits(reg); + cycle_delay(1); + value <<= 1; + } + cycle_delay(2); + + reg |= 2; + do_set_bits(reg); + + reg |= 1; + do_set_bits(reg); + + cycle_delay(1); + do_get_bits(); + + reg &= 0xFE; + do_set_bits(reg); + cycle_delay(3); +} + +static unsigned char seems_rd_byte(void) +{ + int i; + int rd_byte; + unsigned char bits_2, bits_1; + + bits_1 = do_get_bits() | 2; + do_set_bits(bits_1); + + rd_byte = 0; + for (i = 0; i < 8; i++) { + bits_1 &= 0xFE; + do_set_bits(bits_1); + cycle_delay(2); + + bits_1 |= 1; + do_set_bits(bits_1); + cycle_delay(1); + + bits_2 = do_get_bits(); + if (bits_2 & 2) + rd_byte |= 1; + + rd_byte <<= 1; + } + + bits_1 = 0; + if (bits_2 == 0) + bits_1 |= 2; + + do_set_bits(bits_1); + cycle_delay(2); + + bits_1 |= 1; + do_set_bits(bits_1); + cycle_delay(3); + + bits_1 &= 0xFE; + do_set_bits(bits_1); + cycle_delay(2); + + rd_byte >>= 1; + rd_byte &= 0xFF; + return rd_byte; +} + +static void do_set_bits(unsigned char new_bits) +{ + int reg_val; + reg_val = read_index(0x34); + if (new_bits & 2) { + reg_val &= 0xFFFFFFDF; + reg_val |= 1; + } else { + reg_val &= 0xFFFFFFFE; + reg_val |= 0x20; + } + reg_val |= 0x10; + write_index(0x34, reg_val); + + reg_val = read_index(0x31); + if (new_bits & 1) + reg_val |= 0x1000000; + else + reg_val &= 0xFEFFFFFF; + + reg_val |= 0x8000000; + write_index(0x31, reg_val); +} + +static unsigned char do_get_bits(void) +{ + unsigned char bits; + int reg_val; + + reg_val = read_index(0x34); + reg_val |= 0x10; + reg_val &= 0xFFFFFFDF; + write_index(0x34, reg_val); + + reg_val = read_index(0x34); + bits = 0; + if (reg_val & 8) + bits |= 2; + else + bits &= 0xFD; + + reg_val = read_index(0x31); + if (reg_val & 0x1000000) + bits |= 1; + else + bits &= 0xFE; + + return bits; +} + +static unsigned int read_index(unsigned char index) +{ + unsigned char __iomem *addr; + unsigned int value; + /* addr = pci_addr_lin + DATA_PCI_OFF + ((index & 0xFF) << 2); */ + addr = pci_addr_lin + ((index & 0xFF) << 2); + value = readl(addr); + return value; +} + +static void write_index(unsigned char index, unsigned int reg_val) +{ + unsigned char __iomem *addr; + addr = pci_addr_lin + ((index & 0xFF) << 2); + writel(reg_val, addr); +} + +MODULE_AUTHOR("Froenchenko Leonid"); +MODULE_DESCRIPTION("IR remote driver for bt829 based TV cards"); +MODULE_LICENSE("GPL"); + +module_param(debug, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(debug, "Debug enabled or not"); diff --git a/drivers/staging/media/lirc/lirc_igorplugusb.c b/drivers/staging/media/lirc/lirc_igorplugusb.c new file mode 100644 index 00000000000..44b0d076608 --- /dev/null +++ b/drivers/staging/media/lirc/lirc_igorplugusb.c @@ -0,0 +1,514 @@ +/* + * lirc_igorplugusb - USB remote support for LIRC + * + * Supports the standard homebrew IgorPlugUSB receiver with Igor's firmware. + * See http://www.cesko.host.sk/IgorPlugUSB/IgorPlug-USB%20(AVR)_eng.htm + * + * The device can only record bursts of up to 36 pulses/spaces. + * Works fine with RC5. Longer commands lead to device buffer overrun. + * (Maybe a better firmware or a microcontroller with more ram can help?) + * + * Version 0.1 [beta status] + * + * Copyright (C) 2004 Jan M. Hochstein + * <hochstein@algo.informatik.tu-darmstadt.de> + * + * This driver was derived from: + * Paul Miller <pmiller9@users.sourceforge.net> + * "lirc_atiusb" module + * Vladimir Dergachev <volodya@minspring.com>'s 2002 + * "USB ATI Remote support" (input device) + * Adrian Dewhurst <sailor-lk@sailorfrag.net>'s 2002 + * "USB StreamZap remote driver" (LIRC) + * Artur Lipowski <alipowski@kki.net.pl>'s 2002 + * "lirc_dev" and "lirc_gpio" LIRC modules + */ + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/kmod.h> +#include <linux/sched.h> +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/usb.h> +#include <linux/time.h> + +#include <media/lirc.h> +#include <media/lirc_dev.h> + + +/* module identification */ +#define DRIVER_VERSION "0.2" +#define DRIVER_AUTHOR \ + "Jan M. Hochstein <hochstein@algo.informatik.tu-darmstadt.de>" +#define DRIVER_DESC "Igorplug USB remote driver for LIRC" +#define DRIVER_NAME "lirc_igorplugusb" + +/* One mode2 pulse/space has 4 bytes. */ +#define CODE_LENGTH sizeof(int) + +/* Igor's firmware cannot record bursts longer than 36. */ +#define DEVICE_BUFLEN 36 + +/* + * Header at the beginning of the device's buffer: + * unsigned char data_length + * unsigned char data_start (!=0 means ring-buffer overrun) + * unsigned char counter (incremented by each burst) + */ +#define DEVICE_HEADERLEN 3 + +/* This is for the gap */ +#define ADDITIONAL_LIRC_BYTES 2 + +/* times to poll per second */ +#define SAMPLE_RATE 100 +static int sample_rate = SAMPLE_RATE; + + +/**** Igor's USB Request Codes */ + +#define SET_INFRABUFFER_EMPTY 1 +/** + * Params: none + * Answer: empty + */ + +#define GET_INFRACODE 2 +/** + * Params: + * wValue: offset to begin reading infra buffer + * + * Answer: infra data + */ + +#define SET_DATAPORT_DIRECTION 3 +/** + * Params: + * wValue: (byte) 1 bit for each data port pin (0=in, 1=out) + * + * Answer: empty + */ + +#define GET_DATAPORT_DIRECTION 4 +/** + * Params: none + * + * Answer: (byte) 1 bit for each data port pin (0=in, 1=out) + */ + +#define SET_OUT_DATAPORT 5 +/** + * Params: + * wValue: byte to write to output data port + * + * Answer: empty + */ + +#define GET_OUT_DATAPORT 6 +/** + * Params: none + * + * Answer: least significant 3 bits read from output data port + */ + +#define GET_IN_DATAPORT 7 +/** + * Params: none + * + * Answer: least significant 3 bits read from input data port + */ + +#define READ_EEPROM 8 +/** + * Params: + * wValue: offset to begin reading EEPROM + * + * Answer: EEPROM bytes + */ + +#define WRITE_EEPROM 9 +/** + * Params: + * wValue: offset to EEPROM byte + * wIndex: byte to write + * + * Answer: empty + */ + +#define SEND_RS232 10 +/** + * Params: + * wValue: byte to send + * + * Answer: empty + */ + +#define RECV_RS232 11 +/** + * Params: none + * + * Answer: byte received + */ + +#define SET_RS232_BAUD 12 +/** + * Params: + * wValue: byte to write to UART bit rate register (UBRR) + * + * Answer: empty + */ + +#define GET_RS232_BAUD 13 +/** + * Params: none + * + * Answer: byte read from UART bit rate register (UBRR) + */ + + +/* data structure for each usb remote */ +struct igorplug { + + /* usb */ + struct usb_device *usbdev; + int devnum; + + unsigned char *buf_in; + unsigned int len_in; + int in_space; + struct timeval last_time; + + dma_addr_t dma_in; + + /* lirc */ + struct lirc_driver *d; + + /* handle sending (init strings) */ + int send_flags; +}; + +static int unregister_from_lirc(struct igorplug *ir) +{ + struct lirc_driver *d; + int devnum; + + if (!ir) { + dev_err(&ir->usbdev->dev, + "%s: called with NULL device struct!\n", __func__); + return -EINVAL; + } + + devnum = ir->devnum; + d = ir->d; + + if (!d) { + dev_err(&ir->usbdev->dev, + "%s: called with NULL lirc driver struct!\n", __func__); + return -EINVAL; + } + + dev_dbg(&ir->usbdev->dev, "calling lirc_unregister_driver\n"); + lirc_unregister_driver(d->minor); + + return devnum; +} + +static int set_use_inc(void *data) +{ + struct igorplug *ir = data; + + if (!ir) { + printk(DRIVER_NAME "[?]: set_use_inc called with no context\n"); + return -EIO; + } + + dev_dbg(&ir->usbdev->dev, "set use inc\n"); + + if (!ir->usbdev) + return -ENODEV; + + return 0; +} + +static void set_use_dec(void *data) +{ + struct igorplug *ir = data; + + if (!ir) { + printk(DRIVER_NAME "[?]: set_use_dec called with no context\n"); + return; + } + + dev_dbg(&ir->usbdev->dev, "set use dec\n"); +} + +static void send_fragment(struct igorplug *ir, struct lirc_buffer *buf, + int i, int max) +{ + int code; + + /* MODE2: pulse/space (PULSE_BIT) in 1us units */ + while (i < max) { + /* 1 Igor-tick = 85.333333 us */ + code = (unsigned int)ir->buf_in[i] * 85 + + (unsigned int)ir->buf_in[i] / 3; + ir->last_time.tv_usec += code; + if (ir->in_space) + code |= PULSE_BIT; + lirc_buffer_write(buf, (unsigned char *)&code); + /* 1 chunk = CODE_LENGTH bytes */ + ir->in_space ^= 1; + ++i; + } +} + +/** + * Called in user context. + * return 0 if data was added to the buffer and + * -ENODATA if none was available. This should add some number of bits + * evenly divisible by code_length to the buffer + */ +static int igorplugusb_remote_poll(void *data, struct lirc_buffer *buf) +{ + int ret; + struct igorplug *ir = (struct igorplug *)data; + + if (!ir || !ir->usbdev) /* Has the device been removed? */ + return -ENODEV; + + memset(ir->buf_in, 0, ir->len_in); + + ret = usb_control_msg(ir->usbdev, usb_rcvctrlpipe(ir->usbdev, 0), + GET_INFRACODE, USB_TYPE_VENDOR | USB_DIR_IN, + 0/* offset */, /*unused*/0, + ir->buf_in, ir->len_in, + /*timeout*/HZ * USB_CTRL_GET_TIMEOUT); + if (ret > 0) { + int code, timediff; + struct timeval now; + + /* ACK packet has 1 byte --> ignore */ + if (ret < DEVICE_HEADERLEN) + return -ENODATA; + + dev_dbg(&ir->usbdev->dev, "Got %d bytes. Header: %*ph\n", + ret, 3, ir->buf_in); + + do_gettimeofday(&now); + timediff = now.tv_sec - ir->last_time.tv_sec; + if (timediff + 1 > PULSE_MASK / 1000000) + timediff = PULSE_MASK; + else { + timediff *= 1000000; + timediff += now.tv_usec - ir->last_time.tv_usec; + } + ir->last_time.tv_sec = now.tv_sec; + ir->last_time.tv_usec = now.tv_usec; + + /* create leading gap */ + code = timediff; + lirc_buffer_write(buf, (unsigned char *)&code); + ir->in_space = 1; /* next comes a pulse */ + + if (ir->buf_in[2] == 0) + send_fragment(ir, buf, DEVICE_HEADERLEN, ret); + else { + dev_warn(&ir->usbdev->dev, + "[%d]: Device buffer overrun.\n", ir->devnum); + /* HHHNNNNNNNNNNNOOOOOOOO H = header + <---[2]---> N = newer + <---------ret--------> O = older */ + ir->buf_in[2] %= ret - DEVICE_HEADERLEN; /* sanitize */ + /* keep even-ness to not desync pulse/pause */ + send_fragment(ir, buf, DEVICE_HEADERLEN + + ir->buf_in[2] - (ir->buf_in[2] & 1), ret); + send_fragment(ir, buf, DEVICE_HEADERLEN, + DEVICE_HEADERLEN + ir->buf_in[2]); + } + + ret = usb_control_msg( + ir->usbdev, usb_rcvctrlpipe(ir->usbdev, 0), + SET_INFRABUFFER_EMPTY, USB_TYPE_VENDOR|USB_DIR_IN, + /*unused*/0, /*unused*/0, + /*dummy*/ir->buf_in, /*dummy*/ir->len_in, + /*timeout*/HZ * USB_CTRL_GET_TIMEOUT); + if (ret < 0) + printk(DRIVER_NAME "[%d]: SET_INFRABUFFER_EMPTY: error %d\n", + ir->devnum, ret); + return 0; + } else if (ret < 0) + printk(DRIVER_NAME "[%d]: GET_INFRACODE: error %d\n", + ir->devnum, ret); + + return -ENODATA; +} + +static int igorplugusb_remote_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct usb_device *dev; + struct usb_host_interface *idesc = NULL; + struct usb_endpoint_descriptor *ep; + struct igorplug *ir = NULL; + struct lirc_driver *driver = NULL; + int devnum, pipe, maxp; + char buf[63], name[128] = ""; + int ret; + + dev_dbg(&intf->dev, "%s: usb probe called.\n", __func__); + + dev = interface_to_usbdev(intf); + + idesc = intf->cur_altsetting; + + if (idesc->desc.bNumEndpoints != 1) + return -ENODEV; + + ep = &idesc->endpoint->desc; + if (((ep->bEndpointAddress & USB_ENDPOINT_DIR_MASK) + != USB_DIR_IN) + || (ep->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) + != USB_ENDPOINT_XFER_CONTROL) + return -ENODEV; + + pipe = usb_rcvctrlpipe(dev, ep->bEndpointAddress); + devnum = dev->devnum; + maxp = usb_maxpacket(dev, pipe, usb_pipeout(pipe)); + + dev_dbg(&intf->dev, "%s: bytes_in_key=%zu maxp=%d\n", + __func__, CODE_LENGTH, maxp); + + ir = devm_kzalloc(&intf->dev, sizeof(*ir), GFP_KERNEL); + if (!ir) + return -ENOMEM; + + driver = devm_kzalloc(&intf->dev, sizeof(*driver), GFP_KERNEL); + if (!driver) + return -ENOMEM; + + ir->buf_in = usb_alloc_coherent(dev, DEVICE_BUFLEN + DEVICE_HEADERLEN, + GFP_ATOMIC, &ir->dma_in); + if (!ir->buf_in) + return -ENOMEM; + + strcpy(driver->name, DRIVER_NAME " "); + driver->minor = -1; + driver->code_length = CODE_LENGTH * 8; /* in bits */ + driver->features = LIRC_CAN_REC_MODE2; + driver->data = ir; + driver->chunk_size = CODE_LENGTH; + driver->buffer_size = DEVICE_BUFLEN + ADDITIONAL_LIRC_BYTES; + driver->set_use_inc = &set_use_inc; + driver->set_use_dec = &set_use_dec; + driver->sample_rate = sample_rate; /* per second */ + driver->add_to_buf = &igorplugusb_remote_poll; + driver->dev = &intf->dev; + driver->owner = THIS_MODULE; + + ret = lirc_register_driver(driver); + if (ret < 0) { + usb_free_coherent(dev, DEVICE_BUFLEN + DEVICE_HEADERLEN, + ir->buf_in, ir->dma_in); + return ret; + } + + driver->minor = ret; + ir->d = driver; + ir->devnum = devnum; + ir->usbdev = dev; + ir->len_in = DEVICE_BUFLEN + DEVICE_HEADERLEN; + ir->in_space = 1; /* First mode2 event is a space. */ + do_gettimeofday(&ir->last_time); + + if (dev->descriptor.iManufacturer + && usb_string(dev, dev->descriptor.iManufacturer, + buf, sizeof(buf)) > 0) + strlcpy(name, buf, sizeof(name)); + if (dev->descriptor.iProduct + && usb_string(dev, dev->descriptor.iProduct, buf, sizeof(buf)) > 0) + snprintf(name + strlen(name), sizeof(name) - strlen(name), + " %s", buf); + printk(DRIVER_NAME "[%d]: %s on usb%d:%d\n", devnum, name, + dev->bus->busnum, devnum); + + /* clear device buffer */ + ret = usb_control_msg(ir->usbdev, usb_rcvctrlpipe(ir->usbdev, 0), + SET_INFRABUFFER_EMPTY, USB_TYPE_VENDOR|USB_DIR_IN, + /*unused*/0, /*unused*/0, + /*dummy*/ir->buf_in, /*dummy*/ir->len_in, + /*timeout*/HZ * USB_CTRL_GET_TIMEOUT); + if (ret < 0) + printk(DRIVER_NAME "[%d]: SET_INFRABUFFER_EMPTY: error %d\n", + devnum, ret); + + usb_set_intfdata(intf, ir); + return 0; +} + +static void igorplugusb_remote_disconnect(struct usb_interface *intf) +{ + struct usb_device *usbdev = interface_to_usbdev(intf); + struct igorplug *ir = usb_get_intfdata(intf); + struct device *dev = &intf->dev; + int devnum; + + usb_set_intfdata(intf, NULL); + + if (!ir || !ir->d) + return; + + ir->usbdev = NULL; + + usb_free_coherent(usbdev, ir->len_in, ir->buf_in, ir->dma_in); + + devnum = unregister_from_lirc(ir); + + dev_info(dev, DRIVER_NAME "[%d]: %s done\n", devnum, __func__); +} + +static struct usb_device_id igorplugusb_remote_id_table[] = { + /* Igor Plug USB (Atmel's Manufact. ID) */ + { USB_DEVICE(0x03eb, 0x0002) }, + /* Fit PC2 Infrared Adapter */ + { USB_DEVICE(0x03eb, 0x21fe) }, + + /* Terminating entry */ + { } +}; + +static struct usb_driver igorplugusb_remote_driver = { + .name = DRIVER_NAME, + .probe = igorplugusb_remote_probe, + .disconnect = igorplugusb_remote_disconnect, + .id_table = igorplugusb_remote_id_table +}; + +module_usb_driver(igorplugusb_remote_driver); + +#include <linux/vermagic.h> +MODULE_INFO(vermagic, VERMAGIC_STRING); + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_LICENSE("GPL"); +MODULE_DEVICE_TABLE(usb, igorplugusb_remote_id_table); + +module_param(sample_rate, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(sample_rate, "Sampling rate in Hz (default: 100)"); diff --git a/drivers/staging/media/lirc/lirc_imon.c b/drivers/staging/media/lirc/lirc_imon.c new file mode 100644 index 00000000000..a5b62eec5e2 --- /dev/null +++ b/drivers/staging/media/lirc/lirc_imon.c @@ -0,0 +1,1042 @@ +/* + * lirc_imon.c: LIRC/VFD/LCD driver for SoundGraph iMON IR/VFD/LCD + * including the iMON PAD model + * + * Copyright(C) 2004 Venky Raju(dev@venky.ws) + * Copyright(C) 2009 Jarod Wilson <jarod@wilsonet.com> + * + * lirc_imon is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/uaccess.h> +#include <linux/usb.h> + +#include <media/lirc.h> +#include <media/lirc_dev.h> + + +#define MOD_AUTHOR "Venky Raju <dev@venky.ws>" +#define MOD_DESC "Driver for SoundGraph iMON MultiMedia IR/Display" +#define MOD_NAME "lirc_imon" +#define MOD_VERSION "0.8" + +#define DISPLAY_MINOR_BASE 144 +#define DEVICE_NAME "lcd%d" + +#define BUF_CHUNK_SIZE 4 +#define BUF_SIZE 128 + +#define BIT_DURATION 250 /* each bit received is 250us */ + +/*** P R O T O T Y P E S ***/ + +/* USB Callback prototypes */ +static int imon_probe(struct usb_interface *interface, + const struct usb_device_id *id); +static void imon_disconnect(struct usb_interface *interface); +static void usb_rx_callback(struct urb *urb); +static void usb_tx_callback(struct urb *urb); + +/* suspend/resume support */ +static int imon_resume(struct usb_interface *intf); +static int imon_suspend(struct usb_interface *intf, pm_message_t message); + +/* Display file_operations function prototypes */ +static int display_open(struct inode *inode, struct file *file); +static int display_close(struct inode *inode, struct file *file); + +/* VFD write operation */ +static ssize_t vfd_write(struct file *file, const char __user *buf, + size_t n_bytes, loff_t *pos); + +/* LIRC driver function prototypes */ +static int ir_open(void *data); +static void ir_close(void *data); + +/*** G L O B A L S ***/ +#define IMON_DATA_BUF_SZ 35 + +struct imon_context { + struct usb_device *usbdev; + /* Newer devices have two interfaces */ + int display; /* not all controllers do */ + int display_isopen; /* display port has been opened */ + int ir_isopen; /* IR port open */ + int dev_present; /* USB device presence */ + struct mutex ctx_lock; /* to lock this object */ + wait_queue_head_t remove_ok; /* For unexpected USB disconnects */ + + int vfd_proto_6p; /* some VFD require a 6th packet */ + + struct lirc_driver *driver; + struct usb_endpoint_descriptor *rx_endpoint; + struct usb_endpoint_descriptor *tx_endpoint; + struct urb *rx_urb; + struct urb *tx_urb; + unsigned char usb_rx_buf[8]; + unsigned char usb_tx_buf[8]; + + struct rx_data { + int count; /* length of 0 or 1 sequence */ + int prev_bit; /* logic level of sequence */ + int initial_space; /* initial space flag */ + } rx; + + struct tx_t { + unsigned char data_buf[IMON_DATA_BUF_SZ]; /* user data buffer */ + struct completion finished; /* wait for write to finish */ + atomic_t busy; /* write in progress */ + int status; /* status of tx completion */ + } tx; +}; + +static const struct file_operations display_fops = { + .owner = THIS_MODULE, + .open = &display_open, + .write = &vfd_write, + .release = &display_close, + .llseek = noop_llseek, +}; + +/* + * USB Device ID for iMON USB Control Boards + * + * The Windows drivers contain 6 different inf files, more or less one for + * each new device until the 0x0034-0x0046 devices, which all use the same + * driver. Some of the devices in the 34-46 range haven't been definitively + * identified yet. Early devices have either a TriGem Computer, Inc. or a + * Samsung vendor ID (0x0aa8 and 0x04e8 respectively), while all later + * devices use the SoundGraph vendor ID (0x15c2). + */ +static struct usb_device_id imon_usb_id_table[] = { + /* TriGem iMON (IR only) -- TG_iMON.inf */ + { USB_DEVICE(0x0aa8, 0x8001) }, + + /* SoundGraph iMON (IR only) -- sg_imon.inf */ + { USB_DEVICE(0x04e8, 0xff30) }, + + /* SoundGraph iMON VFD (IR & VFD) -- iMON_VFD.inf */ + { USB_DEVICE(0x0aa8, 0xffda) }, + + /* SoundGraph iMON SS (IR & VFD) -- iMON_SS.inf */ + { USB_DEVICE(0x15c2, 0xffda) }, + + {} +}; + +/* Some iMON VFD models requires a 6th packet for VFD writes */ +static struct usb_device_id vfd_proto_6p_list[] = { + { USB_DEVICE(0x15c2, 0xffda) }, + {} +}; + +/* Some iMON devices have no lcd/vfd, don't set one up */ +static struct usb_device_id ir_only_list[] = { + { USB_DEVICE(0x0aa8, 0x8001) }, + { USB_DEVICE(0x04e8, 0xff30) }, + {} +}; + +/* USB Device data */ +static struct usb_driver imon_driver = { + .name = MOD_NAME, + .probe = imon_probe, + .disconnect = imon_disconnect, + .suspend = imon_suspend, + .resume = imon_resume, + .id_table = imon_usb_id_table, +}; + +static struct usb_class_driver imon_class = { + .name = DEVICE_NAME, + .fops = &display_fops, + .minor_base = DISPLAY_MINOR_BASE, +}; + +/* to prevent races between open() and disconnect(), probing, etc */ +static DEFINE_MUTEX(driver_lock); + +static int debug; + +/*** M O D U L E C O D E ***/ + +MODULE_AUTHOR(MOD_AUTHOR); +MODULE_DESCRIPTION(MOD_DESC); +MODULE_VERSION(MOD_VERSION); +MODULE_LICENSE("GPL"); +MODULE_DEVICE_TABLE(usb, imon_usb_id_table); +module_param(debug, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(debug, "Debug messages: 0=no, 1=yes(default: no)"); + +static void free_imon_context(struct imon_context *context) +{ + struct device *dev = context->driver->dev; + usb_free_urb(context->tx_urb); + usb_free_urb(context->rx_urb); + lirc_buffer_free(context->driver->rbuf); + kfree(context->driver->rbuf); + kfree(context->driver); + kfree(context); + + dev_dbg(dev, "%s: iMON context freed\n", __func__); +} + +static void deregister_from_lirc(struct imon_context *context) +{ + int retval; + int minor = context->driver->minor; + + retval = lirc_unregister_driver(minor); + if (retval) + dev_err(&context->usbdev->dev, + ": %s: unable to deregister from lirc(%d)", + __func__, retval); + else + dev_info(&context->usbdev->dev, + "Deregistered iMON driver (minor:%d)\n", minor); + +} + +/** + * Called when the Display device (e.g. /dev/lcd0) + * is opened by the application. + */ +static int display_open(struct inode *inode, struct file *file) +{ + struct usb_interface *interface; + struct imon_context *context = NULL; + int subminor; + int retval = 0; + + /* prevent races with disconnect */ + mutex_lock(&driver_lock); + + subminor = iminor(inode); + interface = usb_find_interface(&imon_driver, subminor); + if (!interface) { + pr_err("%s: could not find interface for minor %d\n", + __func__, subminor); + retval = -ENODEV; + goto exit; + } + context = usb_get_intfdata(interface); + + if (!context) { + dev_err(&interface->dev, + "%s: no context found for minor %d\n", + __func__, subminor); + retval = -ENODEV; + goto exit; + } + + mutex_lock(&context->ctx_lock); + + if (!context->display) { + dev_err(&interface->dev, + "%s: display not supported by device\n", __func__); + retval = -ENODEV; + } else if (context->display_isopen) { + dev_err(&interface->dev, + "%s: display port is already open\n", __func__); + retval = -EBUSY; + } else { + context->display_isopen = 1; + file->private_data = context; + dev_info(context->driver->dev, "display port opened\n"); + } + + mutex_unlock(&context->ctx_lock); + +exit: + mutex_unlock(&driver_lock); + return retval; +} + +/** + * Called when the display device (e.g. /dev/lcd0) + * is closed by the application. + */ +static int display_close(struct inode *inode, struct file *file) +{ + struct imon_context *context = NULL; + int retval = 0; + + context = file->private_data; + + if (!context) { + pr_err("%s: no context for device\n", __func__); + return -ENODEV; + } + + mutex_lock(&context->ctx_lock); + + if (!context->display) { + dev_err(&context->usbdev->dev, + "%s: display not supported by device\n", __func__); + retval = -ENODEV; + } else if (!context->display_isopen) { + dev_err(&context->usbdev->dev, + "%s: display is not open\n", __func__); + retval = -EIO; + } else { + context->display_isopen = 0; + dev_info(context->driver->dev, "display port closed\n"); + if (!context->dev_present && !context->ir_isopen) { + /* + * Device disconnected before close and IR port is not + * open. If IR port is open, context will be deleted by + * ir_close. + */ + mutex_unlock(&context->ctx_lock); + free_imon_context(context); + return retval; + } + } + + mutex_unlock(&context->ctx_lock); + return retval; +} + +/** + * Sends a packet to the device -- this function must be called + * with context->ctx_lock held. + */ +static int send_packet(struct imon_context *context) +{ + unsigned int pipe; + int interval = 0; + int retval = 0; + + /* Check if we need to use control or interrupt urb */ + pipe = usb_sndintpipe(context->usbdev, + context->tx_endpoint->bEndpointAddress); + interval = context->tx_endpoint->bInterval; + + usb_fill_int_urb(context->tx_urb, context->usbdev, pipe, + context->usb_tx_buf, + sizeof(context->usb_tx_buf), + usb_tx_callback, context, interval); + + context->tx_urb->actual_length = 0; + + init_completion(&context->tx.finished); + atomic_set(&(context->tx.busy), 1); + + retval = usb_submit_urb(context->tx_urb, GFP_KERNEL); + if (retval) { + atomic_set(&(context->tx.busy), 0); + dev_err(&context->usbdev->dev, + "%s: error submitting urb(%d)\n", __func__, retval); + } else { + /* Wait for transmission to complete (or abort) */ + mutex_unlock(&context->ctx_lock); + retval = wait_for_completion_interruptible( + &context->tx.finished); + if (retval) + dev_err(&context->usbdev->dev, + "%s: task interrupted\n", __func__); + mutex_lock(&context->ctx_lock); + + retval = context->tx.status; + if (retval) + dev_err(&context->usbdev->dev, + "%s: packet tx failed (%d)\n", + __func__, retval); + } + + return retval; +} + +/** + * Writes data to the VFD. The iMON VFD is 2x16 characters + * and requires data in 5 consecutive USB interrupt packets, + * each packet but the last carrying 7 bytes. + * + * I don't know if the VFD board supports features such as + * scrolling, clearing rows, blanking, etc. so at + * the caller must provide a full screen of data. If fewer + * than 32 bytes are provided spaces will be appended to + * generate a full screen. + */ +static ssize_t vfd_write(struct file *file, const char __user *buf, + size_t n_bytes, loff_t *pos) +{ + int i; + int offset; + int seq; + int retval = 0; + struct imon_context *context; + const unsigned char vfd_packet6[] = { + 0x01, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF }; + int *data_buf = NULL; + + context = file->private_data; + if (!context) { + pr_err("%s: no context for device\n", __func__); + return -ENODEV; + } + + mutex_lock(&context->ctx_lock); + + if (!context->dev_present) { + dev_err(&context->usbdev->dev, + "%s: no iMON device present\n", __func__); + retval = -ENODEV; + goto exit; + } + + if (n_bytes <= 0 || n_bytes > IMON_DATA_BUF_SZ - 3) { + dev_err(&context->usbdev->dev, + "%s: invalid payload size\n", __func__); + retval = -EINVAL; + goto exit; + } + + data_buf = memdup_user(buf, n_bytes); + if (IS_ERR(data_buf)) { + retval = PTR_ERR(data_buf); + goto exit; + } + + memcpy(context->tx.data_buf, data_buf, n_bytes); + + /* Pad with spaces */ + for (i = n_bytes; i < IMON_DATA_BUF_SZ - 3; ++i) + context->tx.data_buf[i] = ' '; + + for (i = IMON_DATA_BUF_SZ - 3; i < IMON_DATA_BUF_SZ; ++i) + context->tx.data_buf[i] = 0xFF; + + offset = 0; + seq = 0; + + do { + memcpy(context->usb_tx_buf, context->tx.data_buf + offset, 7); + context->usb_tx_buf[7] = (unsigned char) seq; + + retval = send_packet(context); + if (retval) { + dev_err(&context->usbdev->dev, + "%s: send packet failed for packet #%d\n", + __func__, seq/2); + goto exit; + } else { + seq += 2; + offset += 7; + } + + } while (offset < IMON_DATA_BUF_SZ); + + if (context->vfd_proto_6p) { + /* Send packet #6 */ + memcpy(context->usb_tx_buf, &vfd_packet6, sizeof(vfd_packet6)); + context->usb_tx_buf[7] = (unsigned char) seq; + retval = send_packet(context); + if (retval) + dev_err(&context->usbdev->dev, + "%s: send packet failed for packet #%d\n", + __func__, seq/2); + } + +exit: + mutex_unlock(&context->ctx_lock); + kfree(data_buf); + + return (!retval) ? n_bytes : retval; +} + +/** + * Callback function for USB core API: transmit data + */ +static void usb_tx_callback(struct urb *urb) +{ + struct imon_context *context; + + if (!urb) + return; + context = (struct imon_context *)urb->context; + if (!context) + return; + + context->tx.status = urb->status; + + /* notify waiters that write has finished */ + atomic_set(&context->tx.busy, 0); + complete(&context->tx.finished); + + return; +} + +/** + * Called by lirc_dev when the application opens /dev/lirc + */ +static int ir_open(void *data) +{ + int retval = 0; + struct imon_context *context; + + /* prevent races with disconnect */ + mutex_lock(&driver_lock); + + context = (struct imon_context *)data; + + /* initial IR protocol decode variables */ + context->rx.count = 0; + context->rx.initial_space = 1; + context->rx.prev_bit = 0; + + context->ir_isopen = 1; + dev_info(context->driver->dev, "IR port opened\n"); + + mutex_unlock(&driver_lock); + return retval; +} + +/** + * Called by lirc_dev when the application closes /dev/lirc + */ +static void ir_close(void *data) +{ + struct imon_context *context; + + context = (struct imon_context *)data; + if (!context) { + pr_err("%s: no context for device\n", __func__); + return; + } + + mutex_lock(&context->ctx_lock); + + context->ir_isopen = 0; + dev_info(context->driver->dev, "IR port closed\n"); + + if (!context->dev_present) { + /* + * Device disconnected while IR port was still open. Driver + * was not deregistered at disconnect time, so do it now. + */ + deregister_from_lirc(context); + + if (!context->display_isopen) { + mutex_unlock(&context->ctx_lock); + free_imon_context(context); + return; + } + /* + * If display port is open, context will be deleted by + * display_close + */ + } + + mutex_unlock(&context->ctx_lock); + return; +} + +/** + * Convert bit count to time duration (in us) and submit + * the value to lirc_dev. + */ +static void submit_data(struct imon_context *context) +{ + unsigned char buf[4]; + int value = context->rx.count; + int i; + + dev_dbg(context->driver->dev, "submitting data to LIRC\n"); + + value *= BIT_DURATION; + value &= PULSE_MASK; + if (context->rx.prev_bit) + value |= PULSE_BIT; + + for (i = 0; i < 4; ++i) + buf[i] = value>>(i*8); + + lirc_buffer_write(context->driver->rbuf, buf); + wake_up(&context->driver->rbuf->wait_poll); + return; +} + +static inline int tv2int(const struct timeval *a, const struct timeval *b) +{ + int usecs = 0; + int sec = 0; + + if (b->tv_usec > a->tv_usec) { + usecs = 1000000; + sec--; + } + + usecs += a->tv_usec - b->tv_usec; + + sec += a->tv_sec - b->tv_sec; + sec *= 1000; + usecs /= 1000; + sec += usecs; + + if (sec < 0) + sec = 1000; + + return sec; +} + +/** + * Process the incoming packet + */ +static void imon_incoming_packet(struct imon_context *context, + struct urb *urb, int intf) +{ + int len = urb->actual_length; + unsigned char *buf = urb->transfer_buffer; + struct device *dev = context->driver->dev; + int octet, bit; + unsigned char mask; + int i; + + /* + * just bail out if no listening IR client + */ + if (!context->ir_isopen) + return; + + if (len != 8) { + dev_warn(dev, "imon %s: invalid incoming packet " + "size (len = %d, intf%d)\n", __func__, len, intf); + return; + } + + if (debug) { + dev_info(dev, "raw packet: "); + for (i = 0; i < len; ++i) + printk("%02x ", buf[i]); + printk("\n"); + } + + /* + * Translate received data to pulse and space lengths. + * Received data is active low, i.e. pulses are 0 and + * spaces are 1. + * + * My original algorithm was essentially similar to + * Changwoo Ryu's with the exception that he switched + * the incoming bits to active high and also fed an + * initial space to LIRC at the start of a new sequence + * if the previous bit was a pulse. + * + * I've decided to adopt his algorithm. + */ + + if (buf[7] == 1 && context->rx.initial_space) { + /* LIRC requires a leading space */ + context->rx.prev_bit = 0; + context->rx.count = 4; + submit_data(context); + context->rx.count = 0; + } + + for (octet = 0; octet < 5; ++octet) { + mask = 0x80; + for (bit = 0; bit < 8; ++bit) { + int curr_bit = !(buf[octet] & mask); + if (curr_bit != context->rx.prev_bit) { + if (context->rx.count) { + submit_data(context); + context->rx.count = 0; + } + context->rx.prev_bit = curr_bit; + } + ++context->rx.count; + mask >>= 1; + } + } + + if (buf[7] == 10) { + if (context->rx.count) { + submit_data(context); + context->rx.count = 0; + } + context->rx.initial_space = context->rx.prev_bit; + } +} + +/** + * Callback function for USB core API: receive data + */ +static void usb_rx_callback(struct urb *urb) +{ + struct imon_context *context; + int intfnum = 0; + + if (!urb) + return; + + context = (struct imon_context *)urb->context; + if (!context) + return; + + switch (urb->status) { + case -ENOENT: /* usbcore unlink successful! */ + return; + + case 0: + imon_incoming_packet(context, urb, intfnum); + break; + + default: + dev_warn(context->driver->dev, "imon %s: status(%d): ignored\n", + __func__, urb->status); + break; + } + + usb_submit_urb(context->rx_urb, GFP_ATOMIC); + + return; +} + +/** + * Callback function for USB core API: Probe + */ +static int imon_probe(struct usb_interface *interface, + const struct usb_device_id *id) +{ + struct usb_device *usbdev = NULL; + struct usb_host_interface *iface_desc = NULL; + struct usb_endpoint_descriptor *rx_endpoint = NULL; + struct usb_endpoint_descriptor *tx_endpoint = NULL; + struct urb *rx_urb = NULL; + struct urb *tx_urb = NULL; + struct lirc_driver *driver = NULL; + struct lirc_buffer *rbuf = NULL; + struct device *dev = &interface->dev; + int ifnum; + int lirc_minor = 0; + int num_endpts; + int retval = 0; + int display_ep_found = 0; + int ir_ep_found = 0; + int alloc_status = 0; + int vfd_proto_6p = 0; + struct imon_context *context = NULL; + int i; + u16 vendor, product; + + /* prevent races probing devices w/multiple interfaces */ + mutex_lock(&driver_lock); + + context = kzalloc(sizeof(struct imon_context), GFP_KERNEL); + if (!context) { + alloc_status = 1; + goto alloc_status_switch; + } + + /* + * Try to auto-detect the type of display if the user hasn't set + * it by hand via the display_type modparam. Default is VFD. + */ + if (usb_match_id(interface, ir_only_list)) + context->display = 0; + else + context->display = 1; + + usbdev = usb_get_dev(interface_to_usbdev(interface)); + iface_desc = interface->cur_altsetting; + num_endpts = iface_desc->desc.bNumEndpoints; + ifnum = iface_desc->desc.bInterfaceNumber; + vendor = le16_to_cpu(usbdev->descriptor.idVendor); + product = le16_to_cpu(usbdev->descriptor.idProduct); + + dev_dbg(dev, "%s: found iMON device (%04x:%04x, intf%d)\n", + __func__, vendor, product, ifnum); + + /* + * Scan the endpoint list and set: + * first input endpoint = IR endpoint + * first output endpoint = display endpoint + */ + for (i = 0; i < num_endpts && !(ir_ep_found && display_ep_found); ++i) { + struct usb_endpoint_descriptor *ep; + int ep_dir; + int ep_type; + ep = &iface_desc->endpoint[i].desc; + ep_dir = ep->bEndpointAddress & USB_ENDPOINT_DIR_MASK; + ep_type = ep->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK; + + if (!ir_ep_found && + ep_dir == USB_DIR_IN && + ep_type == USB_ENDPOINT_XFER_INT) { + + rx_endpoint = ep; + ir_ep_found = 1; + dev_dbg(dev, "%s: found IR endpoint\n", __func__); + + } else if (!display_ep_found && ep_dir == USB_DIR_OUT && + ep_type == USB_ENDPOINT_XFER_INT) { + tx_endpoint = ep; + display_ep_found = 1; + dev_dbg(dev, "%s: found display endpoint\n", __func__); + } + } + + /* + * Some iMON receivers have no display. Unfortunately, it seems + * that SoundGraph recycles device IDs between devices both with + * and without... :\ + */ + if (context->display == 0) { + display_ep_found = 0; + dev_dbg(dev, "%s: device has no display\n", __func__); + } + + /* Input endpoint is mandatory */ + if (!ir_ep_found) { + dev_err(dev, "%s: no valid input (IR) endpoint found.\n", + __func__); + retval = -ENODEV; + alloc_status = 2; + goto alloc_status_switch; + } + + /* Determine if display requires 6 packets */ + if (display_ep_found) { + if (usb_match_id(interface, vfd_proto_6p_list)) + vfd_proto_6p = 1; + + dev_dbg(dev, "%s: vfd_proto_6p: %d\n", + __func__, vfd_proto_6p); + } + + driver = kzalloc(sizeof(struct lirc_driver), GFP_KERNEL); + if (!driver) { + alloc_status = 2; + goto alloc_status_switch; + } + rbuf = kmalloc(sizeof(struct lirc_buffer), GFP_KERNEL); + if (!rbuf) { + alloc_status = 3; + goto alloc_status_switch; + } + if (lirc_buffer_init(rbuf, BUF_CHUNK_SIZE, BUF_SIZE)) { + dev_err(dev, "%s: lirc_buffer_init failed\n", __func__); + alloc_status = 4; + goto alloc_status_switch; + } + rx_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!rx_urb) { + dev_err(dev, "%s: usb_alloc_urb failed for IR urb\n", __func__); + alloc_status = 5; + goto alloc_status_switch; + } + tx_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!tx_urb) { + dev_err(dev, "%s: usb_alloc_urb failed for display urb\n", + __func__); + alloc_status = 6; + goto alloc_status_switch; + } + + mutex_init(&context->ctx_lock); + context->vfd_proto_6p = vfd_proto_6p; + + strcpy(driver->name, MOD_NAME); + driver->minor = -1; + driver->code_length = BUF_CHUNK_SIZE * 8; + driver->sample_rate = 0; + driver->features = LIRC_CAN_REC_MODE2; + driver->data = context; + driver->rbuf = rbuf; + driver->set_use_inc = ir_open; + driver->set_use_dec = ir_close; + driver->dev = &interface->dev; + driver->owner = THIS_MODULE; + + mutex_lock(&context->ctx_lock); + + context->driver = driver; + /* start out in keyboard mode */ + + lirc_minor = lirc_register_driver(driver); + if (lirc_minor < 0) { + dev_err(dev, "%s: lirc_register_driver failed\n", __func__); + alloc_status = 7; + goto unlock; + } else + dev_info(dev, "Registered iMON driver (lirc minor: %d)\n", + lirc_minor); + + /* Needed while unregistering! */ + driver->minor = lirc_minor; + + context->usbdev = usbdev; + context->dev_present = 1; + context->rx_endpoint = rx_endpoint; + context->rx_urb = rx_urb; + + /* + * tx is used to send characters to lcd/vfd, associate RF + * remotes, set IR protocol, and maybe more... + */ + context->tx_endpoint = tx_endpoint; + context->tx_urb = tx_urb; + + if (display_ep_found) + context->display = 1; + + usb_fill_int_urb(context->rx_urb, context->usbdev, + usb_rcvintpipe(context->usbdev, + context->rx_endpoint->bEndpointAddress), + context->usb_rx_buf, sizeof(context->usb_rx_buf), + usb_rx_callback, context, + context->rx_endpoint->bInterval); + + retval = usb_submit_urb(context->rx_urb, GFP_KERNEL); + + if (retval) { + dev_err(dev, "%s: usb_submit_urb failed for intf0 (%d)\n", + __func__, retval); + alloc_status = 8; + goto unlock; + } + + usb_set_intfdata(interface, context); + + if (context->display && ifnum == 0) { + dev_dbg(dev, "%s: Registering iMON display with sysfs\n", + __func__); + + if (usb_register_dev(interface, &imon_class)) { + /* Not a fatal error, so ignore */ + dev_info(dev, "%s: could not get a minor number for display\n", + __func__); + } + } + + dev_info(dev, "iMON device (%04x:%04x, intf%d) on " + "usb<%d:%d> initialized\n", vendor, product, ifnum, + usbdev->bus->busnum, usbdev->devnum); + +unlock: + mutex_unlock(&context->ctx_lock); +alloc_status_switch: + + switch (alloc_status) { + case 8: + lirc_unregister_driver(driver->minor); + case 7: + usb_free_urb(tx_urb); + case 6: + usb_free_urb(rx_urb); + /* fall-through */ + case 5: + if (rbuf) + lirc_buffer_free(rbuf); + /* fall-through */ + case 4: + kfree(rbuf); + /* fall-through */ + case 3: + kfree(driver); + /* fall-through */ + case 2: + kfree(context); + context = NULL; + case 1: + if (retval != -ENODEV) + retval = -ENOMEM; + break; + case 0: + retval = 0; + } + + mutex_unlock(&driver_lock); + + return retval; +} + +/** + * Callback function for USB core API: disconnect + */ +static void imon_disconnect(struct usb_interface *interface) +{ + struct imon_context *context; + int ifnum; + + /* prevent races with ir_open()/display_open() */ + mutex_lock(&driver_lock); + + context = usb_get_intfdata(interface); + ifnum = interface->cur_altsetting->desc.bInterfaceNumber; + + mutex_lock(&context->ctx_lock); + + usb_set_intfdata(interface, NULL); + + /* Abort ongoing write */ + if (atomic_read(&context->tx.busy)) { + usb_kill_urb(context->tx_urb); + complete_all(&context->tx.finished); + } + + context->dev_present = 0; + usb_kill_urb(context->rx_urb); + if (context->display) + usb_deregister_dev(interface, &imon_class); + + if (!context->ir_isopen && !context->dev_present) { + deregister_from_lirc(context); + mutex_unlock(&context->ctx_lock); + if (!context->display_isopen) + free_imon_context(context); + } else + mutex_unlock(&context->ctx_lock); + + mutex_unlock(&driver_lock); + + dev_info(&interface->dev, "%s: iMON device (intf%d) disconnected\n", + __func__, ifnum); +} + +static int imon_suspend(struct usb_interface *intf, pm_message_t message) +{ + struct imon_context *context = usb_get_intfdata(intf); + + usb_kill_urb(context->rx_urb); + + return 0; +} + +static int imon_resume(struct usb_interface *intf) +{ + int rc = 0; + struct imon_context *context = usb_get_intfdata(intf); + + usb_fill_int_urb(context->rx_urb, context->usbdev, + usb_rcvintpipe(context->usbdev, + context->rx_endpoint->bEndpointAddress), + context->usb_rx_buf, sizeof(context->usb_rx_buf), + usb_rx_callback, context, + context->rx_endpoint->bInterval); + + rc = usb_submit_urb(context->rx_urb, GFP_ATOMIC); + + return rc; +} + +module_usb_driver(imon_driver); diff --git a/drivers/staging/media/lirc/lirc_parallel.c b/drivers/staging/media/lirc/lirc_parallel.c new file mode 100644 index 00000000000..1394f020e46 --- /dev/null +++ b/drivers/staging/media/lirc/lirc_parallel.c @@ -0,0 +1,746 @@ +/* + * lirc_parallel.c + * + * lirc_parallel - device driver for infra-red signal receiving and + * transmitting unit built by the author + * + * Copyright (C) 1998 Christoph Bartelmus <lirc@bartelmus.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +/*** Includes ***/ + +#include <linux/module.h> +#include <linux/sched.h> +#include <linux/errno.h> +#include <linux/signal.h> +#include <linux/fs.h> +#include <linux/kernel.h> +#include <linux/ioport.h> +#include <linux/time.h> +#include <linux/mm.h> +#include <linux/delay.h> + +#include <linux/io.h> +#include <linux/irq.h> +#include <linux/uaccess.h> +#include <asm/div64.h> + +#include <linux/poll.h> +#include <linux/parport.h> +#include <linux/platform_device.h> + +#include <media/lirc.h> +#include <media/lirc_dev.h> + +#include "lirc_parallel.h" + +#define LIRC_DRIVER_NAME "lirc_parallel" + +#ifndef LIRC_IRQ +#define LIRC_IRQ 7 +#endif +#ifndef LIRC_PORT +#define LIRC_PORT 0x378 +#endif +#ifndef LIRC_TIMER +#define LIRC_TIMER 65536 +#endif + +/*** Global Variables ***/ + +static bool debug; +static bool check_pselecd; + +static unsigned int irq = LIRC_IRQ; +static unsigned int io = LIRC_PORT; +#ifdef LIRC_TIMER +static unsigned int timer; +static unsigned int default_timer = LIRC_TIMER; +#endif + +#define RBUF_SIZE (256) /* this must be a power of 2 larger than 1 */ + +static int rbuf[RBUF_SIZE]; + +static DECLARE_WAIT_QUEUE_HEAD(lirc_wait); + +static unsigned int rptr; +static unsigned int wptr; +static unsigned int lost_irqs; +static int is_open; + +static struct parport *pport; +static struct pardevice *ppdevice; +static int is_claimed; + +static unsigned int tx_mask = 1; + +/*** Internal Functions ***/ + +static unsigned int in(int offset) +{ + switch (offset) { + case LIRC_LP_BASE: + return parport_read_data(pport); + case LIRC_LP_STATUS: + return parport_read_status(pport); + case LIRC_LP_CONTROL: + return parport_read_control(pport); + } + return 0; /* make compiler happy */ +} + +static void out(int offset, int value) +{ + switch (offset) { + case LIRC_LP_BASE: + parport_write_data(pport, value); + break; + case LIRC_LP_CONTROL: + parport_write_control(pport, value); + break; + case LIRC_LP_STATUS: + pr_info("attempt to write to status register\n"); + break; + } +} + +static unsigned int lirc_get_timer(void) +{ + return in(LIRC_PORT_TIMER) & LIRC_PORT_TIMER_BIT; +} + +static unsigned int lirc_get_signal(void) +{ + return in(LIRC_PORT_SIGNAL) & LIRC_PORT_SIGNAL_BIT; +} + +static void lirc_on(void) +{ + out(LIRC_PORT_DATA, tx_mask); +} + +static void lirc_off(void) +{ + out(LIRC_PORT_DATA, 0); +} + +static unsigned int init_lirc_timer(void) +{ + struct timeval tv, now; + unsigned int level, newlevel, timeelapsed, newtimer; + int count = 0; + + do_gettimeofday(&tv); + tv.tv_sec++; /* wait max. 1 sec. */ + level = lirc_get_timer(); + do { + newlevel = lirc_get_timer(); + if (level == 0 && newlevel != 0) + count++; + level = newlevel; + do_gettimeofday(&now); + } while (count < 1000 && (now.tv_sec < tv.tv_sec + || (now.tv_sec == tv.tv_sec + && now.tv_usec < tv.tv_usec))); + + timeelapsed = ((now.tv_sec + 1 - tv.tv_sec)*1000000 + + (now.tv_usec - tv.tv_usec)); + if (count >= 1000 && timeelapsed > 0) { + if (default_timer == 0) { + /* autodetect timer */ + newtimer = (1000000*count)/timeelapsed; + pr_info("%u Hz timer detected\n", newtimer); + return newtimer; + } else { + newtimer = (1000000*count)/timeelapsed; + if (abs(newtimer - default_timer) > default_timer/10) { + /* bad timer */ + pr_notice("bad timer: %u Hz\n", newtimer); + pr_notice("using default timer: %u Hz\n", + default_timer); + return default_timer; + } else { + pr_info("%u Hz timer detected\n", newtimer); + return newtimer; /* use detected value */ + } + } + } else { + pr_notice("no timer detected\n"); + return 0; + } +} + +static int lirc_claim(void) +{ + if (parport_claim(ppdevice) != 0) { + pr_warn("could not claim port\n"); + pr_warn("waiting for port becoming available\n"); + if (parport_claim_or_block(ppdevice) < 0) { + pr_notice("could not claim port, giving up\n"); + return 0; + } + } + out(LIRC_LP_CONTROL, LP_PSELECP|LP_PINITP); + is_claimed = 1; + return 1; +} + +/*** interrupt handler ***/ + +static void rbuf_write(int signal) +{ + unsigned int nwptr; + + nwptr = (wptr + 1) & (RBUF_SIZE - 1); + if (nwptr == rptr) { + /* no new signals will be accepted */ + lost_irqs++; + pr_notice("buffer overrun\n"); + return; + } + rbuf[wptr] = signal; + wptr = nwptr; +} + +static void lirc_lirc_irq_handler(void *blah) +{ + struct timeval tv; + static struct timeval lasttv; + static int init; + long signal; + int data; + unsigned int level, newlevel; + unsigned int timeout; + + if (!is_open) + return; + + if (!is_claimed) + return; + +#if 0 + /* disable interrupt */ + disable_irq(irq); + out(LIRC_PORT_IRQ, in(LIRC_PORT_IRQ) & (~LP_PINTEN)); +#endif + if (check_pselecd && (in(1) & LP_PSELECD)) + return; + +#ifdef LIRC_TIMER + if (init) { + do_gettimeofday(&tv); + + signal = tv.tv_sec - lasttv.tv_sec; + if (signal > 15) + /* really long time */ + data = PULSE_MASK; + else + data = (int) (signal*1000000 + + tv.tv_usec - lasttv.tv_usec + + LIRC_SFH506_DELAY); + + rbuf_write(data); /* space */ + } else { + if (timer == 0) { + /* + * wake up; we'll lose this signal, but it will be + * garbage if the device is turned on anyway + */ + timer = init_lirc_timer(); + /* enable_irq(irq); */ + return; + } + init = 1; + } + + timeout = timer/10; /* timeout after 1/10 sec. */ + signal = 1; + level = lirc_get_timer(); + do { + newlevel = lirc_get_timer(); + if (level == 0 && newlevel != 0) + signal++; + level = newlevel; + + /* giving up */ + if (signal > timeout + || (check_pselecd && (in(1) & LP_PSELECD))) { + signal = 0; + pr_notice("timeout\n"); + break; + } + } while (lirc_get_signal()); + + if (signal != 0) { + /* adjust value to usecs */ + __u64 helper; + + helper = ((__u64) signal)*1000000; + do_div(helper, timer); + signal = (long) helper; + + if (signal > LIRC_SFH506_DELAY) + data = signal - LIRC_SFH506_DELAY; + else + data = 1; + rbuf_write(PULSE_BIT|data); /* pulse */ + } + do_gettimeofday(&lasttv); +#else + /* add your code here */ +#endif + + wake_up_interruptible(&lirc_wait); + + /* enable interrupt */ + /* + enable_irq(irq); + out(LIRC_PORT_IRQ, in(LIRC_PORT_IRQ)|LP_PINTEN); + */ +} + +/*** file operations ***/ + +static loff_t lirc_lseek(struct file *filep, loff_t offset, int orig) +{ + return -ESPIPE; +} + +static ssize_t lirc_read(struct file *filep, char __user *buf, size_t n, + loff_t *ppos) +{ + int result = 0; + int count = 0; + DECLARE_WAITQUEUE(wait, current); + + if (n % sizeof(int)) + return -EINVAL; + + add_wait_queue(&lirc_wait, &wait); + set_current_state(TASK_INTERRUPTIBLE); + while (count < n) { + if (rptr != wptr) { + if (copy_to_user(buf+count, (char *) &rbuf[rptr], + sizeof(int))) { + result = -EFAULT; + break; + } + rptr = (rptr + 1) & (RBUF_SIZE - 1); + count += sizeof(int); + } else { + if (filep->f_flags & O_NONBLOCK) { + result = -EAGAIN; + break; + } + if (signal_pending(current)) { + result = -ERESTARTSYS; + break; + } + schedule(); + set_current_state(TASK_INTERRUPTIBLE); + } + } + remove_wait_queue(&lirc_wait, &wait); + set_current_state(TASK_RUNNING); + return count ? count : result; +} + +static ssize_t lirc_write(struct file *filep, const char __user *buf, size_t n, + loff_t *ppos) +{ + int count; + unsigned int i; + unsigned int level, newlevel; + unsigned long flags; + int counttimer; + int *wbuf; + ssize_t ret; + + if (!is_claimed) + return -EBUSY; + + count = n / sizeof(int); + + if (n % sizeof(int) || count % 2 == 0) + return -EINVAL; + + wbuf = memdup_user(buf, n); + if (IS_ERR(wbuf)) + return PTR_ERR(wbuf); + +#ifdef LIRC_TIMER + if (timer == 0) { + /* try again if device is ready */ + timer = init_lirc_timer(); + if (timer == 0) { + ret = -EIO; + goto out; + } + } + + /* adjust values from usecs */ + for (i = 0; i < count; i++) { + __u64 helper; + + helper = ((__u64) wbuf[i])*timer; + do_div(helper, 1000000); + wbuf[i] = (int) helper; + } + + local_irq_save(flags); + i = 0; + while (i < count) { + level = lirc_get_timer(); + counttimer = 0; + lirc_on(); + do { + newlevel = lirc_get_timer(); + if (level == 0 && newlevel != 0) + counttimer++; + level = newlevel; + if (check_pselecd && (in(1) & LP_PSELECD)) { + lirc_off(); + local_irq_restore(flags); + ret = -EIO; + goto out; + } + } while (counttimer < wbuf[i]); + i++; + + lirc_off(); + if (i == count) + break; + counttimer = 0; + do { + newlevel = lirc_get_timer(); + if (level == 0 && newlevel != 0) + counttimer++; + level = newlevel; + if (check_pselecd && (in(1) & LP_PSELECD)) { + local_irq_restore(flags); + ret = -EIO; + goto out; + } + } while (counttimer < wbuf[i]); + i++; + } + local_irq_restore(flags); +#else + /* place code that handles write without external timer here */ +#endif + ret = n; +out: + kfree(wbuf); + + return ret; +} + +static unsigned int lirc_poll(struct file *file, poll_table *wait) +{ + poll_wait(file, &lirc_wait, wait); + if (rptr != wptr) + return POLLIN | POLLRDNORM; + return 0; +} + +static long lirc_ioctl(struct file *filep, unsigned int cmd, unsigned long arg) +{ + int result; + u32 __user *uptr = (u32 __user *)arg; + u32 features = LIRC_CAN_SET_TRANSMITTER_MASK | + LIRC_CAN_SEND_PULSE | LIRC_CAN_REC_MODE2; + u32 mode; + u32 value; + + switch (cmd) { + case LIRC_GET_FEATURES: + result = put_user(features, uptr); + if (result) + return result; + break; + case LIRC_GET_SEND_MODE: + result = put_user(LIRC_MODE_PULSE, uptr); + if (result) + return result; + break; + case LIRC_GET_REC_MODE: + result = put_user(LIRC_MODE_MODE2, uptr); + if (result) + return result; + break; + case LIRC_SET_SEND_MODE: + result = get_user(mode, uptr); + if (result) + return result; + if (mode != LIRC_MODE_PULSE) + return -EINVAL; + break; + case LIRC_SET_REC_MODE: + result = get_user(mode, uptr); + if (result) + return result; + if (mode != LIRC_MODE_MODE2) + return -ENOSYS; + break; + case LIRC_SET_TRANSMITTER_MASK: + result = get_user(value, uptr); + if (result) + return result; + if ((value & LIRC_PARALLEL_TRANSMITTER_MASK) != value) + return LIRC_PARALLEL_MAX_TRANSMITTERS; + tx_mask = value; + break; + default: + return -ENOIOCTLCMD; + } + return 0; +} + +static int lirc_open(struct inode *node, struct file *filep) +{ + if (is_open || !lirc_claim()) + return -EBUSY; + + parport_enable_irq(pport); + + /* init read ptr */ + rptr = 0; + wptr = 0; + lost_irqs = 0; + + is_open = 1; + return 0; +} + +static int lirc_close(struct inode *node, struct file *filep) +{ + if (is_claimed) { + is_claimed = 0; + parport_release(ppdevice); + } + is_open = 0; + return 0; +} + +static const struct file_operations lirc_fops = { + .owner = THIS_MODULE, + .llseek = lirc_lseek, + .read = lirc_read, + .write = lirc_write, + .poll = lirc_poll, + .unlocked_ioctl = lirc_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = lirc_ioctl, +#endif + .open = lirc_open, + .release = lirc_close +}; + +static int set_use_inc(void *data) +{ + return 0; +} + +static void set_use_dec(void *data) +{ +} + +static struct lirc_driver driver = { + .name = LIRC_DRIVER_NAME, + .minor = -1, + .code_length = 1, + .sample_rate = 0, + .data = NULL, + .add_to_buf = NULL, + .set_use_inc = set_use_inc, + .set_use_dec = set_use_dec, + .fops = &lirc_fops, + .dev = NULL, + .owner = THIS_MODULE, +}; + +static struct platform_device *lirc_parallel_dev; + +static int lirc_parallel_probe(struct platform_device *dev) +{ + return 0; +} + +static int lirc_parallel_remove(struct platform_device *dev) +{ + return 0; +} + +static int lirc_parallel_suspend(struct platform_device *dev, + pm_message_t state) +{ + return 0; +} + +static int lirc_parallel_resume(struct platform_device *dev) +{ + return 0; +} + +static struct platform_driver lirc_parallel_driver = { + .probe = lirc_parallel_probe, + .remove = lirc_parallel_remove, + .suspend = lirc_parallel_suspend, + .resume = lirc_parallel_resume, + .driver = { + .name = LIRC_DRIVER_NAME, + .owner = THIS_MODULE, + }, +}; + +static int pf(void *handle) +{ + parport_disable_irq(pport); + is_claimed = 0; + return 0; +} + +static void kf(void *handle) +{ + if (!is_open) + return; + if (!lirc_claim()) + return; + parport_enable_irq(pport); + lirc_off(); + /* this is a bit annoying when you actually print...*/ + /* + printk(KERN_INFO "%s: reclaimed port\n", LIRC_DRIVER_NAME); + */ +} + +/*** module initialization and cleanup ***/ + +static int __init lirc_parallel_init(void) +{ + int result; + + result = platform_driver_register(&lirc_parallel_driver); + if (result) { + pr_notice("platform_driver_register returned %d\n", result); + return result; + } + + lirc_parallel_dev = platform_device_alloc(LIRC_DRIVER_NAME, 0); + if (!lirc_parallel_dev) { + result = -ENOMEM; + goto exit_driver_unregister; + } + + result = platform_device_add(lirc_parallel_dev); + if (result) + goto exit_device_put; + + pport = parport_find_base(io); + if (pport == NULL) { + pr_notice("no port at %x found\n", io); + result = -ENXIO; + goto exit_device_put; + } + ppdevice = parport_register_device(pport, LIRC_DRIVER_NAME, + pf, kf, lirc_lirc_irq_handler, 0, NULL); + parport_put_port(pport); + if (ppdevice == NULL) { + pr_notice("parport_register_device() failed\n"); + result = -ENXIO; + goto exit_device_put; + } + if (parport_claim(ppdevice) != 0) + goto skip_init; + is_claimed = 1; + out(LIRC_LP_CONTROL, LP_PSELECP|LP_PINITP); + +#ifdef LIRC_TIMER + if (debug) + out(LIRC_PORT_DATA, tx_mask); + + timer = init_lirc_timer(); + +#if 0 /* continue even if device is offline */ + if (timer == 0) { + is_claimed = 0; + parport_release(pport); + parport_unregister_device(ppdevice); + result = -EIO; + goto exit_device_put; + } + +#endif + if (debug) + out(LIRC_PORT_DATA, 0); +#endif + + is_claimed = 0; + parport_release(ppdevice); + skip_init: + driver.dev = &lirc_parallel_dev->dev; + driver.minor = lirc_register_driver(&driver); + if (driver.minor < 0) { + pr_notice("register_chrdev() failed\n"); + parport_unregister_device(ppdevice); + result = -EIO; + goto exit_device_put; + } + pr_info("installed using port 0x%04x irq %d\n", io, irq); + return 0; + +exit_device_put: + platform_device_put(lirc_parallel_dev); +exit_driver_unregister: + platform_driver_unregister(&lirc_parallel_driver); + return result; +} + +static void __exit lirc_parallel_exit(void) +{ + parport_unregister_device(ppdevice); + lirc_unregister_driver(driver.minor); + + platform_device_unregister(lirc_parallel_dev); + platform_driver_unregister(&lirc_parallel_driver); +} + +module_init(lirc_parallel_init); +module_exit(lirc_parallel_exit); + +MODULE_DESCRIPTION("Infrared receiver driver for parallel ports."); +MODULE_AUTHOR("Christoph Bartelmus"); +MODULE_LICENSE("GPL"); + +module_param(io, int, S_IRUGO); +MODULE_PARM_DESC(io, "I/O address base (0x3bc, 0x378 or 0x278)"); + +module_param(irq, int, S_IRUGO); +MODULE_PARM_DESC(irq, "Interrupt (7 or 5)"); + +module_param(tx_mask, int, S_IRUGO); +MODULE_PARM_DESC(tx_maxk, "Transmitter mask (default: 0x01)"); + +module_param(debug, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(debug, "Enable debugging messages"); + +module_param(check_pselecd, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(check_pselecd, "Check for printer (default: 0)"); diff --git a/drivers/staging/media/lirc/lirc_parallel.h b/drivers/staging/media/lirc/lirc_parallel.h new file mode 100644 index 00000000000..4bed6afe063 --- /dev/null +++ b/drivers/staging/media/lirc/lirc_parallel.h @@ -0,0 +1,26 @@ +/* lirc_parallel.h */ + +#ifndef _LIRC_PARALLEL_H +#define _LIRC_PARALLEL_H + +#include <linux/lp.h> + +#define LIRC_PORT_LEN 3 + +#define LIRC_LP_BASE 0 +#define LIRC_LP_STATUS 1 +#define LIRC_LP_CONTROL 2 + +#define LIRC_PORT_DATA LIRC_LP_BASE /* base */ +#define LIRC_PORT_TIMER LIRC_LP_STATUS /* status port */ +#define LIRC_PORT_TIMER_BIT LP_PBUSY /* busy signal */ +#define LIRC_PORT_SIGNAL LIRC_LP_STATUS /* status port */ +#define LIRC_PORT_SIGNAL_BIT LP_PACK /* ack signal */ +#define LIRC_PORT_IRQ LIRC_LP_CONTROL /* control port */ + +#define LIRC_SFH506_DELAY 0 /* delay t_phl in usecs */ + +#define LIRC_PARALLEL_MAX_TRANSMITTERS 8 +#define LIRC_PARALLEL_TRANSMITTER_MASK ((1<<LIRC_PARALLEL_MAX_TRANSMITTERS) - 1) + +#endif diff --git a/drivers/staging/media/lirc/lirc_sasem.c b/drivers/staging/media/lirc/lirc_sasem.c new file mode 100644 index 00000000000..81f90e17e1e --- /dev/null +++ b/drivers/staging/media/lirc/lirc_sasem.c @@ -0,0 +1,934 @@ +/* + * lirc_sasem.c - USB remote support for LIRC + * Version 0.5 + * + * Copyright (C) 2004-2005 Oliver Stabel <oliver.stabel@gmx.de> + * Tim Davies <tim@opensystems.net.au> + * + * This driver was derived from: + * Venky Raju <dev@venky.ws> + * "lirc_imon - "LIRC/VFD driver for Ahanix/Soundgraph IMON IR/VFD" + * Paul Miller <pmiller9@users.sourceforge.net>'s 2003-2004 + * "lirc_atiusb - USB remote support for LIRC" + * Culver Consulting Services <henry@culcon.com>'s 2003 + * "Sasem OnAir VFD/IR USB driver" + * + * + * NOTE - The LCDproc iMon driver should work with this module. More info at + * http://www.frogstorm.info/sasem + */ + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/uaccess.h> +#include <linux/usb.h> + +#include <media/lirc.h> +#include <media/lirc_dev.h> + + +#define MOD_AUTHOR "Oliver Stabel <oliver.stabel@gmx.de>, " \ + "Tim Davies <tim@opensystems.net.au>" +#define MOD_DESC "USB Driver for Sasem Remote Controller V1.1" +#define MOD_NAME "lirc_sasem" +#define MOD_VERSION "0.5" + +#define VFD_MINOR_BASE 144 /* Same as LCD */ +#define DEVICE_NAME "lcd%d" + +#define BUF_CHUNK_SIZE 8 +#define BUF_SIZE 128 + +#define IOCTL_LCD_CONTRAST 1 + +/*** P R O T O T Y P E S ***/ + +/* USB Callback prototypes */ +static int sasem_probe(struct usb_interface *interface, + const struct usb_device_id *id); +static void sasem_disconnect(struct usb_interface *interface); +static void usb_rx_callback(struct urb *urb); +static void usb_tx_callback(struct urb *urb); + +/* VFD file_operations function prototypes */ +static int vfd_open(struct inode *inode, struct file *file); +static long vfd_ioctl(struct file *file, unsigned cmd, unsigned long arg); +static int vfd_close(struct inode *inode, struct file *file); +static ssize_t vfd_write(struct file *file, const char __user *buf, + size_t n_bytes, loff_t *pos); + +/* LIRC driver function prototypes */ +static int ir_open(void *data); +static void ir_close(void *data); + +/*** G L O B A L S ***/ +#define SASEM_DATA_BUF_SZ 32 + +struct sasem_context { + + struct usb_device *dev; + int vfd_isopen; /* VFD port has been opened */ + unsigned int vfd_contrast; /* VFD contrast */ + int ir_isopen; /* IR port has been opened */ + int dev_present; /* USB device presence */ + struct mutex ctx_lock; /* to lock this object */ + wait_queue_head_t remove_ok; /* For unexpected USB disconnects */ + + struct lirc_driver *driver; + struct usb_endpoint_descriptor *rx_endpoint; + struct usb_endpoint_descriptor *tx_endpoint; + struct urb *rx_urb; + struct urb *tx_urb; + unsigned char usb_rx_buf[8]; + unsigned char usb_tx_buf[8]; + + struct tx_t { + unsigned char data_buf[SASEM_DATA_BUF_SZ]; /* user data + * buffer */ + struct completion finished; /* wait for write to finish */ + atomic_t busy; /* write in progress */ + int status; /* status of tx completion */ + } tx; + + /* for dealing with repeat codes (wish there was a toggle bit!) */ + struct timeval presstime; + char lastcode[8]; + int codesaved; +}; + +/* VFD file operations */ +static const struct file_operations vfd_fops = { + .owner = THIS_MODULE, + .open = &vfd_open, + .write = vfd_write, + .unlocked_ioctl = &vfd_ioctl, + .release = &vfd_close, + .llseek = noop_llseek, +}; + +/* USB Device ID for Sasem USB Control Board */ +static struct usb_device_id sasem_usb_id_table[] = { + /* Sasem USB Control Board */ + { USB_DEVICE(0x11ba, 0x0101) }, + /* Terminating entry */ + {} +}; + +/* USB Device data */ +static struct usb_driver sasem_driver = { + .name = MOD_NAME, + .probe = sasem_probe, + .disconnect = sasem_disconnect, + .id_table = sasem_usb_id_table, +}; + +static struct usb_class_driver sasem_class = { + .name = DEVICE_NAME, + .fops = &vfd_fops, + .minor_base = VFD_MINOR_BASE, +}; + +/* to prevent races between open() and disconnect() */ +static DEFINE_MUTEX(disconnect_lock); + +static int debug; + + +/*** M O D U L E C O D E ***/ + +MODULE_AUTHOR(MOD_AUTHOR); +MODULE_DESCRIPTION(MOD_DESC); +MODULE_LICENSE("GPL"); +module_param(debug, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(debug, "Debug messages: 0=no, 1=yes (default: no)"); + +static void delete_context(struct sasem_context *context) +{ + usb_free_urb(context->tx_urb); /* VFD */ + usb_free_urb(context->rx_urb); /* IR */ + lirc_buffer_free(context->driver->rbuf); + kfree(context->driver->rbuf); + kfree(context->driver); + kfree(context); + + if (debug) + pr_info("%s: context deleted\n", __func__); +} + +static void deregister_from_lirc(struct sasem_context *context) +{ + int retval; + int minor = context->driver->minor; + + retval = lirc_unregister_driver(minor); + if (retval) + pr_err("%s: unable to deregister from lirc (%d)\n", + __func__, retval); + else + pr_info("Deregistered Sasem driver (minor:%d)\n", minor); + +} + +/** + * Called when the VFD device (e.g. /dev/usb/lcd) + * is opened by the application. + */ +static int vfd_open(struct inode *inode, struct file *file) +{ + struct usb_interface *interface; + struct sasem_context *context = NULL; + int subminor; + int retval = 0; + + /* prevent races with disconnect */ + mutex_lock(&disconnect_lock); + + subminor = iminor(inode); + interface = usb_find_interface(&sasem_driver, subminor); + if (!interface) { + pr_err("%s: could not find interface for minor %d\n", + __func__, subminor); + retval = -ENODEV; + goto exit; + } + context = usb_get_intfdata(interface); + + if (!context) { + dev_err(&interface->dev, + "%s: no context found for minor %d\n", + __func__, subminor); + retval = -ENODEV; + goto exit; + } + + mutex_lock(&context->ctx_lock); + + if (context->vfd_isopen) { + dev_err(&interface->dev, + "%s: VFD port is already open", __func__); + retval = -EBUSY; + } else { + context->vfd_isopen = 1; + file->private_data = context; + dev_info(&interface->dev, "VFD port opened\n"); + } + + mutex_unlock(&context->ctx_lock); + +exit: + mutex_unlock(&disconnect_lock); + return retval; +} + +/** + * Called when the VFD device (e.g. /dev/usb/lcd) + * is closed by the application. + */ +static long vfd_ioctl(struct file *file, unsigned cmd, unsigned long arg) +{ + struct sasem_context *context = NULL; + + context = (struct sasem_context *) file->private_data; + + if (!context) { + pr_err("%s: no context for device\n", __func__); + return -ENODEV; + } + + mutex_lock(&context->ctx_lock); + + switch (cmd) { + case IOCTL_LCD_CONTRAST: + if (arg > 1000) + arg = 1000; + context->vfd_contrast = (unsigned int)arg; + break; + default: + pr_info("Unknown IOCTL command\n"); + mutex_unlock(&context->ctx_lock); + return -ENOIOCTLCMD; /* not supported */ + } + + mutex_unlock(&context->ctx_lock); + return 0; +} + +/** + * Called when the VFD device (e.g. /dev/usb/lcd) + * is closed by the application. + */ +static int vfd_close(struct inode *inode, struct file *file) +{ + struct sasem_context *context = NULL; + int retval = 0; + + context = (struct sasem_context *) file->private_data; + + if (!context) { + pr_err("%s: no context for device\n", __func__); + return -ENODEV; + } + + mutex_lock(&context->ctx_lock); + + if (!context->vfd_isopen) { + dev_err(&context->dev->dev, "%s: VFD is not open\n", __func__); + retval = -EIO; + } else { + context->vfd_isopen = 0; + dev_info(&context->dev->dev, "VFD port closed\n"); + if (!context->dev_present && !context->ir_isopen) { + + /* Device disconnected before close and IR port is + * not open. If IR port is open, context will be + * deleted by ir_close. */ + mutex_unlock(&context->ctx_lock); + delete_context(context); + return retval; + } + } + + mutex_unlock(&context->ctx_lock); + return retval; +} + +/** + * Sends a packet to the VFD. + */ +static int send_packet(struct sasem_context *context) +{ + unsigned int pipe; + int interval = 0; + int retval = 0; + + pipe = usb_sndintpipe(context->dev, + context->tx_endpoint->bEndpointAddress); + interval = context->tx_endpoint->bInterval; + + usb_fill_int_urb(context->tx_urb, context->dev, pipe, + context->usb_tx_buf, sizeof(context->usb_tx_buf), + usb_tx_callback, context, interval); + + context->tx_urb->actual_length = 0; + + init_completion(&context->tx.finished); + atomic_set(&(context->tx.busy), 1); + + retval = usb_submit_urb(context->tx_urb, GFP_KERNEL); + if (retval) { + atomic_set(&(context->tx.busy), 0); + dev_err(&context->dev->dev, "%s: error submitting urb (%d)\n", + __func__, retval); + } else { + /* Wait for transmission to complete (or abort) */ + mutex_unlock(&context->ctx_lock); + wait_for_completion(&context->tx.finished); + mutex_lock(&context->ctx_lock); + + retval = context->tx.status; + if (retval) + dev_err(&context->dev->dev, + "%s: packet tx failed (%d)\n", + __func__, retval); + } + + return retval; +} + +/** + * Writes data to the VFD. The Sasem VFD is 2x16 characters + * and requires data in 9 consecutive USB interrupt packets, + * each packet carrying 8 bytes. + */ +static ssize_t vfd_write(struct file *file, const char __user *buf, + size_t n_bytes, loff_t *pos) +{ + int i; + int retval = 0; + struct sasem_context *context; + int *data_buf = NULL; + + context = (struct sasem_context *) file->private_data; + if (!context) { + pr_err("%s: no context for device\n", __func__); + return -ENODEV; + } + + mutex_lock(&context->ctx_lock); + + if (!context->dev_present) { + pr_err("%s: no Sasem device present\n", __func__); + retval = -ENODEV; + goto exit; + } + + if (n_bytes <= 0 || n_bytes > SASEM_DATA_BUF_SZ) { + dev_err(&context->dev->dev, "%s: invalid payload size\n", + __func__); + retval = -EINVAL; + goto exit; + } + + data_buf = memdup_user((void const __user *)buf, n_bytes); + if (IS_ERR(data_buf)) { + retval = PTR_ERR(data_buf); + goto exit; + } + + memcpy(context->tx.data_buf, data_buf, n_bytes); + + /* Pad with spaces */ + for (i = n_bytes; i < SASEM_DATA_BUF_SZ; ++i) + context->tx.data_buf[i] = ' '; + + /* Nine 8 byte packets to be sent */ + /* NOTE: "\x07\x01\0\0\0\0\0\0" or "\x0c\0\0\0\0\0\0\0" + * will clear the VFD */ + for (i = 0; i < 9; i++) { + switch (i) { + case 0: + memcpy(context->usb_tx_buf, "\x07\0\0\0\0\0\0\0", 8); + context->usb_tx_buf[1] = (context->vfd_contrast) ? + (0x2B - (context->vfd_contrast - 1) / 250) + : 0x2B; + break; + case 1: + memcpy(context->usb_tx_buf, "\x09\x01\0\0\0\0\0\0", 8); + break; + case 2: + memcpy(context->usb_tx_buf, "\x0b\x01\0\0\0\0\0\0", 8); + break; + case 3: + memcpy(context->usb_tx_buf, context->tx.data_buf, 8); + break; + case 4: + memcpy(context->usb_tx_buf, + context->tx.data_buf + 8, 8); + break; + case 5: + memcpy(context->usb_tx_buf, "\x09\x01\0\0\0\0\0\0", 8); + break; + case 6: + memcpy(context->usb_tx_buf, "\x0b\x02\0\0\0\0\0\0", 8); + break; + case 7: + memcpy(context->usb_tx_buf, + context->tx.data_buf + 16, 8); + break; + case 8: + memcpy(context->usb_tx_buf, + context->tx.data_buf + 24, 8); + break; + } + retval = send_packet(context); + if (retval) { + dev_err(&context->dev->dev, + "%s: send packet failed for packet #%d\n", + __func__, i); + goto exit; + } + } +exit: + + mutex_unlock(&context->ctx_lock); + kfree(data_buf); + + return (!retval) ? n_bytes : retval; +} + +/** + * Callback function for USB core API: transmit data + */ +static void usb_tx_callback(struct urb *urb) +{ + struct sasem_context *context; + + if (!urb) + return; + context = (struct sasem_context *) urb->context; + if (!context) + return; + + context->tx.status = urb->status; + + /* notify waiters that write has finished */ + atomic_set(&context->tx.busy, 0); + complete(&context->tx.finished); + + return; +} + +/** + * Called by lirc_dev when the application opens /dev/lirc + */ +static int ir_open(void *data) +{ + int retval = 0; + struct sasem_context *context; + + /* prevent races with disconnect */ + mutex_lock(&disconnect_lock); + + context = (struct sasem_context *) data; + + mutex_lock(&context->ctx_lock); + + if (context->ir_isopen) { + dev_err(&context->dev->dev, "%s: IR port is already open\n", + __func__); + retval = -EBUSY; + goto exit; + } + + usb_fill_int_urb(context->rx_urb, context->dev, + usb_rcvintpipe(context->dev, + context->rx_endpoint->bEndpointAddress), + context->usb_rx_buf, sizeof(context->usb_rx_buf), + usb_rx_callback, context, context->rx_endpoint->bInterval); + + retval = usb_submit_urb(context->rx_urb, GFP_KERNEL); + + if (retval) + dev_err(&context->dev->dev, + "%s: usb_submit_urb failed for ir_open (%d)\n", + __func__, retval); + else { + context->ir_isopen = 1; + dev_info(&context->dev->dev, "IR port opened\n"); + } + +exit: + mutex_unlock(&context->ctx_lock); + + mutex_unlock(&disconnect_lock); + return retval; +} + +/** + * Called by lirc_dev when the application closes /dev/lirc + */ +static void ir_close(void *data) +{ + struct sasem_context *context; + + context = (struct sasem_context *)data; + if (!context) { + pr_err("%s: no context for device\n", __func__); + return; + } + + mutex_lock(&context->ctx_lock); + + usb_kill_urb(context->rx_urb); + context->ir_isopen = 0; + pr_info("IR port closed\n"); + + if (!context->dev_present) { + + /* + * Device disconnected while IR port was + * still open. Driver was not deregistered + * at disconnect time, so do it now. + */ + deregister_from_lirc(context); + + if (!context->vfd_isopen) { + + mutex_unlock(&context->ctx_lock); + delete_context(context); + return; + } + /* If VFD port is open, context will be deleted by vfd_close */ + } + + mutex_unlock(&context->ctx_lock); + return; +} + +/** + * Process the incoming packet + */ +static void incoming_packet(struct sasem_context *context, + struct urb *urb) +{ + int len = urb->actual_length; + unsigned char *buf = urb->transfer_buffer; + long ms; + struct timeval tv; + int i; + + if (len != 8) { + dev_warn(&context->dev->dev, + "%s: invalid incoming packet size (%d)\n", + __func__, len); + return; + } + + if (debug) { + printk(KERN_INFO "Incoming data: "); + for (i = 0; i < 8; ++i) + printk(KERN_CONT "%02x ", buf[i]); + printk(KERN_CONT "\n"); + } + + /* + * Lirc could deal with the repeat code, but we really need to block it + * if it arrives too late. Otherwise we could repeat the wrong code. + */ + + /* get the time since the last button press */ + do_gettimeofday(&tv); + ms = (tv.tv_sec - context->presstime.tv_sec) * 1000 + + (tv.tv_usec - context->presstime.tv_usec) / 1000; + + if (memcmp(buf, "\x08\0\0\0\0\0\0\0", 8) == 0) { + /* + * the repeat code is being sent, so we copy + * the old code to LIRC + */ + + /* + * NOTE: Only if the last code was less than 250ms ago + * - no one should be able to push another (undetected) button + * in that time and then get a false repeat of the previous + * press but it is long enough for a genuine repeat + */ + if ((ms < 250) && (context->codesaved != 0)) { + memcpy(buf, &context->lastcode, 8); + context->presstime.tv_sec = tv.tv_sec; + context->presstime.tv_usec = tv.tv_usec; + } + } else { + /* save the current valid code for repeats */ + memcpy(&context->lastcode, buf, 8); + /* + * set flag to signal a valid code was save; + * just for safety reasons + */ + context->codesaved = 1; + context->presstime.tv_sec = tv.tv_sec; + context->presstime.tv_usec = tv.tv_usec; + } + + lirc_buffer_write(context->driver->rbuf, buf); + wake_up(&context->driver->rbuf->wait_poll); +} + +/** + * Callback function for USB core API: receive data + */ +static void usb_rx_callback(struct urb *urb) +{ + struct sasem_context *context; + + if (!urb) + return; + context = (struct sasem_context *) urb->context; + if (!context) + return; + + switch (urb->status) { + + case -ENOENT: /* usbcore unlink successful! */ + return; + + case 0: + if (context->ir_isopen) + incoming_packet(context, urb); + break; + + default: + dev_warn(&urb->dev->dev, "%s: status (%d): ignored", + __func__, urb->status); + break; + } + + usb_submit_urb(context->rx_urb, GFP_ATOMIC); + return; +} + + + +/** + * Callback function for USB core API: Probe + */ +static int sasem_probe(struct usb_interface *interface, + const struct usb_device_id *id) +{ + struct usb_device *dev = NULL; + struct usb_host_interface *iface_desc = NULL; + struct usb_endpoint_descriptor *rx_endpoint = NULL; + struct usb_endpoint_descriptor *tx_endpoint = NULL; + struct urb *rx_urb = NULL; + struct urb *tx_urb = NULL; + struct lirc_driver *driver = NULL; + struct lirc_buffer *rbuf = NULL; + int lirc_minor = 0; + int num_endpoints; + int retval = 0; + int vfd_ep_found; + int ir_ep_found; + int alloc_status; + struct sasem_context *context = NULL; + int i; + + dev_info(&interface->dev, "%s: found Sasem device\n", __func__); + + + dev = usb_get_dev(interface_to_usbdev(interface)); + iface_desc = interface->cur_altsetting; + num_endpoints = iface_desc->desc.bNumEndpoints; + + /* + * Scan the endpoint list and set: + * first input endpoint = IR endpoint + * first output endpoint = VFD endpoint + */ + + ir_ep_found = 0; + vfd_ep_found = 0; + + for (i = 0; i < num_endpoints && !(ir_ep_found && vfd_ep_found); ++i) { + + struct usb_endpoint_descriptor *ep; + int ep_dir; + int ep_type; + ep = &iface_desc->endpoint [i].desc; + ep_dir = ep->bEndpointAddress & USB_ENDPOINT_DIR_MASK; + ep_type = ep->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK; + + if (!ir_ep_found && + ep_dir == USB_DIR_IN && + ep_type == USB_ENDPOINT_XFER_INT) { + + rx_endpoint = ep; + ir_ep_found = 1; + if (debug) + dev_info(&interface->dev, + "%s: found IR endpoint\n", __func__); + + } else if (!vfd_ep_found && + ep_dir == USB_DIR_OUT && + ep_type == USB_ENDPOINT_XFER_INT) { + + tx_endpoint = ep; + vfd_ep_found = 1; + if (debug) + dev_info(&interface->dev, + "%s: found VFD endpoint\n", __func__); + } + } + + /* Input endpoint is mandatory */ + if (!ir_ep_found) { + dev_err(&interface->dev, + "%s: no valid input (IR) endpoint found.\n", __func__); + retval = -ENODEV; + goto exit; + } + + if (!vfd_ep_found) + dev_info(&interface->dev, + "%s: no valid output (VFD) endpoint found.\n", + __func__); + + + /* Allocate memory */ + alloc_status = 0; + + context = kzalloc(sizeof(struct sasem_context), GFP_KERNEL); + if (!context) { + alloc_status = 1; + goto alloc_status_switch; + } + driver = kzalloc(sizeof(struct lirc_driver), GFP_KERNEL); + if (!driver) { + alloc_status = 2; + goto alloc_status_switch; + } + rbuf = kmalloc(sizeof(struct lirc_buffer), GFP_KERNEL); + if (!rbuf) { + alloc_status = 3; + goto alloc_status_switch; + } + if (lirc_buffer_init(rbuf, BUF_CHUNK_SIZE, BUF_SIZE)) { + dev_err(&interface->dev, + "%s: lirc_buffer_init failed\n", __func__); + alloc_status = 4; + goto alloc_status_switch; + } + rx_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!rx_urb) { + dev_err(&interface->dev, + "%s: usb_alloc_urb failed for IR urb\n", __func__); + alloc_status = 5; + goto alloc_status_switch; + } + if (vfd_ep_found) { + tx_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!tx_urb) { + dev_err(&interface->dev, + "%s: usb_alloc_urb failed for VFD urb", + __func__); + alloc_status = 6; + goto alloc_status_switch; + } + } + + mutex_init(&context->ctx_lock); + + strcpy(driver->name, MOD_NAME); + driver->minor = -1; + driver->code_length = 64; + driver->sample_rate = 0; + driver->features = LIRC_CAN_REC_LIRCCODE; + driver->data = context; + driver->rbuf = rbuf; + driver->set_use_inc = ir_open; + driver->set_use_dec = ir_close; + driver->dev = &interface->dev; + driver->owner = THIS_MODULE; + + mutex_lock(&context->ctx_lock); + + lirc_minor = lirc_register_driver(driver); + if (lirc_minor < 0) { + dev_err(&interface->dev, + "%s: lirc_register_driver failed\n", __func__); + alloc_status = 7; + retval = lirc_minor; + goto unlock; + } else + dev_info(&interface->dev, + "%s: Registered Sasem driver (minor:%d)\n", + __func__, lirc_minor); + + /* Needed while unregistering! */ + driver->minor = lirc_minor; + + context->dev = dev; + context->dev_present = 1; + context->rx_endpoint = rx_endpoint; + context->rx_urb = rx_urb; + if (vfd_ep_found) { + context->tx_endpoint = tx_endpoint; + context->tx_urb = tx_urb; + context->vfd_contrast = 1000; /* range 0 - 1000 */ + } + context->driver = driver; + + usb_set_intfdata(interface, context); + + if (vfd_ep_found) { + + if (debug) + dev_info(&interface->dev, + "Registering VFD with sysfs\n"); + if (usb_register_dev(interface, &sasem_class)) + /* Not a fatal error, so ignore */ + dev_info(&interface->dev, + "%s: could not get a minor number for VFD\n", + __func__); + } + + dev_info(&interface->dev, + "%s: Sasem device on usb<%d:%d> initialized\n", + __func__, dev->bus->busnum, dev->devnum); +unlock: + mutex_unlock(&context->ctx_lock); + +alloc_status_switch: + switch (alloc_status) { + + case 7: + if (vfd_ep_found) + usb_free_urb(tx_urb); + case 6: + usb_free_urb(rx_urb); + /* fall-through */ + case 5: + lirc_buffer_free(rbuf); + /* fall-through */ + case 4: + kfree(rbuf); + /* fall-through */ + case 3: + kfree(driver); + /* fall-through */ + case 2: + kfree(context); + context = NULL; + /* fall-through */ + case 1: + if (retval == 0) + retval = -ENOMEM; + } + +exit: + return retval; +} + +/** + * Callback function for USB core API: disconnect + */ +static void sasem_disconnect(struct usb_interface *interface) +{ + struct sasem_context *context; + + /* prevent races with ir_open()/vfd_open() */ + mutex_lock(&disconnect_lock); + + context = usb_get_intfdata(interface); + mutex_lock(&context->ctx_lock); + + dev_info(&interface->dev, "%s: Sasem device disconnected\n", + __func__); + + usb_set_intfdata(interface, NULL); + context->dev_present = 0; + + /* Stop reception */ + usb_kill_urb(context->rx_urb); + + /* Abort ongoing write */ + if (atomic_read(&context->tx.busy)) { + + usb_kill_urb(context->tx_urb); + wait_for_completion(&context->tx.finished); + } + + /* De-register from lirc_dev if IR port is not open */ + if (!context->ir_isopen) + deregister_from_lirc(context); + + usb_deregister_dev(interface, &sasem_class); + + mutex_unlock(&context->ctx_lock); + + if (!context->ir_isopen && !context->vfd_isopen) + delete_context(context); + + mutex_unlock(&disconnect_lock); +} + +module_usb_driver(sasem_driver); diff --git a/drivers/staging/media/lirc/lirc_serial.c b/drivers/staging/media/lirc/lirc_serial.c new file mode 100644 index 00000000000..efe561cd093 --- /dev/null +++ b/drivers/staging/media/lirc/lirc_serial.c @@ -0,0 +1,1240 @@ +/* + * lirc_serial.c + * + * lirc_serial - Device driver that records pulse- and pause-lengths + * (space-lengths) between DDCD event on a serial port. + * + * Copyright (C) 1996,97 Ralph Metzler <rjkm@thp.uni-koeln.de> + * Copyright (C) 1998 Trent Piepho <xyzzy@u.washington.edu> + * Copyright (C) 1998 Ben Pfaff <blp@gnu.org> + * Copyright (C) 1999 Christoph Bartelmus <lirc@bartelmus.de> + * Copyright (C) 2007 Andrei Tanas <andrei@tanas.ca> (suspend/resume support) + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/* + * Steve's changes to improve transmission fidelity: + * - for systems with the rdtsc instruction and the clock counter, a + * send_pule that times the pulses directly using the counter. + * This means that the LIRC_SERIAL_TRANSMITTER_LATENCY fudge is + * not needed. Measurement shows very stable waveform, even where + * PCI activity slows the access to the UART, which trips up other + * versions. + * - For other system, non-integer-microsecond pulse/space lengths, + * done using fixed point binary. So, much more accurate carrier + * frequency. + * - fine tuned transmitter latency, taking advantage of fractional + * microseconds in previous change + * - Fixed bug in the way transmitter latency was accounted for by + * tuning the pulse lengths down - the send_pulse routine ignored + * this overhead as it timed the overall pulse length - so the + * pulse frequency was right but overall pulse length was too + * long. Fixed by accounting for latency on each pulse/space + * iteration. + * + * Steve Davies <steve@daviesfam.org> July 2001 + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/signal.h> +#include <linux/sched.h> +#include <linux/fs.h> +#include <linux/interrupt.h> +#include <linux/ioport.h> +#include <linux/kernel.h> +#include <linux/serial_reg.h> +#include <linux/time.h> +#include <linux/string.h> +#include <linux/types.h> +#include <linux/wait.h> +#include <linux/mm.h> +#include <linux/delay.h> +#include <linux/poll.h> +#include <linux/platform_device.h> +#include <linux/gpio.h> +#include <linux/io.h> +#include <linux/irq.h> +#include <linux/fcntl.h> +#include <linux/spinlock.h> + +/* From Intel IXP42X Developer's Manual (#252480-005): */ +/* ftp://download.intel.com/design/network/manuals/25248005.pdf */ +#define UART_IE_IXP42X_UUE 0x40 /* IXP42X UART Unit enable */ +#define UART_IE_IXP42X_RTOIE 0x10 /* IXP42X Receiver Data Timeout int.enable */ + +#include <media/lirc.h> +#include <media/lirc_dev.h> + +#define LIRC_DRIVER_NAME "lirc_serial" + +struct lirc_serial { + int signal_pin; + int signal_pin_change; + u8 on; + u8 off; + long (*send_pulse)(unsigned long length); + void (*send_space)(long length); + int features; + spinlock_t lock; +}; + +#define LIRC_HOMEBREW 0 +#define LIRC_IRDEO 1 +#define LIRC_IRDEO_REMOTE 2 +#define LIRC_ANIMAX 3 +#define LIRC_IGOR 4 +#define LIRC_NSLU2 5 + +/*** module parameters ***/ +static int type; +static int io; +static int irq; +static bool iommap; +static int ioshift; +static bool softcarrier = 1; +static bool share_irq; +static bool debug; +static int sense = -1; /* -1 = auto, 0 = active high, 1 = active low */ +static bool txsense; /* 0 = active high, 1 = active low */ + +#define dprintk(fmt, args...) \ + do { \ + if (debug) \ + printk(KERN_DEBUG LIRC_DRIVER_NAME ": " \ + fmt, ## args); \ + } while (0) + +/* forward declarations */ +static long send_pulse_irdeo(unsigned long length); +static long send_pulse_homebrew(unsigned long length); +static void send_space_irdeo(long length); +static void send_space_homebrew(long length); + +static struct lirc_serial hardware[] = { + [LIRC_HOMEBREW] = { + .lock = __SPIN_LOCK_UNLOCKED(hardware[LIRC_HOMEBREW].lock), + .signal_pin = UART_MSR_DCD, + .signal_pin_change = UART_MSR_DDCD, + .on = (UART_MCR_RTS | UART_MCR_OUT2 | UART_MCR_DTR), + .off = (UART_MCR_RTS | UART_MCR_OUT2), + .send_pulse = send_pulse_homebrew, + .send_space = send_space_homebrew, +#ifdef CONFIG_LIRC_SERIAL_TRANSMITTER + .features = (LIRC_CAN_SET_SEND_DUTY_CYCLE | + LIRC_CAN_SET_SEND_CARRIER | + LIRC_CAN_SEND_PULSE | LIRC_CAN_REC_MODE2) +#else + .features = LIRC_CAN_REC_MODE2 +#endif + }, + + [LIRC_IRDEO] = { + .lock = __SPIN_LOCK_UNLOCKED(hardware[LIRC_IRDEO].lock), + .signal_pin = UART_MSR_DSR, + .signal_pin_change = UART_MSR_DDSR, + .on = UART_MCR_OUT2, + .off = (UART_MCR_RTS | UART_MCR_DTR | UART_MCR_OUT2), + .send_pulse = send_pulse_irdeo, + .send_space = send_space_irdeo, + .features = (LIRC_CAN_SET_SEND_DUTY_CYCLE | + LIRC_CAN_SEND_PULSE | LIRC_CAN_REC_MODE2) + }, + + [LIRC_IRDEO_REMOTE] = { + .lock = __SPIN_LOCK_UNLOCKED(hardware[LIRC_IRDEO_REMOTE].lock), + .signal_pin = UART_MSR_DSR, + .signal_pin_change = UART_MSR_DDSR, + .on = (UART_MCR_RTS | UART_MCR_DTR | UART_MCR_OUT2), + .off = (UART_MCR_RTS | UART_MCR_DTR | UART_MCR_OUT2), + .send_pulse = send_pulse_irdeo, + .send_space = send_space_irdeo, + .features = (LIRC_CAN_SET_SEND_DUTY_CYCLE | + LIRC_CAN_SEND_PULSE | LIRC_CAN_REC_MODE2) + }, + + [LIRC_ANIMAX] = { + .lock = __SPIN_LOCK_UNLOCKED(hardware[LIRC_ANIMAX].lock), + .signal_pin = UART_MSR_DCD, + .signal_pin_change = UART_MSR_DDCD, + .on = 0, + .off = (UART_MCR_RTS | UART_MCR_DTR | UART_MCR_OUT2), + .send_pulse = NULL, + .send_space = NULL, + .features = LIRC_CAN_REC_MODE2 + }, + + [LIRC_IGOR] = { + .lock = __SPIN_LOCK_UNLOCKED(hardware[LIRC_IGOR].lock), + .signal_pin = UART_MSR_DSR, + .signal_pin_change = UART_MSR_DDSR, + .on = (UART_MCR_RTS | UART_MCR_OUT2 | UART_MCR_DTR), + .off = (UART_MCR_RTS | UART_MCR_OUT2), + .send_pulse = send_pulse_homebrew, + .send_space = send_space_homebrew, +#ifdef CONFIG_LIRC_SERIAL_TRANSMITTER + .features = (LIRC_CAN_SET_SEND_DUTY_CYCLE | + LIRC_CAN_SET_SEND_CARRIER | + LIRC_CAN_SEND_PULSE | LIRC_CAN_REC_MODE2) +#else + .features = LIRC_CAN_REC_MODE2 +#endif + }, +}; + +#define RS_ISR_PASS_LIMIT 256 + +/* + * A long pulse code from a remote might take up to 300 bytes. The + * daemon should read the bytes as soon as they are generated, so take + * the number of keys you think you can push before the daemon runs + * and multiply by 300. The driver will warn you if you overrun this + * buffer. If you have a slow computer or non-busmastering IDE disks, + * maybe you will need to increase this. + */ + +/* This MUST be a power of two! It has to be larger than 1 as well. */ + +#define RBUF_LEN 256 + +static struct timeval lasttv = {0, 0}; + +static struct lirc_buffer rbuf; + +static unsigned int freq = 38000; +static unsigned int duty_cycle = 50; + +/* Initialized in init_timing_params() */ +static unsigned long period; +static unsigned long pulse_width; +static unsigned long space_width; + +#if defined(__i386__) +/* + * From: + * Linux I/O port programming mini-HOWTO + * Author: Riku Saikkonen <Riku.Saikkonen@hut.fi> + * v, 28 December 1997 + * + * [...] + * Actually, a port I/O instruction on most ports in the 0-0x3ff range + * takes almost exactly 1 microsecond, so if you're, for example, using + * the parallel port directly, just do additional inb()s from that port + * to delay. + * [...] + */ +/* transmitter latency 1.5625us 0x1.90 - this figure arrived at from + * comment above plus trimming to match actual measured frequency. + * This will be sensitive to cpu speed, though hopefully most of the 1.5us + * is spent in the uart access. Still - for reference test machine was a + * 1.13GHz Athlon system - Steve + */ + +/* + * changed from 400 to 450 as this works better on slower machines; + * faster machines will use the rdtsc code anyway + */ +#define LIRC_SERIAL_TRANSMITTER_LATENCY 450 + +#else + +/* does anybody have information on other platforms ? */ +/* 256 = 1<<8 */ +#define LIRC_SERIAL_TRANSMITTER_LATENCY 256 + +#endif /* __i386__ */ +/* + * FIXME: should we be using hrtimers instead of this + * LIRC_SERIAL_TRANSMITTER_LATENCY nonsense? + */ + +/* fetch serial input packet (1 byte) from register offset */ +static u8 sinp(int offset) +{ + if (iommap != 0) + /* the register is memory-mapped */ + offset <<= ioshift; + + return inb(io + offset); +} + +/* write serial output packet (1 byte) of value to register offset */ +static void soutp(int offset, u8 value) +{ + if (iommap != 0) + /* the register is memory-mapped */ + offset <<= ioshift; + + outb(value, io + offset); +} + +static void on(void) +{ + if (txsense) + soutp(UART_MCR, hardware[type].off); + else + soutp(UART_MCR, hardware[type].on); +} + +static void off(void) +{ + if (txsense) + soutp(UART_MCR, hardware[type].on); + else + soutp(UART_MCR, hardware[type].off); +} + +#ifndef MAX_UDELAY_MS +#define MAX_UDELAY_US 5000 +#else +#define MAX_UDELAY_US (MAX_UDELAY_MS*1000) +#endif + +static void safe_udelay(unsigned long usecs) +{ + while (usecs > MAX_UDELAY_US) { + udelay(MAX_UDELAY_US); + usecs -= MAX_UDELAY_US; + } + udelay(usecs); +} + +#ifdef USE_RDTSC +/* + * This is an overflow/precision juggle, complicated in that we can't + * do long long divide in the kernel + */ + +/* + * When we use the rdtsc instruction to measure clocks, we keep the + * pulse and space widths as clock cycles. As this is CPU speed + * dependent, the widths must be calculated in init_port and ioctl + * time + */ + +/* So send_pulse can quickly convert microseconds to clocks */ +static unsigned long conv_us_to_clocks; + +static int init_timing_params(unsigned int new_duty_cycle, + unsigned int new_freq) +{ + __u64 loops_per_sec, work; + + duty_cycle = new_duty_cycle; + freq = new_freq; + + loops_per_sec = __this_cpu_read(cpu.info.loops_per_jiffy); + loops_per_sec *= HZ; + + /* How many clocks in a microsecond?, avoiding long long divide */ + work = loops_per_sec; + work *= 4295; /* 4295 = 2^32 / 1e6 */ + conv_us_to_clocks = (work >> 32); + + /* + * Carrier period in clocks, approach good up to 32GHz clock, + * gets carrier frequency within 8Hz + */ + period = loops_per_sec >> 3; + period /= (freq >> 3); + + /* Derive pulse and space from the period */ + pulse_width = period * duty_cycle / 100; + space_width = period - pulse_width; + dprintk("in init_timing_params, freq=%d, duty_cycle=%d, " + "clk/jiffy=%ld, pulse=%ld, space=%ld, " + "conv_us_to_clocks=%ld\n", + freq, duty_cycle, __this_cpu_read(cpu_info.loops_per_jiffy), + pulse_width, space_width, conv_us_to_clocks); + return 0; +} +#else /* ! USE_RDTSC */ +static int init_timing_params(unsigned int new_duty_cycle, + unsigned int new_freq) +{ +/* + * period, pulse/space width are kept with 8 binary places - + * IE multiplied by 256. + */ + if (256 * 1000000L / new_freq * new_duty_cycle / 100 <= + LIRC_SERIAL_TRANSMITTER_LATENCY) + return -EINVAL; + if (256 * 1000000L / new_freq * (100 - new_duty_cycle) / 100 <= + LIRC_SERIAL_TRANSMITTER_LATENCY) + return -EINVAL; + duty_cycle = new_duty_cycle; + freq = new_freq; + period = 256 * 1000000L / freq; + pulse_width = period * duty_cycle / 100; + space_width = period - pulse_width; + dprintk("in init_timing_params, freq=%d pulse=%ld, space=%ld\n", + freq, pulse_width, space_width); + return 0; +} +#endif /* USE_RDTSC */ + + +/* return value: space length delta */ + +static long send_pulse_irdeo(unsigned long length) +{ + long rawbits, ret; + int i; + unsigned char output; + unsigned char chunk, shifted; + + /* how many bits have to be sent ? */ + rawbits = length * 1152 / 10000; + if (duty_cycle > 50) + chunk = 3; + else + chunk = 1; + for (i = 0, output = 0x7f; rawbits > 0; rawbits -= 3) { + shifted = chunk << (i * 3); + shifted >>= 1; + output &= (~shifted); + i++; + if (i == 3) { + soutp(UART_TX, output); + while (!(sinp(UART_LSR) & UART_LSR_THRE)) + ; + output = 0x7f; + i = 0; + } + } + if (i != 0) { + soutp(UART_TX, output); + while (!(sinp(UART_LSR) & UART_LSR_TEMT)) + ; + } + + if (i == 0) + ret = (-rawbits) * 10000 / 1152; + else + ret = (3 - i) * 3 * 10000 / 1152 + (-rawbits) * 10000 / 1152; + + return ret; +} + +#ifdef USE_RDTSC +/* Version that uses Pentium rdtsc instruction to measure clocks */ + +/* + * This version does sub-microsecond timing using rdtsc instruction, + * and does away with the fudged LIRC_SERIAL_TRANSMITTER_LATENCY + * Implicitly i586 architecture... - Steve + */ + +static long send_pulse_homebrew_softcarrier(unsigned long length) +{ + int flag; + unsigned long target, start, now; + + /* Get going quick as we can */ + rdtscl(start); + on(); + /* Convert length from microseconds to clocks */ + length *= conv_us_to_clocks; + /* And loop till time is up - flipping at right intervals */ + now = start; + target = pulse_width; + flag = 1; + /* + * FIXME: This looks like a hard busy wait, without even an occasional, + * polite, cpu_relax() call. There's got to be a better way? + * + * The i2c code has the result of a lot of bit-banging work, I wonder if + * there's something there which could be helpful here. + */ + while ((now - start) < length) { + /* Delay till flip time */ + do { + rdtscl(now); + } while ((now - start) < target); + + /* flip */ + if (flag) { + rdtscl(now); + off(); + target += space_width; + } else { + rdtscl(now); on(); + target += pulse_width; + } + flag = !flag; + } + rdtscl(now); + return ((now - start) - length) / conv_us_to_clocks; +} +#else /* ! USE_RDTSC */ +/* Version using udelay() */ + +/* + * here we use fixed point arithmetic, with 8 + * fractional bits. that gets us within 0.1% or so of the right average + * frequency, albeit with some jitter in pulse length - Steve + */ + +/* To match 8 fractional bits used for pulse/space length */ + +static long send_pulse_homebrew_softcarrier(unsigned long length) +{ + int flag; + unsigned long actual, target, d; + length <<= 8; + + actual = 0; target = 0; flag = 0; + while (actual < length) { + if (flag) { + off(); + target += space_width; + } else { + on(); + target += pulse_width; + } + d = (target - actual - + LIRC_SERIAL_TRANSMITTER_LATENCY + 128) >> 8; + /* + * Note - we've checked in ioctl that the pulse/space + * widths are big enough so that d is > 0 + */ + udelay(d); + actual += (d << 8) + LIRC_SERIAL_TRANSMITTER_LATENCY; + flag = !flag; + } + return (actual-length) >> 8; +} +#endif /* USE_RDTSC */ + +static long send_pulse_homebrew(unsigned long length) +{ + if (length <= 0) + return 0; + + if (softcarrier) + return send_pulse_homebrew_softcarrier(length); + else { + on(); + safe_udelay(length); + return 0; + } +} + +static void send_space_irdeo(long length) +{ + if (length <= 0) + return; + + safe_udelay(length); +} + +static void send_space_homebrew(long length) +{ + off(); + if (length <= 0) + return; + safe_udelay(length); +} + +static void rbwrite(int l) +{ + if (lirc_buffer_full(&rbuf)) { + /* no new signals will be accepted */ + dprintk("Buffer overrun\n"); + return; + } + lirc_buffer_write(&rbuf, (void *)&l); +} + +static void frbwrite(int l) +{ + /* simple noise filter */ + static int pulse, space; + static unsigned int ptr; + + if (ptr > 0 && (l & PULSE_BIT)) { + pulse += l & PULSE_MASK; + if (pulse > 250) { + rbwrite(space); + rbwrite(pulse | PULSE_BIT); + ptr = 0; + pulse = 0; + } + return; + } + if (!(l & PULSE_BIT)) { + if (ptr == 0) { + if (l > 20000) { + space = l; + ptr++; + return; + } + } else { + if (l > 20000) { + space += pulse; + if (space > PULSE_MASK) + space = PULSE_MASK; + space += l; + if (space > PULSE_MASK) + space = PULSE_MASK; + pulse = 0; + return; + } + rbwrite(space); + rbwrite(pulse | PULSE_BIT); + ptr = 0; + pulse = 0; + } + } + rbwrite(l); +} + +static irqreturn_t lirc_irq_handler(int i, void *blah) +{ + struct timeval tv; + int counter, dcd; + u8 status; + long deltv; + int data; + static int last_dcd = -1; + + if ((sinp(UART_IIR) & UART_IIR_NO_INT)) { + /* not our interrupt */ + return IRQ_NONE; + } + + counter = 0; + do { + counter++; + status = sinp(UART_MSR); + if (counter > RS_ISR_PASS_LIMIT) { + pr_warn("AIEEEE: We're caught!\n"); + break; + } + if ((status & hardware[type].signal_pin_change) + && sense != -1) { + /* get current time */ + do_gettimeofday(&tv); + + /* New mode, written by Trent Piepho + <xyzzy@u.washington.edu>. */ + + /* + * The old format was not very portable. + * We now use an int to pass pulses + * and spaces to user space. + * + * If PULSE_BIT is set a pulse has been + * received, otherwise a space has been + * received. The driver needs to know if your + * receiver is active high or active low, or + * the space/pulse sense could be + * inverted. The bits denoted by PULSE_MASK are + * the length in microseconds. Lengths greater + * than or equal to 16 seconds are clamped to + * PULSE_MASK. All other bits are unused. + * This is a much simpler interface for user + * programs, as well as eliminating "out of + * phase" errors with space/pulse + * autodetection. + */ + + /* calc time since last interrupt in microseconds */ + dcd = (status & hardware[type].signal_pin) ? 1 : 0; + + if (dcd == last_dcd) { + pr_warn("ignoring spike: %d %d %lx %lx %lx %lx\n", + dcd, sense, + tv.tv_sec, lasttv.tv_sec, + (unsigned long)tv.tv_usec, + (unsigned long)lasttv.tv_usec); + continue; + } + + deltv = tv.tv_sec-lasttv.tv_sec; + if (tv.tv_sec < lasttv.tv_sec || + (tv.tv_sec == lasttv.tv_sec && + tv.tv_usec < lasttv.tv_usec)) { + pr_warn("AIEEEE: your clock just jumped backwards\n"); + pr_warn("%d %d %lx %lx %lx %lx\n", + dcd, sense, + tv.tv_sec, lasttv.tv_sec, + (unsigned long)tv.tv_usec, + (unsigned long)lasttv.tv_usec); + data = PULSE_MASK; + } else if (deltv > 15) { + data = PULSE_MASK; /* really long time */ + if (!(dcd^sense)) { + /* sanity check */ + pr_warn("AIEEEE: %d %d %lx %lx %lx %lx\n", + dcd, sense, + tv.tv_sec, lasttv.tv_sec, + (unsigned long)tv.tv_usec, + (unsigned long)lasttv.tv_usec); + /* + * detecting pulse while this + * MUST be a space! + */ + sense = sense ? 0 : 1; + } + } else + data = (int) (deltv*1000000 + + tv.tv_usec - + lasttv.tv_usec); + frbwrite(dcd^sense ? data : (data|PULSE_BIT)); + lasttv = tv; + last_dcd = dcd; + wake_up_interruptible(&rbuf.wait_poll); + } + } while (!(sinp(UART_IIR) & UART_IIR_NO_INT)); /* still pending ? */ + return IRQ_HANDLED; +} + + +static int hardware_init_port(void) +{ + u8 scratch, scratch2, scratch3; + + /* + * This is a simple port existence test, borrowed from the autoconfig + * function in drivers/serial/8250.c + */ + scratch = sinp(UART_IER); + soutp(UART_IER, 0); +#ifdef __i386__ + outb(0xff, 0x080); +#endif + scratch2 = sinp(UART_IER) & 0x0f; + soutp(UART_IER, 0x0f); +#ifdef __i386__ + outb(0x00, 0x080); +#endif + scratch3 = sinp(UART_IER) & 0x0f; + soutp(UART_IER, scratch); + if (scratch2 != 0 || scratch3 != 0x0f) { + /* we fail, there's nothing here */ + pr_err("port existence test failed, cannot continue\n"); + return -ENODEV; + } + + + + /* Set DLAB 0. */ + soutp(UART_LCR, sinp(UART_LCR) & (~UART_LCR_DLAB)); + + /* First of all, disable all interrupts */ + soutp(UART_IER, sinp(UART_IER) & + (~(UART_IER_MSI|UART_IER_RLSI|UART_IER_THRI|UART_IER_RDI))); + + /* Clear registers. */ + sinp(UART_LSR); + sinp(UART_RX); + sinp(UART_IIR); + sinp(UART_MSR); + + /* Set line for power source */ + off(); + + /* Clear registers again to be sure. */ + sinp(UART_LSR); + sinp(UART_RX); + sinp(UART_IIR); + sinp(UART_MSR); + + switch (type) { + case LIRC_IRDEO: + case LIRC_IRDEO_REMOTE: + /* setup port to 7N1 @ 115200 Baud */ + /* 7N1+start = 9 bits at 115200 ~ 3 bits at 38kHz */ + + /* Set DLAB 1. */ + soutp(UART_LCR, sinp(UART_LCR) | UART_LCR_DLAB); + /* Set divisor to 1 => 115200 Baud */ + soutp(UART_DLM, 0); + soutp(UART_DLL, 1); + /* Set DLAB 0 + 7N1 */ + soutp(UART_LCR, UART_LCR_WLEN7); + /* THR interrupt already disabled at this point */ + break; + default: + break; + } + + return 0; +} + +static int lirc_serial_probe(struct platform_device *dev) +{ + int i, nlow, nhigh, result; + + result = request_irq(irq, lirc_irq_handler, + (share_irq ? IRQF_SHARED : 0), + LIRC_DRIVER_NAME, (void *)&hardware); + if (result < 0) { + if (result == -EBUSY) + dev_err(&dev->dev, "IRQ %d busy\n", irq); + else if (result == -EINVAL) + dev_err(&dev->dev, "Bad irq number or handler\n"); + return result; + } + + /* Reserve io region. */ + /* + * Future MMAP-Developers: Attention! + * For memory mapped I/O you *might* need to use ioremap() first, + * for the NSLU2 it's done in boot code. + */ + if (((iommap != 0) + && (request_mem_region(iommap, 8 << ioshift, + LIRC_DRIVER_NAME) == NULL)) + || ((iommap == 0) + && (request_region(io, 8, LIRC_DRIVER_NAME) == NULL))) { + dev_err(&dev->dev, "port %04x already in use\n", io); + dev_warn(&dev->dev, "use 'setserial /dev/ttySX uart none'\n"); + dev_warn(&dev->dev, + "or compile the serial port driver as module and\n"); + dev_warn(&dev->dev, "make sure this module is loaded first\n"); + result = -EBUSY; + goto exit_free_irq; + } + + result = hardware_init_port(); + if (result < 0) + goto exit_release_region; + + /* Initialize pulse/space widths */ + init_timing_params(duty_cycle, freq); + + /* If pin is high, then this must be an active low receiver. */ + if (sense == -1) { + /* wait 1/2 sec for the power supply */ + msleep(500); + + /* + * probe 9 times every 0.04s, collect "votes" for + * active high/low + */ + nlow = 0; + nhigh = 0; + for (i = 0; i < 9; i++) { + if (sinp(UART_MSR) & hardware[type].signal_pin) + nlow++; + else + nhigh++; + msleep(40); + } + sense = (nlow >= nhigh ? 1 : 0); + dev_info(&dev->dev, "auto-detected active %s receiver\n", + sense ? "low" : "high"); + } else + dev_info(&dev->dev, "Manually using active %s receiver\n", + sense ? "low" : "high"); + + dprintk("Interrupt %d, port %04x obtained\n", irq, io); + return 0; + +exit_release_region: + if (iommap != 0) + release_mem_region(iommap, 8 << ioshift); + else + release_region(io, 8); +exit_free_irq: + free_irq(irq, (void *)&hardware); + + return result; +} + +static int lirc_serial_remove(struct platform_device *dev) +{ + free_irq(irq, (void *)&hardware); + + if (iommap != 0) + release_mem_region(iommap, 8 << ioshift); + else + release_region(io, 8); + + return 0; +} + +static int set_use_inc(void *data) +{ + unsigned long flags; + + /* initialize timestamp */ + do_gettimeofday(&lasttv); + + spin_lock_irqsave(&hardware[type].lock, flags); + + /* Set DLAB 0. */ + soutp(UART_LCR, sinp(UART_LCR) & (~UART_LCR_DLAB)); + + soutp(UART_IER, sinp(UART_IER)|UART_IER_MSI); + + spin_unlock_irqrestore(&hardware[type].lock, flags); + + return 0; +} + +static void set_use_dec(void *data) +{ unsigned long flags; + + spin_lock_irqsave(&hardware[type].lock, flags); + + /* Set DLAB 0. */ + soutp(UART_LCR, sinp(UART_LCR) & (~UART_LCR_DLAB)); + + /* First of all, disable all interrupts */ + soutp(UART_IER, sinp(UART_IER) & + (~(UART_IER_MSI|UART_IER_RLSI|UART_IER_THRI|UART_IER_RDI))); + spin_unlock_irqrestore(&hardware[type].lock, flags); +} + +static ssize_t lirc_write(struct file *file, const char __user *buf, + size_t n, loff_t *ppos) +{ + int i, count; + unsigned long flags; + long delta = 0; + int *wbuf; + + if (!(hardware[type].features & LIRC_CAN_SEND_PULSE)) + return -EPERM; + + count = n / sizeof(int); + if (n % sizeof(int) || count % 2 == 0) + return -EINVAL; + wbuf = memdup_user(buf, n); + if (IS_ERR(wbuf)) + return PTR_ERR(wbuf); + spin_lock_irqsave(&hardware[type].lock, flags); + if (type == LIRC_IRDEO) { + /* DTR, RTS down */ + on(); + } + for (i = 0; i < count; i++) { + if (i%2) + hardware[type].send_space(wbuf[i] - delta); + else + delta = hardware[type].send_pulse(wbuf[i]); + } + off(); + spin_unlock_irqrestore(&hardware[type].lock, flags); + kfree(wbuf); + return n; +} + +static long lirc_ioctl(struct file *filep, unsigned int cmd, unsigned long arg) +{ + int result; + u32 __user *uptr = (u32 __user *)arg; + u32 value; + + switch (cmd) { + case LIRC_GET_SEND_MODE: + if (!(hardware[type].features&LIRC_CAN_SEND_MASK)) + return -ENOIOCTLCMD; + + result = put_user(LIRC_SEND2MODE + (hardware[type].features&LIRC_CAN_SEND_MASK), + uptr); + if (result) + return result; + break; + + case LIRC_SET_SEND_MODE: + if (!(hardware[type].features&LIRC_CAN_SEND_MASK)) + return -ENOIOCTLCMD; + + result = get_user(value, uptr); + if (result) + return result; + /* only LIRC_MODE_PULSE supported */ + if (value != LIRC_MODE_PULSE) + return -EINVAL; + break; + + case LIRC_GET_LENGTH: + return -ENOIOCTLCMD; + break; + + case LIRC_SET_SEND_DUTY_CYCLE: + dprintk("SET_SEND_DUTY_CYCLE\n"); + if (!(hardware[type].features&LIRC_CAN_SET_SEND_DUTY_CYCLE)) + return -ENOIOCTLCMD; + + result = get_user(value, uptr); + if (result) + return result; + if (value <= 0 || value > 100) + return -EINVAL; + return init_timing_params(value, freq); + break; + + case LIRC_SET_SEND_CARRIER: + dprintk("SET_SEND_CARRIER\n"); + if (!(hardware[type].features&LIRC_CAN_SET_SEND_CARRIER)) + return -ENOIOCTLCMD; + + result = get_user(value, uptr); + if (result) + return result; + if (value > 500000 || value < 20000) + return -EINVAL; + return init_timing_params(duty_cycle, value); + break; + + default: + return lirc_dev_fop_ioctl(filep, cmd, arg); + } + return 0; +} + +static const struct file_operations lirc_fops = { + .owner = THIS_MODULE, + .write = lirc_write, + .unlocked_ioctl = lirc_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = lirc_ioctl, +#endif + .read = lirc_dev_fop_read, + .poll = lirc_dev_fop_poll, + .open = lirc_dev_fop_open, + .release = lirc_dev_fop_close, + .llseek = no_llseek, +}; + +static struct lirc_driver driver = { + .name = LIRC_DRIVER_NAME, + .minor = -1, + .code_length = 1, + .sample_rate = 0, + .data = NULL, + .add_to_buf = NULL, + .rbuf = &rbuf, + .set_use_inc = set_use_inc, + .set_use_dec = set_use_dec, + .fops = &lirc_fops, + .dev = NULL, + .owner = THIS_MODULE, +}; + +static struct platform_device *lirc_serial_dev; + +static int lirc_serial_suspend(struct platform_device *dev, + pm_message_t state) +{ + /* Set DLAB 0. */ + soutp(UART_LCR, sinp(UART_LCR) & (~UART_LCR_DLAB)); + + /* Disable all interrupts */ + soutp(UART_IER, sinp(UART_IER) & + (~(UART_IER_MSI|UART_IER_RLSI|UART_IER_THRI|UART_IER_RDI))); + + /* Clear registers. */ + sinp(UART_LSR); + sinp(UART_RX); + sinp(UART_IIR); + sinp(UART_MSR); + + return 0; +} + +/* twisty maze... need a forward-declaration here... */ +static void lirc_serial_exit(void); + +static int lirc_serial_resume(struct platform_device *dev) +{ + unsigned long flags; + int result; + + result = hardware_init_port(); + if (result < 0) + return result; + + spin_lock_irqsave(&hardware[type].lock, flags); + /* Enable Interrupt */ + do_gettimeofday(&lasttv); + soutp(UART_IER, sinp(UART_IER)|UART_IER_MSI); + off(); + + lirc_buffer_clear(&rbuf); + + spin_unlock_irqrestore(&hardware[type].lock, flags); + + return 0; +} + +static struct platform_driver lirc_serial_driver = { + .probe = lirc_serial_probe, + .remove = lirc_serial_remove, + .suspend = lirc_serial_suspend, + .resume = lirc_serial_resume, + .driver = { + .name = "lirc_serial", + .owner = THIS_MODULE, + }, +}; + +static int __init lirc_serial_init(void) +{ + int result; + + /* Init read buffer. */ + result = lirc_buffer_init(&rbuf, sizeof(int), RBUF_LEN); + if (result < 0) + return result; + + result = platform_driver_register(&lirc_serial_driver); + if (result) { + printk("lirc register returned %d\n", result); + goto exit_buffer_free; + } + + lirc_serial_dev = platform_device_alloc("lirc_serial", 0); + if (!lirc_serial_dev) { + result = -ENOMEM; + goto exit_driver_unregister; + } + + result = platform_device_add(lirc_serial_dev); + if (result) + goto exit_device_put; + + return 0; + +exit_device_put: + platform_device_put(lirc_serial_dev); +exit_driver_unregister: + platform_driver_unregister(&lirc_serial_driver); +exit_buffer_free: + lirc_buffer_free(&rbuf); + return result; +} + +static void lirc_serial_exit(void) +{ + platform_device_unregister(lirc_serial_dev); + platform_driver_unregister(&lirc_serial_driver); + lirc_buffer_free(&rbuf); +} + +static int __init lirc_serial_init_module(void) +{ + int result; + + switch (type) { + case LIRC_HOMEBREW: + case LIRC_IRDEO: + case LIRC_IRDEO_REMOTE: + case LIRC_ANIMAX: + case LIRC_IGOR: + /* if nothing specified, use ttyS0/com1 and irq 4 */ + io = io ? io : 0x3f8; + irq = irq ? irq : 4; + break; + default: + return -EINVAL; + } + if (!softcarrier) { + switch (type) { + case LIRC_HOMEBREW: + case LIRC_IGOR: + hardware[type].features &= + ~(LIRC_CAN_SET_SEND_DUTY_CYCLE| + LIRC_CAN_SET_SEND_CARRIER); + break; + } + } + + /* make sure sense is either -1, 0, or 1 */ + if (sense != -1) + sense = !!sense; + + result = lirc_serial_init(); + if (result) + return result; + + driver.features = hardware[type].features; + driver.dev = &lirc_serial_dev->dev; + driver.minor = lirc_register_driver(&driver); + if (driver.minor < 0) { + pr_err("register_chrdev failed!\n"); + lirc_serial_exit(); + return driver.minor; + } + return 0; +} + +static void __exit lirc_serial_exit_module(void) +{ + lirc_unregister_driver(driver.minor); + lirc_serial_exit(); + dprintk("cleaned up module\n"); +} + + +module_init(lirc_serial_init_module); +module_exit(lirc_serial_exit_module); + +MODULE_DESCRIPTION("Infra-red receiver driver for serial ports."); +MODULE_AUTHOR("Ralph Metzler, Trent Piepho, Ben Pfaff, " + "Christoph Bartelmus, Andrei Tanas"); +MODULE_LICENSE("GPL"); + +module_param(type, int, S_IRUGO); +MODULE_PARM_DESC(type, "Hardware type (0 = home-brew, 1 = IRdeo," + " 2 = IRdeo Remote, 3 = AnimaX, 4 = IgorPlug," + " 5 = NSLU2 RX:CTS2/TX:GreenLED)"); + +module_param(io, int, S_IRUGO); +MODULE_PARM_DESC(io, "I/O address base (0x3f8 or 0x2f8)"); + +/* some architectures (e.g. intel xscale) have memory mapped registers */ +module_param(iommap, bool, S_IRUGO); +MODULE_PARM_DESC(iommap, "physical base for memory mapped I/O" + " (0 = no memory mapped io)"); + +/* + * some architectures (e.g. intel xscale) align the 8bit serial registers + * on 32bit word boundaries. + * See linux-kernel/drivers/tty/serial/8250/8250.c serial_in()/out() + */ +module_param(ioshift, int, S_IRUGO); +MODULE_PARM_DESC(ioshift, "shift I/O register offset (0 = no shift)"); + +module_param(irq, int, S_IRUGO); +MODULE_PARM_DESC(irq, "Interrupt (4 or 3)"); + +module_param(share_irq, bool, S_IRUGO); +MODULE_PARM_DESC(share_irq, "Share interrupts (0 = off, 1 = on)"); + +module_param(sense, int, S_IRUGO); +MODULE_PARM_DESC(sense, "Override autodetection of IR receiver circuit" + " (0 = active high, 1 = active low )"); + +#ifdef CONFIG_LIRC_SERIAL_TRANSMITTER +module_param(txsense, bool, S_IRUGO); +MODULE_PARM_DESC(txsense, "Sense of transmitter circuit" + " (0 = active high, 1 = active low )"); +#endif + +module_param(softcarrier, bool, S_IRUGO); +MODULE_PARM_DESC(softcarrier, "Software carrier (0 = off, 1 = on, default on)"); + +module_param(debug, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(debug, "Enable debugging messages"); diff --git a/drivers/staging/media/lirc/lirc_sir.c b/drivers/staging/media/lirc/lirc_sir.c new file mode 100644 index 00000000000..e31cbb81f05 --- /dev/null +++ b/drivers/staging/media/lirc/lirc_sir.c @@ -0,0 +1,1317 @@ +/* + * LIRC SIR driver, (C) 2000 Milan Pikula <www@fornax.sk> + * + * lirc_sir - Device driver for use with SIR (serial infra red) + * mode of IrDA on many notebooks. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * + * 2000/09/16 Frank Przybylski <mail@frankprzybylski.de> : + * added timeout and relaxed pulse detection, removed gap bug + * + * 2000/12/15 Christoph Bartelmus <lirc@bartelmus.de> : + * added support for Tekram Irmate 210 (sending does not work yet, + * kind of disappointing that nobody was able to implement that + * before), + * major clean-up + * + * 2001/02/27 Christoph Bartelmus <lirc@bartelmus.de> : + * added support for StrongARM SA1100 embedded microprocessor + * parts cut'n'pasted from sa1100_ir.c (C) 2000 Russell King + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/module.h> +#include <linux/sched.h> +#include <linux/errno.h> +#include <linux/signal.h> +#include <linux/fs.h> +#include <linux/interrupt.h> +#include <linux/ioport.h> +#include <linux/kernel.h> +#include <linux/serial_reg.h> +#include <linux/time.h> +#include <linux/string.h> +#include <linux/types.h> +#include <linux/wait.h> +#include <linux/mm.h> +#include <linux/delay.h> +#include <linux/poll.h> +#include <linux/io.h> +#include <asm/irq.h> +#include <linux/fcntl.h> +#include <linux/platform_device.h> +#ifdef LIRC_ON_SA1100 +#include <asm/hardware.h> +#ifdef CONFIG_SA1100_COLLIE +#include <asm/arch/tc35143.h> +#include <asm/ucb1200.h> +#endif +#endif + +#include <linux/timer.h> + +#include <media/lirc.h> +#include <media/lirc_dev.h> + +/* SECTION: Definitions */ + +/*** Tekram dongle ***/ +#ifdef LIRC_SIR_TEKRAM +/* stolen from kernel source */ +/* definitions for Tekram dongle */ +#define TEKRAM_115200 0x00 +#define TEKRAM_57600 0x01 +#define TEKRAM_38400 0x02 +#define TEKRAM_19200 0x03 +#define TEKRAM_9600 0x04 +#define TEKRAM_2400 0x08 + +#define TEKRAM_PW 0x10 /* Pulse select bit */ + +/* 10bit * 1s/115200bit in milliseconds = 87ms*/ +#define TIME_CONST (10000000ul/115200ul) + +#endif + +#ifdef LIRC_SIR_ACTISYS_ACT200L +static void init_act200(void); +#elif defined(LIRC_SIR_ACTISYS_ACT220L) +static void init_act220(void); +#endif + +/*** SA1100 ***/ +#ifdef LIRC_ON_SA1100 +struct sa1100_ser2_registers { + /* HSSP control register */ + unsigned char hscr0; + /* UART registers */ + unsigned char utcr0; + unsigned char utcr1; + unsigned char utcr2; + unsigned char utcr3; + unsigned char utcr4; + unsigned char utdr; + unsigned char utsr0; + unsigned char utsr1; +} sr; + +static int irq = IRQ_Ser2ICP; + +#define LIRC_ON_SA1100_TRANSMITTER_LATENCY 0 + +/* pulse/space ratio of 50/50 */ +static unsigned long pulse_width = (13-LIRC_ON_SA1100_TRANSMITTER_LATENCY); +/* 1000000/freq-pulse_width */ +static unsigned long space_width = (13-LIRC_ON_SA1100_TRANSMITTER_LATENCY); +static unsigned int freq = 38000; /* modulation frequency */ +static unsigned int duty_cycle = 50; /* duty cycle of 50% */ + +#endif + +#define RBUF_LEN 1024 +#define WBUF_LEN 1024 + +#define LIRC_DRIVER_NAME "lirc_sir" + +#define PULSE '[' + +#ifndef LIRC_SIR_TEKRAM +/* 9bit * 1s/115200bit in milli seconds = 78.125ms*/ +#define TIME_CONST (9000000ul/115200ul) +#endif + + +/* timeout for sequences in jiffies (=5/100s), must be longer than TIME_CONST */ +#define SIR_TIMEOUT (HZ*5/100) + +#ifndef LIRC_ON_SA1100 +#ifndef LIRC_IRQ +#define LIRC_IRQ 4 +#endif +#ifndef LIRC_PORT +/* for external dongles, default to com1 */ +#if defined(LIRC_SIR_ACTISYS_ACT200L) || \ + defined(LIRC_SIR_ACTISYS_ACT220L) || \ + defined(LIRC_SIR_TEKRAM) +#define LIRC_PORT 0x3f8 +#else +/* onboard sir ports are typically com3 */ +#define LIRC_PORT 0x3e8 +#endif +#endif + +static int io = LIRC_PORT; +static int irq = LIRC_IRQ; +static int threshold = 3; +#endif + +static DEFINE_SPINLOCK(timer_lock); +static struct timer_list timerlist; +/* time of last signal change detected */ +static struct timeval last_tv = {0, 0}; +/* time of last UART data ready interrupt */ +static struct timeval last_intr_tv = {0, 0}; +static int last_value; + +static DECLARE_WAIT_QUEUE_HEAD(lirc_read_queue); + +static DEFINE_SPINLOCK(hardware_lock); + +static int rx_buf[RBUF_LEN]; +static unsigned int rx_tail, rx_head; + +static bool debug; +#define dprintk(fmt, args...) \ + do { \ + if (debug) \ + printk(KERN_DEBUG LIRC_DRIVER_NAME ": " \ + fmt, ## args); \ + } while (0) + +/* SECTION: Prototypes */ + +/* Communication with user-space */ +static unsigned int lirc_poll(struct file *file, poll_table *wait); +static ssize_t lirc_read(struct file *file, char __user *buf, size_t count, + loff_t *ppos); +static ssize_t lirc_write(struct file *file, const char __user *buf, size_t n, + loff_t *pos); +static long lirc_ioctl(struct file *filep, unsigned int cmd, unsigned long arg); +static void add_read_queue(int flag, unsigned long val); +static int init_chrdev(void); +static void drop_chrdev(void); +/* Hardware */ +static irqreturn_t sir_interrupt(int irq, void *dev_id); +static void send_space(unsigned long len); +static void send_pulse(unsigned long len); +static int init_hardware(void); +static void drop_hardware(void); +/* Initialisation */ +static int init_port(void); +static void drop_port(void); + +#ifdef LIRC_ON_SA1100 +static void on(void) +{ + PPSR |= PPC_TXD2; +} + +static void off(void) +{ + PPSR &= ~PPC_TXD2; +} +#else +static inline unsigned int sinp(int offset) +{ + return inb(io + offset); +} + +static inline void soutp(int offset, int value) +{ + outb(value, io + offset); +} +#endif + +#ifndef MAX_UDELAY_MS +#define MAX_UDELAY_US 5000 +#else +#define MAX_UDELAY_US (MAX_UDELAY_MS*1000) +#endif + +static void safe_udelay(unsigned long usecs) +{ + while (usecs > MAX_UDELAY_US) { + udelay(MAX_UDELAY_US); + usecs -= MAX_UDELAY_US; + } + udelay(usecs); +} + +/* SECTION: Communication with user-space */ + +static unsigned int lirc_poll(struct file *file, poll_table *wait) +{ + poll_wait(file, &lirc_read_queue, wait); + if (rx_head != rx_tail) + return POLLIN | POLLRDNORM; + return 0; +} + +static ssize_t lirc_read(struct file *file, char __user *buf, size_t count, + loff_t *ppos) +{ + int n = 0; + int retval = 0; + DECLARE_WAITQUEUE(wait, current); + + if (count % sizeof(int)) + return -EINVAL; + + add_wait_queue(&lirc_read_queue, &wait); + set_current_state(TASK_INTERRUPTIBLE); + while (n < count) { + if (rx_head != rx_tail) { + if (copy_to_user(buf + n, + rx_buf + rx_head, + sizeof(int))) { + retval = -EFAULT; + break; + } + rx_head = (rx_head + 1) & (RBUF_LEN - 1); + n += sizeof(int); + } else { + if (file->f_flags & O_NONBLOCK) { + retval = -EAGAIN; + break; + } + if (signal_pending(current)) { + retval = -ERESTARTSYS; + break; + } + schedule(); + set_current_state(TASK_INTERRUPTIBLE); + } + } + remove_wait_queue(&lirc_read_queue, &wait); + set_current_state(TASK_RUNNING); + return n ? n : retval; +} +static ssize_t lirc_write(struct file *file, const char __user *buf, size_t n, + loff_t *pos) +{ + unsigned long flags; + int i, count; + int *tx_buf; + + count = n / sizeof(int); + if (n % sizeof(int) || count % 2 == 0) + return -EINVAL; + tx_buf = memdup_user(buf, n); + if (IS_ERR(tx_buf)) + return PTR_ERR(tx_buf); + i = 0; +#ifdef LIRC_ON_SA1100 + /* disable receiver */ + Ser2UTCR3 = 0; +#endif + local_irq_save(flags); + while (1) { + if (i >= count) + break; + if (tx_buf[i]) + send_pulse(tx_buf[i]); + i++; + if (i >= count) + break; + if (tx_buf[i]) + send_space(tx_buf[i]); + i++; + } + local_irq_restore(flags); +#ifdef LIRC_ON_SA1100 + off(); + udelay(1000); /* wait 1ms for IR diode to recover */ + Ser2UTCR3 = 0; + /* clear status register to prevent unwanted interrupts */ + Ser2UTSR0 &= (UTSR0_RID | UTSR0_RBB | UTSR0_REB); + /* enable receiver */ + Ser2UTCR3 = UTCR3_RXE|UTCR3_RIE; +#endif + kfree(tx_buf); + return count; +} + +static long lirc_ioctl(struct file *filep, unsigned int cmd, unsigned long arg) +{ + u32 __user *uptr = (u32 __user *)arg; + int retval = 0; + u32 value = 0; +#ifdef LIRC_ON_SA1100 + + if (cmd == LIRC_GET_FEATURES) + value = LIRC_CAN_SEND_PULSE | + LIRC_CAN_SET_SEND_DUTY_CYCLE | + LIRC_CAN_SET_SEND_CARRIER | + LIRC_CAN_REC_MODE2; + else if (cmd == LIRC_GET_SEND_MODE) + value = LIRC_MODE_PULSE; + else if (cmd == LIRC_GET_REC_MODE) + value = LIRC_MODE_MODE2; +#else + if (cmd == LIRC_GET_FEATURES) + value = LIRC_CAN_SEND_PULSE | LIRC_CAN_REC_MODE2; + else if (cmd == LIRC_GET_SEND_MODE) + value = LIRC_MODE_PULSE; + else if (cmd == LIRC_GET_REC_MODE) + value = LIRC_MODE_MODE2; +#endif + + switch (cmd) { + case LIRC_GET_FEATURES: + case LIRC_GET_SEND_MODE: + case LIRC_GET_REC_MODE: + retval = put_user(value, uptr); + break; + + case LIRC_SET_SEND_MODE: + case LIRC_SET_REC_MODE: + retval = get_user(value, uptr); + break; +#ifdef LIRC_ON_SA1100 + case LIRC_SET_SEND_DUTY_CYCLE: + retval = get_user(value, uptr); + if (retval) + return retval; + if (value <= 0 || value > 100) + return -EINVAL; + /* (value/100)*(1000000/freq) */ + duty_cycle = value; + pulse_width = (unsigned long) duty_cycle*10000/freq; + space_width = (unsigned long) 1000000L/freq-pulse_width; + if (pulse_width >= LIRC_ON_SA1100_TRANSMITTER_LATENCY) + pulse_width -= LIRC_ON_SA1100_TRANSMITTER_LATENCY; + if (space_width >= LIRC_ON_SA1100_TRANSMITTER_LATENCY) + space_width -= LIRC_ON_SA1100_TRANSMITTER_LATENCY; + break; + case LIRC_SET_SEND_CARRIER: + retval = get_user(value, uptr); + if (retval) + return retval; + if (value > 500000 || value < 20000) + return -EINVAL; + freq = value; + pulse_width = (unsigned long) duty_cycle*10000/freq; + space_width = (unsigned long) 1000000L/freq-pulse_width; + if (pulse_width >= LIRC_ON_SA1100_TRANSMITTER_LATENCY) + pulse_width -= LIRC_ON_SA1100_TRANSMITTER_LATENCY; + if (space_width >= LIRC_ON_SA1100_TRANSMITTER_LATENCY) + space_width -= LIRC_ON_SA1100_TRANSMITTER_LATENCY; + break; +#endif + default: + retval = -ENOIOCTLCMD; + + } + + if (retval) + return retval; + if (cmd == LIRC_SET_REC_MODE) { + if (value != LIRC_MODE_MODE2) + retval = -ENOSYS; + } else if (cmd == LIRC_SET_SEND_MODE) { + if (value != LIRC_MODE_PULSE) + retval = -ENOSYS; + } + + return retval; +} + +static void add_read_queue(int flag, unsigned long val) +{ + unsigned int new_rx_tail; + int newval; + + dprintk("add flag %d with val %lu\n", flag, val); + + newval = val & PULSE_MASK; + + /* + * statistically, pulses are ~TIME_CONST/2 too long. we could + * maybe make this more exact, but this is good enough + */ + if (flag) { + /* pulse */ + if (newval > TIME_CONST/2) + newval -= TIME_CONST/2; + else /* should not ever happen */ + newval = 1; + newval |= PULSE_BIT; + } else { + newval += TIME_CONST/2; + } + new_rx_tail = (rx_tail + 1) & (RBUF_LEN - 1); + if (new_rx_tail == rx_head) { + dprintk("Buffer overrun.\n"); + return; + } + rx_buf[rx_tail] = newval; + rx_tail = new_rx_tail; + wake_up_interruptible(&lirc_read_queue); +} + +static const struct file_operations lirc_fops = { + .owner = THIS_MODULE, + .read = lirc_read, + .write = lirc_write, + .poll = lirc_poll, + .unlocked_ioctl = lirc_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = lirc_ioctl, +#endif + .open = lirc_dev_fop_open, + .release = lirc_dev_fop_close, + .llseek = no_llseek, +}; + +static int set_use_inc(void *data) +{ + return 0; +} + +static void set_use_dec(void *data) +{ +} + +static struct lirc_driver driver = { + .name = LIRC_DRIVER_NAME, + .minor = -1, + .code_length = 1, + .sample_rate = 0, + .data = NULL, + .add_to_buf = NULL, + .set_use_inc = set_use_inc, + .set_use_dec = set_use_dec, + .fops = &lirc_fops, + .dev = NULL, + .owner = THIS_MODULE, +}; + +static struct platform_device *lirc_sir_dev; + +static int init_chrdev(void) +{ + driver.dev = &lirc_sir_dev->dev; + driver.minor = lirc_register_driver(&driver); + if (driver.minor < 0) { + pr_err("init_chrdev() failed.\n"); + return -EIO; + } + return 0; +} + +static void drop_chrdev(void) +{ + lirc_unregister_driver(driver.minor); +} + +/* SECTION: Hardware */ +static long delta(struct timeval *tv1, struct timeval *tv2) +{ + unsigned long deltv; + + deltv = tv2->tv_sec - tv1->tv_sec; + if (deltv > 15) + deltv = 0xFFFFFF; + else + deltv = deltv*1000000 + + tv2->tv_usec - + tv1->tv_usec; + return deltv; +} + +static void sir_timeout(unsigned long data) +{ + /* + * if last received signal was a pulse, but receiving stopped + * within the 9 bit frame, we need to finish this pulse and + * simulate a signal change to from pulse to space. Otherwise + * upper layers will receive two sequences next time. + */ + + unsigned long flags; + unsigned long pulse_end; + + /* avoid interference with interrupt */ + spin_lock_irqsave(&timer_lock, flags); + if (last_value) { +#ifndef LIRC_ON_SA1100 + /* clear unread bits in UART and restart */ + outb(UART_FCR_CLEAR_RCVR, io + UART_FCR); +#endif + /* determine 'virtual' pulse end: */ + pulse_end = delta(&last_tv, &last_intr_tv); + dprintk("timeout add %d for %lu usec\n", last_value, pulse_end); + add_read_queue(last_value, pulse_end); + last_value = 0; + last_tv = last_intr_tv; + } + spin_unlock_irqrestore(&timer_lock, flags); +} + +static irqreturn_t sir_interrupt(int irq, void *dev_id) +{ + unsigned char data; + struct timeval curr_tv; + static unsigned long deltv; +#ifdef LIRC_ON_SA1100 + int status; + static int n; + + status = Ser2UTSR0; + /* + * Deal with any receive errors first. The bytes in error may be + * the only bytes in the receive FIFO, so we do this first. + */ + while (status & UTSR0_EIF) { + int bstat; + + if (debug) { + dprintk("EIF\n"); + bstat = Ser2UTSR1; + + if (bstat & UTSR1_FRE) + dprintk("frame error\n"); + if (bstat & UTSR1_ROR) + dprintk("receive fifo overrun\n"); + if (bstat & UTSR1_PRE) + dprintk("parity error\n"); + } + + bstat = Ser2UTDR; + n++; + status = Ser2UTSR0; + } + + if (status & (UTSR0_RFS | UTSR0_RID)) { + do_gettimeofday(&curr_tv); + deltv = delta(&last_tv, &curr_tv); + do { + data = Ser2UTDR; + dprintk("%d data: %u\n", n, (unsigned int) data); + n++; + } while (status & UTSR0_RID && /* do not empty fifo in order to + * get UTSR0_RID in any case */ + Ser2UTSR1 & UTSR1_RNE); /* data ready */ + + if (status&UTSR0_RID) { + add_read_queue(0 , deltv - n * TIME_CONST); /*space*/ + add_read_queue(1, n * TIME_CONST); /*pulse*/ + n = 0; + last_tv = curr_tv; + } + } + + if (status & UTSR0_TFS) + pr_err("transmit fifo not full, shouldn't happen\n"); + + /* We must clear certain bits. */ + status &= (UTSR0_RID | UTSR0_RBB | UTSR0_REB); + if (status) + Ser2UTSR0 = status; +#else + unsigned long deltintrtv; + unsigned long flags; + int iir, lsr; + + while ((iir = inb(io + UART_IIR) & UART_IIR_ID)) { + switch (iir&UART_IIR_ID) { /* FIXME toto treba preriedit */ + case UART_IIR_MSI: + (void) inb(io + UART_MSR); + break; + case UART_IIR_RLSI: + (void) inb(io + UART_LSR); + break; + case UART_IIR_THRI: +#if 0 + if (lsr & UART_LSR_THRE) /* FIFO is empty */ + outb(data, io + UART_TX) +#endif + break; + case UART_IIR_RDI: + /* avoid interference with timer */ + spin_lock_irqsave(&timer_lock, flags); + do { + del_timer(&timerlist); + data = inb(io + UART_RX); + do_gettimeofday(&curr_tv); + deltv = delta(&last_tv, &curr_tv); + deltintrtv = delta(&last_intr_tv, &curr_tv); + dprintk("t %lu, d %d\n", deltintrtv, (int)data); + /* + * if nothing came in last X cycles, + * it was gap + */ + if (deltintrtv > TIME_CONST * threshold) { + if (last_value) { + dprintk("GAP\n"); + /* simulate signal change */ + add_read_queue(last_value, + deltv - + deltintrtv); + last_value = 0; + last_tv.tv_sec = + last_intr_tv.tv_sec; + last_tv.tv_usec = + last_intr_tv.tv_usec; + deltv = deltintrtv; + } + } + data = 1; + if (data ^ last_value) { + /* + * deltintrtv > 2*TIME_CONST, remember? + * the other case is timeout + */ + add_read_queue(last_value, + deltv-TIME_CONST); + last_value = data; + last_tv = curr_tv; + if (last_tv.tv_usec >= TIME_CONST) { + last_tv.tv_usec -= TIME_CONST; + } else { + last_tv.tv_sec--; + last_tv.tv_usec += 1000000 - + TIME_CONST; + } + } + last_intr_tv = curr_tv; + if (data) { + /* + * start timer for end of + * sequence detection + */ + timerlist.expires = jiffies + + SIR_TIMEOUT; + add_timer(&timerlist); + } + + lsr = inb(io + UART_LSR); + } while (lsr & UART_LSR_DR); /* data ready */ + spin_unlock_irqrestore(&timer_lock, flags); + break; + default: + break; + } + } +#endif + return IRQ_RETVAL(IRQ_HANDLED); +} + +#ifdef LIRC_ON_SA1100 +static void send_pulse(unsigned long length) +{ + unsigned long k, delay; + int flag; + + if (length == 0) + return; + /* + * this won't give us the carrier frequency we really want + * due to integer arithmetic, but we can accept this inaccuracy + */ + + for (k = flag = 0; k < length; k += delay, flag = !flag) { + if (flag) { + off(); + delay = space_width; + } else { + on(); + delay = pulse_width; + } + safe_udelay(delay); + } + off(); +} + +static void send_space(unsigned long length) +{ + if (length == 0) + return; + off(); + safe_udelay(length); +} +#else +static void send_space(unsigned long len) +{ + safe_udelay(len); +} + +static void send_pulse(unsigned long len) +{ + long bytes_out = len / TIME_CONST; + + if (bytes_out == 0) + bytes_out++; + + while (bytes_out--) { + outb(PULSE, io + UART_TX); + /* FIXME treba seriozne cakanie z char/serial.c */ + while (!(inb(io + UART_LSR) & UART_LSR_THRE)) + ; + } +} +#endif + +#ifdef CONFIG_SA1100_COLLIE +static int sa1100_irda_set_power_collie(int state) +{ + if (state) { + /* + * 0 - off + * 1 - short range, lowest power + * 2 - medium range, medium power + * 3 - maximum range, high power + */ + ucb1200_set_io_direction(TC35143_GPIO_IR_ON, + TC35143_IODIR_OUTPUT); + ucb1200_set_io(TC35143_GPIO_IR_ON, TC35143_IODAT_LOW); + udelay(100); + } else { + /* OFF */ + ucb1200_set_io_direction(TC35143_GPIO_IR_ON, + TC35143_IODIR_OUTPUT); + ucb1200_set_io(TC35143_GPIO_IR_ON, TC35143_IODAT_HIGH); + } + return 0; +} +#endif + +static int init_hardware(void) +{ + unsigned long flags; + + spin_lock_irqsave(&hardware_lock, flags); + /* reset UART */ +#ifdef LIRC_ON_SA1100 +#ifdef CONFIG_SA1100_COLLIE + sa1100_irda_set_power_collie(3); /* power on */ +#endif + sr.hscr0 = Ser2HSCR0; + + sr.utcr0 = Ser2UTCR0; + sr.utcr1 = Ser2UTCR1; + sr.utcr2 = Ser2UTCR2; + sr.utcr3 = Ser2UTCR3; + sr.utcr4 = Ser2UTCR4; + + sr.utdr = Ser2UTDR; + sr.utsr0 = Ser2UTSR0; + sr.utsr1 = Ser2UTSR1; + + /* configure GPIO */ + /* output */ + PPDR |= PPC_TXD2; + PSDR |= PPC_TXD2; + /* set output to 0 */ + off(); + + /* Enable HP-SIR modulation, and ensure that the port is disabled. */ + Ser2UTCR3 = 0; + Ser2HSCR0 = sr.hscr0 & (~HSCR0_HSSP); + + /* clear status register to prevent unwanted interrupts */ + Ser2UTSR0 &= (UTSR0_RID | UTSR0_RBB | UTSR0_REB); + + /* 7N1 */ + Ser2UTCR0 = UTCR0_1StpBit|UTCR0_7BitData; + /* 115200 */ + Ser2UTCR1 = 0; + Ser2UTCR2 = 1; + /* use HPSIR, 1.6 usec pulses */ + Ser2UTCR4 = UTCR4_HPSIR|UTCR4_Z1_6us; + + /* enable receiver, receive fifo interrupt */ + Ser2UTCR3 = UTCR3_RXE|UTCR3_RIE; + + /* clear status register to prevent unwanted interrupts */ + Ser2UTSR0 &= (UTSR0_RID | UTSR0_RBB | UTSR0_REB); + +#elif defined(LIRC_SIR_TEKRAM) + /* disable FIFO */ + soutp(UART_FCR, + UART_FCR_CLEAR_RCVR| + UART_FCR_CLEAR_XMIT| + UART_FCR_TRIGGER_1); + + /* Set DLAB 0. */ + soutp(UART_LCR, sinp(UART_LCR) & (~UART_LCR_DLAB)); + + /* First of all, disable all interrupts */ + soutp(UART_IER, sinp(UART_IER) & + (~(UART_IER_MSI|UART_IER_RLSI|UART_IER_THRI|UART_IER_RDI))); + + /* Set DLAB 1. */ + soutp(UART_LCR, sinp(UART_LCR) | UART_LCR_DLAB); + + /* Set divisor to 12 => 9600 Baud */ + soutp(UART_DLM, 0); + soutp(UART_DLL, 12); + + /* Set DLAB 0. */ + soutp(UART_LCR, sinp(UART_LCR) & (~UART_LCR_DLAB)); + + /* power supply */ + soutp(UART_MCR, UART_MCR_RTS|UART_MCR_DTR|UART_MCR_OUT2); + safe_udelay(50*1000); + + /* -DTR low -> reset PIC */ + soutp(UART_MCR, UART_MCR_RTS|UART_MCR_OUT2); + udelay(1*1000); + + soutp(UART_MCR, UART_MCR_RTS|UART_MCR_DTR|UART_MCR_OUT2); + udelay(100); + + + /* -RTS low -> send control byte */ + soutp(UART_MCR, UART_MCR_DTR|UART_MCR_OUT2); + udelay(7); + soutp(UART_TX, TEKRAM_115200|TEKRAM_PW); + + /* one byte takes ~1042 usec to transmit at 9600,8N1 */ + udelay(1500); + + /* back to normal operation */ + soutp(UART_MCR, UART_MCR_RTS|UART_MCR_DTR|UART_MCR_OUT2); + udelay(50); + + udelay(1500); + + /* read previous control byte */ + pr_info("0x%02x\n", sinp(UART_RX)); + + /* Set DLAB 1. */ + soutp(UART_LCR, sinp(UART_LCR) | UART_LCR_DLAB); + + /* Set divisor to 1 => 115200 Baud */ + soutp(UART_DLM, 0); + soutp(UART_DLL, 1); + + /* Set DLAB 0, 8 Bit */ + soutp(UART_LCR, UART_LCR_WLEN8); + /* enable interrupts */ + soutp(UART_IER, sinp(UART_IER)|UART_IER_RDI); +#else + outb(0, io + UART_MCR); + outb(0, io + UART_IER); + /* init UART */ + /* set DLAB, speed = 115200 */ + outb(UART_LCR_DLAB | UART_LCR_WLEN7, io + UART_LCR); + outb(1, io + UART_DLL); outb(0, io + UART_DLM); + /* 7N1+start = 9 bits at 115200 ~ 3 bits at 44000 */ + outb(UART_LCR_WLEN7, io + UART_LCR); + /* FIFO operation */ + outb(UART_FCR_ENABLE_FIFO, io + UART_FCR); + /* interrupts */ + /* outb(UART_IER_RLSI|UART_IER_RDI|UART_IER_THRI, io + UART_IER); */ + outb(UART_IER_RDI, io + UART_IER); + /* turn on UART */ + outb(UART_MCR_DTR|UART_MCR_RTS|UART_MCR_OUT2, io + UART_MCR); +#ifdef LIRC_SIR_ACTISYS_ACT200L + init_act200(); +#elif defined(LIRC_SIR_ACTISYS_ACT220L) + init_act220(); +#endif +#endif + spin_unlock_irqrestore(&hardware_lock, flags); + return 0; +} + +static void drop_hardware(void) +{ + unsigned long flags; + + spin_lock_irqsave(&hardware_lock, flags); + +#ifdef LIRC_ON_SA1100 + Ser2UTCR3 = 0; + + Ser2UTCR0 = sr.utcr0; + Ser2UTCR1 = sr.utcr1; + Ser2UTCR2 = sr.utcr2; + Ser2UTCR4 = sr.utcr4; + Ser2UTCR3 = sr.utcr3; + + Ser2HSCR0 = sr.hscr0; +#ifdef CONFIG_SA1100_COLLIE + sa1100_irda_set_power_collie(0); /* power off */ +#endif +#else + /* turn off interrupts */ + outb(0, io + UART_IER); +#endif + spin_unlock_irqrestore(&hardware_lock, flags); +} + +/* SECTION: Initialisation */ + +static int init_port(void) +{ + int retval; + + /* get I/O port access and IRQ line */ +#ifndef LIRC_ON_SA1100 + if (request_region(io, 8, LIRC_DRIVER_NAME) == NULL) { + pr_err("i/o port 0x%.4x already in use.\n", io); + return -EBUSY; + } +#endif + retval = request_irq(irq, sir_interrupt, 0, + LIRC_DRIVER_NAME, NULL); + if (retval < 0) { +# ifndef LIRC_ON_SA1100 + release_region(io, 8); +# endif + pr_err("IRQ %d already in use.\n", irq); + return retval; + } +#ifndef LIRC_ON_SA1100 + pr_info("I/O port 0x%.4x, IRQ %d.\n", io, irq); +#endif + + init_timer(&timerlist); + timerlist.function = sir_timeout; + timerlist.data = 0xabadcafe; + + return 0; +} + +static void drop_port(void) +{ + free_irq(irq, NULL); + del_timer_sync(&timerlist); +#ifndef LIRC_ON_SA1100 + release_region(io, 8); +#endif +} + +#ifdef LIRC_SIR_ACTISYS_ACT200L +/* Crystal/Cirrus CS8130 IR transceiver, used in Actisys Act200L dongle */ +/* some code borrowed from Linux IRDA driver */ + +/* Register 0: Control register #1 */ +#define ACT200L_REG0 0x00 +#define ACT200L_TXEN 0x01 /* Enable transmitter */ +#define ACT200L_RXEN 0x02 /* Enable receiver */ +#define ACT200L_ECHO 0x08 /* Echo control chars */ + +/* Register 1: Control register #2 */ +#define ACT200L_REG1 0x10 +#define ACT200L_LODB 0x01 /* Load new baud rate count value */ +#define ACT200L_WIDE 0x04 /* Expand the maximum allowable pulse */ + +/* Register 3: Transmit mode register #2 */ +#define ACT200L_REG3 0x30 +#define ACT200L_B0 0x01 /* DataBits, 0=6, 1=7, 2=8, 3=9(8P) */ +#define ACT200L_B1 0x02 /* DataBits, 0=6, 1=7, 2=8, 3=9(8P) */ +#define ACT200L_CHSY 0x04 /* StartBit Synced 0=bittime, 1=startbit */ + +/* Register 4: Output Power register */ +#define ACT200L_REG4 0x40 +#define ACT200L_OP0 0x01 /* Enable LED1C output */ +#define ACT200L_OP1 0x02 /* Enable LED2C output */ +#define ACT200L_BLKR 0x04 + +/* Register 5: Receive Mode register */ +#define ACT200L_REG5 0x50 +#define ACT200L_RWIDL 0x01 /* fixed 1.6us pulse mode */ + /*.. other various IRDA bit modes, and TV remote modes..*/ + +/* Register 6: Receive Sensitivity register #1 */ +#define ACT200L_REG6 0x60 +#define ACT200L_RS0 0x01 /* receive threshold bit 0 */ +#define ACT200L_RS1 0x02 /* receive threshold bit 1 */ + +/* Register 7: Receive Sensitivity register #2 */ +#define ACT200L_REG7 0x70 +#define ACT200L_ENPOS 0x04 /* Ignore the falling edge */ + +/* Register 8,9: Baud Rate Divider register #1,#2 */ +#define ACT200L_REG8 0x80 +#define ACT200L_REG9 0x90 + +#define ACT200L_2400 0x5f +#define ACT200L_9600 0x17 +#define ACT200L_19200 0x0b +#define ACT200L_38400 0x05 +#define ACT200L_57600 0x03 +#define ACT200L_115200 0x01 + +/* Register 13: Control register #3 */ +#define ACT200L_REG13 0xd0 +#define ACT200L_SHDW 0x01 /* Enable access to shadow registers */ + +/* Register 15: Status register */ +#define ACT200L_REG15 0xf0 + +/* Register 21: Control register #4 */ +#define ACT200L_REG21 0x50 +#define ACT200L_EXCK 0x02 /* Disable clock output driver */ +#define ACT200L_OSCL 0x04 /* oscillator in low power, medium accuracy mode */ + +static void init_act200(void) +{ + int i; + __u8 control[] = { + ACT200L_REG15, + ACT200L_REG13 | ACT200L_SHDW, + ACT200L_REG21 | ACT200L_EXCK | ACT200L_OSCL, + ACT200L_REG13, + ACT200L_REG7 | ACT200L_ENPOS, + ACT200L_REG6 | ACT200L_RS0 | ACT200L_RS1, + ACT200L_REG5 | ACT200L_RWIDL, + ACT200L_REG4 | ACT200L_OP0 | ACT200L_OP1 | ACT200L_BLKR, + ACT200L_REG3 | ACT200L_B0, + ACT200L_REG0 | ACT200L_TXEN | ACT200L_RXEN, + ACT200L_REG8 | (ACT200L_115200 & 0x0f), + ACT200L_REG9 | ((ACT200L_115200 >> 4) & 0x0f), + ACT200L_REG1 | ACT200L_LODB | ACT200L_WIDE + }; + + /* Set DLAB 1. */ + soutp(UART_LCR, UART_LCR_DLAB | UART_LCR_WLEN8); + + /* Set divisor to 12 => 9600 Baud */ + soutp(UART_DLM, 0); + soutp(UART_DLL, 12); + + /* Set DLAB 0. */ + soutp(UART_LCR, UART_LCR_WLEN8); + /* Set divisor to 12 => 9600 Baud */ + + /* power supply */ + soutp(UART_MCR, UART_MCR_RTS|UART_MCR_DTR|UART_MCR_OUT2); + for (i = 0; i < 50; i++) + safe_udelay(1000); + + /* Reset the dongle : set RTS low for 25 ms */ + soutp(UART_MCR, UART_MCR_DTR|UART_MCR_OUT2); + for (i = 0; i < 25; i++) + udelay(1000); + + soutp(UART_MCR, UART_MCR_RTS|UART_MCR_DTR|UART_MCR_OUT2); + udelay(100); + + /* Clear DTR and set RTS to enter command mode */ + soutp(UART_MCR, UART_MCR_RTS|UART_MCR_OUT2); + udelay(7); + + /* send out the control register settings for 115K 7N1 SIR operation */ + for (i = 0; i < sizeof(control); i++) { + soutp(UART_TX, control[i]); + /* one byte takes ~1042 usec to transmit at 9600,8N1 */ + udelay(1500); + } + + /* back to normal operation */ + soutp(UART_MCR, UART_MCR_RTS|UART_MCR_DTR|UART_MCR_OUT2); + udelay(50); + + udelay(1500); + soutp(UART_LCR, sinp(UART_LCR) | UART_LCR_DLAB); + + /* Set DLAB 1. */ + soutp(UART_LCR, UART_LCR_DLAB | UART_LCR_WLEN7); + + /* Set divisor to 1 => 115200 Baud */ + soutp(UART_DLM, 0); + soutp(UART_DLL, 1); + + /* Set DLAB 0. */ + soutp(UART_LCR, sinp(UART_LCR) & (~UART_LCR_DLAB)); + + /* Set DLAB 0, 7 Bit */ + soutp(UART_LCR, UART_LCR_WLEN7); + + /* enable interrupts */ + soutp(UART_IER, sinp(UART_IER)|UART_IER_RDI); +} +#endif + +#ifdef LIRC_SIR_ACTISYS_ACT220L +/* + * Derived from linux IrDA driver (net/irda/actisys.c) + * Drop me a mail for any kind of comment: maxx@spaceboyz.net + */ + +void init_act220(void) +{ + int i; + + /* DLAB 1 */ + soutp(UART_LCR, UART_LCR_DLAB|UART_LCR_WLEN7); + + /* 9600 baud */ + soutp(UART_DLM, 0); + soutp(UART_DLL, 12); + + /* DLAB 0 */ + soutp(UART_LCR, UART_LCR_WLEN7); + + /* reset the dongle, set DTR low for 10us */ + soutp(UART_MCR, UART_MCR_RTS|UART_MCR_OUT2); + udelay(10); + + /* back to normal (still 9600) */ + soutp(UART_MCR, UART_MCR_DTR|UART_MCR_RTS|UART_MCR_OUT2); + + /* + * send RTS pulses until we reach 115200 + * i hope this is really the same for act220l/act220l+ + */ + for (i = 0; i < 3; i++) { + udelay(10); + /* set RTS low for 10 us */ + soutp(UART_MCR, UART_MCR_DTR|UART_MCR_OUT2); + udelay(10); + /* set RTS high for 10 us */ + soutp(UART_MCR, UART_MCR_RTS|UART_MCR_DTR|UART_MCR_OUT2); + } + + /* back to normal operation */ + udelay(1500); /* better safe than sorry ;) */ + + /* Set DLAB 1. */ + soutp(UART_LCR, UART_LCR_DLAB | UART_LCR_WLEN7); + + /* Set divisor to 1 => 115200 Baud */ + soutp(UART_DLM, 0); + soutp(UART_DLL, 1); + + /* Set DLAB 0, 7 Bit */ + /* The dongle doesn't seem to have any problems with operation at 7N1 */ + soutp(UART_LCR, UART_LCR_WLEN7); + + /* enable interrupts */ + soutp(UART_IER, UART_IER_RDI); +} +#endif + +static int init_lirc_sir(void) +{ + int retval; + + init_waitqueue_head(&lirc_read_queue); + retval = init_port(); + if (retval < 0) + return retval; + init_hardware(); + pr_info("Installed.\n"); + return 0; +} + +static int lirc_sir_probe(struct platform_device *dev) +{ + return 0; +} + +static int lirc_sir_remove(struct platform_device *dev) +{ + return 0; +} + +static struct platform_driver lirc_sir_driver = { + .probe = lirc_sir_probe, + .remove = lirc_sir_remove, + .driver = { + .name = "lirc_sir", + .owner = THIS_MODULE, + }, +}; + +static int __init lirc_sir_init(void) +{ + int retval; + + retval = platform_driver_register(&lirc_sir_driver); + if (retval) { + pr_err("Platform driver register failed!\n"); + return -ENODEV; + } + + lirc_sir_dev = platform_device_alloc("lirc_dev", 0); + if (!lirc_sir_dev) { + pr_err("Platform device alloc failed!\n"); + retval = -ENOMEM; + goto pdev_alloc_fail; + } + + retval = platform_device_add(lirc_sir_dev); + if (retval) { + pr_err("Platform device add failed!\n"); + retval = -ENODEV; + goto pdev_add_fail; + } + + retval = init_chrdev(); + if (retval < 0) + goto fail; + + retval = init_lirc_sir(); + if (retval) { + drop_chrdev(); + goto fail; + } + + return 0; + +fail: + platform_device_del(lirc_sir_dev); +pdev_add_fail: + platform_device_put(lirc_sir_dev); +pdev_alloc_fail: + platform_driver_unregister(&lirc_sir_driver); + return retval; +} + +static void __exit lirc_sir_exit(void) +{ + drop_hardware(); + drop_chrdev(); + drop_port(); + platform_device_unregister(lirc_sir_dev); + platform_driver_unregister(&lirc_sir_driver); + pr_info("Uninstalled.\n"); +} + +module_init(lirc_sir_init); +module_exit(lirc_sir_exit); + +#ifdef LIRC_SIR_TEKRAM +MODULE_DESCRIPTION("Infrared receiver driver for Tekram Irmate 210"); +MODULE_AUTHOR("Christoph Bartelmus"); +#elif defined(LIRC_ON_SA1100) +MODULE_DESCRIPTION("LIRC driver for StrongARM SA1100 embedded microprocessor"); +MODULE_AUTHOR("Christoph Bartelmus"); +#elif defined(LIRC_SIR_ACTISYS_ACT200L) +MODULE_DESCRIPTION("LIRC driver for Actisys Act200L"); +MODULE_AUTHOR("Karl Bongers"); +#elif defined(LIRC_SIR_ACTISYS_ACT220L) +MODULE_DESCRIPTION("LIRC driver for Actisys Act220L(+)"); +MODULE_AUTHOR("Jan Roemisch"); +#else +MODULE_DESCRIPTION("Infrared receiver driver for SIR type serial ports"); +MODULE_AUTHOR("Milan Pikula"); +#endif +MODULE_LICENSE("GPL"); + +#ifdef LIRC_ON_SA1100 +module_param(irq, int, S_IRUGO); +MODULE_PARM_DESC(irq, "Interrupt (16)"); +#else +module_param(io, int, S_IRUGO); +MODULE_PARM_DESC(io, "I/O address base (0x3f8 or 0x2f8)"); + +module_param(irq, int, S_IRUGO); +MODULE_PARM_DESC(irq, "Interrupt (4 or 3)"); + +module_param(threshold, int, S_IRUGO); +MODULE_PARM_DESC(threshold, "space detection threshold (3)"); +#endif + +module_param(debug, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(debug, "Enable debugging messages"); diff --git a/drivers/staging/media/lirc/lirc_zilog.c b/drivers/staging/media/lirc/lirc_zilog.c new file mode 100644 index 00000000000..3259aacfd35 --- /dev/null +++ b/drivers/staging/media/lirc/lirc_zilog.c @@ -0,0 +1,1686 @@ +/* + * i2c IR lirc driver for devices with zilog IR processors + * + * Copyright (c) 2000 Gerd Knorr <kraxel@goldbach.in-berlin.de> + * modified for PixelView (BT878P+W/FM) by + * Michal Kochanowicz <mkochano@pld.org.pl> + * Christoph Bartelmus <lirc@bartelmus.de> + * modified for KNC ONE TV Station/Anubis Typhoon TView Tuner by + * Ulrich Mueller <ulrich.mueller42@web.de> + * modified for Asus TV-Box and Creative/VisionTek BreakOut-Box by + * Stefan Jahn <stefan@lkcc.org> + * modified for inclusion into kernel sources by + * Jerome Brock <jbrock@users.sourceforge.net> + * modified for Leadtek Winfast PVR2000 by + * Thomas Reitmayr (treitmayr@yahoo.com) + * modified for Hauppauge PVR-150 IR TX device by + * Mark Weaver <mark@npsl.co.uk> + * changed name from lirc_pvr150 to lirc_zilog, works on more than pvr-150 + * Jarod Wilson <jarod@redhat.com> + * + * parts are cut&pasted from the lirc_i2c.c driver + * + * Numerous changes updating lirc_zilog.c in kernel 2.6.38 and later are + * Copyright (C) 2011 Andy Walls <awalls@md.metrocast.net> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <linux/module.h> +#include <linux/kmod.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/fs.h> +#include <linux/poll.h> +#include <linux/string.h> +#include <linux/timer.h> +#include <linux/delay.h> +#include <linux/completion.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/firmware.h> +#include <linux/vmalloc.h> + +#include <linux/mutex.h> +#include <linux/kthread.h> + +#include <media/lirc_dev.h> +#include <media/lirc.h> + +/* Max transfer size done by I2C transfer functions */ +#define MAX_XFER_SIZE 64 + +struct IR; + +struct IR_rx { + struct kref ref; + struct IR *ir; + + /* RX device */ + struct mutex client_lock; + struct i2c_client *c; + + /* RX polling thread data */ + struct task_struct *task; + + /* RX read data */ + unsigned char b[3]; + bool hdpvr_data_fmt; +}; + +struct IR_tx { + struct kref ref; + struct IR *ir; + + /* TX device */ + struct mutex client_lock; + struct i2c_client *c; + + /* TX additional actions needed */ + int need_boot; + bool post_tx_ready_poll; +}; + +struct IR { + struct kref ref; + struct list_head list; + + /* FIXME spinlock access to l.features */ + struct lirc_driver l; + struct lirc_buffer rbuf; + + struct mutex ir_lock; + atomic_t open_count; + + struct i2c_adapter *adapter; + + spinlock_t rx_ref_lock; /* struct IR_rx kref get()/put() */ + struct IR_rx *rx; + + spinlock_t tx_ref_lock; /* struct IR_tx kref get()/put() */ + struct IR_tx *tx; +}; + +/* IR transceiver instance object list */ +/* + * This lock is used for the following: + * a. ir_devices_list access, insertions, deletions + * b. struct IR kref get()s and put()s + * c. serialization of ir_probe() for the two i2c_clients for a Z8 + */ +static DEFINE_MUTEX(ir_devices_lock); +static LIST_HEAD(ir_devices_list); + +/* Block size for IR transmitter */ +#define TX_BLOCK_SIZE 99 + +/* Hauppauge IR transmitter data */ +struct tx_data_struct { + /* Boot block */ + unsigned char *boot_data; + + /* Start of binary data block */ + unsigned char *datap; + + /* End of binary data block */ + unsigned char *endp; + + /* Number of installed codesets */ + unsigned int num_code_sets; + + /* Pointers to codesets */ + unsigned char **code_sets; + + /* Global fixed data template */ + int fixed[TX_BLOCK_SIZE]; +}; + +static struct tx_data_struct *tx_data; +static struct mutex tx_data_lock; + +#define zilog_notify(s, args...) printk(KERN_NOTICE KBUILD_MODNAME ": " s, \ + ## args) +#define zilog_error(s, args...) printk(KERN_ERR KBUILD_MODNAME ": " s, ## args) +#define zilog_info(s, args...) printk(KERN_INFO KBUILD_MODNAME ": " s, ## args) + +/* module parameters */ +static bool debug; /* debug output */ +static bool tx_only; /* only handle the IR Tx function */ +static int minor = -1; /* minor number */ + +#define dprintk(fmt, args...) \ + do { \ + if (debug) \ + printk(KERN_DEBUG KBUILD_MODNAME ": " fmt, \ + ## args); \ + } while (0) + + +/* struct IR reference counting */ +static struct IR *get_ir_device(struct IR *ir, bool ir_devices_lock_held) +{ + if (ir_devices_lock_held) { + kref_get(&ir->ref); + } else { + mutex_lock(&ir_devices_lock); + kref_get(&ir->ref); + mutex_unlock(&ir_devices_lock); + } + return ir; +} + +static void release_ir_device(struct kref *ref) +{ + struct IR *ir = container_of(ref, struct IR, ref); + + /* + * Things should be in this state by now: + * ir->rx set to NULL and deallocated - happens before ir->rx->ir put() + * ir->rx->task kthread stopped - happens before ir->rx->ir put() + * ir->tx set to NULL and deallocated - happens before ir->tx->ir put() + * ir->open_count == 0 - happens on final close() + * ir_lock, tx_ref_lock, rx_ref_lock, all released + */ + if (ir->l.minor >= 0 && ir->l.minor < MAX_IRCTL_DEVICES) { + lirc_unregister_driver(ir->l.minor); + ir->l.minor = MAX_IRCTL_DEVICES; + } + if (ir->rbuf.fifo_initialized) + lirc_buffer_free(&ir->rbuf); + list_del(&ir->list); + kfree(ir); +} + +static int put_ir_device(struct IR *ir, bool ir_devices_lock_held) +{ + int released; + + if (ir_devices_lock_held) + return kref_put(&ir->ref, release_ir_device); + + mutex_lock(&ir_devices_lock); + released = kref_put(&ir->ref, release_ir_device); + mutex_unlock(&ir_devices_lock); + + return released; +} + +/* struct IR_rx reference counting */ +static struct IR_rx *get_ir_rx(struct IR *ir) +{ + struct IR_rx *rx; + + spin_lock(&ir->rx_ref_lock); + rx = ir->rx; + if (rx != NULL) + kref_get(&rx->ref); + spin_unlock(&ir->rx_ref_lock); + return rx; +} + +static void destroy_rx_kthread(struct IR_rx *rx, bool ir_devices_lock_held) +{ + /* end up polling thread */ + if (!IS_ERR_OR_NULL(rx->task)) { + kthread_stop(rx->task); + rx->task = NULL; + /* Put the ir ptr that ir_probe() gave to the rx poll thread */ + put_ir_device(rx->ir, ir_devices_lock_held); + } +} + +static void release_ir_rx(struct kref *ref) +{ + struct IR_rx *rx = container_of(ref, struct IR_rx, ref); + struct IR *ir = rx->ir; + + /* + * This release function can't do all the work, as we want + * to keep the rx_ref_lock a spinlock, and killing the poll thread + * and releasing the ir reference can cause a sleep. That work is + * performed by put_ir_rx() + */ + ir->l.features &= ~LIRC_CAN_REC_LIRCCODE; + /* Don't put_ir_device(rx->ir) here; lock can't be freed yet */ + ir->rx = NULL; + /* Don't do the kfree(rx) here; we still need to kill the poll thread */ + return; +} + +static int put_ir_rx(struct IR_rx *rx, bool ir_devices_lock_held) +{ + int released; + struct IR *ir = rx->ir; + + spin_lock(&ir->rx_ref_lock); + released = kref_put(&rx->ref, release_ir_rx); + spin_unlock(&ir->rx_ref_lock); + /* Destroy the rx kthread while not holding the spinlock */ + if (released) { + destroy_rx_kthread(rx, ir_devices_lock_held); + kfree(rx); + /* Make sure we're not still in a poll_table somewhere */ + wake_up_interruptible(&ir->rbuf.wait_poll); + } + /* Do a reference put() for the rx->ir reference, if we released rx */ + if (released) + put_ir_device(ir, ir_devices_lock_held); + return released; +} + +/* struct IR_tx reference counting */ +static struct IR_tx *get_ir_tx(struct IR *ir) +{ + struct IR_tx *tx; + + spin_lock(&ir->tx_ref_lock); + tx = ir->tx; + if (tx != NULL) + kref_get(&tx->ref); + spin_unlock(&ir->tx_ref_lock); + return tx; +} + +static void release_ir_tx(struct kref *ref) +{ + struct IR_tx *tx = container_of(ref, struct IR_tx, ref); + struct IR *ir = tx->ir; + + ir->l.features &= ~LIRC_CAN_SEND_PULSE; + /* Don't put_ir_device(tx->ir) here, so our lock doesn't get freed */ + ir->tx = NULL; + kfree(tx); +} + +static int put_ir_tx(struct IR_tx *tx, bool ir_devices_lock_held) +{ + int released; + struct IR *ir = tx->ir; + + spin_lock(&ir->tx_ref_lock); + released = kref_put(&tx->ref, release_ir_tx); + spin_unlock(&ir->tx_ref_lock); + /* Do a reference put() for the tx->ir reference, if we released tx */ + if (released) + put_ir_device(ir, ir_devices_lock_held); + return released; +} + +static int add_to_buf(struct IR *ir) +{ + __u16 code; + unsigned char codes[2]; + unsigned char keybuf[6]; + int got_data = 0; + int ret; + int failures = 0; + unsigned char sendbuf[1] = { 0 }; + struct lirc_buffer *rbuf = ir->l.rbuf; + struct IR_rx *rx; + struct IR_tx *tx; + + if (lirc_buffer_full(rbuf)) { + dprintk("buffer overflow\n"); + return -EOVERFLOW; + } + + rx = get_ir_rx(ir); + if (rx == NULL) + return -ENXIO; + + /* Ensure our rx->c i2c_client remains valid for the duration */ + mutex_lock(&rx->client_lock); + if (rx->c == NULL) { + mutex_unlock(&rx->client_lock); + put_ir_rx(rx, false); + return -ENXIO; + } + + tx = get_ir_tx(ir); + + /* + * service the device as long as it is returning + * data and we have space + */ + do { + if (kthread_should_stop()) { + ret = -ENODATA; + break; + } + + /* + * Lock i2c bus for the duration. RX/TX chips interfere so + * this is worth it + */ + mutex_lock(&ir->ir_lock); + + if (kthread_should_stop()) { + mutex_unlock(&ir->ir_lock); + ret = -ENODATA; + break; + } + + /* + * Send random "poll command" (?) Windows driver does this + * and it is a good point to detect chip failure. + */ + ret = i2c_master_send(rx->c, sendbuf, 1); + if (ret != 1) { + zilog_error("i2c_master_send failed with %d\n", ret); + if (failures >= 3) { + mutex_unlock(&ir->ir_lock); + zilog_error("unable to read from the IR chip " + "after 3 resets, giving up\n"); + break; + } + + /* Looks like the chip crashed, reset it */ + zilog_error("polling the IR receiver chip failed, " + "trying reset\n"); + + set_current_state(TASK_UNINTERRUPTIBLE); + if (kthread_should_stop()) { + mutex_unlock(&ir->ir_lock); + ret = -ENODATA; + break; + } + schedule_timeout((100 * HZ + 999) / 1000); + if (tx != NULL) + tx->need_boot = 1; + + ++failures; + mutex_unlock(&ir->ir_lock); + ret = 0; + continue; + } + + if (kthread_should_stop()) { + mutex_unlock(&ir->ir_lock); + ret = -ENODATA; + break; + } + ret = i2c_master_recv(rx->c, keybuf, sizeof(keybuf)); + mutex_unlock(&ir->ir_lock); + if (ret != sizeof(keybuf)) { + zilog_error("i2c_master_recv failed with %d -- " + "keeping last read buffer\n", ret); + } else { + rx->b[0] = keybuf[3]; + rx->b[1] = keybuf[4]; + rx->b[2] = keybuf[5]; + dprintk("key (0x%02x/0x%02x)\n", rx->b[0], rx->b[1]); + } + + /* key pressed ? */ + if (rx->hdpvr_data_fmt) { + if (got_data && (keybuf[0] == 0x80)) { + ret = 0; + break; + } else if (got_data && (keybuf[0] == 0x00)) { + ret = -ENODATA; + break; + } + } else if ((rx->b[0] & 0x80) == 0) { + ret = got_data ? 0 : -ENODATA; + break; + } + + /* look what we have */ + code = (((__u16)rx->b[0] & 0x7f) << 6) | (rx->b[1] >> 2); + + codes[0] = (code >> 8) & 0xff; + codes[1] = code & 0xff; + + /* return it */ + lirc_buffer_write(rbuf, codes); + ++got_data; + ret = 0; + } while (!lirc_buffer_full(rbuf)); + + mutex_unlock(&rx->client_lock); + if (tx != NULL) + put_ir_tx(tx, false); + put_ir_rx(rx, false); + return ret; +} + +/* + * Main function of the polling thread -- from lirc_dev. + * We don't fit the LIRC model at all anymore. This is horrible, but + * basically we have a single RX/TX device with a nasty failure mode + * that needs to be accounted for across the pair. lirc lets us provide + * fops, but prevents us from using the internal polling, etc. if we do + * so. Hence the replication. Might be neater to extend the LIRC model + * to account for this but I'd think it's a very special case of seriously + * messed up hardware. + */ +static int lirc_thread(void *arg) +{ + struct IR *ir = arg; + struct lirc_buffer *rbuf = ir->l.rbuf; + + dprintk("poll thread started\n"); + + while (!kthread_should_stop()) { + set_current_state(TASK_INTERRUPTIBLE); + + /* if device not opened, we can sleep half a second */ + if (atomic_read(&ir->open_count) == 0) { + schedule_timeout(HZ/2); + continue; + } + + /* + * This is ~113*2 + 24 + jitter (2*repeat gap + code length). + * We use this interval as the chip resets every time you poll + * it (bad!). This is therefore just sufficient to catch all + * of the button presses. It makes the remote much more + * responsive. You can see the difference by running irw and + * holding down a button. With 100ms, the old polling + * interval, you'll notice breaks in the repeat sequence + * corresponding to lost keypresses. + */ + schedule_timeout((260 * HZ) / 1000); + if (kthread_should_stop()) + break; + if (!add_to_buf(ir)) + wake_up_interruptible(&rbuf->wait_poll); + } + + dprintk("poll thread ended\n"); + return 0; +} + +static int set_use_inc(void *data) +{ + return 0; +} + +static void set_use_dec(void *data) +{ + return; +} + +/* safe read of a uint32 (always network byte order) */ +static int read_uint32(unsigned char **data, + unsigned char *endp, unsigned int *val) +{ + if (*data + 4 > endp) + return 0; + *val = ((*data)[0] << 24) | ((*data)[1] << 16) | + ((*data)[2] << 8) | (*data)[3]; + *data += 4; + return 1; +} + +/* safe read of a uint8 */ +static int read_uint8(unsigned char **data, + unsigned char *endp, unsigned char *val) +{ + if (*data + 1 > endp) + return 0; + *val = *((*data)++); + return 1; +} + +/* safe skipping of N bytes */ +static int skip(unsigned char **data, + unsigned char *endp, unsigned int distance) +{ + if (*data + distance > endp) + return 0; + *data += distance; + return 1; +} + +/* decompress key data into the given buffer */ +static int get_key_data(unsigned char *buf, + unsigned int codeset, unsigned int key) +{ + unsigned char *data, *endp, *diffs, *key_block; + unsigned char keys, ndiffs, id; + unsigned int base, lim, pos, i; + + /* Binary search for the codeset */ + for (base = 0, lim = tx_data->num_code_sets; lim; lim >>= 1) { + pos = base + (lim >> 1); + data = tx_data->code_sets[pos]; + + if (!read_uint32(&data, tx_data->endp, &i)) + goto corrupt; + + if (i == codeset) + break; + else if (codeset > i) { + base = pos + 1; + --lim; + } + } + /* Not found? */ + if (!lim) + return -EPROTO; + + /* Set end of data block */ + endp = pos < tx_data->num_code_sets - 1 ? + tx_data->code_sets[pos + 1] : tx_data->endp; + + /* Read the block header */ + if (!read_uint8(&data, endp, &keys) || + !read_uint8(&data, endp, &ndiffs) || + ndiffs > TX_BLOCK_SIZE || keys == 0) + goto corrupt; + + /* Save diffs & skip */ + diffs = data; + if (!skip(&data, endp, ndiffs)) + goto corrupt; + + /* Read the id of the first key */ + if (!read_uint8(&data, endp, &id)) + goto corrupt; + + /* Unpack the first key's data */ + for (i = 0; i < TX_BLOCK_SIZE; ++i) { + if (tx_data->fixed[i] == -1) { + if (!read_uint8(&data, endp, &buf[i])) + goto corrupt; + } else { + buf[i] = (unsigned char)tx_data->fixed[i]; + } + } + + /* Early out key found/not found */ + if (key == id) + return 0; + if (keys == 1) + return -EPROTO; + + /* Sanity check */ + key_block = data; + if (!skip(&data, endp, (keys - 1) * (ndiffs + 1))) + goto corrupt; + + /* Binary search for the key */ + for (base = 0, lim = keys - 1; lim; lim >>= 1) { + /* Seek to block */ + unsigned char *key_data; + pos = base + (lim >> 1); + key_data = key_block + (ndiffs + 1) * pos; + + if (*key_data == key) { + /* skip key id */ + ++key_data; + + /* found, so unpack the diffs */ + for (i = 0; i < ndiffs; ++i) { + unsigned char val; + if (!read_uint8(&key_data, endp, &val) || + diffs[i] >= TX_BLOCK_SIZE) + goto corrupt; + buf[diffs[i]] = val; + } + + return 0; + } else if (key > *key_data) { + base = pos + 1; + --lim; + } + } + /* Key not found */ + return -EPROTO; + +corrupt: + zilog_error("firmware is corrupt\n"); + return -EFAULT; +} + +/* send a block of data to the IR TX device */ +static int send_data_block(struct IR_tx *tx, unsigned char *data_block) +{ + int i, j, ret; + unsigned char buf[5]; + + for (i = 0; i < TX_BLOCK_SIZE;) { + int tosend = TX_BLOCK_SIZE - i; + if (tosend > 4) + tosend = 4; + buf[0] = (unsigned char)(i + 1); + for (j = 0; j < tosend; ++j) + buf[1 + j] = data_block[i + j]; + dprintk("%*ph", 5, buf); + ret = i2c_master_send(tx->c, buf, tosend + 1); + if (ret != tosend + 1) { + zilog_error("i2c_master_send failed with %d\n", ret); + return ret < 0 ? ret : -EFAULT; + } + i += tosend; + } + return 0; +} + +/* send boot data to the IR TX device */ +static int send_boot_data(struct IR_tx *tx) +{ + int ret, i; + unsigned char buf[4]; + + /* send the boot block */ + ret = send_data_block(tx, tx_data->boot_data); + if (ret != 0) + return ret; + + /* Hit the go button to activate the new boot data */ + buf[0] = 0x00; + buf[1] = 0x20; + ret = i2c_master_send(tx->c, buf, 2); + if (ret != 2) { + zilog_error("i2c_master_send failed with %d\n", ret); + return ret < 0 ? ret : -EFAULT; + } + + /* + * Wait for zilog to settle after hitting go post boot block upload. + * Without this delay, the HD-PVR and HVR-1950 both return an -EIO + * upon attempting to get firmware revision, and tx probe thus fails. + */ + for (i = 0; i < 10; i++) { + ret = i2c_master_send(tx->c, buf, 1); + if (ret == 1) + break; + udelay(100); + } + + if (ret != 1) { + zilog_error("i2c_master_send failed with %d\n", ret); + return ret < 0 ? ret : -EFAULT; + } + + /* Here comes the firmware version... (hopefully) */ + ret = i2c_master_recv(tx->c, buf, 4); + if (ret != 4) { + zilog_error("i2c_master_recv failed with %d\n", ret); + return 0; + } + if ((buf[0] != 0x80) && (buf[0] != 0xa0)) { + zilog_error("unexpected IR TX init response: %02x\n", buf[0]); + return 0; + } + zilog_notify("Zilog/Hauppauge IR blaster firmware version " + "%d.%d.%d loaded\n", buf[1], buf[2], buf[3]); + + return 0; +} + +/* unload "firmware", lock held */ +static void fw_unload_locked(void) +{ + if (tx_data) { + if (tx_data->code_sets) + vfree(tx_data->code_sets); + + if (tx_data->datap) + vfree(tx_data->datap); + + vfree(tx_data); + tx_data = NULL; + dprintk("successfully unloaded IR blaster firmware\n"); + } +} + +/* unload "firmware" for the IR TX device */ +static void fw_unload(void) +{ + mutex_lock(&tx_data_lock); + fw_unload_locked(); + mutex_unlock(&tx_data_lock); +} + +/* load "firmware" for the IR TX device */ +static int fw_load(struct IR_tx *tx) +{ + int ret; + unsigned int i; + unsigned char *data, version, num_global_fixed; + const struct firmware *fw_entry; + + /* Already loaded? */ + mutex_lock(&tx_data_lock); + if (tx_data) { + ret = 0; + goto out; + } + + /* Request codeset data file */ + ret = request_firmware(&fw_entry, "haup-ir-blaster.bin", tx->ir->l.dev); + if (ret != 0) { + zilog_error("firmware haup-ir-blaster.bin not available (%d)\n", + ret); + ret = ret < 0 ? ret : -EFAULT; + goto out; + } + dprintk("firmware of size %zu loaded\n", fw_entry->size); + + /* Parse the file */ + tx_data = vmalloc(sizeof(*tx_data)); + if (tx_data == NULL) { + zilog_error("out of memory\n"); + release_firmware(fw_entry); + ret = -ENOMEM; + goto out; + } + tx_data->code_sets = NULL; + + /* Copy the data so hotplug doesn't get confused and timeout */ + tx_data->datap = vmalloc(fw_entry->size); + if (tx_data->datap == NULL) { + zilog_error("out of memory\n"); + release_firmware(fw_entry); + vfree(tx_data); + ret = -ENOMEM; + goto out; + } + memcpy(tx_data->datap, fw_entry->data, fw_entry->size); + tx_data->endp = tx_data->datap + fw_entry->size; + release_firmware(fw_entry); fw_entry = NULL; + + /* Check version */ + data = tx_data->datap; + if (!read_uint8(&data, tx_data->endp, &version)) + goto corrupt; + if (version != 1) { + zilog_error("unsupported code set file version (%u, expected" + "1) -- please upgrade to a newer driver", + version); + fw_unload_locked(); + ret = -EFAULT; + goto out; + } + + /* Save boot block for later */ + tx_data->boot_data = data; + if (!skip(&data, tx_data->endp, TX_BLOCK_SIZE)) + goto corrupt; + + if (!read_uint32(&data, tx_data->endp, + &tx_data->num_code_sets)) + goto corrupt; + + dprintk("%u IR blaster codesets loaded\n", tx_data->num_code_sets); + + tx_data->code_sets = vmalloc( + tx_data->num_code_sets * sizeof(char *)); + if (tx_data->code_sets == NULL) { + fw_unload_locked(); + ret = -ENOMEM; + goto out; + } + + for (i = 0; i < TX_BLOCK_SIZE; ++i) + tx_data->fixed[i] = -1; + + /* Read global fixed data template */ + if (!read_uint8(&data, tx_data->endp, &num_global_fixed) || + num_global_fixed > TX_BLOCK_SIZE) + goto corrupt; + for (i = 0; i < num_global_fixed; ++i) { + unsigned char pos, val; + if (!read_uint8(&data, tx_data->endp, &pos) || + !read_uint8(&data, tx_data->endp, &val) || + pos >= TX_BLOCK_SIZE) + goto corrupt; + tx_data->fixed[pos] = (int)val; + } + + /* Filch out the position of each code set */ + for (i = 0; i < tx_data->num_code_sets; ++i) { + unsigned int id; + unsigned char keys; + unsigned char ndiffs; + + /* Save the codeset position */ + tx_data->code_sets[i] = data; + + /* Read header */ + if (!read_uint32(&data, tx_data->endp, &id) || + !read_uint8(&data, tx_data->endp, &keys) || + !read_uint8(&data, tx_data->endp, &ndiffs) || + ndiffs > TX_BLOCK_SIZE || keys == 0) + goto corrupt; + + /* skip diff positions */ + if (!skip(&data, tx_data->endp, ndiffs)) + goto corrupt; + + /* + * After the diffs we have the first key id + data - + * global fixed + */ + if (!skip(&data, tx_data->endp, + 1 + TX_BLOCK_SIZE - num_global_fixed)) + goto corrupt; + + /* Then we have keys-1 blocks of key id+diffs */ + if (!skip(&data, tx_data->endp, + (ndiffs + 1) * (keys - 1))) + goto corrupt; + } + ret = 0; + goto out; + +corrupt: + zilog_error("firmware is corrupt\n"); + fw_unload_locked(); + ret = -EFAULT; + +out: + mutex_unlock(&tx_data_lock); + return ret; +} + +/* copied from lirc_dev */ +static ssize_t read(struct file *filep, char __user *outbuf, size_t n, + loff_t *ppos) +{ + struct IR *ir = filep->private_data; + struct IR_rx *rx; + struct lirc_buffer *rbuf = ir->l.rbuf; + int ret = 0, written = 0, retries = 0; + unsigned int m; + DECLARE_WAITQUEUE(wait, current); + + dprintk("read called\n"); + if (n % rbuf->chunk_size) { + dprintk("read result = -EINVAL\n"); + return -EINVAL; + } + + rx = get_ir_rx(ir); + if (rx == NULL) + return -ENXIO; + + /* + * we add ourselves to the task queue before buffer check + * to avoid losing scan code (in case when queue is awaken somewhere + * between while condition checking and scheduling) + */ + add_wait_queue(&rbuf->wait_poll, &wait); + set_current_state(TASK_INTERRUPTIBLE); + + /* + * while we didn't provide 'length' bytes, device is opened in blocking + * mode and 'copy_to_user' is happy, wait for data. + */ + while (written < n && ret == 0) { + if (lirc_buffer_empty(rbuf)) { + /* + * According to the read(2) man page, 'written' can be + * returned as less than 'n', instead of blocking + * again, returning -EWOULDBLOCK, or returning + * -ERESTARTSYS + */ + if (written) + break; + if (filep->f_flags & O_NONBLOCK) { + ret = -EWOULDBLOCK; + break; + } + if (signal_pending(current)) { + ret = -ERESTARTSYS; + break; + } + schedule(); + set_current_state(TASK_INTERRUPTIBLE); + } else { + unsigned char buf[MAX_XFER_SIZE]; + + if (rbuf->chunk_size > sizeof(buf)) { + zilog_error("chunk_size is too big (%d)!\n", + rbuf->chunk_size); + ret = -EINVAL; + break; + } + m = lirc_buffer_read(rbuf, buf); + if (m == rbuf->chunk_size) { + ret = copy_to_user(outbuf + written, buf, + rbuf->chunk_size); + written += rbuf->chunk_size; + } else { + retries++; + } + if (retries >= 5) { + zilog_error("Buffer read failed!\n"); + ret = -EIO; + } + } + } + + remove_wait_queue(&rbuf->wait_poll, &wait); + put_ir_rx(rx, false); + set_current_state(TASK_RUNNING); + + dprintk("read result = %d (%s)\n", ret, ret ? "Error" : "OK"); + + return ret ? ret : written; +} + +/* send a keypress to the IR TX device */ +static int send_code(struct IR_tx *tx, unsigned int code, unsigned int key) +{ + unsigned char data_block[TX_BLOCK_SIZE]; + unsigned char buf[2]; + int i, ret; + + /* Get data for the codeset/key */ + ret = get_key_data(data_block, code, key); + + if (ret == -EPROTO) { + zilog_error("failed to get data for code %u, key %u -- check " + "lircd.conf entries\n", code, key); + return ret; + } else if (ret != 0) + return ret; + + /* Send the data block */ + ret = send_data_block(tx, data_block); + if (ret != 0) + return ret; + + /* Send data block length? */ + buf[0] = 0x00; + buf[1] = 0x40; + ret = i2c_master_send(tx->c, buf, 2); + if (ret != 2) { + zilog_error("i2c_master_send failed with %d\n", ret); + return ret < 0 ? ret : -EFAULT; + } + + /* Give the z8 a moment to process data block */ + for (i = 0; i < 10; i++) { + ret = i2c_master_send(tx->c, buf, 1); + if (ret == 1) + break; + udelay(100); + } + + if (ret != 1) { + zilog_error("i2c_master_send failed with %d\n", ret); + return ret < 0 ? ret : -EFAULT; + } + + /* Send finished download? */ + ret = i2c_master_recv(tx->c, buf, 1); + if (ret != 1) { + zilog_error("i2c_master_recv failed with %d\n", ret); + return ret < 0 ? ret : -EFAULT; + } + if (buf[0] != 0xA0) { + zilog_error("unexpected IR TX response #1: %02x\n", + buf[0]); + return -EFAULT; + } + + /* Send prepare command? */ + buf[0] = 0x00; + buf[1] = 0x80; + ret = i2c_master_send(tx->c, buf, 2); + if (ret != 2) { + zilog_error("i2c_master_send failed with %d\n", ret); + return ret < 0 ? ret : -EFAULT; + } + + /* + * The sleep bits aren't necessary on the HD PVR, and in fact, the + * last i2c_master_recv always fails with a -5, so for now, we're + * going to skip this whole mess and say we're done on the HD PVR + */ + if (!tx->post_tx_ready_poll) { + dprintk("sent code %u, key %u\n", code, key); + return 0; + } + + /* + * This bit NAKs until the device is ready, so we retry it + * sleeping a bit each time. This seems to be what the windows + * driver does, approximately. + * Try for up to 1s. + */ + for (i = 0; i < 20; ++i) { + set_current_state(TASK_UNINTERRUPTIBLE); + schedule_timeout((50 * HZ + 999) / 1000); + ret = i2c_master_send(tx->c, buf, 1); + if (ret == 1) + break; + dprintk("NAK expected: i2c_master_send " + "failed with %d (try %d)\n", ret, i+1); + } + if (ret != 1) { + zilog_error("IR TX chip never got ready: last i2c_master_send " + "failed with %d\n", ret); + return ret < 0 ? ret : -EFAULT; + } + + /* Seems to be an 'ok' response */ + i = i2c_master_recv(tx->c, buf, 1); + if (i != 1) { + zilog_error("i2c_master_recv failed with %d\n", ret); + return -EFAULT; + } + if (buf[0] != 0x80) { + zilog_error("unexpected IR TX response #2: %02x\n", buf[0]); + return -EFAULT; + } + + /* Oh good, it worked */ + dprintk("sent code %u, key %u\n", code, key); + return 0; +} + +/* + * Write a code to the device. We take in a 32-bit number (an int) and then + * decode this to a codeset/key index. The key data is then decompressed and + * sent to the device. We have a spin lock as per i2c documentation to prevent + * multiple concurrent sends which would probably cause the device to explode. + */ +static ssize_t write(struct file *filep, const char __user *buf, size_t n, + loff_t *ppos) +{ + struct IR *ir = filep->private_data; + struct IR_tx *tx; + size_t i; + int failures = 0; + + /* Validate user parameters */ + if (n % sizeof(int)) + return -EINVAL; + + /* Get a struct IR_tx reference */ + tx = get_ir_tx(ir); + if (tx == NULL) + return -ENXIO; + + /* Ensure our tx->c i2c_client remains valid for the duration */ + mutex_lock(&tx->client_lock); + if (tx->c == NULL) { + mutex_unlock(&tx->client_lock); + put_ir_tx(tx, false); + return -ENXIO; + } + + /* Lock i2c bus for the duration */ + mutex_lock(&ir->ir_lock); + + /* Send each keypress */ + for (i = 0; i < n;) { + int ret = 0; + int command; + + if (copy_from_user(&command, buf + i, sizeof(command))) { + mutex_unlock(&ir->ir_lock); + mutex_unlock(&tx->client_lock); + put_ir_tx(tx, false); + return -EFAULT; + } + + /* Send boot data first if required */ + if (tx->need_boot == 1) { + /* Make sure we have the 'firmware' loaded, first */ + ret = fw_load(tx); + if (ret != 0) { + mutex_unlock(&ir->ir_lock); + mutex_unlock(&tx->client_lock); + put_ir_tx(tx, false); + if (ret != -ENOMEM) + ret = -EIO; + return ret; + } + /* Prep the chip for transmitting codes */ + ret = send_boot_data(tx); + if (ret == 0) + tx->need_boot = 0; + } + + /* Send the code */ + if (ret == 0) { + ret = send_code(tx, (unsigned)command >> 16, + (unsigned)command & 0xFFFF); + if (ret == -EPROTO) { + mutex_unlock(&ir->ir_lock); + mutex_unlock(&tx->client_lock); + put_ir_tx(tx, false); + return ret; + } + } + + /* + * Hmm, a failure. If we've had a few then give up, otherwise + * try a reset + */ + if (ret != 0) { + /* Looks like the chip crashed, reset it */ + zilog_error("sending to the IR transmitter chip " + "failed, trying reset\n"); + + if (failures >= 3) { + zilog_error("unable to send to the IR chip " + "after 3 resets, giving up\n"); + mutex_unlock(&ir->ir_lock); + mutex_unlock(&tx->client_lock); + put_ir_tx(tx, false); + return ret; + } + set_current_state(TASK_UNINTERRUPTIBLE); + schedule_timeout((100 * HZ + 999) / 1000); + tx->need_boot = 1; + ++failures; + } else + i += sizeof(int); + } + + /* Release i2c bus */ + mutex_unlock(&ir->ir_lock); + + mutex_unlock(&tx->client_lock); + + /* Give back our struct IR_tx reference */ + put_ir_tx(tx, false); + + /* All looks good */ + return n; +} + +/* copied from lirc_dev */ +static unsigned int poll(struct file *filep, poll_table *wait) +{ + struct IR *ir = filep->private_data; + struct IR_rx *rx; + struct lirc_buffer *rbuf = ir->l.rbuf; + unsigned int ret; + + dprintk("poll called\n"); + + rx = get_ir_rx(ir); + if (rx == NULL) { + /* + * Revisit this, if our poll function ever reports writeable + * status for Tx + */ + dprintk("poll result = POLLERR\n"); + return POLLERR; + } + + /* + * Add our lirc_buffer's wait_queue to the poll_table. A wake up on + * that buffer's wait queue indicates we may have a new poll status. + */ + poll_wait(filep, &rbuf->wait_poll, wait); + + /* Indicate what ops could happen immediately without blocking */ + ret = lirc_buffer_empty(rbuf) ? 0 : (POLLIN|POLLRDNORM); + + dprintk("poll result = %s\n", ret ? "POLLIN|POLLRDNORM" : "none"); + return ret; +} + +static long ioctl(struct file *filep, unsigned int cmd, unsigned long arg) +{ + struct IR *ir = filep->private_data; + unsigned long __user *uptr = (unsigned long __user *)arg; + int result; + unsigned long mode, features; + + features = ir->l.features; + + switch (cmd) { + case LIRC_GET_LENGTH: + result = put_user(13UL, uptr); + break; + case LIRC_GET_FEATURES: + result = put_user(features, uptr); + break; + case LIRC_GET_REC_MODE: + if (!(features&LIRC_CAN_REC_MASK)) + return -ENOSYS; + + result = put_user(LIRC_REC2MODE + (features&LIRC_CAN_REC_MASK), + uptr); + break; + case LIRC_SET_REC_MODE: + if (!(features&LIRC_CAN_REC_MASK)) + return -ENOSYS; + + result = get_user(mode, uptr); + if (!result && !(LIRC_MODE2REC(mode) & features)) + result = -EINVAL; + break; + case LIRC_GET_SEND_MODE: + if (!(features&LIRC_CAN_SEND_MASK)) + return -ENOSYS; + + result = put_user(LIRC_MODE_PULSE, uptr); + break; + case LIRC_SET_SEND_MODE: + if (!(features&LIRC_CAN_SEND_MASK)) + return -ENOSYS; + + result = get_user(mode, uptr); + if (!result && mode != LIRC_MODE_PULSE) + return -EINVAL; + break; + default: + return -EINVAL; + } + return result; +} + +static struct IR *get_ir_device_by_minor(unsigned int minor) +{ + struct IR *ir; + struct IR *ret = NULL; + + mutex_lock(&ir_devices_lock); + + if (!list_empty(&ir_devices_list)) { + list_for_each_entry(ir, &ir_devices_list, list) { + if (ir->l.minor == minor) { + ret = get_ir_device(ir, true); + break; + } + } + } + + mutex_unlock(&ir_devices_lock); + return ret; +} + +/* + * Open the IR device. Get hold of our IR structure and + * stash it in private_data for the file + */ +static int open(struct inode *node, struct file *filep) +{ + struct IR *ir; + unsigned int minor = MINOR(node->i_rdev); + + /* find our IR struct */ + ir = get_ir_device_by_minor(minor); + + if (ir == NULL) + return -ENODEV; + + atomic_inc(&ir->open_count); + + /* stash our IR struct */ + filep->private_data = ir; + + nonseekable_open(node, filep); + return 0; +} + +/* Close the IR device */ +static int close(struct inode *node, struct file *filep) +{ + /* find our IR struct */ + struct IR *ir = filep->private_data; + if (ir == NULL) { + zilog_error("close: no private_data attached to the file!\n"); + return -ENODEV; + } + + atomic_dec(&ir->open_count); + + put_ir_device(ir, false); + return 0; +} + +static int ir_remove(struct i2c_client *client); +static int ir_probe(struct i2c_client *client, const struct i2c_device_id *id); + +#define ID_FLAG_TX 0x01 +#define ID_FLAG_HDPVR 0x02 + +static const struct i2c_device_id ir_transceiver_id[] = { + { "ir_tx_z8f0811_haup", ID_FLAG_TX }, + { "ir_rx_z8f0811_haup", 0 }, + { "ir_tx_z8f0811_hdpvr", ID_FLAG_HDPVR | ID_FLAG_TX }, + { "ir_rx_z8f0811_hdpvr", ID_FLAG_HDPVR }, + { } +}; + +static struct i2c_driver driver = { + .driver = { + .owner = THIS_MODULE, + .name = "Zilog/Hauppauge i2c IR", + }, + .probe = ir_probe, + .remove = ir_remove, + .id_table = ir_transceiver_id, +}; + +static const struct file_operations lirc_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .read = read, + .write = write, + .poll = poll, + .unlocked_ioctl = ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = ioctl, +#endif + .open = open, + .release = close +}; + +static struct lirc_driver lirc_template = { + .name = "lirc_zilog", + .minor = -1, + .code_length = 13, + .buffer_size = BUFLEN / 2, + .sample_rate = 0, /* tell lirc_dev to not start its own kthread */ + .chunk_size = 2, + .set_use_inc = set_use_inc, + .set_use_dec = set_use_dec, + .fops = &lirc_fops, + .owner = THIS_MODULE, +}; + +static int ir_remove(struct i2c_client *client) +{ + if (strncmp("ir_tx_z8", client->name, 8) == 0) { + struct IR_tx *tx = i2c_get_clientdata(client); + if (tx != NULL) { + mutex_lock(&tx->client_lock); + tx->c = NULL; + mutex_unlock(&tx->client_lock); + put_ir_tx(tx, false); + } + } else if (strncmp("ir_rx_z8", client->name, 8) == 0) { + struct IR_rx *rx = i2c_get_clientdata(client); + if (rx != NULL) { + mutex_lock(&rx->client_lock); + rx->c = NULL; + mutex_unlock(&rx->client_lock); + put_ir_rx(rx, false); + } + } + return 0; +} + + +/* ir_devices_lock must be held */ +static struct IR *get_ir_device_by_adapter(struct i2c_adapter *adapter) +{ + struct IR *ir; + + if (list_empty(&ir_devices_list)) + return NULL; + + list_for_each_entry(ir, &ir_devices_list, list) + if (ir->adapter == adapter) { + get_ir_device(ir, true); + return ir; + } + + return NULL; +} + +static int ir_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + struct IR *ir; + struct IR_tx *tx; + struct IR_rx *rx; + struct i2c_adapter *adap = client->adapter; + int ret; + bool tx_probe = false; + + dprintk("%s: %s on i2c-%d (%s), client addr=0x%02x\n", + __func__, id->name, adap->nr, adap->name, client->addr); + + /* + * The IR receiver is at i2c address 0x71. + * The IR transmitter is at i2c address 0x70. + */ + + if (id->driver_data & ID_FLAG_TX) + tx_probe = true; + else if (tx_only) /* module option */ + return -ENXIO; + + zilog_info("probing IR %s on %s (i2c-%d)\n", + tx_probe ? "Tx" : "Rx", adap->name, adap->nr); + + mutex_lock(&ir_devices_lock); + + /* Use a single struct IR instance for both the Rx and Tx functions */ + ir = get_ir_device_by_adapter(adap); + if (ir == NULL) { + ir = kzalloc(sizeof(struct IR), GFP_KERNEL); + if (ir == NULL) { + ret = -ENOMEM; + goto out_no_ir; + } + kref_init(&ir->ref); + + /* store for use in ir_probe() again, and open() later on */ + INIT_LIST_HEAD(&ir->list); + list_add_tail(&ir->list, &ir_devices_list); + + ir->adapter = adap; + mutex_init(&ir->ir_lock); + atomic_set(&ir->open_count, 0); + spin_lock_init(&ir->tx_ref_lock); + spin_lock_init(&ir->rx_ref_lock); + + /* set lirc_dev stuff */ + memcpy(&ir->l, &lirc_template, sizeof(struct lirc_driver)); + /* + * FIXME this is a pointer reference to us, but no refcount. + * + * This OK for now, since lirc_dev currently won't touch this + * buffer as we provide our own lirc_fops. + * + * Currently our own lirc_fops rely on this ir->l.rbuf pointer + */ + ir->l.rbuf = &ir->rbuf; + ir->l.dev = &adap->dev; + ret = lirc_buffer_init(ir->l.rbuf, + ir->l.chunk_size, ir->l.buffer_size); + if (ret) + goto out_put_ir; + } + + if (tx_probe) { + /* Get the IR_rx instance for later, if already allocated */ + rx = get_ir_rx(ir); + + /* Set up a struct IR_tx instance */ + tx = kzalloc(sizeof(struct IR_tx), GFP_KERNEL); + if (tx == NULL) { + ret = -ENOMEM; + goto out_put_xx; + } + kref_init(&tx->ref); + ir->tx = tx; + + ir->l.features |= LIRC_CAN_SEND_PULSE; + mutex_init(&tx->client_lock); + tx->c = client; + tx->need_boot = 1; + tx->post_tx_ready_poll = + (id->driver_data & ID_FLAG_HDPVR) ? false : true; + + /* An ir ref goes to the struct IR_tx instance */ + tx->ir = get_ir_device(ir, true); + + /* A tx ref goes to the i2c_client */ + i2c_set_clientdata(client, get_ir_tx(ir)); + + /* + * Load the 'firmware'. We do this before registering with + * lirc_dev, so the first firmware load attempt does not happen + * after a open() or write() call on the device. + * + * Failure here is not deemed catastrophic, so the receiver will + * still be usable. Firmware load will be retried in write(), + * if it is needed. + */ + fw_load(tx); + + /* Proceed only if the Rx client is also ready or not needed */ + if (rx == NULL && !tx_only) { + zilog_info("probe of IR Tx on %s (i2c-%d) done. Waiting" + " on IR Rx.\n", adap->name, adap->nr); + goto out_ok; + } + } else { + /* Get the IR_tx instance for later, if already allocated */ + tx = get_ir_tx(ir); + + /* Set up a struct IR_rx instance */ + rx = kzalloc(sizeof(struct IR_rx), GFP_KERNEL); + if (rx == NULL) { + ret = -ENOMEM; + goto out_put_xx; + } + kref_init(&rx->ref); + ir->rx = rx; + + ir->l.features |= LIRC_CAN_REC_LIRCCODE; + mutex_init(&rx->client_lock); + rx->c = client; + rx->hdpvr_data_fmt = + (id->driver_data & ID_FLAG_HDPVR) ? true : false; + + /* An ir ref goes to the struct IR_rx instance */ + rx->ir = get_ir_device(ir, true); + + /* An rx ref goes to the i2c_client */ + i2c_set_clientdata(client, get_ir_rx(ir)); + + /* + * Start the polling thread. + * It will only perform an empty loop around schedule_timeout() + * until we register with lirc_dev and the first user open() + */ + /* An ir ref goes to the new rx polling kthread */ + rx->task = kthread_run(lirc_thread, get_ir_device(ir, true), + "zilog-rx-i2c-%d", adap->nr); + if (IS_ERR(rx->task)) { + ret = PTR_ERR(rx->task); + zilog_error("%s: could not start IR Rx polling thread" + "\n", __func__); + /* Failed kthread, so put back the ir ref */ + put_ir_device(ir, true); + /* Failure exit, so put back rx ref from i2c_client */ + i2c_set_clientdata(client, NULL); + put_ir_rx(rx, true); + ir->l.features &= ~LIRC_CAN_REC_LIRCCODE; + goto out_put_xx; + } + + /* Proceed only if the Tx client is also ready */ + if (tx == NULL) { + zilog_info("probe of IR Rx on %s (i2c-%d) done. Waiting" + " on IR Tx.\n", adap->name, adap->nr); + goto out_ok; + } + } + + /* register with lirc */ + ir->l.minor = minor; /* module option: user requested minor number */ + ir->l.minor = lirc_register_driver(&ir->l); + if (ir->l.minor < 0 || ir->l.minor >= MAX_IRCTL_DEVICES) { + zilog_error("%s: \"minor\" must be between 0 and %d (%d)!\n", + __func__, MAX_IRCTL_DEVICES-1, ir->l.minor); + ret = -EBADRQC; + goto out_put_xx; + } + zilog_info("IR unit on %s (i2c-%d) registered as lirc%d and ready\n", + adap->name, adap->nr, ir->l.minor); + +out_ok: + if (rx != NULL) + put_ir_rx(rx, true); + if (tx != NULL) + put_ir_tx(tx, true); + put_ir_device(ir, true); + zilog_info("probe of IR %s on %s (i2c-%d) done\n", + tx_probe ? "Tx" : "Rx", adap->name, adap->nr); + mutex_unlock(&ir_devices_lock); + return 0; + +out_put_xx: + if (rx != NULL) + put_ir_rx(rx, true); + if (tx != NULL) + put_ir_tx(tx, true); +out_put_ir: + put_ir_device(ir, true); +out_no_ir: + zilog_error("%s: probing IR %s on %s (i2c-%d) failed with %d\n", + __func__, tx_probe ? "Tx" : "Rx", adap->name, adap->nr, + ret); + mutex_unlock(&ir_devices_lock); + return ret; +} + +static int __init zilog_init(void) +{ + int ret; + + zilog_notify("Zilog/Hauppauge IR driver initializing\n"); + + mutex_init(&tx_data_lock); + + request_module("firmware_class"); + + ret = i2c_add_driver(&driver); + if (ret) + zilog_error("initialization failed\n"); + else + zilog_notify("initialization complete\n"); + + return ret; +} + +static void __exit zilog_exit(void) +{ + i2c_del_driver(&driver); + /* if loaded */ + fw_unload(); + zilog_notify("Zilog/Hauppauge IR driver unloaded\n"); +} + +module_init(zilog_init); +module_exit(zilog_exit); + +MODULE_DESCRIPTION("Zilog/Hauppauge infrared transmitter driver (i2c stack)"); +MODULE_AUTHOR("Gerd Knorr, Michal Kochanowicz, Christoph Bartelmus, " + "Ulrich Mueller, Stefan Jahn, Jerome Brock, Mark Weaver, " + "Andy Walls"); +MODULE_LICENSE("GPL"); +/* for compat with old name, which isn't all that accurate anymore */ +MODULE_ALIAS("lirc_pvr150"); + +module_param(minor, int, 0444); +MODULE_PARM_DESC(minor, "Preferred minor device number"); + +module_param(debug, bool, 0644); +MODULE_PARM_DESC(debug, "Enable debugging messages"); + +module_param(tx_only, bool, 0644); +MODULE_PARM_DESC(tx_only, "Only handle the IR transmit function"); diff --git a/drivers/staging/media/msi3101/Kconfig b/drivers/staging/media/msi3101/Kconfig new file mode 100644 index 00000000000..de0b3bba387 --- /dev/null +++ b/drivers/staging/media/msi3101/Kconfig @@ -0,0 +1,10 @@ +config USB_MSI3101 + tristate "Mirics MSi3101 SDR Dongle" + depends on USB && VIDEO_DEV && VIDEO_V4L2 && SPI + select VIDEOBUF2_CORE + select VIDEOBUF2_VMALLOC + select MEDIA_TUNER_MSI001 + +config MEDIA_TUNER_MSI001 + tristate "Mirics MSi001" + depends on VIDEO_V4L2 && SPI diff --git a/drivers/staging/media/msi3101/Makefile b/drivers/staging/media/msi3101/Makefile new file mode 100644 index 00000000000..daf4f58d9a5 --- /dev/null +++ b/drivers/staging/media/msi3101/Makefile @@ -0,0 +1,2 @@ +obj-$(CONFIG_USB_MSI3101) += sdr-msi3101.o +obj-$(CONFIG_MEDIA_TUNER_MSI001) += msi001.o diff --git a/drivers/staging/media/msi3101/msi001.c b/drivers/staging/media/msi3101/msi001.c new file mode 100644 index 00000000000..bd0b93cb6c5 --- /dev/null +++ b/drivers/staging/media/msi3101/msi001.c @@ -0,0 +1,500 @@ +/* + * Mirics MSi001 silicon tuner driver + * + * Copyright (C) 2013 Antti Palosaari <crope@iki.fi> + * Copyright (C) 2014 Antti Palosaari <crope@iki.fi> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/module.h> +#include <linux/gcd.h> +#include <media/v4l2-device.h> +#include <media/v4l2-ctrls.h> + +static const struct v4l2_frequency_band bands[] = { + { + .type = V4L2_TUNER_RF, + .index = 0, + .capability = V4L2_TUNER_CAP_1HZ | V4L2_TUNER_CAP_FREQ_BANDS, + .rangelow = 49000000, + .rangehigh = 263000000, + }, { + .type = V4L2_TUNER_RF, + .index = 1, + .capability = V4L2_TUNER_CAP_1HZ | V4L2_TUNER_CAP_FREQ_BANDS, + .rangelow = 390000000, + .rangehigh = 960000000, + }, +}; + +struct msi001 { + struct spi_device *spi; + struct v4l2_subdev sd; + + /* Controls */ + struct v4l2_ctrl_handler hdl; + struct v4l2_ctrl *bandwidth_auto; + struct v4l2_ctrl *bandwidth; + struct v4l2_ctrl *lna_gain; + struct v4l2_ctrl *mixer_gain; + struct v4l2_ctrl *if_gain; + + unsigned int f_tuner; +}; + +static inline struct msi001 *sd_to_msi001(struct v4l2_subdev *sd) +{ + return container_of(sd, struct msi001, sd); +} + +static int msi001_wreg(struct msi001 *s, u32 data) +{ + /* Register format: 4 bits addr + 20 bits value */ + return spi_write(s->spi, &data, 3); +}; + +static int msi001_set_gain(struct msi001 *s, int lna_gain, int mixer_gain, + int if_gain) +{ + int ret; + u32 reg; + dev_dbg(&s->spi->dev, "%s: lna=%d mixer=%d if=%d\n", __func__, + lna_gain, mixer_gain, if_gain); + + reg = 1 << 0; + reg |= (59 - if_gain) << 4; + reg |= 0 << 10; + reg |= (1 - mixer_gain) << 12; + reg |= (1 - lna_gain) << 13; + reg |= 4 << 14; + reg |= 0 << 17; + ret = msi001_wreg(s, reg); + if (ret) + goto err; + + return 0; +err: + dev_dbg(&s->spi->dev, "%s: failed %d\n", __func__, ret); + return ret; +}; + +static int msi001_set_tuner(struct msi001 *s) +{ + int ret, i; + unsigned int n, m, thresh, frac, vco_step, tmp, f_if1; + u32 reg; + u64 f_vco, tmp64; + u8 mode, filter_mode, lo_div; + static const struct { + u32 rf; + u8 mode; + u8 lo_div; + } band_lut[] = { + { 50000000, 0xe1, 16}, /* AM_MODE2, antenna 2 */ + {108000000, 0x42, 32}, /* VHF_MODE */ + {330000000, 0x44, 16}, /* B3_MODE */ + {960000000, 0x48, 4}, /* B45_MODE */ + { ~0U, 0x50, 2}, /* BL_MODE */ + }; + static const struct { + u32 freq; + u8 filter_mode; + } if_freq_lut[] = { + { 0, 0x03}, /* Zero IF */ + { 450000, 0x02}, /* 450 kHz IF */ + {1620000, 0x01}, /* 1.62 MHz IF */ + {2048000, 0x00}, /* 2.048 MHz IF */ + }; + static const struct { + u32 freq; + u8 val; + } bandwidth_lut[] = { + { 200000, 0x00}, /* 200 kHz */ + { 300000, 0x01}, /* 300 kHz */ + { 600000, 0x02}, /* 600 kHz */ + {1536000, 0x03}, /* 1.536 MHz */ + {5000000, 0x04}, /* 5 MHz */ + {6000000, 0x05}, /* 6 MHz */ + {7000000, 0x06}, /* 7 MHz */ + {8000000, 0x07}, /* 8 MHz */ + }; + + unsigned int f_rf = s->f_tuner; + + /* + * bandwidth (Hz) + * 200000, 300000, 600000, 1536000, 5000000, 6000000, 7000000, 8000000 + */ + unsigned int bandwidth; + + /* + * intermediate frequency (Hz) + * 0, 450000, 1620000, 2048000 + */ + unsigned int f_if = 0; + #define F_REF 24000000 + #define R_REF 4 + #define F_OUT_STEP 1 + + dev_dbg(&s->spi->dev, + "%s: f_rf=%d f_if=%d\n", + __func__, f_rf, f_if); + + for (i = 0; i < ARRAY_SIZE(band_lut); i++) { + if (f_rf <= band_lut[i].rf) { + mode = band_lut[i].mode; + lo_div = band_lut[i].lo_div; + break; + } + } + + if (i == ARRAY_SIZE(band_lut)) { + ret = -EINVAL; + goto err; + } + + /* AM_MODE is upconverted */ + if ((mode >> 0) & 0x1) + f_if1 = 5 * F_REF; + else + f_if1 = 0; + + for (i = 0; i < ARRAY_SIZE(if_freq_lut); i++) { + if (f_if == if_freq_lut[i].freq) { + filter_mode = if_freq_lut[i].filter_mode; + break; + } + } + + if (i == ARRAY_SIZE(if_freq_lut)) { + ret = -EINVAL; + goto err; + } + + /* filters */ + bandwidth = s->bandwidth->val; + bandwidth = clamp(bandwidth, 200000U, 8000000U); + + for (i = 0; i < ARRAY_SIZE(bandwidth_lut); i++) { + if (bandwidth <= bandwidth_lut[i].freq) { + bandwidth = bandwidth_lut[i].val; + break; + } + } + + if (i == ARRAY_SIZE(bandwidth_lut)) { + ret = -EINVAL; + goto err; + } + + s->bandwidth->val = bandwidth_lut[i].freq; + + dev_dbg(&s->spi->dev, "%s: bandwidth selected=%d\n", + __func__, bandwidth_lut[i].freq); + + f_vco = (u64) (f_rf + f_if + f_if1) * lo_div; + tmp64 = f_vco; + m = do_div(tmp64, F_REF * R_REF); + n = (unsigned int) tmp64; + + vco_step = F_OUT_STEP * lo_div; + thresh = (F_REF * R_REF) / vco_step; + frac = 1ul * thresh * m / (F_REF * R_REF); + + /* Find out greatest common divisor and divide to smaller. */ + tmp = gcd(thresh, frac); + thresh /= tmp; + frac /= tmp; + + /* Force divide to reg max. Resolution will be reduced. */ + tmp = DIV_ROUND_UP(thresh, 4095); + thresh = DIV_ROUND_CLOSEST(thresh, tmp); + frac = DIV_ROUND_CLOSEST(frac, tmp); + + /* calc real RF set */ + tmp = 1ul * F_REF * R_REF * n; + tmp += 1ul * F_REF * R_REF * frac / thresh; + tmp /= lo_div; + + dev_dbg(&s->spi->dev, + "%s: rf=%u:%u n=%d thresh=%d frac=%d\n", + __func__, f_rf, tmp, n, thresh, frac); + + ret = msi001_wreg(s, 0x00000e); + if (ret) + goto err; + + ret = msi001_wreg(s, 0x000003); + if (ret) + goto err; + + reg = 0 << 0; + reg |= mode << 4; + reg |= filter_mode << 12; + reg |= bandwidth << 14; + reg |= 0x02 << 17; + reg |= 0x00 << 20; + ret = msi001_wreg(s, reg); + if (ret) + goto err; + + reg = 5 << 0; + reg |= thresh << 4; + reg |= 1 << 19; + reg |= 1 << 21; + ret = msi001_wreg(s, reg); + if (ret) + goto err; + + reg = 2 << 0; + reg |= frac << 4; + reg |= n << 16; + ret = msi001_wreg(s, reg); + if (ret) + goto err; + + ret = msi001_set_gain(s, s->lna_gain->cur.val, s->mixer_gain->cur.val, + s->if_gain->cur.val); + if (ret) + goto err; + + reg = 6 << 0; + reg |= 63 << 4; + reg |= 4095 << 10; + ret = msi001_wreg(s, reg); + if (ret) + goto err; + + return 0; +err: + dev_dbg(&s->spi->dev, "%s: failed %d\n", __func__, ret); + return ret; +}; + +static int msi001_s_power(struct v4l2_subdev *sd, int on) +{ + struct msi001 *s = sd_to_msi001(sd); + int ret; + dev_dbg(&s->spi->dev, "%s: on=%d\n", __func__, on); + + if (on) + ret = 0; + else + ret = msi001_wreg(s, 0x000000); + + return ret; +} + +static const struct v4l2_subdev_core_ops msi001_core_ops = { + .s_power = msi001_s_power, +}; + +static int msi001_g_tuner(struct v4l2_subdev *sd, struct v4l2_tuner *v) +{ + struct msi001 *s = sd_to_msi001(sd); + dev_dbg(&s->spi->dev, "%s: index=%d\n", __func__, v->index); + + strlcpy(v->name, "Mirics MSi001", sizeof(v->name)); + v->type = V4L2_TUNER_RF; + v->capability = V4L2_TUNER_CAP_1HZ | V4L2_TUNER_CAP_FREQ_BANDS; + v->rangelow = 49000000; + v->rangehigh = 960000000; + + return 0; +} + +static int msi001_s_tuner(struct v4l2_subdev *sd, const struct v4l2_tuner *v) +{ + struct msi001 *s = sd_to_msi001(sd); + dev_dbg(&s->spi->dev, "%s: index=%d\n", __func__, v->index); + return 0; +} + +static int msi001_g_frequency(struct v4l2_subdev *sd, struct v4l2_frequency *f) +{ + struct msi001 *s = sd_to_msi001(sd); + dev_dbg(&s->spi->dev, "%s: tuner=%d\n", __func__, f->tuner); + f->frequency = s->f_tuner; + return 0; +} + +static int msi001_s_frequency(struct v4l2_subdev *sd, + const struct v4l2_frequency *f) +{ + struct msi001 *s = sd_to_msi001(sd); + unsigned int band; + dev_dbg(&s->spi->dev, "%s: tuner=%d type=%d frequency=%u\n", + __func__, f->tuner, f->type, f->frequency); + + if (f->frequency < ((bands[0].rangehigh + bands[1].rangelow) / 2)) + band = 0; + else + band = 1; + s->f_tuner = clamp_t(unsigned int, f->frequency, + bands[band].rangelow, bands[band].rangehigh); + + return msi001_set_tuner(s); +} + +static int msi001_enum_freq_bands(struct v4l2_subdev *sd, + struct v4l2_frequency_band *band) +{ + struct msi001 *s = sd_to_msi001(sd); + dev_dbg(&s->spi->dev, "%s: tuner=%d type=%d index=%d\n", + __func__, band->tuner, band->type, band->index); + + if (band->index >= ARRAY_SIZE(bands)) + return -EINVAL; + + band->capability = bands[band->index].capability; + band->rangelow = bands[band->index].rangelow; + band->rangehigh = bands[band->index].rangehigh; + + return 0; +} + +static const struct v4l2_subdev_tuner_ops msi001_tuner_ops = { + .g_tuner = msi001_g_tuner, + .s_tuner = msi001_s_tuner, + .g_frequency = msi001_g_frequency, + .s_frequency = msi001_s_frequency, + .enum_freq_bands = msi001_enum_freq_bands, +}; + +static const struct v4l2_subdev_ops msi001_ops = { + .core = &msi001_core_ops, + .tuner = &msi001_tuner_ops, +}; + +static int msi001_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct msi001 *s = container_of(ctrl->handler, struct msi001, hdl); + + int ret; + dev_dbg(&s->spi->dev, + "%s: id=%d name=%s val=%d min=%d max=%d step=%d\n", + __func__, ctrl->id, ctrl->name, ctrl->val, + ctrl->minimum, ctrl->maximum, ctrl->step); + + switch (ctrl->id) { + case V4L2_CID_RF_TUNER_BANDWIDTH_AUTO: + case V4L2_CID_RF_TUNER_BANDWIDTH: + ret = msi001_set_tuner(s); + break; + case V4L2_CID_RF_TUNER_LNA_GAIN: + ret = msi001_set_gain(s, s->lna_gain->val, + s->mixer_gain->cur.val, s->if_gain->cur.val); + break; + case V4L2_CID_RF_TUNER_MIXER_GAIN: + ret = msi001_set_gain(s, s->lna_gain->cur.val, + s->mixer_gain->val, s->if_gain->cur.val); + break; + case V4L2_CID_RF_TUNER_IF_GAIN: + ret = msi001_set_gain(s, s->lna_gain->cur.val, + s->mixer_gain->cur.val, s->if_gain->val); + break; + default: + dev_dbg(&s->spi->dev, "%s: unkown control %d\n", + __func__, ctrl->id); + ret = -EINVAL; + } + + return ret; +} + +static const struct v4l2_ctrl_ops msi001_ctrl_ops = { + .s_ctrl = msi001_s_ctrl, +}; + +static int msi001_probe(struct spi_device *spi) +{ + struct msi001 *s; + int ret; + dev_dbg(&spi->dev, "%s:\n", __func__); + + s = kzalloc(sizeof(struct msi001), GFP_KERNEL); + if (s == NULL) { + ret = -ENOMEM; + dev_dbg(&spi->dev, "Could not allocate memory for msi001\n"); + goto err_kfree; + } + + s->spi = spi; + s->f_tuner = bands[0].rangelow; + v4l2_spi_subdev_init(&s->sd, spi, &msi001_ops); + + /* Register controls */ + v4l2_ctrl_handler_init(&s->hdl, 5); + s->bandwidth_auto = v4l2_ctrl_new_std(&s->hdl, &msi001_ctrl_ops, + V4L2_CID_RF_TUNER_BANDWIDTH_AUTO, 0, 1, 1, 1); + s->bandwidth = v4l2_ctrl_new_std(&s->hdl, &msi001_ctrl_ops, + V4L2_CID_RF_TUNER_BANDWIDTH, 200000, 8000000, 1, 200000); + v4l2_ctrl_auto_cluster(2, &s->bandwidth_auto, 0, false); + s->lna_gain = v4l2_ctrl_new_std(&s->hdl, &msi001_ctrl_ops, + V4L2_CID_RF_TUNER_LNA_GAIN, 0, 1, 1, 1); + s->mixer_gain = v4l2_ctrl_new_std(&s->hdl, &msi001_ctrl_ops, + V4L2_CID_RF_TUNER_MIXER_GAIN, 0, 1, 1, 1); + s->if_gain = v4l2_ctrl_new_std(&s->hdl, &msi001_ctrl_ops, + V4L2_CID_RF_TUNER_IF_GAIN, 0, 59, 1, 0); + if (s->hdl.error) { + ret = s->hdl.error; + dev_err(&s->spi->dev, "Could not initialize controls\n"); + /* control init failed, free handler */ + goto err_ctrl_handler_free; + } + + s->sd.ctrl_handler = &s->hdl; + return 0; + +err_ctrl_handler_free: + v4l2_ctrl_handler_free(&s->hdl); +err_kfree: + kfree(s); + return ret; +} + +static int msi001_remove(struct spi_device *spi) +{ + struct v4l2_subdev *sd = spi_get_drvdata(spi); + struct msi001 *s = sd_to_msi001(sd); + dev_dbg(&spi->dev, "%s:\n", __func__); + + /* + * Registered by v4l2_spi_new_subdev() from master driver, but we must + * unregister it from here. Weird. + */ + v4l2_device_unregister_subdev(&s->sd); + v4l2_ctrl_handler_free(&s->hdl); + kfree(s); + return 0; +} + +static const struct spi_device_id msi001_id[] = { + {"msi001", 0}, + {} +}; +MODULE_DEVICE_TABLE(spi, msi001_id); + +static struct spi_driver msi001_driver = { + .driver = { + .name = "msi001", + .owner = THIS_MODULE, + }, + .probe = msi001_probe, + .remove = msi001_remove, + .id_table = msi001_id, +}; +module_spi_driver(msi001_driver); + +MODULE_AUTHOR("Antti Palosaari <crope@iki.fi>"); +MODULE_DESCRIPTION("Mirics MSi001"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/media/msi3101/sdr-msi3101.c b/drivers/staging/media/msi3101/sdr-msi3101.c new file mode 100644 index 00000000000..08d0d096b88 --- /dev/null +++ b/drivers/staging/media/msi3101/sdr-msi3101.c @@ -0,0 +1,1518 @@ +/* + * Mirics MSi3101 SDR Dongle driver + * + * Copyright (C) 2013 Antti Palosaari <crope@iki.fi> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * That driver is somehow based of pwc driver: + * (C) 1999-2004 Nemosoft Unv. + * (C) 2004-2006 Luc Saillard (luc@saillard.org) + * (C) 2011 Hans de Goede <hdegoede@redhat.com> + */ + +#include <linux/module.h> +#include <linux/slab.h> +#include <asm/div64.h> +#include <media/v4l2-device.h> +#include <media/v4l2-ioctl.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-event.h> +#include <linux/usb.h> +#include <media/videobuf2-vmalloc.h> +#include <linux/spi/spi.h> + +/* + * iConfiguration 0 + * bInterfaceNumber 0 + * bAlternateSetting 1 + * bNumEndpoints 1 + * bEndpointAddress 0x81 EP 1 IN + * bmAttributes 1 + * Transfer Type Isochronous + * wMaxPacketSize 0x1400 3x 1024 bytes + * bInterval 1 + */ +#define MAX_ISO_BUFS (8) +#define ISO_FRAMES_PER_DESC (8) +#define ISO_MAX_FRAME_SIZE (3 * 1024) +#define ISO_BUFFER_SIZE (ISO_FRAMES_PER_DESC * ISO_MAX_FRAME_SIZE) +#define MAX_ISOC_ERRORS 20 + +/* TODO: These should be moved to V4L2 API */ +#define V4L2_PIX_FMT_SDR_S8 v4l2_fourcc('D', 'S', '0', '8') /* signed 8-bit */ +#define V4L2_PIX_FMT_SDR_S12 v4l2_fourcc('D', 'S', '1', '2') /* signed 12-bit */ +#define V4L2_PIX_FMT_SDR_S14 v4l2_fourcc('D', 'S', '1', '4') /* signed 14-bit */ +#define V4L2_PIX_FMT_SDR_MSI2500_384 v4l2_fourcc('M', '3', '8', '4') /* Mirics MSi2500 format 384 */ + +static const struct v4l2_frequency_band bands[] = { + { + .tuner = 0, + .type = V4L2_TUNER_ADC, + .index = 0, + .capability = V4L2_TUNER_CAP_1HZ | V4L2_TUNER_CAP_FREQ_BANDS, + .rangelow = 1200000, + .rangehigh = 15000000, + }, +}; + +/* stream formats */ +struct msi3101_format { + char *name; + u32 pixelformat; +}; + +/* format descriptions for capture and preview */ +static struct msi3101_format formats[] = { + { + .name = "IQ U8", + .pixelformat = V4L2_SDR_FMT_CU8, + }, { + .name = "IQ U16LE", + .pixelformat = V4L2_SDR_FMT_CU16LE, +#if 0 + }, { + .name = "8-bit signed", + .pixelformat = V4L2_PIX_FMT_SDR_S8, + }, { + .name = "10+2-bit signed", + .pixelformat = V4L2_PIX_FMT_SDR_MSI2500_384, + }, { + .name = "12-bit signed", + .pixelformat = V4L2_PIX_FMT_SDR_S12, + }, { + .name = "14-bit signed", + .pixelformat = V4L2_PIX_FMT_SDR_S14, +#endif + }, +}; + +static const unsigned int NUM_FORMATS = ARRAY_SIZE(formats); + +/* intermediate buffers with raw data from the USB device */ +struct msi3101_frame_buf { + struct vb2_buffer vb; /* common v4l buffer stuff -- must be first */ + struct list_head list; +}; + +struct msi3101_state { + struct video_device vdev; + struct v4l2_device v4l2_dev; + struct v4l2_subdev *v4l2_subdev; + struct spi_master *master; + + /* videobuf2 queue and queued buffers list */ + struct vb2_queue vb_queue; + struct list_head queued_bufs; + spinlock_t queued_bufs_lock; /* Protects queued_bufs */ + + /* Note if taking both locks v4l2_lock must always be locked first! */ + struct mutex v4l2_lock; /* Protects everything else */ + struct mutex vb_queue_lock; /* Protects vb_queue and capt_file */ + + /* Pointer to our usb_device, will be NULL after unplug */ + struct usb_device *udev; /* Both mutexes most be hold when setting! */ + + unsigned int f_adc; + u32 pixelformat; + + unsigned int isoc_errors; /* number of contiguous ISOC errors */ + unsigned int vb_full; /* vb is full and packets dropped */ + + struct urb *urbs[MAX_ISO_BUFS]; + int (*convert_stream)(struct msi3101_state *s, u8 *dst, u8 *src, + unsigned int src_len); + + /* Controls */ + struct v4l2_ctrl_handler hdl; + + u32 next_sample; /* for track lost packets */ + u32 sample; /* for sample rate calc */ + unsigned long jiffies_next; + unsigned int sample_ctrl_bit[4]; +}; + +/* Private functions */ +static struct msi3101_frame_buf *msi3101_get_next_fill_buf( + struct msi3101_state *s) +{ + unsigned long flags = 0; + struct msi3101_frame_buf *buf = NULL; + + spin_lock_irqsave(&s->queued_bufs_lock, flags); + if (list_empty(&s->queued_bufs)) + goto leave; + + buf = list_entry(s->queued_bufs.next, struct msi3101_frame_buf, list); + list_del(&buf->list); +leave: + spin_unlock_irqrestore(&s->queued_bufs_lock, flags); + return buf; +} + +/* + * +=========================================================================== + * | 00-1023 | USB packet type '504' + * +=========================================================================== + * | 00- 03 | sequence number of first sample in that USB packet + * +--------------------------------------------------------------------------- + * | 04- 15 | garbage + * +--------------------------------------------------------------------------- + * | 16-1023 | samples + * +--------------------------------------------------------------------------- + * signed 8-bit sample + * 504 * 2 = 1008 samples + */ +static int msi3101_convert_stream_504(struct msi3101_state *s, u8 *dst, + u8 *src, unsigned int src_len) +{ + int i, i_max, dst_len = 0; + u32 sample_num[3]; + + /* There could be 1-3 1024 bytes URB frames */ + i_max = src_len / 1024; + + for (i = 0; i < i_max; i++) { + sample_num[i] = src[3] << 24 | src[2] << 16 | src[1] << 8 | src[0] << 0; + if (i == 0 && s->next_sample != sample_num[0]) { + dev_dbg_ratelimited(&s->udev->dev, + "%d samples lost, %d %08x:%08x\n", + sample_num[0] - s->next_sample, + src_len, s->next_sample, sample_num[0]); + } + + /* + * Dump all unknown 'garbage' data - maybe we will discover + * someday if there is something rational... + */ + dev_dbg_ratelimited(&s->udev->dev, "%*ph\n", 12, &src[4]); + + /* 504 x I+Q samples */ + src += 16; + memcpy(dst, src, 1008); + src += 1008; + dst += 1008; + dst_len += 1008; + } + + /* calculate samping rate and output it in 10 seconds intervals */ + if ((s->jiffies_next + msecs_to_jiffies(10000)) <= jiffies) { + unsigned long jiffies_now = jiffies; + unsigned long msecs = jiffies_to_msecs(jiffies_now) - jiffies_to_msecs(s->jiffies_next); + unsigned int samples = sample_num[i_max - 1] - s->sample; + s->jiffies_next = jiffies_now; + s->sample = sample_num[i_max - 1]; + dev_dbg(&s->udev->dev, + "slen=%d samples=%u msecs=%lu sampling rate=%lu\n", + src_len, samples, msecs, + samples * 1000UL / msecs); + } + + /* next sample (sample = sample + i * 504) */ + s->next_sample = sample_num[i_max - 1] + 504; + + return dst_len; +} + +static int msi3101_convert_stream_504_u8(struct msi3101_state *s, u8 *dst, + u8 *src, unsigned int src_len) +{ + int i, j, i_max, dst_len = 0; + u32 sample_num[3]; + s8 *s8src; + u8 *u8dst; + + /* There could be 1-3 1024 bytes URB frames */ + i_max = src_len / 1024; + u8dst = (u8 *) dst; + + for (i = 0; i < i_max; i++) { + sample_num[i] = src[3] << 24 | src[2] << 16 | src[1] << 8 | src[0] << 0; + if (i == 0 && s->next_sample != sample_num[0]) { + dev_dbg_ratelimited(&s->udev->dev, + "%d samples lost, %d %08x:%08x\n", + sample_num[0] - s->next_sample, + src_len, s->next_sample, sample_num[0]); + } + + /* + * Dump all unknown 'garbage' data - maybe we will discover + * someday if there is something rational... + */ + dev_dbg_ratelimited(&s->udev->dev, "%*ph\n", 12, &src[4]); + + /* 504 x I+Q samples */ + src += 16; + + s8src = (s8 *) src; + for (j = 0; j < 1008; j++) + *u8dst++ = *s8src++ + 128; + + src += 1008; + dst += 1008; + dst_len += 1008; + } + + /* calculate samping rate and output it in 10 seconds intervals */ + if (unlikely(time_is_before_jiffies(s->jiffies_next))) { +#define MSECS 10000UL + unsigned int samples = sample_num[i_max - 1] - s->sample; + s->jiffies_next = jiffies + msecs_to_jiffies(MSECS); + s->sample = sample_num[i_max - 1]; + dev_dbg(&s->udev->dev, + "slen=%d samples=%u msecs=%lu sampling rate=%lu\n", + src_len, samples, MSECS, + samples * 1000UL / MSECS); + } + + /* next sample (sample = sample + i * 504) */ + s->next_sample = sample_num[i_max - 1] + 504; + + return dst_len; +} + +/* + * +=========================================================================== + * | 00-1023 | USB packet type '384' + * +=========================================================================== + * | 00- 03 | sequence number of first sample in that USB packet + * +--------------------------------------------------------------------------- + * | 04- 15 | garbage + * +--------------------------------------------------------------------------- + * | 16- 175 | samples + * +--------------------------------------------------------------------------- + * | 176- 179 | control bits for previous samples + * +--------------------------------------------------------------------------- + * | 180- 339 | samples + * +--------------------------------------------------------------------------- + * | 340- 343 | control bits for previous samples + * +--------------------------------------------------------------------------- + * | 344- 503 | samples + * +--------------------------------------------------------------------------- + * | 504- 507 | control bits for previous samples + * +--------------------------------------------------------------------------- + * | 508- 667 | samples + * +--------------------------------------------------------------------------- + * | 668- 671 | control bits for previous samples + * +--------------------------------------------------------------------------- + * | 672- 831 | samples + * +--------------------------------------------------------------------------- + * | 832- 835 | control bits for previous samples + * +--------------------------------------------------------------------------- + * | 836- 995 | samples + * +--------------------------------------------------------------------------- + * | 996- 999 | control bits for previous samples + * +--------------------------------------------------------------------------- + * | 1000-1023 | garbage + * +--------------------------------------------------------------------------- + * + * Bytes 4 - 7 could have some meaning? + * + * Control bits for previous samples is 32-bit field, containing 16 x 2-bit + * numbers. This results one 2-bit number for 8 samples. It is likely used for + * for bit shifting sample by given bits, increasing actual sampling resolution. + * Number 2 (0b10) was never seen. + * + * 6 * 16 * 2 * 4 = 768 samples. 768 * 4 = 3072 bytes + */ +static int msi3101_convert_stream_384(struct msi3101_state *s, u8 *dst, + u8 *src, unsigned int src_len) +{ + int i, i_max, dst_len = 0; + u32 sample_num[3]; + + /* There could be 1-3 1024 bytes URB frames */ + i_max = src_len / 1024; + for (i = 0; i < i_max; i++) { + sample_num[i] = src[3] << 24 | src[2] << 16 | src[1] << 8 | src[0] << 0; + if (i == 0 && s->next_sample != sample_num[0]) { + dev_dbg_ratelimited(&s->udev->dev, + "%d samples lost, %d %08x:%08x\n", + sample_num[0] - s->next_sample, + src_len, s->next_sample, sample_num[0]); + } + + /* + * Dump all unknown 'garbage' data - maybe we will discover + * someday if there is something rational... + */ + dev_dbg_ratelimited(&s->udev->dev, + "%*ph %*ph\n", 12, &src[4], 24, &src[1000]); + + /* 384 x I+Q samples */ + src += 16; + memcpy(dst, src, 984); + src += 984 + 24; + dst += 984; + dst_len += 984; + } + + /* calculate samping rate and output it in 10 seconds intervals */ + if ((s->jiffies_next + msecs_to_jiffies(10000)) <= jiffies) { + unsigned long jiffies_now = jiffies; + unsigned long msecs = jiffies_to_msecs(jiffies_now) - jiffies_to_msecs(s->jiffies_next); + unsigned int samples = sample_num[i_max - 1] - s->sample; + s->jiffies_next = jiffies_now; + s->sample = sample_num[i_max - 1]; + dev_dbg(&s->udev->dev, + "slen=%d samples=%u msecs=%lu sampling rate=%lu bits=%d.%d.%d.%d\n", + src_len, samples, msecs, + samples * 1000UL / msecs, + s->sample_ctrl_bit[0], s->sample_ctrl_bit[1], + s->sample_ctrl_bit[2], s->sample_ctrl_bit[3]); + } + + /* next sample (sample = sample + i * 384) */ + s->next_sample = sample_num[i_max - 1] + 384; + + return dst_len; +} + +/* + * +=========================================================================== + * | 00-1023 | USB packet type '336' + * +=========================================================================== + * | 00- 03 | sequence number of first sample in that USB packet + * +--------------------------------------------------------------------------- + * | 04- 15 | garbage + * +--------------------------------------------------------------------------- + * | 16-1023 | samples + * +--------------------------------------------------------------------------- + * signed 12-bit sample + */ +static int msi3101_convert_stream_336(struct msi3101_state *s, u8 *dst, + u8 *src, unsigned int src_len) +{ + int i, i_max, dst_len = 0; + u32 sample_num[3]; + + /* There could be 1-3 1024 bytes URB frames */ + i_max = src_len / 1024; + + for (i = 0; i < i_max; i++) { + sample_num[i] = src[3] << 24 | src[2] << 16 | src[1] << 8 | src[0] << 0; + if (i == 0 && s->next_sample != sample_num[0]) { + dev_dbg_ratelimited(&s->udev->dev, + "%d samples lost, %d %08x:%08x\n", + sample_num[0] - s->next_sample, + src_len, s->next_sample, sample_num[0]); + } + + /* + * Dump all unknown 'garbage' data - maybe we will discover + * someday if there is something rational... + */ + dev_dbg_ratelimited(&s->udev->dev, "%*ph\n", 12, &src[4]); + + /* 336 x I+Q samples */ + src += 16; + memcpy(dst, src, 1008); + src += 1008; + dst += 1008; + dst_len += 1008; + } + + /* calculate samping rate and output it in 10 seconds intervals */ + if ((s->jiffies_next + msecs_to_jiffies(10000)) <= jiffies) { + unsigned long jiffies_now = jiffies; + unsigned long msecs = jiffies_to_msecs(jiffies_now) - jiffies_to_msecs(s->jiffies_next); + unsigned int samples = sample_num[i_max - 1] - s->sample; + s->jiffies_next = jiffies_now; + s->sample = sample_num[i_max - 1]; + dev_dbg(&s->udev->dev, + "slen=%d samples=%u msecs=%lu sampling rate=%lu\n", + src_len, samples, msecs, + samples * 1000UL / msecs); + } + + /* next sample (sample = sample + i * 336) */ + s->next_sample = sample_num[i_max - 1] + 336; + + return dst_len; +} + +/* + * +=========================================================================== + * | 00-1023 | USB packet type '252' + * +=========================================================================== + * | 00- 03 | sequence number of first sample in that USB packet + * +--------------------------------------------------------------------------- + * | 04- 15 | garbage + * +--------------------------------------------------------------------------- + * | 16-1023 | samples + * +--------------------------------------------------------------------------- + * signed 14-bit sample + */ +static int msi3101_convert_stream_252(struct msi3101_state *s, u8 *dst, + u8 *src, unsigned int src_len) +{ + int i, i_max, dst_len = 0; + u32 sample_num[3]; + + /* There could be 1-3 1024 bytes URB frames */ + i_max = src_len / 1024; + + for (i = 0; i < i_max; i++) { + sample_num[i] = src[3] << 24 | src[2] << 16 | src[1] << 8 | src[0] << 0; + if (i == 0 && s->next_sample != sample_num[0]) { + dev_dbg_ratelimited(&s->udev->dev, + "%d samples lost, %d %08x:%08x\n", + sample_num[0] - s->next_sample, + src_len, s->next_sample, sample_num[0]); + } + + /* + * Dump all unknown 'garbage' data - maybe we will discover + * someday if there is something rational... + */ + dev_dbg_ratelimited(&s->udev->dev, "%*ph\n", 12, &src[4]); + + /* 252 x I+Q samples */ + src += 16; + memcpy(dst, src, 1008); + src += 1008; + dst += 1008; + dst_len += 1008; + } + + /* calculate samping rate and output it in 10 seconds intervals */ + if ((s->jiffies_next + msecs_to_jiffies(10000)) <= jiffies) { + unsigned long jiffies_now = jiffies; + unsigned long msecs = jiffies_to_msecs(jiffies_now) - jiffies_to_msecs(s->jiffies_next); + unsigned int samples = sample_num[i_max - 1] - s->sample; + s->jiffies_next = jiffies_now; + s->sample = sample_num[i_max - 1]; + dev_dbg(&s->udev->dev, + "slen=%d samples=%u msecs=%lu sampling rate=%lu\n", + src_len, samples, msecs, + samples * 1000UL / msecs); + } + + /* next sample (sample = sample + i * 252) */ + s->next_sample = sample_num[i_max - 1] + 252; + + return dst_len; +} + +static int msi3101_convert_stream_252_u16(struct msi3101_state *s, u8 *dst, + u8 *src, unsigned int src_len) +{ + int i, j, i_max, dst_len = 0; + u32 sample_num[3]; + u16 *u16dst = (u16 *) dst; + struct {signed int x:14;} se; + + /* There could be 1-3 1024 bytes URB frames */ + i_max = src_len / 1024; + + for (i = 0; i < i_max; i++) { + sample_num[i] = src[3] << 24 | src[2] << 16 | src[1] << 8 | src[0] << 0; + if (i == 0 && s->next_sample != sample_num[0]) { + dev_dbg_ratelimited(&s->udev->dev, + "%d samples lost, %d %08x:%08x\n", + sample_num[0] - s->next_sample, + src_len, s->next_sample, sample_num[0]); + } + + /* + * Dump all unknown 'garbage' data - maybe we will discover + * someday if there is something rational... + */ + dev_dbg_ratelimited(&s->udev->dev, "%*ph\n", 12, &src[4]); + + /* 252 x I+Q samples */ + src += 16; + + for (j = 0; j < 1008; j += 4) { + unsigned int usample[2]; + int ssample[2]; + + usample[0] = src[j + 0] >> 0 | src[j + 1] << 8; + usample[1] = src[j + 2] >> 0 | src[j + 3] << 8; + + /* sign extension from 14-bit to signed int */ + ssample[0] = se.x = usample[0]; + ssample[1] = se.x = usample[1]; + + /* from signed to unsigned */ + usample[0] = ssample[0] + 8192; + usample[1] = ssample[1] + 8192; + + /* from 14-bit to 16-bit */ + *u16dst++ = (usample[0] << 2) | (usample[0] >> 12); + *u16dst++ = (usample[1] << 2) | (usample[1] >> 12); + } + + src += 1008; + dst += 1008; + dst_len += 1008; + } + + /* calculate samping rate and output it in 10 seconds intervals */ + if (unlikely(time_is_before_jiffies(s->jiffies_next))) { +#define MSECS 10000UL + unsigned int samples = sample_num[i_max - 1] - s->sample; + s->jiffies_next = jiffies + msecs_to_jiffies(MSECS); + s->sample = sample_num[i_max - 1]; + dev_dbg(&s->udev->dev, + "slen=%d samples=%u msecs=%lu sampling rate=%lu\n", + src_len, samples, MSECS, + samples * 1000UL / MSECS); + } + + /* next sample (sample = sample + i * 252) */ + s->next_sample = sample_num[i_max - 1] + 252; + + return dst_len; +} + +/* + * This gets called for the Isochronous pipe (stream). This is done in interrupt + * time, so it has to be fast, not crash, and not stall. Neat. + */ +static void msi3101_isoc_handler(struct urb *urb) +{ + struct msi3101_state *s = (struct msi3101_state *)urb->context; + int i, flen, fstatus; + unsigned char *iso_buf = NULL; + struct msi3101_frame_buf *fbuf; + + if (unlikely(urb->status == -ENOENT || urb->status == -ECONNRESET || + urb->status == -ESHUTDOWN)) { + dev_dbg(&s->udev->dev, "URB (%p) unlinked %ssynchronuously\n", + urb, urb->status == -ENOENT ? "" : "a"); + return; + } + + if (unlikely(urb->status != 0)) { + dev_dbg(&s->udev->dev, + "msi3101_isoc_handler() called with status %d\n", + urb->status); + /* Give up after a number of contiguous errors */ + if (++s->isoc_errors > MAX_ISOC_ERRORS) + dev_dbg(&s->udev->dev, + "Too many ISOC errors, bailing out\n"); + goto handler_end; + } else { + /* Reset ISOC error counter. We did get here, after all. */ + s->isoc_errors = 0; + } + + /* Compact data */ + for (i = 0; i < urb->number_of_packets; i++) { + void *ptr; + + /* Check frame error */ + fstatus = urb->iso_frame_desc[i].status; + if (unlikely(fstatus)) { + dev_dbg_ratelimited(&s->udev->dev, + "frame=%d/%d has error %d skipping\n", + i, urb->number_of_packets, fstatus); + continue; + } + + /* Check if that frame contains data */ + flen = urb->iso_frame_desc[i].actual_length; + if (unlikely(flen == 0)) + continue; + + iso_buf = urb->transfer_buffer + urb->iso_frame_desc[i].offset; + + /* Get free framebuffer */ + fbuf = msi3101_get_next_fill_buf(s); + if (unlikely(fbuf == NULL)) { + s->vb_full++; + dev_dbg_ratelimited(&s->udev->dev, + "videobuf is full, %d packets dropped\n", + s->vb_full); + continue; + } + + /* fill framebuffer */ + ptr = vb2_plane_vaddr(&fbuf->vb, 0); + flen = s->convert_stream(s, ptr, iso_buf, flen); + vb2_set_plane_payload(&fbuf->vb, 0, flen); + vb2_buffer_done(&fbuf->vb, VB2_BUF_STATE_DONE); + } + +handler_end: + i = usb_submit_urb(urb, GFP_ATOMIC); + if (unlikely(i != 0)) + dev_dbg(&s->udev->dev, + "Error (%d) re-submitting urb in msi3101_isoc_handler\n", + i); +} + +static void msi3101_iso_stop(struct msi3101_state *s) +{ + int i; + dev_dbg(&s->udev->dev, "%s:\n", __func__); + + /* Unlinking ISOC buffers one by one */ + for (i = 0; i < MAX_ISO_BUFS; i++) { + if (s->urbs[i]) { + dev_dbg(&s->udev->dev, "Unlinking URB %p\n", + s->urbs[i]); + usb_kill_urb(s->urbs[i]); + } + } +} + +static void msi3101_iso_free(struct msi3101_state *s) +{ + int i; + dev_dbg(&s->udev->dev, "%s:\n", __func__); + + /* Freeing ISOC buffers one by one */ + for (i = 0; i < MAX_ISO_BUFS; i++) { + if (s->urbs[i]) { + dev_dbg(&s->udev->dev, "Freeing URB\n"); + if (s->urbs[i]->transfer_buffer) { + usb_free_coherent(s->udev, + s->urbs[i]->transfer_buffer_length, + s->urbs[i]->transfer_buffer, + s->urbs[i]->transfer_dma); + } + usb_free_urb(s->urbs[i]); + s->urbs[i] = NULL; + } + } +} + +/* Both v4l2_lock and vb_queue_lock should be locked when calling this */ +static void msi3101_isoc_cleanup(struct msi3101_state *s) +{ + dev_dbg(&s->udev->dev, "%s:\n", __func__); + + msi3101_iso_stop(s); + msi3101_iso_free(s); +} + +/* Both v4l2_lock and vb_queue_lock should be locked when calling this */ +static int msi3101_isoc_init(struct msi3101_state *s) +{ + struct usb_device *udev; + struct urb *urb; + int i, j, ret; + dev_dbg(&s->udev->dev, "%s:\n", __func__); + + s->isoc_errors = 0; + udev = s->udev; + + ret = usb_set_interface(s->udev, 0, 1); + if (ret) + return ret; + + /* Allocate and init Isochronuous urbs */ + for (i = 0; i < MAX_ISO_BUFS; i++) { + urb = usb_alloc_urb(ISO_FRAMES_PER_DESC, GFP_KERNEL); + if (urb == NULL) { + dev_err(&s->udev->dev, + "Failed to allocate urb %d\n", i); + msi3101_isoc_cleanup(s); + return -ENOMEM; + } + s->urbs[i] = urb; + dev_dbg(&s->udev->dev, "Allocated URB at 0x%p\n", urb); + + urb->interval = 1; + urb->dev = udev; + urb->pipe = usb_rcvisocpipe(udev, 0x81); + urb->transfer_flags = URB_ISO_ASAP | URB_NO_TRANSFER_DMA_MAP; + urb->transfer_buffer = usb_alloc_coherent(udev, ISO_BUFFER_SIZE, + GFP_KERNEL, &urb->transfer_dma); + if (urb->transfer_buffer == NULL) { + dev_err(&s->udev->dev, + "Failed to allocate urb buffer %d\n", + i); + msi3101_isoc_cleanup(s); + return -ENOMEM; + } + urb->transfer_buffer_length = ISO_BUFFER_SIZE; + urb->complete = msi3101_isoc_handler; + urb->context = s; + urb->start_frame = 0; + urb->number_of_packets = ISO_FRAMES_PER_DESC; + for (j = 0; j < ISO_FRAMES_PER_DESC; j++) { + urb->iso_frame_desc[j].offset = j * ISO_MAX_FRAME_SIZE; + urb->iso_frame_desc[j].length = ISO_MAX_FRAME_SIZE; + } + } + + /* link */ + for (i = 0; i < MAX_ISO_BUFS; i++) { + ret = usb_submit_urb(s->urbs[i], GFP_KERNEL); + if (ret) { + dev_err(&s->udev->dev, + "isoc_init() submit_urb %d failed with error %d\n", + i, ret); + msi3101_isoc_cleanup(s); + return ret; + } + dev_dbg(&s->udev->dev, "URB 0x%p submitted.\n", s->urbs[i]); + } + + /* All is done... */ + return 0; +} + +/* Must be called with vb_queue_lock hold */ +static void msi3101_cleanup_queued_bufs(struct msi3101_state *s) +{ + unsigned long flags = 0; + dev_dbg(&s->udev->dev, "%s:\n", __func__); + + spin_lock_irqsave(&s->queued_bufs_lock, flags); + while (!list_empty(&s->queued_bufs)) { + struct msi3101_frame_buf *buf; + + buf = list_entry(s->queued_bufs.next, struct msi3101_frame_buf, + list); + list_del(&buf->list); + vb2_buffer_done(&buf->vb, VB2_BUF_STATE_ERROR); + } + spin_unlock_irqrestore(&s->queued_bufs_lock, flags); +} + +/* The user yanked out the cable... */ +static void msi3101_disconnect(struct usb_interface *intf) +{ + struct v4l2_device *v = usb_get_intfdata(intf); + struct msi3101_state *s = + container_of(v, struct msi3101_state, v4l2_dev); + dev_dbg(&s->udev->dev, "%s:\n", __func__); + + mutex_lock(&s->vb_queue_lock); + mutex_lock(&s->v4l2_lock); + /* No need to keep the urbs around after disconnection */ + s->udev = NULL; + v4l2_device_disconnect(&s->v4l2_dev); + video_unregister_device(&s->vdev); + spi_unregister_master(s->master); + mutex_unlock(&s->v4l2_lock); + mutex_unlock(&s->vb_queue_lock); + + v4l2_device_put(&s->v4l2_dev); +} + +static int msi3101_querycap(struct file *file, void *fh, + struct v4l2_capability *cap) +{ + struct msi3101_state *s = video_drvdata(file); + dev_dbg(&s->udev->dev, "%s:\n", __func__); + + strlcpy(cap->driver, KBUILD_MODNAME, sizeof(cap->driver)); + strlcpy(cap->card, s->vdev.name, sizeof(cap->card)); + usb_make_path(s->udev, cap->bus_info, sizeof(cap->bus_info)); + cap->device_caps = V4L2_CAP_SDR_CAPTURE | V4L2_CAP_STREAMING | + V4L2_CAP_READWRITE | V4L2_CAP_TUNER; + cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS; + return 0; +} + +/* Videobuf2 operations */ +static int msi3101_queue_setup(struct vb2_queue *vq, + const struct v4l2_format *fmt, unsigned int *nbuffers, + unsigned int *nplanes, unsigned int sizes[], void *alloc_ctxs[]) +{ + struct msi3101_state *s = vb2_get_drv_priv(vq); + dev_dbg(&s->udev->dev, "%s: *nbuffers=%d\n", __func__, *nbuffers); + + /* Absolute min and max number of buffers available for mmap() */ + *nbuffers = clamp_t(unsigned int, *nbuffers, 8, 32); + *nplanes = 1; + /* + * 3, wMaxPacketSize 3x 1024 bytes + * 504, max IQ sample pairs per 1024 frame + * 2, two samples, I and Q + * 2, 16-bit is enough for single sample + */ + sizes[0] = PAGE_ALIGN(3 * 504 * 2 * 2); + dev_dbg(&s->udev->dev, "%s: nbuffers=%d sizes[0]=%d\n", + __func__, *nbuffers, sizes[0]); + return 0; +} + +static void msi3101_buf_queue(struct vb2_buffer *vb) +{ + struct msi3101_state *s = vb2_get_drv_priv(vb->vb2_queue); + struct msi3101_frame_buf *buf = + container_of(vb, struct msi3101_frame_buf, vb); + unsigned long flags = 0; + + /* Check the device has not disconnected between prep and queuing */ + if (unlikely(!s->udev)) { + vb2_buffer_done(&buf->vb, VB2_BUF_STATE_ERROR); + return; + } + + spin_lock_irqsave(&s->queued_bufs_lock, flags); + list_add_tail(&buf->list, &s->queued_bufs); + spin_unlock_irqrestore(&s->queued_bufs_lock, flags); +} + +#define CMD_WREG 0x41 +#define CMD_START_STREAMING 0x43 +#define CMD_STOP_STREAMING 0x45 +#define CMD_READ_UNKNOW 0x48 + +#define msi3101_dbg_usb_control_msg(udev, r, t, v, _i, b, l) { \ + char *direction; \ + if (t == (USB_TYPE_VENDOR | USB_DIR_OUT)) \ + direction = ">>>"; \ + else \ + direction = "<<<"; \ + dev_dbg(&udev->dev, "%s: %02x %02x %02x %02x %02x %02x %02x %02x " \ + "%s %*ph\n", __func__, t, r, v & 0xff, v >> 8, \ + _i & 0xff, _i >> 8, l & 0xff, l >> 8, direction, l, b); \ +} + +static int msi3101_ctrl_msg(struct msi3101_state *s, u8 cmd, u32 data) +{ + int ret; + u8 request = cmd; + u8 requesttype = USB_DIR_OUT | USB_TYPE_VENDOR; + u16 value = (data >> 0) & 0xffff; + u16 index = (data >> 16) & 0xffff; + + msi3101_dbg_usb_control_msg(s->udev, + request, requesttype, value, index, NULL, 0); + + ret = usb_control_msg(s->udev, usb_sndctrlpipe(s->udev, 0), + request, requesttype, value, index, NULL, 0, 2000); + + if (ret) + dev_err(&s->udev->dev, "%s: failed %d, cmd %02x, data %04x\n", + __func__, ret, cmd, data); + + return ret; +}; + +#define F_REF 24000000 +#define DIV_R_IN 2 +static int msi3101_set_usb_adc(struct msi3101_state *s) +{ + int ret, div_n, div_m, div_r_out, f_sr, f_vco, fract; + u32 reg3, reg4, reg7; + struct v4l2_ctrl *bandwidth_auto; + struct v4l2_ctrl *bandwidth; + + f_sr = s->f_adc; + + /* set tuner, subdev, filters according to sampling rate */ + bandwidth_auto = v4l2_ctrl_find(&s->hdl, V4L2_CID_RF_TUNER_BANDWIDTH_AUTO); + if (v4l2_ctrl_g_ctrl(bandwidth_auto)) { + bandwidth = v4l2_ctrl_find(&s->hdl, V4L2_CID_RF_TUNER_BANDWIDTH); + v4l2_ctrl_s_ctrl(bandwidth, s->f_adc); + } + + /* select stream format */ + switch (s->pixelformat) { + case V4L2_SDR_FMT_CU8: + s->convert_stream = msi3101_convert_stream_504_u8; + reg7 = 0x000c9407; + break; + case V4L2_SDR_FMT_CU16LE: + s->convert_stream = msi3101_convert_stream_252_u16; + reg7 = 0x00009407; + break; + case V4L2_PIX_FMT_SDR_S8: + s->convert_stream = msi3101_convert_stream_504; + reg7 = 0x000c9407; + break; + case V4L2_PIX_FMT_SDR_MSI2500_384: + s->convert_stream = msi3101_convert_stream_384; + reg7 = 0x0000a507; + break; + case V4L2_PIX_FMT_SDR_S12: + s->convert_stream = msi3101_convert_stream_336; + reg7 = 0x00008507; + break; + case V4L2_PIX_FMT_SDR_S14: + s->convert_stream = msi3101_convert_stream_252; + reg7 = 0x00009407; + break; + default: + s->convert_stream = msi3101_convert_stream_504_u8; + reg7 = 0x000c9407; + break; + } + + /* + * Synthesizer config is just a educated guess... + * + * [7:0] 0x03, register address + * [8] 1, power control + * [9] ?, power control + * [12:10] output divider + * [13] 0 ? + * [14] 0 ? + * [15] fractional MSB, bit 20 + * [16:19] N + * [23:20] ? + * [24:31] 0x01 + * + * output divider + * val div + * 0 - (invalid) + * 1 4 + * 2 6 + * 3 8 + * 4 10 + * 5 12 + * 6 14 + * 7 16 + * + * VCO 202000000 - 720000000++ + */ + reg3 = 0x01000303; + reg4 = 0x00000004; + + /* XXX: Filters? AGC? */ + if (f_sr < 6000000) + reg3 |= 0x1 << 20; + else if (f_sr < 7000000) + reg3 |= 0x5 << 20; + else if (f_sr < 8500000) + reg3 |= 0x9 << 20; + else + reg3 |= 0xd << 20; + + for (div_r_out = 4; div_r_out < 16; div_r_out += 2) { + f_vco = f_sr * div_r_out * 12; + dev_dbg(&s->udev->dev, "%s: div_r_out=%d f_vco=%d\n", + __func__, div_r_out, f_vco); + if (f_vco >= 202000000) + break; + } + + div_n = f_vco / (F_REF * DIV_R_IN); + div_m = f_vco % (F_REF * DIV_R_IN); + fract = 0x200000ul * div_m / (F_REF * DIV_R_IN); + + reg3 |= div_n << 16; + reg3 |= (div_r_out / 2 - 1) << 10; + reg3 |= ((fract >> 20) & 0x000001) << 15; /* [20] */ + reg4 |= ((fract >> 0) & 0x0fffff) << 8; /* [19:0] */ + + dev_dbg(&s->udev->dev, + "%s: f_sr=%d f_vco=%d div_n=%d div_m=%d div_r_out=%d reg3=%08x reg4=%08x\n", + __func__, f_sr, f_vco, div_n, div_m, div_r_out, reg3, reg4); + + ret = msi3101_ctrl_msg(s, CMD_WREG, 0x00608008); + if (ret) + goto err; + + ret = msi3101_ctrl_msg(s, CMD_WREG, 0x00000c05); + if (ret) + goto err; + + ret = msi3101_ctrl_msg(s, CMD_WREG, 0x00020000); + if (ret) + goto err; + + ret = msi3101_ctrl_msg(s, CMD_WREG, 0x00480102); + if (ret) + goto err; + + ret = msi3101_ctrl_msg(s, CMD_WREG, 0x00f38008); + if (ret) + goto err; + + ret = msi3101_ctrl_msg(s, CMD_WREG, reg7); + if (ret) + goto err; + + ret = msi3101_ctrl_msg(s, CMD_WREG, reg4); + if (ret) + goto err; + + ret = msi3101_ctrl_msg(s, CMD_WREG, reg3); + if (ret) + goto err; +err: + return ret; +}; + +static int msi3101_start_streaming(struct vb2_queue *vq, unsigned int count) +{ + struct msi3101_state *s = vb2_get_drv_priv(vq); + int ret; + dev_dbg(&s->udev->dev, "%s:\n", __func__); + + if (!s->udev) + return -ENODEV; + + if (mutex_lock_interruptible(&s->v4l2_lock)) + return -ERESTARTSYS; + + /* wake-up tuner */ + v4l2_subdev_call(s->v4l2_subdev, core, s_power, 1); + + ret = msi3101_set_usb_adc(s); + + ret = msi3101_isoc_init(s); + if (ret) + msi3101_cleanup_queued_bufs(s); + + ret = msi3101_ctrl_msg(s, CMD_START_STREAMING, 0); + + mutex_unlock(&s->v4l2_lock); + + return ret; +} + +static void msi3101_stop_streaming(struct vb2_queue *vq) +{ + struct msi3101_state *s = vb2_get_drv_priv(vq); + + dev_dbg(&s->udev->dev, "%s:\n", __func__); + + mutex_lock(&s->v4l2_lock); + + if (s->udev) + msi3101_isoc_cleanup(s); + + msi3101_cleanup_queued_bufs(s); + + /* according to tests, at least 700us delay is required */ + msleep(20); + if (!msi3101_ctrl_msg(s, CMD_STOP_STREAMING, 0)) { + /* sleep USB IF / ADC */ + msi3101_ctrl_msg(s, CMD_WREG, 0x01000003); + } + + /* sleep tuner */ + v4l2_subdev_call(s->v4l2_subdev, core, s_power, 0); + + mutex_unlock(&s->v4l2_lock); +} + +static struct vb2_ops msi3101_vb2_ops = { + .queue_setup = msi3101_queue_setup, + .buf_queue = msi3101_buf_queue, + .start_streaming = msi3101_start_streaming, + .stop_streaming = msi3101_stop_streaming, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, +}; + +static int msi3101_enum_fmt_sdr_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + struct msi3101_state *s = video_drvdata(file); + dev_dbg(&s->udev->dev, "%s: index=%d\n", __func__, f->index); + + if (f->index >= NUM_FORMATS) + return -EINVAL; + + strlcpy(f->description, formats[f->index].name, sizeof(f->description)); + f->pixelformat = formats[f->index].pixelformat; + + return 0; +} + +static int msi3101_g_fmt_sdr_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct msi3101_state *s = video_drvdata(file); + dev_dbg(&s->udev->dev, "%s: pixelformat fourcc %4.4s\n", __func__, + (char *)&s->pixelformat); + + memset(f->fmt.sdr.reserved, 0, sizeof(f->fmt.sdr.reserved)); + f->fmt.sdr.pixelformat = s->pixelformat; + + return 0; +} + +static int msi3101_s_fmt_sdr_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct msi3101_state *s = video_drvdata(file); + struct vb2_queue *q = &s->vb_queue; + int i; + dev_dbg(&s->udev->dev, "%s: pixelformat fourcc %4.4s\n", __func__, + (char *)&f->fmt.sdr.pixelformat); + + if (vb2_is_busy(q)) + return -EBUSY; + + memset(f->fmt.sdr.reserved, 0, sizeof(f->fmt.sdr.reserved)); + for (i = 0; i < NUM_FORMATS; i++) { + if (formats[i].pixelformat == f->fmt.sdr.pixelformat) { + s->pixelformat = f->fmt.sdr.pixelformat; + return 0; + } + } + + f->fmt.sdr.pixelformat = formats[0].pixelformat; + s->pixelformat = formats[0].pixelformat; + + return 0; +} + +static int msi3101_try_fmt_sdr_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct msi3101_state *s = video_drvdata(file); + int i; + dev_dbg(&s->udev->dev, "%s: pixelformat fourcc %4.4s\n", __func__, + (char *)&f->fmt.sdr.pixelformat); + + memset(f->fmt.sdr.reserved, 0, sizeof(f->fmt.sdr.reserved)); + for (i = 0; i < NUM_FORMATS; i++) { + if (formats[i].pixelformat == f->fmt.sdr.pixelformat) + return 0; + } + + f->fmt.sdr.pixelformat = formats[0].pixelformat; + + return 0; +} + +static int msi3101_s_tuner(struct file *file, void *priv, + const struct v4l2_tuner *v) +{ + struct msi3101_state *s = video_drvdata(file); + int ret; + dev_dbg(&s->udev->dev, "%s: index=%d\n", __func__, v->index); + + if (v->index == 0) + ret = 0; + else if (v->index == 1) + ret = v4l2_subdev_call(s->v4l2_subdev, tuner, s_tuner, v); + else + ret = -EINVAL; + + return ret; +} + +static int msi3101_g_tuner(struct file *file, void *priv, struct v4l2_tuner *v) +{ + struct msi3101_state *s = video_drvdata(file); + int ret; + dev_dbg(&s->udev->dev, "%s: index=%d\n", __func__, v->index); + + if (v->index == 0) { + strlcpy(v->name, "Mirics MSi2500", sizeof(v->name)); + v->type = V4L2_TUNER_ADC; + v->capability = V4L2_TUNER_CAP_1HZ | V4L2_TUNER_CAP_FREQ_BANDS; + v->rangelow = 1200000; + v->rangehigh = 15000000; + ret = 0; + } else if (v->index == 1) { + ret = v4l2_subdev_call(s->v4l2_subdev, tuner, g_tuner, v); + } else { + ret = -EINVAL; + } + + return ret; +} + +static int msi3101_g_frequency(struct file *file, void *priv, + struct v4l2_frequency *f) +{ + struct msi3101_state *s = video_drvdata(file); + int ret = 0; + dev_dbg(&s->udev->dev, "%s: tuner=%d type=%d\n", + __func__, f->tuner, f->type); + + if (f->tuner == 0) { + f->frequency = s->f_adc; + ret = 0; + } else if (f->tuner == 1) { + f->type = V4L2_TUNER_RF; + ret = v4l2_subdev_call(s->v4l2_subdev, tuner, g_frequency, f); + } else { + ret = -EINVAL; + } + + return ret; +} + +static int msi3101_s_frequency(struct file *file, void *priv, + const struct v4l2_frequency *f) +{ + struct msi3101_state *s = video_drvdata(file); + int ret; + dev_dbg(&s->udev->dev, "%s: tuner=%d type=%d frequency=%u\n", + __func__, f->tuner, f->type, f->frequency); + + if (f->tuner == 0) { + s->f_adc = clamp_t(unsigned int, f->frequency, + bands[0].rangelow, + bands[0].rangehigh); + dev_dbg(&s->udev->dev, "%s: ADC frequency=%u Hz\n", + __func__, s->f_adc); + ret = msi3101_set_usb_adc(s); + } else if (f->tuner == 1) { + ret = v4l2_subdev_call(s->v4l2_subdev, tuner, s_frequency, f); + } else { + ret = -EINVAL; + } + + return ret; +} + +static int msi3101_enum_freq_bands(struct file *file, void *priv, + struct v4l2_frequency_band *band) +{ + struct msi3101_state *s = video_drvdata(file); + int ret; + dev_dbg(&s->udev->dev, "%s: tuner=%d type=%d index=%d\n", + __func__, band->tuner, band->type, band->index); + + if (band->tuner == 0) { + if (band->index >= ARRAY_SIZE(bands)) { + ret = -EINVAL; + } else { + *band = bands[band->index]; + ret = 0; + } + } else if (band->tuner == 1) { + ret = v4l2_subdev_call(s->v4l2_subdev, tuner, + enum_freq_bands, band); + } else { + ret = -EINVAL; + } + + return ret; +} + +static const struct v4l2_ioctl_ops msi3101_ioctl_ops = { + .vidioc_querycap = msi3101_querycap, + + .vidioc_enum_fmt_sdr_cap = msi3101_enum_fmt_sdr_cap, + .vidioc_g_fmt_sdr_cap = msi3101_g_fmt_sdr_cap, + .vidioc_s_fmt_sdr_cap = msi3101_s_fmt_sdr_cap, + .vidioc_try_fmt_sdr_cap = msi3101_try_fmt_sdr_cap, + + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_prepare_buf = vb2_ioctl_prepare_buf, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, + + .vidioc_g_tuner = msi3101_g_tuner, + .vidioc_s_tuner = msi3101_s_tuner, + + .vidioc_g_frequency = msi3101_g_frequency, + .vidioc_s_frequency = msi3101_s_frequency, + .vidioc_enum_freq_bands = msi3101_enum_freq_bands, + + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, + .vidioc_log_status = v4l2_ctrl_log_status, +}; + +static const struct v4l2_file_operations msi3101_fops = { + .owner = THIS_MODULE, + .open = v4l2_fh_open, + .release = vb2_fop_release, + .read = vb2_fop_read, + .poll = vb2_fop_poll, + .mmap = vb2_fop_mmap, + .unlocked_ioctl = video_ioctl2, +}; + +static struct video_device msi3101_template = { + .name = "Mirics MSi3101 SDR Dongle", + .release = video_device_release_empty, + .fops = &msi3101_fops, + .ioctl_ops = &msi3101_ioctl_ops, +}; + +static void msi3101_video_release(struct v4l2_device *v) +{ + struct msi3101_state *s = + container_of(v, struct msi3101_state, v4l2_dev); + + v4l2_ctrl_handler_free(&s->hdl); + v4l2_device_unregister(&s->v4l2_dev); + kfree(s); +} + +static int msi3101_transfer_one_message(struct spi_master *master, + struct spi_message *m) +{ + struct msi3101_state *s = spi_master_get_devdata(master); + struct spi_transfer *t; + int ret = 0; + u32 data; + + list_for_each_entry(t, &m->transfers, transfer_list) { + dev_dbg(&s->udev->dev, "%s: msg=%*ph\n", + __func__, t->len, t->tx_buf); + data = 0x09; /* reg 9 is SPI adapter */ + data |= ((u8 *)t->tx_buf)[0] << 8; + data |= ((u8 *)t->tx_buf)[1] << 16; + data |= ((u8 *)t->tx_buf)[2] << 24; + ret = msi3101_ctrl_msg(s, CMD_WREG, data); + } + + m->status = ret; + spi_finalize_current_message(master); + return ret; +} + +static int msi3101_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct usb_device *udev = interface_to_usbdev(intf); + struct msi3101_state *s = NULL; + struct v4l2_subdev *sd; + struct spi_master *master; + int ret; + static struct spi_board_info board_info = { + .modalias = "msi001", + .bus_num = 0, + .chip_select = 0, + .max_speed_hz = 12000000, + }; + + s = kzalloc(sizeof(struct msi3101_state), GFP_KERNEL); + if (s == NULL) { + pr_err("Could not allocate memory for msi3101_state\n"); + return -ENOMEM; + } + + mutex_init(&s->v4l2_lock); + mutex_init(&s->vb_queue_lock); + spin_lock_init(&s->queued_bufs_lock); + INIT_LIST_HEAD(&s->queued_bufs); + s->udev = udev; + s->f_adc = bands[0].rangelow; + s->pixelformat = V4L2_SDR_FMT_CU8; + + /* Init videobuf2 queue structure */ + s->vb_queue.type = V4L2_BUF_TYPE_SDR_CAPTURE; + s->vb_queue.io_modes = VB2_MMAP | VB2_USERPTR | VB2_READ; + s->vb_queue.drv_priv = s; + s->vb_queue.buf_struct_size = sizeof(struct msi3101_frame_buf); + s->vb_queue.ops = &msi3101_vb2_ops; + s->vb_queue.mem_ops = &vb2_vmalloc_memops; + s->vb_queue.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + ret = vb2_queue_init(&s->vb_queue); + if (ret) { + dev_err(&s->udev->dev, "Could not initialize vb2 queue\n"); + goto err_free_mem; + } + + /* Init video_device structure */ + s->vdev = msi3101_template; + s->vdev.queue = &s->vb_queue; + s->vdev.queue->lock = &s->vb_queue_lock; + set_bit(V4L2_FL_USE_FH_PRIO, &s->vdev.flags); + video_set_drvdata(&s->vdev, s); + + /* Register the v4l2_device structure */ + s->v4l2_dev.release = msi3101_video_release; + ret = v4l2_device_register(&intf->dev, &s->v4l2_dev); + if (ret) { + dev_err(&s->udev->dev, + "Failed to register v4l2-device (%d)\n", ret); + goto err_free_mem; + } + + /* SPI master adapter */ + master = spi_alloc_master(&s->udev->dev, 0); + if (master == NULL) { + ret = -ENOMEM; + goto err_unregister_v4l2_dev; + } + + s->master = master; + master->bus_num = 0; + master->num_chipselect = 1; + master->transfer_one_message = msi3101_transfer_one_message; + spi_master_set_devdata(master, s); + ret = spi_register_master(master); + if (ret) { + spi_master_put(master); + goto err_unregister_v4l2_dev; + } + + /* load v4l2 subdevice */ + sd = v4l2_spi_new_subdev(&s->v4l2_dev, master, &board_info); + s->v4l2_subdev = sd; + if (sd == NULL) { + dev_err(&s->udev->dev, "cannot get v4l2 subdevice\n"); + ret = -ENODEV; + goto err_unregister_master; + } + + /* Register controls */ + v4l2_ctrl_handler_init(&s->hdl, 0); + if (s->hdl.error) { + ret = s->hdl.error; + dev_err(&s->udev->dev, "Could not initialize controls\n"); + goto err_free_controls; + } + + /* currently all controls are from subdev */ + v4l2_ctrl_add_handler(&s->hdl, sd->ctrl_handler, NULL); + + s->v4l2_dev.ctrl_handler = &s->hdl; + s->vdev.v4l2_dev = &s->v4l2_dev; + s->vdev.lock = &s->v4l2_lock; + + ret = video_register_device(&s->vdev, VFL_TYPE_SDR, -1); + if (ret) { + dev_err(&s->udev->dev, + "Failed to register as video device (%d)\n", + ret); + goto err_unregister_v4l2_dev; + } + dev_info(&s->udev->dev, "Registered as %s\n", + video_device_node_name(&s->vdev)); + + return 0; + +err_free_controls: + v4l2_ctrl_handler_free(&s->hdl); +err_unregister_master: + spi_unregister_master(s->master); +err_unregister_v4l2_dev: + v4l2_device_unregister(&s->v4l2_dev); +err_free_mem: + kfree(s); + return ret; +} + +/* USB device ID list */ +static struct usb_device_id msi3101_id_table[] = { + { USB_DEVICE(0x1df7, 0x2500) }, /* Mirics MSi3101 SDR Dongle */ + { USB_DEVICE(0x2040, 0xd300) }, /* Hauppauge WinTV 133559 LF */ + { } +}; +MODULE_DEVICE_TABLE(usb, msi3101_id_table); + +/* USB subsystem interface */ +static struct usb_driver msi3101_driver = { + .name = KBUILD_MODNAME, + .probe = msi3101_probe, + .disconnect = msi3101_disconnect, + .id_table = msi3101_id_table, +}; + +module_usb_driver(msi3101_driver); + +MODULE_AUTHOR("Antti Palosaari <crope@iki.fi>"); +MODULE_DESCRIPTION("Mirics MSi3101 SDR Dongle"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/media/omap24xx/Kconfig b/drivers/staging/media/omap24xx/Kconfig new file mode 100644 index 00000000000..82e569a21c4 --- /dev/null +++ b/drivers/staging/media/omap24xx/Kconfig @@ -0,0 +1,35 @@ +config VIDEO_V4L2_INT_DEVICE + tristate + +config VIDEO_OMAP2 + tristate "OMAP2 Camera Capture Interface driver (DEPRECATED)" + depends on VIDEO_DEV && ARCH_OMAP2 + select VIDEOBUF_DMA_SG + select VIDEO_V4L2_INT_DEVICE + ---help--- + This is a v4l2 driver for the TI OMAP2 camera capture interface + + It uses the deprecated int-device API. Since this driver is no + longer actively maintained and nobody is interested in converting + it to the subdev API, this driver will be removed soon. + + If you do want to keep this driver in the kernel, and are willing + to convert it to the subdev API, then please contact the linux-media + mailinglist. + +config VIDEO_TCM825X + tristate "TCM825x camera sensor support (DEPRECATED)" + depends on I2C && VIDEO_V4L2 + depends on MEDIA_CAMERA_SUPPORT + select VIDEO_V4L2_INT_DEVICE + ---help--- + This is a driver for the Toshiba TCM825x VGA camera sensor. + It is used for example in Nokia N800. + + It uses the deprecated int-device API. Since this driver is no + longer actively maintained and nobody is interested in converting + it to the subdev API, this driver will be removed soon. + + If you do want to keep this driver in the kernel, and are willing + to convert it to the subdev API, then please contact the linux-media + mailinglist. diff --git a/drivers/staging/media/omap24xx/Makefile b/drivers/staging/media/omap24xx/Makefile new file mode 100644 index 00000000000..c2e7175599c --- /dev/null +++ b/drivers/staging/media/omap24xx/Makefile @@ -0,0 +1,5 @@ +omap2cam-objs := omap24xxcam.o omap24xxcam-dma.o + +obj-$(CONFIG_VIDEO_OMAP2) += omap2cam.o +obj-$(CONFIG_VIDEO_TCM825X) += tcm825x.o +obj-$(CONFIG_VIDEO_V4L2_INT_DEVICE) += v4l2-int-device.o diff --git a/drivers/staging/media/omap24xx/omap24xxcam-dma.c b/drivers/staging/media/omap24xx/omap24xxcam-dma.c new file mode 100644 index 00000000000..9c00776d658 --- /dev/null +++ b/drivers/staging/media/omap24xx/omap24xxcam-dma.c @@ -0,0 +1,601 @@ +/* + * drivers/media/platform/omap24xxcam-dma.c + * + * Copyright (C) 2004 MontaVista Software, Inc. + * Copyright (C) 2004 Texas Instruments. + * Copyright (C) 2007 Nokia Corporation. + * + * Contact: Sakari Ailus <sakari.ailus@nokia.com> + * + * Based on code from Andy Lowe <source@mvista.com> and + * David Cohen <david.cohen@indt.org.br>. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#include <linux/kernel.h> +#include <linux/io.h> +#include <linux/scatterlist.h> + +#include "omap24xxcam.h" + +/* + * + * DMA hardware. + * + */ + +/* Ack all interrupt on CSR and IRQSTATUS_L0 */ +static void omap24xxcam_dmahw_ack_all(void __iomem *base) +{ + u32 csr; + int i; + + for (i = 0; i < NUM_CAMDMA_CHANNELS; ++i) { + csr = omap24xxcam_reg_in(base, CAMDMA_CSR(i)); + /* ack interrupt in CSR */ + omap24xxcam_reg_out(base, CAMDMA_CSR(i), csr); + } + omap24xxcam_reg_out(base, CAMDMA_IRQSTATUS_L0, 0xf); +} + +/* Ack dmach on CSR and IRQSTATUS_L0 */ +static u32 omap24xxcam_dmahw_ack_ch(void __iomem *base, int dmach) +{ + u32 csr; + + csr = omap24xxcam_reg_in(base, CAMDMA_CSR(dmach)); + /* ack interrupt in CSR */ + omap24xxcam_reg_out(base, CAMDMA_CSR(dmach), csr); + /* ack interrupt in IRQSTATUS */ + omap24xxcam_reg_out(base, CAMDMA_IRQSTATUS_L0, (1 << dmach)); + + return csr; +} + +static int omap24xxcam_dmahw_running(void __iomem *base, int dmach) +{ + return omap24xxcam_reg_in(base, CAMDMA_CCR(dmach)) & CAMDMA_CCR_ENABLE; +} + +static void omap24xxcam_dmahw_transfer_setup(void __iomem *base, int dmach, + dma_addr_t start, u32 len) +{ + omap24xxcam_reg_out(base, CAMDMA_CCR(dmach), + CAMDMA_CCR_SEL_SRC_DST_SYNC + | CAMDMA_CCR_BS + | CAMDMA_CCR_DST_AMODE_POST_INC + | CAMDMA_CCR_SRC_AMODE_POST_INC + | CAMDMA_CCR_FS + | CAMDMA_CCR_WR_ACTIVE + | CAMDMA_CCR_RD_ACTIVE + | CAMDMA_CCR_SYNCHRO_CAMERA); + omap24xxcam_reg_out(base, CAMDMA_CLNK_CTRL(dmach), 0); + omap24xxcam_reg_out(base, CAMDMA_CEN(dmach), len); + omap24xxcam_reg_out(base, CAMDMA_CFN(dmach), 1); + omap24xxcam_reg_out(base, CAMDMA_CSDP(dmach), + CAMDMA_CSDP_WRITE_MODE_POSTED + | CAMDMA_CSDP_DST_BURST_EN_32 + | CAMDMA_CSDP_DST_PACKED + | CAMDMA_CSDP_SRC_BURST_EN_32 + | CAMDMA_CSDP_SRC_PACKED + | CAMDMA_CSDP_DATA_TYPE_8BITS); + omap24xxcam_reg_out(base, CAMDMA_CSSA(dmach), 0); + omap24xxcam_reg_out(base, CAMDMA_CDSA(dmach), start); + omap24xxcam_reg_out(base, CAMDMA_CSEI(dmach), 0); + omap24xxcam_reg_out(base, CAMDMA_CSFI(dmach), DMA_THRESHOLD); + omap24xxcam_reg_out(base, CAMDMA_CDEI(dmach), 0); + omap24xxcam_reg_out(base, CAMDMA_CDFI(dmach), 0); + omap24xxcam_reg_out(base, CAMDMA_CSR(dmach), + CAMDMA_CSR_MISALIGNED_ERR + | CAMDMA_CSR_SECURE_ERR + | CAMDMA_CSR_TRANS_ERR + | CAMDMA_CSR_BLOCK + | CAMDMA_CSR_DROP); + omap24xxcam_reg_out(base, CAMDMA_CICR(dmach), + CAMDMA_CICR_MISALIGNED_ERR_IE + | CAMDMA_CICR_SECURE_ERR_IE + | CAMDMA_CICR_TRANS_ERR_IE + | CAMDMA_CICR_BLOCK_IE + | CAMDMA_CICR_DROP_IE); +} + +static void omap24xxcam_dmahw_transfer_start(void __iomem *base, int dmach) +{ + omap24xxcam_reg_out(base, CAMDMA_CCR(dmach), + CAMDMA_CCR_SEL_SRC_DST_SYNC + | CAMDMA_CCR_BS + | CAMDMA_CCR_DST_AMODE_POST_INC + | CAMDMA_CCR_SRC_AMODE_POST_INC + | CAMDMA_CCR_ENABLE + | CAMDMA_CCR_FS + | CAMDMA_CCR_SYNCHRO_CAMERA); +} + +static void omap24xxcam_dmahw_transfer_chain(void __iomem *base, int dmach, + int free_dmach) +{ + int prev_dmach, ch; + + if (dmach == 0) + prev_dmach = NUM_CAMDMA_CHANNELS - 1; + else + prev_dmach = dmach - 1; + omap24xxcam_reg_out(base, CAMDMA_CLNK_CTRL(prev_dmach), + CAMDMA_CLNK_CTRL_ENABLE_LNK | dmach); + /* Did we chain the DMA transfer before the previous one + * finished? + */ + ch = (dmach + free_dmach) % NUM_CAMDMA_CHANNELS; + while (!(omap24xxcam_reg_in(base, CAMDMA_CCR(ch)) + & CAMDMA_CCR_ENABLE)) { + if (ch == dmach) { + /* The previous transfer has ended and this one + * hasn't started, so we must not have chained + * to the previous one in time. We'll have to + * start it now. + */ + omap24xxcam_dmahw_transfer_start(base, dmach); + break; + } else + ch = (ch + 1) % NUM_CAMDMA_CHANNELS; + } +} + +/* Abort all chained DMA transfers. After all transfers have been + * aborted and the DMA controller is idle, the completion routines for + * any aborted transfers will be called in sequence. The DMA + * controller may not be idle after this routine completes, because + * the completion routines might start new transfers. + */ +static void omap24xxcam_dmahw_abort_ch(void __iomem *base, int dmach) +{ + /* mask all interrupts from this channel */ + omap24xxcam_reg_out(base, CAMDMA_CICR(dmach), 0); + /* unlink this channel */ + omap24xxcam_reg_merge(base, CAMDMA_CLNK_CTRL(dmach), 0, + CAMDMA_CLNK_CTRL_ENABLE_LNK); + /* disable this channel */ + omap24xxcam_reg_merge(base, CAMDMA_CCR(dmach), 0, CAMDMA_CCR_ENABLE); +} + +static void omap24xxcam_dmahw_init(void __iomem *base) +{ + omap24xxcam_reg_out(base, CAMDMA_OCP_SYSCONFIG, + CAMDMA_OCP_SYSCONFIG_MIDLEMODE_FSTANDBY + | CAMDMA_OCP_SYSCONFIG_SIDLEMODE_FIDLE + | CAMDMA_OCP_SYSCONFIG_AUTOIDLE); + + omap24xxcam_reg_merge(base, CAMDMA_GCR, 0x10, + CAMDMA_GCR_MAX_CHANNEL_FIFO_DEPTH); + + omap24xxcam_reg_out(base, CAMDMA_IRQENABLE_L0, 0xf); +} + +/* + * + * Individual DMA channel handling. + * + */ + +/* Start a DMA transfer from the camera to memory. + * Returns zero if the transfer was successfully started, or non-zero if all + * DMA channels are already in use or starting is currently inhibited. + */ +static int omap24xxcam_dma_start(struct omap24xxcam_dma *dma, dma_addr_t start, + u32 len, dma_callback_t callback, void *arg) +{ + unsigned long flags; + int dmach; + + spin_lock_irqsave(&dma->lock, flags); + + if (!dma->free_dmach || atomic_read(&dma->dma_stop)) { + spin_unlock_irqrestore(&dma->lock, flags); + return -EBUSY; + } + + dmach = dma->next_dmach; + + dma->ch_state[dmach].callback = callback; + dma->ch_state[dmach].arg = arg; + + omap24xxcam_dmahw_transfer_setup(dma->base, dmach, start, len); + + /* We're ready to start the DMA transfer. */ + + if (dma->free_dmach < NUM_CAMDMA_CHANNELS) { + /* A transfer is already in progress, so try to chain to it. */ + omap24xxcam_dmahw_transfer_chain(dma->base, dmach, + dma->free_dmach); + } else { + /* No transfer is in progress, so we'll just start this one + * now. + */ + omap24xxcam_dmahw_transfer_start(dma->base, dmach); + } + + dma->next_dmach = (dma->next_dmach + 1) % NUM_CAMDMA_CHANNELS; + dma->free_dmach--; + + spin_unlock_irqrestore(&dma->lock, flags); + + return 0; +} + +/* Abort all chained DMA transfers. After all transfers have been + * aborted and the DMA controller is idle, the completion routines for + * any aborted transfers will be called in sequence. The DMA + * controller may not be idle after this routine completes, because + * the completion routines might start new transfers. + */ +static void omap24xxcam_dma_abort(struct omap24xxcam_dma *dma, u32 csr) +{ + unsigned long flags; + int dmach, i, free_dmach; + dma_callback_t callback; + void *arg; + + spin_lock_irqsave(&dma->lock, flags); + + /* stop any DMA transfers in progress */ + dmach = (dma->next_dmach + dma->free_dmach) % NUM_CAMDMA_CHANNELS; + for (i = 0; i < NUM_CAMDMA_CHANNELS; i++) { + omap24xxcam_dmahw_abort_ch(dma->base, dmach); + dmach = (dmach + 1) % NUM_CAMDMA_CHANNELS; + } + + /* We have to be careful here because the callback routine + * might start a new DMA transfer, and we only want to abort + * transfers that were started before this routine was called. + */ + free_dmach = dma->free_dmach; + while ((dma->free_dmach < NUM_CAMDMA_CHANNELS) && + (free_dmach < NUM_CAMDMA_CHANNELS)) { + dmach = (dma->next_dmach + dma->free_dmach) + % NUM_CAMDMA_CHANNELS; + callback = dma->ch_state[dmach].callback; + arg = dma->ch_state[dmach].arg; + dma->free_dmach++; + free_dmach++; + if (callback) { + /* leave interrupts disabled during callback */ + spin_unlock(&dma->lock); + (*callback) (dma, csr, arg); + spin_lock(&dma->lock); + } + } + + spin_unlock_irqrestore(&dma->lock, flags); +} + +/* Abort all chained DMA transfers. After all transfers have been + * aborted and the DMA controller is idle, the completion routines for + * any aborted transfers will be called in sequence. If the completion + * routines attempt to start a new DMA transfer it will fail, so the + * DMA controller will be idle after this routine completes. + */ +static void omap24xxcam_dma_stop(struct omap24xxcam_dma *dma, u32 csr) +{ + atomic_inc(&dma->dma_stop); + omap24xxcam_dma_abort(dma, csr); + atomic_dec(&dma->dma_stop); +} + +/* Camera DMA interrupt service routine. */ +void omap24xxcam_dma_isr(struct omap24xxcam_dma *dma) +{ + int dmach; + dma_callback_t callback; + void *arg; + u32 csr; + const u32 csr_error = CAMDMA_CSR_MISALIGNED_ERR + | CAMDMA_CSR_SUPERVISOR_ERR | CAMDMA_CSR_SECURE_ERR + | CAMDMA_CSR_TRANS_ERR | CAMDMA_CSR_DROP; + + spin_lock(&dma->lock); + + if (dma->free_dmach == NUM_CAMDMA_CHANNELS) { + /* A camera DMA interrupt occurred while all channels + * are idle, so we'll acknowledge the interrupt in the + * IRQSTATUS register and exit. + */ + omap24xxcam_dmahw_ack_all(dma->base); + spin_unlock(&dma->lock); + return; + } + + while (dma->free_dmach < NUM_CAMDMA_CHANNELS) { + dmach = (dma->next_dmach + dma->free_dmach) + % NUM_CAMDMA_CHANNELS; + if (omap24xxcam_dmahw_running(dma->base, dmach)) { + /* This buffer hasn't finished yet, so we're done. */ + break; + } + csr = omap24xxcam_dmahw_ack_ch(dma->base, dmach); + if (csr & csr_error) { + /* A DMA error occurred, so stop all DMA + * transfers in progress. + */ + spin_unlock(&dma->lock); + omap24xxcam_dma_stop(dma, csr); + return; + } else { + callback = dma->ch_state[dmach].callback; + arg = dma->ch_state[dmach].arg; + dma->free_dmach++; + if (callback) { + spin_unlock(&dma->lock); + (*callback) (dma, csr, arg); + spin_lock(&dma->lock); + } + } + } + + spin_unlock(&dma->lock); + + omap24xxcam_sgdma_process( + container_of(dma, struct omap24xxcam_sgdma, dma)); +} + +void omap24xxcam_dma_hwinit(struct omap24xxcam_dma *dma) +{ + unsigned long flags; + + spin_lock_irqsave(&dma->lock, flags); + + omap24xxcam_dmahw_init(dma->base); + + spin_unlock_irqrestore(&dma->lock, flags); +} + +static void omap24xxcam_dma_init(struct omap24xxcam_dma *dma, + void __iomem *base) +{ + int ch; + + /* group all channels on DMA IRQ0 and unmask irq */ + spin_lock_init(&dma->lock); + dma->base = base; + dma->free_dmach = NUM_CAMDMA_CHANNELS; + dma->next_dmach = 0; + for (ch = 0; ch < NUM_CAMDMA_CHANNELS; ch++) { + dma->ch_state[ch].callback = NULL; + dma->ch_state[ch].arg = NULL; + } +} + +/* + * + * Scatter-gather DMA. + * + * High-level DMA construct for transferring whole picture frames to + * memory that is discontinuous. + * + */ + +/* DMA completion routine for the scatter-gather DMA fragments. */ +static void omap24xxcam_sgdma_callback(struct omap24xxcam_dma *dma, u32 csr, + void *arg) +{ + struct omap24xxcam_sgdma *sgdma = + container_of(dma, struct omap24xxcam_sgdma, dma); + int sgslot = (int)arg; + struct sgdma_state *sg_state; + const u32 csr_error = CAMDMA_CSR_MISALIGNED_ERR + | CAMDMA_CSR_SUPERVISOR_ERR | CAMDMA_CSR_SECURE_ERR + | CAMDMA_CSR_TRANS_ERR | CAMDMA_CSR_DROP; + + spin_lock(&sgdma->lock); + + /* We got an interrupt, we can remove the timer */ + del_timer(&sgdma->reset_timer); + + sg_state = sgdma->sg_state + sgslot; + if (!sg_state->queued_sglist) { + spin_unlock(&sgdma->lock); + printk(KERN_ERR "%s: sgdma completed when none queued!\n", + __func__); + return; + } + + sg_state->csr |= csr; + if (!--sg_state->queued_sglist) { + /* Queue for this sglist is empty, so check to see if we're + * done. + */ + if ((sg_state->next_sglist == sg_state->sglen) + || (sg_state->csr & csr_error)) { + sgdma_callback_t callback = sg_state->callback; + void *arg = sg_state->arg; + u32 sg_csr = sg_state->csr; + /* All done with this sglist */ + sgdma->free_sgdma++; + if (callback) { + spin_unlock(&sgdma->lock); + (*callback) (sgdma, sg_csr, arg); + return; + } + } + } + + spin_unlock(&sgdma->lock); +} + +/* Start queued scatter-gather DMA transfers. */ +void omap24xxcam_sgdma_process(struct omap24xxcam_sgdma *sgdma) +{ + unsigned long flags; + int queued_sgdma, sgslot; + struct sgdma_state *sg_state; + const u32 csr_error = CAMDMA_CSR_MISALIGNED_ERR + | CAMDMA_CSR_SUPERVISOR_ERR | CAMDMA_CSR_SECURE_ERR + | CAMDMA_CSR_TRANS_ERR | CAMDMA_CSR_DROP; + + spin_lock_irqsave(&sgdma->lock, flags); + + queued_sgdma = NUM_SG_DMA - sgdma->free_sgdma; + sgslot = (sgdma->next_sgdma + sgdma->free_sgdma) % NUM_SG_DMA; + while (queued_sgdma > 0) { + sg_state = sgdma->sg_state + sgslot; + while ((sg_state->next_sglist < sg_state->sglen) && + !(sg_state->csr & csr_error)) { + const struct scatterlist *sglist; + unsigned int len; + + sglist = sg_state->sglist + sg_state->next_sglist; + /* try to start the next DMA transfer */ + if (sg_state->next_sglist + 1 == sg_state->sglen) { + /* + * On the last sg, we handle the case where + * cam->img.pix.sizeimage % PAGE_ALIGN != 0 + */ + len = sg_state->len - sg_state->bytes_read; + } else { + len = sg_dma_len(sglist); + } + + if (omap24xxcam_dma_start(&sgdma->dma, + sg_dma_address(sglist), + len, + omap24xxcam_sgdma_callback, + (void *)sgslot)) { + /* DMA start failed */ + spin_unlock_irqrestore(&sgdma->lock, flags); + return; + } else { + unsigned long expires; + /* DMA start was successful */ + sg_state->next_sglist++; + sg_state->bytes_read += len; + sg_state->queued_sglist++; + + /* We start the reset timer */ + expires = jiffies + HZ; + mod_timer(&sgdma->reset_timer, expires); + } + } + queued_sgdma--; + sgslot = (sgslot + 1) % NUM_SG_DMA; + } + + spin_unlock_irqrestore(&sgdma->lock, flags); +} + +/* + * Queue a scatter-gather DMA transfer from the camera to memory. + * Returns zero if the transfer was successfully queued, or non-zero + * if all of the scatter-gather slots are already in use. + */ +int omap24xxcam_sgdma_queue(struct omap24xxcam_sgdma *sgdma, + const struct scatterlist *sglist, int sglen, + int len, sgdma_callback_t callback, void *arg) +{ + unsigned long flags; + struct sgdma_state *sg_state; + + if ((sglen < 0) || ((sglen > 0) && !sglist)) + return -EINVAL; + + spin_lock_irqsave(&sgdma->lock, flags); + + if (!sgdma->free_sgdma) { + spin_unlock_irqrestore(&sgdma->lock, flags); + return -EBUSY; + } + + sg_state = sgdma->sg_state + sgdma->next_sgdma; + + sg_state->sglist = sglist; + sg_state->sglen = sglen; + sg_state->next_sglist = 0; + sg_state->bytes_read = 0; + sg_state->len = len; + sg_state->queued_sglist = 0; + sg_state->csr = 0; + sg_state->callback = callback; + sg_state->arg = arg; + + sgdma->next_sgdma = (sgdma->next_sgdma + 1) % NUM_SG_DMA; + sgdma->free_sgdma--; + + spin_unlock_irqrestore(&sgdma->lock, flags); + + omap24xxcam_sgdma_process(sgdma); + + return 0; +} + +/* Sync scatter-gather DMA by aborting any DMA transfers currently in progress. + * Any queued scatter-gather DMA transactions that have not yet been started + * will remain queued. The DMA controller will be idle after this routine + * completes. When the scatter-gather queue is restarted, the next + * scatter-gather DMA transfer will begin at the start of a new transaction. + */ +void omap24xxcam_sgdma_sync(struct omap24xxcam_sgdma *sgdma) +{ + unsigned long flags; + int sgslot; + struct sgdma_state *sg_state; + u32 csr = CAMDMA_CSR_TRANS_ERR; + + /* stop any DMA transfers in progress */ + omap24xxcam_dma_stop(&sgdma->dma, csr); + + spin_lock_irqsave(&sgdma->lock, flags); + + if (sgdma->free_sgdma < NUM_SG_DMA) { + sgslot = (sgdma->next_sgdma + sgdma->free_sgdma) % NUM_SG_DMA; + sg_state = sgdma->sg_state + sgslot; + if (sg_state->next_sglist != 0) { + /* This DMA transfer was in progress, so abort it. */ + sgdma_callback_t callback = sg_state->callback; + void *arg = sg_state->arg; + sgdma->free_sgdma++; + if (callback) { + /* leave interrupts masked */ + spin_unlock(&sgdma->lock); + (*callback) (sgdma, csr, arg); + spin_lock(&sgdma->lock); + } + } + } + + spin_unlock_irqrestore(&sgdma->lock, flags); +} + +void omap24xxcam_sgdma_init(struct omap24xxcam_sgdma *sgdma, + void __iomem *base, + void (*reset_callback)(unsigned long data), + unsigned long reset_callback_data) +{ + int sg; + + spin_lock_init(&sgdma->lock); + sgdma->free_sgdma = NUM_SG_DMA; + sgdma->next_sgdma = 0; + for (sg = 0; sg < NUM_SG_DMA; sg++) { + sgdma->sg_state[sg].sglen = 0; + sgdma->sg_state[sg].next_sglist = 0; + sgdma->sg_state[sg].bytes_read = 0; + sgdma->sg_state[sg].queued_sglist = 0; + sgdma->sg_state[sg].csr = 0; + sgdma->sg_state[sg].callback = NULL; + sgdma->sg_state[sg].arg = NULL; + } + + omap24xxcam_dma_init(&sgdma->dma, base); + setup_timer(&sgdma->reset_timer, reset_callback, reset_callback_data); +} diff --git a/drivers/staging/media/omap24xx/omap24xxcam.c b/drivers/staging/media/omap24xx/omap24xxcam.c new file mode 100644 index 00000000000..d2b440c842b --- /dev/null +++ b/drivers/staging/media/omap24xx/omap24xxcam.c @@ -0,0 +1,1888 @@ +/* + * drivers/media/platform/omap24xxcam.c + * + * OMAP 2 camera block driver. + * + * Copyright (C) 2004 MontaVista Software, Inc. + * Copyright (C) 2004 Texas Instruments. + * Copyright (C) 2007-2008 Nokia Corporation. + * + * Contact: Sakari Ailus <sakari.ailus@nokia.com> + * + * Based on code from Andy Lowe <source@mvista.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#include <linux/delay.h> +#include <linux/kernel.h> +#include <linux/interrupt.h> +#include <linux/videodev2.h> +#include <linux/pci.h> /* needed for videobufs */ +#include <linux/platform_device.h> +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/slab.h> +#include <linux/sched.h> +#include <linux/module.h> + +#include <media/v4l2-common.h> +#include <media/v4l2-ioctl.h> + +#include "omap24xxcam.h" + +#define OMAP24XXCAM_VERSION "0.0.1" + +#define RESET_TIMEOUT_NS 10000 + +static void omap24xxcam_reset(struct omap24xxcam_device *cam); +static int omap24xxcam_sensor_if_enable(struct omap24xxcam_device *cam); +static void omap24xxcam_device_unregister(struct v4l2_int_device *s); +static int omap24xxcam_remove(struct platform_device *pdev); + +/* module parameters */ +static int video_nr = -1; /* video device minor (-1 ==> auto assign) */ +/* + * Maximum amount of memory to use for capture buffers. + * Default is 4800KB, enough to double-buffer SXGA. + */ +static int capture_mem = 1280 * 960 * 2 * 2; + +static struct v4l2_int_device omap24xxcam; + +/* + * + * Clocks. + * + */ + +static void omap24xxcam_clock_put(struct omap24xxcam_device *cam) +{ + if (cam->ick != NULL && !IS_ERR(cam->ick)) + clk_put(cam->ick); + if (cam->fck != NULL && !IS_ERR(cam->fck)) + clk_put(cam->fck); + + cam->ick = cam->fck = NULL; +} + +static int omap24xxcam_clock_get(struct omap24xxcam_device *cam) +{ + int rval = 0; + + cam->fck = clk_get(cam->dev, "fck"); + if (IS_ERR(cam->fck)) { + dev_err(cam->dev, "can't get camera fck"); + rval = PTR_ERR(cam->fck); + omap24xxcam_clock_put(cam); + return rval; + } + + cam->ick = clk_get(cam->dev, "ick"); + if (IS_ERR(cam->ick)) { + dev_err(cam->dev, "can't get camera ick"); + rval = PTR_ERR(cam->ick); + omap24xxcam_clock_put(cam); + } + + return rval; +} + +static void omap24xxcam_clock_on(struct omap24xxcam_device *cam) +{ + clk_enable(cam->fck); + clk_enable(cam->ick); +} + +static void omap24xxcam_clock_off(struct omap24xxcam_device *cam) +{ + clk_disable(cam->fck); + clk_disable(cam->ick); +} + +/* + * + * Camera core + * + */ + +/* + * Set xclk. + * + * To disable xclk, use value zero. + */ +static void omap24xxcam_core_xclk_set(const struct omap24xxcam_device *cam, + u32 xclk) +{ + if (xclk) { + u32 divisor = CAM_MCLK / xclk; + + if (divisor == 1) + omap24xxcam_reg_out(cam->mmio_base + CC_REG_OFFSET, + CC_CTRL_XCLK, + CC_CTRL_XCLK_DIV_BYPASS); + else + omap24xxcam_reg_out(cam->mmio_base + CC_REG_OFFSET, + CC_CTRL_XCLK, divisor); + } else + omap24xxcam_reg_out(cam->mmio_base + CC_REG_OFFSET, + CC_CTRL_XCLK, CC_CTRL_XCLK_DIV_STABLE_LOW); +} + +static void omap24xxcam_core_hwinit(const struct omap24xxcam_device *cam) +{ + /* + * Setting the camera core AUTOIDLE bit causes problems with frame + * synchronization, so we will clear the AUTOIDLE bit instead. + */ + omap24xxcam_reg_out(cam->mmio_base + CC_REG_OFFSET, CC_SYSCONFIG, + CC_SYSCONFIG_AUTOIDLE); + + /* program the camera interface DMA packet size */ + omap24xxcam_reg_out(cam->mmio_base + CC_REG_OFFSET, CC_CTRL_DMA, + CC_CTRL_DMA_EN | (DMA_THRESHOLD / 4 - 1)); + + /* enable camera core error interrupts */ + omap24xxcam_reg_out(cam->mmio_base + CC_REG_OFFSET, CC_IRQENABLE, + CC_IRQENABLE_FW_ERR_IRQ + | CC_IRQENABLE_FSC_ERR_IRQ + | CC_IRQENABLE_SSC_ERR_IRQ + | CC_IRQENABLE_FIFO_OF_IRQ); +} + +/* + * Enable the camera core. + * + * Data transfer to the camera DMA starts from next starting frame. + */ +static void omap24xxcam_core_enable(const struct omap24xxcam_device *cam) +{ + + omap24xxcam_reg_out(cam->mmio_base + CC_REG_OFFSET, CC_CTRL, + cam->cc_ctrl); +} + +/* + * Disable camera core. + * + * The data transfer will be stopped immediately (CC_CTRL_CC_RST). The + * core internal state machines will be reset. Use + * CC_CTRL_CC_FRAME_TRIG instead if you want to transfer the current + * frame completely. + */ +static void omap24xxcam_core_disable(const struct omap24xxcam_device *cam) +{ + omap24xxcam_reg_out(cam->mmio_base + CC_REG_OFFSET, CC_CTRL, + CC_CTRL_CC_RST); +} + +/* Interrupt service routine for camera core interrupts. */ +static void omap24xxcam_core_isr(struct omap24xxcam_device *cam) +{ + u32 cc_irqstatus; + const u32 cc_irqstatus_err = + CC_IRQSTATUS_FW_ERR_IRQ + | CC_IRQSTATUS_FSC_ERR_IRQ + | CC_IRQSTATUS_SSC_ERR_IRQ + | CC_IRQSTATUS_FIFO_UF_IRQ + | CC_IRQSTATUS_FIFO_OF_IRQ; + + cc_irqstatus = omap24xxcam_reg_in(cam->mmio_base + CC_REG_OFFSET, + CC_IRQSTATUS); + omap24xxcam_reg_out(cam->mmio_base + CC_REG_OFFSET, CC_IRQSTATUS, + cc_irqstatus); + + if (cc_irqstatus & cc_irqstatus_err + && !atomic_read(&cam->in_reset)) { + dev_dbg(cam->dev, "resetting camera, cc_irqstatus 0x%x\n", + cc_irqstatus); + omap24xxcam_reset(cam); + } +} + +/* + * + * videobuf_buffer handling. + * + * Memory for mmapped videobuf_buffers is not allocated + * conventionally, but by several kmalloc allocations and then + * creating the scatterlist on our own. User-space buffers are handled + * normally. + * + */ + +/* + * Free the memory-mapped buffer memory allocated for a + * videobuf_buffer and the associated scatterlist. + */ +static void omap24xxcam_vbq_free_mmap_buffer(struct videobuf_buffer *vb) +{ + struct videobuf_dmabuf *dma = videobuf_to_dma(vb); + size_t alloc_size; + struct page *page; + int i; + + if (dma->sglist == NULL) + return; + + i = dma->sglen; + while (i) { + i--; + alloc_size = sg_dma_len(&dma->sglist[i]); + page = sg_page(&dma->sglist[i]); + do { + ClearPageReserved(page++); + } while (alloc_size -= PAGE_SIZE); + __free_pages(sg_page(&dma->sglist[i]), + get_order(sg_dma_len(&dma->sglist[i]))); + } + + kfree(dma->sglist); + dma->sglist = NULL; +} + +/* Release all memory related to the videobuf_queue. */ +static void omap24xxcam_vbq_free_mmap_buffers(struct videobuf_queue *vbq) +{ + int i; + + mutex_lock(&vbq->vb_lock); + + for (i = 0; i < VIDEO_MAX_FRAME; i++) { + if (NULL == vbq->bufs[i]) + continue; + if (V4L2_MEMORY_MMAP != vbq->bufs[i]->memory) + continue; + vbq->ops->buf_release(vbq, vbq->bufs[i]); + omap24xxcam_vbq_free_mmap_buffer(vbq->bufs[i]); + kfree(vbq->bufs[i]); + vbq->bufs[i] = NULL; + } + + mutex_unlock(&vbq->vb_lock); + + videobuf_mmap_free(vbq); +} + +/* + * Allocate physically as contiguous as possible buffer for video + * frame and allocate and build DMA scatter-gather list for it. + */ +static int omap24xxcam_vbq_alloc_mmap_buffer(struct videobuf_buffer *vb) +{ + unsigned int order; + size_t alloc_size, size = vb->bsize; /* vb->bsize is page aligned */ + struct page *page; + int max_pages, err = 0, i = 0; + struct videobuf_dmabuf *dma = videobuf_to_dma(vb); + + /* + * allocate maximum size scatter-gather list. Note this is + * overhead. We may not use as many entries as we allocate + */ + max_pages = vb->bsize >> PAGE_SHIFT; + dma->sglist = kcalloc(max_pages, sizeof(*dma->sglist), GFP_KERNEL); + if (dma->sglist == NULL) { + err = -ENOMEM; + goto out; + } + + while (size) { + order = get_order(size); + /* + * do not over-allocate even if we would get larger + * contiguous chunk that way + */ + if ((PAGE_SIZE << order) > size) + order--; + + /* try to allocate as many contiguous pages as possible */ + page = alloc_pages(GFP_KERNEL, order); + /* if allocation fails, try to allocate smaller amount */ + while (page == NULL) { + order--; + page = alloc_pages(GFP_KERNEL, order); + if (page == NULL && !order) { + err = -ENOMEM; + goto out; + } + } + size -= (PAGE_SIZE << order); + + /* append allocated chunk of pages into scatter-gather list */ + sg_set_page(&dma->sglist[i], page, PAGE_SIZE << order, 0); + dma->sglen++; + i++; + + alloc_size = (PAGE_SIZE << order); + + /* clear pages before giving them to user space */ + memset(page_address(page), 0, alloc_size); + + /* mark allocated pages reserved */ + do { + SetPageReserved(page++); + } while (alloc_size -= PAGE_SIZE); + } + /* + * REVISIT: not fully correct to assign nr_pages == sglen but + * video-buf is passing nr_pages for e.g. unmap_sg calls + */ + dma->nr_pages = dma->sglen; + dma->direction = PCI_DMA_FROMDEVICE; + + return 0; + +out: + omap24xxcam_vbq_free_mmap_buffer(vb); + return err; +} + +static int omap24xxcam_vbq_alloc_mmap_buffers(struct videobuf_queue *vbq, + unsigned int count) +{ + int i, err = 0; + struct omap24xxcam_fh *fh = + container_of(vbq, struct omap24xxcam_fh, vbq); + + mutex_lock(&vbq->vb_lock); + + for (i = 0; i < count; i++) { + err = omap24xxcam_vbq_alloc_mmap_buffer(vbq->bufs[i]); + if (err) + goto out; + dev_dbg(fh->cam->dev, "sglen is %d for buffer %d\n", + videobuf_to_dma(vbq->bufs[i])->sglen, i); + } + + mutex_unlock(&vbq->vb_lock); + + return 0; +out: + while (i) { + i--; + omap24xxcam_vbq_free_mmap_buffer(vbq->bufs[i]); + } + + mutex_unlock(&vbq->vb_lock); + + return err; +} + +/* + * This routine is called from interrupt context when a scatter-gather DMA + * transfer of a videobuf_buffer completes. + */ +static void omap24xxcam_vbq_complete(struct omap24xxcam_sgdma *sgdma, + u32 csr, void *arg) +{ + struct omap24xxcam_device *cam = + container_of(sgdma, struct omap24xxcam_device, sgdma); + struct omap24xxcam_fh *fh = cam->streaming->private_data; + struct videobuf_buffer *vb = (struct videobuf_buffer *)arg; + const u32 csr_error = CAMDMA_CSR_MISALIGNED_ERR + | CAMDMA_CSR_SUPERVISOR_ERR | CAMDMA_CSR_SECURE_ERR + | CAMDMA_CSR_TRANS_ERR | CAMDMA_CSR_DROP; + unsigned long flags; + + spin_lock_irqsave(&cam->core_enable_disable_lock, flags); + if (--cam->sgdma_in_queue == 0) + omap24xxcam_core_disable(cam); + spin_unlock_irqrestore(&cam->core_enable_disable_lock, flags); + + v4l2_get_timestamp(&vb->ts); + vb->field_count = atomic_add_return(2, &fh->field_count); + if (csr & csr_error) { + vb->state = VIDEOBUF_ERROR; + if (!atomic_read(&fh->cam->in_reset)) { + dev_dbg(cam->dev, "resetting camera, csr 0x%x\n", csr); + omap24xxcam_reset(cam); + } + } else + vb->state = VIDEOBUF_DONE; + wake_up(&vb->done); +} + +static void omap24xxcam_vbq_release(struct videobuf_queue *vbq, + struct videobuf_buffer *vb) +{ + struct videobuf_dmabuf *dma = videobuf_to_dma(vb); + + /* wait for buffer, especially to get out of the sgdma queue */ + videobuf_waiton(vbq, vb, 0, 0); + if (vb->memory == V4L2_MEMORY_MMAP) { + dma_unmap_sg(vbq->dev, dma->sglist, dma->sglen, + dma->direction); + dma->direction = DMA_NONE; + } else { + videobuf_dma_unmap(vbq->dev, videobuf_to_dma(vb)); + videobuf_dma_free(videobuf_to_dma(vb)); + } + + vb->state = VIDEOBUF_NEEDS_INIT; +} + +/* + * Limit the number of available kernel image capture buffers based on the + * number requested, the currently selected image size, and the maximum + * amount of memory permitted for kernel capture buffers. + */ +static int omap24xxcam_vbq_setup(struct videobuf_queue *vbq, unsigned int *cnt, + unsigned int *size) +{ + struct omap24xxcam_fh *fh = vbq->priv_data; + + if (*cnt <= 0) + *cnt = VIDEO_MAX_FRAME; /* supply a default number of buffers */ + + if (*cnt > VIDEO_MAX_FRAME) + *cnt = VIDEO_MAX_FRAME; + + *size = fh->pix.sizeimage; + + /* accessing fh->cam->capture_mem is ok, it's constant */ + if (*size * *cnt > fh->cam->capture_mem) + *cnt = fh->cam->capture_mem / *size; + + return 0; +} + +static int omap24xxcam_dma_iolock(struct videobuf_queue *vbq, + struct videobuf_dmabuf *dma) +{ + int err = 0; + + dma->direction = PCI_DMA_FROMDEVICE; + if (!dma_map_sg(vbq->dev, dma->sglist, dma->sglen, dma->direction)) { + kfree(dma->sglist); + dma->sglist = NULL; + dma->sglen = 0; + err = -EIO; + } + + return err; +} + +static int omap24xxcam_vbq_prepare(struct videobuf_queue *vbq, + struct videobuf_buffer *vb, + enum v4l2_field field) +{ + struct omap24xxcam_fh *fh = vbq->priv_data; + int err = 0; + + /* + * Accessing pix here is okay since it's constant while + * streaming is on (and we only get called then). + */ + if (vb->baddr) { + /* This is a userspace buffer. */ + if (fh->pix.sizeimage > vb->bsize) { + /* The buffer isn't big enough. */ + err = -EINVAL; + } else + vb->size = fh->pix.sizeimage; + } else { + if (vb->state != VIDEOBUF_NEEDS_INIT) { + /* + * We have a kernel bounce buffer that has + * already been allocated. + */ + if (fh->pix.sizeimage > vb->size) { + /* + * The image size has been changed to + * a larger size since this buffer was + * allocated, so we need to free and + * reallocate it. + */ + omap24xxcam_vbq_release(vbq, vb); + vb->size = fh->pix.sizeimage; + } + } else { + /* We need to allocate a new kernel bounce buffer. */ + vb->size = fh->pix.sizeimage; + } + } + + if (err) + return err; + + vb->width = fh->pix.width; + vb->height = fh->pix.height; + vb->field = field; + + if (vb->state == VIDEOBUF_NEEDS_INIT) { + if (vb->memory == V4L2_MEMORY_MMAP) + /* + * we have built the scatter-gather list by ourself so + * do the scatter-gather mapping as well + */ + err = omap24xxcam_dma_iolock(vbq, videobuf_to_dma(vb)); + else + err = videobuf_iolock(vbq, vb, NULL); + } + + if (!err) + vb->state = VIDEOBUF_PREPARED; + else + omap24xxcam_vbq_release(vbq, vb); + + return err; +} + +static void omap24xxcam_vbq_queue(struct videobuf_queue *vbq, + struct videobuf_buffer *vb) +{ + struct omap24xxcam_fh *fh = vbq->priv_data; + struct omap24xxcam_device *cam = fh->cam; + enum videobuf_state state = vb->state; + unsigned long flags; + int err; + + /* + * FIXME: We're marking the buffer active since we have no + * pretty way of marking it active exactly when the + * scatter-gather transfer starts. + */ + vb->state = VIDEOBUF_ACTIVE; + + err = omap24xxcam_sgdma_queue(&fh->cam->sgdma, + videobuf_to_dma(vb)->sglist, + videobuf_to_dma(vb)->sglen, vb->size, + omap24xxcam_vbq_complete, vb); + + if (!err) { + spin_lock_irqsave(&cam->core_enable_disable_lock, flags); + if (++cam->sgdma_in_queue == 1 + && !atomic_read(&cam->in_reset)) + omap24xxcam_core_enable(cam); + spin_unlock_irqrestore(&cam->core_enable_disable_lock, flags); + } else { + /* + * Oops. We're not supposed to get any errors here. + * The only way we could get an error is if we ran out + * of scatter-gather DMA slots, but we are supposed to + * have at least as many scatter-gather DMA slots as + * video buffers so that can't happen. + */ + dev_err(cam->dev, "failed to queue a video buffer for dma!\n"); + dev_err(cam->dev, "likely a bug in the driver!\n"); + vb->state = state; + } +} + +static struct videobuf_queue_ops omap24xxcam_vbq_ops = { + .buf_setup = omap24xxcam_vbq_setup, + .buf_prepare = omap24xxcam_vbq_prepare, + .buf_queue = omap24xxcam_vbq_queue, + .buf_release = omap24xxcam_vbq_release, +}; + +/* + * + * OMAP main camera system + * + */ + +/* + * Reset camera block to power-on state. + */ +static void omap24xxcam_poweron_reset(struct omap24xxcam_device *cam) +{ + int max_loop = RESET_TIMEOUT_NS; + + /* Reset whole camera subsystem */ + omap24xxcam_reg_out(cam->mmio_base, + CAM_SYSCONFIG, + CAM_SYSCONFIG_SOFTRESET); + + /* Wait till it's finished */ + while (!(omap24xxcam_reg_in(cam->mmio_base, CAM_SYSSTATUS) + & CAM_SYSSTATUS_RESETDONE) + && --max_loop) { + ndelay(1); + } + + if (!(omap24xxcam_reg_in(cam->mmio_base, CAM_SYSSTATUS) + & CAM_SYSSTATUS_RESETDONE)) + dev_err(cam->dev, "camera soft reset timeout\n"); +} + +/* + * (Re)initialise the camera block. + */ +static void omap24xxcam_hwinit(struct omap24xxcam_device *cam) +{ + omap24xxcam_poweron_reset(cam); + + /* set the camera subsystem autoidle bit */ + omap24xxcam_reg_out(cam->mmio_base, CAM_SYSCONFIG, + CAM_SYSCONFIG_AUTOIDLE); + + /* set the camera MMU autoidle bit */ + omap24xxcam_reg_out(cam->mmio_base, + CAMMMU_REG_OFFSET + CAMMMU_SYSCONFIG, + CAMMMU_SYSCONFIG_AUTOIDLE); + + omap24xxcam_core_hwinit(cam); + + omap24xxcam_dma_hwinit(&cam->sgdma.dma); +} + +/* + * Callback for dma transfer stalling. + */ +static void omap24xxcam_stalled_dma_reset(unsigned long data) +{ + struct omap24xxcam_device *cam = (struct omap24xxcam_device *)data; + + if (!atomic_read(&cam->in_reset)) { + dev_dbg(cam->dev, "dma stalled, resetting camera\n"); + omap24xxcam_reset(cam); + } +} + +/* + * Stop capture. Mark we're doing a reset, stop DMA transfers and + * core. (No new scatter-gather transfers will be queued whilst + * in_reset is non-zero.) + * + * If omap24xxcam_capture_stop is called from several places at + * once, only the first call will have an effect. Similarly, the last + * call omap24xxcam_streaming_cont will have effect. + * + * Serialisation is ensured by using cam->core_enable_disable_lock. + */ +static void omap24xxcam_capture_stop(struct omap24xxcam_device *cam) +{ + unsigned long flags; + + spin_lock_irqsave(&cam->core_enable_disable_lock, flags); + + if (atomic_inc_return(&cam->in_reset) != 1) { + spin_unlock_irqrestore(&cam->core_enable_disable_lock, flags); + return; + } + + omap24xxcam_core_disable(cam); + + spin_unlock_irqrestore(&cam->core_enable_disable_lock, flags); + + omap24xxcam_sgdma_sync(&cam->sgdma); +} + +/* + * Reset and continue streaming. + * + * Note: Resetting the camera FIFO via the CC_RST bit in the CC_CTRL + * register is supposed to be sufficient to recover from a camera + * interface error, but it doesn't seem to be enough. If we only do + * that then subsequent image captures are out of sync by either one + * or two times DMA_THRESHOLD bytes. Resetting and re-initializing the + * entire camera subsystem prevents the problem with frame + * synchronization. + */ +static void omap24xxcam_capture_cont(struct omap24xxcam_device *cam) +{ + unsigned long flags; + + spin_lock_irqsave(&cam->core_enable_disable_lock, flags); + + if (atomic_read(&cam->in_reset) != 1) + goto out; + + omap24xxcam_hwinit(cam); + + omap24xxcam_sensor_if_enable(cam); + + omap24xxcam_sgdma_process(&cam->sgdma); + + if (cam->sgdma_in_queue) + omap24xxcam_core_enable(cam); + +out: + atomic_dec(&cam->in_reset); + spin_unlock_irqrestore(&cam->core_enable_disable_lock, flags); +} + +static ssize_t +omap24xxcam_streaming_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct omap24xxcam_device *cam = dev_get_drvdata(dev); + + return sprintf(buf, "%s\n", cam->streaming ? "active" : "inactive"); +} +static DEVICE_ATTR(streaming, S_IRUGO, omap24xxcam_streaming_show, NULL); + +/* + * Stop capture and restart it. I.e. reset the camera during use. + */ +static void omap24xxcam_reset(struct omap24xxcam_device *cam) +{ + omap24xxcam_capture_stop(cam); + omap24xxcam_capture_cont(cam); +} + +/* + * The main interrupt handler. + */ +static irqreturn_t omap24xxcam_isr(int irq, void *arg) +{ + struct omap24xxcam_device *cam = (struct omap24xxcam_device *)arg; + u32 irqstatus; + unsigned int irqhandled = 0; + + irqstatus = omap24xxcam_reg_in(cam->mmio_base, CAM_IRQSTATUS); + + if (irqstatus & + (CAM_IRQSTATUS_DMA_IRQ2 | CAM_IRQSTATUS_DMA_IRQ1 + | CAM_IRQSTATUS_DMA_IRQ0)) { + omap24xxcam_dma_isr(&cam->sgdma.dma); + irqhandled = 1; + } + if (irqstatus & CAM_IRQSTATUS_CC_IRQ) { + omap24xxcam_core_isr(cam); + irqhandled = 1; + } + if (irqstatus & CAM_IRQSTATUS_MMU_IRQ) + dev_err(cam->dev, "unhandled camera MMU interrupt!\n"); + + return IRQ_RETVAL(irqhandled); +} + +/* + * + * Sensor handling. + * + */ + +/* + * Enable the external sensor interface. Try to negotiate interface + * parameters with the sensor and start using the new ones. The calls + * to sensor_if_enable and sensor_if_disable need not to be balanced. + */ +static int omap24xxcam_sensor_if_enable(struct omap24xxcam_device *cam) +{ + int rval; + struct v4l2_ifparm p; + + rval = vidioc_int_g_ifparm(cam->sdev, &p); + if (rval) { + dev_err(cam->dev, "vidioc_int_g_ifparm failed with %d\n", rval); + return rval; + } + + cam->if_type = p.if_type; + + cam->cc_ctrl = CC_CTRL_CC_EN; + + switch (p.if_type) { + case V4L2_IF_TYPE_BT656: + if (p.u.bt656.frame_start_on_rising_vs) + cam->cc_ctrl |= CC_CTRL_NOBT_SYNCHRO; + if (p.u.bt656.bt_sync_correct) + cam->cc_ctrl |= CC_CTRL_BT_CORRECT; + if (p.u.bt656.swap) + cam->cc_ctrl |= CC_CTRL_PAR_ORDERCAM; + if (p.u.bt656.latch_clk_inv) + cam->cc_ctrl |= CC_CTRL_PAR_CLK_POL; + if (p.u.bt656.nobt_hs_inv) + cam->cc_ctrl |= CC_CTRL_NOBT_HS_POL; + if (p.u.bt656.nobt_vs_inv) + cam->cc_ctrl |= CC_CTRL_NOBT_VS_POL; + + switch (p.u.bt656.mode) { + case V4L2_IF_TYPE_BT656_MODE_NOBT_8BIT: + cam->cc_ctrl |= CC_CTRL_PAR_MODE_NOBT8; + break; + case V4L2_IF_TYPE_BT656_MODE_NOBT_10BIT: + cam->cc_ctrl |= CC_CTRL_PAR_MODE_NOBT10; + break; + case V4L2_IF_TYPE_BT656_MODE_NOBT_12BIT: + cam->cc_ctrl |= CC_CTRL_PAR_MODE_NOBT12; + break; + case V4L2_IF_TYPE_BT656_MODE_BT_8BIT: + cam->cc_ctrl |= CC_CTRL_PAR_MODE_BT8; + break; + case V4L2_IF_TYPE_BT656_MODE_BT_10BIT: + cam->cc_ctrl |= CC_CTRL_PAR_MODE_BT10; + break; + default: + dev_err(cam->dev, + "bt656 interface mode %d not supported\n", + p.u.bt656.mode); + return -EINVAL; + } + /* + * The clock rate that the sensor wants has changed. + * We have to adjust the xclk from OMAP 2 side to + * match the sensor's wish as closely as possible. + */ + if (p.u.bt656.clock_curr != cam->if_u.bt656.xclk) { + u32 xclk = p.u.bt656.clock_curr; + u32 divisor; + + if (xclk == 0) + return -EINVAL; + + if (xclk > CAM_MCLK) + xclk = CAM_MCLK; + + divisor = CAM_MCLK / xclk; + if (divisor * xclk < CAM_MCLK) + divisor++; + if (CAM_MCLK / divisor < p.u.bt656.clock_min + && divisor > 1) + divisor--; + if (divisor > 30) + divisor = 30; + + xclk = CAM_MCLK / divisor; + + if (xclk < p.u.bt656.clock_min + || xclk > p.u.bt656.clock_max) + return -EINVAL; + + cam->if_u.bt656.xclk = xclk; + } + omap24xxcam_core_xclk_set(cam, cam->if_u.bt656.xclk); + break; + default: + /* FIXME: how about other interfaces? */ + dev_err(cam->dev, "interface type %d not supported\n", + p.if_type); + return -EINVAL; + } + + return 0; +} + +static void omap24xxcam_sensor_if_disable(const struct omap24xxcam_device *cam) +{ + switch (cam->if_type) { + case V4L2_IF_TYPE_BT656: + omap24xxcam_core_xclk_set(cam, 0); + break; + } +} + +/* + * Initialise the sensor hardware. + */ +static int omap24xxcam_sensor_init(struct omap24xxcam_device *cam) +{ + int err = 0; + struct v4l2_int_device *sdev = cam->sdev; + + omap24xxcam_clock_on(cam); + err = omap24xxcam_sensor_if_enable(cam); + if (err) { + dev_err(cam->dev, "sensor interface could not be enabled at " + "initialisation, %d\n", err); + cam->sdev = NULL; + goto out; + } + + /* power up sensor during sensor initialization */ + vidioc_int_s_power(sdev, 1); + + err = vidioc_int_dev_init(sdev); + if (err) { + dev_err(cam->dev, "cannot initialize sensor, error %d\n", err); + /* Sensor init failed --- it's nonexistent to us! */ + cam->sdev = NULL; + goto out; + } + + dev_info(cam->dev, "sensor is %s\n", sdev->name); + +out: + omap24xxcam_sensor_if_disable(cam); + omap24xxcam_clock_off(cam); + + vidioc_int_s_power(sdev, 0); + + return err; +} + +static void omap24xxcam_sensor_exit(struct omap24xxcam_device *cam) +{ + if (cam->sdev) + vidioc_int_dev_exit(cam->sdev); +} + +static void omap24xxcam_sensor_disable(struct omap24xxcam_device *cam) +{ + omap24xxcam_sensor_if_disable(cam); + omap24xxcam_clock_off(cam); + vidioc_int_s_power(cam->sdev, 0); +} + +/* + * Power-up and configure camera sensor. It's ready for capturing now. + */ +static int omap24xxcam_sensor_enable(struct omap24xxcam_device *cam) +{ + int rval; + + omap24xxcam_clock_on(cam); + + omap24xxcam_sensor_if_enable(cam); + + rval = vidioc_int_s_power(cam->sdev, 1); + if (rval) + goto out; + + rval = vidioc_int_init(cam->sdev); + if (rval) + goto out; + + return 0; + +out: + omap24xxcam_sensor_disable(cam); + + return rval; +} + +static void omap24xxcam_sensor_reset_work(struct work_struct *work) +{ + struct omap24xxcam_device *cam = + container_of(work, struct omap24xxcam_device, + sensor_reset_work); + + if (atomic_read(&cam->reset_disable)) + return; + + omap24xxcam_capture_stop(cam); + + if (vidioc_int_reset(cam->sdev) == 0) { + vidioc_int_init(cam->sdev); + } else { + /* Can't reset it by vidioc_int_reset. */ + omap24xxcam_sensor_disable(cam); + omap24xxcam_sensor_enable(cam); + } + + omap24xxcam_capture_cont(cam); +} + +/* + * + * IOCTL interface. + * + */ + +static int vidioc_querycap(struct file *file, void *fh, + struct v4l2_capability *cap) +{ + struct omap24xxcam_fh *ofh = fh; + struct omap24xxcam_device *cam = ofh->cam; + + strlcpy(cap->driver, CAM_NAME, sizeof(cap->driver)); + strlcpy(cap->card, cam->vfd->name, sizeof(cap->card)); + cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING; + + return 0; +} + +static int vidioc_enum_fmt_vid_cap(struct file *file, void *fh, + struct v4l2_fmtdesc *f) +{ + struct omap24xxcam_fh *ofh = fh; + struct omap24xxcam_device *cam = ofh->cam; + int rval; + + rval = vidioc_int_enum_fmt_cap(cam->sdev, f); + + return rval; +} + +static int vidioc_g_fmt_vid_cap(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct omap24xxcam_fh *ofh = fh; + struct omap24xxcam_device *cam = ofh->cam; + int rval; + + mutex_lock(&cam->mutex); + rval = vidioc_int_g_fmt_cap(cam->sdev, f); + mutex_unlock(&cam->mutex); + + return rval; +} + +static int vidioc_s_fmt_vid_cap(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct omap24xxcam_fh *ofh = fh; + struct omap24xxcam_device *cam = ofh->cam; + int rval; + + mutex_lock(&cam->mutex); + if (cam->streaming) { + rval = -EBUSY; + goto out; + } + + rval = vidioc_int_s_fmt_cap(cam->sdev, f); + +out: + mutex_unlock(&cam->mutex); + + if (!rval) { + mutex_lock(&ofh->vbq.vb_lock); + ofh->pix = f->fmt.pix; + mutex_unlock(&ofh->vbq.vb_lock); + } + + memset(f, 0, sizeof(*f)); + vidioc_g_fmt_vid_cap(file, fh, f); + + return rval; +} + +static int vidioc_try_fmt_vid_cap(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct omap24xxcam_fh *ofh = fh; + struct omap24xxcam_device *cam = ofh->cam; + int rval; + + mutex_lock(&cam->mutex); + rval = vidioc_int_try_fmt_cap(cam->sdev, f); + mutex_unlock(&cam->mutex); + + return rval; +} + +static int vidioc_reqbufs(struct file *file, void *fh, + struct v4l2_requestbuffers *b) +{ + struct omap24xxcam_fh *ofh = fh; + struct omap24xxcam_device *cam = ofh->cam; + int rval; + + mutex_lock(&cam->mutex); + if (cam->streaming) { + mutex_unlock(&cam->mutex); + return -EBUSY; + } + + omap24xxcam_vbq_free_mmap_buffers(&ofh->vbq); + mutex_unlock(&cam->mutex); + + rval = videobuf_reqbufs(&ofh->vbq, b); + + /* + * Either videobuf_reqbufs failed or the buffers are not + * memory-mapped (which would need special attention). + */ + if (rval < 0 || b->memory != V4L2_MEMORY_MMAP) + goto out; + + rval = omap24xxcam_vbq_alloc_mmap_buffers(&ofh->vbq, rval); + if (rval) + omap24xxcam_vbq_free_mmap_buffers(&ofh->vbq); + +out: + return rval; +} + +static int vidioc_querybuf(struct file *file, void *fh, + struct v4l2_buffer *b) +{ + struct omap24xxcam_fh *ofh = fh; + + return videobuf_querybuf(&ofh->vbq, b); +} + +static int vidioc_qbuf(struct file *file, void *fh, struct v4l2_buffer *b) +{ + struct omap24xxcam_fh *ofh = fh; + + return videobuf_qbuf(&ofh->vbq, b); +} + +static int vidioc_dqbuf(struct file *file, void *fh, struct v4l2_buffer *b) +{ + struct omap24xxcam_fh *ofh = fh; + struct omap24xxcam_device *cam = ofh->cam; + struct videobuf_buffer *vb; + int rval; + +videobuf_dqbuf_again: + rval = videobuf_dqbuf(&ofh->vbq, b, file->f_flags & O_NONBLOCK); + if (rval) + goto out; + + vb = ofh->vbq.bufs[b->index]; + + mutex_lock(&cam->mutex); + /* _needs_reset returns -EIO if reset is required. */ + rval = vidioc_int_g_needs_reset(cam->sdev, (void *)vb->baddr); + mutex_unlock(&cam->mutex); + if (rval == -EIO) + schedule_work(&cam->sensor_reset_work); + else + rval = 0; + +out: + /* + * This is a hack. We don't want to show -EIO to the user + * space. Requeue the buffer and try again if we're not doing + * this in non-blocking mode. + */ + if (rval == -EIO) { + videobuf_qbuf(&ofh->vbq, b); + if (!(file->f_flags & O_NONBLOCK)) + goto videobuf_dqbuf_again; + /* + * We don't have a videobuf_buffer now --- maybe next + * time... + */ + rval = -EAGAIN; + } + + return rval; +} + +static int vidioc_streamon(struct file *file, void *fh, enum v4l2_buf_type i) +{ + struct omap24xxcam_fh *ofh = fh; + struct omap24xxcam_device *cam = ofh->cam; + int rval; + + mutex_lock(&cam->mutex); + if (cam->streaming) { + rval = -EBUSY; + goto out; + } + + rval = omap24xxcam_sensor_if_enable(cam); + if (rval) { + dev_dbg(cam->dev, "vidioc_int_g_ifparm failed\n"); + goto out; + } + + rval = videobuf_streamon(&ofh->vbq); + if (!rval) { + cam->streaming = file; + sysfs_notify(&cam->dev->kobj, NULL, "streaming"); + } + +out: + mutex_unlock(&cam->mutex); + + return rval; +} + +static int vidioc_streamoff(struct file *file, void *fh, enum v4l2_buf_type i) +{ + struct omap24xxcam_fh *ofh = fh; + struct omap24xxcam_device *cam = ofh->cam; + struct videobuf_queue *q = &ofh->vbq; + int rval; + + atomic_inc(&cam->reset_disable); + + flush_work(&cam->sensor_reset_work); + + rval = videobuf_streamoff(q); + if (!rval) { + mutex_lock(&cam->mutex); + cam->streaming = NULL; + mutex_unlock(&cam->mutex); + sysfs_notify(&cam->dev->kobj, NULL, "streaming"); + } + + atomic_dec(&cam->reset_disable); + + return rval; +} + +static int vidioc_enum_input(struct file *file, void *fh, + struct v4l2_input *inp) +{ + if (inp->index > 0) + return -EINVAL; + + strlcpy(inp->name, "camera", sizeof(inp->name)); + inp->type = V4L2_INPUT_TYPE_CAMERA; + + return 0; +} + +static int vidioc_g_input(struct file *file, void *fh, unsigned int *i) +{ + *i = 0; + + return 0; +} + +static int vidioc_s_input(struct file *file, void *fh, unsigned int i) +{ + if (i > 0) + return -EINVAL; + + return 0; +} + +static int vidioc_queryctrl(struct file *file, void *fh, + struct v4l2_queryctrl *a) +{ + struct omap24xxcam_fh *ofh = fh; + struct omap24xxcam_device *cam = ofh->cam; + int rval; + + rval = vidioc_int_queryctrl(cam->sdev, a); + + return rval; +} + +static int vidioc_g_ctrl(struct file *file, void *fh, + struct v4l2_control *a) +{ + struct omap24xxcam_fh *ofh = fh; + struct omap24xxcam_device *cam = ofh->cam; + int rval; + + mutex_lock(&cam->mutex); + rval = vidioc_int_g_ctrl(cam->sdev, a); + mutex_unlock(&cam->mutex); + + return rval; +} + +static int vidioc_s_ctrl(struct file *file, void *fh, + struct v4l2_control *a) +{ + struct omap24xxcam_fh *ofh = fh; + struct omap24xxcam_device *cam = ofh->cam; + int rval; + + mutex_lock(&cam->mutex); + rval = vidioc_int_s_ctrl(cam->sdev, a); + mutex_unlock(&cam->mutex); + + return rval; +} + +static int vidioc_g_parm(struct file *file, void *fh, + struct v4l2_streamparm *a) { + struct omap24xxcam_fh *ofh = fh; + struct omap24xxcam_device *cam = ofh->cam; + int rval; + + mutex_lock(&cam->mutex); + rval = vidioc_int_g_parm(cam->sdev, a); + mutex_unlock(&cam->mutex); + + return rval; +} + +static int vidioc_s_parm(struct file *file, void *fh, + struct v4l2_streamparm *a) +{ + struct omap24xxcam_fh *ofh = fh; + struct omap24xxcam_device *cam = ofh->cam; + struct v4l2_streamparm old_streamparm; + int rval; + + mutex_lock(&cam->mutex); + if (cam->streaming) { + rval = -EBUSY; + goto out; + } + + old_streamparm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + rval = vidioc_int_g_parm(cam->sdev, &old_streamparm); + if (rval) + goto out; + + rval = vidioc_int_s_parm(cam->sdev, a); + if (rval) + goto out; + + rval = omap24xxcam_sensor_if_enable(cam); + /* + * Revert to old streaming parameters if enabling sensor + * interface with the new ones failed. + */ + if (rval) + vidioc_int_s_parm(cam->sdev, &old_streamparm); + +out: + mutex_unlock(&cam->mutex); + + return rval; +} + +/* + * + * File operations. + * + */ + +static unsigned int omap24xxcam_poll(struct file *file, + struct poll_table_struct *wait) +{ + struct omap24xxcam_fh *fh = file->private_data; + struct omap24xxcam_device *cam = fh->cam; + struct videobuf_buffer *vb; + + mutex_lock(&cam->mutex); + if (cam->streaming != file) { + mutex_unlock(&cam->mutex); + return POLLERR; + } + mutex_unlock(&cam->mutex); + + mutex_lock(&fh->vbq.vb_lock); + if (list_empty(&fh->vbq.stream)) { + mutex_unlock(&fh->vbq.vb_lock); + return POLLERR; + } + vb = list_entry(fh->vbq.stream.next, struct videobuf_buffer, stream); + mutex_unlock(&fh->vbq.vb_lock); + + poll_wait(file, &vb->done, wait); + + if (vb->state == VIDEOBUF_DONE || vb->state == VIDEOBUF_ERROR) + return POLLIN | POLLRDNORM; + + return 0; +} + +static int omap24xxcam_mmap_buffers(struct file *file, + struct vm_area_struct *vma) +{ + struct omap24xxcam_fh *fh = file->private_data; + struct omap24xxcam_device *cam = fh->cam; + struct videobuf_queue *vbq = &fh->vbq; + unsigned int first, last, size, i, j; + int err = 0; + + mutex_lock(&cam->mutex); + if (cam->streaming) { + mutex_unlock(&cam->mutex); + return -EBUSY; + } + mutex_unlock(&cam->mutex); + mutex_lock(&vbq->vb_lock); + + /* look for first buffer to map */ + for (first = 0; first < VIDEO_MAX_FRAME; first++) { + if (NULL == vbq->bufs[first]) + continue; + if (V4L2_MEMORY_MMAP != vbq->bufs[first]->memory) + continue; + if (vbq->bufs[first]->boff == (vma->vm_pgoff << PAGE_SHIFT)) + break; + } + + /* look for last buffer to map */ + for (size = 0, last = first; last < VIDEO_MAX_FRAME; last++) { + if (NULL == vbq->bufs[last]) + continue; + if (V4L2_MEMORY_MMAP != vbq->bufs[last]->memory) + continue; + size += vbq->bufs[last]->bsize; + if (size == (vma->vm_end - vma->vm_start)) + break; + } + + size = 0; + for (i = first; i <= last && i < VIDEO_MAX_FRAME; i++) { + struct videobuf_dmabuf *dma = videobuf_to_dma(vbq->bufs[i]); + + for (j = 0; j < dma->sglen; j++) { + err = remap_pfn_range( + vma, vma->vm_start + size, + page_to_pfn(sg_page(&dma->sglist[j])), + sg_dma_len(&dma->sglist[j]), vma->vm_page_prot); + if (err) + goto out; + size += sg_dma_len(&dma->sglist[j]); + } + } + +out: + mutex_unlock(&vbq->vb_lock); + + return err; +} + +static int omap24xxcam_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct omap24xxcam_fh *fh = file->private_data; + int rval; + + /* let the video-buf mapper check arguments and set-up structures */ + rval = videobuf_mmap_mapper(&fh->vbq, vma); + if (rval) + return rval; + + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + + /* do mapping to our allocated buffers */ + rval = omap24xxcam_mmap_buffers(file, vma); + /* + * In case of error, free vma->vm_private_data allocated by + * videobuf_mmap_mapper. + */ + if (rval) + kfree(vma->vm_private_data); + + return rval; +} + +static int omap24xxcam_open(struct file *file) +{ + struct omap24xxcam_device *cam = omap24xxcam.priv; + struct omap24xxcam_fh *fh; + struct v4l2_format format; + + if (!cam || !cam->vfd) + return -ENODEV; + + fh = kzalloc(sizeof(*fh), GFP_KERNEL); + if (fh == NULL) + return -ENOMEM; + + mutex_lock(&cam->mutex); + if (cam->sdev == NULL || !try_module_get(cam->sdev->module)) { + mutex_unlock(&cam->mutex); + goto out_try_module_get; + } + + if (atomic_inc_return(&cam->users) == 1) { + omap24xxcam_hwinit(cam); + if (omap24xxcam_sensor_enable(cam)) { + mutex_unlock(&cam->mutex); + goto out_omap24xxcam_sensor_enable; + } + } + mutex_unlock(&cam->mutex); + + fh->cam = cam; + mutex_lock(&cam->mutex); + vidioc_int_g_fmt_cap(cam->sdev, &format); + mutex_unlock(&cam->mutex); + /* FIXME: how about fh->pix when there are more users? */ + fh->pix = format.fmt.pix; + + file->private_data = fh; + + spin_lock_init(&fh->vbq_lock); + + videobuf_queue_sg_init(&fh->vbq, &omap24xxcam_vbq_ops, NULL, + &fh->vbq_lock, V4L2_BUF_TYPE_VIDEO_CAPTURE, + V4L2_FIELD_NONE, + sizeof(struct videobuf_buffer), fh, NULL); + + return 0; + +out_omap24xxcam_sensor_enable: + omap24xxcam_poweron_reset(cam); + module_put(cam->sdev->module); + +out_try_module_get: + kfree(fh); + + return -ENODEV; +} + +static int omap24xxcam_release(struct file *file) +{ + struct omap24xxcam_fh *fh = file->private_data; + struct omap24xxcam_device *cam = fh->cam; + + atomic_inc(&cam->reset_disable); + + flush_work(&cam->sensor_reset_work); + + /* stop streaming capture */ + videobuf_streamoff(&fh->vbq); + + mutex_lock(&cam->mutex); + if (cam->streaming == file) { + cam->streaming = NULL; + mutex_unlock(&cam->mutex); + sysfs_notify(&cam->dev->kobj, NULL, "streaming"); + } else { + mutex_unlock(&cam->mutex); + } + + atomic_dec(&cam->reset_disable); + + omap24xxcam_vbq_free_mmap_buffers(&fh->vbq); + + /* + * Make sure the reset work we might have scheduled is not + * pending! It may be run *only* if we have users. (And it may + * not be scheduled anymore since streaming is already + * disabled.) + */ + flush_work(&cam->sensor_reset_work); + + mutex_lock(&cam->mutex); + if (atomic_dec_return(&cam->users) == 0) { + omap24xxcam_sensor_disable(cam); + omap24xxcam_poweron_reset(cam); + } + mutex_unlock(&cam->mutex); + + file->private_data = NULL; + + module_put(cam->sdev->module); + kfree(fh); + + return 0; +} + +static struct v4l2_file_operations omap24xxcam_fops = { + .ioctl = video_ioctl2, + .poll = omap24xxcam_poll, + .mmap = omap24xxcam_mmap, + .open = omap24xxcam_open, + .release = omap24xxcam_release, +}; + +/* + * + * Power management. + * + */ + +#ifdef CONFIG_PM +static int omap24xxcam_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct omap24xxcam_device *cam = platform_get_drvdata(pdev); + + if (atomic_read(&cam->users) == 0) + return 0; + + if (!atomic_read(&cam->reset_disable)) + omap24xxcam_capture_stop(cam); + + omap24xxcam_sensor_disable(cam); + omap24xxcam_poweron_reset(cam); + + return 0; +} + +static int omap24xxcam_resume(struct platform_device *pdev) +{ + struct omap24xxcam_device *cam = platform_get_drvdata(pdev); + + if (atomic_read(&cam->users) == 0) + return 0; + + omap24xxcam_hwinit(cam); + omap24xxcam_sensor_enable(cam); + + if (!atomic_read(&cam->reset_disable)) + omap24xxcam_capture_cont(cam); + + return 0; +} +#endif /* CONFIG_PM */ + +static const struct v4l2_ioctl_ops omap24xxcam_ioctl_fops = { + .vidioc_querycap = vidioc_querycap, + .vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_vid_cap, + .vidioc_g_fmt_vid_cap = vidioc_g_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = vidioc_s_fmt_vid_cap, + .vidioc_try_fmt_vid_cap = vidioc_try_fmt_vid_cap, + .vidioc_reqbufs = vidioc_reqbufs, + .vidioc_querybuf = vidioc_querybuf, + .vidioc_qbuf = vidioc_qbuf, + .vidioc_dqbuf = vidioc_dqbuf, + .vidioc_streamon = vidioc_streamon, + .vidioc_streamoff = vidioc_streamoff, + .vidioc_enum_input = vidioc_enum_input, + .vidioc_g_input = vidioc_g_input, + .vidioc_s_input = vidioc_s_input, + .vidioc_queryctrl = vidioc_queryctrl, + .vidioc_g_ctrl = vidioc_g_ctrl, + .vidioc_s_ctrl = vidioc_s_ctrl, + .vidioc_g_parm = vidioc_g_parm, + .vidioc_s_parm = vidioc_s_parm, +}; + +/* + * + * Camera device (i.e. /dev/video). + * + */ + +static int omap24xxcam_device_register(struct v4l2_int_device *s) +{ + struct omap24xxcam_device *cam = s->u.slave->master->priv; + struct video_device *vfd; + int rval; + + /* We already have a slave. */ + if (cam->sdev) + return -EBUSY; + + cam->sdev = s; + + if (device_create_file(cam->dev, &dev_attr_streaming) != 0) { + dev_err(cam->dev, "could not register sysfs entry\n"); + rval = -EBUSY; + goto err; + } + + /* initialize the video_device struct */ + vfd = cam->vfd = video_device_alloc(); + if (!vfd) { + dev_err(cam->dev, "could not allocate video device struct\n"); + rval = -ENOMEM; + goto err; + } + vfd->release = video_device_release; + + vfd->v4l2_dev = &cam->v4l2_dev; + + strlcpy(vfd->name, CAM_NAME, sizeof(vfd->name)); + vfd->fops = &omap24xxcam_fops; + vfd->ioctl_ops = &omap24xxcam_ioctl_fops; + + omap24xxcam_hwinit(cam); + + rval = omap24xxcam_sensor_init(cam); + if (rval) + goto err; + + if (video_register_device(vfd, VFL_TYPE_GRABBER, video_nr) < 0) { + dev_err(cam->dev, "could not register V4L device\n"); + rval = -EBUSY; + goto err; + } + + omap24xxcam_poweron_reset(cam); + + dev_info(cam->dev, "registered device %s\n", + video_device_node_name(vfd)); + + return 0; + +err: + omap24xxcam_device_unregister(s); + + return rval; +} + +static void omap24xxcam_device_unregister(struct v4l2_int_device *s) +{ + struct omap24xxcam_device *cam = s->u.slave->master->priv; + + omap24xxcam_sensor_exit(cam); + + if (cam->vfd) { + if (!video_is_registered(cam->vfd)) { + /* + * The device was never registered, so release the + * video_device struct directly. + */ + video_device_release(cam->vfd); + } else { + /* + * The unregister function will release the + * video_device struct as well as + * unregistering it. + */ + video_unregister_device(cam->vfd); + } + cam->vfd = NULL; + } + + device_remove_file(cam->dev, &dev_attr_streaming); + + cam->sdev = NULL; +} + +static struct v4l2_int_master omap24xxcam_master = { + .attach = omap24xxcam_device_register, + .detach = omap24xxcam_device_unregister, +}; + +static struct v4l2_int_device omap24xxcam = { + .module = THIS_MODULE, + .name = CAM_NAME, + .type = v4l2_int_type_master, + .u = { + .master = &omap24xxcam_master + }, +}; + +/* + * + * Driver initialisation and deinitialisation. + * + */ + +static int omap24xxcam_probe(struct platform_device *pdev) +{ + struct omap24xxcam_device *cam; + struct resource *mem; + int irq; + + cam = kzalloc(sizeof(*cam), GFP_KERNEL); + if (!cam) { + dev_err(&pdev->dev, "could not allocate memory\n"); + goto err; + } + + platform_set_drvdata(pdev, cam); + + cam->dev = &pdev->dev; + + if (v4l2_device_register(&pdev->dev, &cam->v4l2_dev)) { + dev_err(&pdev->dev, "v4l2_device_register failed\n"); + goto err; + } + + /* + * Impose a lower limit on the amount of memory allocated for + * capture. We require at least enough memory to double-buffer + * QVGA (300KB). + */ + if (capture_mem < 320 * 240 * 2 * 2) + capture_mem = 320 * 240 * 2 * 2; + cam->capture_mem = capture_mem; + + /* request the mem region for the camera registers */ + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!mem) { + dev_err(cam->dev, "no mem resource?\n"); + goto err; + } + if (!request_mem_region(mem->start, resource_size(mem), pdev->name)) { + dev_err(cam->dev, + "cannot reserve camera register I/O region\n"); + goto err; + } + cam->mmio_base_phys = mem->start; + cam->mmio_size = resource_size(mem); + + /* map the region */ + cam->mmio_base = ioremap_nocache(cam->mmio_base_phys, cam->mmio_size); + if (!cam->mmio_base) { + dev_err(cam->dev, "cannot map camera register I/O region\n"); + goto err; + } + + irq = platform_get_irq(pdev, 0); + if (irq <= 0) { + dev_err(cam->dev, "no irq for camera?\n"); + goto err; + } + + /* install the interrupt service routine */ + if (request_irq(irq, omap24xxcam_isr, 0, CAM_NAME, cam)) { + dev_err(cam->dev, + "could not install interrupt service routine\n"); + goto err; + } + cam->irq = irq; + + if (omap24xxcam_clock_get(cam)) + goto err; + + INIT_WORK(&cam->sensor_reset_work, omap24xxcam_sensor_reset_work); + + mutex_init(&cam->mutex); + spin_lock_init(&cam->core_enable_disable_lock); + + omap24xxcam_sgdma_init(&cam->sgdma, + cam->mmio_base + CAMDMA_REG_OFFSET, + omap24xxcam_stalled_dma_reset, + (unsigned long)cam); + + omap24xxcam.priv = cam; + + if (v4l2_int_device_register(&omap24xxcam)) + goto err; + + return 0; + +err: + omap24xxcam_remove(pdev); + return -ENODEV; +} + +static int omap24xxcam_remove(struct platform_device *pdev) +{ + struct omap24xxcam_device *cam = platform_get_drvdata(pdev); + + if (!cam) + return 0; + + if (omap24xxcam.priv != NULL) + v4l2_int_device_unregister(&omap24xxcam); + omap24xxcam.priv = NULL; + + omap24xxcam_clock_put(cam); + + if (cam->irq) { + free_irq(cam->irq, cam); + cam->irq = 0; + } + + if (cam->mmio_base) { + iounmap((void *)cam->mmio_base); + cam->mmio_base = 0; + } + + if (cam->mmio_base_phys) { + release_mem_region(cam->mmio_base_phys, cam->mmio_size); + cam->mmio_base_phys = 0; + } + + v4l2_device_unregister(&cam->v4l2_dev); + + kfree(cam); + + return 0; +} + +static struct platform_driver omap24xxcam_driver = { + .probe = omap24xxcam_probe, + .remove = omap24xxcam_remove, +#ifdef CONFIG_PM + .suspend = omap24xxcam_suspend, + .resume = omap24xxcam_resume, +#endif + .driver = { + .name = CAM_NAME, + .owner = THIS_MODULE, + }, +}; + +module_platform_driver(omap24xxcam_driver); + +MODULE_AUTHOR("Sakari Ailus <sakari.ailus@nokia.com>"); +MODULE_DESCRIPTION("OMAP24xx Video for Linux camera driver"); +MODULE_LICENSE("GPL"); +MODULE_VERSION(OMAP24XXCAM_VERSION); +module_param(video_nr, int, 0); +MODULE_PARM_DESC(video_nr, + "Minor number for video device (-1 ==> auto assign)"); +module_param(capture_mem, int, 0); +MODULE_PARM_DESC(capture_mem, "Maximum amount of memory for capture " + "buffers (default 4800kiB)"); diff --git a/drivers/staging/media/omap24xx/omap24xxcam.h b/drivers/staging/media/omap24xx/omap24xxcam.h new file mode 100644 index 00000000000..233bb40cfec --- /dev/null +++ b/drivers/staging/media/omap24xx/omap24xxcam.h @@ -0,0 +1,596 @@ +/* + * drivers/media/platform/omap24xxcam.h + * + * Copyright (C) 2004 MontaVista Software, Inc. + * Copyright (C) 2004 Texas Instruments. + * Copyright (C) 2007 Nokia Corporation. + * + * Contact: Sakari Ailus <sakari.ailus@nokia.com> + * + * Based on code from Andy Lowe <source@mvista.com>. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#ifndef OMAP24XXCAM_H +#define OMAP24XXCAM_H + +#include <media/videobuf-dma-sg.h> +#include <media/v4l2-device.h> +#include "v4l2-int-device.h" + +/* + * + * General driver related definitions. + * + */ + +#define CAM_NAME "omap24xxcam" + +#define CAM_MCLK 96000000 + +/* number of bytes transferred per DMA request */ +#define DMA_THRESHOLD 32 + +/* + * NUM_CAMDMA_CHANNELS is the number of logical channels provided by + * the camera DMA controller. + */ +#define NUM_CAMDMA_CHANNELS 4 + +/* + * NUM_SG_DMA is the number of scatter-gather DMA transfers that can + * be queued. (We don't have any overlay sglists now.) + */ +#define NUM_SG_DMA (VIDEO_MAX_FRAME) + +/* + * + * Register definitions. + * + */ + +/* subsystem register block offsets */ +#define CC_REG_OFFSET 0x00000400 +#define CAMDMA_REG_OFFSET 0x00000800 +#define CAMMMU_REG_OFFSET 0x00000C00 + +/* define camera subsystem register offsets */ +#define CAM_REVISION 0x000 +#define CAM_SYSCONFIG 0x010 +#define CAM_SYSSTATUS 0x014 +#define CAM_IRQSTATUS 0x018 +#define CAM_GPO 0x040 +#define CAM_GPI 0x050 + +/* define camera core register offsets */ +#define CC_REVISION 0x000 +#define CC_SYSCONFIG 0x010 +#define CC_SYSSTATUS 0x014 +#define CC_IRQSTATUS 0x018 +#define CC_IRQENABLE 0x01C +#define CC_CTRL 0x040 +#define CC_CTRL_DMA 0x044 +#define CC_CTRL_XCLK 0x048 +#define CC_FIFODATA 0x04C +#define CC_TEST 0x050 +#define CC_GENPAR 0x054 +#define CC_CCPFSCR 0x058 +#define CC_CCPFECR 0x05C +#define CC_CCPLSCR 0x060 +#define CC_CCPLECR 0x064 +#define CC_CCPDFR 0x068 + +/* define camera dma register offsets */ +#define CAMDMA_REVISION 0x000 +#define CAMDMA_IRQSTATUS_L0 0x008 +#define CAMDMA_IRQSTATUS_L1 0x00C +#define CAMDMA_IRQSTATUS_L2 0x010 +#define CAMDMA_IRQSTATUS_L3 0x014 +#define CAMDMA_IRQENABLE_L0 0x018 +#define CAMDMA_IRQENABLE_L1 0x01C +#define CAMDMA_IRQENABLE_L2 0x020 +#define CAMDMA_IRQENABLE_L3 0x024 +#define CAMDMA_SYSSTATUS 0x028 +#define CAMDMA_OCP_SYSCONFIG 0x02C +#define CAMDMA_CAPS_0 0x064 +#define CAMDMA_CAPS_2 0x06C +#define CAMDMA_CAPS_3 0x070 +#define CAMDMA_CAPS_4 0x074 +#define CAMDMA_GCR 0x078 +#define CAMDMA_CCR(n) (0x080 + (n)*0x60) +#define CAMDMA_CLNK_CTRL(n) (0x084 + (n)*0x60) +#define CAMDMA_CICR(n) (0x088 + (n)*0x60) +#define CAMDMA_CSR(n) (0x08C + (n)*0x60) +#define CAMDMA_CSDP(n) (0x090 + (n)*0x60) +#define CAMDMA_CEN(n) (0x094 + (n)*0x60) +#define CAMDMA_CFN(n) (0x098 + (n)*0x60) +#define CAMDMA_CSSA(n) (0x09C + (n)*0x60) +#define CAMDMA_CDSA(n) (0x0A0 + (n)*0x60) +#define CAMDMA_CSEI(n) (0x0A4 + (n)*0x60) +#define CAMDMA_CSFI(n) (0x0A8 + (n)*0x60) +#define CAMDMA_CDEI(n) (0x0AC + (n)*0x60) +#define CAMDMA_CDFI(n) (0x0B0 + (n)*0x60) +#define CAMDMA_CSAC(n) (0x0B4 + (n)*0x60) +#define CAMDMA_CDAC(n) (0x0B8 + (n)*0x60) +#define CAMDMA_CCEN(n) (0x0BC + (n)*0x60) +#define CAMDMA_CCFN(n) (0x0C0 + (n)*0x60) +#define CAMDMA_COLOR(n) (0x0C4 + (n)*0x60) + +/* define camera mmu register offsets */ +#define CAMMMU_REVISION 0x000 +#define CAMMMU_SYSCONFIG 0x010 +#define CAMMMU_SYSSTATUS 0x014 +#define CAMMMU_IRQSTATUS 0x018 +#define CAMMMU_IRQENABLE 0x01C +#define CAMMMU_WALKING_ST 0x040 +#define CAMMMU_CNTL 0x044 +#define CAMMMU_FAULT_AD 0x048 +#define CAMMMU_TTB 0x04C +#define CAMMMU_LOCK 0x050 +#define CAMMMU_LD_TLB 0x054 +#define CAMMMU_CAM 0x058 +#define CAMMMU_RAM 0x05C +#define CAMMMU_GFLUSH 0x060 +#define CAMMMU_FLUSH_ENTRY 0x064 +#define CAMMMU_READ_CAM 0x068 +#define CAMMMU_READ_RAM 0x06C +#define CAMMMU_EMU_FAULT_AD 0x070 + +/* Define bit fields within selected registers */ +#define CAM_REVISION_MAJOR (15 << 4) +#define CAM_REVISION_MAJOR_SHIFT 4 +#define CAM_REVISION_MINOR (15 << 0) +#define CAM_REVISION_MINOR_SHIFT 0 + +#define CAM_SYSCONFIG_SOFTRESET (1 << 1) +#define CAM_SYSCONFIG_AUTOIDLE (1 << 0) + +#define CAM_SYSSTATUS_RESETDONE (1 << 0) + +#define CAM_IRQSTATUS_CC_IRQ (1 << 4) +#define CAM_IRQSTATUS_MMU_IRQ (1 << 3) +#define CAM_IRQSTATUS_DMA_IRQ2 (1 << 2) +#define CAM_IRQSTATUS_DMA_IRQ1 (1 << 1) +#define CAM_IRQSTATUS_DMA_IRQ0 (1 << 0) + +#define CAM_GPO_CAM_S_P_EN (1 << 1) +#define CAM_GPO_CAM_CCP_MODE (1 << 0) + +#define CAM_GPI_CC_DMA_REQ1 (1 << 24) +#define CAP_GPI_CC_DMA_REQ0 (1 << 23) +#define CAP_GPI_CAM_MSTANDBY (1 << 21) +#define CAP_GPI_CAM_WAIT (1 << 20) +#define CAP_GPI_CAM_S_DATA (1 << 17) +#define CAP_GPI_CAM_S_CLK (1 << 16) +#define CAP_GPI_CAM_P_DATA (0xFFF << 3) +#define CAP_GPI_CAM_P_DATA_SHIFT 3 +#define CAP_GPI_CAM_P_VS (1 << 2) +#define CAP_GPI_CAM_P_HS (1 << 1) +#define CAP_GPI_CAM_P_CLK (1 << 0) + +#define CC_REVISION_MAJOR (15 << 4) +#define CC_REVISION_MAJOR_SHIFT 4 +#define CC_REVISION_MINOR (15 << 0) +#define CC_REVISION_MINOR_SHIFT 0 + +#define CC_SYSCONFIG_SIDLEMODE (3 << 3) +#define CC_SYSCONFIG_SIDLEMODE_FIDLE (0 << 3) +#define CC_SYSCONFIG_SIDLEMODE_NIDLE (1 << 3) +#define CC_SYSCONFIG_SOFTRESET (1 << 1) +#define CC_SYSCONFIG_AUTOIDLE (1 << 0) + +#define CC_SYSSTATUS_RESETDONE (1 << 0) + +#define CC_IRQSTATUS_FS_IRQ (1 << 19) +#define CC_IRQSTATUS_LE_IRQ (1 << 18) +#define CC_IRQSTATUS_LS_IRQ (1 << 17) +#define CC_IRQSTATUS_FE_IRQ (1 << 16) +#define CC_IRQSTATUS_FW_ERR_IRQ (1 << 10) +#define CC_IRQSTATUS_FSC_ERR_IRQ (1 << 9) +#define CC_IRQSTATUS_SSC_ERR_IRQ (1 << 8) +#define CC_IRQSTATUS_FIFO_NOEMPTY_IRQ (1 << 4) +#define CC_IRQSTATUS_FIFO_FULL_IRQ (1 << 3) +#define CC_IRQSTATUS_FIFO_THR_IRQ (1 << 2) +#define CC_IRQSTATUS_FIFO_OF_IRQ (1 << 1) +#define CC_IRQSTATUS_FIFO_UF_IRQ (1 << 0) + +#define CC_IRQENABLE_FS_IRQ (1 << 19) +#define CC_IRQENABLE_LE_IRQ (1 << 18) +#define CC_IRQENABLE_LS_IRQ (1 << 17) +#define CC_IRQENABLE_FE_IRQ (1 << 16) +#define CC_IRQENABLE_FW_ERR_IRQ (1 << 10) +#define CC_IRQENABLE_FSC_ERR_IRQ (1 << 9) +#define CC_IRQENABLE_SSC_ERR_IRQ (1 << 8) +#define CC_IRQENABLE_FIFO_NOEMPTY_IRQ (1 << 4) +#define CC_IRQENABLE_FIFO_FULL_IRQ (1 << 3) +#define CC_IRQENABLE_FIFO_THR_IRQ (1 << 2) +#define CC_IRQENABLE_FIFO_OF_IRQ (1 << 1) +#define CC_IRQENABLE_FIFO_UF_IRQ (1 << 0) + +#define CC_CTRL_CC_ONE_SHOT (1 << 20) +#define CC_CTRL_CC_IF_SYNCHRO (1 << 19) +#define CC_CTRL_CC_RST (1 << 18) +#define CC_CTRL_CC_FRAME_TRIG (1 << 17) +#define CC_CTRL_CC_EN (1 << 16) +#define CC_CTRL_NOBT_SYNCHRO (1 << 13) +#define CC_CTRL_BT_CORRECT (1 << 12) +#define CC_CTRL_PAR_ORDERCAM (1 << 11) +#define CC_CTRL_PAR_CLK_POL (1 << 10) +#define CC_CTRL_NOBT_HS_POL (1 << 9) +#define CC_CTRL_NOBT_VS_POL (1 << 8) +#define CC_CTRL_PAR_MODE (7 << 1) +#define CC_CTRL_PAR_MODE_SHIFT 1 +#define CC_CTRL_PAR_MODE_NOBT8 (0 << 1) +#define CC_CTRL_PAR_MODE_NOBT10 (1 << 1) +#define CC_CTRL_PAR_MODE_NOBT12 (2 << 1) +#define CC_CTRL_PAR_MODE_BT8 (4 << 1) +#define CC_CTRL_PAR_MODE_BT10 (5 << 1) +#define CC_CTRL_PAR_MODE_FIFOTEST (7 << 1) +#define CC_CTRL_CCP_MODE (1 << 0) + +#define CC_CTRL_DMA_EN (1 << 8) +#define CC_CTRL_DMA_FIFO_THRESHOLD (0x7F << 0) +#define CC_CTRL_DMA_FIFO_THRESHOLD_SHIFT 0 + +#define CC_CTRL_XCLK_DIV (0x1F << 0) +#define CC_CTRL_XCLK_DIV_SHIFT 0 +#define CC_CTRL_XCLK_DIV_STABLE_LOW (0 << 0) +#define CC_CTRL_XCLK_DIV_STABLE_HIGH (1 << 0) +#define CC_CTRL_XCLK_DIV_BYPASS (31 << 0) + +#define CC_TEST_FIFO_RD_POINTER (0xFF << 24) +#define CC_TEST_FIFO_RD_POINTER_SHIFT 24 +#define CC_TEST_FIFO_WR_POINTER (0xFF << 16) +#define CC_TEST_FIFO_WR_POINTER_SHIFT 16 +#define CC_TEST_FIFO_LEVEL (0xFF << 8) +#define CC_TEST_FIFO_LEVEL_SHIFT 8 +#define CC_TEST_FIFO_LEVEL_PEAK (0xFF << 0) +#define CC_TEST_FIFO_LEVEL_PEAK_SHIFT 0 + +#define CC_GENPAR_FIFO_DEPTH (7 << 0) +#define CC_GENPAR_FIFO_DEPTH_SHIFT 0 + +#define CC_CCPDFR_ALPHA (0xFF << 8) +#define CC_CCPDFR_ALPHA_SHIFT 8 +#define CC_CCPDFR_DATAFORMAT (15 << 0) +#define CC_CCPDFR_DATAFORMAT_SHIFT 0 +#define CC_CCPDFR_DATAFORMAT_YUV422BE (0 << 0) +#define CC_CCPDFR_DATAFORMAT_YUV422 (1 << 0) +#define CC_CCPDFR_DATAFORMAT_YUV420 (2 << 0) +#define CC_CCPDFR_DATAFORMAT_RGB444 (4 << 0) +#define CC_CCPDFR_DATAFORMAT_RGB565 (5 << 0) +#define CC_CCPDFR_DATAFORMAT_RGB888NDE (6 << 0) +#define CC_CCPDFR_DATAFORMAT_RGB888 (7 << 0) +#define CC_CCPDFR_DATAFORMAT_RAW8NDE (8 << 0) +#define CC_CCPDFR_DATAFORMAT_RAW8 (9 << 0) +#define CC_CCPDFR_DATAFORMAT_RAW10NDE (10 << 0) +#define CC_CCPDFR_DATAFORMAT_RAW10 (11 << 0) +#define CC_CCPDFR_DATAFORMAT_RAW12NDE (12 << 0) +#define CC_CCPDFR_DATAFORMAT_RAW12 (13 << 0) +#define CC_CCPDFR_DATAFORMAT_JPEG8 (15 << 0) + +#define CAMDMA_REVISION_MAJOR (15 << 4) +#define CAMDMA_REVISION_MAJOR_SHIFT 4 +#define CAMDMA_REVISION_MINOR (15 << 0) +#define CAMDMA_REVISION_MINOR_SHIFT 0 + +#define CAMDMA_OCP_SYSCONFIG_MIDLEMODE (3 << 12) +#define CAMDMA_OCP_SYSCONFIG_MIDLEMODE_FSTANDBY (0 << 12) +#define CAMDMA_OCP_SYSCONFIG_MIDLEMODE_NSTANDBY (1 << 12) +#define CAMDMA_OCP_SYSCONFIG_MIDLEMODE_SSTANDBY (2 << 12) +#define CAMDMA_OCP_SYSCONFIG_FUNC_CLOCK (1 << 9) +#define CAMDMA_OCP_SYSCONFIG_OCP_CLOCK (1 << 8) +#define CAMDMA_OCP_SYSCONFIG_EMUFREE (1 << 5) +#define CAMDMA_OCP_SYSCONFIG_SIDLEMODE (3 << 3) +#define CAMDMA_OCP_SYSCONFIG_SIDLEMODE_FIDLE (0 << 3) +#define CAMDMA_OCP_SYSCONFIG_SIDLEMODE_NIDLE (1 << 3) +#define CAMDMA_OCP_SYSCONFIG_SIDLEMODE_SIDLE (2 << 3) +#define CAMDMA_OCP_SYSCONFIG_SOFTRESET (1 << 1) +#define CAMDMA_OCP_SYSCONFIG_AUTOIDLE (1 << 0) + +#define CAMDMA_SYSSTATUS_RESETDONE (1 << 0) + +#define CAMDMA_GCR_ARBITRATION_RATE (0xFF << 16) +#define CAMDMA_GCR_ARBITRATION_RATE_SHIFT 16 +#define CAMDMA_GCR_MAX_CHANNEL_FIFO_DEPTH (0xFF << 0) +#define CAMDMA_GCR_MAX_CHANNEL_FIFO_DEPTH_SHIFT 0 + +#define CAMDMA_CCR_SEL_SRC_DST_SYNC (1 << 24) +#define CAMDMA_CCR_PREFETCH (1 << 23) +#define CAMDMA_CCR_SUPERVISOR (1 << 22) +#define CAMDMA_CCR_SECURE (1 << 21) +#define CAMDMA_CCR_BS (1 << 18) +#define CAMDMA_CCR_TRANSPARENT_COPY_ENABLE (1 << 17) +#define CAMDMA_CCR_CONSTANT_FILL_ENABLE (1 << 16) +#define CAMDMA_CCR_DST_AMODE (3 << 14) +#define CAMDMA_CCR_DST_AMODE_CONST_ADDR (0 << 14) +#define CAMDMA_CCR_DST_AMODE_POST_INC (1 << 14) +#define CAMDMA_CCR_DST_AMODE_SGL_IDX (2 << 14) +#define CAMDMA_CCR_DST_AMODE_DBL_IDX (3 << 14) +#define CAMDMA_CCR_SRC_AMODE (3 << 12) +#define CAMDMA_CCR_SRC_AMODE_CONST_ADDR (0 << 12) +#define CAMDMA_CCR_SRC_AMODE_POST_INC (1 << 12) +#define CAMDMA_CCR_SRC_AMODE_SGL_IDX (2 << 12) +#define CAMDMA_CCR_SRC_AMODE_DBL_IDX (3 << 12) +#define CAMDMA_CCR_WR_ACTIVE (1 << 10) +#define CAMDMA_CCR_RD_ACTIVE (1 << 9) +#define CAMDMA_CCR_SUSPEND_SENSITIVE (1 << 8) +#define CAMDMA_CCR_ENABLE (1 << 7) +#define CAMDMA_CCR_PRIO (1 << 6) +#define CAMDMA_CCR_FS (1 << 5) +#define CAMDMA_CCR_SYNCHRO ((3 << 19) | (31 << 0)) +#define CAMDMA_CCR_SYNCHRO_CAMERA 0x01 + +#define CAMDMA_CLNK_CTRL_ENABLE_LNK (1 << 15) +#define CAMDMA_CLNK_CTRL_NEXTLCH_ID (0x1F << 0) +#define CAMDMA_CLNK_CTRL_NEXTLCH_ID_SHIFT 0 + +#define CAMDMA_CICR_MISALIGNED_ERR_IE (1 << 11) +#define CAMDMA_CICR_SUPERVISOR_ERR_IE (1 << 10) +#define CAMDMA_CICR_SECURE_ERR_IE (1 << 9) +#define CAMDMA_CICR_TRANS_ERR_IE (1 << 8) +#define CAMDMA_CICR_PACKET_IE (1 << 7) +#define CAMDMA_CICR_BLOCK_IE (1 << 5) +#define CAMDMA_CICR_LAST_IE (1 << 4) +#define CAMDMA_CICR_FRAME_IE (1 << 3) +#define CAMDMA_CICR_HALF_IE (1 << 2) +#define CAMDMA_CICR_DROP_IE (1 << 1) + +#define CAMDMA_CSR_MISALIGNED_ERR (1 << 11) +#define CAMDMA_CSR_SUPERVISOR_ERR (1 << 10) +#define CAMDMA_CSR_SECURE_ERR (1 << 9) +#define CAMDMA_CSR_TRANS_ERR (1 << 8) +#define CAMDMA_CSR_PACKET (1 << 7) +#define CAMDMA_CSR_SYNC (1 << 6) +#define CAMDMA_CSR_BLOCK (1 << 5) +#define CAMDMA_CSR_LAST (1 << 4) +#define CAMDMA_CSR_FRAME (1 << 3) +#define CAMDMA_CSR_HALF (1 << 2) +#define CAMDMA_CSR_DROP (1 << 1) + +#define CAMDMA_CSDP_SRC_ENDIANNESS (1 << 21) +#define CAMDMA_CSDP_SRC_ENDIANNESS_LOCK (1 << 20) +#define CAMDMA_CSDP_DST_ENDIANNESS (1 << 19) +#define CAMDMA_CSDP_DST_ENDIANNESS_LOCK (1 << 18) +#define CAMDMA_CSDP_WRITE_MODE (3 << 16) +#define CAMDMA_CSDP_WRITE_MODE_WRNP (0 << 16) +#define CAMDMA_CSDP_WRITE_MODE_POSTED (1 << 16) +#define CAMDMA_CSDP_WRITE_MODE_POSTED_LAST_WRNP (2 << 16) +#define CAMDMA_CSDP_DST_BURST_EN (3 << 14) +#define CAMDMA_CSDP_DST_BURST_EN_1 (0 << 14) +#define CAMDMA_CSDP_DST_BURST_EN_16 (1 << 14) +#define CAMDMA_CSDP_DST_BURST_EN_32 (2 << 14) +#define CAMDMA_CSDP_DST_BURST_EN_64 (3 << 14) +#define CAMDMA_CSDP_DST_PACKED (1 << 13) +#define CAMDMA_CSDP_WR_ADD_TRSLT (15 << 9) +#define CAMDMA_CSDP_WR_ADD_TRSLT_ENABLE_MREQADD (3 << 9) +#define CAMDMA_CSDP_SRC_BURST_EN (3 << 7) +#define CAMDMA_CSDP_SRC_BURST_EN_1 (0 << 7) +#define CAMDMA_CSDP_SRC_BURST_EN_16 (1 << 7) +#define CAMDMA_CSDP_SRC_BURST_EN_32 (2 << 7) +#define CAMDMA_CSDP_SRC_BURST_EN_64 (3 << 7) +#define CAMDMA_CSDP_SRC_PACKED (1 << 6) +#define CAMDMA_CSDP_RD_ADD_TRSLT (15 << 2) +#define CAMDMA_CSDP_RD_ADD_TRSLT_ENABLE_MREQADD (3 << 2) +#define CAMDMA_CSDP_DATA_TYPE (3 << 0) +#define CAMDMA_CSDP_DATA_TYPE_8BITS (0 << 0) +#define CAMDMA_CSDP_DATA_TYPE_16BITS (1 << 0) +#define CAMDMA_CSDP_DATA_TYPE_32BITS (2 << 0) + +#define CAMMMU_SYSCONFIG_AUTOIDLE (1 << 0) + +/* + * + * Declarations. + * + */ + +/* forward declarations */ +struct omap24xxcam_sgdma; +struct omap24xxcam_dma; + +typedef void (*sgdma_callback_t)(struct omap24xxcam_sgdma *cam, + u32 status, void *arg); +typedef void (*dma_callback_t)(struct omap24xxcam_dma *cam, + u32 status, void *arg); + +struct channel_state { + dma_callback_t callback; + void *arg; +}; + +/* sgdma state for each of the possible videobuf_buffers + 2 overlays */ +struct sgdma_state { + const struct scatterlist *sglist; + int sglen; /* number of sglist entries */ + int next_sglist; /* index of next sglist entry to process */ + unsigned int bytes_read; /* number of bytes read */ + unsigned int len; /* total length of sglist (excluding + * bytes due to page alignment) */ + int queued_sglist; /* number of sglist entries queued for DMA */ + u32 csr; /* DMA return code */ + sgdma_callback_t callback; + void *arg; +}; + +/* physical DMA channel management */ +struct omap24xxcam_dma { + spinlock_t lock; /* Lock for the whole structure. */ + + void __iomem *base; /* base address for dma controller */ + + /* While dma_stop!=0, an attempt to start a new DMA transfer will + * fail. + */ + atomic_t dma_stop; + int free_dmach; /* number of dma channels free */ + int next_dmach; /* index of next dma channel to use */ + struct channel_state ch_state[NUM_CAMDMA_CHANNELS]; +}; + +/* scatter-gather DMA (scatterlist stuff) management */ +struct omap24xxcam_sgdma { + struct omap24xxcam_dma dma; + + spinlock_t lock; /* Lock for the fields below. */ + int free_sgdma; /* number of free sg dma slots */ + int next_sgdma; /* index of next sg dma slot to use */ + struct sgdma_state sg_state[NUM_SG_DMA]; + + /* Reset timer data */ + struct timer_list reset_timer; +}; + +/* per-device data structure */ +struct omap24xxcam_device { + /*** mutex ***/ + /* + * mutex serialises access to this structure. Also camera + * opening and releasing is synchronised by this. + */ + struct mutex mutex; + + struct v4l2_device v4l2_dev; + + /*** general driver state information ***/ + atomic_t users; + /* + * Lock to serialise core enabling and disabling and access to + * sgdma_in_queue. + */ + spinlock_t core_enable_disable_lock; + /* + * Number or sgdma requests in scatter-gather queue, protected + * by the lock above. + */ + int sgdma_in_queue; + /* + * Sensor interface parameters: interface type, CC_CTRL + * register value and interface specific data. + */ + int if_type; + union { + struct parallel { + u32 xclk; + } bt656; + } if_u; + u32 cc_ctrl; + + /*** subsystem structures ***/ + struct omap24xxcam_sgdma sgdma; + + /*** hardware resources ***/ + unsigned int irq; + void __iomem *mmio_base; + unsigned long mmio_base_phys; + unsigned long mmio_size; + + /*** interfaces and device ***/ + struct v4l2_int_device *sdev; + struct device *dev; + struct video_device *vfd; + + /*** camera and sensor reset related stuff ***/ + struct work_struct sensor_reset_work; + /* + * We're in the middle of a reset. Don't enable core if this + * is non-zero! This exists to help decisionmaking in a case + * where videobuf_qbuf is called while we are in the middle of + * a reset. + */ + atomic_t in_reset; + /* + * Non-zero if we don't want any resets for now. Used to + * prevent reset work to run when we're about to stop + * streaming. + */ + atomic_t reset_disable; + + /*** video device parameters ***/ + int capture_mem; + + /*** camera module clocks ***/ + struct clk *fck; + struct clk *ick; + + /*** capture data ***/ + /* file handle, if streaming is on */ + struct file *streaming; +}; + +/* Per-file handle data. */ +struct omap24xxcam_fh { + spinlock_t vbq_lock; /* spinlock for the videobuf queue */ + struct videobuf_queue vbq; + struct v4l2_pix_format pix; /* serialise pix by vbq->lock */ + atomic_t field_count; /* field counter for videobuf_buffer */ + /* accessing cam here doesn't need serialisation: it's constant */ + struct omap24xxcam_device *cam; +}; + +/* + * + * Register I/O functions. + * + */ + +static inline u32 omap24xxcam_reg_in(u32 __iomem *base, u32 offset) +{ + return readl(base + offset); +} + +static inline u32 omap24xxcam_reg_out(u32 __iomem *base, u32 offset, + u32 val) +{ + writel(val, base + offset); + return val; +} + +static inline u32 omap24xxcam_reg_merge(u32 __iomem *base, u32 offset, + u32 val, u32 mask) +{ + u32 __iomem *addr = base + offset; + u32 new_val = (readl(addr) & ~mask) | (val & mask); + + writel(new_val, addr); + return new_val; +} + +/* + * + * Function prototypes. + * + */ + +/* dma prototypes */ + +void omap24xxcam_dma_hwinit(struct omap24xxcam_dma *dma); +void omap24xxcam_dma_isr(struct omap24xxcam_dma *dma); + +/* sgdma prototypes */ + +void omap24xxcam_sgdma_process(struct omap24xxcam_sgdma *sgdma); +int omap24xxcam_sgdma_queue(struct omap24xxcam_sgdma *sgdma, + const struct scatterlist *sglist, int sglen, + int len, sgdma_callback_t callback, void *arg); +void omap24xxcam_sgdma_sync(struct omap24xxcam_sgdma *sgdma); +void omap24xxcam_sgdma_init(struct omap24xxcam_sgdma *sgdma, + void __iomem *base, + void (*reset_callback)(unsigned long data), + unsigned long reset_callback_data); +void omap24xxcam_sgdma_exit(struct omap24xxcam_sgdma *sgdma); + +#endif diff --git a/drivers/staging/media/omap24xx/tcm825x.c b/drivers/staging/media/omap24xx/tcm825x.c new file mode 100644 index 00000000000..3367ccd1d1e --- /dev/null +++ b/drivers/staging/media/omap24xx/tcm825x.c @@ -0,0 +1,937 @@ +/* + * drivers/media/i2c/tcm825x.c + * + * TCM825X camera sensor driver. + * + * Copyright (C) 2007 Nokia Corporation. + * + * Contact: Sakari Ailus <sakari.ailus@nokia.com> + * + * Based on code from David Cohen <david.cohen@indt.org.br> + * + * This driver was based on ov9640 sensor driver from MontaVista + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#include <linux/i2c.h> +#include <linux/module.h> +#include "v4l2-int-device.h" + +#include "tcm825x.h" + +/* + * The sensor has two fps modes: the lower one just gives half the fps + * at the same xclk than the high one. + */ +#define MAX_FPS 30 +#define MIN_FPS 8 +#define MAX_HALF_FPS (MAX_FPS / 2) +#define HIGH_FPS_MODE_LOWER_LIMIT 14 +#define DEFAULT_FPS MAX_HALF_FPS + +struct tcm825x_sensor { + const struct tcm825x_platform_data *platform_data; + struct v4l2_int_device *v4l2_int_device; + struct i2c_client *i2c_client; + struct v4l2_pix_format pix; + struct v4l2_fract timeperframe; +}; + +/* list of image formats supported by TCM825X sensor */ +static const struct v4l2_fmtdesc tcm825x_formats[] = { + { + .description = "YUYV (YUV 4:2:2), packed", + .pixelformat = V4L2_PIX_FMT_UYVY, + }, { + /* Note: V4L2 defines RGB565 as: + * + * Byte 0 Byte 1 + * g2 g1 g0 r4 r3 r2 r1 r0 b4 b3 b2 b1 b0 g5 g4 g3 + * + * We interpret RGB565 as: + * + * Byte 0 Byte 1 + * g2 g1 g0 b4 b3 b2 b1 b0 r4 r3 r2 r1 r0 g5 g4 g3 + */ + .description = "RGB565, le", + .pixelformat = V4L2_PIX_FMT_RGB565, + }, +}; + +#define TCM825X_NUM_CAPTURE_FORMATS ARRAY_SIZE(tcm825x_formats) + +/* + * TCM825X register configuration for all combinations of pixel format and + * image size + */ +static const struct tcm825x_reg subqcif = { 0x20, TCM825X_PICSIZ }; +static const struct tcm825x_reg qcif = { 0x18, TCM825X_PICSIZ }; +static const struct tcm825x_reg cif = { 0x14, TCM825X_PICSIZ }; +static const struct tcm825x_reg qqvga = { 0x0c, TCM825X_PICSIZ }; +static const struct tcm825x_reg qvga = { 0x04, TCM825X_PICSIZ }; +static const struct tcm825x_reg vga = { 0x00, TCM825X_PICSIZ }; + +static const struct tcm825x_reg yuv422 = { 0x00, TCM825X_PICFMT }; +static const struct tcm825x_reg rgb565 = { 0x02, TCM825X_PICFMT }; + +/* Our own specific controls */ +#define V4L2_CID_ALC V4L2_CID_PRIVATE_BASE +#define V4L2_CID_H_EDGE_EN (V4L2_CID_PRIVATE_BASE + 1) +#define V4L2_CID_V_EDGE_EN (V4L2_CID_PRIVATE_BASE + 2) +#define V4L2_CID_LENS (V4L2_CID_PRIVATE_BASE + 3) +#define V4L2_CID_MAX_EXPOSURE_TIME (V4L2_CID_PRIVATE_BASE + 4) +#define V4L2_CID_LAST_PRIV V4L2_CID_MAX_EXPOSURE_TIME + +/* Video controls */ +static struct vcontrol { + struct v4l2_queryctrl qc; + u16 reg; + u16 start_bit; +} video_control[] = { + { + { + .id = V4L2_CID_GAIN, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Gain", + .minimum = 0, + .maximum = 63, + .step = 1, + }, + .reg = TCM825X_AG, + .start_bit = 0, + }, + { + { + .id = V4L2_CID_RED_BALANCE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Red Balance", + .minimum = 0, + .maximum = 255, + .step = 1, + }, + .reg = TCM825X_MRG, + .start_bit = 0, + }, + { + { + .id = V4L2_CID_BLUE_BALANCE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Blue Balance", + .minimum = 0, + .maximum = 255, + .step = 1, + }, + .reg = TCM825X_MBG, + .start_bit = 0, + }, + { + { + .id = V4L2_CID_AUTO_WHITE_BALANCE, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "Auto White Balance", + .minimum = 0, + .maximum = 1, + .step = 0, + }, + .reg = TCM825X_AWBSW, + .start_bit = 7, + }, + { + { + .id = V4L2_CID_EXPOSURE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Exposure Time", + .minimum = 0, + .maximum = 0x1fff, + .step = 1, + }, + .reg = TCM825X_ESRSPD_U, + .start_bit = 0, + }, + { + { + .id = V4L2_CID_HFLIP, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "Mirror Image", + .minimum = 0, + .maximum = 1, + .step = 0, + }, + .reg = TCM825X_H_INV, + .start_bit = 6, + }, + { + { + .id = V4L2_CID_VFLIP, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "Vertical Flip", + .minimum = 0, + .maximum = 1, + .step = 0, + }, + .reg = TCM825X_V_INV, + .start_bit = 7, + }, + /* Private controls */ + { + { + .id = V4L2_CID_ALC, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "Auto Luminance Control", + .minimum = 0, + .maximum = 1, + .step = 0, + }, + .reg = TCM825X_ALCSW, + .start_bit = 7, + }, + { + { + .id = V4L2_CID_H_EDGE_EN, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Horizontal Edge Enhancement", + .minimum = 0, + .maximum = 0xff, + .step = 1, + }, + .reg = TCM825X_HDTG, + .start_bit = 0, + }, + { + { + .id = V4L2_CID_V_EDGE_EN, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Vertical Edge Enhancement", + .minimum = 0, + .maximum = 0xff, + .step = 1, + }, + .reg = TCM825X_VDTG, + .start_bit = 0, + }, + { + { + .id = V4L2_CID_LENS, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Lens Shading Compensation", + .minimum = 0, + .maximum = 0x3f, + .step = 1, + }, + .reg = TCM825X_LENS, + .start_bit = 0, + }, + { + { + .id = V4L2_CID_MAX_EXPOSURE_TIME, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Maximum Exposure Time", + .minimum = 0, + .maximum = 0x3, + .step = 1, + }, + .reg = TCM825X_ESRLIM, + .start_bit = 5, + }, +}; + + +static const struct tcm825x_reg *tcm825x_siz_reg[NUM_IMAGE_SIZES] = { + &subqcif, &qqvga, &qcif, &qvga, &cif, &vga }; + +static const struct tcm825x_reg *tcm825x_fmt_reg[NUM_PIXEL_FORMATS] = { + &yuv422, &rgb565 }; + +/* + * Read a value from a register in an TCM825X sensor device. The value is + * returned in 'val'. + * Returns zero if successful, or non-zero otherwise. + */ +static int tcm825x_read_reg(struct i2c_client *client, int reg) +{ + int err; + struct i2c_msg msg[2]; + u8 reg_buf, data_buf = 0; + + if (!client->adapter) + return -ENODEV; + + msg[0].addr = client->addr; + msg[0].flags = 0; + msg[0].len = 1; + msg[0].buf = ®_buf; + msg[1].addr = client->addr; + msg[1].flags = I2C_M_RD; + msg[1].len = 1; + msg[1].buf = &data_buf; + + reg_buf = reg; + + err = i2c_transfer(client->adapter, msg, 2); + if (err < 0) + return err; + return data_buf; +} + +/* + * Write a value to a register in an TCM825X sensor device. + * Returns zero if successful, or non-zero otherwise. + */ +static int tcm825x_write_reg(struct i2c_client *client, u8 reg, u8 val) +{ + int err; + struct i2c_msg msg[1]; + unsigned char data[2]; + + if (!client->adapter) + return -ENODEV; + + msg->addr = client->addr; + msg->flags = 0; + msg->len = 2; + msg->buf = data; + data[0] = reg; + data[1] = val; + err = i2c_transfer(client->adapter, msg, 1); + if (err >= 0) + return 0; + return err; +} + +static int __tcm825x_write_reg_mask(struct i2c_client *client, + u8 reg, u8 val, u8 mask) +{ + int rc; + + /* need to do read - modify - write */ + rc = tcm825x_read_reg(client, reg); + if (rc < 0) + return rc; + + rc &= (~mask); /* Clear the masked bits */ + val &= mask; /* Enforce mask on value */ + val |= rc; + + /* write the new value to the register */ + rc = tcm825x_write_reg(client, reg, val); + if (rc) + return rc; + + return 0; +} + +#define tcm825x_write_reg_mask(client, regmask, val) \ + __tcm825x_write_reg_mask(client, TCM825X_ADDR((regmask)), val, \ + TCM825X_MASK((regmask))) + + +/* + * Initialize a list of TCM825X registers. + * The list of registers is terminated by the pair of values + * { TCM825X_REG_TERM, TCM825X_VAL_TERM }. + * Returns zero if successful, or non-zero otherwise. + */ +static int tcm825x_write_default_regs(struct i2c_client *client, + const struct tcm825x_reg *reglist) +{ + int err; + const struct tcm825x_reg *next = reglist; + + while (!((next->reg == TCM825X_REG_TERM) + && (next->val == TCM825X_VAL_TERM))) { + err = tcm825x_write_reg(client, next->reg, next->val); + if (err) { + dev_err(&client->dev, "register writing failed\n"); + return err; + } + next++; + } + + return 0; +} + +static struct vcontrol *find_vctrl(int id) +{ + int i; + + if (id < V4L2_CID_BASE) + return NULL; + + for (i = 0; i < ARRAY_SIZE(video_control); i++) + if (video_control[i].qc.id == id) + return &video_control[i]; + + return NULL; +} + +/* + * Find the best match for a requested image capture size. The best match + * is chosen as the nearest match that has the same number or fewer pixels + * as the requested size, or the smallest image size if the requested size + * has fewer pixels than the smallest image. + */ +static enum image_size tcm825x_find_size(struct v4l2_int_device *s, + unsigned int width, + unsigned int height) +{ + enum image_size isize; + unsigned long pixels = width * height; + struct tcm825x_sensor *sensor = s->priv; + + for (isize = subQCIF; isize < VGA; isize++) { + if (tcm825x_sizes[isize + 1].height + * tcm825x_sizes[isize + 1].width > pixels) { + dev_dbg(&sensor->i2c_client->dev, "size %d\n", isize); + + return isize; + } + } + + dev_dbg(&sensor->i2c_client->dev, "format default VGA\n"); + + return VGA; +} + +/* + * Configure the TCM825X for current image size, pixel format, and + * frame period. fper is the frame period (in seconds) expressed as a + * fraction. Returns zero if successful, or non-zero otherwise. The + * actual frame period is returned in fper. + */ +static int tcm825x_configure(struct v4l2_int_device *s) +{ + struct tcm825x_sensor *sensor = s->priv; + struct v4l2_pix_format *pix = &sensor->pix; + enum image_size isize = tcm825x_find_size(s, pix->width, pix->height); + struct v4l2_fract *fper = &sensor->timeperframe; + enum pixel_format pfmt; + int err; + u32 tgt_fps; + u8 val; + + /* common register initialization */ + err = tcm825x_write_default_regs( + sensor->i2c_client, sensor->platform_data->default_regs()); + if (err) + return err; + + /* configure image size */ + val = tcm825x_siz_reg[isize]->val; + dev_dbg(&sensor->i2c_client->dev, + "configuring image size %d\n", isize); + err = tcm825x_write_reg_mask(sensor->i2c_client, + tcm825x_siz_reg[isize]->reg, val); + if (err) + return err; + + /* configure pixel format */ + switch (pix->pixelformat) { + default: + case V4L2_PIX_FMT_RGB565: + pfmt = RGB565; + break; + case V4L2_PIX_FMT_UYVY: + pfmt = YUV422; + break; + } + + dev_dbg(&sensor->i2c_client->dev, + "configuring pixel format %d\n", pfmt); + val = tcm825x_fmt_reg[pfmt]->val; + + err = tcm825x_write_reg_mask(sensor->i2c_client, + tcm825x_fmt_reg[pfmt]->reg, val); + if (err) + return err; + + /* + * For frame rate < 15, the FPS reg (addr 0x02, bit 7) must be + * set. Frame rate will be halved from the normal. + */ + tgt_fps = fper->denominator / fper->numerator; + if (tgt_fps <= HIGH_FPS_MODE_LOWER_LIMIT) { + val = tcm825x_read_reg(sensor->i2c_client, 0x02); + val |= 0x80; + tcm825x_write_reg(sensor->i2c_client, 0x02, val); + } + + return 0; +} + +static int ioctl_queryctrl(struct v4l2_int_device *s, + struct v4l2_queryctrl *qc) +{ + struct vcontrol *control; + + control = find_vctrl(qc->id); + + if (control == NULL) + return -EINVAL; + + *qc = control->qc; + + return 0; +} + +static int ioctl_g_ctrl(struct v4l2_int_device *s, + struct v4l2_control *vc) +{ + struct tcm825x_sensor *sensor = s->priv; + struct i2c_client *client = sensor->i2c_client; + int val, r; + struct vcontrol *lvc; + + /* exposure time is special, spread across 2 registers */ + if (vc->id == V4L2_CID_EXPOSURE) { + int val_lower, val_upper; + + val_upper = tcm825x_read_reg(client, + TCM825X_ADDR(TCM825X_ESRSPD_U)); + if (val_upper < 0) + return val_upper; + val_lower = tcm825x_read_reg(client, + TCM825X_ADDR(TCM825X_ESRSPD_L)); + if (val_lower < 0) + return val_lower; + + vc->value = ((val_upper & 0x1f) << 8) | (val_lower); + return 0; + } + + lvc = find_vctrl(vc->id); + if (lvc == NULL) + return -EINVAL; + + r = tcm825x_read_reg(client, TCM825X_ADDR(lvc->reg)); + if (r < 0) + return r; + val = r & TCM825X_MASK(lvc->reg); + val >>= lvc->start_bit; + + if (val < 0) + return val; + + if (vc->id == V4L2_CID_HFLIP || vc->id == V4L2_CID_VFLIP) + val ^= sensor->platform_data->is_upside_down(); + + vc->value = val; + return 0; +} + +static int ioctl_s_ctrl(struct v4l2_int_device *s, + struct v4l2_control *vc) +{ + struct tcm825x_sensor *sensor = s->priv; + struct i2c_client *client = sensor->i2c_client; + struct vcontrol *lvc; + int val = vc->value; + + /* exposure time is special, spread across 2 registers */ + if (vc->id == V4L2_CID_EXPOSURE) { + int val_lower, val_upper; + val_lower = val & TCM825X_MASK(TCM825X_ESRSPD_L); + val_upper = (val >> 8) & TCM825X_MASK(TCM825X_ESRSPD_U); + + if (tcm825x_write_reg_mask(client, + TCM825X_ESRSPD_U, val_upper)) + return -EIO; + + if (tcm825x_write_reg_mask(client, + TCM825X_ESRSPD_L, val_lower)) + return -EIO; + + return 0; + } + + lvc = find_vctrl(vc->id); + if (lvc == NULL) + return -EINVAL; + + if (vc->id == V4L2_CID_HFLIP || vc->id == V4L2_CID_VFLIP) + val ^= sensor->platform_data->is_upside_down(); + + val = val << lvc->start_bit; + if (tcm825x_write_reg_mask(client, lvc->reg, val)) + return -EIO; + + return 0; +} + +static int ioctl_enum_fmt_cap(struct v4l2_int_device *s, + struct v4l2_fmtdesc *fmt) +{ + int index = fmt->index; + + switch (fmt->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + if (index >= TCM825X_NUM_CAPTURE_FORMATS) + return -EINVAL; + break; + + default: + return -EINVAL; + } + + fmt->flags = tcm825x_formats[index].flags; + strlcpy(fmt->description, tcm825x_formats[index].description, + sizeof(fmt->description)); + fmt->pixelformat = tcm825x_formats[index].pixelformat; + + return 0; +} + +static int ioctl_try_fmt_cap(struct v4l2_int_device *s, + struct v4l2_format *f) +{ + struct tcm825x_sensor *sensor = s->priv; + enum image_size isize; + int ifmt; + struct v4l2_pix_format *pix = &f->fmt.pix; + + isize = tcm825x_find_size(s, pix->width, pix->height); + dev_dbg(&sensor->i2c_client->dev, "isize = %d num_capture = %lu\n", + isize, (unsigned long)TCM825X_NUM_CAPTURE_FORMATS); + + pix->width = tcm825x_sizes[isize].width; + pix->height = tcm825x_sizes[isize].height; + + for (ifmt = 0; ifmt < TCM825X_NUM_CAPTURE_FORMATS; ifmt++) + if (pix->pixelformat == tcm825x_formats[ifmt].pixelformat) + break; + + if (ifmt == TCM825X_NUM_CAPTURE_FORMATS) + ifmt = 0; /* Default = YUV 4:2:2 */ + + pix->pixelformat = tcm825x_formats[ifmt].pixelformat; + pix->field = V4L2_FIELD_NONE; + pix->bytesperline = pix->width * TCM825X_BYTES_PER_PIXEL; + pix->sizeimage = pix->bytesperline * pix->height; + pix->priv = 0; + dev_dbg(&sensor->i2c_client->dev, "format = 0x%08x\n", + pix->pixelformat); + + switch (pix->pixelformat) { + case V4L2_PIX_FMT_UYVY: + default: + pix->colorspace = V4L2_COLORSPACE_JPEG; + break; + case V4L2_PIX_FMT_RGB565: + pix->colorspace = V4L2_COLORSPACE_SRGB; + break; + } + + return 0; +} + +static int ioctl_s_fmt_cap(struct v4l2_int_device *s, + struct v4l2_format *f) +{ + struct tcm825x_sensor *sensor = s->priv; + struct v4l2_pix_format *pix = &f->fmt.pix; + int rval; + + rval = ioctl_try_fmt_cap(s, f); + if (rval) + return rval; + + rval = tcm825x_configure(s); + + sensor->pix = *pix; + + return rval; +} + +static int ioctl_g_fmt_cap(struct v4l2_int_device *s, + struct v4l2_format *f) +{ + struct tcm825x_sensor *sensor = s->priv; + + f->fmt.pix = sensor->pix; + + return 0; +} + +static int ioctl_g_parm(struct v4l2_int_device *s, + struct v4l2_streamparm *a) +{ + struct tcm825x_sensor *sensor = s->priv; + struct v4l2_captureparm *cparm = &a->parm.capture; + + if (a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + memset(a, 0, sizeof(*a)); + a->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + cparm->capability = V4L2_CAP_TIMEPERFRAME; + cparm->timeperframe = sensor->timeperframe; + + return 0; +} + +static int ioctl_s_parm(struct v4l2_int_device *s, + struct v4l2_streamparm *a) +{ + struct tcm825x_sensor *sensor = s->priv; + struct v4l2_fract *timeperframe = &a->parm.capture.timeperframe; + u32 tgt_fps; /* target frames per secound */ + int rval; + + if (a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + if ((timeperframe->numerator == 0) + || (timeperframe->denominator == 0)) { + timeperframe->denominator = DEFAULT_FPS; + timeperframe->numerator = 1; + } + + tgt_fps = timeperframe->denominator / timeperframe->numerator; + + if (tgt_fps > MAX_FPS) { + timeperframe->denominator = MAX_FPS; + timeperframe->numerator = 1; + } else if (tgt_fps < MIN_FPS) { + timeperframe->denominator = MIN_FPS; + timeperframe->numerator = 1; + } + + sensor->timeperframe = *timeperframe; + + rval = tcm825x_configure(s); + + return rval; +} + +static int ioctl_s_power(struct v4l2_int_device *s, int on) +{ + struct tcm825x_sensor *sensor = s->priv; + + return sensor->platform_data->power_set(on); +} + +/* + * Given the image capture format in pix, the nominal frame period in + * timeperframe, calculate the required xclk frequency. + * + * TCM825X input frequency characteristics are: + * Minimum 11.9 MHz, Typical 24.57 MHz and maximum 25/27 MHz + */ + +static int ioctl_g_ifparm(struct v4l2_int_device *s, struct v4l2_ifparm *p) +{ + struct tcm825x_sensor *sensor = s->priv; + struct v4l2_fract *timeperframe = &sensor->timeperframe; + u32 tgt_xclk; /* target xclk */ + u32 tgt_fps; /* target frames per secound */ + int rval; + + rval = sensor->platform_data->ifparm(p); + if (rval) + return rval; + + tgt_fps = timeperframe->denominator / timeperframe->numerator; + + tgt_xclk = (tgt_fps <= HIGH_FPS_MODE_LOWER_LIMIT) ? + (2457 * tgt_fps) / MAX_HALF_FPS : + (2457 * tgt_fps) / MAX_FPS; + tgt_xclk *= 10000; + + tgt_xclk = min(tgt_xclk, (u32)TCM825X_XCLK_MAX); + tgt_xclk = max(tgt_xclk, (u32)TCM825X_XCLK_MIN); + + p->u.bt656.clock_curr = tgt_xclk; + + return 0; +} + +static int ioctl_g_needs_reset(struct v4l2_int_device *s, void *buf) +{ + struct tcm825x_sensor *sensor = s->priv; + + return sensor->platform_data->needs_reset(s, buf, &sensor->pix); +} + +static int ioctl_reset(struct v4l2_int_device *s) +{ + return -EBUSY; +} + +static int ioctl_init(struct v4l2_int_device *s) +{ + return tcm825x_configure(s); +} + +static int ioctl_dev_exit(struct v4l2_int_device *s) +{ + return 0; +} + +static int ioctl_dev_init(struct v4l2_int_device *s) +{ + struct tcm825x_sensor *sensor = s->priv; + int r; + + r = tcm825x_read_reg(sensor->i2c_client, 0x01); + if (r < 0) + return r; + if (r == 0) { + dev_err(&sensor->i2c_client->dev, "device not detected\n"); + return -EIO; + } + return 0; +} + +static struct v4l2_int_ioctl_desc tcm825x_ioctl_desc[] = { + { vidioc_int_dev_init_num, + (v4l2_int_ioctl_func *)ioctl_dev_init }, + { vidioc_int_dev_exit_num, + (v4l2_int_ioctl_func *)ioctl_dev_exit }, + { vidioc_int_s_power_num, + (v4l2_int_ioctl_func *)ioctl_s_power }, + { vidioc_int_g_ifparm_num, + (v4l2_int_ioctl_func *)ioctl_g_ifparm }, + { vidioc_int_g_needs_reset_num, + (v4l2_int_ioctl_func *)ioctl_g_needs_reset }, + { vidioc_int_reset_num, + (v4l2_int_ioctl_func *)ioctl_reset }, + { vidioc_int_init_num, + (v4l2_int_ioctl_func *)ioctl_init }, + { vidioc_int_enum_fmt_cap_num, + (v4l2_int_ioctl_func *)ioctl_enum_fmt_cap }, + { vidioc_int_try_fmt_cap_num, + (v4l2_int_ioctl_func *)ioctl_try_fmt_cap }, + { vidioc_int_g_fmt_cap_num, + (v4l2_int_ioctl_func *)ioctl_g_fmt_cap }, + { vidioc_int_s_fmt_cap_num, + (v4l2_int_ioctl_func *)ioctl_s_fmt_cap }, + { vidioc_int_g_parm_num, + (v4l2_int_ioctl_func *)ioctl_g_parm }, + { vidioc_int_s_parm_num, + (v4l2_int_ioctl_func *)ioctl_s_parm }, + { vidioc_int_queryctrl_num, + (v4l2_int_ioctl_func *)ioctl_queryctrl }, + { vidioc_int_g_ctrl_num, + (v4l2_int_ioctl_func *)ioctl_g_ctrl }, + { vidioc_int_s_ctrl_num, + (v4l2_int_ioctl_func *)ioctl_s_ctrl }, +}; + +static struct v4l2_int_slave tcm825x_slave = { + .ioctls = tcm825x_ioctl_desc, + .num_ioctls = ARRAY_SIZE(tcm825x_ioctl_desc), +}; + +static struct tcm825x_sensor tcm825x; + +static struct v4l2_int_device tcm825x_int_device = { + .module = THIS_MODULE, + .name = TCM825X_NAME, + .priv = &tcm825x, + .type = v4l2_int_type_slave, + .u = { + .slave = &tcm825x_slave, + }, +}; + +static int tcm825x_probe(struct i2c_client *client, + const struct i2c_device_id *did) +{ + struct tcm825x_sensor *sensor = &tcm825x; + + if (i2c_get_clientdata(client)) + return -EBUSY; + + sensor->platform_data = client->dev.platform_data; + + if (sensor->platform_data == NULL + || !sensor->platform_data->is_okay()) + return -ENODEV; + + sensor->v4l2_int_device = &tcm825x_int_device; + + sensor->i2c_client = client; + i2c_set_clientdata(client, sensor); + + /* Make the default capture format QVGA RGB565 */ + sensor->pix.width = tcm825x_sizes[QVGA].width; + sensor->pix.height = tcm825x_sizes[QVGA].height; + sensor->pix.pixelformat = V4L2_PIX_FMT_RGB565; + + return v4l2_int_device_register(sensor->v4l2_int_device); +} + +static int tcm825x_remove(struct i2c_client *client) +{ + struct tcm825x_sensor *sensor = i2c_get_clientdata(client); + + if (!client->adapter) + return -ENODEV; /* our client isn't attached */ + + v4l2_int_device_unregister(sensor->v4l2_int_device); + + return 0; +} + +static const struct i2c_device_id tcm825x_id[] = { + { "tcm825x", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tcm825x_id); + +static struct i2c_driver tcm825x_i2c_driver = { + .driver = { + .name = TCM825X_NAME, + }, + .probe = tcm825x_probe, + .remove = tcm825x_remove, + .id_table = tcm825x_id, +}; + +static struct tcm825x_sensor tcm825x = { + .timeperframe = { + .numerator = 1, + .denominator = DEFAULT_FPS, + }, +}; + +static int __init tcm825x_init(void) +{ + int rval; + + rval = i2c_add_driver(&tcm825x_i2c_driver); + if (rval) + pr_info("%s: failed registering " TCM825X_NAME "\n", + __func__); + + return rval; +} + +static void __exit tcm825x_exit(void) +{ + i2c_del_driver(&tcm825x_i2c_driver); +} + +/* + * FIXME: Menelaus isn't ready (?) at module_init stage, so use + * late_initcall for now. + */ +late_initcall(tcm825x_init); +module_exit(tcm825x_exit); + +MODULE_AUTHOR("Sakari Ailus <sakari.ailus@nokia.com>"); +MODULE_DESCRIPTION("TCM825x camera sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/media/omap24xx/tcm825x.h b/drivers/staging/media/omap24xx/tcm825x.h new file mode 100644 index 00000000000..8a29636d1ad --- /dev/null +++ b/drivers/staging/media/omap24xx/tcm825x.h @@ -0,0 +1,200 @@ +/* + * drivers/media/i2c/tcm825x.h + * + * Register definitions for the TCM825X CameraChip. + * + * Author: David Cohen (david.cohen@indt.org.br) + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + * + * This file was based on ov9640.h from MontaVista + */ + +#ifndef TCM825X_H +#define TCM825X_H + +#include <linux/videodev2.h> + +#include "v4l2-int-device.h" + +#define TCM825X_NAME "tcm825x" + +#define TCM825X_MASK(x) (x & 0x00ff) +#define TCM825X_ADDR(x) ((x & 0xff00) >> 8) + +/* The TCM825X I2C sensor chip has a fixed slave address of 0x3d. */ +#define TCM825X_I2C_ADDR 0x3d + +/* + * define register offsets for the TCM825X sensor chip + * OFFSET(8 bits) + MASK(8 bits) + * MASK bit 4 and 3 are used when the register uses more than one address + */ +#define TCM825X_FPS 0x0280 +#define TCM825X_ACF 0x0240 +#define TCM825X_DOUTBUF 0x020C +#define TCM825X_DCLKP 0x0202 +#define TCM825X_ACFDET 0x0201 +#define TCM825X_DOUTSW 0x0380 +#define TCM825X_DATAHZ 0x0340 +#define TCM825X_PICSIZ 0x033c +#define TCM825X_PICFMT 0x0302 +#define TCM825X_V_INV 0x0480 +#define TCM825X_H_INV 0x0440 +#define TCM825X_ESRLSW 0x0430 +#define TCM825X_V_LENGTH 0x040F +#define TCM825X_ALCSW 0x0580 +#define TCM825X_ESRLIM 0x0560 +#define TCM825X_ESRSPD_U 0x051F +#define TCM825X_ESRSPD_L 0x06FF +#define TCM825X_AG 0x07FF +#define TCM825X_ESRSPD2 0x06FF +#define TCM825X_ALCMODE 0x0830 +#define TCM825X_ALCH 0x080F +#define TCM825X_ALCL 0x09FF +#define TCM825X_AWBSW 0x0A80 +#define TCM825X_MRG 0x0BFF +#define TCM825X_MBG 0x0CFF +#define TCM825X_GAMSW 0x0D80 +#define TCM825X_HDTG 0x0EFF +#define TCM825X_VDTG 0x0FFF +#define TCM825X_HDTCORE 0x10F0 +#define TCM825X_VDTCORE 0x100F +#define TCM825X_CONT 0x11FF +#define TCM825X_BRIGHT 0x12FF +#define TCM825X_VHUE 0x137F +#define TCM825X_UHUE 0x147F +#define TCM825X_VGAIN 0x153F +#define TCM825X_UGAIN 0x163F +#define TCM825X_UVCORE 0x170F +#define TCM825X_SATU 0x187F +#define TCM825X_MHMODE 0x1980 +#define TCM825X_MHLPFSEL 0x1940 +#define TCM825X_YMODE 0x1930 +#define TCM825X_MIXHG 0x1907 +#define TCM825X_LENS 0x1A3F +#define TCM825X_AGLIM 0x1BE0 +#define TCM825X_LENSRPOL 0x1B10 +#define TCM825X_LENSRGAIN 0x1B0F +#define TCM825X_ES100S 0x1CFF +#define TCM825X_ES120S 0x1DFF +#define TCM825X_DMASK 0x1EC0 +#define TCM825X_CODESW 0x1E20 +#define TCM825X_CODESEL 0x1E10 +#define TCM825X_TESPIC 0x1E04 +#define TCM825X_PICSEL 0x1E03 +#define TCM825X_HNUM 0x20FF +#define TCM825X_VOUTPH 0x287F +#define TCM825X_ESROUT 0x327F +#define TCM825X_ESROUT2 0x33FF +#define TCM825X_AGOUT 0x34FF +#define TCM825X_DGOUT 0x353F +#define TCM825X_AGSLOW1 0x39C0 +#define TCM825X_FLLSMODE 0x3930 +#define TCM825X_FLLSLIM 0x390F +#define TCM825X_DETSEL 0x3AF0 +#define TCM825X_ACDETNC 0x3A0F +#define TCM825X_AGSLOW2 0x3BC0 +#define TCM825X_DG 0x3B3F +#define TCM825X_REJHLEV 0x3CFF +#define TCM825X_ALCLOCK 0x3D80 +#define TCM825X_FPSLNKSW 0x3D40 +#define TCM825X_ALCSPD 0x3D30 +#define TCM825X_REJH 0x3D03 +#define TCM825X_SHESRSW 0x3E80 +#define TCM825X_ESLIMSEL 0x3E40 +#define TCM825X_SHESRSPD 0x3E30 +#define TCM825X_ELSTEP 0x3E0C +#define TCM825X_ELSTART 0x3E03 +#define TCM825X_AGMIN 0x3FFF +#define TCM825X_PREGRG 0x423F +#define TCM825X_PREGBG 0x433F +#define TCM825X_PRERG 0x443F +#define TCM825X_PREBG 0x453F +#define TCM825X_MSKBR 0x477F +#define TCM825X_MSKGR 0x487F +#define TCM825X_MSKRB 0x497F +#define TCM825X_MSKGB 0x4A7F +#define TCM825X_MSKRG 0x4B7F +#define TCM825X_MSKBG 0x4C7F +#define TCM825X_HDTCSW 0x4D80 +#define TCM825X_VDTCSW 0x4D40 +#define TCM825X_DTCYL 0x4D3F +#define TCM825X_HDTPSW 0x4E80 +#define TCM825X_VDTPSW 0x4E40 +#define TCM825X_DTCGAIN 0x4E3F +#define TCM825X_DTLLIMSW 0x4F10 +#define TCM825X_DTLYLIM 0x4F0F +#define TCM825X_YLCUTLMSK 0x5080 +#define TCM825X_YLCUTL 0x503F +#define TCM825X_YLCUTHMSK 0x5180 +#define TCM825X_YLCUTH 0x513F +#define TCM825X_UVSKNC 0x527F +#define TCM825X_UVLJ 0x537F +#define TCM825X_WBGMIN 0x54FF +#define TCM825X_WBGMAX 0x55FF +#define TCM825X_WBSPDUP 0x5603 +#define TCM825X_ALLAREA 0x5820 +#define TCM825X_WBLOCK 0x5810 +#define TCM825X_WB2SP 0x580F +#define TCM825X_KIZUSW 0x5920 +#define TCM825X_PBRSW 0x5910 +#define TCM825X_ABCSW 0x5903 +#define TCM825X_PBDLV 0x5AFF +#define TCM825X_PBC1LV 0x5BFF + +#define TCM825X_NUM_REGS (TCM825X_ADDR(TCM825X_PBC1LV) + 1) + +#define TCM825X_BYTES_PER_PIXEL 2 + +#define TCM825X_REG_TERM 0xff /* terminating list entry for reg */ +#define TCM825X_VAL_TERM 0xff /* terminating list entry for val */ + +/* define a structure for tcm825x register initialization values */ +struct tcm825x_reg { + u8 val; + u16 reg; +}; + +enum image_size { subQCIF = 0, QQVGA, QCIF, QVGA, CIF, VGA }; +enum pixel_format { YUV422 = 0, RGB565 }; +#define NUM_IMAGE_SIZES 6 +#define NUM_PIXEL_FORMATS 2 + +#define TCM825X_XCLK_MIN 11900000 +#define TCM825X_XCLK_MAX 25000000 + +struct capture_size { + unsigned long width; + unsigned long height; +}; + +struct tcm825x_platform_data { + /* Is the sensor usable? Doesn't yet mean it's there, but you + * can try! */ + int (*is_okay)(void); + /* Set power state, zero is off, non-zero is on. */ + int (*power_set)(int power); + /* Default registers written after power-on or reset. */ + const struct tcm825x_reg * (*default_regs)(void); + int (*needs_reset)(struct v4l2_int_device *s, void *buf, + struct v4l2_pix_format *fmt); + int (*ifparm)(struct v4l2_ifparm *p); + int (*is_upside_down)(void); +}; + +/* Array of image sizes supported by TCM825X. These must be ordered from + * smallest image size to largest. + */ +static const struct capture_size tcm825x_sizes[] = { + { 128, 96 }, /* subQCIF */ + { 160, 120 }, /* QQVGA */ + { 176, 144 }, /* QCIF */ + { 320, 240 }, /* QVGA */ + { 352, 288 }, /* CIF */ + { 640, 480 }, /* VGA */ +}; + +#endif /* ifndef TCM825X_H */ diff --git a/drivers/staging/media/omap24xx/v4l2-int-device.c b/drivers/staging/media/omap24xx/v4l2-int-device.c new file mode 100644 index 00000000000..427a89033a1 --- /dev/null +++ b/drivers/staging/media/omap24xx/v4l2-int-device.c @@ -0,0 +1,164 @@ +/* + * drivers/media/video/v4l2-int-device.c + * + * V4L2 internal ioctl interface. + * + * Copyright (C) 2007 Nokia Corporation. + * + * Contact: Sakari Ailus <sakari.ailus@nokia.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#include <linux/kernel.h> +#include <linux/list.h> +#include <linux/sort.h> +#include <linux/string.h> +#include <linux/module.h> + +#include "v4l2-int-device.h" + +static DEFINE_MUTEX(mutex); +static LIST_HEAD(int_list); + +void v4l2_int_device_try_attach_all(void) +{ + struct v4l2_int_device *m, *s; + + list_for_each_entry(m, &int_list, head) { + if (m->type != v4l2_int_type_master) + continue; + + list_for_each_entry(s, &int_list, head) { + if (s->type != v4l2_int_type_slave) + continue; + + /* Slave is connected? */ + if (s->u.slave->master) + continue; + + /* Slave wants to attach to master? */ + if (s->u.slave->attach_to[0] != 0 + && strncmp(m->name, s->u.slave->attach_to, + V4L2NAMESIZE)) + continue; + + if (!try_module_get(m->module)) + continue; + + s->u.slave->master = m; + if (m->u.master->attach(s)) { + s->u.slave->master = NULL; + module_put(m->module); + continue; + } + } + } +} +EXPORT_SYMBOL_GPL(v4l2_int_device_try_attach_all); + +static int ioctl_sort_cmp(const void *a, const void *b) +{ + const struct v4l2_int_ioctl_desc *d1 = a, *d2 = b; + + if (d1->num > d2->num) + return 1; + + if (d1->num < d2->num) + return -1; + + return 0; +} + +int v4l2_int_device_register(struct v4l2_int_device *d) +{ + if (d->type == v4l2_int_type_slave) + sort(d->u.slave->ioctls, d->u.slave->num_ioctls, + sizeof(struct v4l2_int_ioctl_desc), + &ioctl_sort_cmp, NULL); + mutex_lock(&mutex); + list_add(&d->head, &int_list); + v4l2_int_device_try_attach_all(); + mutex_unlock(&mutex); + + return 0; +} +EXPORT_SYMBOL_GPL(v4l2_int_device_register); + +void v4l2_int_device_unregister(struct v4l2_int_device *d) +{ + mutex_lock(&mutex); + list_del(&d->head); + if (d->type == v4l2_int_type_slave + && d->u.slave->master != NULL) { + d->u.slave->master->u.master->detach(d); + module_put(d->u.slave->master->module); + d->u.slave->master = NULL; + } + mutex_unlock(&mutex); +} +EXPORT_SYMBOL_GPL(v4l2_int_device_unregister); + +/* Adapted from search_extable in extable.c. */ +static v4l2_int_ioctl_func *find_ioctl(struct v4l2_int_slave *slave, int cmd, + v4l2_int_ioctl_func *no_such_ioctl) +{ + const struct v4l2_int_ioctl_desc *first = slave->ioctls; + const struct v4l2_int_ioctl_desc *last = + first + slave->num_ioctls - 1; + + while (first <= last) { + const struct v4l2_int_ioctl_desc *mid; + + mid = (last - first) / 2 + first; + + if (mid->num < cmd) + first = mid + 1; + else if (mid->num > cmd) + last = mid - 1; + else + return mid->func; + } + + return no_such_ioctl; +} + +static int no_such_ioctl_0(struct v4l2_int_device *d) +{ + return -ENOIOCTLCMD; +} + +int v4l2_int_ioctl_0(struct v4l2_int_device *d, int cmd) +{ + return ((v4l2_int_ioctl_func_0 *) + find_ioctl(d->u.slave, cmd, + (v4l2_int_ioctl_func *)no_such_ioctl_0))(d); +} +EXPORT_SYMBOL_GPL(v4l2_int_ioctl_0); + +static int no_such_ioctl_1(struct v4l2_int_device *d, void *arg) +{ + return -ENOIOCTLCMD; +} + +int v4l2_int_ioctl_1(struct v4l2_int_device *d, int cmd, void *arg) +{ + return ((v4l2_int_ioctl_func_1 *) + find_ioctl(d->u.slave, cmd, + (v4l2_int_ioctl_func *)no_such_ioctl_1))(d, arg); +} +EXPORT_SYMBOL_GPL(v4l2_int_ioctl_1); + +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/media/omap24xx/v4l2-int-device.h b/drivers/staging/media/omap24xx/v4l2-int-device.h new file mode 100644 index 00000000000..0286c95814f --- /dev/null +++ b/drivers/staging/media/omap24xx/v4l2-int-device.h @@ -0,0 +1,305 @@ +/* + * include/media/v4l2-int-device.h + * + * V4L2 internal ioctl interface. + * + * Copyright (C) 2007 Nokia Corporation. + * + * Contact: Sakari Ailus <sakari.ailus@nokia.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#ifndef V4L2_INT_DEVICE_H +#define V4L2_INT_DEVICE_H + +#include <media/v4l2-common.h> + +#define V4L2NAMESIZE 32 + +/* + * + * The internal V4L2 device interface core. + * + */ + +enum v4l2_int_type { + v4l2_int_type_master = 1, + v4l2_int_type_slave +}; + +struct module; + +struct v4l2_int_device; + +struct v4l2_int_master { + int (*attach)(struct v4l2_int_device *slave); + void (*detach)(struct v4l2_int_device *slave); +}; + +typedef int (v4l2_int_ioctl_func)(struct v4l2_int_device *); +typedef int (v4l2_int_ioctl_func_0)(struct v4l2_int_device *); +typedef int (v4l2_int_ioctl_func_1)(struct v4l2_int_device *, void *); + +struct v4l2_int_ioctl_desc { + int num; + v4l2_int_ioctl_func *func; +}; + +struct v4l2_int_slave { + /* Don't touch master. */ + struct v4l2_int_device *master; + + char attach_to[V4L2NAMESIZE]; + + int num_ioctls; + struct v4l2_int_ioctl_desc *ioctls; +}; + +struct v4l2_int_device { + /* Don't touch head. */ + struct list_head head; + + struct module *module; + + char name[V4L2NAMESIZE]; + + enum v4l2_int_type type; + union { + struct v4l2_int_master *master; + struct v4l2_int_slave *slave; + } u; + + void *priv; +}; + +void v4l2_int_device_try_attach_all(void); + +int v4l2_int_device_register(struct v4l2_int_device *d); +void v4l2_int_device_unregister(struct v4l2_int_device *d); + +int v4l2_int_ioctl_0(struct v4l2_int_device *d, int cmd); +int v4l2_int_ioctl_1(struct v4l2_int_device *d, int cmd, void *arg); + +/* + * + * Types and definitions for IOCTL commands. + * + */ + +enum v4l2_power { + V4L2_POWER_OFF = 0, + V4L2_POWER_ON, + V4L2_POWER_STANDBY, +}; + +/* Slave interface type. */ +enum v4l2_if_type { + /* + * Parallel 8-, 10- or 12-bit interface, used by for example + * on certain image sensors. + */ + V4L2_IF_TYPE_BT656, +}; + +enum v4l2_if_type_bt656_mode { + /* + * Modes without Bt synchronisation codes. Separate + * synchronisation signal lines are used. + */ + V4L2_IF_TYPE_BT656_MODE_NOBT_8BIT, + V4L2_IF_TYPE_BT656_MODE_NOBT_10BIT, + V4L2_IF_TYPE_BT656_MODE_NOBT_12BIT, + /* + * Use Bt synchronisation codes. The vertical and horizontal + * synchronisation is done based on synchronisation codes. + */ + V4L2_IF_TYPE_BT656_MODE_BT_8BIT, + V4L2_IF_TYPE_BT656_MODE_BT_10BIT, +}; + +struct v4l2_if_type_bt656 { + /* + * 0: Frame begins when vsync is high. + * 1: Frame begins when vsync changes from low to high. + */ + unsigned frame_start_on_rising_vs:1; + /* Use Bt synchronisation codes for sync correction. */ + unsigned bt_sync_correct:1; + /* Swap every two adjacent image data elements. */ + unsigned swap:1; + /* Inverted latch clock polarity from slave. */ + unsigned latch_clk_inv:1; + /* Hs polarity. 0 is active high, 1 active low. */ + unsigned nobt_hs_inv:1; + /* Vs polarity. 0 is active high, 1 active low. */ + unsigned nobt_vs_inv:1; + enum v4l2_if_type_bt656_mode mode; + /* Minimum accepted bus clock for slave (in Hz). */ + u32 clock_min; + /* Maximum accepted bus clock for slave. */ + u32 clock_max; + /* + * Current wish of the slave. May only change in response to + * ioctls that affect image capture. + */ + u32 clock_curr; +}; + +struct v4l2_ifparm { + enum v4l2_if_type if_type; + union { + struct v4l2_if_type_bt656 bt656; + } u; +}; + +/* IOCTL command numbers. */ +enum v4l2_int_ioctl_num { + /* + * + * "Proper" V4L ioctls, as in struct video_device. + * + */ + vidioc_int_enum_fmt_cap_num = 1, + vidioc_int_g_fmt_cap_num, + vidioc_int_s_fmt_cap_num, + vidioc_int_try_fmt_cap_num, + vidioc_int_queryctrl_num, + vidioc_int_g_ctrl_num, + vidioc_int_s_ctrl_num, + vidioc_int_cropcap_num, + vidioc_int_g_crop_num, + vidioc_int_s_crop_num, + vidioc_int_g_parm_num, + vidioc_int_s_parm_num, + vidioc_int_querystd_num, + vidioc_int_s_std_num, + vidioc_int_s_video_routing_num, + + /* + * + * Strictly internal ioctls. + * + */ + /* Initialise the device when slave attaches to the master. */ + vidioc_int_dev_init_num = 1000, + /* Delinitialise the device at slave detach. */ + vidioc_int_dev_exit_num, + /* Set device power state. */ + vidioc_int_s_power_num, + /* + * Get slave private data, e.g. platform-specific slave + * configuration used by the master. + */ + vidioc_int_g_priv_num, + /* Get slave interface parameters. */ + vidioc_int_g_ifparm_num, + /* Does the slave need to be reset after VIDIOC_DQBUF? */ + vidioc_int_g_needs_reset_num, + vidioc_int_enum_framesizes_num, + vidioc_int_enum_frameintervals_num, + + /* + * + * VIDIOC_INT_* ioctls. + * + */ + /* VIDIOC_INT_RESET */ + vidioc_int_reset_num, + /* VIDIOC_INT_INIT */ + vidioc_int_init_num, + + /* + * + * Start of private ioctls. + * + */ + vidioc_int_priv_start_num = 2000, +}; + +/* + * + * IOCTL wrapper functions for better type checking. + * + */ + +#define V4L2_INT_WRAPPER_0(name) \ + static inline int vidioc_int_##name(struct v4l2_int_device *d) \ + { \ + return v4l2_int_ioctl_0(d, vidioc_int_##name##_num); \ + } \ + \ + static inline struct v4l2_int_ioctl_desc \ + vidioc_int_##name##_cb(int (*func) \ + (struct v4l2_int_device *)) \ + { \ + struct v4l2_int_ioctl_desc desc; \ + \ + desc.num = vidioc_int_##name##_num; \ + desc.func = (v4l2_int_ioctl_func *)func; \ + \ + return desc; \ + } + +#define V4L2_INT_WRAPPER_1(name, arg_type, asterisk) \ + static inline int vidioc_int_##name(struct v4l2_int_device *d, \ + arg_type asterisk arg) \ + { \ + return v4l2_int_ioctl_1(d, vidioc_int_##name##_num, \ + (void *)(unsigned long)arg); \ + } \ + \ + static inline struct v4l2_int_ioctl_desc \ + vidioc_int_##name##_cb(int (*func) \ + (struct v4l2_int_device *, \ + arg_type asterisk)) \ + { \ + struct v4l2_int_ioctl_desc desc; \ + \ + desc.num = vidioc_int_##name##_num; \ + desc.func = (v4l2_int_ioctl_func *)func; \ + \ + return desc; \ + } + +V4L2_INT_WRAPPER_1(enum_fmt_cap, struct v4l2_fmtdesc, *); +V4L2_INT_WRAPPER_1(g_fmt_cap, struct v4l2_format, *); +V4L2_INT_WRAPPER_1(s_fmt_cap, struct v4l2_format, *); +V4L2_INT_WRAPPER_1(try_fmt_cap, struct v4l2_format, *); +V4L2_INT_WRAPPER_1(queryctrl, struct v4l2_queryctrl, *); +V4L2_INT_WRAPPER_1(g_ctrl, struct v4l2_control, *); +V4L2_INT_WRAPPER_1(s_ctrl, struct v4l2_control, *); +V4L2_INT_WRAPPER_1(cropcap, struct v4l2_cropcap, *); +V4L2_INT_WRAPPER_1(g_crop, struct v4l2_crop, *); +V4L2_INT_WRAPPER_1(s_crop, struct v4l2_crop, *); +V4L2_INT_WRAPPER_1(g_parm, struct v4l2_streamparm, *); +V4L2_INT_WRAPPER_1(s_parm, struct v4l2_streamparm, *); +V4L2_INT_WRAPPER_1(querystd, v4l2_std_id, *); +V4L2_INT_WRAPPER_1(s_std, v4l2_std_id, *); +V4L2_INT_WRAPPER_1(s_video_routing, struct v4l2_routing, *); + +V4L2_INT_WRAPPER_0(dev_init); +V4L2_INT_WRAPPER_0(dev_exit); +V4L2_INT_WRAPPER_1(s_power, enum v4l2_power, ); +V4L2_INT_WRAPPER_1(g_priv, void, *); +V4L2_INT_WRAPPER_1(g_ifparm, struct v4l2_ifparm, *); +V4L2_INT_WRAPPER_1(g_needs_reset, void, *); +V4L2_INT_WRAPPER_1(enum_framesizes, struct v4l2_frmsizeenum, *); +V4L2_INT_WRAPPER_1(enum_frameintervals, struct v4l2_frmivalenum, *); + +V4L2_INT_WRAPPER_0(reset); +V4L2_INT_WRAPPER_0(init); + +#endif diff --git a/drivers/staging/media/omap4iss/Kconfig b/drivers/staging/media/omap4iss/Kconfig new file mode 100644 index 00000000000..8afc6fee40c --- /dev/null +++ b/drivers/staging/media/omap4iss/Kconfig @@ -0,0 +1,6 @@ +config VIDEO_OMAP4 + bool "OMAP 4 Camera support" + depends on VIDEO_V4L2=y && VIDEO_V4L2_SUBDEV_API && I2C=y && ARCH_OMAP4 + select VIDEOBUF2_DMA_CONTIG + ---help--- + Driver for an OMAP 4 ISS controller. diff --git a/drivers/staging/media/omap4iss/Makefile b/drivers/staging/media/omap4iss/Makefile new file mode 100644 index 00000000000..a716ce936cf --- /dev/null +++ b/drivers/staging/media/omap4iss/Makefile @@ -0,0 +1,6 @@ +# Makefile for OMAP4 ISS driver + +omap4-iss-objs += \ + iss.o iss_csi2.o iss_csiphy.o iss_ipipeif.o iss_ipipe.o iss_resizer.o iss_video.o + +obj-$(CONFIG_VIDEO_OMAP4) += omap4-iss.o diff --git a/drivers/staging/media/omap4iss/TODO b/drivers/staging/media/omap4iss/TODO new file mode 100644 index 00000000000..fcde88860a2 --- /dev/null +++ b/drivers/staging/media/omap4iss/TODO @@ -0,0 +1,4 @@ +* Make the driver compile as a module +* Fix FIFO/buffer overflows and underflows +* Replace dummy resizer code with a real implementation +* Fix checkpatch errors and warnings diff --git a/drivers/staging/media/omap4iss/iss.c b/drivers/staging/media/omap4iss/iss.c new file mode 100644 index 00000000000..2e422dde074 --- /dev/null +++ b/drivers/staging/media/omap4iss/iss.c @@ -0,0 +1,1557 @@ +/* + * TI OMAP4 ISS V4L2 Driver + * + * Copyright (C) 2012, Texas Instruments + * + * Author: Sergio Aguirre <sergio.a.aguirre@gmail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/dma-mapping.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/sched.h> +#include <linux/vmalloc.h> + +#include <media/v4l2-common.h> +#include <media/v4l2-device.h> +#include <media/v4l2-ctrls.h> + +#include "iss.h" +#include "iss_regs.h" + +#define ISS_PRINT_REGISTER(iss, name)\ + dev_dbg(iss->dev, "###ISS " #name "=0x%08x\n", \ + iss_reg_read(iss, OMAP4_ISS_MEM_TOP, ISS_##name)) + +static void iss_print_status(struct iss_device *iss) +{ + dev_dbg(iss->dev, "-------------ISS HL Register dump-------------\n"); + + ISS_PRINT_REGISTER(iss, HL_REVISION); + ISS_PRINT_REGISTER(iss, HL_SYSCONFIG); + ISS_PRINT_REGISTER(iss, HL_IRQSTATUS(5)); + ISS_PRINT_REGISTER(iss, HL_IRQENABLE_SET(5)); + ISS_PRINT_REGISTER(iss, HL_IRQENABLE_CLR(5)); + ISS_PRINT_REGISTER(iss, CTRL); + ISS_PRINT_REGISTER(iss, CLKCTRL); + ISS_PRINT_REGISTER(iss, CLKSTAT); + + dev_dbg(iss->dev, "-----------------------------------------------\n"); +} + +/* + * omap4iss_flush - Post pending L3 bus writes by doing a register readback + * @iss: OMAP4 ISS device + * + * In order to force posting of pending writes, we need to write and + * readback the same register, in this case the revision register. + * + * See this link for reference: + * http://www.mail-archive.com/linux-omap@vger.kernel.org/msg08149.html + */ +void omap4iss_flush(struct iss_device *iss) +{ + iss_reg_write(iss, OMAP4_ISS_MEM_TOP, ISS_HL_REVISION, 0); + iss_reg_read(iss, OMAP4_ISS_MEM_TOP, ISS_HL_REVISION); +} + +/* + * iss_isp_enable_interrupts - Enable ISS ISP interrupts. + * @iss: OMAP4 ISS device + */ +static void omap4iss_isp_enable_interrupts(struct iss_device *iss) +{ + static const u32 isp_irq = ISP5_IRQ_OCP_ERR | + ISP5_IRQ_RSZ_FIFO_IN_BLK_ERR | + ISP5_IRQ_RSZ_FIFO_OVF | + ISP5_IRQ_RSZ_INT_DMA | + ISP5_IRQ_ISIF_INT(0); + + /* Enable ISP interrupts */ + iss_reg_write(iss, OMAP4_ISS_MEM_ISP_SYS1, ISP5_IRQSTATUS(0), isp_irq); + iss_reg_write(iss, OMAP4_ISS_MEM_ISP_SYS1, ISP5_IRQENABLE_SET(0), + isp_irq); +} + +/* + * iss_isp_disable_interrupts - Disable ISS interrupts. + * @iss: OMAP4 ISS device + */ +static void omap4iss_isp_disable_interrupts(struct iss_device *iss) +{ + iss_reg_write(iss, OMAP4_ISS_MEM_ISP_SYS1, ISP5_IRQENABLE_CLR(0), ~0); +} + +/* + * iss_enable_interrupts - Enable ISS interrupts. + * @iss: OMAP4 ISS device + */ +static void iss_enable_interrupts(struct iss_device *iss) +{ + static const u32 hl_irq = ISS_HL_IRQ_CSIA | ISS_HL_IRQ_CSIB + | ISS_HL_IRQ_ISP(0); + + /* Enable HL interrupts */ + iss_reg_write(iss, OMAP4_ISS_MEM_TOP, ISS_HL_IRQSTATUS(5), hl_irq); + iss_reg_write(iss, OMAP4_ISS_MEM_TOP, ISS_HL_IRQENABLE_SET(5), hl_irq); + + if (iss->regs[OMAP4_ISS_MEM_ISP_SYS1]) + omap4iss_isp_enable_interrupts(iss); +} + +/* + * iss_disable_interrupts - Disable ISS interrupts. + * @iss: OMAP4 ISS device + */ +static void iss_disable_interrupts(struct iss_device *iss) +{ + if (iss->regs[OMAP4_ISS_MEM_ISP_SYS1]) + omap4iss_isp_disable_interrupts(iss); + + iss_reg_write(iss, OMAP4_ISS_MEM_TOP, ISS_HL_IRQENABLE_CLR(5), ~0); +} + +int omap4iss_get_external_info(struct iss_pipeline *pipe, + struct media_link *link) +{ + struct iss_device *iss = + container_of(pipe, struct iss_video, pipe)->iss; + struct v4l2_subdev_format fmt; + struct v4l2_ctrl *ctrl; + int ret; + + if (!pipe->external) + return 0; + + if (pipe->external_rate) + return 0; + + memset(&fmt, 0, sizeof(fmt)); + + fmt.pad = link->source->index; + fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE; + ret = v4l2_subdev_call(media_entity_to_v4l2_subdev(link->sink->entity), + pad, get_fmt, NULL, &fmt); + if (ret < 0) + return -EPIPE; + + pipe->external_bpp = omap4iss_video_format_info(fmt.format.code)->bpp; + + ctrl = v4l2_ctrl_find(pipe->external->ctrl_handler, + V4L2_CID_PIXEL_RATE); + if (ctrl == NULL) { + dev_warn(iss->dev, "no pixel rate control in subdev %s\n", + pipe->external->name); + return -EPIPE; + } + + pipe->external_rate = v4l2_ctrl_g_ctrl_int64(ctrl); + + return 0; +} + +/* + * Configure the bridge. Valid inputs are + * + * IPIPEIF_INPUT_CSI2A: CSI2a receiver + * IPIPEIF_INPUT_CSI2B: CSI2b receiver + * + * The bridge and lane shifter are configured according to the selected input + * and the ISP platform data. + */ +void omap4iss_configure_bridge(struct iss_device *iss, + enum ipipeif_input_entity input) +{ + u32 issctrl_val; + u32 isp5ctrl_val; + + issctrl_val = iss_reg_read(iss, OMAP4_ISS_MEM_TOP, ISS_CTRL); + issctrl_val &= ~ISS_CTRL_INPUT_SEL_MASK; + issctrl_val &= ~ISS_CTRL_CLK_DIV_MASK; + + isp5ctrl_val = iss_reg_read(iss, OMAP4_ISS_MEM_ISP_SYS1, ISP5_CTRL); + + switch (input) { + case IPIPEIF_INPUT_CSI2A: + issctrl_val |= ISS_CTRL_INPUT_SEL_CSI2A; + break; + + case IPIPEIF_INPUT_CSI2B: + issctrl_val |= ISS_CTRL_INPUT_SEL_CSI2B; + break; + + default: + return; + } + + issctrl_val |= ISS_CTRL_SYNC_DETECT_VS_RAISING; + + isp5ctrl_val |= ISP5_CTRL_VD_PULSE_EXT | ISP5_CTRL_PSYNC_CLK_SEL | + ISP5_CTRL_SYNC_ENABLE; + + iss_reg_write(iss, OMAP4_ISS_MEM_TOP, ISS_CTRL, issctrl_val); + iss_reg_write(iss, OMAP4_ISS_MEM_ISP_SYS1, ISP5_CTRL, isp5ctrl_val); +} + +#ifdef ISS_ISR_DEBUG +static void iss_isr_dbg(struct iss_device *iss, u32 irqstatus) +{ + static const char * const name[] = { + "ISP_0", + "ISP_1", + "ISP_2", + "ISP_3", + "CSIA", + "CSIB", + "CCP2_0", + "CCP2_1", + "CCP2_2", + "CCP2_3", + "CBUFF", + "BTE", + "SIMCOP_0", + "SIMCOP_1", + "SIMCOP_2", + "SIMCOP_3", + "CCP2_8", + "HS_VS", + "18", + "19", + "20", + "21", + "22", + "23", + "24", + "25", + "26", + "27", + "28", + "29", + "30", + "31", + }; + unsigned int i; + + dev_dbg(iss->dev, "ISS IRQ: "); + + for (i = 0; i < ARRAY_SIZE(name); i++) { + if ((1 << i) & irqstatus) + pr_cont("%s ", name[i]); + } + pr_cont("\n"); +} + +static void iss_isp_isr_dbg(struct iss_device *iss, u32 irqstatus) +{ + static const char * const name[] = { + "ISIF_0", + "ISIF_1", + "ISIF_2", + "ISIF_3", + "IPIPEREQ", + "IPIPELAST_PIX", + "IPIPEDMA", + "IPIPEBSC", + "IPIPEHST", + "IPIPEIF", + "AEW", + "AF", + "H3A", + "RSZ_REG", + "RSZ_LAST_PIX", + "RSZ_DMA", + "RSZ_CYC_RZA", + "RSZ_CYC_RZB", + "RSZ_FIFO_OVF", + "RSZ_FIFO_IN_BLK_ERR", + "20", + "21", + "RSZ_EOF0", + "RSZ_EOF1", + "H3A_EOF", + "IPIPE_EOF", + "26", + "IPIPE_DPC_INI", + "IPIPE_DPC_RNEW0", + "IPIPE_DPC_RNEW1", + "30", + "OCP_ERR", + }; + unsigned int i; + + dev_dbg(iss->dev, "ISP IRQ: "); + + for (i = 0; i < ARRAY_SIZE(name); i++) { + if ((1 << i) & irqstatus) + pr_cont("%s ", name[i]); + } + pr_cont("\n"); +} +#endif + +/* + * iss_isr - Interrupt Service Routine for ISS module. + * @irq: Not used currently. + * @_iss: Pointer to the OMAP4 ISS device + * + * Handles the corresponding callback if plugged in. + * + * Returns IRQ_HANDLED when IRQ was correctly handled, or IRQ_NONE when the + * IRQ wasn't handled. + */ +static irqreturn_t iss_isr(int irq, void *_iss) +{ + static const u32 ipipeif_events = ISP5_IRQ_IPIPEIF_IRQ | + ISP5_IRQ_ISIF_INT(0); + static const u32 resizer_events = ISP5_IRQ_RSZ_FIFO_IN_BLK_ERR | + ISP5_IRQ_RSZ_FIFO_OVF | + ISP5_IRQ_RSZ_INT_DMA; + struct iss_device *iss = _iss; + u32 irqstatus; + + irqstatus = iss_reg_read(iss, OMAP4_ISS_MEM_TOP, ISS_HL_IRQSTATUS(5)); + iss_reg_write(iss, OMAP4_ISS_MEM_TOP, ISS_HL_IRQSTATUS(5), irqstatus); + + if (irqstatus & ISS_HL_IRQ_CSIA) + omap4iss_csi2_isr(&iss->csi2a); + + if (irqstatus & ISS_HL_IRQ_CSIB) + omap4iss_csi2_isr(&iss->csi2b); + + if (irqstatus & ISS_HL_IRQ_ISP(0)) { + u32 isp_irqstatus = iss_reg_read(iss, OMAP4_ISS_MEM_ISP_SYS1, + ISP5_IRQSTATUS(0)); + iss_reg_write(iss, OMAP4_ISS_MEM_ISP_SYS1, ISP5_IRQSTATUS(0), + isp_irqstatus); + + if (isp_irqstatus & ISP5_IRQ_OCP_ERR) + dev_dbg(iss->dev, "ISP5 OCP Error!\n"); + + if (isp_irqstatus & ipipeif_events) { + omap4iss_ipipeif_isr(&iss->ipipeif, + isp_irqstatus & ipipeif_events); + } + + if (isp_irqstatus & resizer_events) + omap4iss_resizer_isr(&iss->resizer, + isp_irqstatus & resizer_events); + +#ifdef ISS_ISR_DEBUG + iss_isp_isr_dbg(iss, isp_irqstatus); +#endif + } + + omap4iss_flush(iss); + +#ifdef ISS_ISR_DEBUG + iss_isr_dbg(iss, irqstatus); +#endif + + return IRQ_HANDLED; +} + +/* ----------------------------------------------------------------------------- + * Pipeline power management + * + * Entities must be powered up when part of a pipeline that contains at least + * one open video device node. + * + * To achieve this use the entity use_count field to track the number of users. + * For entities corresponding to video device nodes the use_count field stores + * the users count of the node. For entities corresponding to subdevs the + * use_count field stores the total number of users of all video device nodes + * in the pipeline. + * + * The omap4iss_pipeline_pm_use() function must be called in the open() and + * close() handlers of video device nodes. It increments or decrements the use + * count of all subdev entities in the pipeline. + * + * To react to link management on powered pipelines, the link setup notification + * callback updates the use count of all entities in the source and sink sides + * of the link. + */ + +/* + * iss_pipeline_pm_use_count - Count the number of users of a pipeline + * @entity: The entity + * + * Return the total number of users of all video device nodes in the pipeline. + */ +static int iss_pipeline_pm_use_count(struct media_entity *entity) +{ + struct media_entity_graph graph; + int use = 0; + + media_entity_graph_walk_start(&graph, entity); + + while ((entity = media_entity_graph_walk_next(&graph))) { + if (media_entity_type(entity) == MEDIA_ENT_T_DEVNODE) + use += entity->use_count; + } + + return use; +} + +/* + * iss_pipeline_pm_power_one - Apply power change to an entity + * @entity: The entity + * @change: Use count change + * + * Change the entity use count by @change. If the entity is a subdev update its + * power state by calling the core::s_power operation when the use count goes + * from 0 to != 0 or from != 0 to 0. + * + * Return 0 on success or a negative error code on failure. + */ +static int iss_pipeline_pm_power_one(struct media_entity *entity, int change) +{ + struct v4l2_subdev *subdev; + + subdev = media_entity_type(entity) == MEDIA_ENT_T_V4L2_SUBDEV + ? media_entity_to_v4l2_subdev(entity) : NULL; + + if (entity->use_count == 0 && change > 0 && subdev != NULL) { + int ret; + + ret = v4l2_subdev_call(subdev, core, s_power, 1); + if (ret < 0 && ret != -ENOIOCTLCMD) + return ret; + } + + entity->use_count += change; + WARN_ON(entity->use_count < 0); + + if (entity->use_count == 0 && change < 0 && subdev != NULL) + v4l2_subdev_call(subdev, core, s_power, 0); + + return 0; +} + +/* + * iss_pipeline_pm_power - Apply power change to all entities in a pipeline + * @entity: The entity + * @change: Use count change + * + * Walk the pipeline to update the use count and the power state of all non-node + * entities. + * + * Return 0 on success or a negative error code on failure. + */ +static int iss_pipeline_pm_power(struct media_entity *entity, int change) +{ + struct media_entity_graph graph; + struct media_entity *first = entity; + int ret = 0; + + if (!change) + return 0; + + media_entity_graph_walk_start(&graph, entity); + + while (!ret && (entity = media_entity_graph_walk_next(&graph))) + if (media_entity_type(entity) != MEDIA_ENT_T_DEVNODE) + ret = iss_pipeline_pm_power_one(entity, change); + + if (!ret) + return 0; + + media_entity_graph_walk_start(&graph, first); + + while ((first = media_entity_graph_walk_next(&graph)) + && first != entity) + if (media_entity_type(first) != MEDIA_ENT_T_DEVNODE) + iss_pipeline_pm_power_one(first, -change); + + return ret; +} + +/* + * omap4iss_pipeline_pm_use - Update the use count of an entity + * @entity: The entity + * @use: Use (1) or stop using (0) the entity + * + * Update the use count of all entities in the pipeline and power entities on or + * off accordingly. + * + * Return 0 on success or a negative error code on failure. Powering entities + * off is assumed to never fail. No failure can occur when the use parameter is + * set to 0. + */ +int omap4iss_pipeline_pm_use(struct media_entity *entity, int use) +{ + int change = use ? 1 : -1; + int ret; + + mutex_lock(&entity->parent->graph_mutex); + + /* Apply use count to node. */ + entity->use_count += change; + WARN_ON(entity->use_count < 0); + + /* Apply power change to connected non-nodes. */ + ret = iss_pipeline_pm_power(entity, change); + if (ret < 0) + entity->use_count -= change; + + mutex_unlock(&entity->parent->graph_mutex); + + return ret; +} + +/* + * iss_pipeline_link_notify - Link management notification callback + * @link: The link + * @flags: New link flags that will be applied + * + * React to link management on powered pipelines by updating the use count of + * all entities in the source and sink sides of the link. Entities are powered + * on or off accordingly. + * + * Return 0 on success or a negative error code on failure. Powering entities + * off is assumed to never fail. This function will not fail for disconnection + * events. + */ +static int iss_pipeline_link_notify(struct media_link *link, u32 flags, + unsigned int notification) +{ + struct media_entity *source = link->source->entity; + struct media_entity *sink = link->sink->entity; + int source_use = iss_pipeline_pm_use_count(source); + int sink_use = iss_pipeline_pm_use_count(sink); + int ret; + + if (notification == MEDIA_DEV_NOTIFY_POST_LINK_CH && + !(link->flags & MEDIA_LNK_FL_ENABLED)) { + /* Powering off entities is assumed to never fail. */ + iss_pipeline_pm_power(source, -sink_use); + iss_pipeline_pm_power(sink, -source_use); + return 0; + } + + if (notification == MEDIA_DEV_NOTIFY_POST_LINK_CH && + (flags & MEDIA_LNK_FL_ENABLED)) { + ret = iss_pipeline_pm_power(source, sink_use); + if (ret < 0) + return ret; + + ret = iss_pipeline_pm_power(sink, source_use); + if (ret < 0) + iss_pipeline_pm_power(source, -sink_use); + + return ret; + } + + return 0; +} + +/* ----------------------------------------------------------------------------- + * Pipeline stream management + */ + +/* + * iss_pipeline_enable - Enable streaming on a pipeline + * @pipe: ISS pipeline + * @mode: Stream mode (single shot or continuous) + * + * Walk the entities chain starting at the pipeline output video node and start + * all modules in the chain in the given mode. + * + * Return 0 if successful, or the return value of the failed video::s_stream + * operation otherwise. + */ +static int iss_pipeline_enable(struct iss_pipeline *pipe, + enum iss_pipeline_stream_state mode) +{ + struct iss_device *iss = pipe->output->iss; + struct media_entity *entity; + struct media_pad *pad; + struct v4l2_subdev *subdev; + unsigned long flags; + int ret; + + /* If one of the entities in the pipeline has crashed it will not work + * properly. Refuse to start streaming in that case. This check must be + * performed before the loop below to avoid starting entities if the + * pipeline won't start anyway (those entities would then likely fail to + * stop, making the problem worse). + */ + if (pipe->entities & iss->crashed) + return -EIO; + + spin_lock_irqsave(&pipe->lock, flags); + pipe->state &= ~(ISS_PIPELINE_IDLE_INPUT | ISS_PIPELINE_IDLE_OUTPUT); + spin_unlock_irqrestore(&pipe->lock, flags); + + pipe->do_propagation = false; + + entity = &pipe->output->video.entity; + while (1) { + pad = &entity->pads[0]; + if (!(pad->flags & MEDIA_PAD_FL_SINK)) + break; + + pad = media_entity_remote_pad(pad); + if (pad == NULL || + media_entity_type(pad->entity) != MEDIA_ENT_T_V4L2_SUBDEV) + break; + + entity = pad->entity; + subdev = media_entity_to_v4l2_subdev(entity); + + ret = v4l2_subdev_call(subdev, video, s_stream, mode); + if (ret < 0 && ret != -ENOIOCTLCMD) + return ret; + } + iss_print_status(pipe->output->iss); + return 0; +} + +/* + * iss_pipeline_disable - Disable streaming on a pipeline + * @pipe: ISS pipeline + * + * Walk the entities chain starting at the pipeline output video node and stop + * all modules in the chain. Wait synchronously for the modules to be stopped if + * necessary. + */ +static int iss_pipeline_disable(struct iss_pipeline *pipe) +{ + struct iss_device *iss = pipe->output->iss; + struct media_entity *entity; + struct media_pad *pad; + struct v4l2_subdev *subdev; + int failure = 0; + int ret; + + entity = &pipe->output->video.entity; + while (1) { + pad = &entity->pads[0]; + if (!(pad->flags & MEDIA_PAD_FL_SINK)) + break; + + pad = media_entity_remote_pad(pad); + if (pad == NULL || + media_entity_type(pad->entity) != MEDIA_ENT_T_V4L2_SUBDEV) + break; + + entity = pad->entity; + subdev = media_entity_to_v4l2_subdev(entity); + + ret = v4l2_subdev_call(subdev, video, s_stream, 0); + if (ret < 0) { + dev_dbg(iss->dev, "%s: module stop timeout.\n", + subdev->name); + /* If the entity failed to stopped, assume it has + * crashed. Mark it as such, the ISS will be reset when + * applications will release it. + */ + iss->crashed |= 1U << subdev->entity.id; + failure = -ETIMEDOUT; + } + } + + return failure; +} + +/* + * omap4iss_pipeline_set_stream - Enable/disable streaming on a pipeline + * @pipe: ISS pipeline + * @state: Stream state (stopped, single shot or continuous) + * + * Set the pipeline to the given stream state. Pipelines can be started in + * single-shot or continuous mode. + * + * Return 0 if successful, or the return value of the failed video::s_stream + * operation otherwise. The pipeline state is not updated when the operation + * fails, except when stopping the pipeline. + */ +int omap4iss_pipeline_set_stream(struct iss_pipeline *pipe, + enum iss_pipeline_stream_state state) +{ + int ret; + + if (state == ISS_PIPELINE_STREAM_STOPPED) + ret = iss_pipeline_disable(pipe); + else + ret = iss_pipeline_enable(pipe, state); + + if (ret == 0 || state == ISS_PIPELINE_STREAM_STOPPED) + pipe->stream_state = state; + + return ret; +} + +/* + * omap4iss_pipeline_cancel_stream - Cancel stream on a pipeline + * @pipe: ISS pipeline + * + * Cancelling a stream mark all buffers on all video nodes in the pipeline as + * erroneous and makes sure no new buffer can be queued. This function is called + * when a fatal error that prevents any further operation on the pipeline + * occurs. + */ +void omap4iss_pipeline_cancel_stream(struct iss_pipeline *pipe) +{ + if (pipe->input) + omap4iss_video_cancel_stream(pipe->input); + if (pipe->output) + omap4iss_video_cancel_stream(pipe->output); +} + +/* + * iss_pipeline_is_last - Verify if entity has an enabled link to the output + * video node + * @me: ISS module's media entity + * + * Returns 1 if the entity has an enabled link to the output video node or 0 + * otherwise. It's true only while pipeline can have no more than one output + * node. + */ +static int iss_pipeline_is_last(struct media_entity *me) +{ + struct iss_pipeline *pipe; + struct media_pad *pad; + + if (!me->pipe) + return 0; + pipe = to_iss_pipeline(me); + if (pipe->stream_state == ISS_PIPELINE_STREAM_STOPPED) + return 0; + pad = media_entity_remote_pad(&pipe->output->pad); + return pad->entity == me; +} + +static int iss_reset(struct iss_device *iss) +{ + unsigned int timeout; + + iss_reg_set(iss, OMAP4_ISS_MEM_TOP, ISS_HL_SYSCONFIG, + ISS_HL_SYSCONFIG_SOFTRESET); + + timeout = iss_poll_condition_timeout( + !(iss_reg_read(iss, OMAP4_ISS_MEM_TOP, ISS_HL_SYSCONFIG) & + ISS_HL_SYSCONFIG_SOFTRESET), 1000, 10, 100); + if (timeout) { + dev_err(iss->dev, "ISS reset timeout\n"); + return -ETIMEDOUT; + } + + iss->crashed = 0; + return 0; +} + +static int iss_isp_reset(struct iss_device *iss) +{ + unsigned int timeout; + + /* Fist, ensure that the ISP is IDLE (no transactions happening) */ + iss_reg_update(iss, OMAP4_ISS_MEM_ISP_SYS1, ISP5_SYSCONFIG, + ISP5_SYSCONFIG_STANDBYMODE_MASK, + ISP5_SYSCONFIG_STANDBYMODE_SMART); + + iss_reg_set(iss, OMAP4_ISS_MEM_ISP_SYS1, ISP5_CTRL, ISP5_CTRL_MSTANDBY); + + timeout = iss_poll_condition_timeout( + iss_reg_read(iss, OMAP4_ISS_MEM_ISP_SYS1, ISP5_CTRL) & + ISP5_CTRL_MSTANDBY_WAIT, 1000000, 1000, 1500); + if (timeout) { + dev_err(iss->dev, "ISP5 standby timeout\n"); + return -ETIMEDOUT; + } + + /* Now finally, do the reset */ + iss_reg_set(iss, OMAP4_ISS_MEM_ISP_SYS1, ISP5_SYSCONFIG, + ISP5_SYSCONFIG_SOFTRESET); + + timeout = iss_poll_condition_timeout( + !(iss_reg_read(iss, OMAP4_ISS_MEM_ISP_SYS1, ISP5_SYSCONFIG) & + ISP5_SYSCONFIG_SOFTRESET), 1000000, 1000, 1500); + if (timeout) { + dev_err(iss->dev, "ISP5 reset timeout\n"); + return -ETIMEDOUT; + } + + return 0; +} + +/* + * iss_module_sync_idle - Helper to sync module with its idle state + * @me: ISS submodule's media entity + * @wait: ISS submodule's wait queue for streamoff/interrupt synchronization + * @stopping: flag which tells module wants to stop + * + * This function checks if ISS submodule needs to wait for next interrupt. If + * yes, makes the caller to sleep while waiting for such event. + */ +int omap4iss_module_sync_idle(struct media_entity *me, wait_queue_head_t *wait, + atomic_t *stopping) +{ + struct iss_pipeline *pipe = to_iss_pipeline(me); + struct iss_video *video = pipe->output; + unsigned long flags; + + if (pipe->stream_state == ISS_PIPELINE_STREAM_STOPPED || + (pipe->stream_state == ISS_PIPELINE_STREAM_SINGLESHOT && + !iss_pipeline_ready(pipe))) + return 0; + + /* + * atomic_set() doesn't include memory barrier on ARM platform for SMP + * scenario. We'll call it here to avoid race conditions. + */ + atomic_set(stopping, 1); + smp_wmb(); + + /* + * If module is the last one, it's writing to memory. In this case, + * it's necessary to check if the module is already paused due to + * DMA queue underrun or if it has to wait for next interrupt to be + * idle. + * If it isn't the last one, the function won't sleep but *stopping + * will still be set to warn next submodule caller's interrupt the + * module wants to be idle. + */ + if (!iss_pipeline_is_last(me)) + return 0; + + spin_lock_irqsave(&video->qlock, flags); + if (video->dmaqueue_flags & ISS_VIDEO_DMAQUEUE_UNDERRUN) { + spin_unlock_irqrestore(&video->qlock, flags); + atomic_set(stopping, 0); + smp_wmb(); + return 0; + } + spin_unlock_irqrestore(&video->qlock, flags); + if (!wait_event_timeout(*wait, !atomic_read(stopping), + msecs_to_jiffies(1000))) { + atomic_set(stopping, 0); + smp_wmb(); + return -ETIMEDOUT; + } + + return 0; +} + +/* + * omap4iss_module_sync_is_stopped - Helper to verify if module was stopping + * @wait: ISS submodule's wait queue for streamoff/interrupt synchronization + * @stopping: flag which tells module wants to stop + * + * This function checks if ISS submodule was stopping. In case of yes, it + * notices the caller by setting stopping to 0 and waking up the wait queue. + * Returns 1 if it was stopping or 0 otherwise. + */ +int omap4iss_module_sync_is_stopping(wait_queue_head_t *wait, + atomic_t *stopping) +{ + if (atomic_cmpxchg(stopping, 1, 0)) { + wake_up(wait); + return 1; + } + + return 0; +} + +/* -------------------------------------------------------------------------- + * Clock management + */ + +#define ISS_CLKCTRL_MASK (ISS_CLKCTRL_CSI2_A |\ + ISS_CLKCTRL_CSI2_B |\ + ISS_CLKCTRL_ISP) + +static int __iss_subclk_update(struct iss_device *iss) +{ + u32 clk = 0; + int ret = 0, timeout = 1000; + + if (iss->subclk_resources & OMAP4_ISS_SUBCLK_CSI2_A) + clk |= ISS_CLKCTRL_CSI2_A; + + if (iss->subclk_resources & OMAP4_ISS_SUBCLK_CSI2_B) + clk |= ISS_CLKCTRL_CSI2_B; + + if (iss->subclk_resources & OMAP4_ISS_SUBCLK_ISP) + clk |= ISS_CLKCTRL_ISP; + + iss_reg_update(iss, OMAP4_ISS_MEM_TOP, ISS_CLKCTRL, + ISS_CLKCTRL_MASK, clk); + + /* Wait for HW assertion */ + while (--timeout > 0) { + udelay(1); + if ((iss_reg_read(iss, OMAP4_ISS_MEM_TOP, ISS_CLKSTAT) & + ISS_CLKCTRL_MASK) == clk) + break; + } + + if (!timeout) + ret = -EBUSY; + + return ret; +} + +int omap4iss_subclk_enable(struct iss_device *iss, + enum iss_subclk_resource res) +{ + iss->subclk_resources |= res; + + return __iss_subclk_update(iss); +} + +int omap4iss_subclk_disable(struct iss_device *iss, + enum iss_subclk_resource res) +{ + iss->subclk_resources &= ~res; + + return __iss_subclk_update(iss); +} + +#define ISS_ISP5_CLKCTRL_MASK (ISP5_CTRL_BL_CLK_ENABLE |\ + ISP5_CTRL_ISIF_CLK_ENABLE |\ + ISP5_CTRL_H3A_CLK_ENABLE |\ + ISP5_CTRL_RSZ_CLK_ENABLE |\ + ISP5_CTRL_IPIPE_CLK_ENABLE |\ + ISP5_CTRL_IPIPEIF_CLK_ENABLE) + +static void __iss_isp_subclk_update(struct iss_device *iss) +{ + u32 clk = 0; + + if (iss->isp_subclk_resources & OMAP4_ISS_ISP_SUBCLK_ISIF) + clk |= ISP5_CTRL_ISIF_CLK_ENABLE; + + if (iss->isp_subclk_resources & OMAP4_ISS_ISP_SUBCLK_H3A) + clk |= ISP5_CTRL_H3A_CLK_ENABLE; + + if (iss->isp_subclk_resources & OMAP4_ISS_ISP_SUBCLK_RSZ) + clk |= ISP5_CTRL_RSZ_CLK_ENABLE; + + if (iss->isp_subclk_resources & OMAP4_ISS_ISP_SUBCLK_IPIPE) + clk |= ISP5_CTRL_IPIPE_CLK_ENABLE; + + if (iss->isp_subclk_resources & OMAP4_ISS_ISP_SUBCLK_IPIPEIF) + clk |= ISP5_CTRL_IPIPEIF_CLK_ENABLE; + + if (clk) + clk |= ISP5_CTRL_BL_CLK_ENABLE; + + iss_reg_update(iss, OMAP4_ISS_MEM_ISP_SYS1, ISP5_CTRL, + ISS_ISP5_CLKCTRL_MASK, clk); +} + +void omap4iss_isp_subclk_enable(struct iss_device *iss, + enum iss_isp_subclk_resource res) +{ + iss->isp_subclk_resources |= res; + + __iss_isp_subclk_update(iss); +} + +void omap4iss_isp_subclk_disable(struct iss_device *iss, + enum iss_isp_subclk_resource res) +{ + iss->isp_subclk_resources &= ~res; + + __iss_isp_subclk_update(iss); +} + +/* + * iss_enable_clocks - Enable ISS clocks + * @iss: OMAP4 ISS device + * + * Return 0 if successful, or clk_enable return value if any of tthem fails. + */ +static int iss_enable_clocks(struct iss_device *iss) +{ + int ret; + + ret = clk_enable(iss->iss_fck); + if (ret) { + dev_err(iss->dev, "clk_enable iss_fck failed\n"); + return ret; + } + + ret = clk_enable(iss->iss_ctrlclk); + if (ret) { + dev_err(iss->dev, "clk_enable iss_ctrlclk failed\n"); + clk_disable(iss->iss_fck); + return ret; + } + + return 0; +} + +/* + * iss_disable_clocks - Disable ISS clocks + * @iss: OMAP4 ISS device + */ +static void iss_disable_clocks(struct iss_device *iss) +{ + clk_disable(iss->iss_ctrlclk); + clk_disable(iss->iss_fck); +} + +static void iss_put_clocks(struct iss_device *iss) +{ + if (iss->iss_fck) { + clk_put(iss->iss_fck); + iss->iss_fck = NULL; + } + + if (iss->iss_ctrlclk) { + clk_put(iss->iss_ctrlclk); + iss->iss_ctrlclk = NULL; + } +} + +static int iss_get_clocks(struct iss_device *iss) +{ + iss->iss_fck = clk_get(iss->dev, "iss_fck"); + if (IS_ERR(iss->iss_fck)) { + dev_err(iss->dev, "Unable to get iss_fck clock info\n"); + iss_put_clocks(iss); + return PTR_ERR(iss->iss_fck); + } + + iss->iss_ctrlclk = clk_get(iss->dev, "iss_ctrlclk"); + if (IS_ERR(iss->iss_ctrlclk)) { + dev_err(iss->dev, "Unable to get iss_ctrlclk clock info\n"); + iss_put_clocks(iss); + return PTR_ERR(iss->iss_fck); + } + + return 0; +} + +/* + * omap4iss_get - Acquire the ISS resource. + * + * Initializes the clocks for the first acquire. + * + * Increment the reference count on the ISS. If the first reference is taken, + * enable clocks and power-up all submodules. + * + * Return a pointer to the ISS device structure, or NULL if an error occurred. + */ +struct iss_device *omap4iss_get(struct iss_device *iss) +{ + struct iss_device *__iss = iss; + + if (iss == NULL) + return NULL; + + mutex_lock(&iss->iss_mutex); + if (iss->ref_count > 0) + goto out; + + if (iss_enable_clocks(iss) < 0) { + __iss = NULL; + goto out; + } + + iss_enable_interrupts(iss); + +out: + if (__iss != NULL) + iss->ref_count++; + mutex_unlock(&iss->iss_mutex); + + return __iss; +} + +/* + * omap4iss_put - Release the ISS + * + * Decrement the reference count on the ISS. If the last reference is released, + * power-down all submodules, disable clocks and free temporary buffers. + */ +void omap4iss_put(struct iss_device *iss) +{ + if (iss == NULL) + return; + + mutex_lock(&iss->iss_mutex); + BUG_ON(iss->ref_count == 0); + if (--iss->ref_count == 0) { + iss_disable_interrupts(iss); + /* Reset the ISS if an entity has failed to stop. This is the + * only way to recover from such conditions, although it would + * be worth investigating whether resetting the ISP only can't + * fix the problem in some cases. + */ + if (iss->crashed) + iss_reset(iss); + iss_disable_clocks(iss); + } + mutex_unlock(&iss->iss_mutex); +} + +static int iss_map_mem_resource(struct platform_device *pdev, + struct iss_device *iss, + enum iss_mem_resources res) +{ + struct resource *mem; + + /* request the mem region for the camera registers */ + + mem = platform_get_resource(pdev, IORESOURCE_MEM, res); + if (!mem) { + dev_err(iss->dev, "no mem resource?\n"); + return -ENODEV; + } + + if (!request_mem_region(mem->start, resource_size(mem), pdev->name)) { + dev_err(iss->dev, + "cannot reserve camera register I/O region\n"); + return -ENODEV; + } + iss->res[res] = mem; + + /* map the region */ + iss->regs[res] = ioremap_nocache(mem->start, resource_size(mem)); + if (!iss->regs[res]) { + dev_err(iss->dev, "cannot map camera register I/O region\n"); + return -ENODEV; + } + + return 0; +} + +static void iss_unregister_entities(struct iss_device *iss) +{ + omap4iss_resizer_unregister_entities(&iss->resizer); + omap4iss_ipipe_unregister_entities(&iss->ipipe); + omap4iss_ipipeif_unregister_entities(&iss->ipipeif); + omap4iss_csi2_unregister_entities(&iss->csi2a); + omap4iss_csi2_unregister_entities(&iss->csi2b); + + v4l2_device_unregister(&iss->v4l2_dev); + media_device_unregister(&iss->media_dev); +} + +/* + * iss_register_subdev_group - Register a group of subdevices + * @iss: OMAP4 ISS device + * @board_info: I2C subdevs board information array + * + * Register all I2C subdevices in the board_info array. The array must be + * terminated by a NULL entry, and the first entry must be the sensor. + * + * Return a pointer to the sensor media entity if it has been successfully + * registered, or NULL otherwise. + */ +static struct v4l2_subdev * +iss_register_subdev_group(struct iss_device *iss, + struct iss_subdev_i2c_board_info *board_info) +{ + struct v4l2_subdev *sensor = NULL; + unsigned int first; + + if (board_info->board_info == NULL) + return NULL; + + for (first = 1; board_info->board_info; ++board_info, first = 0) { + struct v4l2_subdev *subdev; + struct i2c_adapter *adapter; + + adapter = i2c_get_adapter(board_info->i2c_adapter_id); + if (adapter == NULL) { + dev_err(iss->dev, + "%s: Unable to get I2C adapter %d for device %s\n", + __func__, board_info->i2c_adapter_id, + board_info->board_info->type); + continue; + } + + subdev = v4l2_i2c_new_subdev_board(&iss->v4l2_dev, adapter, + board_info->board_info, NULL); + if (subdev == NULL) { + dev_err(iss->dev, "%s: Unable to register subdev %s\n", + __func__, board_info->board_info->type); + continue; + } + + if (first) + sensor = subdev; + } + + return sensor; +} + +static int iss_register_entities(struct iss_device *iss) +{ + struct iss_platform_data *pdata = iss->pdata; + struct iss_v4l2_subdevs_group *subdevs; + int ret; + + iss->media_dev.dev = iss->dev; + strlcpy(iss->media_dev.model, "TI OMAP4 ISS", + sizeof(iss->media_dev.model)); + iss->media_dev.hw_revision = iss->revision; + iss->media_dev.link_notify = iss_pipeline_link_notify; + ret = media_device_register(&iss->media_dev); + if (ret < 0) { + dev_err(iss->dev, "%s: Media device registration failed (%d)\n", + __func__, ret); + return ret; + } + + iss->v4l2_dev.mdev = &iss->media_dev; + ret = v4l2_device_register(iss->dev, &iss->v4l2_dev); + if (ret < 0) { + dev_err(iss->dev, "%s: V4L2 device registration failed (%d)\n", + __func__, ret); + goto done; + } + + /* Register internal entities */ + ret = omap4iss_csi2_register_entities(&iss->csi2a, &iss->v4l2_dev); + if (ret < 0) + goto done; + + ret = omap4iss_csi2_register_entities(&iss->csi2b, &iss->v4l2_dev); + if (ret < 0) + goto done; + + ret = omap4iss_ipipeif_register_entities(&iss->ipipeif, &iss->v4l2_dev); + if (ret < 0) + goto done; + + ret = omap4iss_ipipe_register_entities(&iss->ipipe, &iss->v4l2_dev); + if (ret < 0) + goto done; + + ret = omap4iss_resizer_register_entities(&iss->resizer, &iss->v4l2_dev); + if (ret < 0) + goto done; + + /* Register external entities */ + for (subdevs = pdata->subdevs; subdevs && subdevs->subdevs; ++subdevs) { + struct v4l2_subdev *sensor; + struct media_entity *input; + unsigned int flags; + unsigned int pad; + + sensor = iss_register_subdev_group(iss, subdevs->subdevs); + if (sensor == NULL) + continue; + + sensor->host_priv = subdevs; + + /* Connect the sensor to the correct interface module. + * CSI2a receiver through CSIPHY1, or + * CSI2b receiver through CSIPHY2 + */ + switch (subdevs->interface) { + case ISS_INTERFACE_CSI2A_PHY1: + input = &iss->csi2a.subdev.entity; + pad = CSI2_PAD_SINK; + flags = MEDIA_LNK_FL_IMMUTABLE + | MEDIA_LNK_FL_ENABLED; + break; + + case ISS_INTERFACE_CSI2B_PHY2: + input = &iss->csi2b.subdev.entity; + pad = CSI2_PAD_SINK; + flags = MEDIA_LNK_FL_IMMUTABLE + | MEDIA_LNK_FL_ENABLED; + break; + + default: + dev_err(iss->dev, "%s: invalid interface type %u\n", + __func__, subdevs->interface); + ret = -EINVAL; + goto done; + } + + ret = media_entity_create_link(&sensor->entity, 0, input, pad, + flags); + if (ret < 0) + goto done; + } + + ret = v4l2_device_register_subdev_nodes(&iss->v4l2_dev); + +done: + if (ret < 0) + iss_unregister_entities(iss); + + return ret; +} + +static void iss_cleanup_modules(struct iss_device *iss) +{ + omap4iss_csi2_cleanup(iss); + omap4iss_ipipeif_cleanup(iss); + omap4iss_ipipe_cleanup(iss); + omap4iss_resizer_cleanup(iss); +} + +static int iss_initialize_modules(struct iss_device *iss) +{ + int ret; + + ret = omap4iss_csiphy_init(iss); + if (ret < 0) { + dev_err(iss->dev, "CSI PHY initialization failed\n"); + goto error_csiphy; + } + + ret = omap4iss_csi2_init(iss); + if (ret < 0) { + dev_err(iss->dev, "CSI2 initialization failed\n"); + goto error_csi2; + } + + ret = omap4iss_ipipeif_init(iss); + if (ret < 0) { + dev_err(iss->dev, "ISP IPIPEIF initialization failed\n"); + goto error_ipipeif; + } + + ret = omap4iss_ipipe_init(iss); + if (ret < 0) { + dev_err(iss->dev, "ISP IPIPE initialization failed\n"); + goto error_ipipe; + } + + ret = omap4iss_resizer_init(iss); + if (ret < 0) { + dev_err(iss->dev, "ISP RESIZER initialization failed\n"); + goto error_resizer; + } + + /* Connect the submodules. */ + ret = media_entity_create_link( + &iss->csi2a.subdev.entity, CSI2_PAD_SOURCE, + &iss->ipipeif.subdev.entity, IPIPEIF_PAD_SINK, 0); + if (ret < 0) + goto error_link; + + ret = media_entity_create_link( + &iss->csi2b.subdev.entity, CSI2_PAD_SOURCE, + &iss->ipipeif.subdev.entity, IPIPEIF_PAD_SINK, 0); + if (ret < 0) + goto error_link; + + ret = media_entity_create_link( + &iss->ipipeif.subdev.entity, IPIPEIF_PAD_SOURCE_VP, + &iss->resizer.subdev.entity, RESIZER_PAD_SINK, 0); + if (ret < 0) + goto error_link; + + ret = media_entity_create_link( + &iss->ipipeif.subdev.entity, IPIPEIF_PAD_SOURCE_VP, + &iss->ipipe.subdev.entity, IPIPE_PAD_SINK, 0); + if (ret < 0) + goto error_link; + + ret = media_entity_create_link( + &iss->ipipe.subdev.entity, IPIPE_PAD_SOURCE_VP, + &iss->resizer.subdev.entity, RESIZER_PAD_SINK, 0); + if (ret < 0) + goto error_link; + + return 0; + +error_link: + omap4iss_resizer_cleanup(iss); +error_resizer: + omap4iss_ipipe_cleanup(iss); +error_ipipe: + omap4iss_ipipeif_cleanup(iss); +error_ipipeif: + omap4iss_csi2_cleanup(iss); +error_csi2: +error_csiphy: + return ret; +} + +static int iss_probe(struct platform_device *pdev) +{ + struct iss_platform_data *pdata = pdev->dev.platform_data; + struct iss_device *iss; + unsigned int i; + int ret; + + if (pdata == NULL) + return -EINVAL; + + iss = kzalloc(sizeof(*iss), GFP_KERNEL); + if (!iss) { + dev_err(&pdev->dev, "Could not allocate memory\n"); + return -ENOMEM; + } + + mutex_init(&iss->iss_mutex); + + iss->dev = &pdev->dev; + iss->pdata = pdata; + + iss->raw_dmamask = DMA_BIT_MASK(32); + iss->dev->dma_mask = &iss->raw_dmamask; + iss->dev->coherent_dma_mask = DMA_BIT_MASK(32); + + platform_set_drvdata(pdev, iss); + + /* Clocks */ + ret = iss_map_mem_resource(pdev, iss, OMAP4_ISS_MEM_TOP); + if (ret < 0) + goto error; + + ret = iss_get_clocks(iss); + if (ret < 0) + goto error; + + if (omap4iss_get(iss) == NULL) + goto error; + + ret = iss_reset(iss); + if (ret < 0) + goto error_iss; + + iss->revision = iss_reg_read(iss, OMAP4_ISS_MEM_TOP, ISS_HL_REVISION); + dev_info(iss->dev, "Revision %08x found\n", iss->revision); + + for (i = 1; i < OMAP4_ISS_MEM_LAST; i++) { + ret = iss_map_mem_resource(pdev, iss, i); + if (ret) + goto error_iss; + } + + /* Configure BTE BW_LIMITER field to max recommended value (1 GB) */ + iss_reg_update(iss, OMAP4_ISS_MEM_BTE, BTE_CTRL, + BTE_CTRL_BW_LIMITER_MASK, + 18 << BTE_CTRL_BW_LIMITER_SHIFT); + + /* Perform ISP reset */ + ret = omap4iss_subclk_enable(iss, OMAP4_ISS_SUBCLK_ISP); + if (ret < 0) + goto error_iss; + + ret = iss_isp_reset(iss); + if (ret < 0) + goto error_iss; + + dev_info(iss->dev, "ISP Revision %08x found\n", + iss_reg_read(iss, OMAP4_ISS_MEM_ISP_SYS1, ISP5_REVISION)); + + /* Interrupt */ + iss->irq_num = platform_get_irq(pdev, 0); + if (iss->irq_num <= 0) { + dev_err(iss->dev, "No IRQ resource\n"); + ret = -ENODEV; + goto error_iss; + } + + if (request_irq(iss->irq_num, iss_isr, IRQF_SHARED, "OMAP4 ISS", iss)) { + dev_err(iss->dev, "Unable to request IRQ\n"); + ret = -EINVAL; + goto error_iss; + } + + /* Entities */ + ret = iss_initialize_modules(iss); + if (ret < 0) + goto error_irq; + + ret = iss_register_entities(iss); + if (ret < 0) + goto error_modules; + + omap4iss_put(iss); + + return 0; + +error_modules: + iss_cleanup_modules(iss); +error_irq: + free_irq(iss->irq_num, iss); +error_iss: + omap4iss_put(iss); +error: + iss_put_clocks(iss); + + for (i = 0; i < OMAP4_ISS_MEM_LAST; i++) { + if (iss->regs[i]) { + iounmap(iss->regs[i]); + iss->regs[i] = NULL; + } + + if (iss->res[i]) { + release_mem_region(iss->res[i]->start, + resource_size(iss->res[i])); + iss->res[i] = NULL; + } + } + platform_set_drvdata(pdev, NULL); + + mutex_destroy(&iss->iss_mutex); + kfree(iss); + + return ret; +} + +static int iss_remove(struct platform_device *pdev) +{ + struct iss_device *iss = platform_get_drvdata(pdev); + unsigned int i; + + iss_unregister_entities(iss); + iss_cleanup_modules(iss); + + free_irq(iss->irq_num, iss); + iss_put_clocks(iss); + + for (i = 0; i < OMAP4_ISS_MEM_LAST; i++) { + if (iss->regs[i]) { + iounmap(iss->regs[i]); + iss->regs[i] = NULL; + } + + if (iss->res[i]) { + release_mem_region(iss->res[i]->start, + resource_size(iss->res[i])); + iss->res[i] = NULL; + } + } + + kfree(iss); + + return 0; +} + +static struct platform_device_id omap4iss_id_table[] = { + { "omap4iss", 0 }, + { }, +}; +MODULE_DEVICE_TABLE(platform, omap4iss_id_table); + +static struct platform_driver iss_driver = { + .probe = iss_probe, + .remove = iss_remove, + .id_table = omap4iss_id_table, + .driver = { + .owner = THIS_MODULE, + .name = "omap4iss", + }, +}; + +module_platform_driver(iss_driver); + +MODULE_DESCRIPTION("TI OMAP4 ISS driver"); +MODULE_AUTHOR("Sergio Aguirre <sergio.a.aguirre@gmail.com>"); +MODULE_LICENSE("GPL"); +MODULE_VERSION(ISS_VIDEO_DRIVER_VERSION); diff --git a/drivers/staging/media/omap4iss/iss.h b/drivers/staging/media/omap4iss/iss.h new file mode 100644 index 00000000000..05cd9bf3b41 --- /dev/null +++ b/drivers/staging/media/omap4iss/iss.h @@ -0,0 +1,250 @@ +/* + * TI OMAP4 ISS V4L2 Driver + * + * Copyright (C) 2012 Texas Instruments. + * + * Author: Sergio Aguirre <sergio.a.aguirre@gmail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#ifndef _OMAP4_ISS_H_ +#define _OMAP4_ISS_H_ + +#include <media/v4l2-device.h> +#include <linux/device.h> +#include <linux/io.h> +#include <linux/platform_device.h> +#include <linux/wait.h> + +#include <media/omap4iss.h> + +#include "iss_regs.h" +#include "iss_csiphy.h" +#include "iss_csi2.h" +#include "iss_ipipeif.h" +#include "iss_ipipe.h" +#include "iss_resizer.h" + +#define to_iss_device(ptr_module) \ + container_of(ptr_module, struct iss_device, ptr_module) +#define to_device(ptr_module) \ + (to_iss_device(ptr_module)->dev) + +enum iss_mem_resources { + OMAP4_ISS_MEM_TOP, + OMAP4_ISS_MEM_CSI2_A_REGS1, + OMAP4_ISS_MEM_CAMERARX_CORE1, + OMAP4_ISS_MEM_CSI2_B_REGS1, + OMAP4_ISS_MEM_CAMERARX_CORE2, + OMAP4_ISS_MEM_BTE, + OMAP4_ISS_MEM_ISP_SYS1, + OMAP4_ISS_MEM_ISP_RESIZER, + OMAP4_ISS_MEM_ISP_IPIPE, + OMAP4_ISS_MEM_ISP_ISIF, + OMAP4_ISS_MEM_ISP_IPIPEIF, + OMAP4_ISS_MEM_LAST, +}; + +enum iss_subclk_resource { + OMAP4_ISS_SUBCLK_SIMCOP = (1 << 0), + OMAP4_ISS_SUBCLK_ISP = (1 << 1), + OMAP4_ISS_SUBCLK_CSI2_A = (1 << 2), + OMAP4_ISS_SUBCLK_CSI2_B = (1 << 3), + OMAP4_ISS_SUBCLK_CCP2 = (1 << 4), +}; + +enum iss_isp_subclk_resource { + OMAP4_ISS_ISP_SUBCLK_BL = (1 << 0), + OMAP4_ISS_ISP_SUBCLK_ISIF = (1 << 1), + OMAP4_ISS_ISP_SUBCLK_H3A = (1 << 2), + OMAP4_ISS_ISP_SUBCLK_RSZ = (1 << 3), + OMAP4_ISS_ISP_SUBCLK_IPIPE = (1 << 4), + OMAP4_ISS_ISP_SUBCLK_IPIPEIF = (1 << 5), +}; + +/* + * struct iss_reg - Structure for ISS register values. + * @reg: 32-bit Register address. + * @val: 32-bit Register value. + */ +struct iss_reg { + enum iss_mem_resources mmio_range; + u32 reg; + u32 val; +}; + +/* + * struct iss_device - ISS device structure. + * @crashed: Bitmask of crashed entities (indexed by entity ID) + */ +struct iss_device { + struct v4l2_device v4l2_dev; + struct media_device media_dev; + struct device *dev; + u32 revision; + + /* platform HW resources */ + struct iss_platform_data *pdata; + unsigned int irq_num; + + struct resource *res[OMAP4_ISS_MEM_LAST]; + void __iomem *regs[OMAP4_ISS_MEM_LAST]; + + u64 raw_dmamask; + + struct mutex iss_mutex; /* For handling ref_count field */ + bool crashed; + int has_context; + int ref_count; + + struct clk *iss_fck; + struct clk *iss_ctrlclk; + + /* ISS modules */ + struct iss_csi2_device csi2a; + struct iss_csi2_device csi2b; + struct iss_csiphy csiphy1; + struct iss_csiphy csiphy2; + struct iss_ipipeif_device ipipeif; + struct iss_ipipe_device ipipe; + struct iss_resizer_device resizer; + + unsigned int subclk_resources; + unsigned int isp_subclk_resources; +}; + +#define v4l2_dev_to_iss_device(dev) \ + container_of(dev, struct iss_device, v4l2_dev) + +int omap4iss_get_external_info(struct iss_pipeline *pipe, + struct media_link *link); + +int omap4iss_module_sync_idle(struct media_entity *me, wait_queue_head_t *wait, + atomic_t *stopping); + +int omap4iss_module_sync_is_stopping(wait_queue_head_t *wait, + atomic_t *stopping); + +int omap4iss_pipeline_set_stream(struct iss_pipeline *pipe, + enum iss_pipeline_stream_state state); +void omap4iss_pipeline_cancel_stream(struct iss_pipeline *pipe); + +void omap4iss_configure_bridge(struct iss_device *iss, + enum ipipeif_input_entity input); + +struct iss_device *omap4iss_get(struct iss_device *iss); +void omap4iss_put(struct iss_device *iss); +int omap4iss_subclk_enable(struct iss_device *iss, + enum iss_subclk_resource res); +int omap4iss_subclk_disable(struct iss_device *iss, + enum iss_subclk_resource res); +void omap4iss_isp_subclk_enable(struct iss_device *iss, + enum iss_isp_subclk_resource res); +void omap4iss_isp_subclk_disable(struct iss_device *iss, + enum iss_isp_subclk_resource res); + +int omap4iss_pipeline_pm_use(struct media_entity *entity, int use); + +int omap4iss_register_entities(struct platform_device *pdev, + struct v4l2_device *v4l2_dev); +void omap4iss_unregister_entities(struct platform_device *pdev); + +/* + * iss_reg_read - Read the value of an OMAP4 ISS register + * @iss: the ISS device + * @res: memory resource in which the register is located + * @offset: register offset in the memory resource + * + * Return the register value. + */ +static inline +u32 iss_reg_read(struct iss_device *iss, enum iss_mem_resources res, + u32 offset) +{ + return readl(iss->regs[res] + offset); +} + +/* + * iss_reg_write - Write a value to an OMAP4 ISS register + * @iss: the ISS device + * @res: memory resource in which the register is located + * @offset: register offset in the memory resource + * @value: value to be written + */ +static inline +void iss_reg_write(struct iss_device *iss, enum iss_mem_resources res, + u32 offset, u32 value) +{ + writel(value, iss->regs[res] + offset); +} + +/* + * iss_reg_clr - Clear bits in an OMAP4 ISS register + * @iss: the ISS device + * @res: memory resource in which the register is located + * @offset: register offset in the memory resource + * @clr: bit mask to be cleared + */ +static inline +void iss_reg_clr(struct iss_device *iss, enum iss_mem_resources res, + u32 offset, u32 clr) +{ + u32 v = iss_reg_read(iss, res, offset); + + iss_reg_write(iss, res, offset, v & ~clr); +} + +/* + * iss_reg_set - Set bits in an OMAP4 ISS register + * @iss: the ISS device + * @res: memory resource in which the register is located + * @offset: register offset in the memory resource + * @set: bit mask to be set + */ +static inline +void iss_reg_set(struct iss_device *iss, enum iss_mem_resources res, + u32 offset, u32 set) +{ + u32 v = iss_reg_read(iss, res, offset); + + iss_reg_write(iss, res, offset, v | set); +} + +/* + * iss_reg_update - Clear and set bits in an OMAP4 ISS register + * @iss: the ISS device + * @res: memory resource in which the register is located + * @offset: register offset in the memory resource + * @clr: bit mask to be cleared + * @set: bit mask to be set + * + * Clear the clr mask first and then set the set mask. + */ +static inline +void iss_reg_update(struct iss_device *iss, enum iss_mem_resources res, + u32 offset, u32 clr, u32 set) +{ + u32 v = iss_reg_read(iss, res, offset); + + iss_reg_write(iss, res, offset, (v & ~clr) | set); +} + +#define iss_poll_condition_timeout(cond, timeout, min_ival, max_ival) \ +({ \ + unsigned long __timeout = jiffies + usecs_to_jiffies(timeout); \ + unsigned int __min_ival = (min_ival); \ + unsigned int __max_ival = (max_ival); \ + bool __cond; \ + while (!(__cond = (cond))) { \ + if (time_after(jiffies, __timeout)) \ + break; \ + usleep_range(__min_ival, __max_ival); \ + } \ + !__cond; \ +}) + +#endif /* _OMAP4_ISS_H_ */ diff --git a/drivers/staging/media/omap4iss/iss_csi2.c b/drivers/staging/media/omap4iss/iss_csi2.c new file mode 100644 index 00000000000..bf8a6572610 --- /dev/null +++ b/drivers/staging/media/omap4iss/iss_csi2.c @@ -0,0 +1,1326 @@ +/* + * TI OMAP4 ISS V4L2 Driver - CSI PHY module + * + * Copyright (C) 2012 Texas Instruments, Inc. + * + * Author: Sergio Aguirre <sergio.a.aguirre@gmail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include <linux/delay.h> +#include <media/v4l2-common.h> +#include <linux/v4l2-mediabus.h> +#include <linux/mm.h> + +#include "iss.h" +#include "iss_regs.h" +#include "iss_csi2.h" + +/* + * csi2_if_enable - Enable CSI2 Receiver interface. + * @enable: enable flag + * + */ +static void csi2_if_enable(struct iss_csi2_device *csi2, u8 enable) +{ + struct iss_csi2_ctrl_cfg *currctrl = &csi2->ctrl; + + iss_reg_update(csi2->iss, csi2->regs1, CSI2_CTRL, CSI2_CTRL_IF_EN, + enable ? CSI2_CTRL_IF_EN : 0); + + currctrl->if_enable = enable; +} + +/* + * csi2_recv_config - CSI2 receiver module configuration. + * @currctrl: iss_csi2_ctrl_cfg structure + * + */ +static void csi2_recv_config(struct iss_csi2_device *csi2, + struct iss_csi2_ctrl_cfg *currctrl) +{ + u32 reg = 0; + + if (currctrl->frame_mode) + reg |= CSI2_CTRL_FRAME; + else + reg &= ~CSI2_CTRL_FRAME; + + if (currctrl->vp_clk_enable) + reg |= CSI2_CTRL_VP_CLK_EN; + else + reg &= ~CSI2_CTRL_VP_CLK_EN; + + if (currctrl->vp_only_enable) + reg |= CSI2_CTRL_VP_ONLY_EN; + else + reg &= ~CSI2_CTRL_VP_ONLY_EN; + + reg &= ~CSI2_CTRL_VP_OUT_CTRL_MASK; + reg |= currctrl->vp_out_ctrl << CSI2_CTRL_VP_OUT_CTRL_SHIFT; + + if (currctrl->ecc_enable) + reg |= CSI2_CTRL_ECC_EN; + else + reg &= ~CSI2_CTRL_ECC_EN; + + /* + * Set MFlag assertion boundaries to: + * Low: 4/8 of FIFO size + * High: 6/8 of FIFO size + */ + reg &= ~(CSI2_CTRL_MFLAG_LEVH_MASK | CSI2_CTRL_MFLAG_LEVL_MASK); + reg |= (2 << CSI2_CTRL_MFLAG_LEVH_SHIFT) | + (4 << CSI2_CTRL_MFLAG_LEVL_SHIFT); + + /* Generation of 16x64-bit bursts (Recommended) */ + reg |= CSI2_CTRL_BURST_SIZE_EXPAND; + + /* Do Non-Posted writes (Recommended) */ + reg |= CSI2_CTRL_NON_POSTED_WRITE; + + /* + * Enforce Little endian for all formats, including: + * YUV4:2:2 8-bit and YUV4:2:0 Legacy + */ + reg |= CSI2_CTRL_ENDIANNESS; + + iss_reg_write(csi2->iss, csi2->regs1, CSI2_CTRL, reg); +} + +static const unsigned int csi2_input_fmts[] = { + V4L2_MBUS_FMT_SGRBG10_1X10, + V4L2_MBUS_FMT_SGRBG10_DPCM8_1X8, + V4L2_MBUS_FMT_SRGGB10_1X10, + V4L2_MBUS_FMT_SRGGB10_DPCM8_1X8, + V4L2_MBUS_FMT_SBGGR10_1X10, + V4L2_MBUS_FMT_SBGGR10_DPCM8_1X8, + V4L2_MBUS_FMT_SGBRG10_1X10, + V4L2_MBUS_FMT_SGBRG10_DPCM8_1X8, + V4L2_MBUS_FMT_SBGGR8_1X8, + V4L2_MBUS_FMT_SGBRG8_1X8, + V4L2_MBUS_FMT_SGRBG8_1X8, + V4L2_MBUS_FMT_SRGGB8_1X8, + V4L2_MBUS_FMT_UYVY8_1X16, + V4L2_MBUS_FMT_YUYV8_1X16, +}; + +/* To set the format on the CSI2 requires a mapping function that takes + * the following inputs: + * - 3 different formats (at this time) + * - 2 destinations (mem, vp+mem) (vp only handled separately) + * - 2 decompression options (on, off) + * Output should be CSI2 frame format code + * Array indices as follows: [format][dest][decompr] + * Not all combinations are valid. 0 means invalid. + */ +static const u16 __csi2_fmt_map[][2][2] = { + /* RAW10 formats */ + { + /* Output to memory */ + { + /* No DPCM decompression */ + CSI2_PIX_FMT_RAW10_EXP16, + /* DPCM decompression */ + 0, + }, + /* Output to both */ + { + /* No DPCM decompression */ + CSI2_PIX_FMT_RAW10_EXP16_VP, + /* DPCM decompression */ + 0, + }, + }, + /* RAW10 DPCM8 formats */ + { + /* Output to memory */ + { + /* No DPCM decompression */ + CSI2_USERDEF_8BIT_DATA1, + /* DPCM decompression */ + CSI2_USERDEF_8BIT_DATA1_DPCM10, + }, + /* Output to both */ + { + /* No DPCM decompression */ + CSI2_PIX_FMT_RAW8_VP, + /* DPCM decompression */ + CSI2_USERDEF_8BIT_DATA1_DPCM10_VP, + }, + }, + /* RAW8 formats */ + { + /* Output to memory */ + { + /* No DPCM decompression */ + CSI2_PIX_FMT_RAW8, + /* DPCM decompression */ + 0, + }, + /* Output to both */ + { + /* No DPCM decompression */ + CSI2_PIX_FMT_RAW8_VP, + /* DPCM decompression */ + 0, + }, + }, + /* YUV422 formats */ + { + /* Output to memory */ + { + /* No DPCM decompression */ + CSI2_PIX_FMT_YUV422_8BIT, + /* DPCM decompression */ + 0, + }, + /* Output to both */ + { + /* No DPCM decompression */ + CSI2_PIX_FMT_YUV422_8BIT_VP16, + /* DPCM decompression */ + 0, + }, + }, +}; + +/* + * csi2_ctx_map_format - Map CSI2 sink media bus format to CSI2 format ID + * @csi2: ISS CSI2 device + * + * Returns CSI2 physical format id + */ +static u16 csi2_ctx_map_format(struct iss_csi2_device *csi2) +{ + const struct v4l2_mbus_framefmt *fmt = &csi2->formats[CSI2_PAD_SINK]; + int fmtidx, destidx; + + switch (fmt->code) { + case V4L2_MBUS_FMT_SGRBG10_1X10: + case V4L2_MBUS_FMT_SRGGB10_1X10: + case V4L2_MBUS_FMT_SBGGR10_1X10: + case V4L2_MBUS_FMT_SGBRG10_1X10: + fmtidx = 0; + break; + case V4L2_MBUS_FMT_SGRBG10_DPCM8_1X8: + case V4L2_MBUS_FMT_SRGGB10_DPCM8_1X8: + case V4L2_MBUS_FMT_SBGGR10_DPCM8_1X8: + case V4L2_MBUS_FMT_SGBRG10_DPCM8_1X8: + fmtidx = 1; + break; + case V4L2_MBUS_FMT_SBGGR8_1X8: + case V4L2_MBUS_FMT_SGBRG8_1X8: + case V4L2_MBUS_FMT_SGRBG8_1X8: + case V4L2_MBUS_FMT_SRGGB8_1X8: + fmtidx = 2; + break; + case V4L2_MBUS_FMT_UYVY8_1X16: + case V4L2_MBUS_FMT_YUYV8_1X16: + fmtidx = 3; + break; + default: + WARN(1, KERN_ERR "CSI2: pixel format %08x unsupported!\n", + fmt->code); + return 0; + } + + if (!(csi2->output & CSI2_OUTPUT_IPIPEIF) && + !(csi2->output & CSI2_OUTPUT_MEMORY)) { + /* Neither output enabled is a valid combination */ + return CSI2_PIX_FMT_OTHERS; + } + + /* If we need to skip frames at the beginning of the stream disable the + * video port to avoid sending the skipped frames to the IPIPEIF. + */ + destidx = csi2->frame_skip ? 0 : !!(csi2->output & CSI2_OUTPUT_IPIPEIF); + + return __csi2_fmt_map[fmtidx][destidx][csi2->dpcm_decompress]; +} + +/* + * csi2_set_outaddr - Set memory address to save output image + * @csi2: Pointer to ISS CSI2a device. + * @addr: 32-bit memory address aligned on 32 byte boundary. + * + * Sets the memory address where the output will be saved. + * + * Returns 0 if successful, or -EINVAL if the address is not in the 32 byte + * boundary. + */ +static void csi2_set_outaddr(struct iss_csi2_device *csi2, u32 addr) +{ + struct iss_csi2_ctx_cfg *ctx = &csi2->contexts[0]; + + ctx->ping_addr = addr; + ctx->pong_addr = addr; + iss_reg_write(csi2->iss, csi2->regs1, CSI2_CTX_PING_ADDR(ctx->ctxnum), + ctx->ping_addr); + iss_reg_write(csi2->iss, csi2->regs1, CSI2_CTX_PONG_ADDR(ctx->ctxnum), + ctx->pong_addr); +} + +/* + * is_usr_def_mapping - Checks whether USER_DEF_MAPPING should + * be enabled by CSI2. + * @format_id: mapped format id + * + */ +static inline int is_usr_def_mapping(u32 format_id) +{ + return (format_id & 0xf0) == 0x40 ? 1 : 0; +} + +/* + * csi2_ctx_enable - Enable specified CSI2 context + * @ctxnum: Context number, valid between 0 and 7 values. + * @enable: enable + * + */ +static void csi2_ctx_enable(struct iss_csi2_device *csi2, u8 ctxnum, u8 enable) +{ + struct iss_csi2_ctx_cfg *ctx = &csi2->contexts[ctxnum]; + u32 reg; + + reg = iss_reg_read(csi2->iss, csi2->regs1, CSI2_CTX_CTRL1(ctxnum)); + + if (enable) { + unsigned int skip = 0; + + if (csi2->frame_skip) + skip = csi2->frame_skip; + else if (csi2->output & CSI2_OUTPUT_MEMORY) + skip = 1; + + reg &= ~CSI2_CTX_CTRL1_COUNT_MASK; + reg |= CSI2_CTX_CTRL1_COUNT_UNLOCK + | (skip << CSI2_CTX_CTRL1_COUNT_SHIFT) + | CSI2_CTX_CTRL1_CTX_EN; + } else { + reg &= ~CSI2_CTX_CTRL1_CTX_EN; + } + + iss_reg_write(csi2->iss, csi2->regs1, CSI2_CTX_CTRL1(ctxnum), reg); + ctx->enabled = enable; +} + +/* + * csi2_ctx_config - CSI2 context configuration. + * @ctx: context configuration + * + */ +static void csi2_ctx_config(struct iss_csi2_device *csi2, + struct iss_csi2_ctx_cfg *ctx) +{ + u32 reg; + + /* Set up CSI2_CTx_CTRL1 */ + if (ctx->eof_enabled) + reg = CSI2_CTX_CTRL1_EOF_EN; + + if (ctx->eol_enabled) + reg |= CSI2_CTX_CTRL1_EOL_EN; + + if (ctx->checksum_enabled) + reg |= CSI2_CTX_CTRL1_CS_EN; + + iss_reg_write(csi2->iss, csi2->regs1, CSI2_CTX_CTRL1(ctx->ctxnum), reg); + + /* Set up CSI2_CTx_CTRL2 */ + reg = ctx->virtual_id << CSI2_CTX_CTRL2_VIRTUAL_ID_SHIFT; + reg |= ctx->format_id << CSI2_CTX_CTRL2_FORMAT_SHIFT; + + if (ctx->dpcm_decompress && ctx->dpcm_predictor) + reg |= CSI2_CTX_CTRL2_DPCM_PRED; + + if (is_usr_def_mapping(ctx->format_id)) + reg |= 2 << CSI2_CTX_CTRL2_USER_DEF_MAP_SHIFT; + + iss_reg_write(csi2->iss, csi2->regs1, CSI2_CTX_CTRL2(ctx->ctxnum), reg); + + /* Set up CSI2_CTx_CTRL3 */ + iss_reg_write(csi2->iss, csi2->regs1, CSI2_CTX_CTRL3(ctx->ctxnum), + ctx->alpha << CSI2_CTX_CTRL3_ALPHA_SHIFT); + + /* Set up CSI2_CTx_DAT_OFST */ + iss_reg_update(csi2->iss, csi2->regs1, CSI2_CTX_DAT_OFST(ctx->ctxnum), + CSI2_CTX_DAT_OFST_MASK, ctx->data_offset); + + iss_reg_write(csi2->iss, csi2->regs1, CSI2_CTX_PING_ADDR(ctx->ctxnum), + ctx->ping_addr); + iss_reg_write(csi2->iss, csi2->regs1, CSI2_CTX_PONG_ADDR(ctx->ctxnum), + ctx->pong_addr); +} + +/* + * csi2_timing_config - CSI2 timing configuration. + * @timing: csi2_timing_cfg structure + */ +static void csi2_timing_config(struct iss_csi2_device *csi2, + struct iss_csi2_timing_cfg *timing) +{ + u32 reg; + + reg = iss_reg_read(csi2->iss, csi2->regs1, CSI2_TIMING); + + if (timing->force_rx_mode) + reg |= CSI2_TIMING_FORCE_RX_MODE_IO1; + else + reg &= ~CSI2_TIMING_FORCE_RX_MODE_IO1; + + if (timing->stop_state_16x) + reg |= CSI2_TIMING_STOP_STATE_X16_IO1; + else + reg &= ~CSI2_TIMING_STOP_STATE_X16_IO1; + + if (timing->stop_state_4x) + reg |= CSI2_TIMING_STOP_STATE_X4_IO1; + else + reg &= ~CSI2_TIMING_STOP_STATE_X4_IO1; + + reg &= ~CSI2_TIMING_STOP_STATE_COUNTER_IO1_MASK; + reg |= timing->stop_state_counter << + CSI2_TIMING_STOP_STATE_COUNTER_IO1_SHIFT; + + iss_reg_write(csi2->iss, csi2->regs1, CSI2_TIMING, reg); +} + +/* + * csi2_irq_ctx_set - Enables CSI2 Context IRQs. + * @enable: Enable/disable CSI2 Context interrupts + */ +static void csi2_irq_ctx_set(struct iss_csi2_device *csi2, int enable) +{ + u32 reg = CSI2_CTX_IRQ_FE; + int i; + + if (csi2->use_fs_irq) + reg |= CSI2_CTX_IRQ_FS; + + for (i = 0; i < 8; i++) { + iss_reg_write(csi2->iss, csi2->regs1, CSI2_CTX_IRQSTATUS(i), + reg); + if (enable) + iss_reg_set(csi2->iss, csi2->regs1, + CSI2_CTX_IRQENABLE(i), reg); + else + iss_reg_clr(csi2->iss, csi2->regs1, + CSI2_CTX_IRQENABLE(i), reg); + } +} + +/* + * csi2_irq_complexio1_set - Enables CSI2 ComplexIO IRQs. + * @enable: Enable/disable CSI2 ComplexIO #1 interrupts + */ +static void csi2_irq_complexio1_set(struct iss_csi2_device *csi2, int enable) +{ + u32 reg; + reg = CSI2_COMPLEXIO_IRQ_STATEALLULPMEXIT | + CSI2_COMPLEXIO_IRQ_STATEALLULPMENTER | + CSI2_COMPLEXIO_IRQ_STATEULPM5 | + CSI2_COMPLEXIO_IRQ_ERRCONTROL5 | + CSI2_COMPLEXIO_IRQ_ERRESC5 | + CSI2_COMPLEXIO_IRQ_ERRSOTSYNCHS5 | + CSI2_COMPLEXIO_IRQ_ERRSOTHS5 | + CSI2_COMPLEXIO_IRQ_STATEULPM4 | + CSI2_COMPLEXIO_IRQ_ERRCONTROL4 | + CSI2_COMPLEXIO_IRQ_ERRESC4 | + CSI2_COMPLEXIO_IRQ_ERRSOTSYNCHS4 | + CSI2_COMPLEXIO_IRQ_ERRSOTHS4 | + CSI2_COMPLEXIO_IRQ_STATEULPM3 | + CSI2_COMPLEXIO_IRQ_ERRCONTROL3 | + CSI2_COMPLEXIO_IRQ_ERRESC3 | + CSI2_COMPLEXIO_IRQ_ERRSOTSYNCHS3 | + CSI2_COMPLEXIO_IRQ_ERRSOTHS3 | + CSI2_COMPLEXIO_IRQ_STATEULPM2 | + CSI2_COMPLEXIO_IRQ_ERRCONTROL2 | + CSI2_COMPLEXIO_IRQ_ERRESC2 | + CSI2_COMPLEXIO_IRQ_ERRSOTSYNCHS2 | + CSI2_COMPLEXIO_IRQ_ERRSOTHS2 | + CSI2_COMPLEXIO_IRQ_STATEULPM1 | + CSI2_COMPLEXIO_IRQ_ERRCONTROL1 | + CSI2_COMPLEXIO_IRQ_ERRESC1 | + CSI2_COMPLEXIO_IRQ_ERRSOTSYNCHS1 | + CSI2_COMPLEXIO_IRQ_ERRSOTHS1; + iss_reg_write(csi2->iss, csi2->regs1, CSI2_COMPLEXIO_IRQSTATUS, reg); + if (enable) + iss_reg_set(csi2->iss, csi2->regs1, CSI2_COMPLEXIO_IRQENABLE, + reg); + else + iss_reg_write(csi2->iss, csi2->regs1, CSI2_COMPLEXIO_IRQENABLE, + 0); +} + +/* + * csi2_irq_status_set - Enables CSI2 Status IRQs. + * @enable: Enable/disable CSI2 Status interrupts + */ +static void csi2_irq_status_set(struct iss_csi2_device *csi2, int enable) +{ + u32 reg; + reg = CSI2_IRQ_OCP_ERR | + CSI2_IRQ_SHORT_PACKET | + CSI2_IRQ_ECC_CORRECTION | + CSI2_IRQ_ECC_NO_CORRECTION | + CSI2_IRQ_COMPLEXIO_ERR | + CSI2_IRQ_FIFO_OVF | + CSI2_IRQ_CONTEXT0; + iss_reg_write(csi2->iss, csi2->regs1, CSI2_IRQSTATUS, reg); + if (enable) + iss_reg_set(csi2->iss, csi2->regs1, CSI2_IRQENABLE, reg); + else + iss_reg_write(csi2->iss, csi2->regs1, CSI2_IRQENABLE, 0); +} + +/* + * omap4iss_csi2_reset - Resets the CSI2 module. + * + * Must be called with the phy lock held. + * + * Returns 0 if successful, or -EBUSY if power command didn't respond. + */ +int omap4iss_csi2_reset(struct iss_csi2_device *csi2) +{ + unsigned int timeout; + + if (!csi2->available) + return -ENODEV; + + if (csi2->phy->phy_in_use) + return -EBUSY; + + iss_reg_set(csi2->iss, csi2->regs1, CSI2_SYSCONFIG, + CSI2_SYSCONFIG_SOFT_RESET); + + timeout = iss_poll_condition_timeout( + iss_reg_read(csi2->iss, csi2->regs1, CSI2_SYSSTATUS) & + CSI2_SYSSTATUS_RESET_DONE, 500, 100, 200); + if (timeout) { + dev_err(csi2->iss->dev, "CSI2: Soft reset timeout!\n"); + return -EBUSY; + } + + iss_reg_set(csi2->iss, csi2->regs1, CSI2_COMPLEXIO_CFG, + CSI2_COMPLEXIO_CFG_RESET_CTRL); + + timeout = iss_poll_condition_timeout( + iss_reg_read(csi2->iss, csi2->phy->phy_regs, REGISTER1) & + REGISTER1_RESET_DONE_CTRLCLK, 10000, 100, 500); + if (timeout) { + dev_err(csi2->iss->dev, "CSI2: CSI2_96M_FCLK reset timeout!\n"); + return -EBUSY; + } + + iss_reg_update(csi2->iss, csi2->regs1, CSI2_SYSCONFIG, + CSI2_SYSCONFIG_MSTANDBY_MODE_MASK | + CSI2_SYSCONFIG_AUTO_IDLE, + CSI2_SYSCONFIG_MSTANDBY_MODE_NO); + + return 0; +} + +static int csi2_configure(struct iss_csi2_device *csi2) +{ + const struct iss_v4l2_subdevs_group *pdata; + struct iss_csi2_timing_cfg *timing = &csi2->timing[0]; + struct v4l2_subdev *sensor; + struct media_pad *pad; + + /* + * CSI2 fields that can be updated while the context has + * been enabled or the interface has been enabled are not + * updated dynamically currently. So we do not allow to + * reconfigure if either has been enabled + */ + if (csi2->contexts[0].enabled || csi2->ctrl.if_enable) + return -EBUSY; + + pad = media_entity_remote_pad(&csi2->pads[CSI2_PAD_SINK]); + sensor = media_entity_to_v4l2_subdev(pad->entity); + pdata = sensor->host_priv; + + csi2->frame_skip = 0; + v4l2_subdev_call(sensor, sensor, g_skip_frames, &csi2->frame_skip); + + csi2->ctrl.vp_out_ctrl = pdata->bus.csi2.vpclk_div; + csi2->ctrl.frame_mode = ISS_CSI2_FRAME_IMMEDIATE; + csi2->ctrl.ecc_enable = pdata->bus.csi2.crc; + + timing->force_rx_mode = 1; + timing->stop_state_16x = 1; + timing->stop_state_4x = 1; + timing->stop_state_counter = 0x1ff; + + /* + * The CSI2 receiver can't do any format conversion except DPCM + * decompression, so every set_format call configures both pads + * and enables DPCM decompression as a special case: + */ + if (csi2->formats[CSI2_PAD_SINK].code != + csi2->formats[CSI2_PAD_SOURCE].code) + csi2->dpcm_decompress = true; + else + csi2->dpcm_decompress = false; + + csi2->contexts[0].format_id = csi2_ctx_map_format(csi2); + + if (csi2->video_out.bpl_padding == 0) + csi2->contexts[0].data_offset = 0; + else + csi2->contexts[0].data_offset = csi2->video_out.bpl_value; + + /* + * Enable end of frame and end of line signals generation for + * context 0. These signals are generated from CSI2 receiver to + * qualify the last pixel of a frame and the last pixel of a line. + * Without enabling the signals CSI2 receiver writes data to memory + * beyond buffer size and/or data line offset is not handled correctly. + */ + csi2->contexts[0].eof_enabled = 1; + csi2->contexts[0].eol_enabled = 1; + + csi2_irq_complexio1_set(csi2, 1); + csi2_irq_ctx_set(csi2, 1); + csi2_irq_status_set(csi2, 1); + + /* Set configuration (timings, format and links) */ + csi2_timing_config(csi2, timing); + csi2_recv_config(csi2, &csi2->ctrl); + csi2_ctx_config(csi2, &csi2->contexts[0]); + + return 0; +} + +/* + * csi2_print_status - Prints CSI2 debug information. + */ +#define CSI2_PRINT_REGISTER(iss, regs, name)\ + dev_dbg(iss->dev, "###CSI2 " #name "=0x%08x\n", \ + iss_reg_read(iss, regs, CSI2_##name)) + +static void csi2_print_status(struct iss_csi2_device *csi2) +{ + struct iss_device *iss = csi2->iss; + + if (!csi2->available) + return; + + dev_dbg(iss->dev, "-------------CSI2 Register dump-------------\n"); + + CSI2_PRINT_REGISTER(iss, csi2->regs1, SYSCONFIG); + CSI2_PRINT_REGISTER(iss, csi2->regs1, SYSSTATUS); + CSI2_PRINT_REGISTER(iss, csi2->regs1, IRQENABLE); + CSI2_PRINT_REGISTER(iss, csi2->regs1, IRQSTATUS); + CSI2_PRINT_REGISTER(iss, csi2->regs1, CTRL); + CSI2_PRINT_REGISTER(iss, csi2->regs1, DBG_H); + CSI2_PRINT_REGISTER(iss, csi2->regs1, COMPLEXIO_CFG); + CSI2_PRINT_REGISTER(iss, csi2->regs1, COMPLEXIO_IRQSTATUS); + CSI2_PRINT_REGISTER(iss, csi2->regs1, SHORT_PACKET); + CSI2_PRINT_REGISTER(iss, csi2->regs1, COMPLEXIO_IRQENABLE); + CSI2_PRINT_REGISTER(iss, csi2->regs1, DBG_P); + CSI2_PRINT_REGISTER(iss, csi2->regs1, TIMING); + CSI2_PRINT_REGISTER(iss, csi2->regs1, CTX_CTRL1(0)); + CSI2_PRINT_REGISTER(iss, csi2->regs1, CTX_CTRL2(0)); + CSI2_PRINT_REGISTER(iss, csi2->regs1, CTX_DAT_OFST(0)); + CSI2_PRINT_REGISTER(iss, csi2->regs1, CTX_PING_ADDR(0)); + CSI2_PRINT_REGISTER(iss, csi2->regs1, CTX_PONG_ADDR(0)); + CSI2_PRINT_REGISTER(iss, csi2->regs1, CTX_IRQENABLE(0)); + CSI2_PRINT_REGISTER(iss, csi2->regs1, CTX_IRQSTATUS(0)); + CSI2_PRINT_REGISTER(iss, csi2->regs1, CTX_CTRL3(0)); + + dev_dbg(iss->dev, "--------------------------------------------\n"); +} + +/* ----------------------------------------------------------------------------- + * Interrupt handling + */ + +/* + * csi2_isr_buffer - Does buffer handling at end-of-frame + * when writing to memory. + */ +static void csi2_isr_buffer(struct iss_csi2_device *csi2) +{ + struct iss_buffer *buffer; + + csi2_ctx_enable(csi2, 0, 0); + + buffer = omap4iss_video_buffer_next(&csi2->video_out); + + /* + * Let video queue operation restart engine if there is an underrun + * condition. + */ + if (buffer == NULL) + return; + + csi2_set_outaddr(csi2, buffer->iss_addr); + csi2_ctx_enable(csi2, 0, 1); +} + +static void csi2_isr_ctx(struct iss_csi2_device *csi2, + struct iss_csi2_ctx_cfg *ctx) +{ + unsigned int n = ctx->ctxnum; + u32 status; + + status = iss_reg_read(csi2->iss, csi2->regs1, CSI2_CTX_IRQSTATUS(n)); + iss_reg_write(csi2->iss, csi2->regs1, CSI2_CTX_IRQSTATUS(n), status); + + /* Propagate frame number */ + if (status & CSI2_CTX_IRQ_FS) { + struct iss_pipeline *pipe = + to_iss_pipeline(&csi2->subdev.entity); + if (pipe->do_propagation) + atomic_inc(&pipe->frame_number); + } + + if (!(status & CSI2_CTX_IRQ_FE)) + return; + + /* Skip interrupts until we reach the frame skip count. The CSI2 will be + * automatically disabled, as the frame skip count has been programmed + * in the CSI2_CTx_CTRL1::COUNT field, so reenable it. + * + * It would have been nice to rely on the FRAME_NUMBER interrupt instead + * but it turned out that the interrupt is only generated when the CSI2 + * writes to memory (the CSI2_CTx_CTRL1::COUNT field is decreased + * correctly and reaches 0 when data is forwarded to the video port only + * but no interrupt arrives). Maybe a CSI2 hardware bug. + */ + if (csi2->frame_skip) { + csi2->frame_skip--; + if (csi2->frame_skip == 0) { + ctx->format_id = csi2_ctx_map_format(csi2); + csi2_ctx_config(csi2, ctx); + csi2_ctx_enable(csi2, n, 1); + } + return; + } + + if (csi2->output & CSI2_OUTPUT_MEMORY) + csi2_isr_buffer(csi2); +} + +/* + * omap4iss_csi2_isr - CSI2 interrupt handling. + */ +void omap4iss_csi2_isr(struct iss_csi2_device *csi2) +{ + struct iss_pipeline *pipe = to_iss_pipeline(&csi2->subdev.entity); + u32 csi2_irqstatus, cpxio1_irqstatus; + struct iss_device *iss = csi2->iss; + + if (!csi2->available) + return; + + csi2_irqstatus = iss_reg_read(csi2->iss, csi2->regs1, CSI2_IRQSTATUS); + iss_reg_write(csi2->iss, csi2->regs1, CSI2_IRQSTATUS, csi2_irqstatus); + + /* Failure Cases */ + if (csi2_irqstatus & CSI2_IRQ_COMPLEXIO_ERR) { + cpxio1_irqstatus = iss_reg_read(csi2->iss, csi2->regs1, + CSI2_COMPLEXIO_IRQSTATUS); + iss_reg_write(csi2->iss, csi2->regs1, CSI2_COMPLEXIO_IRQSTATUS, + cpxio1_irqstatus); + dev_dbg(iss->dev, "CSI2: ComplexIO Error IRQ %x\n", + cpxio1_irqstatus); + pipe->error = true; + } + + if (csi2_irqstatus & (CSI2_IRQ_OCP_ERR | + CSI2_IRQ_SHORT_PACKET | + CSI2_IRQ_ECC_NO_CORRECTION | + CSI2_IRQ_COMPLEXIO_ERR | + CSI2_IRQ_FIFO_OVF)) { + dev_dbg(iss->dev, + "CSI2 Err: OCP:%d SHORT:%d ECC:%d CPXIO:%d OVF:%d\n", + csi2_irqstatus & CSI2_IRQ_OCP_ERR ? 1 : 0, + csi2_irqstatus & CSI2_IRQ_SHORT_PACKET ? 1 : 0, + csi2_irqstatus & CSI2_IRQ_ECC_NO_CORRECTION ? 1 : 0, + csi2_irqstatus & CSI2_IRQ_COMPLEXIO_ERR ? 1 : 0, + csi2_irqstatus & CSI2_IRQ_FIFO_OVF ? 1 : 0); + pipe->error = true; + } + + if (omap4iss_module_sync_is_stopping(&csi2->wait, &csi2->stopping)) + return; + + /* Successful cases */ + if (csi2_irqstatus & CSI2_IRQ_CONTEXT0) + csi2_isr_ctx(csi2, &csi2->contexts[0]); + + if (csi2_irqstatus & CSI2_IRQ_ECC_CORRECTION) + dev_dbg(iss->dev, "CSI2: ECC correction done\n"); +} + +/* ----------------------------------------------------------------------------- + * ISS video operations + */ + +/* + * csi2_queue - Queues the first buffer when using memory output + * @video: The video node + * @buffer: buffer to queue + */ +static int csi2_queue(struct iss_video *video, struct iss_buffer *buffer) +{ + struct iss_csi2_device *csi2 = container_of(video, + struct iss_csi2_device, video_out); + + csi2_set_outaddr(csi2, buffer->iss_addr); + + /* + * If streaming was enabled before there was a buffer queued + * or underrun happened in the ISR, the hardware was not enabled + * and DMA queue flag ISS_VIDEO_DMAQUEUE_UNDERRUN is still set. + * Enable it now. + */ + if (csi2->video_out.dmaqueue_flags & ISS_VIDEO_DMAQUEUE_UNDERRUN) { + /* Enable / disable context 0 and IRQs */ + csi2_if_enable(csi2, 1); + csi2_ctx_enable(csi2, 0, 1); + iss_video_dmaqueue_flags_clr(&csi2->video_out); + } + + return 0; +} + +static const struct iss_video_operations csi2_issvideo_ops = { + .queue = csi2_queue, +}; + +/* ----------------------------------------------------------------------------- + * V4L2 subdev operations + */ + +static struct v4l2_mbus_framefmt * +__csi2_get_format(struct iss_csi2_device *csi2, struct v4l2_subdev_fh *fh, + unsigned int pad, enum v4l2_subdev_format_whence which) +{ + if (which == V4L2_SUBDEV_FORMAT_TRY) + return v4l2_subdev_get_try_format(fh, pad); + else + return &csi2->formats[pad]; +} + +static void +csi2_try_format(struct iss_csi2_device *csi2, struct v4l2_subdev_fh *fh, + unsigned int pad, struct v4l2_mbus_framefmt *fmt, + enum v4l2_subdev_format_whence which) +{ + enum v4l2_mbus_pixelcode pixelcode; + struct v4l2_mbus_framefmt *format; + const struct iss_format_info *info; + unsigned int i; + + switch (pad) { + case CSI2_PAD_SINK: + /* Clamp the width and height to valid range (1-8191). */ + for (i = 0; i < ARRAY_SIZE(csi2_input_fmts); i++) { + if (fmt->code == csi2_input_fmts[i]) + break; + } + + /* If not found, use SGRBG10 as default */ + if (i >= ARRAY_SIZE(csi2_input_fmts)) + fmt->code = V4L2_MBUS_FMT_SGRBG10_1X10; + + fmt->width = clamp_t(u32, fmt->width, 1, 8191); + fmt->height = clamp_t(u32, fmt->height, 1, 8191); + break; + + case CSI2_PAD_SOURCE: + /* Source format same as sink format, except for DPCM + * compression. + */ + pixelcode = fmt->code; + format = __csi2_get_format(csi2, fh, CSI2_PAD_SINK, which); + memcpy(fmt, format, sizeof(*fmt)); + + /* + * Only Allow DPCM decompression, and check that the + * pattern is preserved + */ + info = omap4iss_video_format_info(fmt->code); + if (info->uncompressed == pixelcode) + fmt->code = pixelcode; + break; + } + + /* RGB, non-interlaced */ + fmt->colorspace = V4L2_COLORSPACE_SRGB; + fmt->field = V4L2_FIELD_NONE; +} + +/* + * csi2_enum_mbus_code - Handle pixel format enumeration + * @sd : pointer to v4l2 subdev structure + * @fh : V4L2 subdev file handle + * @code : pointer to v4l2_subdev_mbus_code_enum structure + * return -EINVAL or zero on success + */ +static int csi2_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_fh *fh, + struct v4l2_subdev_mbus_code_enum *code) +{ + struct iss_csi2_device *csi2 = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt *format; + const struct iss_format_info *info; + + if (code->pad == CSI2_PAD_SINK) { + if (code->index >= ARRAY_SIZE(csi2_input_fmts)) + return -EINVAL; + + code->code = csi2_input_fmts[code->index]; + } else { + format = __csi2_get_format(csi2, fh, CSI2_PAD_SINK, + V4L2_SUBDEV_FORMAT_TRY); + switch (code->index) { + case 0: + /* Passthrough sink pad code */ + code->code = format->code; + break; + case 1: + /* Uncompressed code */ + info = omap4iss_video_format_info(format->code); + if (info->uncompressed == format->code) + return -EINVAL; + + code->code = info->uncompressed; + break; + default: + return -EINVAL; + } + } + + return 0; +} + +static int csi2_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_fh *fh, + struct v4l2_subdev_frame_size_enum *fse) +{ + struct iss_csi2_device *csi2 = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt format; + + if (fse->index != 0) + return -EINVAL; + + format.code = fse->code; + format.width = 1; + format.height = 1; + csi2_try_format(csi2, fh, fse->pad, &format, V4L2_SUBDEV_FORMAT_TRY); + fse->min_width = format.width; + fse->min_height = format.height; + + if (format.code != fse->code) + return -EINVAL; + + format.code = fse->code; + format.width = -1; + format.height = -1; + csi2_try_format(csi2, fh, fse->pad, &format, V4L2_SUBDEV_FORMAT_TRY); + fse->max_width = format.width; + fse->max_height = format.height; + + return 0; +} + +/* + * csi2_get_format - Handle get format by pads subdev method + * @sd : pointer to v4l2 subdev structure + * @fh : V4L2 subdev file handle + * @fmt: pointer to v4l2 subdev format structure + * return -EINVAL or zero on success + */ +static int csi2_get_format(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh, + struct v4l2_subdev_format *fmt) +{ + struct iss_csi2_device *csi2 = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt *format; + + format = __csi2_get_format(csi2, fh, fmt->pad, fmt->which); + if (format == NULL) + return -EINVAL; + + fmt->format = *format; + return 0; +} + +/* + * csi2_set_format - Handle set format by pads subdev method + * @sd : pointer to v4l2 subdev structure + * @fh : V4L2 subdev file handle + * @fmt: pointer to v4l2 subdev format structure + * return -EINVAL or zero on success + */ +static int csi2_set_format(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh, + struct v4l2_subdev_format *fmt) +{ + struct iss_csi2_device *csi2 = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt *format; + + format = __csi2_get_format(csi2, fh, fmt->pad, fmt->which); + if (format == NULL) + return -EINVAL; + + csi2_try_format(csi2, fh, fmt->pad, &fmt->format, fmt->which); + *format = fmt->format; + + /* Propagate the format from sink to source */ + if (fmt->pad == CSI2_PAD_SINK) { + format = __csi2_get_format(csi2, fh, CSI2_PAD_SOURCE, + fmt->which); + *format = fmt->format; + csi2_try_format(csi2, fh, CSI2_PAD_SOURCE, format, fmt->which); + } + + return 0; +} + +static int csi2_link_validate(struct v4l2_subdev *sd, struct media_link *link, + struct v4l2_subdev_format *source_fmt, + struct v4l2_subdev_format *sink_fmt) +{ + struct iss_csi2_device *csi2 = v4l2_get_subdevdata(sd); + struct iss_pipeline *pipe = to_iss_pipeline(&csi2->subdev.entity); + int rval; + + pipe->external = media_entity_to_v4l2_subdev(link->source->entity); + rval = omap4iss_get_external_info(pipe, link); + if (rval < 0) + return rval; + + return v4l2_subdev_link_validate_default(sd, link, source_fmt, + sink_fmt); +} + +/* + * csi2_init_formats - Initialize formats on all pads + * @sd: ISS CSI2 V4L2 subdevice + * @fh: V4L2 subdev file handle + * + * Initialize all pad formats with default values. If fh is not NULL, try + * formats are initialized on the file handle. Otherwise active formats are + * initialized on the device. + */ +static int csi2_init_formats(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + struct v4l2_subdev_format format; + + memset(&format, 0, sizeof(format)); + format.pad = CSI2_PAD_SINK; + format.which = fh ? V4L2_SUBDEV_FORMAT_TRY : V4L2_SUBDEV_FORMAT_ACTIVE; + format.format.code = V4L2_MBUS_FMT_SGRBG10_1X10; + format.format.width = 4096; + format.format.height = 4096; + csi2_set_format(sd, fh, &format); + + return 0; +} + +/* + * csi2_set_stream - Enable/Disable streaming on the CSI2 module + * @sd: ISS CSI2 V4L2 subdevice + * @enable: ISS pipeline stream state + * + * Return 0 on success or a negative error code otherwise. + */ +static int csi2_set_stream(struct v4l2_subdev *sd, int enable) +{ + struct iss_csi2_device *csi2 = v4l2_get_subdevdata(sd); + struct iss_device *iss = csi2->iss; + struct iss_pipeline *pipe = to_iss_pipeline(&csi2->subdev.entity); + struct iss_video *video_out = &csi2->video_out; + int ret = 0; + + if (csi2->state == ISS_PIPELINE_STREAM_STOPPED) { + if (enable == ISS_PIPELINE_STREAM_STOPPED) + return 0; + + omap4iss_subclk_enable(iss, csi2->subclk); + } + + switch (enable) { + case ISS_PIPELINE_STREAM_CONTINUOUS: { + ret = omap4iss_csiphy_config(iss, sd); + if (ret < 0) + return ret; + + if (omap4iss_csiphy_acquire(csi2->phy) < 0) + return -ENODEV; + csi2->use_fs_irq = pipe->do_propagation; + csi2_configure(csi2); + csi2_print_status(csi2); + + /* + * When outputting to memory with no buffer available, let the + * buffer queue handler start the hardware. A DMA queue flag + * ISS_VIDEO_DMAQUEUE_QUEUED will be set as soon as there is + * a buffer available. + */ + if (csi2->output & CSI2_OUTPUT_MEMORY && + !(video_out->dmaqueue_flags & ISS_VIDEO_DMAQUEUE_QUEUED)) + break; + /* Enable context 0 and IRQs */ + atomic_set(&csi2->stopping, 0); + csi2_ctx_enable(csi2, 0, 1); + csi2_if_enable(csi2, 1); + iss_video_dmaqueue_flags_clr(video_out); + break; + } + case ISS_PIPELINE_STREAM_STOPPED: + if (csi2->state == ISS_PIPELINE_STREAM_STOPPED) + return 0; + if (omap4iss_module_sync_idle(&sd->entity, &csi2->wait, + &csi2->stopping)) + ret = -ETIMEDOUT; + csi2_ctx_enable(csi2, 0, 0); + csi2_if_enable(csi2, 0); + csi2_irq_ctx_set(csi2, 0); + omap4iss_csiphy_release(csi2->phy); + omap4iss_subclk_disable(iss, csi2->subclk); + iss_video_dmaqueue_flags_clr(video_out); + break; + } + + csi2->state = enable; + return ret; +} + +/* subdev video operations */ +static const struct v4l2_subdev_video_ops csi2_video_ops = { + .s_stream = csi2_set_stream, +}; + +/* subdev pad operations */ +static const struct v4l2_subdev_pad_ops csi2_pad_ops = { + .enum_mbus_code = csi2_enum_mbus_code, + .enum_frame_size = csi2_enum_frame_size, + .get_fmt = csi2_get_format, + .set_fmt = csi2_set_format, + .link_validate = csi2_link_validate, +}; + +/* subdev operations */ +static const struct v4l2_subdev_ops csi2_ops = { + .video = &csi2_video_ops, + .pad = &csi2_pad_ops, +}; + +/* subdev internal operations */ +static const struct v4l2_subdev_internal_ops csi2_internal_ops = { + .open = csi2_init_formats, +}; + +/* ----------------------------------------------------------------------------- + * Media entity operations + */ + +/* + * csi2_link_setup - Setup CSI2 connections. + * @entity : Pointer to media entity structure + * @local : Pointer to local pad array + * @remote : Pointer to remote pad array + * @flags : Link flags + * return -EINVAL or zero on success + */ +static int csi2_link_setup(struct media_entity *entity, + const struct media_pad *local, + const struct media_pad *remote, u32 flags) +{ + struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(entity); + struct iss_csi2_device *csi2 = v4l2_get_subdevdata(sd); + struct iss_csi2_ctrl_cfg *ctrl = &csi2->ctrl; + + /* + * The ISS core doesn't support pipelines with multiple video outputs. + * Revisit this when it will be implemented, and return -EBUSY for now. + */ + + switch (local->index | media_entity_type(remote->entity)) { + case CSI2_PAD_SOURCE | MEDIA_ENT_T_DEVNODE: + if (flags & MEDIA_LNK_FL_ENABLED) { + if (csi2->output & ~CSI2_OUTPUT_MEMORY) + return -EBUSY; + csi2->output |= CSI2_OUTPUT_MEMORY; + } else { + csi2->output &= ~CSI2_OUTPUT_MEMORY; + } + break; + + case CSI2_PAD_SOURCE | MEDIA_ENT_T_V4L2_SUBDEV: + if (flags & MEDIA_LNK_FL_ENABLED) { + if (csi2->output & ~CSI2_OUTPUT_IPIPEIF) + return -EBUSY; + csi2->output |= CSI2_OUTPUT_IPIPEIF; + } else { + csi2->output &= ~CSI2_OUTPUT_IPIPEIF; + } + break; + + default: + /* Link from camera to CSI2 is fixed... */ + return -EINVAL; + } + + ctrl->vp_only_enable = csi2->output & CSI2_OUTPUT_MEMORY ? false : true; + ctrl->vp_clk_enable = !!(csi2->output & CSI2_OUTPUT_IPIPEIF); + + return 0; +} + +/* media operations */ +static const struct media_entity_operations csi2_media_ops = { + .link_setup = csi2_link_setup, + .link_validate = v4l2_subdev_link_validate, +}; + +void omap4iss_csi2_unregister_entities(struct iss_csi2_device *csi2) +{ + v4l2_device_unregister_subdev(&csi2->subdev); + omap4iss_video_unregister(&csi2->video_out); +} + +int omap4iss_csi2_register_entities(struct iss_csi2_device *csi2, + struct v4l2_device *vdev) +{ + int ret; + + /* Register the subdev and video nodes. */ + ret = v4l2_device_register_subdev(vdev, &csi2->subdev); + if (ret < 0) + goto error; + + ret = omap4iss_video_register(&csi2->video_out, vdev); + if (ret < 0) + goto error; + + return 0; + +error: + omap4iss_csi2_unregister_entities(csi2); + return ret; +} + +/* ----------------------------------------------------------------------------- + * ISS CSI2 initialisation and cleanup + */ + +/* + * csi2_init_entities - Initialize subdev and media entity. + * @csi2: Pointer to csi2 structure. + * return -ENOMEM or zero on success + */ +static int csi2_init_entities(struct iss_csi2_device *csi2, const char *subname) +{ + struct v4l2_subdev *sd = &csi2->subdev; + struct media_pad *pads = csi2->pads; + struct media_entity *me = &sd->entity; + int ret; + char name[V4L2_SUBDEV_NAME_SIZE]; + + v4l2_subdev_init(sd, &csi2_ops); + sd->internal_ops = &csi2_internal_ops; + sprintf(name, "CSI2%s", subname); + snprintf(sd->name, sizeof(sd->name), "OMAP4 ISS %s", name); + + sd->grp_id = 1 << 16; /* group ID for iss subdevs */ + v4l2_set_subdevdata(sd, csi2); + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + + pads[CSI2_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE; + pads[CSI2_PAD_SINK].flags = MEDIA_PAD_FL_SINK; + + me->ops = &csi2_media_ops; + ret = media_entity_init(me, CSI2_PADS_NUM, pads, 0); + if (ret < 0) + return ret; + + csi2_init_formats(sd, NULL); + + /* Video device node */ + csi2->video_out.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + csi2->video_out.ops = &csi2_issvideo_ops; + csi2->video_out.bpl_alignment = 32; + csi2->video_out.bpl_zero_padding = 1; + csi2->video_out.bpl_max = 0x1ffe0; + csi2->video_out.iss = csi2->iss; + csi2->video_out.capture_mem = PAGE_ALIGN(4096 * 4096) * 3; + + ret = omap4iss_video_init(&csi2->video_out, name); + if (ret < 0) + goto error_video; + + /* Connect the CSI2 subdev to the video node. */ + ret = media_entity_create_link(&csi2->subdev.entity, CSI2_PAD_SOURCE, + &csi2->video_out.video.entity, 0, 0); + if (ret < 0) + goto error_link; + + return 0; + +error_link: + omap4iss_video_cleanup(&csi2->video_out); +error_video: + media_entity_cleanup(&csi2->subdev.entity); + return ret; +} + +/* + * omap4iss_csi2_init - Routine for module driver init + */ +int omap4iss_csi2_init(struct iss_device *iss) +{ + struct iss_csi2_device *csi2a = &iss->csi2a; + struct iss_csi2_device *csi2b = &iss->csi2b; + int ret; + + csi2a->iss = iss; + csi2a->available = 1; + csi2a->regs1 = OMAP4_ISS_MEM_CSI2_A_REGS1; + csi2a->phy = &iss->csiphy1; + csi2a->subclk = OMAP4_ISS_SUBCLK_CSI2_A; + csi2a->state = ISS_PIPELINE_STREAM_STOPPED; + init_waitqueue_head(&csi2a->wait); + + ret = csi2_init_entities(csi2a, "a"); + if (ret < 0) + return ret; + + csi2b->iss = iss; + csi2b->available = 1; + csi2b->regs1 = OMAP4_ISS_MEM_CSI2_B_REGS1; + csi2b->phy = &iss->csiphy2; + csi2b->subclk = OMAP4_ISS_SUBCLK_CSI2_B; + csi2b->state = ISS_PIPELINE_STREAM_STOPPED; + init_waitqueue_head(&csi2b->wait); + + ret = csi2_init_entities(csi2b, "b"); + if (ret < 0) + return ret; + + return 0; +} + +/* + * omap4iss_csi2_cleanup - Routine for module driver cleanup + */ +void omap4iss_csi2_cleanup(struct iss_device *iss) +{ + struct iss_csi2_device *csi2a = &iss->csi2a; + struct iss_csi2_device *csi2b = &iss->csi2b; + + omap4iss_video_cleanup(&csi2a->video_out); + media_entity_cleanup(&csi2a->subdev.entity); + + omap4iss_video_cleanup(&csi2b->video_out); + media_entity_cleanup(&csi2b->subdev.entity); +} diff --git a/drivers/staging/media/omap4iss/iss_csi2.h b/drivers/staging/media/omap4iss/iss_csi2.h new file mode 100644 index 00000000000..971aa7b0801 --- /dev/null +++ b/drivers/staging/media/omap4iss/iss_csi2.h @@ -0,0 +1,158 @@ +/* + * TI OMAP4 ISS V4L2 Driver - CSI2 module + * + * Copyright (C) 2012 Texas Instruments, Inc. + * + * Author: Sergio Aguirre <sergio.a.aguirre@gmail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#ifndef OMAP4_ISS_CSI2_H +#define OMAP4_ISS_CSI2_H + +#include <linux/types.h> +#include <linux/videodev2.h> + +#include "iss_video.h" + +struct iss_csiphy; + +/* This is not an exhaustive list */ +enum iss_csi2_pix_formats { + CSI2_PIX_FMT_OTHERS = 0, + CSI2_PIX_FMT_YUV422_8BIT = 0x1e, + CSI2_PIX_FMT_YUV422_8BIT_VP = 0x9e, + CSI2_PIX_FMT_YUV422_8BIT_VP16 = 0xde, + CSI2_PIX_FMT_RAW10_EXP16 = 0xab, + CSI2_PIX_FMT_RAW10_EXP16_VP = 0x12f, + CSI2_PIX_FMT_RAW8 = 0x2a, + CSI2_PIX_FMT_RAW8_DPCM10_EXP16 = 0x2aa, + CSI2_PIX_FMT_RAW8_DPCM10_VP = 0x32a, + CSI2_PIX_FMT_RAW8_VP = 0x12a, + CSI2_USERDEF_8BIT_DATA1_DPCM10_VP = 0x340, + CSI2_USERDEF_8BIT_DATA1_DPCM10 = 0x2c0, + CSI2_USERDEF_8BIT_DATA1 = 0x40, +}; + +enum iss_csi2_irqevents { + OCP_ERR_IRQ = 0x4000, + SHORT_PACKET_IRQ = 0x2000, + ECC_CORRECTION_IRQ = 0x1000, + ECC_NO_CORRECTION_IRQ = 0x800, + COMPLEXIO2_ERR_IRQ = 0x400, + COMPLEXIO1_ERR_IRQ = 0x200, + FIFO_OVF_IRQ = 0x100, + CONTEXT7 = 0x80, + CONTEXT6 = 0x40, + CONTEXT5 = 0x20, + CONTEXT4 = 0x10, + CONTEXT3 = 0x8, + CONTEXT2 = 0x4, + CONTEXT1 = 0x2, + CONTEXT0 = 0x1, +}; + +enum iss_csi2_ctx_irqevents { + CTX_ECC_CORRECTION = 0x100, + CTX_LINE_NUMBER = 0x80, + CTX_FRAME_NUMBER = 0x40, + CTX_CS = 0x20, + CTX_LE = 0x8, + CTX_LS = 0x4, + CTX_FE = 0x2, + CTX_FS = 0x1, +}; + +enum iss_csi2_frame_mode { + ISS_CSI2_FRAME_IMMEDIATE, + ISS_CSI2_FRAME_AFTERFEC, +}; + +#define ISS_CSI2_MAX_CTX_NUM 7 + +struct iss_csi2_ctx_cfg { + u8 ctxnum; /* context number 0 - 7 */ + u8 dpcm_decompress; + + /* Fields in CSI2_CTx_CTRL2 - locked by CSI2_CTx_CTRL1.CTX_EN */ + u8 virtual_id; + u16 format_id; /* as in CSI2_CTx_CTRL2[9:0] */ + u8 dpcm_predictor; /* 1: simple, 0: advanced */ + + /* Fields in CSI2_CTx_CTRL1/3 - Shadowed */ + u16 alpha; + u16 data_offset; + u32 ping_addr; + u32 pong_addr; + u8 eof_enabled; + u8 eol_enabled; + u8 checksum_enabled; + u8 enabled; +}; + +struct iss_csi2_timing_cfg { + u8 ionum; /* IO1 or IO2 as in CSI2_TIMING */ + unsigned force_rx_mode:1; + unsigned stop_state_16x:1; + unsigned stop_state_4x:1; + u16 stop_state_counter; +}; + +struct iss_csi2_ctrl_cfg { + bool vp_clk_enable; + bool vp_only_enable; + u8 vp_out_ctrl; + enum iss_csi2_frame_mode frame_mode; + bool ecc_enable; + bool if_enable; +}; + +#define CSI2_PAD_SINK 0 +#define CSI2_PAD_SOURCE 1 +#define CSI2_PADS_NUM 2 + +#define CSI2_OUTPUT_IPIPEIF (1 << 0) +#define CSI2_OUTPUT_MEMORY (1 << 1) + +struct iss_csi2_device { + struct v4l2_subdev subdev; + struct media_pad pads[CSI2_PADS_NUM]; + struct v4l2_mbus_framefmt formats[CSI2_PADS_NUM]; + + struct iss_video video_out; + struct iss_device *iss; + + u8 available; /* Is the IP present on the silicon? */ + + /* memory resources, as defined in enum iss_mem_resources */ + unsigned int regs1; + unsigned int regs2; + /* ISP subclock, as defined in enum iss_isp_subclk_resource */ + unsigned int subclk; + + u32 output; /* output to IPIPEIF, memory or both? */ + bool dpcm_decompress; + unsigned int frame_skip; + bool use_fs_irq; + + struct iss_csiphy *phy; + struct iss_csi2_ctx_cfg contexts[ISS_CSI2_MAX_CTX_NUM + 1]; + struct iss_csi2_timing_cfg timing[2]; + struct iss_csi2_ctrl_cfg ctrl; + enum iss_pipeline_stream_state state; + wait_queue_head_t wait; + atomic_t stopping; +}; + +void omap4iss_csi2_isr(struct iss_csi2_device *csi2); +int omap4iss_csi2_reset(struct iss_csi2_device *csi2); +int omap4iss_csi2_init(struct iss_device *iss); +void omap4iss_csi2_cleanup(struct iss_device *iss); +void omap4iss_csi2_unregister_entities(struct iss_csi2_device *csi2); +int omap4iss_csi2_register_entities(struct iss_csi2_device *csi2, + struct v4l2_device *vdev); +#endif /* OMAP4_ISS_CSI2_H */ diff --git a/drivers/staging/media/omap4iss/iss_csiphy.c b/drivers/staging/media/omap4iss/iss_csiphy.c new file mode 100644 index 00000000000..7c3d55d811e --- /dev/null +++ b/drivers/staging/media/omap4iss/iss_csiphy.c @@ -0,0 +1,279 @@ +/* + * TI OMAP4 ISS V4L2 Driver - CSI PHY module + * + * Copyright (C) 2012 Texas Instruments, Inc. + * + * Author: Sergio Aguirre <sergio.a.aguirre@gmail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include <linux/delay.h> +#include <linux/device.h> + +#include "../../../../arch/arm/mach-omap2/control.h" + +#include "iss.h" +#include "iss_regs.h" +#include "iss_csiphy.h" + +/* + * csiphy_lanes_config - Configuration of CSIPHY lanes. + * + * Updates HW configuration. + * Called with phy->mutex taken. + */ +static void csiphy_lanes_config(struct iss_csiphy *phy) +{ + unsigned int i; + u32 reg; + + reg = iss_reg_read(phy->iss, phy->cfg_regs, CSI2_COMPLEXIO_CFG); + + for (i = 0; i < phy->max_data_lanes; i++) { + reg &= ~(CSI2_COMPLEXIO_CFG_DATA_POL(i + 1) | + CSI2_COMPLEXIO_CFG_DATA_POSITION_MASK(i + 1)); + reg |= (phy->lanes.data[i].pol ? + CSI2_COMPLEXIO_CFG_DATA_POL(i + 1) : 0); + reg |= (phy->lanes.data[i].pos << + CSI2_COMPLEXIO_CFG_DATA_POSITION_SHIFT(i + 1)); + } + + reg &= ~(CSI2_COMPLEXIO_CFG_CLOCK_POL | + CSI2_COMPLEXIO_CFG_CLOCK_POSITION_MASK); + reg |= phy->lanes.clk.pol ? CSI2_COMPLEXIO_CFG_CLOCK_POL : 0; + reg |= phy->lanes.clk.pos << CSI2_COMPLEXIO_CFG_CLOCK_POSITION_SHIFT; + + iss_reg_write(phy->iss, phy->cfg_regs, CSI2_COMPLEXIO_CFG, reg); +} + +/* + * csiphy_set_power + * @power: Power state to be set. + * + * Returns 0 if successful, or -EBUSY if the retry count is exceeded. + */ +static int csiphy_set_power(struct iss_csiphy *phy, u32 power) +{ + u32 reg; + u8 retry_count; + + iss_reg_update(phy->iss, phy->cfg_regs, CSI2_COMPLEXIO_CFG, + CSI2_COMPLEXIO_CFG_PWD_CMD_MASK, + power | CSI2_COMPLEXIO_CFG_PWR_AUTO); + + retry_count = 0; + do { + udelay(1); + reg = iss_reg_read(phy->iss, phy->cfg_regs, CSI2_COMPLEXIO_CFG) + & CSI2_COMPLEXIO_CFG_PWD_STATUS_MASK; + + if (reg != power >> 2) + retry_count++; + + } while ((reg != power >> 2) && (retry_count < 250)); + + if (retry_count == 250) { + dev_err(phy->iss->dev, "CSI2 CIO set power failed!\n"); + return -EBUSY; + } + + return 0; +} + +/* + * csiphy_dphy_config - Configure CSI2 D-PHY parameters. + * + * Called with phy->mutex taken. + */ +static void csiphy_dphy_config(struct iss_csiphy *phy) +{ + u32 reg; + + /* Set up REGISTER0 */ + reg = phy->dphy.ths_term << REGISTER0_THS_TERM_SHIFT; + reg |= phy->dphy.ths_settle << REGISTER0_THS_SETTLE_SHIFT; + + iss_reg_write(phy->iss, phy->phy_regs, REGISTER0, reg); + + /* Set up REGISTER1 */ + reg = phy->dphy.tclk_term << REGISTER1_TCLK_TERM_SHIFT; + reg |= phy->dphy.tclk_miss << REGISTER1_CTRLCLK_DIV_FACTOR_SHIFT; + reg |= phy->dphy.tclk_settle << REGISTER1_TCLK_SETTLE_SHIFT; + reg |= 0xb8 << REGISTER1_DPHY_HS_SYNC_PATTERN_SHIFT; + + iss_reg_write(phy->iss, phy->phy_regs, REGISTER1, reg); +} + +/* + * TCLK values are OK at their reset values + */ +#define TCLK_TERM 0 +#define TCLK_MISS 1 +#define TCLK_SETTLE 14 + +int omap4iss_csiphy_config(struct iss_device *iss, + struct v4l2_subdev *csi2_subdev) +{ + struct iss_csi2_device *csi2 = v4l2_get_subdevdata(csi2_subdev); + struct iss_pipeline *pipe = to_iss_pipeline(&csi2_subdev->entity); + struct iss_v4l2_subdevs_group *subdevs = pipe->external->host_priv; + struct iss_csiphy_dphy_cfg csi2phy; + int csi2_ddrclk_khz; + struct iss_csiphy_lanes_cfg *lanes; + unsigned int used_lanes = 0; + u32 cam_rx_ctrl; + unsigned int i; + + lanes = &subdevs->bus.csi2.lanecfg; + + /* + * SCM.CONTROL_CAMERA_RX + * - bit [31] : CSIPHY2 lane 2 enable (4460+ only) + * - bit [30:29] : CSIPHY2 per-lane enable (1 to 0) + * - bit [28:24] : CSIPHY1 per-lane enable (4 to 0) + * - bit [21] : CSIPHY2 CTRLCLK enable + * - bit [20:19] : CSIPHY2 config: 00 d-phy, 01/10 ccp2 + * - bit [18] : CSIPHY1 CTRLCLK enable + * - bit [17:16] : CSIPHY1 config: 00 d-phy, 01/10 ccp2 + */ + cam_rx_ctrl = omap4_ctrl_pad_readl( + OMAP4_CTRL_MODULE_PAD_CORE_CONTROL_CAMERA_RX); + + + if (subdevs->interface == ISS_INTERFACE_CSI2A_PHY1) { + cam_rx_ctrl &= ~(OMAP4_CAMERARX_CSI21_LANEENABLE_MASK | + OMAP4_CAMERARX_CSI21_CAMMODE_MASK); + /* NOTE: Leave CSIPHY1 config to 0x0: D-PHY mode */ + /* Enable all lanes for now */ + cam_rx_ctrl |= + 0x1f << OMAP4_CAMERARX_CSI21_LANEENABLE_SHIFT; + /* Enable CTRLCLK */ + cam_rx_ctrl |= OMAP4_CAMERARX_CSI21_CTRLCLKEN_MASK; + } + + if (subdevs->interface == ISS_INTERFACE_CSI2B_PHY2) { + cam_rx_ctrl &= ~(OMAP4_CAMERARX_CSI22_LANEENABLE_MASK | + OMAP4_CAMERARX_CSI22_CAMMODE_MASK); + /* NOTE: Leave CSIPHY2 config to 0x0: D-PHY mode */ + /* Enable all lanes for now */ + cam_rx_ctrl |= + 0x3 << OMAP4_CAMERARX_CSI22_LANEENABLE_SHIFT; + /* Enable CTRLCLK */ + cam_rx_ctrl |= OMAP4_CAMERARX_CSI22_CTRLCLKEN_MASK; + } + + omap4_ctrl_pad_writel(cam_rx_ctrl, + OMAP4_CTRL_MODULE_PAD_CORE_CONTROL_CAMERA_RX); + + /* Reset used lane count */ + csi2->phy->used_data_lanes = 0; + + /* Clock and data lanes verification */ + for (i = 0; i < csi2->phy->max_data_lanes; i++) { + if (lanes->data[i].pos == 0) + continue; + + if (lanes->data[i].pol > 1 || + lanes->data[i].pos > (csi2->phy->max_data_lanes + 1)) + return -EINVAL; + + if (used_lanes & (1 << lanes->data[i].pos)) + return -EINVAL; + + used_lanes |= 1 << lanes->data[i].pos; + csi2->phy->used_data_lanes++; + } + + if (lanes->clk.pol > 1 || + lanes->clk.pos > (csi2->phy->max_data_lanes + 1)) + return -EINVAL; + + if (lanes->clk.pos == 0 || used_lanes & (1 << lanes->clk.pos)) + return -EINVAL; + + csi2_ddrclk_khz = pipe->external_rate / 1000 + / (2 * csi2->phy->used_data_lanes) + * pipe->external_bpp; + + /* + * THS_TERM: Programmed value = ceil(12.5 ns/DDRClk period) - 1. + * THS_SETTLE: Programmed value = ceil(90 ns/DDRClk period) + 3. + */ + csi2phy.ths_term = DIV_ROUND_UP(25 * csi2_ddrclk_khz, 2000000) - 1; + csi2phy.ths_settle = DIV_ROUND_UP(90 * csi2_ddrclk_khz, 1000000) + 3; + csi2phy.tclk_term = TCLK_TERM; + csi2phy.tclk_miss = TCLK_MISS; + csi2phy.tclk_settle = TCLK_SETTLE; + + mutex_lock(&csi2->phy->mutex); + csi2->phy->dphy = csi2phy; + csi2->phy->lanes = *lanes; + mutex_unlock(&csi2->phy->mutex); + + return 0; +} + +int omap4iss_csiphy_acquire(struct iss_csiphy *phy) +{ + int rval; + + mutex_lock(&phy->mutex); + + rval = omap4iss_csi2_reset(phy->csi2); + if (rval) + goto done; + + csiphy_dphy_config(phy); + csiphy_lanes_config(phy); + + rval = csiphy_set_power(phy, CSI2_COMPLEXIO_CFG_PWD_CMD_ON); + if (rval) + goto done; + + phy->phy_in_use = 1; + +done: + mutex_unlock(&phy->mutex); + return rval; +} + +void omap4iss_csiphy_release(struct iss_csiphy *phy) +{ + mutex_lock(&phy->mutex); + if (phy->phy_in_use) { + csiphy_set_power(phy, CSI2_COMPLEXIO_CFG_PWD_CMD_OFF); + phy->phy_in_use = 0; + } + mutex_unlock(&phy->mutex); +} + +/* + * omap4iss_csiphy_init - Initialize the CSI PHY frontends + */ +int omap4iss_csiphy_init(struct iss_device *iss) +{ + struct iss_csiphy *phy1 = &iss->csiphy1; + struct iss_csiphy *phy2 = &iss->csiphy2; + + phy1->iss = iss; + phy1->csi2 = &iss->csi2a; + phy1->max_data_lanes = ISS_CSIPHY1_NUM_DATA_LANES; + phy1->used_data_lanes = 0; + phy1->cfg_regs = OMAP4_ISS_MEM_CSI2_A_REGS1; + phy1->phy_regs = OMAP4_ISS_MEM_CAMERARX_CORE1; + mutex_init(&phy1->mutex); + + phy2->iss = iss; + phy2->csi2 = &iss->csi2b; + phy2->max_data_lanes = ISS_CSIPHY2_NUM_DATA_LANES; + phy2->used_data_lanes = 0; + phy2->cfg_regs = OMAP4_ISS_MEM_CSI2_B_REGS1; + phy2->phy_regs = OMAP4_ISS_MEM_CAMERARX_CORE2; + mutex_init(&phy2->mutex); + + return 0; +} diff --git a/drivers/staging/media/omap4iss/iss_csiphy.h b/drivers/staging/media/omap4iss/iss_csiphy.h new file mode 100644 index 00000000000..e9ca4395565 --- /dev/null +++ b/drivers/staging/media/omap4iss/iss_csiphy.h @@ -0,0 +1,51 @@ +/* + * TI OMAP4 ISS V4L2 Driver - CSI PHY module + * + * Copyright (C) 2012 Texas Instruments, Inc. + * + * Author: Sergio Aguirre <sergio.a.aguirre@gmail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#ifndef OMAP4_ISS_CSI_PHY_H +#define OMAP4_ISS_CSI_PHY_H + +#include <media/omap4iss.h> + +struct iss_csi2_device; + +struct iss_csiphy_dphy_cfg { + u8 ths_term; + u8 ths_settle; + u8 tclk_term; + unsigned tclk_miss:1; + u8 tclk_settle; +}; + +struct iss_csiphy { + struct iss_device *iss; + struct mutex mutex; /* serialize csiphy configuration */ + u8 phy_in_use; + struct iss_csi2_device *csi2; + + /* memory resources, as defined in enum iss_mem_resources */ + unsigned int cfg_regs; + unsigned int phy_regs; + + u8 max_data_lanes; /* number of CSI2 Data Lanes supported */ + u8 used_data_lanes; /* number of CSI2 Data Lanes used */ + struct iss_csiphy_lanes_cfg lanes; + struct iss_csiphy_dphy_cfg dphy; +}; + +int omap4iss_csiphy_config(struct iss_device *iss, + struct v4l2_subdev *csi2_subdev); +int omap4iss_csiphy_acquire(struct iss_csiphy *phy); +void omap4iss_csiphy_release(struct iss_csiphy *phy); +int omap4iss_csiphy_init(struct iss_device *iss); + +#endif /* OMAP4_ISS_CSI_PHY_H */ diff --git a/drivers/staging/media/omap4iss/iss_ipipe.c b/drivers/staging/media/omap4iss/iss_ipipe.c new file mode 100644 index 00000000000..6eaafc5e2ee --- /dev/null +++ b/drivers/staging/media/omap4iss/iss_ipipe.c @@ -0,0 +1,570 @@ +/* + * TI OMAP4 ISS V4L2 Driver - ISP IPIPE module + * + * Copyright (C) 2012 Texas Instruments, Inc. + * + * Author: Sergio Aguirre <sergio.a.aguirre@gmail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include <linux/module.h> +#include <linux/uaccess.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/dma-mapping.h> +#include <linux/mm.h> +#include <linux/sched.h> + +#include "iss.h" +#include "iss_regs.h" +#include "iss_ipipe.h" + +static struct v4l2_mbus_framefmt * +__ipipe_get_format(struct iss_ipipe_device *ipipe, struct v4l2_subdev_fh *fh, + unsigned int pad, enum v4l2_subdev_format_whence which); + +static const unsigned int ipipe_fmts[] = { + V4L2_MBUS_FMT_SGRBG10_1X10, + V4L2_MBUS_FMT_SRGGB10_1X10, + V4L2_MBUS_FMT_SBGGR10_1X10, + V4L2_MBUS_FMT_SGBRG10_1X10, +}; + +/* + * ipipe_print_status - Print current IPIPE Module register values. + * @ipipe: Pointer to ISS ISP IPIPE device. + * + * Also prints other debug information stored in the IPIPE module. + */ +#define IPIPE_PRINT_REGISTER(iss, name)\ + dev_dbg(iss->dev, "###IPIPE " #name "=0x%08x\n", \ + iss_reg_read(iss, OMAP4_ISS_MEM_ISP_IPIPE, IPIPE_##name)) + +static void ipipe_print_status(struct iss_ipipe_device *ipipe) +{ + struct iss_device *iss = to_iss_device(ipipe); + + dev_dbg(iss->dev, "-------------IPIPE Register dump-------------\n"); + + IPIPE_PRINT_REGISTER(iss, SRC_EN); + IPIPE_PRINT_REGISTER(iss, SRC_MODE); + IPIPE_PRINT_REGISTER(iss, SRC_FMT); + IPIPE_PRINT_REGISTER(iss, SRC_COL); + IPIPE_PRINT_REGISTER(iss, SRC_VPS); + IPIPE_PRINT_REGISTER(iss, SRC_VSZ); + IPIPE_PRINT_REGISTER(iss, SRC_HPS); + IPIPE_PRINT_REGISTER(iss, SRC_HSZ); + IPIPE_PRINT_REGISTER(iss, GCK_MMR); + IPIPE_PRINT_REGISTER(iss, YUV_PHS); + + dev_dbg(iss->dev, "-----------------------------------------------\n"); +} + +/* + * ipipe_enable - Enable/Disable IPIPE. + * @enable: enable flag + * + */ +static void ipipe_enable(struct iss_ipipe_device *ipipe, u8 enable) +{ + struct iss_device *iss = to_iss_device(ipipe); + + iss_reg_update(iss, OMAP4_ISS_MEM_ISP_IPIPE, IPIPE_SRC_EN, + IPIPE_SRC_EN_EN, enable ? IPIPE_SRC_EN_EN : 0); +} + +/* ----------------------------------------------------------------------------- + * Format- and pipeline-related configuration helpers + */ + +static void ipipe_configure(struct iss_ipipe_device *ipipe) +{ + struct iss_device *iss = to_iss_device(ipipe); + struct v4l2_mbus_framefmt *format; + + /* IPIPE_PAD_SINK */ + format = &ipipe->formats[IPIPE_PAD_SINK]; + + /* NOTE: Currently just supporting pipeline IN: RGB, OUT: YUV422 */ + iss_reg_write(iss, OMAP4_ISS_MEM_ISP_IPIPE, IPIPE_SRC_FMT, + IPIPE_SRC_FMT_RAW2YUV); + + /* Enable YUV444 -> YUV422 conversion */ + iss_reg_write(iss, OMAP4_ISS_MEM_ISP_IPIPE, IPIPE_YUV_PHS, + IPIPE_YUV_PHS_LPF); + + iss_reg_write(iss, OMAP4_ISS_MEM_ISP_IPIPE, IPIPE_SRC_VPS, 0); + iss_reg_write(iss, OMAP4_ISS_MEM_ISP_IPIPE, IPIPE_SRC_HPS, 0); + iss_reg_write(iss, OMAP4_ISS_MEM_ISP_IPIPE, IPIPE_SRC_VSZ, + (format->height - 2) & IPIPE_SRC_VSZ_MASK); + iss_reg_write(iss, OMAP4_ISS_MEM_ISP_IPIPE, IPIPE_SRC_HSZ, + (format->width - 1) & IPIPE_SRC_HSZ_MASK); + + /* Ignore ipipeif_wrt signal, and operate on-the-fly. */ + iss_reg_clr(iss, OMAP4_ISS_MEM_ISP_IPIPE, IPIPE_SRC_MODE, + IPIPE_SRC_MODE_WRT | IPIPE_SRC_MODE_OST); + + /* HACK: Values tuned for Ducati SW (OV) */ + iss_reg_write(iss, OMAP4_ISS_MEM_ISP_IPIPE, IPIPE_SRC_COL, + IPIPE_SRC_COL_EE_B | IPIPE_SRC_COL_EO_GB | + IPIPE_SRC_COL_OE_GR | IPIPE_SRC_COL_OO_R); + + /* IPIPE_PAD_SOURCE_VP */ + format = &ipipe->formats[IPIPE_PAD_SOURCE_VP]; + /* Do nothing? */ +} + +/* ----------------------------------------------------------------------------- + * V4L2 subdev operations + */ + +/* + * ipipe_set_stream - Enable/Disable streaming on the IPIPE module + * @sd: ISP IPIPE V4L2 subdevice + * @enable: Enable/disable stream + */ +static int ipipe_set_stream(struct v4l2_subdev *sd, int enable) +{ + struct iss_ipipe_device *ipipe = v4l2_get_subdevdata(sd); + struct iss_device *iss = to_iss_device(ipipe); + int ret = 0; + + if (ipipe->state == ISS_PIPELINE_STREAM_STOPPED) { + if (enable == ISS_PIPELINE_STREAM_STOPPED) + return 0; + + omap4iss_isp_subclk_enable(iss, OMAP4_ISS_ISP_SUBCLK_IPIPE); + + /* Enable clk_arm_g0 */ + iss_reg_write(iss, OMAP4_ISS_MEM_ISP_IPIPE, IPIPE_GCK_MMR, + IPIPE_GCK_MMR_REG); + + /* Enable clk_pix_g[3:0] */ + iss_reg_write(iss, OMAP4_ISS_MEM_ISP_IPIPE, IPIPE_GCK_PIX, + IPIPE_GCK_PIX_G3 | IPIPE_GCK_PIX_G2 | + IPIPE_GCK_PIX_G1 | IPIPE_GCK_PIX_G0); + } + + switch (enable) { + case ISS_PIPELINE_STREAM_CONTINUOUS: + + ipipe_configure(ipipe); + ipipe_print_status(ipipe); + + atomic_set(&ipipe->stopping, 0); + ipipe_enable(ipipe, 1); + break; + + case ISS_PIPELINE_STREAM_STOPPED: + if (ipipe->state == ISS_PIPELINE_STREAM_STOPPED) + return 0; + if (omap4iss_module_sync_idle(&sd->entity, &ipipe->wait, + &ipipe->stopping)) + ret = -ETIMEDOUT; + + ipipe_enable(ipipe, 0); + omap4iss_isp_subclk_disable(iss, OMAP4_ISS_ISP_SUBCLK_IPIPE); + break; + } + + ipipe->state = enable; + return ret; +} + +static struct v4l2_mbus_framefmt * +__ipipe_get_format(struct iss_ipipe_device *ipipe, struct v4l2_subdev_fh *fh, + unsigned int pad, enum v4l2_subdev_format_whence which) +{ + if (which == V4L2_SUBDEV_FORMAT_TRY) + return v4l2_subdev_get_try_format(fh, pad); + else + return &ipipe->formats[pad]; +} + +/* + * ipipe_try_format - Try video format on a pad + * @ipipe: ISS IPIPE device + * @fh : V4L2 subdev file handle + * @pad: Pad number + * @fmt: Format + */ +static void +ipipe_try_format(struct iss_ipipe_device *ipipe, struct v4l2_subdev_fh *fh, + unsigned int pad, struct v4l2_mbus_framefmt *fmt, + enum v4l2_subdev_format_whence which) +{ + struct v4l2_mbus_framefmt *format; + unsigned int width = fmt->width; + unsigned int height = fmt->height; + unsigned int i; + + switch (pad) { + case IPIPE_PAD_SINK: + for (i = 0; i < ARRAY_SIZE(ipipe_fmts); i++) { + if (fmt->code == ipipe_fmts[i]) + break; + } + + /* If not found, use SGRBG10 as default */ + if (i >= ARRAY_SIZE(ipipe_fmts)) + fmt->code = V4L2_MBUS_FMT_SGRBG10_1X10; + + /* Clamp the input size. */ + fmt->width = clamp_t(u32, width, 1, 8192); + fmt->height = clamp_t(u32, height, 1, 8192); + fmt->colorspace = V4L2_COLORSPACE_SRGB; + break; + + case IPIPE_PAD_SOURCE_VP: + format = __ipipe_get_format(ipipe, fh, IPIPE_PAD_SINK, which); + memcpy(fmt, format, sizeof(*fmt)); + + fmt->code = V4L2_MBUS_FMT_UYVY8_1X16; + fmt->width = clamp_t(u32, width, 32, fmt->width); + fmt->height = clamp_t(u32, height, 32, fmt->height); + fmt->colorspace = V4L2_COLORSPACE_JPEG; + break; + } + + fmt->field = V4L2_FIELD_NONE; +} + +/* + * ipipe_enum_mbus_code - Handle pixel format enumeration + * @sd : pointer to v4l2 subdev structure + * @fh : V4L2 subdev file handle + * @code : pointer to v4l2_subdev_mbus_code_enum structure + * return -EINVAL or zero on success + */ +static int ipipe_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_fh *fh, + struct v4l2_subdev_mbus_code_enum *code) +{ + switch (code->pad) { + case IPIPE_PAD_SINK: + if (code->index >= ARRAY_SIZE(ipipe_fmts)) + return -EINVAL; + + code->code = ipipe_fmts[code->index]; + break; + + case IPIPE_PAD_SOURCE_VP: + /* FIXME: Forced format conversion inside IPIPE ? */ + if (code->index != 0) + return -EINVAL; + + code->code = V4L2_MBUS_FMT_UYVY8_1X16; + break; + + default: + return -EINVAL; + } + + return 0; +} + +static int ipipe_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_fh *fh, + struct v4l2_subdev_frame_size_enum *fse) +{ + struct iss_ipipe_device *ipipe = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt format; + + if (fse->index != 0) + return -EINVAL; + + format.code = fse->code; + format.width = 1; + format.height = 1; + ipipe_try_format(ipipe, fh, fse->pad, &format, V4L2_SUBDEV_FORMAT_TRY); + fse->min_width = format.width; + fse->min_height = format.height; + + if (format.code != fse->code) + return -EINVAL; + + format.code = fse->code; + format.width = -1; + format.height = -1; + ipipe_try_format(ipipe, fh, fse->pad, &format, V4L2_SUBDEV_FORMAT_TRY); + fse->max_width = format.width; + fse->max_height = format.height; + + return 0; +} + +/* + * ipipe_get_format - Retrieve the video format on a pad + * @sd : ISP IPIPE V4L2 subdevice + * @fh : V4L2 subdev file handle + * @fmt: Format + * + * Return 0 on success or -EINVAL if the pad is invalid or doesn't correspond + * to the format type. + */ +static int ipipe_get_format(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh, + struct v4l2_subdev_format *fmt) +{ + struct iss_ipipe_device *ipipe = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt *format; + + format = __ipipe_get_format(ipipe, fh, fmt->pad, fmt->which); + if (format == NULL) + return -EINVAL; + + fmt->format = *format; + return 0; +} + +/* + * ipipe_set_format - Set the video format on a pad + * @sd : ISP IPIPE V4L2 subdevice + * @fh : V4L2 subdev file handle + * @fmt: Format + * + * Return 0 on success or -EINVAL if the pad is invalid or doesn't correspond + * to the format type. + */ +static int ipipe_set_format(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh, + struct v4l2_subdev_format *fmt) +{ + struct iss_ipipe_device *ipipe = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt *format; + + format = __ipipe_get_format(ipipe, fh, fmt->pad, fmt->which); + if (format == NULL) + return -EINVAL; + + ipipe_try_format(ipipe, fh, fmt->pad, &fmt->format, fmt->which); + *format = fmt->format; + + /* Propagate the format from sink to source */ + if (fmt->pad == IPIPE_PAD_SINK) { + format = __ipipe_get_format(ipipe, fh, IPIPE_PAD_SOURCE_VP, + fmt->which); + *format = fmt->format; + ipipe_try_format(ipipe, fh, IPIPE_PAD_SOURCE_VP, format, + fmt->which); + } + + return 0; +} + +static int ipipe_link_validate(struct v4l2_subdev *sd, struct media_link *link, + struct v4l2_subdev_format *source_fmt, + struct v4l2_subdev_format *sink_fmt) +{ + /* Check if the two ends match */ + if (source_fmt->format.width != sink_fmt->format.width || + source_fmt->format.height != sink_fmt->format.height) + return -EPIPE; + + if (source_fmt->format.code != sink_fmt->format.code) + return -EPIPE; + + return 0; +} + +/* + * ipipe_init_formats - Initialize formats on all pads + * @sd: ISP IPIPE V4L2 subdevice + * @fh: V4L2 subdev file handle + * + * Initialize all pad formats with default values. If fh is not NULL, try + * formats are initialized on the file handle. Otherwise active formats are + * initialized on the device. + */ +static int ipipe_init_formats(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + struct v4l2_subdev_format format; + + memset(&format, 0, sizeof(format)); + format.pad = IPIPE_PAD_SINK; + format.which = fh ? V4L2_SUBDEV_FORMAT_TRY : V4L2_SUBDEV_FORMAT_ACTIVE; + format.format.code = V4L2_MBUS_FMT_SGRBG10_1X10; + format.format.width = 4096; + format.format.height = 4096; + ipipe_set_format(sd, fh, &format); + + return 0; +} + +/* V4L2 subdev video operations */ +static const struct v4l2_subdev_video_ops ipipe_v4l2_video_ops = { + .s_stream = ipipe_set_stream, +}; + +/* V4L2 subdev pad operations */ +static const struct v4l2_subdev_pad_ops ipipe_v4l2_pad_ops = { + .enum_mbus_code = ipipe_enum_mbus_code, + .enum_frame_size = ipipe_enum_frame_size, + .get_fmt = ipipe_get_format, + .set_fmt = ipipe_set_format, + .link_validate = ipipe_link_validate, +}; + +/* V4L2 subdev operations */ +static const struct v4l2_subdev_ops ipipe_v4l2_ops = { + .video = &ipipe_v4l2_video_ops, + .pad = &ipipe_v4l2_pad_ops, +}; + +/* V4L2 subdev internal operations */ +static const struct v4l2_subdev_internal_ops ipipe_v4l2_internal_ops = { + .open = ipipe_init_formats, +}; + +/* ----------------------------------------------------------------------------- + * Media entity operations + */ + +/* + * ipipe_link_setup - Setup IPIPE connections + * @entity: IPIPE media entity + * @local: Pad at the local end of the link + * @remote: Pad at the remote end of the link + * @flags: Link flags + * + * return -EINVAL or zero on success + */ +static int ipipe_link_setup(struct media_entity *entity, + const struct media_pad *local, + const struct media_pad *remote, u32 flags) +{ + struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(entity); + struct iss_ipipe_device *ipipe = v4l2_get_subdevdata(sd); + struct iss_device *iss = to_iss_device(ipipe); + + switch (local->index | media_entity_type(remote->entity)) { + case IPIPE_PAD_SINK | MEDIA_ENT_T_V4L2_SUBDEV: + /* Read from IPIPEIF. */ + if (!(flags & MEDIA_LNK_FL_ENABLED)) { + ipipe->input = IPIPE_INPUT_NONE; + break; + } + + if (ipipe->input != IPIPE_INPUT_NONE) + return -EBUSY; + + if (remote->entity == &iss->ipipeif.subdev.entity) + ipipe->input = IPIPE_INPUT_IPIPEIF; + + break; + + case IPIPE_PAD_SOURCE_VP | MEDIA_ENT_T_V4L2_SUBDEV: + /* Send to RESIZER */ + if (flags & MEDIA_LNK_FL_ENABLED) { + if (ipipe->output & ~IPIPE_OUTPUT_VP) + return -EBUSY; + ipipe->output |= IPIPE_OUTPUT_VP; + } else { + ipipe->output &= ~IPIPE_OUTPUT_VP; + } + break; + + default: + return -EINVAL; + } + + return 0; +} + +/* media operations */ +static const struct media_entity_operations ipipe_media_ops = { + .link_setup = ipipe_link_setup, + .link_validate = v4l2_subdev_link_validate, +}; + +/* + * ipipe_init_entities - Initialize V4L2 subdev and media entity + * @ipipe: ISS ISP IPIPE module + * + * Return 0 on success and a negative error code on failure. + */ +static int ipipe_init_entities(struct iss_ipipe_device *ipipe) +{ + struct v4l2_subdev *sd = &ipipe->subdev; + struct media_pad *pads = ipipe->pads; + struct media_entity *me = &sd->entity; + int ret; + + ipipe->input = IPIPE_INPUT_NONE; + + v4l2_subdev_init(sd, &ipipe_v4l2_ops); + sd->internal_ops = &ipipe_v4l2_internal_ops; + strlcpy(sd->name, "OMAP4 ISS ISP IPIPE", sizeof(sd->name)); + sd->grp_id = 1 << 16; /* group ID for iss subdevs */ + v4l2_set_subdevdata(sd, ipipe); + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + + pads[IPIPE_PAD_SINK].flags = MEDIA_PAD_FL_SINK; + pads[IPIPE_PAD_SOURCE_VP].flags = MEDIA_PAD_FL_SOURCE; + + me->ops = &ipipe_media_ops; + ret = media_entity_init(me, IPIPE_PADS_NUM, pads, 0); + if (ret < 0) + return ret; + + ipipe_init_formats(sd, NULL); + + return 0; +} + +void omap4iss_ipipe_unregister_entities(struct iss_ipipe_device *ipipe) +{ + media_entity_cleanup(&ipipe->subdev.entity); + + v4l2_device_unregister_subdev(&ipipe->subdev); +} + +int omap4iss_ipipe_register_entities(struct iss_ipipe_device *ipipe, + struct v4l2_device *vdev) +{ + int ret; + + /* Register the subdev and video node. */ + ret = v4l2_device_register_subdev(vdev, &ipipe->subdev); + if (ret < 0) + goto error; + + return 0; + +error: + omap4iss_ipipe_unregister_entities(ipipe); + return ret; +} + +/* ----------------------------------------------------------------------------- + * ISP IPIPE initialisation and cleanup + */ + +/* + * omap4iss_ipipe_init - IPIPE module initialization. + * @iss: Device pointer specific to the OMAP4 ISS. + * + * TODO: Get the initialisation values from platform data. + * + * Return 0 on success or a negative error code otherwise. + */ +int omap4iss_ipipe_init(struct iss_device *iss) +{ + struct iss_ipipe_device *ipipe = &iss->ipipe; + + ipipe->state = ISS_PIPELINE_STREAM_STOPPED; + init_waitqueue_head(&ipipe->wait); + + return ipipe_init_entities(ipipe); +} + +/* + * omap4iss_ipipe_cleanup - IPIPE module cleanup. + * @iss: Device pointer specific to the OMAP4 ISS. + */ +void omap4iss_ipipe_cleanup(struct iss_device *iss) +{ + /* FIXME: are you sure there's nothing to do? */ +} diff --git a/drivers/staging/media/omap4iss/iss_ipipe.h b/drivers/staging/media/omap4iss/iss_ipipe.h new file mode 100644 index 00000000000..c22d9041f2a --- /dev/null +++ b/drivers/staging/media/omap4iss/iss_ipipe.h @@ -0,0 +1,67 @@ +/* + * TI OMAP4 ISS V4L2 Driver - ISP IPIPE module + * + * Copyright (C) 2012 Texas Instruments, Inc. + * + * Author: Sergio Aguirre <sergio.a.aguirre@gmail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#ifndef OMAP4_ISS_IPIPE_H +#define OMAP4_ISS_IPIPE_H + +#include "iss_video.h" + +enum ipipe_input_entity { + IPIPE_INPUT_NONE, + IPIPE_INPUT_IPIPEIF, +}; + +#define IPIPE_OUTPUT_VP (1 << 0) + +/* Sink and source IPIPE pads */ +#define IPIPE_PAD_SINK 0 +#define IPIPE_PAD_SOURCE_VP 1 +#define IPIPE_PADS_NUM 2 + +/* + * struct iss_ipipe_device - Structure for the IPIPE module to store its own + * information + * @subdev: V4L2 subdevice + * @pads: Sink and source media entity pads + * @formats: Active video formats + * @input: Active input + * @output: Active outputs + * @error: A hardware error occurred during capture + * @state: Streaming state + * @wait: Wait queue used to stop the module + * @stopping: Stopping state + */ +struct iss_ipipe_device { + struct v4l2_subdev subdev; + struct media_pad pads[IPIPE_PADS_NUM]; + struct v4l2_mbus_framefmt formats[IPIPE_PADS_NUM]; + + enum ipipe_input_entity input; + unsigned int output; + unsigned int error; + + enum iss_pipeline_stream_state state; + wait_queue_head_t wait; + atomic_t stopping; +}; + +struct iss_device; + +int omap4iss_ipipe_register_entities(struct iss_ipipe_device *ipipe, + struct v4l2_device *vdev); +void omap4iss_ipipe_unregister_entities(struct iss_ipipe_device *ipipe); + +int omap4iss_ipipe_init(struct iss_device *iss); +void omap4iss_ipipe_cleanup(struct iss_device *iss); + +#endif /* OMAP4_ISS_IPIPE_H */ diff --git a/drivers/staging/media/omap4iss/iss_ipipeif.c b/drivers/staging/media/omap4iss/iss_ipipeif.c new file mode 100644 index 00000000000..7bc14576249 --- /dev/null +++ b/drivers/staging/media/omap4iss/iss_ipipeif.c @@ -0,0 +1,849 @@ +/* + * TI OMAP4 ISS V4L2 Driver - ISP IPIPEIF module + * + * Copyright (C) 2012 Texas Instruments, Inc. + * + * Author: Sergio Aguirre <sergio.a.aguirre@gmail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include <linux/module.h> +#include <linux/uaccess.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/dma-mapping.h> +#include <linux/mm.h> +#include <linux/sched.h> + +#include "iss.h" +#include "iss_regs.h" +#include "iss_ipipeif.h" + +static const unsigned int ipipeif_fmts[] = { + V4L2_MBUS_FMT_SGRBG10_1X10, + V4L2_MBUS_FMT_SRGGB10_1X10, + V4L2_MBUS_FMT_SBGGR10_1X10, + V4L2_MBUS_FMT_SGBRG10_1X10, + V4L2_MBUS_FMT_UYVY8_1X16, + V4L2_MBUS_FMT_YUYV8_1X16, +}; + +/* + * ipipeif_print_status - Print current IPIPEIF Module register values. + * @ipipeif: Pointer to ISS ISP IPIPEIF device. + * + * Also prints other debug information stored in the IPIPEIF module. + */ +#define IPIPEIF_PRINT_REGISTER(iss, name)\ + dev_dbg(iss->dev, "###IPIPEIF " #name "=0x%08x\n", \ + iss_reg_read(iss, OMAP4_ISS_MEM_ISP_IPIPEIF, IPIPEIF_##name)) + +#define ISIF_PRINT_REGISTER(iss, name)\ + dev_dbg(iss->dev, "###ISIF " #name "=0x%08x\n", \ + iss_reg_read(iss, OMAP4_ISS_MEM_ISP_ISIF, ISIF_##name)) + +#define ISP5_PRINT_REGISTER(iss, name)\ + dev_dbg(iss->dev, "###ISP5 " #name "=0x%08x\n", \ + iss_reg_read(iss, OMAP4_ISS_MEM_ISP_SYS1, ISP5_##name)) + +static void ipipeif_print_status(struct iss_ipipeif_device *ipipeif) +{ + struct iss_device *iss = to_iss_device(ipipeif); + + dev_dbg(iss->dev, "-------------IPIPEIF Register dump-------------\n"); + + IPIPEIF_PRINT_REGISTER(iss, CFG1); + IPIPEIF_PRINT_REGISTER(iss, CFG2); + + ISIF_PRINT_REGISTER(iss, SYNCEN); + ISIF_PRINT_REGISTER(iss, CADU); + ISIF_PRINT_REGISTER(iss, CADL); + ISIF_PRINT_REGISTER(iss, MODESET); + ISIF_PRINT_REGISTER(iss, CCOLP); + ISIF_PRINT_REGISTER(iss, SPH); + ISIF_PRINT_REGISTER(iss, LNH); + ISIF_PRINT_REGISTER(iss, LNV); + ISIF_PRINT_REGISTER(iss, VDINT(0)); + ISIF_PRINT_REGISTER(iss, HSIZE); + + ISP5_PRINT_REGISTER(iss, SYSCONFIG); + ISP5_PRINT_REGISTER(iss, CTRL); + ISP5_PRINT_REGISTER(iss, IRQSTATUS(0)); + ISP5_PRINT_REGISTER(iss, IRQENABLE_SET(0)); + ISP5_PRINT_REGISTER(iss, IRQENABLE_CLR(0)); + + dev_dbg(iss->dev, "-----------------------------------------------\n"); +} + +static void ipipeif_write_enable(struct iss_ipipeif_device *ipipeif, u8 enable) +{ + struct iss_device *iss = to_iss_device(ipipeif); + + iss_reg_update(iss, OMAP4_ISS_MEM_ISP_ISIF, ISIF_SYNCEN, + ISIF_SYNCEN_DWEN, enable ? ISIF_SYNCEN_DWEN : 0); +} + +/* + * ipipeif_enable - Enable/Disable IPIPEIF. + * @enable: enable flag + * + */ +static void ipipeif_enable(struct iss_ipipeif_device *ipipeif, u8 enable) +{ + struct iss_device *iss = to_iss_device(ipipeif); + + iss_reg_update(iss, OMAP4_ISS_MEM_ISP_ISIF, ISIF_SYNCEN, + ISIF_SYNCEN_SYEN, enable ? ISIF_SYNCEN_SYEN : 0); +} + +/* ----------------------------------------------------------------------------- + * Format- and pipeline-related configuration helpers + */ + +/* + * ipipeif_set_outaddr - Set memory address to save output image + * @ipipeif: Pointer to ISP IPIPEIF device. + * @addr: 32-bit memory address aligned on 32 byte boundary. + * + * Sets the memory address where the output will be saved. + */ +static void ipipeif_set_outaddr(struct iss_ipipeif_device *ipipeif, u32 addr) +{ + struct iss_device *iss = to_iss_device(ipipeif); + + /* Save address splitted in Base Address H & L */ + iss_reg_write(iss, OMAP4_ISS_MEM_ISP_ISIF, ISIF_CADU, + (addr >> (16 + 5)) & ISIF_CADU_MASK); + iss_reg_write(iss, OMAP4_ISS_MEM_ISP_ISIF, ISIF_CADL, + (addr >> 5) & ISIF_CADL_MASK); +} + +static void ipipeif_configure(struct iss_ipipeif_device *ipipeif) +{ + struct iss_device *iss = to_iss_device(ipipeif); + const struct iss_format_info *info; + struct v4l2_mbus_framefmt *format; + u32 isif_ccolp = 0; + + omap4iss_configure_bridge(iss, ipipeif->input); + + /* IPIPEIF_PAD_SINK */ + format = &ipipeif->formats[IPIPEIF_PAD_SINK]; + + /* IPIPEIF with YUV422 input from ISIF */ + iss_reg_clr(iss, OMAP4_ISS_MEM_ISP_IPIPEIF, IPIPEIF_CFG1, + IPIPEIF_CFG1_INPSRC1_MASK | IPIPEIF_CFG1_INPSRC2_MASK); + + /* Select ISIF/IPIPEIF input format */ + switch (format->code) { + case V4L2_MBUS_FMT_UYVY8_1X16: + case V4L2_MBUS_FMT_YUYV8_1X16: + iss_reg_update(iss, OMAP4_ISS_MEM_ISP_ISIF, ISIF_MODESET, + ISIF_MODESET_CCDMD | ISIF_MODESET_INPMOD_MASK | + ISIF_MODESET_CCDW_MASK, + ISIF_MODESET_INPMOD_YCBCR16); + + iss_reg_update(iss, OMAP4_ISS_MEM_ISP_IPIPEIF, IPIPEIF_CFG2, + IPIPEIF_CFG2_YUV8, IPIPEIF_CFG2_YUV16); + + break; + case V4L2_MBUS_FMT_SGRBG10_1X10: + isif_ccolp = ISIF_CCOLP_CP0_F0_GR | + ISIF_CCOLP_CP1_F0_R | + ISIF_CCOLP_CP2_F0_B | + ISIF_CCOLP_CP3_F0_GB; + goto cont_raw; + case V4L2_MBUS_FMT_SRGGB10_1X10: + isif_ccolp = ISIF_CCOLP_CP0_F0_R | + ISIF_CCOLP_CP1_F0_GR | + ISIF_CCOLP_CP2_F0_GB | + ISIF_CCOLP_CP3_F0_B; + goto cont_raw; + case V4L2_MBUS_FMT_SBGGR10_1X10: + isif_ccolp = ISIF_CCOLP_CP0_F0_B | + ISIF_CCOLP_CP1_F0_GB | + ISIF_CCOLP_CP2_F0_GR | + ISIF_CCOLP_CP3_F0_R; + goto cont_raw; + case V4L2_MBUS_FMT_SGBRG10_1X10: + isif_ccolp = ISIF_CCOLP_CP0_F0_GB | + ISIF_CCOLP_CP1_F0_B | + ISIF_CCOLP_CP2_F0_R | + ISIF_CCOLP_CP3_F0_GR; +cont_raw: + iss_reg_clr(iss, OMAP4_ISS_MEM_ISP_IPIPEIF, IPIPEIF_CFG2, + IPIPEIF_CFG2_YUV16); + + iss_reg_update(iss, OMAP4_ISS_MEM_ISP_ISIF, ISIF_MODESET, + ISIF_MODESET_CCDMD | ISIF_MODESET_INPMOD_MASK | + ISIF_MODESET_CCDW_MASK, ISIF_MODESET_INPMOD_RAW | + ISIF_MODESET_CCDW_2BIT); + + info = omap4iss_video_format_info(format->code); + iss_reg_update(iss, OMAP4_ISS_MEM_ISP_ISIF, ISIF_CGAMMAWD, + ISIF_CGAMMAWD_GWDI_MASK, + ISIF_CGAMMAWD_GWDI(info->bpp)); + + /* Set RAW Bayer pattern */ + iss_reg_write(iss, OMAP4_ISS_MEM_ISP_ISIF, ISIF_CCOLP, + isif_ccolp); + break; + } + + iss_reg_write(iss, OMAP4_ISS_MEM_ISP_ISIF, ISIF_SPH, 0 & ISIF_SPH_MASK); + iss_reg_write(iss, OMAP4_ISS_MEM_ISP_ISIF, ISIF_LNH, + (format->width - 1) & ISIF_LNH_MASK); + iss_reg_write(iss, OMAP4_ISS_MEM_ISP_ISIF, ISIF_LNV, + (format->height - 1) & ISIF_LNV_MASK); + + /* Generate ISIF0 on the last line of the image */ + iss_reg_write(iss, OMAP4_ISS_MEM_ISP_ISIF, ISIF_VDINT(0), + format->height - 1); + + /* IPIPEIF_PAD_SOURCE_ISIF_SF */ + format = &ipipeif->formats[IPIPEIF_PAD_SOURCE_ISIF_SF]; + + iss_reg_write(iss, OMAP4_ISS_MEM_ISP_ISIF, ISIF_HSIZE, + (ipipeif->video_out.bpl_value >> 5) & + ISIF_HSIZE_HSIZE_MASK); + + /* IPIPEIF_PAD_SOURCE_VP */ + /* Do nothing? */ +} + +/* ----------------------------------------------------------------------------- + * Interrupt handling + */ + +static void ipipeif_isr_buffer(struct iss_ipipeif_device *ipipeif) +{ + struct iss_buffer *buffer; + + /* The ISIF generates VD0 interrupts even when writes are disabled. + * deal with it anyway). Disabling the ISIF when no buffer is available + * is thus not be enough, we need to handle the situation explicitly. + */ + if (list_empty(&ipipeif->video_out.dmaqueue)) + return; + + ipipeif_write_enable(ipipeif, 0); + + buffer = omap4iss_video_buffer_next(&ipipeif->video_out); + if (buffer == NULL) + return; + + ipipeif_set_outaddr(ipipeif, buffer->iss_addr); + + ipipeif_write_enable(ipipeif, 1); +} + +/* + * ipipeif_isif0_isr - Handle ISIF0 event + * @ipipeif: Pointer to ISP IPIPEIF device. + * + * Executes LSC deferred enablement before next frame starts. + */ +static void ipipeif_isif0_isr(struct iss_ipipeif_device *ipipeif) +{ + struct iss_pipeline *pipe = + to_iss_pipeline(&ipipeif->subdev.entity); + if (pipe->do_propagation) + atomic_inc(&pipe->frame_number); + + if (ipipeif->output & IPIPEIF_OUTPUT_MEMORY) + ipipeif_isr_buffer(ipipeif); +} + +/* + * omap4iss_ipipeif_isr - Configure ipipeif during interframe time. + * @ipipeif: Pointer to ISP IPIPEIF device. + * @events: IPIPEIF events + */ +void omap4iss_ipipeif_isr(struct iss_ipipeif_device *ipipeif, u32 events) +{ + if (omap4iss_module_sync_is_stopping(&ipipeif->wait, + &ipipeif->stopping)) + return; + + if (events & ISP5_IRQ_ISIF_INT(0)) + ipipeif_isif0_isr(ipipeif); +} + +/* ----------------------------------------------------------------------------- + * ISP video operations + */ + +static int ipipeif_video_queue(struct iss_video *video, + struct iss_buffer *buffer) +{ + struct iss_ipipeif_device *ipipeif = container_of(video, + struct iss_ipipeif_device, video_out); + + if (!(ipipeif->output & IPIPEIF_OUTPUT_MEMORY)) + return -ENODEV; + + ipipeif_set_outaddr(ipipeif, buffer->iss_addr); + + /* + * If streaming was enabled before there was a buffer queued + * or underrun happened in the ISR, the hardware was not enabled + * and DMA queue flag ISS_VIDEO_DMAQUEUE_UNDERRUN is still set. + * Enable it now. + */ + if (video->dmaqueue_flags & ISS_VIDEO_DMAQUEUE_UNDERRUN) { + if (ipipeif->output & IPIPEIF_OUTPUT_MEMORY) + ipipeif_write_enable(ipipeif, 1); + ipipeif_enable(ipipeif, 1); + iss_video_dmaqueue_flags_clr(video); + } + + return 0; +} + +static const struct iss_video_operations ipipeif_video_ops = { + .queue = ipipeif_video_queue, +}; + +/* ----------------------------------------------------------------------------- + * V4L2 subdev operations + */ + +#define IPIPEIF_DRV_SUBCLK_MASK (OMAP4_ISS_ISP_SUBCLK_IPIPEIF |\ + OMAP4_ISS_ISP_SUBCLK_ISIF) +/* + * ipipeif_set_stream - Enable/Disable streaming on the IPIPEIF module + * @sd: ISP IPIPEIF V4L2 subdevice + * @enable: Enable/disable stream + */ +static int ipipeif_set_stream(struct v4l2_subdev *sd, int enable) +{ + struct iss_ipipeif_device *ipipeif = v4l2_get_subdevdata(sd); + struct iss_device *iss = to_iss_device(ipipeif); + struct iss_video *video_out = &ipipeif->video_out; + int ret = 0; + + if (ipipeif->state == ISS_PIPELINE_STREAM_STOPPED) { + if (enable == ISS_PIPELINE_STREAM_STOPPED) + return 0; + + omap4iss_isp_subclk_enable(iss, IPIPEIF_DRV_SUBCLK_MASK); + } + + switch (enable) { + case ISS_PIPELINE_STREAM_CONTINUOUS: + + ipipeif_configure(ipipeif); + ipipeif_print_status(ipipeif); + + /* + * When outputting to memory with no buffer available, let the + * buffer queue handler start the hardware. A DMA queue flag + * ISS_VIDEO_DMAQUEUE_QUEUED will be set as soon as there is + * a buffer available. + */ + if (ipipeif->output & IPIPEIF_OUTPUT_MEMORY && + !(video_out->dmaqueue_flags & ISS_VIDEO_DMAQUEUE_QUEUED)) + break; + + atomic_set(&ipipeif->stopping, 0); + if (ipipeif->output & IPIPEIF_OUTPUT_MEMORY) + ipipeif_write_enable(ipipeif, 1); + ipipeif_enable(ipipeif, 1); + iss_video_dmaqueue_flags_clr(video_out); + break; + + case ISS_PIPELINE_STREAM_STOPPED: + if (ipipeif->state == ISS_PIPELINE_STREAM_STOPPED) + return 0; + if (omap4iss_module_sync_idle(&sd->entity, &ipipeif->wait, + &ipipeif->stopping)) + ret = -ETIMEDOUT; + + if (ipipeif->output & IPIPEIF_OUTPUT_MEMORY) + ipipeif_write_enable(ipipeif, 0); + ipipeif_enable(ipipeif, 0); + omap4iss_isp_subclk_disable(iss, IPIPEIF_DRV_SUBCLK_MASK); + iss_video_dmaqueue_flags_clr(video_out); + break; + } + + ipipeif->state = enable; + return ret; +} + +static struct v4l2_mbus_framefmt * +__ipipeif_get_format(struct iss_ipipeif_device *ipipeif, + struct v4l2_subdev_fh *fh, unsigned int pad, + enum v4l2_subdev_format_whence which) +{ + if (which == V4L2_SUBDEV_FORMAT_TRY) + return v4l2_subdev_get_try_format(fh, pad); + else + return &ipipeif->formats[pad]; +} + +/* + * ipipeif_try_format - Try video format on a pad + * @ipipeif: ISS IPIPEIF device + * @fh : V4L2 subdev file handle + * @pad: Pad number + * @fmt: Format + */ +static void +ipipeif_try_format(struct iss_ipipeif_device *ipipeif, + struct v4l2_subdev_fh *fh, unsigned int pad, + struct v4l2_mbus_framefmt *fmt, + enum v4l2_subdev_format_whence which) +{ + struct v4l2_mbus_framefmt *format; + unsigned int width = fmt->width; + unsigned int height = fmt->height; + unsigned int i; + + switch (pad) { + case IPIPEIF_PAD_SINK: + /* TODO: If the IPIPEIF output formatter pad is connected + * directly to the resizer, only YUV formats can be used. + */ + for (i = 0; i < ARRAY_SIZE(ipipeif_fmts); i++) { + if (fmt->code == ipipeif_fmts[i]) + break; + } + + /* If not found, use SGRBG10 as default */ + if (i >= ARRAY_SIZE(ipipeif_fmts)) + fmt->code = V4L2_MBUS_FMT_SGRBG10_1X10; + + /* Clamp the input size. */ + fmt->width = clamp_t(u32, width, 1, 8192); + fmt->height = clamp_t(u32, height, 1, 8192); + break; + + case IPIPEIF_PAD_SOURCE_ISIF_SF: + format = __ipipeif_get_format(ipipeif, fh, IPIPEIF_PAD_SINK, + which); + memcpy(fmt, format, sizeof(*fmt)); + + /* The data formatter truncates the number of horizontal output + * pixels to a multiple of 16. To avoid clipping data, allow + * callers to request an output size bigger than the input size + * up to the nearest multiple of 16. + */ + fmt->width = clamp_t(u32, width, 32, (fmt->width + 15) & ~15); + fmt->width &= ~15; + fmt->height = clamp_t(u32, height, 32, fmt->height); + break; + + case IPIPEIF_PAD_SOURCE_VP: + format = __ipipeif_get_format(ipipeif, fh, IPIPEIF_PAD_SINK, + which); + memcpy(fmt, format, sizeof(*fmt)); + + fmt->width = clamp_t(u32, width, 32, fmt->width); + fmt->height = clamp_t(u32, height, 32, fmt->height); + break; + } + + /* Data is written to memory unpacked, each 10-bit or 12-bit pixel is + * stored on 2 bytes. + */ + fmt->colorspace = V4L2_COLORSPACE_SRGB; + fmt->field = V4L2_FIELD_NONE; +} + +/* + * ipipeif_enum_mbus_code - Handle pixel format enumeration + * @sd : pointer to v4l2 subdev structure + * @fh : V4L2 subdev file handle + * @code : pointer to v4l2_subdev_mbus_code_enum structure + * return -EINVAL or zero on success + */ +static int ipipeif_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_fh *fh, + struct v4l2_subdev_mbus_code_enum *code) +{ + struct iss_ipipeif_device *ipipeif = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt *format; + + switch (code->pad) { + case IPIPEIF_PAD_SINK: + if (code->index >= ARRAY_SIZE(ipipeif_fmts)) + return -EINVAL; + + code->code = ipipeif_fmts[code->index]; + break; + + case IPIPEIF_PAD_SOURCE_ISIF_SF: + case IPIPEIF_PAD_SOURCE_VP: + /* No format conversion inside IPIPEIF */ + if (code->index != 0) + return -EINVAL; + + format = __ipipeif_get_format(ipipeif, fh, IPIPEIF_PAD_SINK, + V4L2_SUBDEV_FORMAT_TRY); + + code->code = format->code; + break; + + default: + return -EINVAL; + } + + return 0; +} + +static int ipipeif_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_fh *fh, + struct v4l2_subdev_frame_size_enum *fse) +{ + struct iss_ipipeif_device *ipipeif = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt format; + + if (fse->index != 0) + return -EINVAL; + + format.code = fse->code; + format.width = 1; + format.height = 1; + ipipeif_try_format(ipipeif, fh, fse->pad, &format, + V4L2_SUBDEV_FORMAT_TRY); + fse->min_width = format.width; + fse->min_height = format.height; + + if (format.code != fse->code) + return -EINVAL; + + format.code = fse->code; + format.width = -1; + format.height = -1; + ipipeif_try_format(ipipeif, fh, fse->pad, &format, + V4L2_SUBDEV_FORMAT_TRY); + fse->max_width = format.width; + fse->max_height = format.height; + + return 0; +} + +/* + * ipipeif_get_format - Retrieve the video format on a pad + * @sd : ISP IPIPEIF V4L2 subdevice + * @fh : V4L2 subdev file handle + * @fmt: Format + * + * Return 0 on success or -EINVAL if the pad is invalid or doesn't correspond + * to the format type. + */ +static int ipipeif_get_format(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh, + struct v4l2_subdev_format *fmt) +{ + struct iss_ipipeif_device *ipipeif = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt *format; + + format = __ipipeif_get_format(ipipeif, fh, fmt->pad, fmt->which); + if (format == NULL) + return -EINVAL; + + fmt->format = *format; + return 0; +} + +/* + * ipipeif_set_format - Set the video format on a pad + * @sd : ISP IPIPEIF V4L2 subdevice + * @fh : V4L2 subdev file handle + * @fmt: Format + * + * Return 0 on success or -EINVAL if the pad is invalid or doesn't correspond + * to the format type. + */ +static int ipipeif_set_format(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh, + struct v4l2_subdev_format *fmt) +{ + struct iss_ipipeif_device *ipipeif = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt *format; + + format = __ipipeif_get_format(ipipeif, fh, fmt->pad, fmt->which); + if (format == NULL) + return -EINVAL; + + ipipeif_try_format(ipipeif, fh, fmt->pad, &fmt->format, fmt->which); + *format = fmt->format; + + /* Propagate the format from sink to source */ + if (fmt->pad == IPIPEIF_PAD_SINK) { + format = __ipipeif_get_format(ipipeif, fh, + IPIPEIF_PAD_SOURCE_ISIF_SF, + fmt->which); + *format = fmt->format; + ipipeif_try_format(ipipeif, fh, IPIPEIF_PAD_SOURCE_ISIF_SF, + format, fmt->which); + + format = __ipipeif_get_format(ipipeif, fh, + IPIPEIF_PAD_SOURCE_VP, + fmt->which); + *format = fmt->format; + ipipeif_try_format(ipipeif, fh, IPIPEIF_PAD_SOURCE_VP, format, + fmt->which); + } + + return 0; +} + +static int ipipeif_link_validate(struct v4l2_subdev *sd, + struct media_link *link, + struct v4l2_subdev_format *source_fmt, + struct v4l2_subdev_format *sink_fmt) +{ + /* Check if the two ends match */ + if (source_fmt->format.width != sink_fmt->format.width || + source_fmt->format.height != sink_fmt->format.height) + return -EPIPE; + + if (source_fmt->format.code != sink_fmt->format.code) + return -EPIPE; + + return 0; +} + +/* + * ipipeif_init_formats - Initialize formats on all pads + * @sd: ISP IPIPEIF V4L2 subdevice + * @fh: V4L2 subdev file handle + * + * Initialize all pad formats with default values. If fh is not NULL, try + * formats are initialized on the file handle. Otherwise active formats are + * initialized on the device. + */ +static int ipipeif_init_formats(struct v4l2_subdev *sd, + struct v4l2_subdev_fh *fh) +{ + struct v4l2_subdev_format format; + + memset(&format, 0, sizeof(format)); + format.pad = IPIPEIF_PAD_SINK; + format.which = fh ? V4L2_SUBDEV_FORMAT_TRY : V4L2_SUBDEV_FORMAT_ACTIVE; + format.format.code = V4L2_MBUS_FMT_SGRBG10_1X10; + format.format.width = 4096; + format.format.height = 4096; + ipipeif_set_format(sd, fh, &format); + + return 0; +} + +/* V4L2 subdev video operations */ +static const struct v4l2_subdev_video_ops ipipeif_v4l2_video_ops = { + .s_stream = ipipeif_set_stream, +}; + +/* V4L2 subdev pad operations */ +static const struct v4l2_subdev_pad_ops ipipeif_v4l2_pad_ops = { + .enum_mbus_code = ipipeif_enum_mbus_code, + .enum_frame_size = ipipeif_enum_frame_size, + .get_fmt = ipipeif_get_format, + .set_fmt = ipipeif_set_format, + .link_validate = ipipeif_link_validate, +}; + +/* V4L2 subdev operations */ +static const struct v4l2_subdev_ops ipipeif_v4l2_ops = { + .video = &ipipeif_v4l2_video_ops, + .pad = &ipipeif_v4l2_pad_ops, +}; + +/* V4L2 subdev internal operations */ +static const struct v4l2_subdev_internal_ops ipipeif_v4l2_internal_ops = { + .open = ipipeif_init_formats, +}; + +/* ----------------------------------------------------------------------------- + * Media entity operations + */ + +/* + * ipipeif_link_setup - Setup IPIPEIF connections + * @entity: IPIPEIF media entity + * @local: Pad at the local end of the link + * @remote: Pad at the remote end of the link + * @flags: Link flags + * + * return -EINVAL or zero on success + */ +static int ipipeif_link_setup(struct media_entity *entity, + const struct media_pad *local, + const struct media_pad *remote, u32 flags) +{ + struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(entity); + struct iss_ipipeif_device *ipipeif = v4l2_get_subdevdata(sd); + struct iss_device *iss = to_iss_device(ipipeif); + + switch (local->index | media_entity_type(remote->entity)) { + case IPIPEIF_PAD_SINK | MEDIA_ENT_T_V4L2_SUBDEV: + /* Read from the sensor CSI2a or CSI2b. */ + if (!(flags & MEDIA_LNK_FL_ENABLED)) { + ipipeif->input = IPIPEIF_INPUT_NONE; + break; + } + + if (ipipeif->input != IPIPEIF_INPUT_NONE) + return -EBUSY; + + if (remote->entity == &iss->csi2a.subdev.entity) + ipipeif->input = IPIPEIF_INPUT_CSI2A; + else if (remote->entity == &iss->csi2b.subdev.entity) + ipipeif->input = IPIPEIF_INPUT_CSI2B; + + break; + + case IPIPEIF_PAD_SOURCE_ISIF_SF | MEDIA_ENT_T_DEVNODE: + /* Write to memory */ + if (flags & MEDIA_LNK_FL_ENABLED) { + if (ipipeif->output & ~IPIPEIF_OUTPUT_MEMORY) + return -EBUSY; + ipipeif->output |= IPIPEIF_OUTPUT_MEMORY; + } else { + ipipeif->output &= ~IPIPEIF_OUTPUT_MEMORY; + } + break; + + case IPIPEIF_PAD_SOURCE_VP | MEDIA_ENT_T_V4L2_SUBDEV: + /* Send to IPIPE/RESIZER */ + if (flags & MEDIA_LNK_FL_ENABLED) { + if (ipipeif->output & ~IPIPEIF_OUTPUT_VP) + return -EBUSY; + ipipeif->output |= IPIPEIF_OUTPUT_VP; + } else { + ipipeif->output &= ~IPIPEIF_OUTPUT_VP; + } + break; + + default: + return -EINVAL; + } + + return 0; +} + +/* media operations */ +static const struct media_entity_operations ipipeif_media_ops = { + .link_setup = ipipeif_link_setup, + .link_validate = v4l2_subdev_link_validate, +}; + +/* + * ipipeif_init_entities - Initialize V4L2 subdev and media entity + * @ipipeif: ISS ISP IPIPEIF module + * + * Return 0 on success and a negative error code on failure. + */ +static int ipipeif_init_entities(struct iss_ipipeif_device *ipipeif) +{ + struct v4l2_subdev *sd = &ipipeif->subdev; + struct media_pad *pads = ipipeif->pads; + struct media_entity *me = &sd->entity; + int ret; + + ipipeif->input = IPIPEIF_INPUT_NONE; + + v4l2_subdev_init(sd, &ipipeif_v4l2_ops); + sd->internal_ops = &ipipeif_v4l2_internal_ops; + strlcpy(sd->name, "OMAP4 ISS ISP IPIPEIF", sizeof(sd->name)); + sd->grp_id = 1 << 16; /* group ID for iss subdevs */ + v4l2_set_subdevdata(sd, ipipeif); + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + + pads[IPIPEIF_PAD_SINK].flags = MEDIA_PAD_FL_SINK; + pads[IPIPEIF_PAD_SOURCE_ISIF_SF].flags = MEDIA_PAD_FL_SOURCE; + pads[IPIPEIF_PAD_SOURCE_VP].flags = MEDIA_PAD_FL_SOURCE; + + me->ops = &ipipeif_media_ops; + ret = media_entity_init(me, IPIPEIF_PADS_NUM, pads, 0); + if (ret < 0) + return ret; + + ipipeif_init_formats(sd, NULL); + + ipipeif->video_out.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + ipipeif->video_out.ops = &ipipeif_video_ops; + ipipeif->video_out.iss = to_iss_device(ipipeif); + ipipeif->video_out.capture_mem = PAGE_ALIGN(4096 * 4096) * 3; + ipipeif->video_out.bpl_alignment = 32; + ipipeif->video_out.bpl_zero_padding = 1; + ipipeif->video_out.bpl_max = 0x1ffe0; + + ret = omap4iss_video_init(&ipipeif->video_out, "ISP IPIPEIF"); + if (ret < 0) + return ret; + + /* Connect the IPIPEIF subdev to the video node. */ + ret = media_entity_create_link(&ipipeif->subdev.entity, + IPIPEIF_PAD_SOURCE_ISIF_SF, + &ipipeif->video_out.video.entity, 0, 0); + if (ret < 0) + return ret; + + return 0; +} + +void omap4iss_ipipeif_unregister_entities(struct iss_ipipeif_device *ipipeif) +{ + media_entity_cleanup(&ipipeif->subdev.entity); + + v4l2_device_unregister_subdev(&ipipeif->subdev); + omap4iss_video_unregister(&ipipeif->video_out); +} + +int omap4iss_ipipeif_register_entities(struct iss_ipipeif_device *ipipeif, + struct v4l2_device *vdev) +{ + int ret; + + /* Register the subdev and video node. */ + ret = v4l2_device_register_subdev(vdev, &ipipeif->subdev); + if (ret < 0) + goto error; + + ret = omap4iss_video_register(&ipipeif->video_out, vdev); + if (ret < 0) + goto error; + + return 0; + +error: + omap4iss_ipipeif_unregister_entities(ipipeif); + return ret; +} + +/* ----------------------------------------------------------------------------- + * ISP IPIPEIF initialisation and cleanup + */ + +/* + * omap4iss_ipipeif_init - IPIPEIF module initialization. + * @iss: Device pointer specific to the OMAP4 ISS. + * + * TODO: Get the initialisation values from platform data. + * + * Return 0 on success or a negative error code otherwise. + */ +int omap4iss_ipipeif_init(struct iss_device *iss) +{ + struct iss_ipipeif_device *ipipeif = &iss->ipipeif; + + ipipeif->state = ISS_PIPELINE_STREAM_STOPPED; + init_waitqueue_head(&ipipeif->wait); + + return ipipeif_init_entities(ipipeif); +} + +/* + * omap4iss_ipipeif_cleanup - IPIPEIF module cleanup. + * @iss: Device pointer specific to the OMAP4 ISS. + */ +void omap4iss_ipipeif_cleanup(struct iss_device *iss) +{ + /* FIXME: are you sure there's nothing to do? */ +} diff --git a/drivers/staging/media/omap4iss/iss_ipipeif.h b/drivers/staging/media/omap4iss/iss_ipipeif.h new file mode 100644 index 00000000000..cbdccb982ee --- /dev/null +++ b/drivers/staging/media/omap4iss/iss_ipipeif.h @@ -0,0 +1,92 @@ +/* + * TI OMAP4 ISS V4L2 Driver - ISP IPIPEIF module + * + * Copyright (C) 2012 Texas Instruments, Inc. + * + * Author: Sergio Aguirre <sergio.a.aguirre@gmail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#ifndef OMAP4_ISS_IPIPEIF_H +#define OMAP4_ISS_IPIPEIF_H + +#include "iss_video.h" + +enum ipipeif_input_entity { + IPIPEIF_INPUT_NONE, + IPIPEIF_INPUT_CSI2A, + IPIPEIF_INPUT_CSI2B +}; + +#define IPIPEIF_OUTPUT_MEMORY (1 << 0) +#define IPIPEIF_OUTPUT_VP (1 << 1) + +/* Sink and source IPIPEIF pads */ +#define IPIPEIF_PAD_SINK 0 +#define IPIPEIF_PAD_SOURCE_ISIF_SF 1 +#define IPIPEIF_PAD_SOURCE_VP 2 +#define IPIPEIF_PADS_NUM 3 + +/* + * struct iss_ipipeif_device - Structure for the IPIPEIF module to store its own + * information + * @subdev: V4L2 subdevice + * @pads: Sink and source media entity pads + * @formats: Active video formats + * @input: Active input + * @output: Active outputs + * @video_out: Output video node + * @error: A hardware error occurred during capture + * @alaw: A-law compression enabled (1) or disabled (0) + * @lpf: Low pass filter enabled (1) or disabled (0) + * @obclamp: Optical-black clamp enabled (1) or disabled (0) + * @fpc_en: Faulty pixels correction enabled (1) or disabled (0) + * @blcomp: Black level compensation configuration + * @clamp: Optical-black or digital clamp configuration + * @fpc: Faulty pixels correction configuration + * @lsc: Lens shading compensation configuration + * @update: Bitmask of controls to update during the next interrupt + * @shadow_update: Controls update in progress by userspace + * @syncif: Interface synchronization configuration + * @vpcfg: Video port configuration + * @underrun: A buffer underrun occurred and a new buffer has been queued + * @state: Streaming state + * @lock: Serializes shadow_update with interrupt handler + * @wait: Wait queue used to stop the module + * @stopping: Stopping state + * @ioctl_lock: Serializes ioctl calls and LSC requests freeing + */ +struct iss_ipipeif_device { + struct v4l2_subdev subdev; + struct media_pad pads[IPIPEIF_PADS_NUM]; + struct v4l2_mbus_framefmt formats[IPIPEIF_PADS_NUM]; + + enum ipipeif_input_entity input; + unsigned int output; + struct iss_video video_out; + unsigned int error; + + enum iss_pipeline_stream_state state; + wait_queue_head_t wait; + atomic_t stopping; +}; + +struct iss_device; + +int omap4iss_ipipeif_init(struct iss_device *iss); +void omap4iss_ipipeif_cleanup(struct iss_device *iss); +int omap4iss_ipipeif_register_entities(struct iss_ipipeif_device *ipipeif, + struct v4l2_device *vdev); +void omap4iss_ipipeif_unregister_entities(struct iss_ipipeif_device *ipipeif); + +int omap4iss_ipipeif_busy(struct iss_ipipeif_device *ipipeif); +void omap4iss_ipipeif_isr(struct iss_ipipeif_device *ipipeif, u32 events); +void omap4iss_ipipeif_restore_context(struct iss_device *iss); +void omap4iss_ipipeif_max_rate(struct iss_ipipeif_device *ipipeif, + unsigned int *max_rate); + +#endif /* OMAP4_ISS_IPIPEIF_H */ diff --git a/drivers/staging/media/omap4iss/iss_regs.h b/drivers/staging/media/omap4iss/iss_regs.h new file mode 100644 index 00000000000..efd0291a86f --- /dev/null +++ b/drivers/staging/media/omap4iss/iss_regs.h @@ -0,0 +1,901 @@ +/* + * TI OMAP4 ISS V4L2 Driver - Register defines + * + * Copyright (C) 2012 Texas Instruments. + * + * Author: Sergio Aguirre <sergio.a.aguirre@gmail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#ifndef _OMAP4_ISS_REGS_H_ +#define _OMAP4_ISS_REGS_H_ + +/* ISS */ +#define ISS_HL_REVISION 0x0 + +#define ISS_HL_SYSCONFIG 0x10 +#define ISS_HL_SYSCONFIG_IDLEMODE_SHIFT 2 +#define ISS_HL_SYSCONFIG_IDLEMODE_FORCEIDLE 0x0 +#define ISS_HL_SYSCONFIG_IDLEMODE_NOIDLE 0x1 +#define ISS_HL_SYSCONFIG_IDLEMODE_SMARTIDLE 0x2 +#define ISS_HL_SYSCONFIG_SOFTRESET (1 << 0) + +#define ISS_HL_IRQSTATUS_RAW(i) (0x20 + (0x10 * (i))) +#define ISS_HL_IRQSTATUS(i) (0x24 + (0x10 * (i))) +#define ISS_HL_IRQENABLE_SET(i) (0x28 + (0x10 * (i))) +#define ISS_HL_IRQENABLE_CLR(i) (0x2c + (0x10 * (i))) + +#define ISS_HL_IRQ_HS_VS (1 << 17) +#define ISS_HL_IRQ_SIMCOP(i) (1 << (12 + (i))) +#define ISS_HL_IRQ_BTE (1 << 11) +#define ISS_HL_IRQ_CBUFF (1 << 10) +#define ISS_HL_IRQ_CCP2(i) (1 << ((i) > 3 ? 16 : 14 + (i))) +#define ISS_HL_IRQ_CSIB (1 << 5) +#define ISS_HL_IRQ_CSIA (1 << 4) +#define ISS_HL_IRQ_ISP(i) (1 << (i)) + +#define ISS_CTRL 0x80 +#define ISS_CTRL_CLK_DIV_MASK (3 << 4) +#define ISS_CTRL_INPUT_SEL_MASK (3 << 2) +#define ISS_CTRL_INPUT_SEL_CSI2A (0 << 2) +#define ISS_CTRL_INPUT_SEL_CSI2B (1 << 2) +#define ISS_CTRL_SYNC_DETECT_VS_RAISING (3 << 0) + +#define ISS_CLKCTRL 0x84 +#define ISS_CLKCTRL_VPORT2_CLK (1 << 30) +#define ISS_CLKCTRL_VPORT1_CLK (1 << 29) +#define ISS_CLKCTRL_VPORT0_CLK (1 << 28) +#define ISS_CLKCTRL_CCP2 (1 << 4) +#define ISS_CLKCTRL_CSI2_B (1 << 3) +#define ISS_CLKCTRL_CSI2_A (1 << 2) +#define ISS_CLKCTRL_ISP (1 << 1) +#define ISS_CLKCTRL_SIMCOP (1 << 0) + +#define ISS_CLKSTAT 0x88 +#define ISS_CLKSTAT_VPORT2_CLK (1 << 30) +#define ISS_CLKSTAT_VPORT1_CLK (1 << 29) +#define ISS_CLKSTAT_VPORT0_CLK (1 << 28) +#define ISS_CLKSTAT_CCP2 (1 << 4) +#define ISS_CLKSTAT_CSI2_B (1 << 3) +#define ISS_CLKSTAT_CSI2_A (1 << 2) +#define ISS_CLKSTAT_ISP (1 << 1) +#define ISS_CLKSTAT_SIMCOP (1 << 0) + +#define ISS_PM_STATUS 0x8c +#define ISS_PM_STATUS_CBUFF_PM_MASK (3 << 12) +#define ISS_PM_STATUS_BTE_PM_MASK (3 << 10) +#define ISS_PM_STATUS_SIMCOP_PM_MASK (3 << 8) +#define ISS_PM_STATUS_ISP_PM_MASK (3 << 6) +#define ISS_PM_STATUS_CCP2_PM_MASK (3 << 4) +#define ISS_PM_STATUS_CSI2_B_PM_MASK (3 << 2) +#define ISS_PM_STATUS_CSI2_A_PM_MASK (3 << 0) + +#define REGISTER0 0x0 +#define REGISTER0_HSCLOCKCONFIG (1 << 24) +#define REGISTER0_THS_TERM_MASK (0xff << 8) +#define REGISTER0_THS_TERM_SHIFT 8 +#define REGISTER0_THS_SETTLE_MASK (0xff << 0) +#define REGISTER0_THS_SETTLE_SHIFT 0 + +#define REGISTER1 0x4 +#define REGISTER1_RESET_DONE_CTRLCLK (1 << 29) +#define REGISTER1_CLOCK_MISS_DETECTOR_STATUS (1 << 25) +#define REGISTER1_TCLK_TERM_MASK (0x3f << 18) +#define REGISTER1_TCLK_TERM_SHIFT 18 +#define REGISTER1_DPHY_HS_SYNC_PATTERN_SHIFT 10 +#define REGISTER1_CTRLCLK_DIV_FACTOR_MASK (0x3 << 8) +#define REGISTER1_CTRLCLK_DIV_FACTOR_SHIFT 8 +#define REGISTER1_TCLK_SETTLE_MASK (0xff << 0) +#define REGISTER1_TCLK_SETTLE_SHIFT 0 + +#define REGISTER2 0x8 + +#define CSI2_SYSCONFIG 0x10 +#define CSI2_SYSCONFIG_MSTANDBY_MODE_MASK (3 << 12) +#define CSI2_SYSCONFIG_MSTANDBY_MODE_FORCE (0 << 12) +#define CSI2_SYSCONFIG_MSTANDBY_MODE_NO (1 << 12) +#define CSI2_SYSCONFIG_MSTANDBY_MODE_SMART (2 << 12) +#define CSI2_SYSCONFIG_SOFT_RESET (1 << 1) +#define CSI2_SYSCONFIG_AUTO_IDLE (1 << 0) + +#define CSI2_SYSSTATUS 0x14 +#define CSI2_SYSSTATUS_RESET_DONE (1 << 0) + +#define CSI2_IRQSTATUS 0x18 +#define CSI2_IRQENABLE 0x1c + +/* Shared bits across CSI2_IRQENABLE and IRQSTATUS */ + +#define CSI2_IRQ_OCP_ERR (1 << 14) +#define CSI2_IRQ_SHORT_PACKET (1 << 13) +#define CSI2_IRQ_ECC_CORRECTION (1 << 12) +#define CSI2_IRQ_ECC_NO_CORRECTION (1 << 11) +#define CSI2_IRQ_COMPLEXIO_ERR (1 << 9) +#define CSI2_IRQ_FIFO_OVF (1 << 8) +#define CSI2_IRQ_CONTEXT0 (1 << 0) + +#define CSI2_CTRL 0x40 +#define CSI2_CTRL_MFLAG_LEVH_MASK (7 << 20) +#define CSI2_CTRL_MFLAG_LEVH_SHIFT 20 +#define CSI2_CTRL_MFLAG_LEVL_MASK (7 << 17) +#define CSI2_CTRL_MFLAG_LEVL_SHIFT 17 +#define CSI2_CTRL_BURST_SIZE_EXPAND (1 << 16) +#define CSI2_CTRL_VP_CLK_EN (1 << 15) +#define CSI2_CTRL_NON_POSTED_WRITE (1 << 13) +#define CSI2_CTRL_VP_ONLY_EN (1 << 11) +#define CSI2_CTRL_VP_OUT_CTRL_MASK (3 << 8) +#define CSI2_CTRL_VP_OUT_CTRL_SHIFT 8 +#define CSI2_CTRL_DBG_EN (1 << 7) +#define CSI2_CTRL_BURST_SIZE_MASK (3 << 5) +#define CSI2_CTRL_ENDIANNESS (1 << 4) +#define CSI2_CTRL_FRAME (1 << 3) +#define CSI2_CTRL_ECC_EN (1 << 2) +#define CSI2_CTRL_IF_EN (1 << 0) + +#define CSI2_DBG_H 0x44 + +#define CSI2_COMPLEXIO_CFG 0x50 +#define CSI2_COMPLEXIO_CFG_RESET_CTRL (1 << 30) +#define CSI2_COMPLEXIO_CFG_RESET_DONE (1 << 29) +#define CSI2_COMPLEXIO_CFG_PWD_CMD_MASK (3 << 27) +#define CSI2_COMPLEXIO_CFG_PWD_CMD_OFF (0 << 27) +#define CSI2_COMPLEXIO_CFG_PWD_CMD_ON (1 << 27) +#define CSI2_COMPLEXIO_CFG_PWD_CMD_ULP (2 << 27) +#define CSI2_COMPLEXIO_CFG_PWD_STATUS_MASK (3 << 25) +#define CSI2_COMPLEXIO_CFG_PWD_STATUS_OFF (0 << 25) +#define CSI2_COMPLEXIO_CFG_PWD_STATUS_ON (1 << 25) +#define CSI2_COMPLEXIO_CFG_PWD_STATUS_ULP (2 << 25) +#define CSI2_COMPLEXIO_CFG_PWR_AUTO (1 << 24) +#define CSI2_COMPLEXIO_CFG_DATA_POL(i) (1 << (((i) * 4) + 3)) +#define CSI2_COMPLEXIO_CFG_DATA_POSITION_MASK(i) (7 << ((i) * 4)) +#define CSI2_COMPLEXIO_CFG_DATA_POSITION_SHIFT(i) ((i) * 4) +#define CSI2_COMPLEXIO_CFG_CLOCK_POL (1 << 3) +#define CSI2_COMPLEXIO_CFG_CLOCK_POSITION_MASK (7 << 0) +#define CSI2_COMPLEXIO_CFG_CLOCK_POSITION_SHIFT 0 + +#define CSI2_COMPLEXIO_IRQSTATUS 0x54 + +#define CSI2_SHORT_PACKET 0x5c + +#define CSI2_COMPLEXIO_IRQENABLE 0x60 + +/* Shared bits across CSI2_COMPLEXIO_IRQENABLE and IRQSTATUS */ +#define CSI2_COMPLEXIO_IRQ_STATEALLULPMEXIT (1 << 26) +#define CSI2_COMPLEXIO_IRQ_STATEALLULPMENTER (1 << 25) +#define CSI2_COMPLEXIO_IRQ_STATEULPM5 (1 << 24) +#define CSI2_COMPLEXIO_IRQ_STATEULPM4 (1 << 23) +#define CSI2_COMPLEXIO_IRQ_STATEULPM3 (1 << 22) +#define CSI2_COMPLEXIO_IRQ_STATEULPM2 (1 << 21) +#define CSI2_COMPLEXIO_IRQ_STATEULPM1 (1 << 20) +#define CSI2_COMPLEXIO_IRQ_ERRCONTROL5 (1 << 19) +#define CSI2_COMPLEXIO_IRQ_ERRCONTROL4 (1 << 18) +#define CSI2_COMPLEXIO_IRQ_ERRCONTROL3 (1 << 17) +#define CSI2_COMPLEXIO_IRQ_ERRCONTROL2 (1 << 16) +#define CSI2_COMPLEXIO_IRQ_ERRCONTROL1 (1 << 15) +#define CSI2_COMPLEXIO_IRQ_ERRESC5 (1 << 14) +#define CSI2_COMPLEXIO_IRQ_ERRESC4 (1 << 13) +#define CSI2_COMPLEXIO_IRQ_ERRESC3 (1 << 12) +#define CSI2_COMPLEXIO_IRQ_ERRESC2 (1 << 11) +#define CSI2_COMPLEXIO_IRQ_ERRESC1 (1 << 10) +#define CSI2_COMPLEXIO_IRQ_ERRSOTSYNCHS5 (1 << 9) +#define CSI2_COMPLEXIO_IRQ_ERRSOTSYNCHS4 (1 << 8) +#define CSI2_COMPLEXIO_IRQ_ERRSOTSYNCHS3 (1 << 7) +#define CSI2_COMPLEXIO_IRQ_ERRSOTSYNCHS2 (1 << 6) +#define CSI2_COMPLEXIO_IRQ_ERRSOTSYNCHS1 (1 << 5) +#define CSI2_COMPLEXIO_IRQ_ERRSOTHS5 (1 << 4) +#define CSI2_COMPLEXIO_IRQ_ERRSOTHS4 (1 << 3) +#define CSI2_COMPLEXIO_IRQ_ERRSOTHS3 (1 << 2) +#define CSI2_COMPLEXIO_IRQ_ERRSOTHS2 (1 << 1) +#define CSI2_COMPLEXIO_IRQ_ERRSOTHS1 (1 << 0) + +#define CSI2_DBG_P 0x68 + +#define CSI2_TIMING 0x6c +#define CSI2_TIMING_FORCE_RX_MODE_IO1 (1 << 15) +#define CSI2_TIMING_STOP_STATE_X16_IO1 (1 << 14) +#define CSI2_TIMING_STOP_STATE_X4_IO1 (1 << 13) +#define CSI2_TIMING_STOP_STATE_COUNTER_IO1_MASK (0x1fff << 0) +#define CSI2_TIMING_STOP_STATE_COUNTER_IO1_SHIFT 0 + +#define CSI2_CTX_CTRL1(i) (0x70 + (0x20 * i)) +#define CSI2_CTX_CTRL1_GENERIC (1 << 30) +#define CSI2_CTX_CTRL1_TRANSCODE (0xf << 24) +#define CSI2_CTX_CTRL1_FEC_NUMBER_MASK (0xff << 16) +#define CSI2_CTX_CTRL1_COUNT_MASK (0xff << 8) +#define CSI2_CTX_CTRL1_COUNT_SHIFT 8 +#define CSI2_CTX_CTRL1_EOF_EN (1 << 7) +#define CSI2_CTX_CTRL1_EOL_EN (1 << 6) +#define CSI2_CTX_CTRL1_CS_EN (1 << 5) +#define CSI2_CTX_CTRL1_COUNT_UNLOCK (1 << 4) +#define CSI2_CTX_CTRL1_PING_PONG (1 << 3) +#define CSI2_CTX_CTRL1_CTX_EN (1 << 0) + +#define CSI2_CTX_CTRL2(i) (0x74 + (0x20 * i)) +#define CSI2_CTX_CTRL2_USER_DEF_MAP_SHIFT 13 +#define CSI2_CTX_CTRL2_USER_DEF_MAP_MASK \ + (0x3 << CSI2_CTX_CTRL2_USER_DEF_MAP_SHIFT) +#define CSI2_CTX_CTRL2_VIRTUAL_ID_MASK (3 << 11) +#define CSI2_CTX_CTRL2_VIRTUAL_ID_SHIFT 11 +#define CSI2_CTX_CTRL2_DPCM_PRED (1 << 10) +#define CSI2_CTX_CTRL2_FORMAT_MASK (0x3ff << 0) +#define CSI2_CTX_CTRL2_FORMAT_SHIFT 0 + +#define CSI2_CTX_DAT_OFST(i) (0x78 + (0x20 * i)) +#define CSI2_CTX_DAT_OFST_MASK (0xfff << 5) + +#define CSI2_CTX_PING_ADDR(i) (0x7c + (0x20 * i)) +#define CSI2_CTX_PING_ADDR_MASK 0xffffffe0 + +#define CSI2_CTX_PONG_ADDR(i) (0x80 + (0x20 * i)) +#define CSI2_CTX_PONG_ADDR_MASK CSI2_CTX_PING_ADDR_MASK + +#define CSI2_CTX_IRQENABLE(i) (0x84 + (0x20 * i)) +#define CSI2_CTX_IRQSTATUS(i) (0x88 + (0x20 * i)) + +#define CSI2_CTX_CTRL3(i) (0x8c + (0x20 * i)) +#define CSI2_CTX_CTRL3_ALPHA_SHIFT 5 +#define CSI2_CTX_CTRL3_ALPHA_MASK \ + (0x3fff << CSI2_CTX_CTRL3_ALPHA_SHIFT) + +/* Shared bits across CSI2_CTX_IRQENABLE and IRQSTATUS */ +#define CSI2_CTX_IRQ_ECC_CORRECTION (1 << 8) +#define CSI2_CTX_IRQ_LINE_NUMBER (1 << 7) +#define CSI2_CTX_IRQ_FRAME_NUMBER (1 << 6) +#define CSI2_CTX_IRQ_CS (1 << 5) +#define CSI2_CTX_IRQ_LE (1 << 3) +#define CSI2_CTX_IRQ_LS (1 << 2) +#define CSI2_CTX_IRQ_FE (1 << 1) +#define CSI2_CTX_IRQ_FS (1 << 0) + +/* ISS BTE */ +#define BTE_CTRL (0x0030) +#define BTE_CTRL_BW_LIMITER_MASK (0x3ff << 22) +#define BTE_CTRL_BW_LIMITER_SHIFT 22 + +/* ISS ISP_SYS1 */ +#define ISP5_REVISION (0x0000) +#define ISP5_SYSCONFIG (0x0010) +#define ISP5_SYSCONFIG_STANDBYMODE_MASK (3 << 4) +#define ISP5_SYSCONFIG_STANDBYMODE_FORCE (0 << 4) +#define ISP5_SYSCONFIG_STANDBYMODE_NO (1 << 4) +#define ISP5_SYSCONFIG_STANDBYMODE_SMART (2 << 4) +#define ISP5_SYSCONFIG_SOFTRESET (1 << 1) + +#define ISP5_IRQSTATUS(i) (0x0028 + (0x10 * (i))) +#define ISP5_IRQENABLE_SET(i) (0x002c + (0x10 * (i))) +#define ISP5_IRQENABLE_CLR(i) (0x0030 + (0x10 * (i))) + +/* Bits shared for ISP5_IRQ* registers */ +#define ISP5_IRQ_OCP_ERR (1 << 31) +#define ISP5_IRQ_IPIPE_INT_DPC_RNEW1 (1 << 29) +#define ISP5_IRQ_IPIPE_INT_DPC_RNEW0 (1 << 28) +#define ISP5_IRQ_IPIPE_INT_DPC_INIT (1 << 27) +#define ISP5_IRQ_IPIPE_INT_EOF (1 << 25) +#define ISP5_IRQ_H3A_INT_EOF (1 << 24) +#define ISP5_IRQ_RSZ_INT_EOF1 (1 << 23) +#define ISP5_IRQ_RSZ_INT_EOF0 (1 << 22) +#define ISP5_IRQ_RSZ_FIFO_IN_BLK_ERR (1 << 19) +#define ISP5_IRQ_RSZ_FIFO_OVF (1 << 18) +#define ISP5_IRQ_RSZ_INT_CYC_RSZB (1 << 17) +#define ISP5_IRQ_RSZ_INT_CYC_RSZA (1 << 16) +#define ISP5_IRQ_RSZ_INT_DMA (1 << 15) +#define ISP5_IRQ_RSZ_INT_LAST_PIX (1 << 14) +#define ISP5_IRQ_RSZ_INT_REG (1 << 13) +#define ISP5_IRQ_H3A_INT (1 << 12) +#define ISP5_IRQ_AF_INT (1 << 11) +#define ISP5_IRQ_AEW_INT (1 << 10) +#define ISP5_IRQ_IPIPEIF_IRQ (1 << 9) +#define ISP5_IRQ_IPIPE_INT_HST (1 << 8) +#define ISP5_IRQ_IPIPE_INT_BSC (1 << 7) +#define ISP5_IRQ_IPIPE_INT_DMA (1 << 6) +#define ISP5_IRQ_IPIPE_INT_LAST_PIX (1 << 5) +#define ISP5_IRQ_IPIPE_INT_REG (1 << 4) +#define ISP5_IRQ_ISIF_INT(i) (1 << (i)) + +#define ISP5_CTRL (0x006c) +#define ISP5_CTRL_MSTANDBY (1 << 24) +#define ISP5_CTRL_VD_PULSE_EXT (1 << 23) +#define ISP5_CTRL_MSTANDBY_WAIT (1 << 20) +#define ISP5_CTRL_BL_CLK_ENABLE (1 << 15) +#define ISP5_CTRL_ISIF_CLK_ENABLE (1 << 14) +#define ISP5_CTRL_H3A_CLK_ENABLE (1 << 13) +#define ISP5_CTRL_RSZ_CLK_ENABLE (1 << 12) +#define ISP5_CTRL_IPIPE_CLK_ENABLE (1 << 11) +#define ISP5_CTRL_IPIPEIF_CLK_ENABLE (1 << 10) +#define ISP5_CTRL_SYNC_ENABLE (1 << 9) +#define ISP5_CTRL_PSYNC_CLK_SEL (1 << 8) + +/* ISS ISP ISIF register offsets */ +#define ISIF_SYNCEN (0x0000) +#define ISIF_SYNCEN_DWEN (1 << 1) +#define ISIF_SYNCEN_SYEN (1 << 0) + +#define ISIF_MODESET (0x0004) +#define ISIF_MODESET_INPMOD_MASK (3 << 12) +#define ISIF_MODESET_INPMOD_RAW (0 << 12) +#define ISIF_MODESET_INPMOD_YCBCR16 (1 << 12) +#define ISIF_MODESET_INPMOD_YCBCR8 (2 << 12) +#define ISIF_MODESET_CCDW_MASK (7 << 8) +#define ISIF_MODESET_CCDW_2BIT (2 << 8) +#define ISIF_MODESET_CCDMD (1 << 7) +#define ISIF_MODESET_SWEN (1 << 5) +#define ISIF_MODESET_HDPOL (1 << 3) +#define ISIF_MODESET_VDPOL (1 << 2) + +#define ISIF_SPH (0x0018) +#define ISIF_SPH_MASK (0x7fff) + +#define ISIF_LNH (0x001c) +#define ISIF_LNH_MASK (0x7fff) + +#define ISIF_LNV (0x0028) +#define ISIF_LNV_MASK (0x7fff) + +#define ISIF_HSIZE (0x0034) +#define ISIF_HSIZE_ADCR (1 << 12) +#define ISIF_HSIZE_HSIZE_MASK (0xfff) + +#define ISIF_CADU (0x003c) +#define ISIF_CADU_MASK (0x7ff) + +#define ISIF_CADL (0x0040) +#define ISIF_CADL_MASK (0xffff) + +#define ISIF_CCOLP (0x004c) +#define ISIF_CCOLP_CP0_F0_R (0 << 6) +#define ISIF_CCOLP_CP0_F0_GR (1 << 6) +#define ISIF_CCOLP_CP0_F0_B (3 << 6) +#define ISIF_CCOLP_CP0_F0_GB (2 << 6) +#define ISIF_CCOLP_CP1_F0_R (0 << 4) +#define ISIF_CCOLP_CP1_F0_GR (1 << 4) +#define ISIF_CCOLP_CP1_F0_B (3 << 4) +#define ISIF_CCOLP_CP1_F0_GB (2 << 4) +#define ISIF_CCOLP_CP2_F0_R (0 << 2) +#define ISIF_CCOLP_CP2_F0_GR (1 << 2) +#define ISIF_CCOLP_CP2_F0_B (3 << 2) +#define ISIF_CCOLP_CP2_F0_GB (2 << 2) +#define ISIF_CCOLP_CP3_F0_R (0 << 0) +#define ISIF_CCOLP_CP3_F0_GR (1 << 0) +#define ISIF_CCOLP_CP3_F0_B (3 << 0) +#define ISIF_CCOLP_CP3_F0_GB (2 << 0) + +#define ISIF_VDINT(i) (0x0070 + (i) * 4) +#define ISIF_VDINT_MASK (0x7fff) + +#define ISIF_CGAMMAWD (0x0080) +#define ISIF_CGAMMAWD_GWDI_MASK (0xf << 1) +#define ISIF_CGAMMAWD_GWDI(bpp) ((16 - (bpp)) << 1) + +#define ISIF_CCDCFG (0x0088) +#define ISIF_CCDCFG_Y8POS (1 << 11) + +/* ISS ISP IPIPEIF register offsets */ +#define IPIPEIF_ENABLE (0x0000) + +#define IPIPEIF_CFG1 (0x0004) +#define IPIPEIF_CFG1_INPSRC1_MASK (3 << 14) +#define IPIPEIF_CFG1_INPSRC1_VPORT_RAW (0 << 14) +#define IPIPEIF_CFG1_INPSRC1_SDRAM_RAW (1 << 14) +#define IPIPEIF_CFG1_INPSRC1_ISIF_DARKFM (2 << 14) +#define IPIPEIF_CFG1_INPSRC1_SDRAM_YUV (3 << 14) +#define IPIPEIF_CFG1_INPSRC2_MASK (3 << 2) +#define IPIPEIF_CFG1_INPSRC2_ISIF (0 << 2) +#define IPIPEIF_CFG1_INPSRC2_SDRAM_RAW (1 << 2) +#define IPIPEIF_CFG1_INPSRC2_ISIF_DARKFM (2 << 2) +#define IPIPEIF_CFG1_INPSRC2_SDRAM_YUV (3 << 2) + +#define IPIPEIF_CFG2 (0x0030) +#define IPIPEIF_CFG2_YUV8P (1 << 7) +#define IPIPEIF_CFG2_YUV8 (1 << 6) +#define IPIPEIF_CFG2_YUV16 (1 << 3) +#define IPIPEIF_CFG2_VDPOL (1 << 2) +#define IPIPEIF_CFG2_HDPOL (1 << 1) +#define IPIPEIF_CFG2_INTSW (1 << 0) + +#define IPIPEIF_CLKDIV (0x0040) + +/* ISS ISP IPIPE register offsets */ +#define IPIPE_SRC_EN (0x0000) +#define IPIPE_SRC_EN_EN (1 << 0) + +#define IPIPE_SRC_MODE (0x0004) +#define IPIPE_SRC_MODE_WRT (1 << 1) +#define IPIPE_SRC_MODE_OST (1 << 0) + +#define IPIPE_SRC_FMT (0x0008) +#define IPIPE_SRC_FMT_RAW2YUV (0 << 0) +#define IPIPE_SRC_FMT_RAW2RAW (1 << 0) +#define IPIPE_SRC_FMT_RAW2STATS (2 << 0) +#define IPIPE_SRC_FMT_YUV2YUV (3 << 0) + +#define IPIPE_SRC_COL (0x000c) +#define IPIPE_SRC_COL_OO_R (0 << 6) +#define IPIPE_SRC_COL_OO_GR (1 << 6) +#define IPIPE_SRC_COL_OO_B (3 << 6) +#define IPIPE_SRC_COL_OO_GB (2 << 6) +#define IPIPE_SRC_COL_OE_R (0 << 4) +#define IPIPE_SRC_COL_OE_GR (1 << 4) +#define IPIPE_SRC_COL_OE_B (3 << 4) +#define IPIPE_SRC_COL_OE_GB (2 << 4) +#define IPIPE_SRC_COL_EO_R (0 << 2) +#define IPIPE_SRC_COL_EO_GR (1 << 2) +#define IPIPE_SRC_COL_EO_B (3 << 2) +#define IPIPE_SRC_COL_EO_GB (2 << 2) +#define IPIPE_SRC_COL_EE_R (0 << 0) +#define IPIPE_SRC_COL_EE_GR (1 << 0) +#define IPIPE_SRC_COL_EE_B (3 << 0) +#define IPIPE_SRC_COL_EE_GB (2 << 0) + +#define IPIPE_SRC_VPS (0x0010) +#define IPIPE_SRC_VPS_MASK (0xffff) + +#define IPIPE_SRC_VSZ (0x0014) +#define IPIPE_SRC_VSZ_MASK (0x1fff) + +#define IPIPE_SRC_HPS (0x0018) +#define IPIPE_SRC_HPS_MASK (0xffff) + +#define IPIPE_SRC_HSZ (0x001c) +#define IPIPE_SRC_HSZ_MASK (0x1ffe) + +#define IPIPE_SEL_SBU (0x0020) + +#define IPIPE_SRC_STA (0x0024) + +#define IPIPE_GCK_MMR (0x0028) +#define IPIPE_GCK_MMR_REG (1 << 0) + +#define IPIPE_GCK_PIX (0x002c) +#define IPIPE_GCK_PIX_G3 (1 << 3) +#define IPIPE_GCK_PIX_G2 (1 << 2) +#define IPIPE_GCK_PIX_G1 (1 << 1) +#define IPIPE_GCK_PIX_G0 (1 << 0) + +#define IPIPE_DPC_LUT_EN (0x0034) +#define IPIPE_DPC_LUT_SEL (0x0038) +#define IPIPE_DPC_LUT_ADR (0x003c) +#define IPIPE_DPC_LUT_SIZ (0x0040) + +#define IPIPE_DPC_OTF_EN (0x0044) +#define IPIPE_DPC_OTF_TYP (0x0048) +#define IPIPE_DPC_OTF_2_D_THR_R (0x004c) +#define IPIPE_DPC_OTF_2_D_THR_GR (0x0050) +#define IPIPE_DPC_OTF_2_D_THR_GB (0x0054) +#define IPIPE_DPC_OTF_2_D_THR_B (0x0058) +#define IPIPE_DPC_OTF_2_C_THR_R (0x005c) +#define IPIPE_DPC_OTF_2_C_THR_GR (0x0060) +#define IPIPE_DPC_OTF_2_C_THR_GB (0x0064) +#define IPIPE_DPC_OTF_2_C_THR_B (0x0068) +#define IPIPE_DPC_OTF_3_SHF (0x006c) +#define IPIPE_DPC_OTF_3_D_THR (0x0070) +#define IPIPE_DPC_OTF_3_D_SPL (0x0074) +#define IPIPE_DPC_OTF_3_D_MIN (0x0078) +#define IPIPE_DPC_OTF_3_D_MAX (0x007c) +#define IPIPE_DPC_OTF_3_C_THR (0x0080) +#define IPIPE_DPC_OTF_3_C_SLP (0x0084) +#define IPIPE_DPC_OTF_3_C_MIN (0x0088) +#define IPIPE_DPC_OTF_3_C_MAX (0x008c) + +#define IPIPE_LSC_VOFT (0x0090) +#define IPIPE_LSC_VA2 (0x0094) +#define IPIPE_LSC_VA1 (0x0098) +#define IPIPE_LSC_VS (0x009c) +#define IPIPE_LSC_HOFT (0x00a0) +#define IPIPE_LSC_HA2 (0x00a4) +#define IPIPE_LSC_HA1 (0x00a8) +#define IPIPE_LSC_HS (0x00ac) +#define IPIPE_LSC_GAN_R (0x00b0) +#define IPIPE_LSC_GAN_GR (0x00b4) +#define IPIPE_LSC_GAN_GB (0x00b8) +#define IPIPE_LSC_GAN_B (0x00bc) +#define IPIPE_LSC_OFT_R (0x00c0) +#define IPIPE_LSC_OFT_GR (0x00c4) +#define IPIPE_LSC_OFT_GB (0x00c8) +#define IPIPE_LSC_OFT_B (0x00cc) +#define IPIPE_LSC_SHF (0x00d0) +#define IPIPE_LSC_MAX (0x00d4) + +#define IPIPE_D2F_1ST_EN (0x00d8) +#define IPIPE_D2F_1ST_TYP (0x00dc) +#define IPIPE_D2F_1ST_THR_00 (0x00e0) +#define IPIPE_D2F_1ST_THR_01 (0x00e4) +#define IPIPE_D2F_1ST_THR_02 (0x00e8) +#define IPIPE_D2F_1ST_THR_03 (0x00ec) +#define IPIPE_D2F_1ST_THR_04 (0x00f0) +#define IPIPE_D2F_1ST_THR_05 (0x00f4) +#define IPIPE_D2F_1ST_THR_06 (0x00f8) +#define IPIPE_D2F_1ST_THR_07 (0x00fc) +#define IPIPE_D2F_1ST_STR_00 (0x0100) +#define IPIPE_D2F_1ST_STR_01 (0x0104) +#define IPIPE_D2F_1ST_STR_02 (0x0108) +#define IPIPE_D2F_1ST_STR_03 (0x010c) +#define IPIPE_D2F_1ST_STR_04 (0x0110) +#define IPIPE_D2F_1ST_STR_05 (0x0114) +#define IPIPE_D2F_1ST_STR_06 (0x0118) +#define IPIPE_D2F_1ST_STR_07 (0x011c) +#define IPIPE_D2F_1ST_SPR_00 (0x0120) +#define IPIPE_D2F_1ST_SPR_01 (0x0124) +#define IPIPE_D2F_1ST_SPR_02 (0x0128) +#define IPIPE_D2F_1ST_SPR_03 (0x012c) +#define IPIPE_D2F_1ST_SPR_04 (0x0130) +#define IPIPE_D2F_1ST_SPR_05 (0x0134) +#define IPIPE_D2F_1ST_SPR_06 (0x0138) +#define IPIPE_D2F_1ST_SPR_07 (0x013c) +#define IPIPE_D2F_1ST_EDG_MIN (0x0140) +#define IPIPE_D2F_1ST_EDG_MAX (0x0144) +#define IPIPE_D2F_2ND_EN (0x0148) +#define IPIPE_D2F_2ND_TYP (0x014c) +#define IPIPE_D2F_2ND_THR00 (0x0150) +#define IPIPE_D2F_2ND_THR01 (0x0154) +#define IPIPE_D2F_2ND_THR02 (0x0158) +#define IPIPE_D2F_2ND_THR03 (0x015c) +#define IPIPE_D2F_2ND_THR04 (0x0160) +#define IPIPE_D2F_2ND_THR05 (0x0164) +#define IPIPE_D2F_2ND_THR06 (0x0168) +#define IPIPE_D2F_2ND_THR07 (0x016c) +#define IPIPE_D2F_2ND_STR_00 (0x0170) +#define IPIPE_D2F_2ND_STR_01 (0x0174) +#define IPIPE_D2F_2ND_STR_02 (0x0178) +#define IPIPE_D2F_2ND_STR_03 (0x017c) +#define IPIPE_D2F_2ND_STR_04 (0x0180) +#define IPIPE_D2F_2ND_STR_05 (0x0184) +#define IPIPE_D2F_2ND_STR_06 (0x0188) +#define IPIPE_D2F_2ND_STR_07 (0x018c) +#define IPIPE_D2F_2ND_SPR_00 (0x0190) +#define IPIPE_D2F_2ND_SPR_01 (0x0194) +#define IPIPE_D2F_2ND_SPR_02 (0x0198) +#define IPIPE_D2F_2ND_SPR_03 (0x019c) +#define IPIPE_D2F_2ND_SPR_04 (0x01a0) +#define IPIPE_D2F_2ND_SPR_05 (0x01a4) +#define IPIPE_D2F_2ND_SPR_06 (0x01a8) +#define IPIPE_D2F_2ND_SPR_07 (0x01ac) +#define IPIPE_D2F_2ND_EDG_MIN (0x01b0) +#define IPIPE_D2F_2ND_EDG_MAX (0x01b4) + +#define IPIPE_GIC_EN (0x01b8) +#define IPIPE_GIC_TYP (0x01bc) +#define IPIPE_GIC_GAN (0x01c0) +#define IPIPE_GIC_NFGAIN (0x01c4) +#define IPIPE_GIC_THR (0x01c8) +#define IPIPE_GIC_SLP (0x01cc) + +#define IPIPE_WB2_OFT_R (0x01d0) +#define IPIPE_WB2_OFT_GR (0x01d4) +#define IPIPE_WB2_OFT_GB (0x01d8) +#define IPIPE_WB2_OFT_B (0x01dc) + +#define IPIPE_WB2_WGN_R (0x01e0) +#define IPIPE_WB2_WGN_GR (0x01e4) +#define IPIPE_WB2_WGN_GB (0x01e8) +#define IPIPE_WB2_WGN_B (0x01ec) + +#define IPIPE_CFA_MODE (0x01f0) +#define IPIPE_CFA_2DIR_HPF_THR (0x01f4) +#define IPIPE_CFA_2DIR_HPF_SLP (0x01f8) +#define IPIPE_CFA_2DIR_MIX_THR (0x01fc) +#define IPIPE_CFA_2DIR_MIX_SLP (0x0200) +#define IPIPE_CFA_2DIR_DIR_TRH (0x0204) +#define IPIPE_CFA_2DIR_DIR_SLP (0x0208) +#define IPIPE_CFA_2DIR_NDWT (0x020c) +#define IPIPE_CFA_MONO_HUE_FRA (0x0210) +#define IPIPE_CFA_MONO_EDG_THR (0x0214) +#define IPIPE_CFA_MONO_THR_MIN (0x0218) + +#define IPIPE_CFA_MONO_THR_SLP (0x021c) +#define IPIPE_CFA_MONO_SLP_MIN (0x0220) +#define IPIPE_CFA_MONO_SLP_SLP (0x0224) +#define IPIPE_CFA_MONO_LPWT (0x0228) + +#define IPIPE_RGB1_MUL_RR (0x022c) +#define IPIPE_RGB1_MUL_GR (0x0230) +#define IPIPE_RGB1_MUL_BR (0x0234) +#define IPIPE_RGB1_MUL_RG (0x0238) +#define IPIPE_RGB1_MUL_GG (0x023c) +#define IPIPE_RGB1_MUL_BG (0x0240) +#define IPIPE_RGB1_MUL_RB (0x0244) +#define IPIPE_RGB1_MUL_GB (0x0248) +#define IPIPE_RGB1_MUL_BB (0x024c) +#define IPIPE_RGB1_OFT_OR (0x0250) +#define IPIPE_RGB1_OFT_OG (0x0254) +#define IPIPE_RGB1_OFT_OB (0x0258) +#define IPIPE_GMM_CFG (0x025c) +#define IPIPE_RGB2_MUL_RR (0x0260) +#define IPIPE_RGB2_MUL_GR (0x0264) +#define IPIPE_RGB2_MUL_BR (0x0268) +#define IPIPE_RGB2_MUL_RG (0x026c) +#define IPIPE_RGB2_MUL_GG (0x0270) +#define IPIPE_RGB2_MUL_BG (0x0274) +#define IPIPE_RGB2_MUL_RB (0x0278) +#define IPIPE_RGB2_MUL_GB (0x027c) +#define IPIPE_RGB2_MUL_BB (0x0280) +#define IPIPE_RGB2_OFT_OR (0x0284) +#define IPIPE_RGB2_OFT_OG (0x0288) +#define IPIPE_RGB2_OFT_OB (0x028c) + +#define IPIPE_YUV_ADJ (0x0294) +#define IPIPE_YUV_MUL_RY (0x0298) +#define IPIPE_YUV_MUL_GY (0x029c) +#define IPIPE_YUV_MUL_BY (0x02a0) +#define IPIPE_YUV_MUL_RCB (0x02a4) +#define IPIPE_YUV_MUL_GCB (0x02a8) +#define IPIPE_YUV_MUL_BCB (0x02ac) +#define IPIPE_YUV_MUL_RCR (0x02b0) +#define IPIPE_YUV_MUL_GCR (0x02b4) +#define IPIPE_YUV_MUL_BCR (0x02b8) +#define IPIPE_YUV_OFT_Y (0x02bc) +#define IPIPE_YUV_OFT_CB (0x02c0) +#define IPIPE_YUV_OFT_CR (0x02c4) + +#define IPIPE_YUV_PHS (0x02c8) +#define IPIPE_YUV_PHS_LPF (1 << 1) +#define IPIPE_YUV_PHS_POS (1 << 0) + +#define IPIPE_YEE_EN (0x02d4) +#define IPIPE_YEE_TYP (0x02d8) +#define IPIPE_YEE_SHF (0x02dc) +#define IPIPE_YEE_MUL_00 (0x02e0) +#define IPIPE_YEE_MUL_01 (0x02e4) +#define IPIPE_YEE_MUL_02 (0x02e8) +#define IPIPE_YEE_MUL_10 (0x02ec) +#define IPIPE_YEE_MUL_11 (0x02f0) +#define IPIPE_YEE_MUL_12 (0x02f4) +#define IPIPE_YEE_MUL_20 (0x02f8) +#define IPIPE_YEE_MUL_21 (0x02fc) +#define IPIPE_YEE_MUL_22 (0x0300) +#define IPIPE_YEE_THR (0x0304) +#define IPIPE_YEE_E_GAN (0x0308) +#define IPIPE_YEE_E_THR_1 (0x030c) +#define IPIPE_YEE_E_THR_2 (0x0310) +#define IPIPE_YEE_G_GAN (0x0314) +#define IPIPE_YEE_G_OFT (0x0318) + +#define IPIPE_CAR_EN (0x031c) +#define IPIPE_CAR_TYP (0x0320) +#define IPIPE_CAR_SW (0x0324) +#define IPIPE_CAR_HPF_TYP (0x0328) +#define IPIPE_CAR_HPF_SHF (0x032c) +#define IPIPE_CAR_HPF_THR (0x0330) +#define IPIPE_CAR_GN1_GAN (0x0334) +#define IPIPE_CAR_GN1_SHF (0x0338) +#define IPIPE_CAR_GN1_MIN (0x033c) +#define IPIPE_CAR_GN2_GAN (0x0340) +#define IPIPE_CAR_GN2_SHF (0x0344) +#define IPIPE_CAR_GN2_MIN (0x0348) +#define IPIPE_CGS_EN (0x034c) +#define IPIPE_CGS_GN1_L_THR (0x0350) +#define IPIPE_CGS_GN1_L_GAIN (0x0354) +#define IPIPE_CGS_GN1_L_SHF (0x0358) +#define IPIPE_CGS_GN1_L_MIN (0x035c) +#define IPIPE_CGS_GN1_H_THR (0x0360) +#define IPIPE_CGS_GN1_H_GAIN (0x0364) +#define IPIPE_CGS_GN1_H_SHF (0x0368) +#define IPIPE_CGS_GN1_H_MIN (0x036c) +#define IPIPE_CGS_GN2_L_THR (0x0370) +#define IPIPE_CGS_GN2_L_GAIN (0x0374) +#define IPIPE_CGS_GN2_L_SHF (0x0378) +#define IPIPE_CGS_GN2_L_MIN (0x037c) + +#define IPIPE_BOX_EN (0x0380) +#define IPIPE_BOX_MODE (0x0384) +#define IPIPE_BOX_TYP (0x0388) +#define IPIPE_BOX_SHF (0x038c) +#define IPIPE_BOX_SDR_SAD_H (0x0390) +#define IPIPE_BOX_SDR_SAD_L (0x0394) + +#define IPIPE_HST_EN (0x039c) +#define IPIPE_HST_MODE (0x03a0) +#define IPIPE_HST_SEL (0x03a4) +#define IPIPE_HST_PARA (0x03a8) +#define IPIPE_HST_0_VPS (0x03ac) +#define IPIPE_HST_0_VSZ (0x03b0) +#define IPIPE_HST_0_HPS (0x03b4) +#define IPIPE_HST_0_HSZ (0x03b8) +#define IPIPE_HST_1_VPS (0x03bc) +#define IPIPE_HST_1_VSZ (0x03c0) +#define IPIPE_HST_1_HPS (0x03c4) +#define IPIPE_HST_1_HSZ (0x03c8) +#define IPIPE_HST_2_VPS (0x03cc) +#define IPIPE_HST_2_VSZ (0x03d0) +#define IPIPE_HST_2_HPS (0x03d4) +#define IPIPE_HST_2_HSZ (0x03d8) +#define IPIPE_HST_3_VPS (0x03dc) +#define IPIPE_HST_3_VSZ (0x03e0) +#define IPIPE_HST_3_HPS (0x03e4) +#define IPIPE_HST_3_HSZ (0x03e8) +#define IPIPE_HST_TBL (0x03ec) +#define IPIPE_HST_MUL_R (0x03f0) +#define IPIPE_HST_MUL_GR (0x03f4) +#define IPIPE_HST_MUL_GB (0x03f8) +#define IPIPE_HST_MUL_B (0x03fc) + +#define IPIPE_BSC_EN (0x0400) +#define IPIPE_BSC_MODE (0x0404) +#define IPIPE_BSC_TYP (0x0408) +#define IPIPE_BSC_ROW_VCT (0x040c) +#define IPIPE_BSC_ROW_SHF (0x0410) +#define IPIPE_BSC_ROW_VPO (0x0414) +#define IPIPE_BSC_ROW_VNU (0x0418) +#define IPIPE_BSC_ROW_VSKIP (0x041c) +#define IPIPE_BSC_ROW_HPO (0x0420) +#define IPIPE_BSC_ROW_HNU (0x0424) +#define IPIPE_BSC_ROW_HSKIP (0x0428) +#define IPIPE_BSC_COL_VCT (0x042c) +#define IPIPE_BSC_COL_SHF (0x0430) +#define IPIPE_BSC_COL_VPO (0x0434) +#define IPIPE_BSC_COL_VNU (0x0438) +#define IPIPE_BSC_COL_VSKIP (0x043c) +#define IPIPE_BSC_COL_HPO (0x0440) +#define IPIPE_BSC_COL_HNU (0x0444) +#define IPIPE_BSC_COL_HSKIP (0x0448) + +#define IPIPE_BSC_EN (0x0400) + +/* ISS ISP Resizer register offsets */ +#define RSZ_REVISION (0x0000) +#define RSZ_SYSCONFIG (0x0004) +#define RSZ_SYSCONFIG_RSZB_CLK_EN (1 << 9) +#define RSZ_SYSCONFIG_RSZA_CLK_EN (1 << 8) + +#define RSZ_IN_FIFO_CTRL (0x000c) +#define RSZ_IN_FIFO_CTRL_THRLD_LOW_MASK (0x1ff << 16) +#define RSZ_IN_FIFO_CTRL_THRLD_LOW_SHIFT 16 +#define RSZ_IN_FIFO_CTRL_THRLD_HIGH_MASK (0x1ff << 0) +#define RSZ_IN_FIFO_CTRL_THRLD_HIGH_SHIFT 0 + +#define RSZ_FRACDIV (0x0008) +#define RSZ_FRACDIV_MASK (0xffff) + +#define RSZ_SRC_EN (0x0020) +#define RSZ_SRC_EN_SRC_EN (1 << 0) + +#define RSZ_SRC_MODE (0x0024) +#define RSZ_SRC_MODE_OST (1 << 0) +#define RSZ_SRC_MODE_WRT (1 << 1) + +#define RSZ_SRC_FMT0 (0x0028) +#define RSZ_SRC_FMT0_BYPASS (1 << 1) +#define RSZ_SRC_FMT0_SEL (1 << 0) + +#define RSZ_SRC_FMT1 (0x002c) +#define RSZ_SRC_FMT1_IN420 (1 << 1) + +#define RSZ_SRC_VPS (0x0030) +#define RSZ_SRC_VSZ (0x0034) +#define RSZ_SRC_HPS (0x0038) +#define RSZ_SRC_HSZ (0x003c) +#define RSZ_DMA_RZA (0x0040) +#define RSZ_DMA_RZB (0x0044) +#define RSZ_DMA_STA (0x0048) +#define RSZ_GCK_MMR (0x004c) +#define RSZ_GCK_MMR_MMR (1 << 0) + +#define RSZ_GCK_SDR (0x0054) +#define RSZ_GCK_SDR_CORE (1 << 0) + +#define RSZ_IRQ_RZA (0x0058) +#define RSZ_IRQ_RZA_MASK (0x1fff) + +#define RSZ_IRQ_RZB (0x005c) +#define RSZ_IRQ_RZB_MASK (0x1fff) + +#define RSZ_YUV_Y_MIN (0x0060) +#define RSZ_YUV_Y_MAX (0x0064) +#define RSZ_YUV_C_MIN (0x0068) +#define RSZ_YUV_C_MAX (0x006c) + +#define RSZ_SEQ (0x0074) +#define RSZ_SEQ_HRVB (1 << 2) +#define RSZ_SEQ_HRVA (1 << 0) + +#define RZA_EN (0x0078) +#define RZA_MODE (0x007c) +#define RZA_MODE_ONE_SHOT (1 << 0) + +#define RZA_420 (0x0080) +#define RZA_I_VPS (0x0084) +#define RZA_I_HPS (0x0088) +#define RZA_O_VSZ (0x008c) +#define RZA_O_HSZ (0x0090) +#define RZA_V_PHS_Y (0x0094) +#define RZA_V_PHS_C (0x0098) +#define RZA_V_DIF (0x009c) +#define RZA_V_TYP (0x00a0) +#define RZA_V_LPF (0x00a4) +#define RZA_H_PHS (0x00a8) +#define RZA_H_DIF (0x00b0) +#define RZA_H_TYP (0x00b4) +#define RZA_H_LPF (0x00b8) +#define RZA_DWN_EN (0x00bc) +#define RZA_SDR_Y_BAD_H (0x00d0) +#define RZA_SDR_Y_BAD_L (0x00d4) +#define RZA_SDR_Y_SAD_H (0x00d8) +#define RZA_SDR_Y_SAD_L (0x00dc) +#define RZA_SDR_Y_OFT (0x00e0) +#define RZA_SDR_Y_PTR_S (0x00e4) +#define RZA_SDR_Y_PTR_E (0x00e8) +#define RZA_SDR_C_BAD_H (0x00ec) +#define RZA_SDR_C_BAD_L (0x00f0) +#define RZA_SDR_C_SAD_H (0x00f4) +#define RZA_SDR_C_SAD_L (0x00f8) +#define RZA_SDR_C_OFT (0x00fc) +#define RZA_SDR_C_PTR_S (0x0100) +#define RZA_SDR_C_PTR_E (0x0104) + +#define RZB_EN (0x0108) +#define RZB_MODE (0x010c) +#define RZB_420 (0x0110) +#define RZB_I_VPS (0x0114) +#define RZB_I_HPS (0x0118) +#define RZB_O_VSZ (0x011c) +#define RZB_O_HSZ (0x0120) + +#define RZB_V_DIF (0x012c) +#define RZB_V_TYP (0x0130) +#define RZB_V_LPF (0x0134) + +#define RZB_H_DIF (0x0140) +#define RZB_H_TYP (0x0144) +#define RZB_H_LPF (0x0148) + +#define RZB_SDR_Y_BAD_H (0x0160) +#define RZB_SDR_Y_BAD_L (0x0164) +#define RZB_SDR_Y_SAD_H (0x0168) +#define RZB_SDR_Y_SAD_L (0x016c) +#define RZB_SDR_Y_OFT (0x0170) +#define RZB_SDR_Y_PTR_S (0x0174) +#define RZB_SDR_Y_PTR_E (0x0178) +#define RZB_SDR_C_BAD_H (0x017c) +#define RZB_SDR_C_BAD_L (0x0180) +#define RZB_SDR_C_SAD_H (0x0184) +#define RZB_SDR_C_SAD_L (0x0188) + +#define RZB_SDR_C_PTR_S (0x0190) +#define RZB_SDR_C_PTR_E (0x0194) + +/* Shared Bitmasks between RZA & RZB */ +#define RSZ_EN_EN (1 << 0) + +#define RSZ_420_CEN (1 << 1) +#define RSZ_420_YEN (1 << 0) + +#define RSZ_I_VPS_MASK (0x1fff) + +#define RSZ_I_HPS_MASK (0x1fff) + +#define RSZ_O_VSZ_MASK (0x1fff) + +#define RSZ_O_HSZ_MASK (0x1ffe) + +#define RSZ_V_PHS_Y_MASK (0x3fff) + +#define RSZ_V_PHS_C_MASK (0x3fff) + +#define RSZ_V_DIF_MASK (0x3fff) + +#define RSZ_V_TYP_C (1 << 1) +#define RSZ_V_TYP_Y (1 << 0) + +#define RSZ_V_LPF_C_MASK (0x3f << 6) +#define RSZ_V_LPF_C_SHIFT 6 +#define RSZ_V_LPF_Y_MASK (0x3f << 0) +#define RSZ_V_LPF_Y_SHIFT 0 + +#define RSZ_H_PHS_MASK (0x3fff) + +#define RSZ_H_DIF_MASK (0x3fff) + +#define RSZ_H_TYP_C (1 << 1) +#define RSZ_H_TYP_Y (1 << 0) + +#define RSZ_H_LPF_C_MASK (0x3f << 6) +#define RSZ_H_LPF_C_SHIFT 6 +#define RSZ_H_LPF_Y_MASK (0x3f << 0) +#define RSZ_H_LPF_Y_SHIFT 0 + +#define RSZ_DWN_EN_DWN_EN (1 << 0) + +#endif /* _OMAP4_ISS_REGS_H_ */ diff --git a/drivers/staging/media/omap4iss/iss_resizer.c b/drivers/staging/media/omap4iss/iss_resizer.c new file mode 100644 index 00000000000..ae831b8985c --- /dev/null +++ b/drivers/staging/media/omap4iss/iss_resizer.c @@ -0,0 +1,893 @@ +/* + * TI OMAP4 ISS V4L2 Driver - ISP RESIZER module + * + * Copyright (C) 2012 Texas Instruments, Inc. + * + * Author: Sergio Aguirre <sergio.a.aguirre@gmail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include <linux/module.h> +#include <linux/uaccess.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/dma-mapping.h> +#include <linux/mm.h> +#include <linux/sched.h> + +#include "iss.h" +#include "iss_regs.h" +#include "iss_resizer.h" + +static const unsigned int resizer_fmts[] = { + V4L2_MBUS_FMT_UYVY8_1X16, + V4L2_MBUS_FMT_YUYV8_1X16, +}; + +/* + * resizer_print_status - Print current RESIZER Module register values. + * @resizer: Pointer to ISS ISP RESIZER device. + * + * Also prints other debug information stored in the RESIZER module. + */ +#define RSZ_PRINT_REGISTER(iss, name)\ + dev_dbg(iss->dev, "###RSZ " #name "=0x%08x\n", \ + iss_reg_read(iss, OMAP4_ISS_MEM_ISP_RESIZER, RSZ_##name)) + +#define RZA_PRINT_REGISTER(iss, name)\ + dev_dbg(iss->dev, "###RZA " #name "=0x%08x\n", \ + iss_reg_read(iss, OMAP4_ISS_MEM_ISP_RESIZER, RZA_##name)) + +static void resizer_print_status(struct iss_resizer_device *resizer) +{ + struct iss_device *iss = to_iss_device(resizer); + + dev_dbg(iss->dev, "-------------RESIZER Register dump-------------\n"); + + RSZ_PRINT_REGISTER(iss, SYSCONFIG); + RSZ_PRINT_REGISTER(iss, IN_FIFO_CTRL); + RSZ_PRINT_REGISTER(iss, FRACDIV); + RSZ_PRINT_REGISTER(iss, SRC_EN); + RSZ_PRINT_REGISTER(iss, SRC_MODE); + RSZ_PRINT_REGISTER(iss, SRC_FMT0); + RSZ_PRINT_REGISTER(iss, SRC_FMT1); + RSZ_PRINT_REGISTER(iss, SRC_VPS); + RSZ_PRINT_REGISTER(iss, SRC_VSZ); + RSZ_PRINT_REGISTER(iss, SRC_HPS); + RSZ_PRINT_REGISTER(iss, SRC_HSZ); + RSZ_PRINT_REGISTER(iss, DMA_RZA); + RSZ_PRINT_REGISTER(iss, DMA_RZB); + RSZ_PRINT_REGISTER(iss, DMA_STA); + RSZ_PRINT_REGISTER(iss, GCK_MMR); + RSZ_PRINT_REGISTER(iss, GCK_SDR); + RSZ_PRINT_REGISTER(iss, IRQ_RZA); + RSZ_PRINT_REGISTER(iss, IRQ_RZB); + RSZ_PRINT_REGISTER(iss, YUV_Y_MIN); + RSZ_PRINT_REGISTER(iss, YUV_Y_MAX); + RSZ_PRINT_REGISTER(iss, YUV_C_MIN); + RSZ_PRINT_REGISTER(iss, YUV_C_MAX); + RSZ_PRINT_REGISTER(iss, SEQ); + + RZA_PRINT_REGISTER(iss, EN); + RZA_PRINT_REGISTER(iss, MODE); + RZA_PRINT_REGISTER(iss, 420); + RZA_PRINT_REGISTER(iss, I_VPS); + RZA_PRINT_REGISTER(iss, I_HPS); + RZA_PRINT_REGISTER(iss, O_VSZ); + RZA_PRINT_REGISTER(iss, O_HSZ); + RZA_PRINT_REGISTER(iss, V_PHS_Y); + RZA_PRINT_REGISTER(iss, V_PHS_C); + RZA_PRINT_REGISTER(iss, V_DIF); + RZA_PRINT_REGISTER(iss, V_TYP); + RZA_PRINT_REGISTER(iss, V_LPF); + RZA_PRINT_REGISTER(iss, H_PHS); + RZA_PRINT_REGISTER(iss, H_DIF); + RZA_PRINT_REGISTER(iss, H_TYP); + RZA_PRINT_REGISTER(iss, H_LPF); + RZA_PRINT_REGISTER(iss, DWN_EN); + RZA_PRINT_REGISTER(iss, SDR_Y_BAD_H); + RZA_PRINT_REGISTER(iss, SDR_Y_BAD_L); + RZA_PRINT_REGISTER(iss, SDR_Y_SAD_H); + RZA_PRINT_REGISTER(iss, SDR_Y_SAD_L); + RZA_PRINT_REGISTER(iss, SDR_Y_OFT); + RZA_PRINT_REGISTER(iss, SDR_Y_PTR_S); + RZA_PRINT_REGISTER(iss, SDR_Y_PTR_E); + RZA_PRINT_REGISTER(iss, SDR_C_BAD_H); + RZA_PRINT_REGISTER(iss, SDR_C_BAD_L); + RZA_PRINT_REGISTER(iss, SDR_C_SAD_H); + RZA_PRINT_REGISTER(iss, SDR_C_SAD_L); + RZA_PRINT_REGISTER(iss, SDR_C_OFT); + RZA_PRINT_REGISTER(iss, SDR_C_PTR_S); + RZA_PRINT_REGISTER(iss, SDR_C_PTR_E); + + dev_dbg(iss->dev, "-----------------------------------------------\n"); +} + +/* + * resizer_enable - Enable/Disable RESIZER. + * @enable: enable flag + * + */ +static void resizer_enable(struct iss_resizer_device *resizer, u8 enable) +{ + struct iss_device *iss = to_iss_device(resizer); + + iss_reg_update(iss, OMAP4_ISS_MEM_ISP_RESIZER, RSZ_SRC_EN, + RSZ_SRC_EN_SRC_EN, enable ? RSZ_SRC_EN_SRC_EN : 0); + + /* TODO: Enable RSZB */ + iss_reg_update(iss, OMAP4_ISS_MEM_ISP_RESIZER, RZA_EN, RSZ_EN_EN, + enable ? RSZ_EN_EN : 0); +} + +/* ----------------------------------------------------------------------------- + * Format- and pipeline-related configuration helpers + */ + +/* + * resizer_set_outaddr - Set memory address to save output image + * @resizer: Pointer to ISP RESIZER device. + * @addr: 32-bit memory address aligned on 32 byte boundary. + * + * Sets the memory address where the output will be saved. + */ +static void resizer_set_outaddr(struct iss_resizer_device *resizer, u32 addr) +{ + struct iss_device *iss = to_iss_device(resizer); + struct v4l2_mbus_framefmt *informat, *outformat; + + informat = &resizer->formats[RESIZER_PAD_SINK]; + outformat = &resizer->formats[RESIZER_PAD_SOURCE_MEM]; + + /* Save address splitted in Base Address H & L */ + iss_reg_write(iss, OMAP4_ISS_MEM_ISP_RESIZER, RZA_SDR_Y_BAD_H, + (addr >> 16) & 0xffff); + iss_reg_write(iss, OMAP4_ISS_MEM_ISP_RESIZER, RZA_SDR_Y_BAD_L, + addr & 0xffff); + + /* SAD = BAD */ + iss_reg_write(iss, OMAP4_ISS_MEM_ISP_RESIZER, RZA_SDR_Y_SAD_H, + (addr >> 16) & 0xffff); + iss_reg_write(iss, OMAP4_ISS_MEM_ISP_RESIZER, RZA_SDR_Y_SAD_L, + addr & 0xffff); + + /* Program UV buffer address... Hardcoded to be contiguous! */ + if ((informat->code == V4L2_MBUS_FMT_UYVY8_1X16) && + (outformat->code == V4L2_MBUS_FMT_YUYV8_1_5X8)) { + u32 c_addr = addr + (resizer->video_out.bpl_value * + (outformat->height - 1)); + + /* Ensure Y_BAD_L[6:0] = C_BAD_L[6:0]*/ + if ((c_addr ^ addr) & 0x7f) { + c_addr &= ~0x7f; + c_addr += 0x80; + c_addr |= addr & 0x7f; + } + + /* Save address splitted in Base Address H & L */ + iss_reg_write(iss, OMAP4_ISS_MEM_ISP_RESIZER, RZA_SDR_C_BAD_H, + (c_addr >> 16) & 0xffff); + iss_reg_write(iss, OMAP4_ISS_MEM_ISP_RESIZER, RZA_SDR_C_BAD_L, + c_addr & 0xffff); + + /* SAD = BAD */ + iss_reg_write(iss, OMAP4_ISS_MEM_ISP_RESIZER, RZA_SDR_C_SAD_H, + (c_addr >> 16) & 0xffff); + iss_reg_write(iss, OMAP4_ISS_MEM_ISP_RESIZER, RZA_SDR_C_SAD_L, + c_addr & 0xffff); + } +} + +static void resizer_configure(struct iss_resizer_device *resizer) +{ + struct iss_device *iss = to_iss_device(resizer); + struct v4l2_mbus_framefmt *informat, *outformat; + + informat = &resizer->formats[RESIZER_PAD_SINK]; + outformat = &resizer->formats[RESIZER_PAD_SOURCE_MEM]; + + /* Disable pass-through more. Despite its name, the BYPASS bit controls + * pass-through mode, not bypass mode. + */ + iss_reg_clr(iss, OMAP4_ISS_MEM_ISP_RESIZER, RSZ_SRC_FMT0, + RSZ_SRC_FMT0_BYPASS); + + /* Select RSZ input */ + iss_reg_update(iss, OMAP4_ISS_MEM_ISP_RESIZER, RSZ_SRC_FMT0, + RSZ_SRC_FMT0_SEL, + resizer->input == RESIZER_INPUT_IPIPEIF ? + RSZ_SRC_FMT0_SEL : 0); + + /* RSZ ignores WEN signal from IPIPE/IPIPEIF */ + iss_reg_clr(iss, OMAP4_ISS_MEM_ISP_RESIZER, RSZ_SRC_MODE, + RSZ_SRC_MODE_WRT); + + /* Set Resizer in free-running mode */ + iss_reg_clr(iss, OMAP4_ISS_MEM_ISP_RESIZER, RSZ_SRC_MODE, + RSZ_SRC_MODE_OST); + + /* Init Resizer A */ + iss_reg_clr(iss, OMAP4_ISS_MEM_ISP_RESIZER, RZA_MODE, + RZA_MODE_ONE_SHOT); + + /* Set size related things now */ + iss_reg_write(iss, OMAP4_ISS_MEM_ISP_RESIZER, RSZ_SRC_VPS, 0); + iss_reg_write(iss, OMAP4_ISS_MEM_ISP_RESIZER, RSZ_SRC_HPS, 0); + iss_reg_write(iss, OMAP4_ISS_MEM_ISP_RESIZER, RSZ_SRC_VSZ, + informat->height - 2); + iss_reg_write(iss, OMAP4_ISS_MEM_ISP_RESIZER, RSZ_SRC_HSZ, + informat->width - 1); + + iss_reg_write(iss, OMAP4_ISS_MEM_ISP_RESIZER, RZA_I_VPS, 0); + iss_reg_write(iss, OMAP4_ISS_MEM_ISP_RESIZER, RZA_I_HPS, 0); + + iss_reg_write(iss, OMAP4_ISS_MEM_ISP_RESIZER, RZA_O_VSZ, + outformat->height - 2); + iss_reg_write(iss, OMAP4_ISS_MEM_ISP_RESIZER, RZA_O_HSZ, + outformat->width - 1); + + iss_reg_write(iss, OMAP4_ISS_MEM_ISP_RESIZER, RZA_V_DIF, 0x100); + iss_reg_write(iss, OMAP4_ISS_MEM_ISP_RESIZER, RZA_H_DIF, 0x100); + + /* Buffer output settings */ + iss_reg_write(iss, OMAP4_ISS_MEM_ISP_RESIZER, RZA_SDR_Y_PTR_S, 0); + iss_reg_write(iss, OMAP4_ISS_MEM_ISP_RESIZER, RZA_SDR_Y_PTR_E, + outformat->height - 1); + + iss_reg_write(iss, OMAP4_ISS_MEM_ISP_RESIZER, RZA_SDR_Y_OFT, + resizer->video_out.bpl_value); + + /* UYVY -> NV12 conversion */ + if ((informat->code == V4L2_MBUS_FMT_UYVY8_1X16) && + (outformat->code == V4L2_MBUS_FMT_YUYV8_1_5X8)) { + iss_reg_write(iss, OMAP4_ISS_MEM_ISP_RESIZER, RZA_420, + RSZ_420_CEN | RSZ_420_YEN); + + /* UV Buffer output settings */ + iss_reg_write(iss, OMAP4_ISS_MEM_ISP_RESIZER, RZA_SDR_C_PTR_S, + 0); + iss_reg_write(iss, OMAP4_ISS_MEM_ISP_RESIZER, RZA_SDR_C_PTR_E, + outformat->height - 1); + + iss_reg_write(iss, OMAP4_ISS_MEM_ISP_RESIZER, RZA_SDR_C_OFT, + resizer->video_out.bpl_value); + } else { + iss_reg_write(iss, OMAP4_ISS_MEM_ISP_RESIZER, RZA_420, 0); + } +} + +/* ----------------------------------------------------------------------------- + * Interrupt handling + */ + +static void resizer_isr_buffer(struct iss_resizer_device *resizer) +{ + struct iss_buffer *buffer; + + /* The whole resizer needs to be stopped. Disabling RZA only produces + * input FIFO overflows, most probably when the next frame is received. + */ + resizer_enable(resizer, 0); + + buffer = omap4iss_video_buffer_next(&resizer->video_out); + if (buffer == NULL) + return; + + resizer_set_outaddr(resizer, buffer->iss_addr); + + resizer_enable(resizer, 1); +} + +/* + * resizer_isif0_isr - Handle ISIF0 event + * @resizer: Pointer to ISP RESIZER device. + * + * Executes LSC deferred enablement before next frame starts. + */ +static void resizer_int_dma_isr(struct iss_resizer_device *resizer) +{ + struct iss_pipeline *pipe = + to_iss_pipeline(&resizer->subdev.entity); + if (pipe->do_propagation) + atomic_inc(&pipe->frame_number); + + resizer_isr_buffer(resizer); +} + +/* + * omap4iss_resizer_isr - Configure resizer during interframe time. + * @resizer: Pointer to ISP RESIZER device. + * @events: RESIZER events + */ +void omap4iss_resizer_isr(struct iss_resizer_device *resizer, u32 events) +{ + struct iss_device *iss = to_iss_device(resizer); + struct iss_pipeline *pipe = + to_iss_pipeline(&resizer->subdev.entity); + + if (events & (ISP5_IRQ_RSZ_FIFO_IN_BLK_ERR | + ISP5_IRQ_RSZ_FIFO_OVF)) { + dev_dbg(iss->dev, "RSZ Err: FIFO_IN_BLK:%d, FIFO_OVF:%d\n", + events & ISP5_IRQ_RSZ_FIFO_IN_BLK_ERR ? 1 : 0, + events & ISP5_IRQ_RSZ_FIFO_OVF ? 1 : 0); + omap4iss_pipeline_cancel_stream(pipe); + } + + if (omap4iss_module_sync_is_stopping(&resizer->wait, + &resizer->stopping)) + return; + + if (events & ISP5_IRQ_RSZ_INT_DMA) + resizer_int_dma_isr(resizer); +} + +/* ----------------------------------------------------------------------------- + * ISS video operations + */ + +static int resizer_video_queue(struct iss_video *video, + struct iss_buffer *buffer) +{ + struct iss_resizer_device *resizer = container_of(video, + struct iss_resizer_device, video_out); + + if (!(resizer->output & RESIZER_OUTPUT_MEMORY)) + return -ENODEV; + + resizer_set_outaddr(resizer, buffer->iss_addr); + + /* + * If streaming was enabled before there was a buffer queued + * or underrun happened in the ISR, the hardware was not enabled + * and DMA queue flag ISS_VIDEO_DMAQUEUE_UNDERRUN is still set. + * Enable it now. + */ + if (video->dmaqueue_flags & ISS_VIDEO_DMAQUEUE_UNDERRUN) { + resizer_enable(resizer, 1); + iss_video_dmaqueue_flags_clr(video); + } + + return 0; +} + +static const struct iss_video_operations resizer_video_ops = { + .queue = resizer_video_queue, +}; + +/* ----------------------------------------------------------------------------- + * V4L2 subdev operations + */ + +/* + * resizer_set_stream - Enable/Disable streaming on the RESIZER module + * @sd: ISP RESIZER V4L2 subdevice + * @enable: Enable/disable stream + */ +static int resizer_set_stream(struct v4l2_subdev *sd, int enable) +{ + struct iss_resizer_device *resizer = v4l2_get_subdevdata(sd); + struct iss_device *iss = to_iss_device(resizer); + struct iss_video *video_out = &resizer->video_out; + int ret = 0; + + if (resizer->state == ISS_PIPELINE_STREAM_STOPPED) { + if (enable == ISS_PIPELINE_STREAM_STOPPED) + return 0; + + omap4iss_isp_subclk_enable(iss, OMAP4_ISS_ISP_SUBCLK_RSZ); + + iss_reg_set(iss, OMAP4_ISS_MEM_ISP_RESIZER, RSZ_GCK_MMR, + RSZ_GCK_MMR_MMR); + iss_reg_set(iss, OMAP4_ISS_MEM_ISP_RESIZER, RSZ_GCK_SDR, + RSZ_GCK_SDR_CORE); + + /* FIXME: Enable RSZB also */ + iss_reg_set(iss, OMAP4_ISS_MEM_ISP_RESIZER, RSZ_SYSCONFIG, + RSZ_SYSCONFIG_RSZA_CLK_EN); + } + + switch (enable) { + case ISS_PIPELINE_STREAM_CONTINUOUS: + + resizer_configure(resizer); + resizer_print_status(resizer); + + /* + * When outputting to memory with no buffer available, let the + * buffer queue handler start the hardware. A DMA queue flag + * ISS_VIDEO_DMAQUEUE_QUEUED will be set as soon as there is + * a buffer available. + */ + if (resizer->output & RESIZER_OUTPUT_MEMORY && + !(video_out->dmaqueue_flags & ISS_VIDEO_DMAQUEUE_QUEUED)) + break; + + atomic_set(&resizer->stopping, 0); + resizer_enable(resizer, 1); + iss_video_dmaqueue_flags_clr(video_out); + break; + + case ISS_PIPELINE_STREAM_STOPPED: + if (resizer->state == ISS_PIPELINE_STREAM_STOPPED) + return 0; + if (omap4iss_module_sync_idle(&sd->entity, &resizer->wait, + &resizer->stopping)) + ret = -ETIMEDOUT; + + resizer_enable(resizer, 0); + iss_reg_clr(iss, OMAP4_ISS_MEM_ISP_RESIZER, RSZ_SYSCONFIG, + RSZ_SYSCONFIG_RSZA_CLK_EN); + iss_reg_clr(iss, OMAP4_ISS_MEM_ISP_RESIZER, RSZ_GCK_SDR, + RSZ_GCK_SDR_CORE); + iss_reg_clr(iss, OMAP4_ISS_MEM_ISP_RESIZER, RSZ_GCK_MMR, + RSZ_GCK_MMR_MMR); + omap4iss_isp_subclk_disable(iss, OMAP4_ISS_ISP_SUBCLK_RSZ); + iss_video_dmaqueue_flags_clr(video_out); + break; + } + + resizer->state = enable; + return ret; +} + +static struct v4l2_mbus_framefmt * +__resizer_get_format(struct iss_resizer_device *resizer, + struct v4l2_subdev_fh *fh, unsigned int pad, + enum v4l2_subdev_format_whence which) +{ + if (which == V4L2_SUBDEV_FORMAT_TRY) + return v4l2_subdev_get_try_format(fh, pad); + else + return &resizer->formats[pad]; +} + +/* + * resizer_try_format - Try video format on a pad + * @resizer: ISS RESIZER device + * @fh : V4L2 subdev file handle + * @pad: Pad number + * @fmt: Format + */ +static void +resizer_try_format(struct iss_resizer_device *resizer, + struct v4l2_subdev_fh *fh, unsigned int pad, + struct v4l2_mbus_framefmt *fmt, + enum v4l2_subdev_format_whence which) +{ + enum v4l2_mbus_pixelcode pixelcode; + struct v4l2_mbus_framefmt *format; + unsigned int width = fmt->width; + unsigned int height = fmt->height; + unsigned int i; + + switch (pad) { + case RESIZER_PAD_SINK: + for (i = 0; i < ARRAY_SIZE(resizer_fmts); i++) { + if (fmt->code == resizer_fmts[i]) + break; + } + + /* If not found, use UYVY as default */ + if (i >= ARRAY_SIZE(resizer_fmts)) + fmt->code = V4L2_MBUS_FMT_UYVY8_1X16; + + /* Clamp the input size. */ + fmt->width = clamp_t(u32, width, 1, 8192); + fmt->height = clamp_t(u32, height, 1, 8192); + break; + + case RESIZER_PAD_SOURCE_MEM: + pixelcode = fmt->code; + format = __resizer_get_format(resizer, fh, RESIZER_PAD_SINK, + which); + memcpy(fmt, format, sizeof(*fmt)); + + if ((pixelcode == V4L2_MBUS_FMT_YUYV8_1_5X8) && + (fmt->code == V4L2_MBUS_FMT_UYVY8_1X16)) + fmt->code = pixelcode; + + /* The data formatter truncates the number of horizontal output + * pixels to a multiple of 16. To avoid clipping data, allow + * callers to request an output size bigger than the input size + * up to the nearest multiple of 16. + */ + fmt->width = clamp_t(u32, width, 32, (fmt->width + 15) & ~15); + fmt->width &= ~15; + fmt->height = clamp_t(u32, height, 32, fmt->height); + break; + + } + + fmt->colorspace = V4L2_COLORSPACE_JPEG; + fmt->field = V4L2_FIELD_NONE; +} + +/* + * resizer_enum_mbus_code - Handle pixel format enumeration + * @sd : pointer to v4l2 subdev structure + * @fh : V4L2 subdev file handle + * @code : pointer to v4l2_subdev_mbus_code_enum structure + * return -EINVAL or zero on success + */ +static int resizer_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_fh *fh, + struct v4l2_subdev_mbus_code_enum *code) +{ + struct iss_resizer_device *resizer = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt *format; + + switch (code->pad) { + case RESIZER_PAD_SINK: + if (code->index >= ARRAY_SIZE(resizer_fmts)) + return -EINVAL; + + code->code = resizer_fmts[code->index]; + break; + + case RESIZER_PAD_SOURCE_MEM: + format = __resizer_get_format(resizer, fh, RESIZER_PAD_SINK, + V4L2_SUBDEV_FORMAT_TRY); + + if (code->index == 0) { + code->code = format->code; + break; + } + + switch (format->code) { + case V4L2_MBUS_FMT_UYVY8_1X16: + if (code->index == 1) + code->code = V4L2_MBUS_FMT_YUYV8_1_5X8; + else + return -EINVAL; + break; + default: + if (code->index != 0) + return -EINVAL; + } + + break; + + default: + return -EINVAL; + } + + return 0; +} + +static int resizer_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_fh *fh, + struct v4l2_subdev_frame_size_enum *fse) +{ + struct iss_resizer_device *resizer = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt format; + + if (fse->index != 0) + return -EINVAL; + + format.code = fse->code; + format.width = 1; + format.height = 1; + resizer_try_format(resizer, fh, fse->pad, &format, + V4L2_SUBDEV_FORMAT_TRY); + fse->min_width = format.width; + fse->min_height = format.height; + + if (format.code != fse->code) + return -EINVAL; + + format.code = fse->code; + format.width = -1; + format.height = -1; + resizer_try_format(resizer, fh, fse->pad, &format, + V4L2_SUBDEV_FORMAT_TRY); + fse->max_width = format.width; + fse->max_height = format.height; + + return 0; +} + +/* + * resizer_get_format - Retrieve the video format on a pad + * @sd : ISP RESIZER V4L2 subdevice + * @fh : V4L2 subdev file handle + * @fmt: Format + * + * Return 0 on success or -EINVAL if the pad is invalid or doesn't correspond + * to the format type. + */ +static int resizer_get_format(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh, + struct v4l2_subdev_format *fmt) +{ + struct iss_resizer_device *resizer = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt *format; + + format = __resizer_get_format(resizer, fh, fmt->pad, fmt->which); + if (format == NULL) + return -EINVAL; + + fmt->format = *format; + return 0; +} + +/* + * resizer_set_format - Set the video format on a pad + * @sd : ISP RESIZER V4L2 subdevice + * @fh : V4L2 subdev file handle + * @fmt: Format + * + * Return 0 on success or -EINVAL if the pad is invalid or doesn't correspond + * to the format type. + */ +static int resizer_set_format(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh, + struct v4l2_subdev_format *fmt) +{ + struct iss_resizer_device *resizer = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt *format; + + format = __resizer_get_format(resizer, fh, fmt->pad, fmt->which); + if (format == NULL) + return -EINVAL; + + resizer_try_format(resizer, fh, fmt->pad, &fmt->format, fmt->which); + *format = fmt->format; + + /* Propagate the format from sink to source */ + if (fmt->pad == RESIZER_PAD_SINK) { + format = __resizer_get_format(resizer, fh, + RESIZER_PAD_SOURCE_MEM, + fmt->which); + *format = fmt->format; + resizer_try_format(resizer, fh, RESIZER_PAD_SOURCE_MEM, format, + fmt->which); + } + + return 0; +} + +static int resizer_link_validate(struct v4l2_subdev *sd, + struct media_link *link, + struct v4l2_subdev_format *source_fmt, + struct v4l2_subdev_format *sink_fmt) +{ + /* Check if the two ends match */ + if (source_fmt->format.width != sink_fmt->format.width || + source_fmt->format.height != sink_fmt->format.height) + return -EPIPE; + + if (source_fmt->format.code != sink_fmt->format.code) + return -EPIPE; + + return 0; +} + +/* + * resizer_init_formats - Initialize formats on all pads + * @sd: ISP RESIZER V4L2 subdevice + * @fh: V4L2 subdev file handle + * + * Initialize all pad formats with default values. If fh is not NULL, try + * formats are initialized on the file handle. Otherwise active formats are + * initialized on the device. + */ +static int resizer_init_formats(struct v4l2_subdev *sd, + struct v4l2_subdev_fh *fh) +{ + struct v4l2_subdev_format format; + + memset(&format, 0, sizeof(format)); + format.pad = RESIZER_PAD_SINK; + format.which = fh ? V4L2_SUBDEV_FORMAT_TRY : V4L2_SUBDEV_FORMAT_ACTIVE; + format.format.code = V4L2_MBUS_FMT_UYVY8_1X16; + format.format.width = 4096; + format.format.height = 4096; + resizer_set_format(sd, fh, &format); + + return 0; +} + +/* V4L2 subdev video operations */ +static const struct v4l2_subdev_video_ops resizer_v4l2_video_ops = { + .s_stream = resizer_set_stream, +}; + +/* V4L2 subdev pad operations */ +static const struct v4l2_subdev_pad_ops resizer_v4l2_pad_ops = { + .enum_mbus_code = resizer_enum_mbus_code, + .enum_frame_size = resizer_enum_frame_size, + .get_fmt = resizer_get_format, + .set_fmt = resizer_set_format, + .link_validate = resizer_link_validate, +}; + +/* V4L2 subdev operations */ +static const struct v4l2_subdev_ops resizer_v4l2_ops = { + .video = &resizer_v4l2_video_ops, + .pad = &resizer_v4l2_pad_ops, +}; + +/* V4L2 subdev internal operations */ +static const struct v4l2_subdev_internal_ops resizer_v4l2_internal_ops = { + .open = resizer_init_formats, +}; + +/* ----------------------------------------------------------------------------- + * Media entity operations + */ + +/* + * resizer_link_setup - Setup RESIZER connections + * @entity: RESIZER media entity + * @local: Pad at the local end of the link + * @remote: Pad at the remote end of the link + * @flags: Link flags + * + * return -EINVAL or zero on success + */ +static int resizer_link_setup(struct media_entity *entity, + const struct media_pad *local, + const struct media_pad *remote, u32 flags) +{ + struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(entity); + struct iss_resizer_device *resizer = v4l2_get_subdevdata(sd); + struct iss_device *iss = to_iss_device(resizer); + + switch (local->index | media_entity_type(remote->entity)) { + case RESIZER_PAD_SINK | MEDIA_ENT_T_V4L2_SUBDEV: + /* Read from IPIPE or IPIPEIF. */ + if (!(flags & MEDIA_LNK_FL_ENABLED)) { + resizer->input = RESIZER_INPUT_NONE; + break; + } + + if (resizer->input != RESIZER_INPUT_NONE) + return -EBUSY; + + if (remote->entity == &iss->ipipeif.subdev.entity) + resizer->input = RESIZER_INPUT_IPIPEIF; + else if (remote->entity == &iss->ipipe.subdev.entity) + resizer->input = RESIZER_INPUT_IPIPE; + + + break; + + case RESIZER_PAD_SOURCE_MEM | MEDIA_ENT_T_DEVNODE: + /* Write to memory */ + if (flags & MEDIA_LNK_FL_ENABLED) { + if (resizer->output & ~RESIZER_OUTPUT_MEMORY) + return -EBUSY; + resizer->output |= RESIZER_OUTPUT_MEMORY; + } else { + resizer->output &= ~RESIZER_OUTPUT_MEMORY; + } + break; + + default: + return -EINVAL; + } + + return 0; +} + +/* media operations */ +static const struct media_entity_operations resizer_media_ops = { + .link_setup = resizer_link_setup, + .link_validate = v4l2_subdev_link_validate, +}; + +/* + * resizer_init_entities - Initialize V4L2 subdev and media entity + * @resizer: ISS ISP RESIZER module + * + * Return 0 on success and a negative error code on failure. + */ +static int resizer_init_entities(struct iss_resizer_device *resizer) +{ + struct v4l2_subdev *sd = &resizer->subdev; + struct media_pad *pads = resizer->pads; + struct media_entity *me = &sd->entity; + int ret; + + resizer->input = RESIZER_INPUT_NONE; + + v4l2_subdev_init(sd, &resizer_v4l2_ops); + sd->internal_ops = &resizer_v4l2_internal_ops; + strlcpy(sd->name, "OMAP4 ISS ISP resizer", sizeof(sd->name)); + sd->grp_id = 1 << 16; /* group ID for iss subdevs */ + v4l2_set_subdevdata(sd, resizer); + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + + pads[RESIZER_PAD_SINK].flags = MEDIA_PAD_FL_SINK; + pads[RESIZER_PAD_SOURCE_MEM].flags = MEDIA_PAD_FL_SOURCE; + + me->ops = &resizer_media_ops; + ret = media_entity_init(me, RESIZER_PADS_NUM, pads, 0); + if (ret < 0) + return ret; + + resizer_init_formats(sd, NULL); + + resizer->video_out.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + resizer->video_out.ops = &resizer_video_ops; + resizer->video_out.iss = to_iss_device(resizer); + resizer->video_out.capture_mem = PAGE_ALIGN(4096 * 4096) * 3; + resizer->video_out.bpl_alignment = 32; + resizer->video_out.bpl_zero_padding = 1; + resizer->video_out.bpl_max = 0x1ffe0; + + ret = omap4iss_video_init(&resizer->video_out, "ISP resizer a"); + if (ret < 0) + return ret; + + /* Connect the RESIZER subdev to the video node. */ + ret = media_entity_create_link(&resizer->subdev.entity, + RESIZER_PAD_SOURCE_MEM, + &resizer->video_out.video.entity, 0, 0); + if (ret < 0) + return ret; + + return 0; +} + +void omap4iss_resizer_unregister_entities(struct iss_resizer_device *resizer) +{ + media_entity_cleanup(&resizer->subdev.entity); + + v4l2_device_unregister_subdev(&resizer->subdev); + omap4iss_video_unregister(&resizer->video_out); +} + +int omap4iss_resizer_register_entities(struct iss_resizer_device *resizer, + struct v4l2_device *vdev) +{ + int ret; + + /* Register the subdev and video node. */ + ret = v4l2_device_register_subdev(vdev, &resizer->subdev); + if (ret < 0) + goto error; + + ret = omap4iss_video_register(&resizer->video_out, vdev); + if (ret < 0) + goto error; + + return 0; + +error: + omap4iss_resizer_unregister_entities(resizer); + return ret; +} + +/* ----------------------------------------------------------------------------- + * ISP RESIZER initialisation and cleanup + */ + +/* + * omap4iss_resizer_init - RESIZER module initialization. + * @iss: Device pointer specific to the OMAP4 ISS. + * + * TODO: Get the initialisation values from platform data. + * + * Return 0 on success or a negative error code otherwise. + */ +int omap4iss_resizer_init(struct iss_device *iss) +{ + struct iss_resizer_device *resizer = &iss->resizer; + + resizer->state = ISS_PIPELINE_STREAM_STOPPED; + init_waitqueue_head(&resizer->wait); + + return resizer_init_entities(resizer); +} + +/* + * omap4iss_resizer_cleanup - RESIZER module cleanup. + * @iss: Device pointer specific to the OMAP4 ISS. + */ +void omap4iss_resizer_cleanup(struct iss_device *iss) +{ + /* FIXME: are you sure there's nothing to do? */ +} diff --git a/drivers/staging/media/omap4iss/iss_resizer.h b/drivers/staging/media/omap4iss/iss_resizer.h new file mode 100644 index 00000000000..3727498b06a --- /dev/null +++ b/drivers/staging/media/omap4iss/iss_resizer.h @@ -0,0 +1,75 @@ +/* + * TI OMAP4 ISS V4L2 Driver - ISP RESIZER module + * + * Copyright (C) 2012 Texas Instruments, Inc. + * + * Author: Sergio Aguirre <sergio.a.aguirre@gmail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#ifndef OMAP4_ISS_RESIZER_H +#define OMAP4_ISS_RESIZER_H + +#include "iss_video.h" + +enum resizer_input_entity { + RESIZER_INPUT_NONE, + RESIZER_INPUT_IPIPE, + RESIZER_INPUT_IPIPEIF +}; + +#define RESIZER_OUTPUT_MEMORY (1 << 0) + +/* Sink and source RESIZER pads */ +#define RESIZER_PAD_SINK 0 +#define RESIZER_PAD_SOURCE_MEM 1 +#define RESIZER_PADS_NUM 2 + +/* + * struct iss_resizer_device - Structure for the RESIZER module to store its own + * information + * @subdev: V4L2 subdevice + * @pads: Sink and source media entity pads + * @formats: Active video formats + * @input: Active input + * @output: Active outputs + * @video_out: Output video node + * @error: A hardware error occurred during capture + * @state: Streaming state + * @wait: Wait queue used to stop the module + * @stopping: Stopping state + */ +struct iss_resizer_device { + struct v4l2_subdev subdev; + struct media_pad pads[RESIZER_PADS_NUM]; + struct v4l2_mbus_framefmt formats[RESIZER_PADS_NUM]; + + enum resizer_input_entity input; + unsigned int output; + struct iss_video video_out; + unsigned int error; + + enum iss_pipeline_stream_state state; + wait_queue_head_t wait; + atomic_t stopping; +}; + +struct iss_device; + +int omap4iss_resizer_init(struct iss_device *iss); +void omap4iss_resizer_cleanup(struct iss_device *iss); +int omap4iss_resizer_register_entities(struct iss_resizer_device *resizer, + struct v4l2_device *vdev); +void omap4iss_resizer_unregister_entities(struct iss_resizer_device *resizer); + +int omap4iss_resizer_busy(struct iss_resizer_device *resizer); +void omap4iss_resizer_isr(struct iss_resizer_device *resizer, u32 events); +void omap4iss_resizer_restore_context(struct iss_device *iss); +void omap4iss_resizer_max_rate(struct iss_resizer_device *resizer, + unsigned int *max_rate); + +#endif /* OMAP4_ISS_RESIZER_H */ diff --git a/drivers/staging/media/omap4iss/iss_video.c b/drivers/staging/media/omap4iss/iss_video.c new file mode 100644 index 00000000000..cbf455d66f7 --- /dev/null +++ b/drivers/staging/media/omap4iss/iss_video.c @@ -0,0 +1,1226 @@ +/* + * TI OMAP4 ISS V4L2 Driver - Generic video node + * + * Copyright (C) 2012 Texas Instruments, Inc. + * + * Author: Sergio Aguirre <sergio.a.aguirre@gmail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include <asm/cacheflush.h> +#include <linux/clk.h> +#include <linux/mm.h> +#include <linux/pagemap.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/vmalloc.h> +#include <linux/module.h> +#include <media/v4l2-dev.h> +#include <media/v4l2-ioctl.h> + +#include "iss_video.h" +#include "iss.h" + + +/* ----------------------------------------------------------------------------- + * Helper functions + */ + +static struct iss_format_info formats[] = { + { V4L2_MBUS_FMT_Y8_1X8, V4L2_MBUS_FMT_Y8_1X8, + V4L2_MBUS_FMT_Y8_1X8, V4L2_MBUS_FMT_Y8_1X8, + V4L2_PIX_FMT_GREY, 8, "Greyscale 8 bpp", }, + { V4L2_MBUS_FMT_Y10_1X10, V4L2_MBUS_FMT_Y10_1X10, + V4L2_MBUS_FMT_Y10_1X10, V4L2_MBUS_FMT_Y8_1X8, + V4L2_PIX_FMT_Y10, 10, "Greyscale 10 bpp", }, + { V4L2_MBUS_FMT_Y12_1X12, V4L2_MBUS_FMT_Y10_1X10, + V4L2_MBUS_FMT_Y12_1X12, V4L2_MBUS_FMT_Y8_1X8, + V4L2_PIX_FMT_Y12, 12, "Greyscale 12 bpp", }, + { V4L2_MBUS_FMT_SBGGR8_1X8, V4L2_MBUS_FMT_SBGGR8_1X8, + V4L2_MBUS_FMT_SBGGR8_1X8, V4L2_MBUS_FMT_SBGGR8_1X8, + V4L2_PIX_FMT_SBGGR8, 8, "BGGR Bayer 8 bpp", }, + { V4L2_MBUS_FMT_SGBRG8_1X8, V4L2_MBUS_FMT_SGBRG8_1X8, + V4L2_MBUS_FMT_SGBRG8_1X8, V4L2_MBUS_FMT_SGBRG8_1X8, + V4L2_PIX_FMT_SGBRG8, 8, "GBRG Bayer 8 bpp", }, + { V4L2_MBUS_FMT_SGRBG8_1X8, V4L2_MBUS_FMT_SGRBG8_1X8, + V4L2_MBUS_FMT_SGRBG8_1X8, V4L2_MBUS_FMT_SGRBG8_1X8, + V4L2_PIX_FMT_SGRBG8, 8, "GRBG Bayer 8 bpp", }, + { V4L2_MBUS_FMT_SRGGB8_1X8, V4L2_MBUS_FMT_SRGGB8_1X8, + V4L2_MBUS_FMT_SRGGB8_1X8, V4L2_MBUS_FMT_SRGGB8_1X8, + V4L2_PIX_FMT_SRGGB8, 8, "RGGB Bayer 8 bpp", }, + { V4L2_MBUS_FMT_SGRBG10_DPCM8_1X8, V4L2_MBUS_FMT_SGRBG10_DPCM8_1X8, + V4L2_MBUS_FMT_SGRBG10_1X10, 0, + V4L2_PIX_FMT_SGRBG10DPCM8, 8, "GRBG Bayer 10 bpp DPCM8", }, + { V4L2_MBUS_FMT_SBGGR10_1X10, V4L2_MBUS_FMT_SBGGR10_1X10, + V4L2_MBUS_FMT_SBGGR10_1X10, V4L2_MBUS_FMT_SBGGR8_1X8, + V4L2_PIX_FMT_SBGGR10, 10, "BGGR Bayer 10 bpp", }, + { V4L2_MBUS_FMT_SGBRG10_1X10, V4L2_MBUS_FMT_SGBRG10_1X10, + V4L2_MBUS_FMT_SGBRG10_1X10, V4L2_MBUS_FMT_SGBRG8_1X8, + V4L2_PIX_FMT_SGBRG10, 10, "GBRG Bayer 10 bpp", }, + { V4L2_MBUS_FMT_SGRBG10_1X10, V4L2_MBUS_FMT_SGRBG10_1X10, + V4L2_MBUS_FMT_SGRBG10_1X10, V4L2_MBUS_FMT_SGRBG8_1X8, + V4L2_PIX_FMT_SGRBG10, 10, "GRBG Bayer 10 bpp", }, + { V4L2_MBUS_FMT_SRGGB10_1X10, V4L2_MBUS_FMT_SRGGB10_1X10, + V4L2_MBUS_FMT_SRGGB10_1X10, V4L2_MBUS_FMT_SRGGB8_1X8, + V4L2_PIX_FMT_SRGGB10, 10, "RGGB Bayer 10 bpp", }, + { V4L2_MBUS_FMT_SBGGR12_1X12, V4L2_MBUS_FMT_SBGGR10_1X10, + V4L2_MBUS_FMT_SBGGR12_1X12, V4L2_MBUS_FMT_SBGGR8_1X8, + V4L2_PIX_FMT_SBGGR12, 12, "BGGR Bayer 12 bpp", }, + { V4L2_MBUS_FMT_SGBRG12_1X12, V4L2_MBUS_FMT_SGBRG10_1X10, + V4L2_MBUS_FMT_SGBRG12_1X12, V4L2_MBUS_FMT_SGBRG8_1X8, + V4L2_PIX_FMT_SGBRG12, 12, "GBRG Bayer 12 bpp", }, + { V4L2_MBUS_FMT_SGRBG12_1X12, V4L2_MBUS_FMT_SGRBG10_1X10, + V4L2_MBUS_FMT_SGRBG12_1X12, V4L2_MBUS_FMT_SGRBG8_1X8, + V4L2_PIX_FMT_SGRBG12, 12, "GRBG Bayer 12 bpp", }, + { V4L2_MBUS_FMT_SRGGB12_1X12, V4L2_MBUS_FMT_SRGGB10_1X10, + V4L2_MBUS_FMT_SRGGB12_1X12, V4L2_MBUS_FMT_SRGGB8_1X8, + V4L2_PIX_FMT_SRGGB12, 12, "RGGB Bayer 12 bpp", }, + { V4L2_MBUS_FMT_UYVY8_1X16, V4L2_MBUS_FMT_UYVY8_1X16, + V4L2_MBUS_FMT_UYVY8_1X16, 0, + V4L2_PIX_FMT_UYVY, 16, "YUV 4:2:2 (UYVY)", }, + { V4L2_MBUS_FMT_YUYV8_1X16, V4L2_MBUS_FMT_YUYV8_1X16, + V4L2_MBUS_FMT_YUYV8_1X16, 0, + V4L2_PIX_FMT_YUYV, 16, "YUV 4:2:2 (YUYV)", }, + { V4L2_MBUS_FMT_YUYV8_1_5X8, V4L2_MBUS_FMT_YUYV8_1_5X8, + V4L2_MBUS_FMT_YUYV8_1_5X8, 0, + V4L2_PIX_FMT_NV12, 8, "YUV 4:2:0 (NV12)", }, +}; + +const struct iss_format_info * +omap4iss_video_format_info(enum v4l2_mbus_pixelcode code) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(formats); ++i) { + if (formats[i].code == code) + return &formats[i]; + } + + return NULL; +} + +/* + * iss_video_mbus_to_pix - Convert v4l2_mbus_framefmt to v4l2_pix_format + * @video: ISS video instance + * @mbus: v4l2_mbus_framefmt format (input) + * @pix: v4l2_pix_format format (output) + * + * Fill the output pix structure with information from the input mbus format. + * The bytesperline and sizeimage fields are computed from the requested bytes + * per line value in the pix format and information from the video instance. + * + * Return the number of padding bytes at end of line. + */ +static unsigned int iss_video_mbus_to_pix(const struct iss_video *video, + const struct v4l2_mbus_framefmt *mbus, + struct v4l2_pix_format *pix) +{ + unsigned int bpl = pix->bytesperline; + unsigned int min_bpl; + unsigned int i; + + memset(pix, 0, sizeof(*pix)); + pix->width = mbus->width; + pix->height = mbus->height; + + /* Skip the last format in the loop so that it will be selected if no + * match is found. + */ + for (i = 0; i < ARRAY_SIZE(formats) - 1; ++i) { + if (formats[i].code == mbus->code) + break; + } + + min_bpl = pix->width * ALIGN(formats[i].bpp, 8) / 8; + + /* Clamp the requested bytes per line value. If the maximum bytes per + * line value is zero, the module doesn't support user configurable line + * sizes. Override the requested value with the minimum in that case. + */ + if (video->bpl_max) + bpl = clamp(bpl, min_bpl, video->bpl_max); + else + bpl = min_bpl; + + if (!video->bpl_zero_padding || bpl != min_bpl) + bpl = ALIGN(bpl, video->bpl_alignment); + + pix->pixelformat = formats[i].pixelformat; + pix->bytesperline = bpl; + pix->sizeimage = pix->bytesperline * pix->height; + pix->colorspace = mbus->colorspace; + pix->field = mbus->field; + + /* FIXME: Special case for NV12! We should make this nicer... */ + if (pix->pixelformat == V4L2_PIX_FMT_NV12) + pix->sizeimage += (pix->bytesperline * pix->height) / 2; + + return bpl - min_bpl; +} + +static void iss_video_pix_to_mbus(const struct v4l2_pix_format *pix, + struct v4l2_mbus_framefmt *mbus) +{ + unsigned int i; + + memset(mbus, 0, sizeof(*mbus)); + mbus->width = pix->width; + mbus->height = pix->height; + + for (i = 0; i < ARRAY_SIZE(formats); ++i) { + if (formats[i].pixelformat == pix->pixelformat) + break; + } + + if (WARN_ON(i == ARRAY_SIZE(formats))) + return; + + mbus->code = formats[i].code; + mbus->colorspace = pix->colorspace; + mbus->field = pix->field; +} + +static struct v4l2_subdev * +iss_video_remote_subdev(struct iss_video *video, u32 *pad) +{ + struct media_pad *remote; + + remote = media_entity_remote_pad(&video->pad); + + if (remote == NULL || + media_entity_type(remote->entity) != MEDIA_ENT_T_V4L2_SUBDEV) + return NULL; + + if (pad) + *pad = remote->index; + + return media_entity_to_v4l2_subdev(remote->entity); +} + +/* Return a pointer to the ISS video instance at the far end of the pipeline. */ +static struct iss_video * +iss_video_far_end(struct iss_video *video) +{ + struct media_entity_graph graph; + struct media_entity *entity = &video->video.entity; + struct media_device *mdev = entity->parent; + struct iss_video *far_end = NULL; + + mutex_lock(&mdev->graph_mutex); + media_entity_graph_walk_start(&graph, entity); + + while ((entity = media_entity_graph_walk_next(&graph))) { + if (entity == &video->video.entity) + continue; + + if (media_entity_type(entity) != MEDIA_ENT_T_DEVNODE) + continue; + + far_end = to_iss_video(media_entity_to_video_device(entity)); + if (far_end->type != video->type) + break; + + far_end = NULL; + } + + mutex_unlock(&mdev->graph_mutex); + return far_end; +} + +static int +__iss_video_get_format(struct iss_video *video, + struct v4l2_mbus_framefmt *format) +{ + struct v4l2_subdev_format fmt; + struct v4l2_subdev *subdev; + u32 pad; + int ret; + + subdev = iss_video_remote_subdev(video, &pad); + if (subdev == NULL) + return -EINVAL; + + memset(&fmt, 0, sizeof(fmt)); + fmt.pad = pad; + fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE; + + mutex_lock(&video->mutex); + ret = v4l2_subdev_call(subdev, pad, get_fmt, NULL, &fmt); + mutex_unlock(&video->mutex); + + if (ret) + return ret; + + *format = fmt.format; + return 0; +} + +static int +iss_video_check_format(struct iss_video *video, struct iss_video_fh *vfh) +{ + struct v4l2_mbus_framefmt format; + struct v4l2_pix_format pixfmt; + int ret; + + ret = __iss_video_get_format(video, &format); + if (ret < 0) + return ret; + + pixfmt.bytesperline = 0; + ret = iss_video_mbus_to_pix(video, &format, &pixfmt); + + if (vfh->format.fmt.pix.pixelformat != pixfmt.pixelformat || + vfh->format.fmt.pix.height != pixfmt.height || + vfh->format.fmt.pix.width != pixfmt.width || + vfh->format.fmt.pix.bytesperline != pixfmt.bytesperline || + vfh->format.fmt.pix.sizeimage != pixfmt.sizeimage) + return -EINVAL; + + return ret; +} + +/* ----------------------------------------------------------------------------- + * Video queue operations + */ + +static int iss_video_queue_setup(struct vb2_queue *vq, + const struct v4l2_format *fmt, + unsigned int *count, unsigned int *num_planes, + unsigned int sizes[], void *alloc_ctxs[]) +{ + struct iss_video_fh *vfh = vb2_get_drv_priv(vq); + struct iss_video *video = vfh->video; + + /* Revisit multi-planar support for NV12 */ + *num_planes = 1; + + sizes[0] = vfh->format.fmt.pix.sizeimage; + if (sizes[0] == 0) + return -EINVAL; + + alloc_ctxs[0] = video->alloc_ctx; + + *count = min(*count, video->capture_mem / PAGE_ALIGN(sizes[0])); + + return 0; +} + +static void iss_video_buf_cleanup(struct vb2_buffer *vb) +{ + struct iss_buffer *buffer = container_of(vb, struct iss_buffer, vb); + + if (buffer->iss_addr) + buffer->iss_addr = 0; +} + +static int iss_video_buf_prepare(struct vb2_buffer *vb) +{ + struct iss_video_fh *vfh = vb2_get_drv_priv(vb->vb2_queue); + struct iss_buffer *buffer = container_of(vb, struct iss_buffer, vb); + struct iss_video *video = vfh->video; + unsigned long size = vfh->format.fmt.pix.sizeimage; + dma_addr_t addr; + + if (vb2_plane_size(vb, 0) < size) + return -ENOBUFS; + + /* Refuse to prepare the buffer is the video node has registered an + * error. We don't need to take any lock here as the operation is + * inherently racy. The authoritative check will be performed in the + * queue handler, which can't return an error, this check is just a best + * effort to notify userspace as early as possible. + */ + if (unlikely(video->error)) + return -EIO; + + addr = vb2_dma_contig_plane_dma_addr(vb, 0); + if (!IS_ALIGNED(addr, 32)) { + dev_dbg(video->iss->dev, + "Buffer address must be aligned to 32 bytes boundary.\n"); + return -EINVAL; + } + + vb2_set_plane_payload(vb, 0, size); + buffer->iss_addr = addr; + return 0; +} + +static void iss_video_buf_queue(struct vb2_buffer *vb) +{ + struct iss_video_fh *vfh = vb2_get_drv_priv(vb->vb2_queue); + struct iss_video *video = vfh->video; + struct iss_buffer *buffer = container_of(vb, struct iss_buffer, vb); + struct iss_pipeline *pipe = to_iss_pipeline(&video->video.entity); + unsigned long flags; + bool empty; + + spin_lock_irqsave(&video->qlock, flags); + + if (unlikely(video->error)) { + vb2_buffer_done(vb, VB2_BUF_STATE_ERROR); + spin_unlock_irqrestore(&video->qlock, flags); + return; + } + + empty = list_empty(&video->dmaqueue); + list_add_tail(&buffer->list, &video->dmaqueue); + + spin_unlock_irqrestore(&video->qlock, flags); + + if (empty) { + enum iss_pipeline_state state; + unsigned int start; + + if (video->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) + state = ISS_PIPELINE_QUEUE_OUTPUT; + else + state = ISS_PIPELINE_QUEUE_INPUT; + + spin_lock_irqsave(&pipe->lock, flags); + pipe->state |= state; + video->ops->queue(video, buffer); + video->dmaqueue_flags |= ISS_VIDEO_DMAQUEUE_QUEUED; + + start = iss_pipeline_ready(pipe); + if (start) + pipe->state |= ISS_PIPELINE_STREAM; + spin_unlock_irqrestore(&pipe->lock, flags); + + if (start) + omap4iss_pipeline_set_stream(pipe, + ISS_PIPELINE_STREAM_SINGLESHOT); + } +} + +static const struct vb2_ops iss_video_vb2ops = { + .queue_setup = iss_video_queue_setup, + .buf_prepare = iss_video_buf_prepare, + .buf_queue = iss_video_buf_queue, + .buf_cleanup = iss_video_buf_cleanup, +}; + +/* + * omap4iss_video_buffer_next - Complete the current buffer and return the next + * @video: ISS video object + * + * Remove the current video buffer from the DMA queue and fill its timestamp, + * field count and state fields before waking up its completion handler. + * + * For capture video nodes, the buffer state is set to VB2_BUF_STATE_DONE if no + * error has been flagged in the pipeline, or to VB2_BUF_STATE_ERROR otherwise. + * + * The DMA queue is expected to contain at least one buffer. + * + * Return a pointer to the next buffer in the DMA queue, or NULL if the queue is + * empty. + */ +struct iss_buffer *omap4iss_video_buffer_next(struct iss_video *video) +{ + struct iss_pipeline *pipe = to_iss_pipeline(&video->video.entity); + enum iss_pipeline_state state; + struct iss_buffer *buf; + unsigned long flags; + struct timespec ts; + + spin_lock_irqsave(&video->qlock, flags); + if (WARN_ON(list_empty(&video->dmaqueue))) { + spin_unlock_irqrestore(&video->qlock, flags); + return NULL; + } + + buf = list_first_entry(&video->dmaqueue, struct iss_buffer, + list); + list_del(&buf->list); + spin_unlock_irqrestore(&video->qlock, flags); + + ktime_get_ts(&ts); + buf->vb.v4l2_buf.timestamp.tv_sec = ts.tv_sec; + buf->vb.v4l2_buf.timestamp.tv_usec = ts.tv_nsec / NSEC_PER_USEC; + + /* Do frame number propagation only if this is the output video node. + * Frame number either comes from the CSI receivers or it gets + * incremented here if H3A is not active. + * Note: There is no guarantee that the output buffer will finish + * first, so the input number might lag behind by 1 in some cases. + */ + if (video == pipe->output && !pipe->do_propagation) + buf->vb.v4l2_buf.sequence = + atomic_inc_return(&pipe->frame_number); + else + buf->vb.v4l2_buf.sequence = atomic_read(&pipe->frame_number); + + vb2_buffer_done(&buf->vb, pipe->error ? + VB2_BUF_STATE_ERROR : VB2_BUF_STATE_DONE); + pipe->error = false; + + spin_lock_irqsave(&video->qlock, flags); + if (list_empty(&video->dmaqueue)) { + spin_unlock_irqrestore(&video->qlock, flags); + if (video->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) + state = ISS_PIPELINE_QUEUE_OUTPUT + | ISS_PIPELINE_STREAM; + else + state = ISS_PIPELINE_QUEUE_INPUT + | ISS_PIPELINE_STREAM; + + spin_lock_irqsave(&pipe->lock, flags); + pipe->state &= ~state; + if (video->pipe.stream_state == ISS_PIPELINE_STREAM_CONTINUOUS) + video->dmaqueue_flags |= ISS_VIDEO_DMAQUEUE_UNDERRUN; + spin_unlock_irqrestore(&pipe->lock, flags); + return NULL; + } + + if (video->type == V4L2_BUF_TYPE_VIDEO_CAPTURE && pipe->input != NULL) { + spin_lock(&pipe->lock); + pipe->state &= ~ISS_PIPELINE_STREAM; + spin_unlock(&pipe->lock); + } + + buf = list_first_entry(&video->dmaqueue, struct iss_buffer, + list); + spin_unlock_irqrestore(&video->qlock, flags); + buf->vb.state = VB2_BUF_STATE_ACTIVE; + return buf; +} + +/* + * omap4iss_video_cancel_stream - Cancel stream on a video node + * @video: ISS video object + * + * Cancelling a stream mark all buffers on the video node as erroneous and makes + * sure no new buffer can be queued. + */ +void omap4iss_video_cancel_stream(struct iss_video *video) +{ + unsigned long flags; + + spin_lock_irqsave(&video->qlock, flags); + + while (!list_empty(&video->dmaqueue)) { + struct iss_buffer *buf; + + buf = list_first_entry(&video->dmaqueue, struct iss_buffer, + list); + list_del(&buf->list); + vb2_buffer_done(&buf->vb, VB2_BUF_STATE_ERROR); + } + + video->error = true; + + spin_unlock_irqrestore(&video->qlock, flags); +} + +/* ----------------------------------------------------------------------------- + * V4L2 ioctls + */ + +static int +iss_video_querycap(struct file *file, void *fh, struct v4l2_capability *cap) +{ + struct iss_video *video = video_drvdata(file); + + strlcpy(cap->driver, ISS_VIDEO_DRIVER_NAME, sizeof(cap->driver)); + strlcpy(cap->card, video->video.name, sizeof(cap->card)); + strlcpy(cap->bus_info, "media", sizeof(cap->bus_info)); + + if (video->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) + cap->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING; + else + cap->device_caps = V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_STREAMING; + + cap->capabilities = V4L2_CAP_DEVICE_CAPS | V4L2_CAP_STREAMING + | V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_VIDEO_OUTPUT; + + return 0; +} + +static int +iss_video_enum_format(struct file *file, void *fh, struct v4l2_fmtdesc *f) +{ + struct iss_video *video = video_drvdata(file); + struct v4l2_mbus_framefmt format; + unsigned int index = f->index; + unsigned int i; + int ret; + + if (f->type != video->type) + return -EINVAL; + + ret = __iss_video_get_format(video, &format); + if (ret < 0) + return ret; + + for (i = 0; i < ARRAY_SIZE(formats); ++i) { + const struct iss_format_info *info = &formats[i]; + + if (format.code != info->code) + continue; + + if (index == 0) { + f->pixelformat = info->pixelformat; + strlcpy(f->description, info->description, + sizeof(f->description)); + return 0; + } + + index--; + } + + return -EINVAL; +} + +static int +iss_video_get_format(struct file *file, void *fh, struct v4l2_format *format) +{ + struct iss_video_fh *vfh = to_iss_video_fh(fh); + struct iss_video *video = video_drvdata(file); + + if (format->type != video->type) + return -EINVAL; + + mutex_lock(&video->mutex); + *format = vfh->format; + mutex_unlock(&video->mutex); + + return 0; +} + +static int +iss_video_set_format(struct file *file, void *fh, struct v4l2_format *format) +{ + struct iss_video_fh *vfh = to_iss_video_fh(fh); + struct iss_video *video = video_drvdata(file); + struct v4l2_mbus_framefmt fmt; + + if (format->type != video->type) + return -EINVAL; + + mutex_lock(&video->mutex); + + /* Fill the bytesperline and sizeimage fields by converting to media bus + * format and back to pixel format. + */ + iss_video_pix_to_mbus(&format->fmt.pix, &fmt); + iss_video_mbus_to_pix(video, &fmt, &format->fmt.pix); + + vfh->format = *format; + + mutex_unlock(&video->mutex); + return 0; +} + +static int +iss_video_try_format(struct file *file, void *fh, struct v4l2_format *format) +{ + struct iss_video *video = video_drvdata(file); + struct v4l2_subdev_format fmt; + struct v4l2_subdev *subdev; + u32 pad; + int ret; + + if (format->type != video->type) + return -EINVAL; + + subdev = iss_video_remote_subdev(video, &pad); + if (subdev == NULL) + return -EINVAL; + + iss_video_pix_to_mbus(&format->fmt.pix, &fmt.format); + + fmt.pad = pad; + fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE; + ret = v4l2_subdev_call(subdev, pad, get_fmt, NULL, &fmt); + if (ret) + return ret; + + iss_video_mbus_to_pix(video, &fmt.format, &format->fmt.pix); + return 0; +} + +static int +iss_video_cropcap(struct file *file, void *fh, struct v4l2_cropcap *cropcap) +{ + struct iss_video *video = video_drvdata(file); + struct v4l2_subdev *subdev; + int ret; + + subdev = iss_video_remote_subdev(video, NULL); + if (subdev == NULL) + return -EINVAL; + + mutex_lock(&video->mutex); + ret = v4l2_subdev_call(subdev, video, cropcap, cropcap); + mutex_unlock(&video->mutex); + + return ret == -ENOIOCTLCMD ? -ENOTTY : ret; +} + +static int +iss_video_get_crop(struct file *file, void *fh, struct v4l2_crop *crop) +{ + struct iss_video *video = video_drvdata(file); + struct v4l2_subdev_format format; + struct v4l2_subdev *subdev; + u32 pad; + int ret; + + subdev = iss_video_remote_subdev(video, &pad); + if (subdev == NULL) + return -EINVAL; + + /* Try the get crop operation first and fallback to get format if not + * implemented. + */ + ret = v4l2_subdev_call(subdev, video, g_crop, crop); + if (ret != -ENOIOCTLCMD) + return ret; + + format.pad = pad; + format.which = V4L2_SUBDEV_FORMAT_ACTIVE; + ret = v4l2_subdev_call(subdev, pad, get_fmt, NULL, &format); + if (ret < 0) + return ret == -ENOIOCTLCMD ? -ENOTTY : ret; + + crop->c.left = 0; + crop->c.top = 0; + crop->c.width = format.format.width; + crop->c.height = format.format.height; + + return 0; +} + +static int +iss_video_set_crop(struct file *file, void *fh, const struct v4l2_crop *crop) +{ + struct iss_video *video = video_drvdata(file); + struct v4l2_subdev *subdev; + int ret; + + subdev = iss_video_remote_subdev(video, NULL); + if (subdev == NULL) + return -EINVAL; + + mutex_lock(&video->mutex); + ret = v4l2_subdev_call(subdev, video, s_crop, crop); + mutex_unlock(&video->mutex); + + return ret == -ENOIOCTLCMD ? -ENOTTY : ret; +} + +static int +iss_video_get_param(struct file *file, void *fh, struct v4l2_streamparm *a) +{ + struct iss_video_fh *vfh = to_iss_video_fh(fh); + struct iss_video *video = video_drvdata(file); + + if (video->type != V4L2_BUF_TYPE_VIDEO_OUTPUT || + video->type != a->type) + return -EINVAL; + + memset(a, 0, sizeof(*a)); + a->type = V4L2_BUF_TYPE_VIDEO_OUTPUT; + a->parm.output.capability = V4L2_CAP_TIMEPERFRAME; + a->parm.output.timeperframe = vfh->timeperframe; + + return 0; +} + +static int +iss_video_set_param(struct file *file, void *fh, struct v4l2_streamparm *a) +{ + struct iss_video_fh *vfh = to_iss_video_fh(fh); + struct iss_video *video = video_drvdata(file); + + if (video->type != V4L2_BUF_TYPE_VIDEO_OUTPUT || + video->type != a->type) + return -EINVAL; + + if (a->parm.output.timeperframe.denominator == 0) + a->parm.output.timeperframe.denominator = 1; + + vfh->timeperframe = a->parm.output.timeperframe; + + return 0; +} + +static int +iss_video_reqbufs(struct file *file, void *fh, struct v4l2_requestbuffers *rb) +{ + struct iss_video_fh *vfh = to_iss_video_fh(fh); + + return vb2_reqbufs(&vfh->queue, rb); +} + +static int +iss_video_querybuf(struct file *file, void *fh, struct v4l2_buffer *b) +{ + struct iss_video_fh *vfh = to_iss_video_fh(fh); + + return vb2_querybuf(&vfh->queue, b); +} + +static int +iss_video_qbuf(struct file *file, void *fh, struct v4l2_buffer *b) +{ + struct iss_video_fh *vfh = to_iss_video_fh(fh); + + return vb2_qbuf(&vfh->queue, b); +} + +static int +iss_video_dqbuf(struct file *file, void *fh, struct v4l2_buffer *b) +{ + struct iss_video_fh *vfh = to_iss_video_fh(fh); + + return vb2_dqbuf(&vfh->queue, b, file->f_flags & O_NONBLOCK); +} + +/* + * Stream management + * + * Every ISS pipeline has a single input and a single output. The input can be + * either a sensor or a video node. The output is always a video node. + * + * As every pipeline has an output video node, the ISS video objects at the + * pipeline output stores the pipeline state. It tracks the streaming state of + * both the input and output, as well as the availability of buffers. + * + * In sensor-to-memory mode, frames are always available at the pipeline input. + * Starting the sensor usually requires I2C transfers and must be done in + * interruptible context. The pipeline is started and stopped synchronously + * to the stream on/off commands. All modules in the pipeline will get their + * subdev set stream handler called. The module at the end of the pipeline must + * delay starting the hardware until buffers are available at its output. + * + * In memory-to-memory mode, starting/stopping the stream requires + * synchronization between the input and output. ISS modules can't be stopped + * in the middle of a frame, and at least some of the modules seem to become + * busy as soon as they're started, even if they don't receive a frame start + * event. For that reason frames need to be processed in single-shot mode. The + * driver needs to wait until a frame is completely processed and written to + * memory before restarting the pipeline for the next frame. Pipelined + * processing might be possible but requires more testing. + * + * Stream start must be delayed until buffers are available at both the input + * and output. The pipeline must be started in the videobuf queue callback with + * the buffers queue spinlock held. The modules subdev set stream operation must + * not sleep. + */ +static int +iss_video_streamon(struct file *file, void *fh, enum v4l2_buf_type type) +{ + struct iss_video_fh *vfh = to_iss_video_fh(fh); + struct iss_video *video = video_drvdata(file); + struct media_entity_graph graph; + struct media_entity *entity; + enum iss_pipeline_state state; + struct iss_pipeline *pipe; + struct iss_video *far_end; + unsigned long flags; + int ret; + + if (type != video->type) + return -EINVAL; + + mutex_lock(&video->stream_lock); + + /* Start streaming on the pipeline. No link touching an entity in the + * pipeline can be activated or deactivated once streaming is started. + */ + pipe = video->video.entity.pipe + ? to_iss_pipeline(&video->video.entity) : &video->pipe; + pipe->external = NULL; + pipe->external_rate = 0; + pipe->external_bpp = 0; + pipe->entities = 0; + + if (video->iss->pdata->set_constraints) + video->iss->pdata->set_constraints(video->iss, true); + + ret = media_entity_pipeline_start(&video->video.entity, &pipe->pipe); + if (ret < 0) + goto err_media_entity_pipeline_start; + + entity = &video->video.entity; + media_entity_graph_walk_start(&graph, entity); + while ((entity = media_entity_graph_walk_next(&graph))) + pipe->entities |= 1 << entity->id; + + /* Verify that the currently configured format matches the output of + * the connected subdev. + */ + ret = iss_video_check_format(video, vfh); + if (ret < 0) + goto err_iss_video_check_format; + + video->bpl_padding = ret; + video->bpl_value = vfh->format.fmt.pix.bytesperline; + + /* Find the ISS video node connected at the far end of the pipeline and + * update the pipeline. + */ + far_end = iss_video_far_end(video); + + if (video->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) { + state = ISS_PIPELINE_STREAM_OUTPUT | ISS_PIPELINE_IDLE_OUTPUT; + pipe->input = far_end; + pipe->output = video; + } else { + if (far_end == NULL) { + ret = -EPIPE; + goto err_iss_video_check_format; + } + + state = ISS_PIPELINE_STREAM_INPUT | ISS_PIPELINE_IDLE_INPUT; + pipe->input = video; + pipe->output = far_end; + } + + spin_lock_irqsave(&pipe->lock, flags); + pipe->state &= ~ISS_PIPELINE_STREAM; + pipe->state |= state; + spin_unlock_irqrestore(&pipe->lock, flags); + + /* Set the maximum time per frame as the value requested by userspace. + * This is a soft limit that can be overridden if the hardware doesn't + * support the request limit. + */ + if (video->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) + pipe->max_timeperframe = vfh->timeperframe; + + video->queue = &vfh->queue; + INIT_LIST_HEAD(&video->dmaqueue); + spin_lock_init(&video->qlock); + video->error = false; + atomic_set(&pipe->frame_number, -1); + + ret = vb2_streamon(&vfh->queue, type); + if (ret < 0) + goto err_iss_video_check_format; + + /* In sensor-to-memory mode, the stream can be started synchronously + * to the stream on command. In memory-to-memory mode, it will be + * started when buffers are queued on both the input and output. + */ + if (pipe->input == NULL) { + unsigned long flags; + ret = omap4iss_pipeline_set_stream(pipe, + ISS_PIPELINE_STREAM_CONTINUOUS); + if (ret < 0) + goto err_omap4iss_set_stream; + spin_lock_irqsave(&video->qlock, flags); + if (list_empty(&video->dmaqueue)) + video->dmaqueue_flags |= ISS_VIDEO_DMAQUEUE_UNDERRUN; + spin_unlock_irqrestore(&video->qlock, flags); + } + + mutex_unlock(&video->stream_lock); + return 0; + +err_omap4iss_set_stream: + vb2_streamoff(&vfh->queue, type); +err_iss_video_check_format: + media_entity_pipeline_stop(&video->video.entity); +err_media_entity_pipeline_start: + if (video->iss->pdata->set_constraints) + video->iss->pdata->set_constraints(video->iss, false); + video->queue = NULL; + + mutex_unlock(&video->stream_lock); + return ret; +} + +static int +iss_video_streamoff(struct file *file, void *fh, enum v4l2_buf_type type) +{ + struct iss_video_fh *vfh = to_iss_video_fh(fh); + struct iss_video *video = video_drvdata(file); + struct iss_pipeline *pipe = to_iss_pipeline(&video->video.entity); + enum iss_pipeline_state state; + unsigned long flags; + + if (type != video->type) + return -EINVAL; + + mutex_lock(&video->stream_lock); + + if (!vb2_is_streaming(&vfh->queue)) + goto done; + + /* Update the pipeline state. */ + if (video->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) + state = ISS_PIPELINE_STREAM_OUTPUT + | ISS_PIPELINE_QUEUE_OUTPUT; + else + state = ISS_PIPELINE_STREAM_INPUT + | ISS_PIPELINE_QUEUE_INPUT; + + spin_lock_irqsave(&pipe->lock, flags); + pipe->state &= ~state; + spin_unlock_irqrestore(&pipe->lock, flags); + + /* Stop the stream. */ + omap4iss_pipeline_set_stream(pipe, ISS_PIPELINE_STREAM_STOPPED); + vb2_streamoff(&vfh->queue, type); + video->queue = NULL; + + if (video->iss->pdata->set_constraints) + video->iss->pdata->set_constraints(video->iss, false); + media_entity_pipeline_stop(&video->video.entity); + +done: + mutex_unlock(&video->stream_lock); + return 0; +} + +static int +iss_video_enum_input(struct file *file, void *fh, struct v4l2_input *input) +{ + if (input->index > 0) + return -EINVAL; + + strlcpy(input->name, "camera", sizeof(input->name)); + input->type = V4L2_INPUT_TYPE_CAMERA; + + return 0; +} + +static int +iss_video_g_input(struct file *file, void *fh, unsigned int *input) +{ + *input = 0; + + return 0; +} + +static int +iss_video_s_input(struct file *file, void *fh, unsigned int input) +{ + return input == 0 ? 0 : -EINVAL; +} + +static const struct v4l2_ioctl_ops iss_video_ioctl_ops = { + .vidioc_querycap = iss_video_querycap, + .vidioc_enum_fmt_vid_cap = iss_video_enum_format, + .vidioc_g_fmt_vid_cap = iss_video_get_format, + .vidioc_s_fmt_vid_cap = iss_video_set_format, + .vidioc_try_fmt_vid_cap = iss_video_try_format, + .vidioc_g_fmt_vid_out = iss_video_get_format, + .vidioc_s_fmt_vid_out = iss_video_set_format, + .vidioc_try_fmt_vid_out = iss_video_try_format, + .vidioc_cropcap = iss_video_cropcap, + .vidioc_g_crop = iss_video_get_crop, + .vidioc_s_crop = iss_video_set_crop, + .vidioc_g_parm = iss_video_get_param, + .vidioc_s_parm = iss_video_set_param, + .vidioc_reqbufs = iss_video_reqbufs, + .vidioc_querybuf = iss_video_querybuf, + .vidioc_qbuf = iss_video_qbuf, + .vidioc_dqbuf = iss_video_dqbuf, + .vidioc_streamon = iss_video_streamon, + .vidioc_streamoff = iss_video_streamoff, + .vidioc_enum_input = iss_video_enum_input, + .vidioc_g_input = iss_video_g_input, + .vidioc_s_input = iss_video_s_input, +}; + +/* ----------------------------------------------------------------------------- + * V4L2 file operations + */ + +static int iss_video_open(struct file *file) +{ + struct iss_video *video = video_drvdata(file); + struct iss_video_fh *handle; + struct vb2_queue *q; + int ret = 0; + + handle = kzalloc(sizeof(*handle), GFP_KERNEL); + if (handle == NULL) + return -ENOMEM; + + v4l2_fh_init(&handle->vfh, &video->video); + v4l2_fh_add(&handle->vfh); + + /* If this is the first user, initialise the pipeline. */ + if (omap4iss_get(video->iss) == NULL) { + ret = -EBUSY; + goto done; + } + + ret = omap4iss_pipeline_pm_use(&video->video.entity, 1); + if (ret < 0) { + omap4iss_put(video->iss); + goto done; + } + + video->alloc_ctx = vb2_dma_contig_init_ctx(video->iss->dev); + if (IS_ERR(video->alloc_ctx)) { + ret = PTR_ERR(video->alloc_ctx); + omap4iss_put(video->iss); + goto done; + } + + q = &handle->queue; + + q->type = video->type; + q->io_modes = VB2_MMAP; + q->drv_priv = handle; + q->ops = &iss_video_vb2ops; + q->mem_ops = &vb2_dma_contig_memops; + q->buf_struct_size = sizeof(struct iss_buffer); + q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + + ret = vb2_queue_init(q); + if (ret) { + omap4iss_put(video->iss); + goto done; + } + + memset(&handle->format, 0, sizeof(handle->format)); + handle->format.type = video->type; + handle->timeperframe.denominator = 1; + + handle->video = video; + file->private_data = &handle->vfh; + +done: + if (ret < 0) { + v4l2_fh_del(&handle->vfh); + kfree(handle); + } + + return ret; +} + +static int iss_video_release(struct file *file) +{ + struct iss_video *video = video_drvdata(file); + struct v4l2_fh *vfh = file->private_data; + struct iss_video_fh *handle = to_iss_video_fh(vfh); + + /* Disable streaming and free the buffers queue resources. */ + iss_video_streamoff(file, vfh, video->type); + + omap4iss_pipeline_pm_use(&video->video.entity, 0); + + /* Release the videobuf2 queue */ + vb2_queue_release(&handle->queue); + + /* Release the file handle. */ + v4l2_fh_del(vfh); + kfree(handle); + file->private_data = NULL; + + omap4iss_put(video->iss); + + return 0; +} + +static unsigned int iss_video_poll(struct file *file, poll_table *wait) +{ + struct iss_video_fh *vfh = to_iss_video_fh(file->private_data); + + return vb2_poll(&vfh->queue, file, wait); +} + +static int iss_video_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct iss_video_fh *vfh = to_iss_video_fh(file->private_data); + + return vb2_mmap(&vfh->queue, vma); +} + +static struct v4l2_file_operations iss_video_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = video_ioctl2, + .open = iss_video_open, + .release = iss_video_release, + .poll = iss_video_poll, + .mmap = iss_video_mmap, +}; + +/* ----------------------------------------------------------------------------- + * ISS video core + */ + +static const struct iss_video_operations iss_video_dummy_ops = { +}; + +int omap4iss_video_init(struct iss_video *video, const char *name) +{ + const char *direction; + int ret; + + switch (video->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + direction = "output"; + video->pad.flags = MEDIA_PAD_FL_SINK; + break; + case V4L2_BUF_TYPE_VIDEO_OUTPUT: + direction = "input"; + video->pad.flags = MEDIA_PAD_FL_SOURCE; + break; + + default: + return -EINVAL; + } + + ret = media_entity_init(&video->video.entity, 1, &video->pad, 0); + if (ret < 0) + return ret; + + mutex_init(&video->mutex); + atomic_set(&video->active, 0); + + spin_lock_init(&video->pipe.lock); + mutex_init(&video->stream_lock); + + /* Initialize the video device. */ + if (video->ops == NULL) + video->ops = &iss_video_dummy_ops; + + video->video.fops = &iss_video_fops; + snprintf(video->video.name, sizeof(video->video.name), + "OMAP4 ISS %s %s", name, direction); + video->video.vfl_type = VFL_TYPE_GRABBER; + video->video.release = video_device_release_empty; + video->video.ioctl_ops = &iss_video_ioctl_ops; + video->pipe.stream_state = ISS_PIPELINE_STREAM_STOPPED; + + video_set_drvdata(&video->video, video); + + return 0; +} + +void omap4iss_video_cleanup(struct iss_video *video) +{ + media_entity_cleanup(&video->video.entity); + mutex_destroy(&video->stream_lock); + mutex_destroy(&video->mutex); +} + +int omap4iss_video_register(struct iss_video *video, struct v4l2_device *vdev) +{ + int ret; + + video->video.v4l2_dev = vdev; + + ret = video_register_device(&video->video, VFL_TYPE_GRABBER, -1); + if (ret < 0) + dev_err(video->iss->dev, + "%s: could not register video device (%d)\n", + __func__, ret); + + return ret; +} + +void omap4iss_video_unregister(struct iss_video *video) +{ + video_unregister_device(&video->video); +} diff --git a/drivers/staging/media/omap4iss/iss_video.h b/drivers/staging/media/omap4iss/iss_video.h new file mode 100644 index 00000000000..9dccdb154e1 --- /dev/null +++ b/drivers/staging/media/omap4iss/iss_video.h @@ -0,0 +1,204 @@ +/* + * TI OMAP4 ISS V4L2 Driver - Generic video node + * + * Copyright (C) 2012 Texas Instruments, Inc. + * + * Author: Sergio Aguirre <sergio.a.aguirre@gmail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#ifndef OMAP4_ISS_VIDEO_H +#define OMAP4_ISS_VIDEO_H + +#include <linux/v4l2-mediabus.h> +#include <media/media-entity.h> +#include <media/v4l2-dev.h> +#include <media/v4l2-fh.h> +#include <media/videobuf2-core.h> +#include <media/videobuf2-dma-contig.h> + +#define ISS_VIDEO_DRIVER_NAME "issvideo" +#define ISS_VIDEO_DRIVER_VERSION "0.0.2" + +struct iss_device; +struct iss_video; +struct v4l2_mbus_framefmt; +struct v4l2_pix_format; + +/* + * struct iss_format_info - ISS media bus format information + * @code: V4L2 media bus format code + * @truncated: V4L2 media bus format code for the same format truncated to 10 + * bits. Identical to @code if the format is 10 bits wide or less. + * @uncompressed: V4L2 media bus format code for the corresponding uncompressed + * format. Identical to @code if the format is not DPCM compressed. + * @flavor: V4L2 media bus format code for the same pixel layout but + * shifted to be 8 bits per pixel. =0 if format is not shiftable. + * @pixelformat: V4L2 pixel format FCC identifier + * @bpp: Bits per pixel + * @description: Human-readable format description + */ +struct iss_format_info { + enum v4l2_mbus_pixelcode code; + enum v4l2_mbus_pixelcode truncated; + enum v4l2_mbus_pixelcode uncompressed; + enum v4l2_mbus_pixelcode flavor; + u32 pixelformat; + unsigned int bpp; + const char *description; +}; + +enum iss_pipeline_stream_state { + ISS_PIPELINE_STREAM_STOPPED = 0, + ISS_PIPELINE_STREAM_CONTINUOUS = 1, + ISS_PIPELINE_STREAM_SINGLESHOT = 2, +}; + +enum iss_pipeline_state { + /* The stream has been started on the input video node. */ + ISS_PIPELINE_STREAM_INPUT = 1, + /* The stream has been started on the output video node. */ + ISS_PIPELINE_STREAM_OUTPUT = (1 << 1), + /* At least one buffer is queued on the input video node. */ + ISS_PIPELINE_QUEUE_INPUT = (1 << 2), + /* At least one buffer is queued on the output video node. */ + ISS_PIPELINE_QUEUE_OUTPUT = (1 << 3), + /* The input entity is idle, ready to be started. */ + ISS_PIPELINE_IDLE_INPUT = (1 << 4), + /* The output entity is idle, ready to be started. */ + ISS_PIPELINE_IDLE_OUTPUT = (1 << 5), + /* The pipeline is currently streaming. */ + ISS_PIPELINE_STREAM = (1 << 6), +}; + +/* + * struct iss_pipeline - An OMAP4 ISS hardware pipeline + * @entities: Bitmask of entities in the pipeline (indexed by entity ID) + * @error: A hardware error occurred during capture + */ +struct iss_pipeline { + struct media_pipeline pipe; + spinlock_t lock; /* Pipeline state and queue flags */ + unsigned int state; + enum iss_pipeline_stream_state stream_state; + struct iss_video *input; + struct iss_video *output; + unsigned int entities; + atomic_t frame_number; + bool do_propagation; /* of frame number */ + bool error; + struct v4l2_fract max_timeperframe; + struct v4l2_subdev *external; + unsigned int external_rate; + int external_bpp; +}; + +#define to_iss_pipeline(__e) \ + container_of((__e)->pipe, struct iss_pipeline, pipe) + +static inline int iss_pipeline_ready(struct iss_pipeline *pipe) +{ + return pipe->state == (ISS_PIPELINE_STREAM_INPUT | + ISS_PIPELINE_STREAM_OUTPUT | + ISS_PIPELINE_QUEUE_INPUT | + ISS_PIPELINE_QUEUE_OUTPUT | + ISS_PIPELINE_IDLE_INPUT | + ISS_PIPELINE_IDLE_OUTPUT); +} + +/* + * struct iss_buffer - ISS buffer + * @buffer: ISS video buffer + * @iss_addr: Physical address of the buffer. + */ +struct iss_buffer { + /* common v4l buffer stuff -- must be first */ + struct vb2_buffer vb; + struct list_head list; + dma_addr_t iss_addr; +}; + +#define to_iss_buffer(buf) container_of(buf, struct iss_buffer, buffer) + +enum iss_video_dmaqueue_flags { + /* Set if DMA queue becomes empty when ISS_PIPELINE_STREAM_CONTINUOUS */ + ISS_VIDEO_DMAQUEUE_UNDERRUN = (1 << 0), + /* Set when queuing buffer to an empty DMA queue */ + ISS_VIDEO_DMAQUEUE_QUEUED = (1 << 1), +}; + +#define iss_video_dmaqueue_flags_clr(video) \ + ({ (video)->dmaqueue_flags = 0; }) + +/* + * struct iss_video_operations - ISS video operations + * @queue: Resume streaming when a buffer is queued. Called on VIDIOC_QBUF + * if there was no buffer previously queued. + */ +struct iss_video_operations { + int (*queue)(struct iss_video *video, struct iss_buffer *buffer); +}; + +struct iss_video { + struct video_device video; + enum v4l2_buf_type type; + struct media_pad pad; + + struct mutex mutex; /* format and crop settings */ + atomic_t active; + + struct iss_device *iss; + + unsigned int capture_mem; + unsigned int bpl_alignment; /* alignment value */ + unsigned int bpl_zero_padding; /* whether the alignment is optional */ + unsigned int bpl_max; /* maximum bytes per line value */ + unsigned int bpl_value; /* bytes per line value */ + unsigned int bpl_padding; /* padding at end of line */ + + /* Pipeline state */ + struct iss_pipeline pipe; + struct mutex stream_lock; /* pipeline and stream states */ + bool error; + + /* Video buffers queue */ + struct vb2_queue *queue; + spinlock_t qlock; /* protects dmaqueue and error */ + struct list_head dmaqueue; + enum iss_video_dmaqueue_flags dmaqueue_flags; + struct vb2_alloc_ctx *alloc_ctx; + + const struct iss_video_operations *ops; +}; + +#define to_iss_video(vdev) container_of(vdev, struct iss_video, video) + +struct iss_video_fh { + struct v4l2_fh vfh; + struct iss_video *video; + struct vb2_queue queue; + struct v4l2_format format; + struct v4l2_fract timeperframe; +}; + +#define to_iss_video_fh(fh) container_of(fh, struct iss_video_fh, vfh) +#define iss_video_queue_to_iss_video_fh(q) \ + container_of(q, struct iss_video_fh, queue) + +int omap4iss_video_init(struct iss_video *video, const char *name); +void omap4iss_video_cleanup(struct iss_video *video); +int omap4iss_video_register(struct iss_video *video, + struct v4l2_device *vdev); +void omap4iss_video_unregister(struct iss_video *video); +struct iss_buffer *omap4iss_video_buffer_next(struct iss_video *video); +void omap4iss_video_cancel_stream(struct iss_video *video); +struct media_pad *omap4iss_video_remote_pad(struct iss_video *video); + +const struct iss_format_info * +omap4iss_video_format_info(enum v4l2_mbus_pixelcode code); + +#endif /* OMAP4_ISS_VIDEO_H */ diff --git a/drivers/staging/media/rtl2832u_sdr/Kconfig b/drivers/staging/media/rtl2832u_sdr/Kconfig new file mode 100644 index 00000000000..3ede5fe8f0a --- /dev/null +++ b/drivers/staging/media/rtl2832u_sdr/Kconfig @@ -0,0 +1,7 @@ +config DVB_RTL2832_SDR + tristate "Realtek RTL2832 SDR" + depends on USB && DVB_CORE && I2C && VIDEO_V4L2 && DVB_USB_RTL28XXU + select DVB_RTL2832 + select VIDEOBUF2_VMALLOC + default m if !MEDIA_SUBDRV_AUTOSELECT + diff --git a/drivers/staging/media/rtl2832u_sdr/Makefile b/drivers/staging/media/rtl2832u_sdr/Makefile new file mode 100644 index 00000000000..7e00a0df463 --- /dev/null +++ b/drivers/staging/media/rtl2832u_sdr/Makefile @@ -0,0 +1,6 @@ +obj-$(CONFIG_DVB_RTL2832_SDR) += rtl2832_sdr.o + +ccflags-y += -Idrivers/media/dvb-core +ccflags-y += -Idrivers/media/dvb-frontends +ccflags-y += -Idrivers/media/tuners +ccflags-y += -Idrivers/media/usb/dvb-usb-v2 diff --git a/drivers/staging/media/rtl2832u_sdr/rtl2832_sdr.c b/drivers/staging/media/rtl2832u_sdr/rtl2832_sdr.c new file mode 100644 index 00000000000..093df6b6ae3 --- /dev/null +++ b/drivers/staging/media/rtl2832u_sdr/rtl2832_sdr.c @@ -0,0 +1,1497 @@ +/* + * Realtek RTL2832U SDR driver + * + * Copyright (C) 2013 Antti Palosaari <crope@iki.fi> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * GNU Radio plugin "gr-kernel" for device usage will be on: + * http://git.linuxtv.org/anttip/gr-kernel.git + * + */ + +#include "dvb_frontend.h" +#include "rtl2832_sdr.h" +#include "dvb_usb.h" + +#include <media/v4l2-device.h> +#include <media/v4l2-ioctl.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-event.h> +#include <media/videobuf2-vmalloc.h> + +#include <linux/jiffies.h> +#include <linux/math64.h> + +#define MAX_BULK_BUFS (10) +#define BULK_BUFFER_SIZE (128 * 512) + +static const struct v4l2_frequency_band bands_adc[] = { + { + .tuner = 0, + .type = V4L2_TUNER_ADC, + .index = 0, + .capability = V4L2_TUNER_CAP_1HZ | V4L2_TUNER_CAP_FREQ_BANDS, + .rangelow = 300000, + .rangehigh = 300000, + }, + { + .tuner = 0, + .type = V4L2_TUNER_ADC, + .index = 1, + .capability = V4L2_TUNER_CAP_1HZ | V4L2_TUNER_CAP_FREQ_BANDS, + .rangelow = 900001, + .rangehigh = 2800000, + }, + { + .tuner = 0, + .type = V4L2_TUNER_ADC, + .index = 2, + .capability = V4L2_TUNER_CAP_1HZ | V4L2_TUNER_CAP_FREQ_BANDS, + .rangelow = 3200000, + .rangehigh = 3200000, + }, +}; + +static const struct v4l2_frequency_band bands_fm[] = { + { + .tuner = 1, + .type = V4L2_TUNER_RF, + .index = 0, + .capability = V4L2_TUNER_CAP_1HZ | V4L2_TUNER_CAP_FREQ_BANDS, + .rangelow = 50000000, + .rangehigh = 2000000000, + }, +}; + +/* stream formats */ +struct rtl2832_sdr_format { + char *name; + u32 pixelformat; +}; + +static struct rtl2832_sdr_format formats[] = { + { + .name = "IQ U8", + .pixelformat = V4L2_SDR_FMT_CU8, + }, { + .name = "IQ U16LE (emulated)", + .pixelformat = V4L2_SDR_FMT_CU16LE, + }, +}; + +static const unsigned int NUM_FORMATS = ARRAY_SIZE(formats); + +/* intermediate buffers with raw data from the USB device */ +struct rtl2832_sdr_frame_buf { + struct vb2_buffer vb; /* common v4l buffer stuff -- must be first */ + struct list_head list; +}; + +struct rtl2832_sdr_state { +#define POWER_ON (1 << 1) +#define URB_BUF (1 << 2) + unsigned long flags; + + const struct rtl2832_config *cfg; + struct dvb_frontend *fe; + struct dvb_usb_device *d; + struct i2c_adapter *i2c; + u8 bank; + + struct video_device vdev; + struct v4l2_device v4l2_dev; + + /* videobuf2 queue and queued buffers list */ + struct vb2_queue vb_queue; + struct list_head queued_bufs; + spinlock_t queued_bufs_lock; /* Protects queued_bufs */ + unsigned sequence; /* buffer sequence counter */ + + /* Note if taking both locks v4l2_lock must always be locked first! */ + struct mutex v4l2_lock; /* Protects everything else */ + struct mutex vb_queue_lock; /* Protects vb_queue and capt_file */ + + /* Pointer to our usb_device, will be NULL after unplug */ + struct usb_device *udev; /* Both mutexes most be hold when setting! */ + + unsigned int vb_full; /* vb is full and packets dropped */ + + struct urb *urb_list[MAX_BULK_BUFS]; + int buf_num; + unsigned long buf_size; + u8 *buf_list[MAX_BULK_BUFS]; + dma_addr_t dma_addr[MAX_BULK_BUFS]; + int urbs_initialized; + int urbs_submitted; + + unsigned int f_adc, f_tuner; + u32 pixelformat; + + /* Controls */ + struct v4l2_ctrl_handler hdl; + struct v4l2_ctrl *bandwidth_auto; + struct v4l2_ctrl *bandwidth; + + /* for sample rate calc */ + unsigned int sample; + unsigned int sample_measured; + unsigned long jiffies_next; +}; + +/* write multiple hardware registers */ +static int rtl2832_sdr_wr(struct rtl2832_sdr_state *s, u8 reg, const u8 *val, + int len) +{ + int ret; +#define MAX_WR_LEN 24 +#define MAX_WR_XFER_LEN (MAX_WR_LEN + 1) + u8 buf[MAX_WR_XFER_LEN]; + struct i2c_msg msg[1] = { + { + .addr = s->cfg->i2c_addr, + .flags = 0, + .len = 1 + len, + .buf = buf, + } + }; + + if (WARN_ON(len > MAX_WR_LEN)) + return -EINVAL; + + buf[0] = reg; + memcpy(&buf[1], val, len); + + ret = i2c_transfer(s->i2c, msg, 1); + if (ret == 1) { + ret = 0; + } else { + dev_err(&s->i2c->dev, + "%s: I2C wr failed=%d reg=%02x len=%d\n", + KBUILD_MODNAME, ret, reg, len); + ret = -EREMOTEIO; + } + return ret; +} + +/* read multiple hardware registers */ +static int rtl2832_sdr_rd(struct rtl2832_sdr_state *s, u8 reg, u8 *val, int len) +{ + int ret; + struct i2c_msg msg[2] = { + { + .addr = s->cfg->i2c_addr, + .flags = 0, + .len = 1, + .buf = ®, + }, { + .addr = s->cfg->i2c_addr, + .flags = I2C_M_RD, + .len = len, + .buf = val, + } + }; + + ret = i2c_transfer(s->i2c, msg, 2); + if (ret == 2) { + ret = 0; + } else { + dev_err(&s->i2c->dev, + "%s: I2C rd failed=%d reg=%02x len=%d\n", + KBUILD_MODNAME, ret, reg, len); + ret = -EREMOTEIO; + } + return ret; +} + +/* write multiple registers */ +static int rtl2832_sdr_wr_regs(struct rtl2832_sdr_state *s, u16 reg, + const u8 *val, int len) +{ + int ret; + u8 reg2 = (reg >> 0) & 0xff; + u8 bank = (reg >> 8) & 0xff; + + /* switch bank if needed */ + if (bank != s->bank) { + ret = rtl2832_sdr_wr(s, 0x00, &bank, 1); + if (ret) + return ret; + + s->bank = bank; + } + + return rtl2832_sdr_wr(s, reg2, val, len); +} + +/* read multiple registers */ +static int rtl2832_sdr_rd_regs(struct rtl2832_sdr_state *s, u16 reg, u8 *val, + int len) +{ + int ret; + u8 reg2 = (reg >> 0) & 0xff; + u8 bank = (reg >> 8) & 0xff; + + /* switch bank if needed */ + if (bank != s->bank) { + ret = rtl2832_sdr_wr(s, 0x00, &bank, 1); + if (ret) + return ret; + + s->bank = bank; + } + + return rtl2832_sdr_rd(s, reg2, val, len); +} + +/* write single register */ +static int rtl2832_sdr_wr_reg(struct rtl2832_sdr_state *s, u16 reg, u8 val) +{ + return rtl2832_sdr_wr_regs(s, reg, &val, 1); +} + +#if 0 +/* read single register */ +static int rtl2832_sdr_rd_reg(struct rtl2832_sdr_state *s, u16 reg, u8 *val) +{ + return rtl2832_sdr_rd_regs(s, reg, val, 1); +} +#endif + +/* write single register with mask */ +static int rtl2832_sdr_wr_reg_mask(struct rtl2832_sdr_state *s, u16 reg, + u8 val, u8 mask) +{ + int ret; + u8 tmp; + + /* no need for read if whole reg is written */ + if (mask != 0xff) { + ret = rtl2832_sdr_rd_regs(s, reg, &tmp, 1); + if (ret) + return ret; + + val &= mask; + tmp &= ~mask; + val |= tmp; + } + + return rtl2832_sdr_wr_regs(s, reg, &val, 1); +} + +#if 0 +/* read single register with mask */ +static int rtl2832_sdr_rd_reg_mask(struct rtl2832_sdr_state *s, u16 reg, + u8 *val, u8 mask) +{ + int ret, i; + u8 tmp; + + ret = rtl2832_sdr_rd_regs(s, reg, &tmp, 1); + if (ret) + return ret; + + tmp &= mask; + + /* find position of the first bit */ + for (i = 0; i < 8; i++) { + if ((mask >> i) & 0x01) + break; + } + *val = tmp >> i; + + return 0; +} +#endif + +/* Private functions */ +static struct rtl2832_sdr_frame_buf *rtl2832_sdr_get_next_fill_buf( + struct rtl2832_sdr_state *s) +{ + unsigned long flags = 0; + struct rtl2832_sdr_frame_buf *buf = NULL; + + spin_lock_irqsave(&s->queued_bufs_lock, flags); + if (list_empty(&s->queued_bufs)) + goto leave; + + buf = list_entry(s->queued_bufs.next, + struct rtl2832_sdr_frame_buf, list); + list_del(&buf->list); +leave: + spin_unlock_irqrestore(&s->queued_bufs_lock, flags); + return buf; +} + +static unsigned int rtl2832_sdr_convert_stream(struct rtl2832_sdr_state *s, + void *dst, const u8 *src, unsigned int src_len) +{ + unsigned int dst_len; + + if (s->pixelformat == V4L2_SDR_FMT_CU8) { + /* native stream, no need to convert */ + memcpy(dst, src, src_len); + dst_len = src_len; + } else if (s->pixelformat == V4L2_SDR_FMT_CU16LE) { + /* convert u8 to u16 */ + unsigned int i; + u16 *u16dst = dst; + for (i = 0; i < src_len; i++) + *u16dst++ = (src[i] << 8) | (src[i] >> 0); + dst_len = 2 * src_len; + } else { + dst_len = 0; + } + + /* calculate samping rate and output it in 10 seconds intervals */ + if (unlikely(time_is_before_jiffies(s->jiffies_next))) { +#define MSECS 10000UL + unsigned int samples = s->sample - s->sample_measured; + s->jiffies_next = jiffies + msecs_to_jiffies(MSECS); + s->sample_measured = s->sample; + dev_dbg(&s->udev->dev, + "slen=%d samples=%u msecs=%lu sampling rate=%lu\n", + src_len, samples, MSECS, + samples * 1000UL / MSECS); + } + + /* total number of I+Q pairs */ + s->sample += src_len / 2; + + return dst_len; +} + +/* + * This gets called for the bulk stream pipe. This is done in interrupt + * time, so it has to be fast, not crash, and not stall. Neat. + */ +static void rtl2832_sdr_urb_complete(struct urb *urb) +{ + struct rtl2832_sdr_state *s = urb->context; + struct rtl2832_sdr_frame_buf *fbuf; + + dev_dbg_ratelimited(&s->udev->dev, + "%s: status=%d length=%d/%d errors=%d\n", + __func__, urb->status, urb->actual_length, + urb->transfer_buffer_length, urb->error_count); + + switch (urb->status) { + case 0: /* success */ + case -ETIMEDOUT: /* NAK */ + break; + case -ECONNRESET: /* kill */ + case -ENOENT: + case -ESHUTDOWN: + return; + default: /* error */ + dev_err_ratelimited(&s->udev->dev, "urb failed=%d\n", + urb->status); + break; + } + + if (likely(urb->actual_length > 0)) { + void *ptr; + unsigned int len; + /* get free framebuffer */ + fbuf = rtl2832_sdr_get_next_fill_buf(s); + if (unlikely(fbuf == NULL)) { + s->vb_full++; + dev_notice_ratelimited(&s->udev->dev, + "videobuf is full, %d packets dropped\n", + s->vb_full); + goto skip; + } + + /* fill framebuffer */ + ptr = vb2_plane_vaddr(&fbuf->vb, 0); + len = rtl2832_sdr_convert_stream(s, ptr, urb->transfer_buffer, + urb->actual_length); + vb2_set_plane_payload(&fbuf->vb, 0, len); + v4l2_get_timestamp(&fbuf->vb.v4l2_buf.timestamp); + fbuf->vb.v4l2_buf.sequence = s->sequence++; + vb2_buffer_done(&fbuf->vb, VB2_BUF_STATE_DONE); + } +skip: + usb_submit_urb(urb, GFP_ATOMIC); +} + +static int rtl2832_sdr_kill_urbs(struct rtl2832_sdr_state *s) +{ + int i; + + for (i = s->urbs_submitted - 1; i >= 0; i--) { + dev_dbg(&s->udev->dev, "%s: kill urb=%d\n", __func__, i); + /* stop the URB */ + usb_kill_urb(s->urb_list[i]); + } + s->urbs_submitted = 0; + + return 0; +} + +static int rtl2832_sdr_submit_urbs(struct rtl2832_sdr_state *s) +{ + int i, ret; + + for (i = 0; i < s->urbs_initialized; i++) { + dev_dbg(&s->udev->dev, "%s: submit urb=%d\n", __func__, i); + ret = usb_submit_urb(s->urb_list[i], GFP_ATOMIC); + if (ret) { + dev_err(&s->udev->dev, + "Could not submit urb no. %d - get them all back\n", + i); + rtl2832_sdr_kill_urbs(s); + return ret; + } + s->urbs_submitted++; + } + + return 0; +} + +static int rtl2832_sdr_free_stream_bufs(struct rtl2832_sdr_state *s) +{ + if (s->flags & USB_STATE_URB_BUF) { + while (s->buf_num) { + s->buf_num--; + dev_dbg(&s->udev->dev, "%s: free buf=%d\n", + __func__, s->buf_num); + usb_free_coherent(s->udev, s->buf_size, + s->buf_list[s->buf_num], + s->dma_addr[s->buf_num]); + } + } + s->flags &= ~USB_STATE_URB_BUF; + + return 0; +} + +static int rtl2832_sdr_alloc_stream_bufs(struct rtl2832_sdr_state *s) +{ + s->buf_num = 0; + s->buf_size = BULK_BUFFER_SIZE; + + dev_dbg(&s->udev->dev, + "%s: all in all I will use %u bytes for streaming\n", + __func__, MAX_BULK_BUFS * BULK_BUFFER_SIZE); + + for (s->buf_num = 0; s->buf_num < MAX_BULK_BUFS; s->buf_num++) { + s->buf_list[s->buf_num] = usb_alloc_coherent(s->udev, + BULK_BUFFER_SIZE, GFP_ATOMIC, + &s->dma_addr[s->buf_num]); + if (!s->buf_list[s->buf_num]) { + dev_dbg(&s->udev->dev, "%s: alloc buf=%d failed\n", + __func__, s->buf_num); + rtl2832_sdr_free_stream_bufs(s); + return -ENOMEM; + } + + dev_dbg(&s->udev->dev, "%s: alloc buf=%d %p (dma %llu)\n", + __func__, s->buf_num, + s->buf_list[s->buf_num], + (long long)s->dma_addr[s->buf_num]); + s->flags |= USB_STATE_URB_BUF; + } + + return 0; +} + +static int rtl2832_sdr_free_urbs(struct rtl2832_sdr_state *s) +{ + int i; + + rtl2832_sdr_kill_urbs(s); + + for (i = s->urbs_initialized - 1; i >= 0; i--) { + if (s->urb_list[i]) { + dev_dbg(&s->udev->dev, "%s: free urb=%d\n", + __func__, i); + /* free the URBs */ + usb_free_urb(s->urb_list[i]); + } + } + s->urbs_initialized = 0; + + return 0; +} + +static int rtl2832_sdr_alloc_urbs(struct rtl2832_sdr_state *s) +{ + int i, j; + + /* allocate the URBs */ + for (i = 0; i < MAX_BULK_BUFS; i++) { + dev_dbg(&s->udev->dev, "%s: alloc urb=%d\n", __func__, i); + s->urb_list[i] = usb_alloc_urb(0, GFP_ATOMIC); + if (!s->urb_list[i]) { + dev_dbg(&s->udev->dev, "%s: failed\n", __func__); + for (j = 0; j < i; j++) + usb_free_urb(s->urb_list[j]); + return -ENOMEM; + } + usb_fill_bulk_urb(s->urb_list[i], + s->udev, + usb_rcvbulkpipe(s->udev, 0x81), + s->buf_list[i], + BULK_BUFFER_SIZE, + rtl2832_sdr_urb_complete, s); + + s->urb_list[i]->transfer_flags = URB_NO_TRANSFER_DMA_MAP; + s->urb_list[i]->transfer_dma = s->dma_addr[i]; + s->urbs_initialized++; + } + + return 0; +} + +/* Must be called with vb_queue_lock hold */ +static void rtl2832_sdr_cleanup_queued_bufs(struct rtl2832_sdr_state *s) +{ + unsigned long flags = 0; + dev_dbg(&s->udev->dev, "%s:\n", __func__); + + spin_lock_irqsave(&s->queued_bufs_lock, flags); + while (!list_empty(&s->queued_bufs)) { + struct rtl2832_sdr_frame_buf *buf; + buf = list_entry(s->queued_bufs.next, + struct rtl2832_sdr_frame_buf, list); + list_del(&buf->list); + vb2_buffer_done(&buf->vb, VB2_BUF_STATE_ERROR); + } + spin_unlock_irqrestore(&s->queued_bufs_lock, flags); +} + +/* The user yanked out the cable... */ +static void rtl2832_sdr_release_sec(struct dvb_frontend *fe) +{ + struct rtl2832_sdr_state *s = fe->sec_priv; + dev_dbg(&s->udev->dev, "%s:\n", __func__); + + mutex_lock(&s->vb_queue_lock); + mutex_lock(&s->v4l2_lock); + /* No need to keep the urbs around after disconnection */ + s->udev = NULL; + + v4l2_device_disconnect(&s->v4l2_dev); + video_unregister_device(&s->vdev); + mutex_unlock(&s->v4l2_lock); + mutex_unlock(&s->vb_queue_lock); + + v4l2_device_put(&s->v4l2_dev); + + fe->sec_priv = NULL; +} + +static int rtl2832_sdr_querycap(struct file *file, void *fh, + struct v4l2_capability *cap) +{ + struct rtl2832_sdr_state *s = video_drvdata(file); + dev_dbg(&s->udev->dev, "%s:\n", __func__); + + strlcpy(cap->driver, KBUILD_MODNAME, sizeof(cap->driver)); + strlcpy(cap->card, s->vdev.name, sizeof(cap->card)); + usb_make_path(s->udev, cap->bus_info, sizeof(cap->bus_info)); + cap->device_caps = V4L2_CAP_SDR_CAPTURE | V4L2_CAP_STREAMING | + V4L2_CAP_READWRITE | V4L2_CAP_TUNER; + cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS; + return 0; +} + +/* Videobuf2 operations */ +static int rtl2832_sdr_queue_setup(struct vb2_queue *vq, + const struct v4l2_format *fmt, unsigned int *nbuffers, + unsigned int *nplanes, unsigned int sizes[], void *alloc_ctxs[]) +{ + struct rtl2832_sdr_state *s = vb2_get_drv_priv(vq); + dev_dbg(&s->udev->dev, "%s: *nbuffers=%d\n", __func__, *nbuffers); + + /* Need at least 8 buffers */ + if (vq->num_buffers + *nbuffers < 8) + *nbuffers = 8 - vq->num_buffers; + *nplanes = 1; + /* 2 = max 16-bit sample returned */ + sizes[0] = PAGE_ALIGN(BULK_BUFFER_SIZE * 2); + dev_dbg(&s->udev->dev, "%s: nbuffers=%d sizes[0]=%d\n", + __func__, *nbuffers, sizes[0]); + return 0; +} + +static int rtl2832_sdr_buf_prepare(struct vb2_buffer *vb) +{ + struct rtl2832_sdr_state *s = vb2_get_drv_priv(vb->vb2_queue); + + /* Don't allow queing new buffers after device disconnection */ + if (!s->udev) + return -ENODEV; + + return 0; +} + +static void rtl2832_sdr_buf_queue(struct vb2_buffer *vb) +{ + struct rtl2832_sdr_state *s = vb2_get_drv_priv(vb->vb2_queue); + struct rtl2832_sdr_frame_buf *buf = + container_of(vb, struct rtl2832_sdr_frame_buf, vb); + unsigned long flags = 0; + + /* Check the device has not disconnected between prep and queuing */ + if (!s->udev) { + vb2_buffer_done(&buf->vb, VB2_BUF_STATE_ERROR); + return; + } + + spin_lock_irqsave(&s->queued_bufs_lock, flags); + list_add_tail(&buf->list, &s->queued_bufs); + spin_unlock_irqrestore(&s->queued_bufs_lock, flags); +} + +static int rtl2832_sdr_set_adc(struct rtl2832_sdr_state *s) +{ + struct dvb_frontend *fe = s->fe; + int ret; + unsigned int f_sr, f_if; + u8 buf[4], u8tmp1, u8tmp2; + u64 u64tmp; + u32 u32tmp; + dev_dbg(&s->udev->dev, "%s: f_adc=%u\n", __func__, s->f_adc); + + if (!test_bit(POWER_ON, &s->flags)) + return 0; + + if (s->f_adc == 0) + return 0; + + f_sr = s->f_adc; + + ret = rtl2832_sdr_wr_regs(s, 0x13e, "\x00\x00", 2); + if (ret) + goto err; + + ret = rtl2832_sdr_wr_regs(s, 0x115, "\x00\x00\x00\x00", 4); + if (ret) + goto err; + + /* get IF from tuner */ + if (fe->ops.tuner_ops.get_if_frequency) + ret = fe->ops.tuner_ops.get_if_frequency(fe, &f_if); + else + ret = -EINVAL; + + if (ret) + goto err; + + /* program IF */ + u64tmp = f_if % s->cfg->xtal; + u64tmp *= 0x400000; + u64tmp = div_u64(u64tmp, s->cfg->xtal); + u64tmp = -u64tmp; + u32tmp = u64tmp & 0x3fffff; + + dev_dbg(&s->udev->dev, "%s: f_if=%u if_ctl=%08x\n", + __func__, f_if, u32tmp); + + buf[0] = (u32tmp >> 16) & 0xff; + buf[1] = (u32tmp >> 8) & 0xff; + buf[2] = (u32tmp >> 0) & 0xff; + + ret = rtl2832_sdr_wr_regs(s, 0x119, buf, 3); + if (ret) + goto err; + + /* BB / IF mode */ + /* POR: 0x1b1=0x1f, 0x008=0x0d, 0x006=0x80 */ + if (f_if) { + u8tmp1 = 0x1a; /* disable Zero-IF */ + u8tmp2 = 0x8d; /* enable ADC I */ + } else { + u8tmp1 = 0x1b; /* enable Zero-IF, DC, IQ */ + u8tmp2 = 0xcd; /* enable ADC I, ADC Q */ + } + + ret = rtl2832_sdr_wr_reg(s, 0x1b1, u8tmp1); + if (ret) + goto err; + + ret = rtl2832_sdr_wr_reg(s, 0x008, u8tmp2); + if (ret) + goto err; + + ret = rtl2832_sdr_wr_reg(s, 0x006, 0x80); + if (ret) + goto err; + + /* program sampling rate (resampling down) */ + u32tmp = div_u64(s->cfg->xtal * 0x400000ULL, f_sr * 4U); + u32tmp <<= 2; + buf[0] = (u32tmp >> 24) & 0xff; + buf[1] = (u32tmp >> 16) & 0xff; + buf[2] = (u32tmp >> 8) & 0xff; + buf[3] = (u32tmp >> 0) & 0xff; + ret = rtl2832_sdr_wr_regs(s, 0x19f, buf, 4); + if (ret) + goto err; + + /* low-pass filter */ + ret = rtl2832_sdr_wr_regs(s, 0x11c, + "\xca\xdc\xd7\xd8\xe0\xf2\x0e\x35\x06\x50\x9c\x0d\x71\x11\x14\x71\x74\x19\x41\xa5", + 20); + if (ret) + goto err; + + ret = rtl2832_sdr_wr_regs(s, 0x017, "\x11\x10", 2); + if (ret) + goto err; + + /* mode */ + ret = rtl2832_sdr_wr_regs(s, 0x019, "\x05", 1); + if (ret) + goto err; + + ret = rtl2832_sdr_wr_regs(s, 0x01a, "\x1b\x16\x0d\x06\x01\xff", 6); + if (ret) + goto err; + + /* FSM */ + ret = rtl2832_sdr_wr_regs(s, 0x192, "\x00\xf0\x0f", 3); + if (ret) + goto err; + + /* PID filter */ + ret = rtl2832_sdr_wr_regs(s, 0x061, "\x60", 1); + if (ret) + goto err; + + /* used RF tuner based settings */ + switch (s->cfg->tuner) { + case RTL2832_TUNER_E4000: + ret = rtl2832_sdr_wr_regs(s, 0x112, "\x5a", 1); + ret = rtl2832_sdr_wr_regs(s, 0x102, "\x40", 1); + ret = rtl2832_sdr_wr_regs(s, 0x103, "\x5a", 1); + ret = rtl2832_sdr_wr_regs(s, 0x1c7, "\x30", 1); + ret = rtl2832_sdr_wr_regs(s, 0x104, "\xd0", 1); + ret = rtl2832_sdr_wr_regs(s, 0x105, "\xbe", 1); + ret = rtl2832_sdr_wr_regs(s, 0x1c8, "\x18", 1); + ret = rtl2832_sdr_wr_regs(s, 0x106, "\x35", 1); + ret = rtl2832_sdr_wr_regs(s, 0x1c9, "\x21", 1); + ret = rtl2832_sdr_wr_regs(s, 0x1ca, "\x21", 1); + ret = rtl2832_sdr_wr_regs(s, 0x1cb, "\x00", 1); + ret = rtl2832_sdr_wr_regs(s, 0x107, "\x40", 1); + ret = rtl2832_sdr_wr_regs(s, 0x1cd, "\x10", 1); + ret = rtl2832_sdr_wr_regs(s, 0x1ce, "\x10", 1); + ret = rtl2832_sdr_wr_regs(s, 0x108, "\x80", 1); + ret = rtl2832_sdr_wr_regs(s, 0x109, "\x7f", 1); + ret = rtl2832_sdr_wr_regs(s, 0x10a, "\x80", 1); + ret = rtl2832_sdr_wr_regs(s, 0x10b, "\x7f", 1); + ret = rtl2832_sdr_wr_regs(s, 0x00e, "\xfc", 1); + ret = rtl2832_sdr_wr_regs(s, 0x00e, "\xfc", 1); + ret = rtl2832_sdr_wr_regs(s, 0x011, "\xd4", 1); + ret = rtl2832_sdr_wr_regs(s, 0x1e5, "\xf0", 1); + ret = rtl2832_sdr_wr_regs(s, 0x1d9, "\x00", 1); + ret = rtl2832_sdr_wr_regs(s, 0x1db, "\x00", 1); + ret = rtl2832_sdr_wr_regs(s, 0x1dd, "\x14", 1); + ret = rtl2832_sdr_wr_regs(s, 0x1de, "\xec", 1); + ret = rtl2832_sdr_wr_regs(s, 0x1d8, "\x0c", 1); + ret = rtl2832_sdr_wr_regs(s, 0x1e6, "\x02", 1); + ret = rtl2832_sdr_wr_regs(s, 0x1d7, "\x09", 1); + ret = rtl2832_sdr_wr_regs(s, 0x00d, "\x83", 1); + ret = rtl2832_sdr_wr_regs(s, 0x010, "\x49", 1); + ret = rtl2832_sdr_wr_regs(s, 0x00d, "\x87", 1); + ret = rtl2832_sdr_wr_regs(s, 0x00d, "\x85", 1); + ret = rtl2832_sdr_wr_regs(s, 0x013, "\x02", 1); + break; + case RTL2832_TUNER_FC0012: + case RTL2832_TUNER_FC0013: + ret = rtl2832_sdr_wr_regs(s, 0x112, "\x5a", 1); + ret = rtl2832_sdr_wr_regs(s, 0x102, "\x40", 1); + ret = rtl2832_sdr_wr_regs(s, 0x103, "\x5a", 1); + ret = rtl2832_sdr_wr_regs(s, 0x1c7, "\x2c", 1); + ret = rtl2832_sdr_wr_regs(s, 0x104, "\xcc", 1); + ret = rtl2832_sdr_wr_regs(s, 0x105, "\xbe", 1); + ret = rtl2832_sdr_wr_regs(s, 0x1c8, "\x16", 1); + ret = rtl2832_sdr_wr_regs(s, 0x106, "\x35", 1); + ret = rtl2832_sdr_wr_regs(s, 0x1c9, "\x21", 1); + ret = rtl2832_sdr_wr_regs(s, 0x1ca, "\x21", 1); + ret = rtl2832_sdr_wr_regs(s, 0x1cb, "\x00", 1); + ret = rtl2832_sdr_wr_regs(s, 0x107, "\x40", 1); + ret = rtl2832_sdr_wr_regs(s, 0x1cd, "\x10", 1); + ret = rtl2832_sdr_wr_regs(s, 0x1ce, "\x10", 1); + ret = rtl2832_sdr_wr_regs(s, 0x108, "\x80", 1); + ret = rtl2832_sdr_wr_regs(s, 0x109, "\x7f", 1); + ret = rtl2832_sdr_wr_regs(s, 0x10a, "\x80", 1); + ret = rtl2832_sdr_wr_regs(s, 0x10b, "\x7f", 1); + ret = rtl2832_sdr_wr_regs(s, 0x00e, "\xfc", 1); + ret = rtl2832_sdr_wr_regs(s, 0x00e, "\xfc", 1); + ret = rtl2832_sdr_wr_regs(s, 0x011, "\xe9\xbf", 2); + ret = rtl2832_sdr_wr_regs(s, 0x1e5, "\xf0", 1); + ret = rtl2832_sdr_wr_regs(s, 0x1d9, "\x00", 1); + ret = rtl2832_sdr_wr_regs(s, 0x1db, "\x00", 1); + ret = rtl2832_sdr_wr_regs(s, 0x1dd, "\x11", 1); + ret = rtl2832_sdr_wr_regs(s, 0x1de, "\xef", 1); + ret = rtl2832_sdr_wr_regs(s, 0x1d8, "\x0c", 1); + ret = rtl2832_sdr_wr_regs(s, 0x1e6, "\x02", 1); + ret = rtl2832_sdr_wr_regs(s, 0x1d7, "\x09", 1); + break; + case RTL2832_TUNER_R820T: + ret = rtl2832_sdr_wr_regs(s, 0x112, "\x5a", 1); + ret = rtl2832_sdr_wr_regs(s, 0x102, "\x40", 1); + ret = rtl2832_sdr_wr_regs(s, 0x115, "\x01", 1); + ret = rtl2832_sdr_wr_regs(s, 0x103, "\x80", 1); + ret = rtl2832_sdr_wr_regs(s, 0x1c7, "\x24", 1); + ret = rtl2832_sdr_wr_regs(s, 0x104, "\xcc", 1); + ret = rtl2832_sdr_wr_regs(s, 0x105, "\xbe", 1); + ret = rtl2832_sdr_wr_regs(s, 0x1c8, "\x14", 1); + ret = rtl2832_sdr_wr_regs(s, 0x106, "\x35", 1); + ret = rtl2832_sdr_wr_regs(s, 0x1c9, "\x21", 1); + ret = rtl2832_sdr_wr_regs(s, 0x1ca, "\x21", 1); + ret = rtl2832_sdr_wr_regs(s, 0x1cb, "\x00", 1); + ret = rtl2832_sdr_wr_regs(s, 0x107, "\x40", 1); + ret = rtl2832_sdr_wr_regs(s, 0x1cd, "\x10", 1); + ret = rtl2832_sdr_wr_regs(s, 0x1ce, "\x10", 1); + ret = rtl2832_sdr_wr_regs(s, 0x108, "\x80", 1); + ret = rtl2832_sdr_wr_regs(s, 0x109, "\x7f", 1); + ret = rtl2832_sdr_wr_regs(s, 0x10a, "\x80", 1); + ret = rtl2832_sdr_wr_regs(s, 0x10b, "\x7f", 1); + ret = rtl2832_sdr_wr_regs(s, 0x00e, "\xfc", 1); + ret = rtl2832_sdr_wr_regs(s, 0x00e, "\xfc", 1); + ret = rtl2832_sdr_wr_regs(s, 0x011, "\xf4", 1); + break; + default: + dev_notice(&s->udev->dev, "Unsupported tuner\n"); + } + + /* software reset */ + ret = rtl2832_sdr_wr_reg_mask(s, 0x101, 0x04, 0x04); + if (ret) + goto err; + + ret = rtl2832_sdr_wr_reg_mask(s, 0x101, 0x00, 0x04); + if (ret) + goto err; +err: + return ret; +}; + +static void rtl2832_sdr_unset_adc(struct rtl2832_sdr_state *s) +{ + int ret; + + dev_dbg(&s->udev->dev, "%s:\n", __func__); + + /* PID filter */ + ret = rtl2832_sdr_wr_regs(s, 0x061, "\xe0", 1); + if (ret) + goto err; + + /* mode */ + ret = rtl2832_sdr_wr_regs(s, 0x019, "\x20", 1); + if (ret) + goto err; + + ret = rtl2832_sdr_wr_regs(s, 0x017, "\x11\x10", 2); + if (ret) + goto err; + + /* FSM */ + ret = rtl2832_sdr_wr_regs(s, 0x192, "\x00\x0f\xff", 3); + if (ret) + goto err; + + ret = rtl2832_sdr_wr_regs(s, 0x13e, "\x40\x00", 2); + if (ret) + goto err; + + ret = rtl2832_sdr_wr_regs(s, 0x115, "\x06\x3f\xce\xcc", 4); + if (ret) + goto err; +err: + return; +}; + +static int rtl2832_sdr_set_tuner_freq(struct rtl2832_sdr_state *s) +{ + struct dvb_frontend *fe = s->fe; + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + struct v4l2_ctrl *bandwidth_auto; + struct v4l2_ctrl *bandwidth; + + /* + * tuner RF (Hz) + */ + if (s->f_tuner == 0) + return 0; + + /* + * bandwidth (Hz) + */ + bandwidth_auto = v4l2_ctrl_find(&s->hdl, V4L2_CID_RF_TUNER_BANDWIDTH_AUTO); + bandwidth = v4l2_ctrl_find(&s->hdl, V4L2_CID_RF_TUNER_BANDWIDTH); + if (v4l2_ctrl_g_ctrl(bandwidth_auto)) { + c->bandwidth_hz = s->f_adc; + v4l2_ctrl_s_ctrl(bandwidth, s->f_adc); + } else { + c->bandwidth_hz = v4l2_ctrl_g_ctrl(bandwidth); + } + + c->frequency = s->f_tuner; + c->delivery_system = SYS_DVBT; + + dev_dbg(&s->udev->dev, "%s: frequency=%u bandwidth=%d\n", + __func__, c->frequency, c->bandwidth_hz); + + if (!test_bit(POWER_ON, &s->flags)) + return 0; + + if (fe->ops.tuner_ops.set_params) + fe->ops.tuner_ops.set_params(fe); + + return 0; +}; + +static int rtl2832_sdr_set_tuner(struct rtl2832_sdr_state *s) +{ + struct dvb_frontend *fe = s->fe; + + dev_dbg(&s->udev->dev, "%s:\n", __func__); + + if (fe->ops.tuner_ops.init) + fe->ops.tuner_ops.init(fe); + + return 0; +}; + +static void rtl2832_sdr_unset_tuner(struct rtl2832_sdr_state *s) +{ + struct dvb_frontend *fe = s->fe; + + dev_dbg(&s->udev->dev, "%s:\n", __func__); + + if (fe->ops.tuner_ops.sleep) + fe->ops.tuner_ops.sleep(fe); + + return; +}; + +static int rtl2832_sdr_start_streaming(struct vb2_queue *vq, unsigned int count) +{ + struct rtl2832_sdr_state *s = vb2_get_drv_priv(vq); + int ret; + dev_dbg(&s->udev->dev, "%s:\n", __func__); + + if (!s->udev) + return -ENODEV; + + if (mutex_lock_interruptible(&s->v4l2_lock)) + return -ERESTARTSYS; + + if (s->d->props->power_ctrl) + s->d->props->power_ctrl(s->d, 1); + + set_bit(POWER_ON, &s->flags); + + ret = rtl2832_sdr_set_tuner(s); + if (ret) + goto err; + + ret = rtl2832_sdr_set_tuner_freq(s); + if (ret) + goto err; + + ret = rtl2832_sdr_set_adc(s); + if (ret) + goto err; + + ret = rtl2832_sdr_alloc_stream_bufs(s); + if (ret) + goto err; + + ret = rtl2832_sdr_alloc_urbs(s); + if (ret) + goto err; + + s->sequence = 0; + + ret = rtl2832_sdr_submit_urbs(s); + if (ret) + goto err; + +err: + mutex_unlock(&s->v4l2_lock); + + return ret; +} + +static void rtl2832_sdr_stop_streaming(struct vb2_queue *vq) +{ + struct rtl2832_sdr_state *s = vb2_get_drv_priv(vq); + dev_dbg(&s->udev->dev, "%s:\n", __func__); + + mutex_lock(&s->v4l2_lock); + + rtl2832_sdr_kill_urbs(s); + rtl2832_sdr_free_urbs(s); + rtl2832_sdr_free_stream_bufs(s); + rtl2832_sdr_cleanup_queued_bufs(s); + rtl2832_sdr_unset_adc(s); + rtl2832_sdr_unset_tuner(s); + + clear_bit(POWER_ON, &s->flags); + + if (s->d->props->power_ctrl) + s->d->props->power_ctrl(s->d, 0); + + mutex_unlock(&s->v4l2_lock); +} + +static struct vb2_ops rtl2832_sdr_vb2_ops = { + .queue_setup = rtl2832_sdr_queue_setup, + .buf_prepare = rtl2832_sdr_buf_prepare, + .buf_queue = rtl2832_sdr_buf_queue, + .start_streaming = rtl2832_sdr_start_streaming, + .stop_streaming = rtl2832_sdr_stop_streaming, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, +}; + +static int rtl2832_sdr_g_tuner(struct file *file, void *priv, + struct v4l2_tuner *v) +{ + struct rtl2832_sdr_state *s = video_drvdata(file); + dev_dbg(&s->udev->dev, "%s: index=%d type=%d\n", + __func__, v->index, v->type); + + if (v->index == 0) { + strlcpy(v->name, "ADC: Realtek RTL2832", sizeof(v->name)); + v->type = V4L2_TUNER_ADC; + v->capability = V4L2_TUNER_CAP_1HZ | V4L2_TUNER_CAP_FREQ_BANDS; + v->rangelow = 300000; + v->rangehigh = 3200000; + } else if (v->index == 1) { + strlcpy(v->name, "RF: <unknown>", sizeof(v->name)); + v->type = V4L2_TUNER_RF; + v->capability = V4L2_TUNER_CAP_1HZ | V4L2_TUNER_CAP_FREQ_BANDS; + v->rangelow = 50000000; + v->rangehigh = 2000000000; + } else { + return -EINVAL; + } + + return 0; +} + +static int rtl2832_sdr_s_tuner(struct file *file, void *priv, + const struct v4l2_tuner *v) +{ + struct rtl2832_sdr_state *s = video_drvdata(file); + dev_dbg(&s->udev->dev, "%s:\n", __func__); + + if (v->index > 1) + return -EINVAL; + return 0; +} + +static int rtl2832_sdr_enum_freq_bands(struct file *file, void *priv, + struct v4l2_frequency_band *band) +{ + struct rtl2832_sdr_state *s = video_drvdata(file); + dev_dbg(&s->udev->dev, "%s: tuner=%d type=%d index=%d\n", + __func__, band->tuner, band->type, band->index); + + if (band->tuner == 0) { + if (band->index >= ARRAY_SIZE(bands_adc)) + return -EINVAL; + + *band = bands_adc[band->index]; + } else if (band->tuner == 1) { + if (band->index >= ARRAY_SIZE(bands_fm)) + return -EINVAL; + + *band = bands_fm[band->index]; + } else { + return -EINVAL; + } + + return 0; +} + +static int rtl2832_sdr_g_frequency(struct file *file, void *priv, + struct v4l2_frequency *f) +{ + struct rtl2832_sdr_state *s = video_drvdata(file); + int ret = 0; + dev_dbg(&s->udev->dev, "%s: tuner=%d type=%d\n", + __func__, f->tuner, f->type); + + if (f->tuner == 0) { + f->frequency = s->f_adc; + f->type = V4L2_TUNER_ADC; + } else if (f->tuner == 1) { + f->frequency = s->f_tuner; + f->type = V4L2_TUNER_RF; + } else { + return -EINVAL; + } + + return ret; +} + +static int rtl2832_sdr_s_frequency(struct file *file, void *priv, + const struct v4l2_frequency *f) +{ + struct rtl2832_sdr_state *s = video_drvdata(file); + int ret, band; + + dev_dbg(&s->udev->dev, "%s: tuner=%d type=%d frequency=%u\n", + __func__, f->tuner, f->type, f->frequency); + + /* ADC band midpoints */ + #define BAND_ADC_0 ((bands_adc[0].rangehigh + bands_adc[1].rangelow) / 2) + #define BAND_ADC_1 ((bands_adc[1].rangehigh + bands_adc[2].rangelow) / 2) + + if (f->tuner == 0 && f->type == V4L2_TUNER_ADC) { + if (f->frequency < BAND_ADC_0) + band = 0; + else if (f->frequency < BAND_ADC_1) + band = 1; + else + band = 2; + + s->f_adc = clamp_t(unsigned int, f->frequency, + bands_adc[band].rangelow, + bands_adc[band].rangehigh); + + dev_dbg(&s->udev->dev, "%s: ADC frequency=%u Hz\n", + __func__, s->f_adc); + ret = rtl2832_sdr_set_adc(s); + } else if (f->tuner == 1) { + s->f_tuner = clamp_t(unsigned int, f->frequency, + bands_fm[0].rangelow, + bands_fm[0].rangehigh); + dev_dbg(&s->udev->dev, "%s: RF frequency=%u Hz\n", + __func__, f->frequency); + + ret = rtl2832_sdr_set_tuner_freq(s); + } else { + ret = -EINVAL; + } + + return ret; +} + +static int rtl2832_sdr_enum_fmt_sdr_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + struct rtl2832_sdr_state *s = video_drvdata(file); + dev_dbg(&s->udev->dev, "%s:\n", __func__); + + if (f->index >= NUM_FORMATS) + return -EINVAL; + + strlcpy(f->description, formats[f->index].name, sizeof(f->description)); + f->pixelformat = formats[f->index].pixelformat; + + return 0; +} + +static int rtl2832_sdr_g_fmt_sdr_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct rtl2832_sdr_state *s = video_drvdata(file); + dev_dbg(&s->udev->dev, "%s:\n", __func__); + + f->fmt.sdr.pixelformat = s->pixelformat; + memset(f->fmt.sdr.reserved, 0, sizeof(f->fmt.sdr.reserved)); + + return 0; +} + +static int rtl2832_sdr_s_fmt_sdr_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct rtl2832_sdr_state *s = video_drvdata(file); + struct vb2_queue *q = &s->vb_queue; + int i; + dev_dbg(&s->udev->dev, "%s: pixelformat fourcc %4.4s\n", __func__, + (char *)&f->fmt.sdr.pixelformat); + + if (vb2_is_busy(q)) + return -EBUSY; + + memset(f->fmt.sdr.reserved, 0, sizeof(f->fmt.sdr.reserved)); + for (i = 0; i < NUM_FORMATS; i++) { + if (formats[i].pixelformat == f->fmt.sdr.pixelformat) { + s->pixelformat = f->fmt.sdr.pixelformat; + return 0; + } + } + + f->fmt.sdr.pixelformat = formats[0].pixelformat; + s->pixelformat = formats[0].pixelformat; + + return 0; +} + +static int rtl2832_sdr_try_fmt_sdr_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct rtl2832_sdr_state *s = video_drvdata(file); + int i; + dev_dbg(&s->udev->dev, "%s: pixelformat fourcc %4.4s\n", __func__, + (char *)&f->fmt.sdr.pixelformat); + + memset(f->fmt.sdr.reserved, 0, sizeof(f->fmt.sdr.reserved)); + for (i = 0; i < NUM_FORMATS; i++) { + if (formats[i].pixelformat == f->fmt.sdr.pixelformat) + return 0; + } + + f->fmt.sdr.pixelformat = formats[0].pixelformat; + + return 0; +} + +static const struct v4l2_ioctl_ops rtl2832_sdr_ioctl_ops = { + .vidioc_querycap = rtl2832_sdr_querycap, + + .vidioc_enum_fmt_sdr_cap = rtl2832_sdr_enum_fmt_sdr_cap, + .vidioc_g_fmt_sdr_cap = rtl2832_sdr_g_fmt_sdr_cap, + .vidioc_s_fmt_sdr_cap = rtl2832_sdr_s_fmt_sdr_cap, + .vidioc_try_fmt_sdr_cap = rtl2832_sdr_try_fmt_sdr_cap, + + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_prepare_buf = vb2_ioctl_prepare_buf, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, + + .vidioc_g_tuner = rtl2832_sdr_g_tuner, + .vidioc_s_tuner = rtl2832_sdr_s_tuner, + + .vidioc_enum_freq_bands = rtl2832_sdr_enum_freq_bands, + .vidioc_g_frequency = rtl2832_sdr_g_frequency, + .vidioc_s_frequency = rtl2832_sdr_s_frequency, + + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, + .vidioc_log_status = v4l2_ctrl_log_status, +}; + +static const struct v4l2_file_operations rtl2832_sdr_fops = { + .owner = THIS_MODULE, + .open = v4l2_fh_open, + .release = vb2_fop_release, + .read = vb2_fop_read, + .poll = vb2_fop_poll, + .mmap = vb2_fop_mmap, + .unlocked_ioctl = video_ioctl2, +}; + +static struct video_device rtl2832_sdr_template = { + .name = "Realtek RTL2832 SDR", + .release = video_device_release_empty, + .fops = &rtl2832_sdr_fops, + .ioctl_ops = &rtl2832_sdr_ioctl_ops, +}; + +static int rtl2832_sdr_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct rtl2832_sdr_state *s = + container_of(ctrl->handler, struct rtl2832_sdr_state, + hdl); + struct dvb_frontend *fe = s->fe; + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + int ret; + dev_dbg(&s->udev->dev, + "%s: id=%d name=%s val=%d min=%d max=%d step=%d\n", + __func__, ctrl->id, ctrl->name, ctrl->val, + ctrl->minimum, ctrl->maximum, ctrl->step); + + switch (ctrl->id) { + case V4L2_CID_RF_TUNER_BANDWIDTH_AUTO: + case V4L2_CID_RF_TUNER_BANDWIDTH: + /* TODO: these controls should be moved to tuner drivers */ + if (s->bandwidth_auto->val) { + /* Round towards the closest legal value */ + s32 val = s->f_adc + s->bandwidth->step / 2; + u32 offset; + val = clamp(val, s->bandwidth->minimum, s->bandwidth->maximum); + offset = val - s->bandwidth->minimum; + offset = s->bandwidth->step * (offset / s->bandwidth->step); + s->bandwidth->val = s->bandwidth->minimum + offset; + } + + c->bandwidth_hz = s->bandwidth->val; + + if (!test_bit(POWER_ON, &s->flags)) + return 0; + + if (fe->ops.tuner_ops.set_params) + ret = fe->ops.tuner_ops.set_params(fe); + else + ret = 0; + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static const struct v4l2_ctrl_ops rtl2832_sdr_ctrl_ops = { + .s_ctrl = rtl2832_sdr_s_ctrl, +}; + +static void rtl2832_sdr_video_release(struct v4l2_device *v) +{ + struct rtl2832_sdr_state *s = + container_of(v, struct rtl2832_sdr_state, v4l2_dev); + + v4l2_ctrl_handler_free(&s->hdl); + v4l2_device_unregister(&s->v4l2_dev); + kfree(s); +} + +struct dvb_frontend *rtl2832_sdr_attach(struct dvb_frontend *fe, + struct i2c_adapter *i2c, const struct rtl2832_config *cfg, + struct v4l2_subdev *sd) +{ + int ret; + struct rtl2832_sdr_state *s; + const struct v4l2_ctrl_ops *ops = &rtl2832_sdr_ctrl_ops; + struct dvb_usb_device *d = i2c_get_adapdata(i2c); + + s = kzalloc(sizeof(struct rtl2832_sdr_state), GFP_KERNEL); + if (s == NULL) { + dev_err(&d->udev->dev, + "Could not allocate memory for rtl2832_sdr_state\n"); + return NULL; + } + + /* setup the state */ + s->fe = fe; + s->d = d; + s->udev = d->udev; + s->i2c = i2c; + s->cfg = cfg; + s->f_adc = bands_adc[0].rangelow; + s->f_tuner = bands_fm[0].rangelow; + s->pixelformat = V4L2_SDR_FMT_CU8; + + mutex_init(&s->v4l2_lock); + mutex_init(&s->vb_queue_lock); + spin_lock_init(&s->queued_bufs_lock); + INIT_LIST_HEAD(&s->queued_bufs); + + /* Init videobuf2 queue structure */ + s->vb_queue.type = V4L2_BUF_TYPE_SDR_CAPTURE; + s->vb_queue.io_modes = VB2_MMAP | VB2_USERPTR | VB2_READ; + s->vb_queue.drv_priv = s; + s->vb_queue.buf_struct_size = sizeof(struct rtl2832_sdr_frame_buf); + s->vb_queue.ops = &rtl2832_sdr_vb2_ops; + s->vb_queue.mem_ops = &vb2_vmalloc_memops; + s->vb_queue.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + ret = vb2_queue_init(&s->vb_queue); + if (ret) { + dev_err(&s->udev->dev, "Could not initialize vb2 queue\n"); + goto err_free_mem; + } + + /* Register controls */ + switch (s->cfg->tuner) { + case RTL2832_TUNER_E4000: + v4l2_ctrl_handler_init(&s->hdl, 9); + if (sd) + v4l2_ctrl_add_handler(&s->hdl, sd->ctrl_handler, NULL); + break; + case RTL2832_TUNER_R820T: + v4l2_ctrl_handler_init(&s->hdl, 2); + s->bandwidth_auto = v4l2_ctrl_new_std(&s->hdl, ops, V4L2_CID_RF_TUNER_BANDWIDTH_AUTO, 0, 1, 1, 1); + s->bandwidth = v4l2_ctrl_new_std(&s->hdl, ops, V4L2_CID_RF_TUNER_BANDWIDTH, 0, 8000000, 100000, 0); + v4l2_ctrl_auto_cluster(2, &s->bandwidth_auto, 0, false); + break; + case RTL2832_TUNER_FC0012: + case RTL2832_TUNER_FC0013: + v4l2_ctrl_handler_init(&s->hdl, 2); + s->bandwidth_auto = v4l2_ctrl_new_std(&s->hdl, ops, V4L2_CID_RF_TUNER_BANDWIDTH_AUTO, 0, 1, 1, 1); + s->bandwidth = v4l2_ctrl_new_std(&s->hdl, ops, V4L2_CID_RF_TUNER_BANDWIDTH, 6000000, 8000000, 1000000, 6000000); + v4l2_ctrl_auto_cluster(2, &s->bandwidth_auto, 0, false); + break; + default: + v4l2_ctrl_handler_init(&s->hdl, 0); + dev_notice(&s->udev->dev, "%s: Unsupported tuner\n", + KBUILD_MODNAME); + goto err_free_controls; + } + + if (s->hdl.error) { + ret = s->hdl.error; + dev_err(&s->udev->dev, "Could not initialize controls\n"); + goto err_free_controls; + } + + /* Init video_device structure */ + s->vdev = rtl2832_sdr_template; + s->vdev.queue = &s->vb_queue; + s->vdev.queue->lock = &s->vb_queue_lock; + set_bit(V4L2_FL_USE_FH_PRIO, &s->vdev.flags); + video_set_drvdata(&s->vdev, s); + + /* Register the v4l2_device structure */ + s->v4l2_dev.release = rtl2832_sdr_video_release; + ret = v4l2_device_register(&s->udev->dev, &s->v4l2_dev); + if (ret) { + dev_err(&s->udev->dev, + "Failed to register v4l2-device (%d)\n", ret); + goto err_free_controls; + } + + s->v4l2_dev.ctrl_handler = &s->hdl; + s->vdev.v4l2_dev = &s->v4l2_dev; + s->vdev.lock = &s->v4l2_lock; + s->vdev.vfl_dir = VFL_DIR_RX; + + ret = video_register_device(&s->vdev, VFL_TYPE_SDR, -1); + if (ret) { + dev_err(&s->udev->dev, + "Failed to register as video device (%d)\n", + ret); + goto err_unregister_v4l2_dev; + } + dev_info(&s->udev->dev, "Registered as %s\n", + video_device_node_name(&s->vdev)); + + fe->sec_priv = s; + fe->ops.release_sec = rtl2832_sdr_release_sec; + + dev_info(&s->i2c->dev, "%s: Realtek RTL2832 SDR attached\n", + KBUILD_MODNAME); + return fe; + +err_unregister_v4l2_dev: + v4l2_device_unregister(&s->v4l2_dev); +err_free_controls: + v4l2_ctrl_handler_free(&s->hdl); +err_free_mem: + kfree(s); + return NULL; +} +EXPORT_SYMBOL(rtl2832_sdr_attach); + +MODULE_AUTHOR("Antti Palosaari <crope@iki.fi>"); +MODULE_DESCRIPTION("Realtek RTL2832 SDR driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/media/rtl2832u_sdr/rtl2832_sdr.h b/drivers/staging/media/rtl2832u_sdr/rtl2832_sdr.h new file mode 100644 index 00000000000..b865fadf184 --- /dev/null +++ b/drivers/staging/media/rtl2832u_sdr/rtl2832_sdr.h @@ -0,0 +1,54 @@ +/* + * Realtek RTL2832U SDR driver + * + * Copyright (C) 2013 Antti Palosaari <crope@iki.fi> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * GNU Radio plugin "gr-kernel" for device usage will be on: + * http://git.linuxtv.org/anttip/gr-kernel.git + * + * TODO: + * Help is very highly welcome for these + all the others you could imagine: + * - move controls to V4L2 API + * - use libv4l2 for stream format conversions + * - gr-kernel: switch to v4l2_mmap (current read eats a lot of cpu) + * - SDRSharp support + */ + +#ifndef RTL2832_SDR_H +#define RTL2832_SDR_H + +#include <linux/kconfig.h> +#include <media/v4l2-subdev.h> + +/* for config struct */ +#include "rtl2832.h" + +#if IS_ENABLED(CONFIG_DVB_RTL2832_SDR) +extern struct dvb_frontend *rtl2832_sdr_attach(struct dvb_frontend *fe, + struct i2c_adapter *i2c, const struct rtl2832_config *cfg, + struct v4l2_subdev *sd); +#else +static inline struct dvb_frontend *rtl2832_sdr_attach(struct dvb_frontend *fe, + struct i2c_adapter *i2c, const struct rtl2832_config *cfg, + struct v4l2_subdev *sd) +{ + dev_warn(&i2c->dev, "%s: driver disabled by Kconfig\n", __func__); + return NULL; +} +#endif + +#endif /* RTL2832_SDR_H */ diff --git a/drivers/staging/media/sn9c102/Kconfig b/drivers/staging/media/sn9c102/Kconfig new file mode 100644 index 00000000000..c9aba59258d --- /dev/null +++ b/drivers/staging/media/sn9c102/Kconfig @@ -0,0 +1,17 @@ +config USB_SN9C102 + tristate "USB SN9C1xx PC Camera Controller support (DEPRECATED)" + depends on VIDEO_V4L2 && MEDIA_USB_SUPPORT + ---help--- + This driver is DEPRECATED, please use the gspca sonixb and + sonixj modules instead. + + Say Y here if you want support for cameras based on SONiX SN9C101, + SN9C102, SN9C103, SN9C105 and SN9C120 PC Camera Controllers. + + See <file:drivers/staging/media/sn9c102/sn9c102.txt> for more info. + + If you have webcams that are only supported by this driver and not by + the gspca driver, then contact the linux-media mailinglist. + + To compile this driver as a module, choose M here: the + module will be called sn9c102. diff --git a/drivers/staging/media/sn9c102/Makefile b/drivers/staging/media/sn9c102/Makefile new file mode 100644 index 00000000000..7ecd5a90c7c --- /dev/null +++ b/drivers/staging/media/sn9c102/Makefile @@ -0,0 +1,15 @@ +sn9c102-objs := sn9c102_core.o \ + sn9c102_hv7131d.o \ + sn9c102_hv7131r.o \ + sn9c102_mi0343.o \ + sn9c102_mi0360.o \ + sn9c102_mt9v111.o \ + sn9c102_ov7630.o \ + sn9c102_ov7660.o \ + sn9c102_pas106b.o \ + sn9c102_pas202bcb.o \ + sn9c102_tas5110c1b.o \ + sn9c102_tas5110d.o \ + sn9c102_tas5130d1b.o + +obj-$(CONFIG_USB_SN9C102) += sn9c102.o diff --git a/drivers/staging/media/sn9c102/sn9c102.h b/drivers/staging/media/sn9c102/sn9c102.h new file mode 100644 index 00000000000..37ca7225fcf --- /dev/null +++ b/drivers/staging/media/sn9c102/sn9c102.h @@ -0,0 +1,214 @@ +/*************************************************************************** + * V4L2 driver for SN9C1xx PC Camera Controllers * + * * + * Copyright (C) 2004-2006 by Luca Risolia <luca.risolia@studio.unibo.it> * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the Free Software * + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * + ***************************************************************************/ + +#ifndef _SN9C102_H_ +#define _SN9C102_H_ + +#include <linux/usb.h> +#include <linux/videodev2.h> +#include <media/v4l2-common.h> +#include <media/v4l2-ioctl.h> +#include <media/v4l2-device.h> +#include <linux/device.h> +#include <linux/list.h> +#include <linux/spinlock.h> +#include <linux/time.h> +#include <linux/wait.h> +#include <linux/types.h> +#include <linux/param.h> +#include <linux/rwsem.h> +#include <linux/mutex.h> +#include <linux/string.h> +#include <linux/stddef.h> +#include <linux/kref.h> + +#include "sn9c102_config.h" +#include "sn9c102_sensor.h" +#include "sn9c102_devtable.h" + + +enum sn9c102_frame_state { + F_UNUSED, + F_QUEUED, + F_GRABBING, + F_DONE, + F_ERROR, +}; + +struct sn9c102_frame_t { + void *bufmem; + struct v4l2_buffer buf; + enum sn9c102_frame_state state; + struct list_head frame; + unsigned long vma_use_count; +}; + +enum sn9c102_dev_state { + DEV_INITIALIZED = 0x01, + DEV_DISCONNECTED = 0x02, + DEV_MISCONFIGURED = 0x04, +}; + +enum sn9c102_io_method { + IO_NONE, + IO_READ, + IO_MMAP, +}; + +enum sn9c102_stream_state { + STREAM_OFF, + STREAM_INTERRUPT, + STREAM_ON, +}; + +typedef char sn9c102_sof_header_t[62]; + +struct sn9c102_sof_t { + sn9c102_sof_header_t header; + u16 bytesread; +}; + +struct sn9c102_sysfs_attr { + u16 reg, i2c_reg; + sn9c102_sof_header_t frame_header; +}; + +struct sn9c102_module_param { + u8 force_munmap; + u16 frame_timeout; +}; + +static DEFINE_MUTEX(sn9c102_sysfs_lock); +static DECLARE_RWSEM(sn9c102_dev_lock); + +struct sn9c102_device { + struct video_device *v4ldev; + + struct v4l2_device v4l2_dev; + + enum sn9c102_bridge bridge; + struct sn9c102_sensor sensor; + + struct usb_device *usbdev; + struct urb *urb[SN9C102_URBS]; + void *transfer_buffer[SN9C102_URBS]; + u8 *control_buffer; + + struct sn9c102_frame_t *frame_current, frame[SN9C102_MAX_FRAMES]; + struct list_head inqueue, outqueue; + u32 frame_count, nbuffers, nreadbuffers; + + enum sn9c102_io_method io; + enum sn9c102_stream_state stream; + + struct v4l2_jpegcompression compression; + + struct sn9c102_sysfs_attr sysfs; + struct sn9c102_sof_t sof; + u16 reg[384]; + + struct sn9c102_module_param module_param; + + struct kref kref; + enum sn9c102_dev_state state; + u8 users; + + struct completion probe; + struct mutex open_mutex, fileop_mutex; + spinlock_t queue_lock; + wait_queue_head_t wait_open, wait_frame, wait_stream; +}; + +/*****************************************************************************/ + +struct sn9c102_device* +sn9c102_match_id(struct sn9c102_device *cam, const struct usb_device_id *id) +{ + return usb_match_id(usb_ifnum_to_if(cam->usbdev, 0), id) ? cam : NULL; +} + + +void +sn9c102_attach_sensor(struct sn9c102_device *cam, + const struct sn9c102_sensor *sensor) +{ + memcpy(&cam->sensor, sensor, sizeof(struct sn9c102_sensor)); +} + + +enum sn9c102_bridge +sn9c102_get_bridge(struct sn9c102_device *cam) +{ + return cam->bridge; +} + + +struct sn9c102_sensor *sn9c102_get_sensor(struct sn9c102_device *cam) +{ + return &cam->sensor; +} + +/*****************************************************************************/ + +#undef DBG +#undef KDBG +#ifdef SN9C102_DEBUG +# define DBG(level, fmt, args...) \ +do { \ + if (debug >= (level)) { \ + if ((level) == 1) \ + dev_err(&cam->usbdev->dev, fmt "\n", ## args); \ + else if ((level) == 2) \ + dev_info(&cam->usbdev->dev, fmt "\n", ## args); \ + else if ((level) >= 3) \ + dev_info(&cam->usbdev->dev, "[%s:%d] " fmt "\n", \ + __func__, __LINE__ , ## args); \ + } \ +} while (0) +# define V4LDBG(level, name, cmd) \ +do { \ + if (debug >= (level)) \ + v4l_printk_ioctl(name, cmd); \ +} while (0) +# define KDBG(level, fmt, args...) \ +do { \ + if (debug >= (level)) { \ + if ((level) == 1 || (level) == 2) \ + pr_info("sn9c102: " fmt "\n", ## args); \ + else if ((level) == 3) \ + pr_debug("sn9c102: [%s:%d] " fmt "\n", \ + __func__, __LINE__ , ## args); \ + } \ +} while (0) +#else +# define DBG(level, fmt, args...) do { ; } while (0) +# define V4LDBG(level, name, cmd) do { ; } while (0) +# define KDBG(level, fmt, args...) do { ; } while (0) +#endif + +#undef PDBG +#define PDBG(fmt, args...) \ +dev_info(&cam->usbdev->dev, "[%s:%s:%d] " fmt "\n", __FILE__, __func__, \ + __LINE__ , ## args) + +#undef PDBGG +#define PDBGG(fmt, args...) do { ; } while (0) /* placeholder */ + +#endif /* _SN9C102_H_ */ diff --git a/drivers/staging/media/sn9c102/sn9c102.txt b/drivers/staging/media/sn9c102/sn9c102.txt new file mode 100644 index 00000000000..b4f67040403 --- /dev/null +++ b/drivers/staging/media/sn9c102/sn9c102.txt @@ -0,0 +1,592 @@ + + SN9C1xx PC Camera Controllers + Driver for Linux + ============================= + + - Documentation - + + +Index +===== +1. Copyright +2. Disclaimer +3. License +4. Overview and features +5. Module dependencies +6. Module loading +7. Module parameters +8. Optional device control through "sysfs" +9. Supported devices +10. Notes for V4L2 application developers +11. Video frame formats +12. Contact information +13. Credits + + +1. Copyright +============ +Copyright (C) 2004-2007 by Luca Risolia <luca.risolia@studio.unibo.it> + + +2. Disclaimer +============= +SONiX is a trademark of SONiX Technology Company Limited, inc. +This software is not sponsored or developed by SONiX. + + +3. License +========== +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + +4. Overview and features +======================== +This driver attempts to support the video interface of the devices assembling +the SONiX SN9C101, SN9C102, SN9C103, SN9C105 and SN9C120 PC Camera Controllers +("SN9C1xx" from now on). + +The driver relies on the Video4Linux2 and USB core modules. It has been +designed to run properly on SMP systems as well. + +The latest version of the SN9C1xx driver can be found at the following URL: +http://www.linux-projects.org/ + +Some of the features of the driver are: + +- full compliance with the Video4Linux2 API (see also "Notes for V4L2 + application developers" paragraph); +- available mmap or read/poll methods for video streaming through isochronous + data transfers; +- automatic detection of image sensor; +- support for built-in microphone interface; +- support for any window resolutions and optional panning within the maximum + pixel area of image sensor; +- image downscaling with arbitrary scaling factors from 1, 2 and 4 in both + directions (see "Notes for V4L2 application developers" paragraph); +- two different video formats for uncompressed or compressed data in low or + high compression quality (see also "Notes for V4L2 application developers" + and "Video frame formats" paragraphs); +- full support for the capabilities of many of the possible image sensors that + can be connected to the SN9C1xx bridges, including, for instance, red, green, + blue and global gain adjustments and exposure (see "Supported devices" + paragraph for details); +- use of default color settings for sunlight conditions; +- dynamic I/O interface for both SN9C1xx and image sensor control and + monitoring (see "Optional device control through 'sysfs'" paragraph); +- dynamic driver control thanks to various module parameters (see "Module + parameters" paragraph); +- up to 64 cameras can be handled at the same time; they can be connected and + disconnected from the host many times without turning off the computer, if + the system supports hotplugging; +- no known bugs. + + +5. Module dependencies +====================== +For it to work properly, the driver needs kernel support for Video4Linux and +USB. + +The following options of the kernel configuration file must be enabled and +corresponding modules must be compiled: + + # Multimedia devices + # + CONFIG_VIDEO_DEV=m + +To enable advanced debugging functionality on the device through /sysfs: + + # Multimedia devices + # + CONFIG_VIDEO_ADV_DEBUG=y + + # USB support + # + CONFIG_USB=m + +In addition, depending on the hardware being used, the modules below are +necessary: + + # USB Host Controller Drivers + # + CONFIG_USB_EHCI_HCD=m + CONFIG_USB_UHCI_HCD=m + CONFIG_USB_OHCI_HCD=m + +The SN9C103, SN9c105 and SN9C120 controllers also provide a built-in microphone +interface. It is supported by the USB Audio driver thanks to the ALSA API: + + # Sound + # + CONFIG_SOUND=y + + # Advanced Linux Sound Architecture + # + CONFIG_SND=m + + # USB devices + # + CONFIG_SND_USB_AUDIO=m + +And finally: + + # USB Multimedia devices + # + CONFIG_USB_SN9C102=m + + +6. Module loading +================= +To use the driver, it is necessary to load the "sn9c102" module into memory +after every other module required: "videodev", "v4l2_common", "compat_ioctl32", +"usbcore" and, depending on the USB host controller you have, "ehci-hcd", +"uhci-hcd" or "ohci-hcd". + +Loading can be done as shown below: + + [root@localhost home]# modprobe sn9c102 + +Note that the module is called "sn9c102" for historic reasons, although it +does not just support the SN9C102. + +At this point all the devices supported by the driver and connected to the USB +ports should be recognized. You can invoke "dmesg" to analyze kernel messages +and verify that the loading process has gone well: + + [user@localhost home]$ dmesg + +or, to isolate all the kernel messages generated by the driver: + + [user@localhost home]$ dmesg | grep sn9c102 + + +7. Module parameters +==================== +Module parameters are listed below: +------------------------------------------------------------------------------- +Name: video_nr +Type: short array (min = 0, max = 64) +Syntax: <-1|n[,...]> +Description: Specify V4L2 minor mode number: + -1 = use next available + n = use minor number n + You can specify up to 64 cameras this way. + For example: + video_nr=-1,2,-1 would assign minor number 2 to the second + recognized camera and use auto for the first one and for every + other camera. +Default: -1 +------------------------------------------------------------------------------- +Name: force_munmap +Type: bool array (min = 0, max = 64) +Syntax: <0|1[,...]> +Description: Force the application to unmap previously mapped buffer memory + before calling any VIDIOC_S_CROP or VIDIOC_S_FMT ioctl's. Not + all the applications support this feature. This parameter is + specific for each detected camera. + 0 = do not force memory unmapping + 1 = force memory unmapping (save memory) +Default: 0 +------------------------------------------------------------------------------- +Name: frame_timeout +Type: uint array (min = 0, max = 64) +Syntax: <0|n[,...]> +Description: Timeout for a video frame in seconds before returning an I/O + error; 0 for infinity. This parameter is specific for each + detected camera and can be changed at runtime thanks to the + /sys filesystem interface. +Default: 2 +------------------------------------------------------------------------------- +Name: debug +Type: ushort +Syntax: <n> +Description: Debugging information level, from 0 to 3: + 0 = none (use carefully) + 1 = critical errors + 2 = significant information + 3 = more verbose messages + Level 3 is useful for testing only. It also shows some more + information about the hardware being detected. + This parameter can be changed at runtime thanks to the /sys + filesystem interface. +Default: 2 +------------------------------------------------------------------------------- + + +8. Optional device control through "sysfs" [1] +========================================== +If the kernel has been compiled with the CONFIG_VIDEO_ADV_DEBUG option enabled, +it is possible to read and write both the SN9C1xx and the image sensor +registers by using the "sysfs" filesystem interface. + +Every time a supported device is recognized, a write-only file named "green" is +created in the /sys/class/video4linux/videoX directory. You can set the green +channel's gain by writing the desired value to it. The value may range from 0 +to 15 for the SN9C101 or SN9C102 bridges, from 0 to 127 for the SN9C103, +SN9C105 and SN9C120 bridges. +Similarly, only for the SN9C103, SN9C105 and SN9C120 controllers, blue and red +gain control files are available in the same directory, for which accepted +values may range from 0 to 127. + +There are other four entries in the directory above for each registered camera: +"reg", "val", "i2c_reg" and "i2c_val". The first two files control the +SN9C1xx bridge, while the other two control the sensor chip. "reg" and +"i2c_reg" hold the values of the current register index where the following +reading/writing operations are addressed at through "val" and "i2c_val". Their +use is not intended for end-users. Note that "i2c_reg" and "i2c_val" will not +be created if the sensor does not actually support the standard I2C protocol or +its registers are not 8-bit long. Also, remember that you must be logged in as +root before writing to them. + +As an example, suppose we were to want to read the value contained in the +register number 1 of the sensor register table - which is usually the product +identifier - of the camera registered as "/dev/video0": + + [root@localhost #] cd /sys/class/video4linux/video0 + [root@localhost #] echo 1 > i2c_reg + [root@localhost #] cat i2c_val + +Note that "cat" will fail if sensor registers cannot be read. + +Now let's set the green gain's register of the SN9C101 or SN9C102 chips to 2: + + [root@localhost #] echo 0x11 > reg + [root@localhost #] echo 2 > val + +Note that the SN9C1xx always returns 0 when some of its registers are read. +To avoid race conditions, all the I/O accesses to the above files are +serialized. +The sysfs interface also provides the "frame_header" entry, which exports the +frame header of the most recent requested and captured video frame. The header +is always 18-bytes long and is appended to every video frame by the SN9C1xx +controllers. As an example, this additional information can be used by the user +application for implementing auto-exposure features via software. + +The following table describes the frame header exported by the SN9C101 and +SN9C102: + +Byte # Value or bits Description +------ ------------- ----------- +0x00 0xFF Frame synchronisation pattern +0x01 0xFF Frame synchronisation pattern +0x02 0x00 Frame synchronisation pattern +0x03 0xC4 Frame synchronisation pattern +0x04 0xC4 Frame synchronisation pattern +0x05 0x96 Frame synchronisation pattern +0x06 [3:0] Read channel gain control = (1+R_GAIN/8) + [7:4] Blue channel gain control = (1+B_GAIN/8) +0x07 [ 0 ] Compression mode. 0=No compression, 1=Compression enabled + [2:1] Maximum scale factor for compression + [ 3 ] 1 = USB fifo(2K bytes) is full + [ 4 ] 1 = Digital gain is finish + [ 5 ] 1 = Exposure is finish + [7:6] Frame index +0x08 [7:0] Y sum inside Auto-Exposure area (low-byte) +0x09 [7:0] Y sum inside Auto-Exposure area (high-byte) + where Y sum = (R/4 + 5G/16 + B/8) / 32 +0x0A [7:0] Y sum outside Auto-Exposure area (low-byte) +0x0B [7:0] Y sum outside Auto-Exposure area (high-byte) + where Y sum = (R/4 + 5G/16 + B/8) / 128 +0x0C 0xXX Not used +0x0D 0xXX Not used +0x0E 0xXX Not used +0x0F 0xXX Not used +0x10 0xXX Not used +0x11 0xXX Not used + +The following table describes the frame header exported by the SN9C103: + +Byte # Value or bits Description +------ ------------- ----------- +0x00 0xFF Frame synchronisation pattern +0x01 0xFF Frame synchronisation pattern +0x02 0x00 Frame synchronisation pattern +0x03 0xC4 Frame synchronisation pattern +0x04 0xC4 Frame synchronisation pattern +0x05 0x96 Frame synchronisation pattern +0x06 [6:0] Read channel gain control = (1/2+R_GAIN/64) +0x07 [6:0] Blue channel gain control = (1/2+B_GAIN/64) + [7:4] +0x08 [ 0 ] Compression mode. 0=No compression, 1=Compression enabled + [2:1] Maximum scale factor for compression + [ 3 ] 1 = USB fifo(2K bytes) is full + [ 4 ] 1 = Digital gain is finish + [ 5 ] 1 = Exposure is finish + [7:6] Frame index +0x09 [7:0] Y sum inside Auto-Exposure area (low-byte) +0x0A [7:0] Y sum inside Auto-Exposure area (high-byte) + where Y sum = (R/4 + 5G/16 + B/8) / 32 +0x0B [7:0] Y sum outside Auto-Exposure area (low-byte) +0x0C [7:0] Y sum outside Auto-Exposure area (high-byte) + where Y sum = (R/4 + 5G/16 + B/8) / 128 +0x0D [1:0] Audio frame number + [ 2 ] 1 = Audio is recording +0x0E [7:0] Audio summation (low-byte) +0x0F [7:0] Audio summation (high-byte) +0x10 [7:0] Audio sample count +0x11 [7:0] Audio peak data in audio frame + +The AE area (sx, sy, ex, ey) in the active window can be set by programming the +registers 0x1c, 0x1d, 0x1e and 0x1f of the SN9C1xx controllers, where one unit +corresponds to 32 pixels. + +[1] The frame headers exported by the SN9C105 and SN9C120 are not described. + + +9. Supported devices +==================== +None of the names of the companies as well as their products will be mentioned +here. They have never collaborated with the author, so no advertising. + +From the point of view of a driver, what unambiguously identify a device are +its vendor and product USB identifiers. Below is a list of known identifiers of +devices assembling the SN9C1xx PC camera controllers: + +Vendor ID Product ID +--------- ---------- +0x0458 0x7025 +0x045e 0x00f5 +0x045e 0x00f7 +0x0471 0x0327 +0x0471 0x0328 +0x0c45 0x6001 +0x0c45 0x6005 +0x0c45 0x6007 +0x0c45 0x6009 +0x0c45 0x600d +0x0c45 0x6011 +0x0c45 0x6019 +0x0c45 0x6024 +0x0c45 0x6025 +0x0c45 0x6028 +0x0c45 0x6029 +0x0c45 0x602a +0x0c45 0x602b +0x0c45 0x602c +0x0c45 0x602d +0x0c45 0x602e +0x0c45 0x6030 +0x0c45 0x603f +0x0c45 0x6080 +0x0c45 0x6082 +0x0c45 0x6083 +0x0c45 0x6088 +0x0c45 0x608a +0x0c45 0x608b +0x0c45 0x608c +0x0c45 0x608e +0x0c45 0x608f +0x0c45 0x60a0 +0x0c45 0x60a2 +0x0c45 0x60a3 +0x0c45 0x60a8 +0x0c45 0x60aa +0x0c45 0x60ab +0x0c45 0x60ac +0x0c45 0x60ae +0x0c45 0x60af +0x0c45 0x60b0 +0x0c45 0x60b2 +0x0c45 0x60b3 +0x0c45 0x60b8 +0x0c45 0x60ba +0x0c45 0x60bb +0x0c45 0x60bc +0x0c45 0x60be +0x0c45 0x60c0 +0x0c45 0x60c2 +0x0c45 0x60c8 +0x0c45 0x60cc +0x0c45 0x60ea +0x0c45 0x60ec +0x0c45 0x60ef +0x0c45 0x60fa +0x0c45 0x60fb +0x0c45 0x60fc +0x0c45 0x60fe +0x0c45 0x6102 +0x0c45 0x6108 +0x0c45 0x610f +0x0c45 0x6130 +0x0c45 0x6138 +0x0c45 0x613a +0x0c45 0x613b +0x0c45 0x613c +0x0c45 0x613e + +The list above does not imply that all those devices work with this driver: up +until now only the ones that assemble the following pairs of SN9C1xx bridges +and image sensors are supported; kernel messages will always tell you whether +this is the case (see "Module loading" paragraph): + +Image sensor / SN9C1xx bridge | SN9C10[12] SN9C103 SN9C105 SN9C120 +------------------------------------------------------------------------------- +HV7131D Hynix Semiconductor | Yes No No No +HV7131R Hynix Semiconductor | No Yes Yes Yes +MI-0343 Micron Technology | Yes No No No +MI-0360 Micron Technology | No Yes Yes Yes +OV7630 OmniVision Technologies | Yes Yes Yes Yes +OV7660 OmniVision Technologies | No No Yes Yes +PAS106B PixArt Imaging | Yes No No No +PAS202B PixArt Imaging | Yes Yes No No +TAS5110C1B Taiwan Advanced Sensor | Yes No No No +TAS5110D Taiwan Advanced Sensor | Yes No No No +TAS5130D1B Taiwan Advanced Sensor | Yes No No No + +"Yes" means that the pair is supported by the driver, while "No" means that the +pair does not exist or is not supported by the driver. + +Only some of the available control settings of each image sensor are supported +through the V4L2 interface. + +Donations of new models for further testing and support would be much +appreciated. Non-available hardware will not be supported by the author of this +driver. + + +10. Notes for V4L2 application developers +========================================= +This driver follows the V4L2 API specifications. In particular, it enforces two +rules: + +- exactly one I/O method, either "mmap" or "read", is associated with each +file descriptor. Once it is selected, the application must close and reopen the +device to switch to the other I/O method; + +- although it is not mandatory, previously mapped buffer memory should always +be unmapped before calling any "VIDIOC_S_CROP" or "VIDIOC_S_FMT" ioctl's. +The same number of buffers as before will be allocated again to match the size +of the new video frames, so you have to map the buffers again before any I/O +attempts on them. + +Consistently with the hardware limits, this driver also supports image +downscaling with arbitrary scaling factors from 1, 2 and 4 in both directions. +However, the V4L2 API specifications don't correctly define how the scaling +factor can be chosen arbitrarily by the "negotiation" of the "source" and +"target" rectangles. To work around this flaw, we have added the convention +that, during the negotiation, whenever the "VIDIOC_S_CROP" ioctl is issued, the +scaling factor is restored to 1. + +This driver supports two different video formats: the first one is the "8-bit +Sequential Bayer" format and can be used to obtain uncompressed video data +from the device through the current I/O method, while the second one provides +either "raw" compressed video data (without frame headers not related to the +compressed data) or standard JPEG (with frame headers). The compression quality +may vary from 0 to 1 and can be selected or queried thanks to the +VIDIOC_S_JPEGCOMP and VIDIOC_G_JPEGCOMP V4L2 ioctl's. For maximum flexibility, +both the default active video format and the default compression quality +depend on how the image sensor being used is initialized. + + +11. Video frame formats [1] +======================= +The SN9C1xx PC Camera Controllers can send images in two possible video +formats over the USB: either native "Sequential RGB Bayer" or compressed. +The compression is used to achieve high frame rates. With regard to the +SN9C101, SN9C102 and SN9C103, the compression is based on the Huffman encoding +algorithm described below, while with regard to the SN9C105 and SN9C120 the +compression is based on the JPEG standard. +The current video format may be selected or queried from the user application +by calling the VIDIOC_S_FMT or VIDIOC_G_FMT ioctl's, as described in the V4L2 +API specifications. + +The name "Sequential Bayer" indicates the organization of the red, green and +blue pixels in one video frame. Each pixel is associated with a 8-bit long +value and is disposed in memory according to the pattern shown below: + +B[0] G[1] B[2] G[3] ... B[m-2] G[m-1] +G[m] R[m+1] G[m+2] R[m+2] ... G[2m-2] R[2m-1] +... +... B[(n-1)(m-2)] G[(n-1)(m-1)] +... G[n(m-2)] R[n(m-1)] + +The above matrix also represents the sequential or progressive read-out mode of +the (n, m) Bayer color filter array used in many CCD or CMOS image sensors. + +The Huffman compressed video frame consists of a bitstream that encodes for +every R, G, or B pixel the difference between the value of the pixel itself and +some reference pixel value. Pixels are organised in the Bayer pattern and the +Bayer sub-pixels are tracked individually and alternatingly. For example, in +the first line values for the B and G1 pixels are alternatingly encoded, while +in the second line values for the G2 and R pixels are alternatingly encoded. + +The pixel reference value is calculated as follows: +- the 4 top left pixels are encoded in raw uncompressed 8-bit format; +- the value in the top two rows is the value of the pixel left of the current + pixel; +- the value in the left column is the value of the pixel above the current + pixel; +- for all other pixels, the reference value is the average of the value of the + pixel on the left and the value of the pixel above the current pixel; +- there is one code in the bitstream that specifies the value of a pixel + directly (in 4-bit resolution); +- pixel values need to be clamped inside the range [0..255] for proper + decoding. + +The algorithm purely describes the conversion from compressed Bayer code used +in the SN9C101, SN9C102 and SN9C103 chips to uncompressed Bayer. Additional +steps are required to convert this to a color image (i.e. a color interpolation +algorithm). + +The following Huffman codes have been found: +0: +0 (relative to reference pixel value) +100: +4 +101: -4? +1110xxxx: set absolute value to xxxx.0000 +1101: +11 +1111: -11 +11001: +20 +110000: -20 +110001: ??? - these codes are apparently not used + +[1] The Huffman compression algorithm has been reverse-engineered and + documented by Bertrik Sikken. + + +12. Contact information +======================= +The author may be contacted by e-mail at <luca.risolia@studio.unibo.it>. + +GPG/PGP encrypted e-mail's are accepted. The GPG key ID of the author is +'FCE635A4'; the public 1024-bit key should be available at any keyserver; +the fingerprint is: '88E8 F32F 7244 68BA 3958 5D40 99DA 5D2A FCE6 35A4'. + + +13. Credits +=========== +Many thanks to following persons for their contribute (listed in alphabetical +order): + +- David Anderson for the donation of a webcam; +- Luca Capello for the donation of a webcam; +- Philippe Coval for having helped testing the PAS202BCA image sensor; +- Joao Rodrigo Fuzaro, Joao Limirio, Claudio Filho and Caio Begotti for the + donation of a webcam; +- Dennis Heitmann for the donation of a webcam; +- Jon Hollstrom for the donation of a webcam; +- Nick McGill for the donation of a webcam; +- Carlos Eduardo Medaglia Dyonisio, who added the support for the PAS202BCB + image sensor; +- Stefano Mozzi, who donated 45 EU; +- Andrew Pearce for the donation of a webcam; +- John Pullan for the donation of a webcam; +- Bertrik Sikken, who reverse-engineered and documented the Huffman compression + algorithm used in the SN9C101, SN9C102 and SN9C103 controllers and + implemented the first decoder; +- Ronny Standke for the donation of a webcam; +- Mizuno Takafumi for the donation of a webcam; +- an "anonymous" donator (who didn't want his name to be revealed) for the + donation of a webcam. +- an anonymous donator for the donation of four webcams and two boards with ten + image sensors. diff --git a/drivers/staging/media/sn9c102/sn9c102_config.h b/drivers/staging/media/sn9c102/sn9c102_config.h new file mode 100644 index 00000000000..0f4e0378b07 --- /dev/null +++ b/drivers/staging/media/sn9c102/sn9c102_config.h @@ -0,0 +1,86 @@ +/*************************************************************************** + * Global parameters for the V4L2 driver for SN9C1xx PC Camera Controllers * + * * + * Copyright (C) 2007 by Luca Risolia <luca.risolia@studio.unibo.it> * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the Free Software * + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * + ***************************************************************************/ + +#ifndef _SN9C102_CONFIG_H_ +#define _SN9C102_CONFIG_H_ + +#include <linux/types.h> +#include <linux/jiffies.h> + +#define SN9C102_DEBUG +#define SN9C102_DEBUG_LEVEL 2 +#define SN9C102_MAX_DEVICES 64 +#define SN9C102_PRESERVE_IMGSCALE 0 +#define SN9C102_FORCE_MUNMAP 0 +#define SN9C102_MAX_FRAMES 32 +#define SN9C102_URBS 2 +#define SN9C102_ISO_PACKETS 7 +#define SN9C102_ALTERNATE_SETTING 8 +#define SN9C102_URB_TIMEOUT msecs_to_jiffies(2 * SN9C102_ISO_PACKETS) +#define SN9C102_CTRL_TIMEOUT 300 +#define SN9C102_FRAME_TIMEOUT 0 + +/*****************************************************************************/ + +static const u8 SN9C102_Y_QTABLE0[64] = { + 8, 5, 5, 8, 12, 20, 25, 30, + 6, 6, 7, 9, 13, 29, 30, 27, + 7, 6, 8, 12, 20, 28, 34, 28, + 7, 8, 11, 14, 25, 43, 40, 31, + 9, 11, 18, 28, 34, 54, 51, 38, + 12, 17, 27, 32, 40, 52, 56, 46, + 24, 32, 39, 43, 51, 60, 60, 50, + 36, 46, 47, 49, 56, 50, 51, 49 +}; + +static const u8 SN9C102_UV_QTABLE0[64] = { + 8, 9, 12, 23, 49, 49, 49, 49, + 9, 10, 13, 33, 49, 49, 49, 49, + 12, 13, 28, 49, 49, 49, 49, 49, + 23, 33, 49, 49, 49, 49, 49, 49, + 49, 49, 49, 49, 49, 49, 49, 49, + 49, 49, 49, 49, 49, 49, 49, 49, + 49, 49, 49, 49, 49, 49, 49, 49, + 49, 49, 49, 49, 49, 49, 49, 49 +}; + +static const u8 SN9C102_Y_QTABLE1[64] = { + 16, 11, 10, 16, 24, 40, 51, 61, + 12, 12, 14, 19, 26, 58, 60, 55, + 14, 13, 16, 24, 40, 57, 69, 56, + 14, 17, 22, 29, 51, 87, 80, 62, + 18, 22, 37, 56, 68, 109, 103, 77, + 24, 35, 55, 64, 81, 104, 113, 92, + 49, 64, 78, 87, 103, 121, 120, 101, + 72, 92, 95, 98, 112, 100, 103, 99 +}; + +static const u8 SN9C102_UV_QTABLE1[64] = { + 17, 18, 24, 47, 99, 99, 99, 99, + 18, 21, 26, 66, 99, 99, 99, 99, + 24, 26, 56, 99, 99, 99, 99, 99, + 47, 66, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99 +}; + +#endif /* _SN9C102_CONFIG_H_ */ diff --git a/drivers/staging/media/sn9c102/sn9c102_core.c b/drivers/staging/media/sn9c102/sn9c102_core.c new file mode 100644 index 00000000000..98b30579b0a --- /dev/null +++ b/drivers/staging/media/sn9c102/sn9c102_core.c @@ -0,0 +1,3465 @@ +/*************************************************************************** + * V4L2 driver for SN9C1xx PC Camera Controllers * + * * + * Copyright (C) 2004-2007 by Luca Risolia <luca.risolia@studio.unibo.it> * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the Free Software * + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * + ***************************************************************************/ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/param.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/device.h> +#include <linux/fs.h> +#include <linux/delay.h> +#include <linux/compiler.h> +#include <linux/ioctl.h> +#include <linux/poll.h> +#include <linux/stat.h> +#include <linux/mm.h> +#include <linux/vmalloc.h> +#include <linux/version.h> +#include <linux/page-flags.h> +#include <asm/byteorder.h> +#include <asm/page.h> +#include <asm/uaccess.h> + +#include "sn9c102.h" + +/*****************************************************************************/ + +#define SN9C102_MODULE_NAME "V4L2 driver for SN9C1xx PC Camera Controllers" +#define SN9C102_MODULE_ALIAS "sn9c1xx" +#define SN9C102_MODULE_AUTHOR "(C) 2004-2007 Luca Risolia" +#define SN9C102_AUTHOR_EMAIL "<luca.risolia@studio.unibo.it>" +#define SN9C102_MODULE_LICENSE "GPL" +#define SN9C102_MODULE_VERSION "1:1.48" + +/*****************************************************************************/ + +MODULE_DEVICE_TABLE(usb, sn9c102_id_table); + +MODULE_AUTHOR(SN9C102_MODULE_AUTHOR " " SN9C102_AUTHOR_EMAIL); +MODULE_DESCRIPTION(SN9C102_MODULE_NAME); +MODULE_ALIAS(SN9C102_MODULE_ALIAS); +MODULE_VERSION(SN9C102_MODULE_VERSION); +MODULE_LICENSE(SN9C102_MODULE_LICENSE); + +static short video_nr[] = {[0 ... SN9C102_MAX_DEVICES-1] = -1}; +module_param_array(video_nr, short, NULL, 0444); +MODULE_PARM_DESC(video_nr, + " <-1|n[,...]>" + "\nSpecify V4L2 minor mode number." + "\n-1 = use next available (default)" + "\n n = use minor number n (integer >= 0)" + "\nYou can specify up to "__MODULE_STRING(SN9C102_MAX_DEVICES) + " cameras this way." + "\nFor example:" + "\nvideo_nr=-1,2,-1 would assign minor number 2 to" + "\nthe second camera and use auto for the first" + "\none and for every other camera." + "\n"); + +static bool force_munmap[] = {[0 ... SN9C102_MAX_DEVICES-1] = + SN9C102_FORCE_MUNMAP}; +module_param_array(force_munmap, bool, NULL, 0444); +MODULE_PARM_DESC(force_munmap, + " <0|1[,...]>" + "\nForce the application to unmap previously" + "\nmapped buffer memory before calling any VIDIOC_S_CROP or" + "\nVIDIOC_S_FMT ioctl's. Not all the applications support" + "\nthis feature. This parameter is specific for each" + "\ndetected camera." + "\n0 = do not force memory unmapping" + "\n1 = force memory unmapping (save memory)" + "\nDefault value is "__MODULE_STRING(SN9C102_FORCE_MUNMAP)"." + "\n"); + +static unsigned int frame_timeout[] = {[0 ... SN9C102_MAX_DEVICES-1] = + SN9C102_FRAME_TIMEOUT}; +module_param_array(frame_timeout, uint, NULL, 0644); +MODULE_PARM_DESC(frame_timeout, + " <0|n[,...]>" + "\nTimeout for a video frame in seconds before" + "\nreturning an I/O error; 0 for infinity." + "\nThis parameter is specific for each detected camera." + "\nDefault value is "__MODULE_STRING(SN9C102_FRAME_TIMEOUT)"." + "\n"); + +#ifdef SN9C102_DEBUG +static unsigned short debug = SN9C102_DEBUG_LEVEL; +module_param(debug, ushort, 0644); +MODULE_PARM_DESC(debug, + " <n>" + "\nDebugging information level, from 0 to 3:" + "\n0 = none (use carefully)" + "\n1 = critical errors" + "\n2 = significant informations" + "\n3 = more verbose messages" + "\nLevel 3 is useful for testing only." + "\nDefault value is "__MODULE_STRING(SN9C102_DEBUG_LEVEL)"." + "\n"); +#endif + +/* + Add the probe entries to this table. Be sure to add the entry in the right + place, since, on failure, the next probing routine is called according to + the order of the list below, from top to bottom. +*/ +static int (*sn9c102_sensor_table[])(struct sn9c102_device *) = { + &sn9c102_probe_hv7131d, /* strong detection based on SENSOR ids */ + &sn9c102_probe_hv7131r, /* strong detection based on SENSOR ids */ + &sn9c102_probe_mi0343, /* strong detection based on SENSOR ids */ + &sn9c102_probe_mi0360, /* strong detection based on SENSOR ids */ + &sn9c102_probe_mt9v111, /* strong detection based on SENSOR ids */ + &sn9c102_probe_pas106b, /* strong detection based on SENSOR ids */ + &sn9c102_probe_pas202bcb, /* strong detection based on SENSOR ids */ + &sn9c102_probe_ov7630, /* strong detection based on SENSOR ids */ + &sn9c102_probe_ov7660, /* strong detection based on SENSOR ids */ + &sn9c102_probe_tas5110c1b, /* detection based on USB pid/vid */ + &sn9c102_probe_tas5110d, /* detection based on USB pid/vid */ + &sn9c102_probe_tas5130d1b, /* detection based on USB pid/vid */ +}; + +/*****************************************************************************/ + +static u32 +sn9c102_request_buffers(struct sn9c102_device *cam, u32 count, + enum sn9c102_io_method io) +{ + struct v4l2_pix_format *p = &(cam->sensor.pix_format); + struct v4l2_rect *r = &(cam->sensor.cropcap.bounds); + size_t imagesize = cam->module_param.force_munmap || io == IO_READ ? + (p->width * p->height * p->priv) / 8 : + (r->width * r->height * p->priv) / 8; + void *buff = NULL; + u32 i; + + if (count > SN9C102_MAX_FRAMES) + count = SN9C102_MAX_FRAMES; + + if (cam->bridge == BRIDGE_SN9C105 || cam->bridge == BRIDGE_SN9C120) + imagesize += 589 + 2; /* length of JPEG header + EOI marker */ + + cam->nbuffers = count; + while (cam->nbuffers > 0) { + buff = vmalloc_32_user(cam->nbuffers * PAGE_ALIGN(imagesize)); + if (buff) + break; + cam->nbuffers--; + } + + for (i = 0; i < cam->nbuffers; i++) { + cam->frame[i].bufmem = buff + i*PAGE_ALIGN(imagesize); + cam->frame[i].buf.index = i; + cam->frame[i].buf.m.offset = i*PAGE_ALIGN(imagesize); + cam->frame[i].buf.length = imagesize; + cam->frame[i].buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + cam->frame[i].buf.sequence = 0; + cam->frame[i].buf.field = V4L2_FIELD_NONE; + cam->frame[i].buf.memory = V4L2_MEMORY_MMAP; + cam->frame[i].buf.flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + } + + return cam->nbuffers; +} + + +static void sn9c102_release_buffers(struct sn9c102_device *cam) +{ + if (cam->nbuffers) { + vfree(cam->frame[0].bufmem); + cam->nbuffers = 0; + } + cam->frame_current = NULL; +} + + +static void sn9c102_empty_framequeues(struct sn9c102_device *cam) +{ + u32 i; + + INIT_LIST_HEAD(&cam->inqueue); + INIT_LIST_HEAD(&cam->outqueue); + + for (i = 0; i < SN9C102_MAX_FRAMES; i++) { + cam->frame[i].state = F_UNUSED; + cam->frame[i].buf.bytesused = 0; + } +} + + +static void sn9c102_requeue_outqueue(struct sn9c102_device *cam) +{ + struct sn9c102_frame_t *i; + + list_for_each_entry(i, &cam->outqueue, frame) { + i->state = F_QUEUED; + list_add(&i->frame, &cam->inqueue); + } + + INIT_LIST_HEAD(&cam->outqueue); +} + + +static void sn9c102_queue_unusedframes(struct sn9c102_device *cam) +{ + unsigned long lock_flags; + u32 i; + + for (i = 0; i < cam->nbuffers; i++) + if (cam->frame[i].state == F_UNUSED) { + cam->frame[i].state = F_QUEUED; + spin_lock_irqsave(&cam->queue_lock, lock_flags); + list_add_tail(&cam->frame[i].frame, &cam->inqueue); + spin_unlock_irqrestore(&cam->queue_lock, lock_flags); + } +} + +/*****************************************************************************/ + +/* + Write a sequence of count value/register pairs. Returns -1 after the first + failed write, or 0 for no errors. +*/ +int sn9c102_write_regs(struct sn9c102_device *cam, const u8 valreg[][2], + int count) +{ + struct usb_device *udev = cam->usbdev; + u8 *buff = cam->control_buffer; + int i, res; + + for (i = 0; i < count; i++) { + u8 index = valreg[i][1]; + + /* + index is a u8, so it must be <256 and can't be out of range. + If we put in a check anyway, gcc annoys us with a warning + hat our check is useless. People get all uppity when they + see warnings in the kernel compile. + */ + + *buff = valreg[i][0]; + + res = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), 0x08, + 0x41, index, 0, buff, 1, + SN9C102_CTRL_TIMEOUT); + + if (res < 0) { + DBG(3, "Failed to write a register (value 0x%02X, " + "index 0x%02X, error %d)", *buff, index, res); + return -1; + } + + cam->reg[index] = *buff; + } + + return 0; +} + + +int sn9c102_write_reg(struct sn9c102_device *cam, u8 value, u16 index) +{ + struct usb_device *udev = cam->usbdev; + u8 *buff = cam->control_buffer; + int res; + + if (index >= ARRAY_SIZE(cam->reg)) + return -1; + + *buff = value; + + res = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), 0x08, 0x41, + index, 0, buff, 1, SN9C102_CTRL_TIMEOUT); + if (res < 0) { + DBG(3, "Failed to write a register (value 0x%02X, index " + "0x%02X, error %d)", value, index, res); + return -1; + } + + cam->reg[index] = value; + + return 0; +} + + +/* NOTE: with the SN9C10[123] reading some registers always returns 0 */ +int sn9c102_read_reg(struct sn9c102_device *cam, u16 index) +{ + struct usb_device *udev = cam->usbdev; + u8 *buff = cam->control_buffer; + int res; + + res = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), 0x00, 0xc1, + index, 0, buff, 1, SN9C102_CTRL_TIMEOUT); + if (res < 0) + DBG(3, "Failed to read a register (index 0x%02X, error %d)", + index, res); + + return (res >= 0) ? (int)(*buff) : -1; +} + + +int sn9c102_pread_reg(struct sn9c102_device *cam, u16 index) +{ + if (index >= ARRAY_SIZE(cam->reg)) + return -1; + + return cam->reg[index]; +} + + +static int +sn9c102_i2c_wait(struct sn9c102_device *cam, + const struct sn9c102_sensor *sensor) +{ + int i, r; + + for (i = 1; i <= 5; i++) { + r = sn9c102_read_reg(cam, 0x08); + if (r < 0) + return -EIO; + if (r & 0x04) + return 0; + if (sensor->frequency & SN9C102_I2C_400KHZ) + udelay(5*16); + else + udelay(16*16); + } + return -EBUSY; +} + + +static int +sn9c102_i2c_detect_read_error(struct sn9c102_device *cam, + const struct sn9c102_sensor *sensor) +{ + int r , err = 0; + + r = sn9c102_read_reg(cam, 0x08); + if (r < 0) + err += r; + + if (cam->bridge == BRIDGE_SN9C101 || cam->bridge == BRIDGE_SN9C102) { + if (!(r & 0x08)) + err += -1; + } else { + if (r & 0x08) + err += -1; + } + + return err ? -EIO : 0; +} + + +static int +sn9c102_i2c_detect_write_error(struct sn9c102_device *cam, + const struct sn9c102_sensor *sensor) +{ + int r; + + r = sn9c102_read_reg(cam, 0x08); + return (r < 0 || (r >= 0 && (r & 0x08))) ? -EIO : 0; +} + + +int +sn9c102_i2c_try_raw_read(struct sn9c102_device *cam, + const struct sn9c102_sensor *sensor, u8 data0, + u8 data1, u8 n, u8 buffer[]) +{ + struct usb_device *udev = cam->usbdev; + u8 *data = cam->control_buffer; + int i = 0, err = 0, res; + + /* Write cycle */ + data[0] = ((sensor->interface == SN9C102_I2C_2WIRES) ? 0x80 : 0) | + ((sensor->frequency & SN9C102_I2C_400KHZ) ? 0x01 : 0) | 0x10; + data[1] = data0; /* I2C slave id */ + data[2] = data1; /* address */ + data[7] = 0x10; + res = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), 0x08, 0x41, + 0x08, 0, data, 8, SN9C102_CTRL_TIMEOUT); + if (res < 0) + err += res; + + err += sn9c102_i2c_wait(cam, sensor); + + /* Read cycle - n bytes */ + data[0] = ((sensor->interface == SN9C102_I2C_2WIRES) ? 0x80 : 0) | + ((sensor->frequency & SN9C102_I2C_400KHZ) ? 0x01 : 0) | + (n << 4) | 0x02; + data[1] = data0; + data[7] = 0x10; + res = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), 0x08, 0x41, + 0x08, 0, data, 8, SN9C102_CTRL_TIMEOUT); + if (res < 0) + err += res; + + err += sn9c102_i2c_wait(cam, sensor); + + /* The first read byte will be placed in data[4] */ + res = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), 0x00, 0xc1, + 0x0a, 0, data, 5, SN9C102_CTRL_TIMEOUT); + if (res < 0) + err += res; + + err += sn9c102_i2c_detect_read_error(cam, sensor); + + PDBGG("I2C read: address 0x%02X, first read byte: 0x%02X", data1, + data[4]); + + if (err) { + DBG(3, "I2C read failed for %s image sensor", sensor->name); + return -1; + } + + if (buffer) + for (i = 0; i < n && i < 5; i++) + buffer[n-i-1] = data[4-i]; + + return (int)data[4]; +} + + +int +sn9c102_i2c_try_raw_write(struct sn9c102_device *cam, + const struct sn9c102_sensor *sensor, u8 n, u8 data0, + u8 data1, u8 data2, u8 data3, u8 data4, u8 data5) +{ + struct usb_device *udev = cam->usbdev; + u8 *data = cam->control_buffer; + int err = 0, res; + + /* Write cycle. It usually is address + value */ + data[0] = ((sensor->interface == SN9C102_I2C_2WIRES) ? 0x80 : 0) | + ((sensor->frequency & SN9C102_I2C_400KHZ) ? 0x01 : 0) + | ((n - 1) << 4); + data[1] = data0; + data[2] = data1; + data[3] = data2; + data[4] = data3; + data[5] = data4; + data[6] = data5; + data[7] = 0x17; + res = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), 0x08, 0x41, + 0x08, 0, data, 8, SN9C102_CTRL_TIMEOUT); + if (res < 0) + err += res; + + err += sn9c102_i2c_wait(cam, sensor); + err += sn9c102_i2c_detect_write_error(cam, sensor); + + if (err) + DBG(3, "I2C write failed for %s image sensor", sensor->name); + + PDBGG("I2C raw write: %u bytes, data0 = 0x%02X, data1 = 0x%02X, " + "data2 = 0x%02X, data3 = 0x%02X, data4 = 0x%02X, data5 = 0x%02X", + n, data0, data1, data2, data3, data4, data5); + + return err ? -1 : 0; +} + + +int +sn9c102_i2c_try_read(struct sn9c102_device *cam, + const struct sn9c102_sensor *sensor, u8 address) +{ + return sn9c102_i2c_try_raw_read(cam, sensor, sensor->i2c_slave_id, + address, 1, NULL); +} + + +static int sn9c102_i2c_try_write(struct sn9c102_device *cam, + const struct sn9c102_sensor *sensor, + u8 address, u8 value) +{ + return sn9c102_i2c_try_raw_write(cam, sensor, 3, + sensor->i2c_slave_id, address, + value, 0, 0, 0); +} + + +int sn9c102_i2c_read(struct sn9c102_device *cam, u8 address) +{ + return sn9c102_i2c_try_read(cam, &cam->sensor, address); +} + + +int sn9c102_i2c_write(struct sn9c102_device *cam, u8 address, u8 value) +{ + return sn9c102_i2c_try_write(cam, &cam->sensor, address, value); +} + +/*****************************************************************************/ + +static size_t sn9c102_sof_length(struct sn9c102_device *cam) +{ + switch (cam->bridge) { + case BRIDGE_SN9C101: + case BRIDGE_SN9C102: + return 12; + case BRIDGE_SN9C103: + return 18; + case BRIDGE_SN9C105: + case BRIDGE_SN9C120: + return 62; + } + + return 0; +} + + +static void* +sn9c102_find_sof_header(struct sn9c102_device *cam, void *mem, size_t len) +{ + static const char marker[6] = {0xff, 0xff, 0x00, 0xc4, 0xc4, 0x96}; + const char *m = mem; + size_t soflen = 0, i, j; + + soflen = sn9c102_sof_length(cam); + + for (i = 0; i < len; i++) { + size_t b; + + /* Read the variable part of the header */ + if (unlikely(cam->sof.bytesread >= sizeof(marker))) { + cam->sof.header[cam->sof.bytesread] = *(m+i); + if (++cam->sof.bytesread == soflen) { + cam->sof.bytesread = 0; + return mem + i; + } + continue; + } + + /* Search for the SOF marker (fixed part) in the header */ + for (j = 0, b = cam->sof.bytesread; j+b < sizeof(marker); j++) { + if (unlikely(i+j == len)) + return NULL; + if (*(m+i+j) == marker[cam->sof.bytesread]) { + cam->sof.header[cam->sof.bytesread] = *(m+i+j); + if (++cam->sof.bytesread == sizeof(marker)) { + PDBGG("Bytes to analyze: %zd. SOF " + "starts at byte #%zd", len, i); + i += j+1; + break; + } + } else { + cam->sof.bytesread = 0; + break; + } + } + } + + return NULL; +} + + +static void* +sn9c102_find_eof_header(struct sn9c102_device *cam, void *mem, size_t len) +{ + static const u8 eof_header[4][4] = { + {0x00, 0x00, 0x00, 0x00}, + {0x40, 0x00, 0x00, 0x00}, + {0x80, 0x00, 0x00, 0x00}, + {0xc0, 0x00, 0x00, 0x00}, + }; + size_t i, j; + + /* The EOF header does not exist in compressed data */ + if (cam->sensor.pix_format.pixelformat == V4L2_PIX_FMT_SN9C10X || + cam->sensor.pix_format.pixelformat == V4L2_PIX_FMT_JPEG) + return NULL; + + /* + The EOF header might cross the packet boundary, but this is not a + problem, since the end of a frame is determined by checking its size + in the first place. + */ + for (i = 0; (len >= 4) && (i <= len - 4); i++) + for (j = 0; j < ARRAY_SIZE(eof_header); j++) + if (!memcmp(mem + i, eof_header[j], 4)) + return mem + i; + + return NULL; +} + + +static void +sn9c102_write_jpegheader(struct sn9c102_device *cam, struct sn9c102_frame_t *f) +{ + static const u8 jpeg_header[589] = { + 0xff, 0xd8, 0xff, 0xdb, 0x00, 0x84, 0x00, 0x06, 0x04, 0x05, + 0x06, 0x05, 0x04, 0x06, 0x06, 0x05, 0x06, 0x07, 0x07, 0x06, + 0x08, 0x0a, 0x10, 0x0a, 0x0a, 0x09, 0x09, 0x0a, 0x14, 0x0e, + 0x0f, 0x0c, 0x10, 0x17, 0x14, 0x18, 0x18, 0x17, 0x14, 0x16, + 0x16, 0x1a, 0x1d, 0x25, 0x1f, 0x1a, 0x1b, 0x23, 0x1c, 0x16, + 0x16, 0x20, 0x2c, 0x20, 0x23, 0x26, 0x27, 0x29, 0x2a, 0x29, + 0x19, 0x1f, 0x2d, 0x30, 0x2d, 0x28, 0x30, 0x25, 0x28, 0x29, + 0x28, 0x01, 0x07, 0x07, 0x07, 0x0a, 0x08, 0x0a, 0x13, 0x0a, + 0x0a, 0x13, 0x28, 0x1a, 0x16, 0x1a, 0x28, 0x28, 0x28, 0x28, + 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, + 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, + 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, + 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, + 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0xff, 0xc4, 0x01, 0xa2, + 0x00, 0x00, 0x01, 0x05, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, + 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x01, + 0x00, 0x03, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, + 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x10, 0x00, + 0x02, 0x01, 0x03, 0x03, 0x02, 0x04, 0x03, 0x05, 0x05, 0x04, + 0x04, 0x00, 0x00, 0x01, 0x7d, 0x01, 0x02, 0x03, 0x00, 0x04, + 0x11, 0x05, 0x12, 0x21, 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, + 0x07, 0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xa1, 0x08, 0x23, + 0x42, 0xb1, 0xc1, 0x15, 0x52, 0xd1, 0xf0, 0x24, 0x33, 0x62, + 0x72, 0x82, 0x09, 0x0a, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x25, + 0x26, 0x27, 0x28, 0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, 0x38, + 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, + 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, + 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, + 0x77, 0x78, 0x79, 0x7a, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, + 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, + 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, + 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, + 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, + 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe1, 0xe2, 0xe3, + 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf1, 0xf2, 0xf3, + 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0x11, 0x00, 0x02, + 0x01, 0x02, 0x04, 0x04, 0x03, 0x04, 0x07, 0x05, 0x04, 0x04, + 0x00, 0x01, 0x02, 0x77, 0x00, 0x01, 0x02, 0x03, 0x11, 0x04, + 0x05, 0x21, 0x31, 0x06, 0x12, 0x41, 0x51, 0x07, 0x61, 0x71, + 0x13, 0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91, 0xa1, 0xb1, + 0xc1, 0x09, 0x23, 0x33, 0x52, 0xf0, 0x15, 0x62, 0x72, 0xd1, + 0x0a, 0x16, 0x24, 0x34, 0xe1, 0x25, 0xf1, 0x17, 0x18, 0x19, + 0x1a, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, 0x37, 0x38, + 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, + 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, + 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, + 0x77, 0x78, 0x79, 0x7a, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, + 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, + 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, + 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, + 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe2, 0xe3, + 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf2, 0xf3, 0xf4, + 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xff, 0xc0, 0x00, 0x11, + 0x08, 0x01, 0xe0, 0x02, 0x80, 0x03, 0x01, 0x21, 0x00, 0x02, + 0x11, 0x01, 0x03, 0x11, 0x01, 0xff, 0xda, 0x00, 0x0c, 0x03, + 0x01, 0x00, 0x02, 0x11, 0x03, 0x11, 0x00, 0x3f, 0x00 + }; + u8 *pos = f->bufmem; + + memcpy(pos, jpeg_header, sizeof(jpeg_header)); + *(pos + 6) = 0x00; + *(pos + 7 + 64) = 0x01; + if (cam->compression.quality == 0) { + memcpy(pos + 7, SN9C102_Y_QTABLE0, 64); + memcpy(pos + 8 + 64, SN9C102_UV_QTABLE0, 64); + } else if (cam->compression.quality == 1) { + memcpy(pos + 7, SN9C102_Y_QTABLE1, 64); + memcpy(pos + 8 + 64, SN9C102_UV_QTABLE1, 64); + } + *(pos + 564) = cam->sensor.pix_format.width & 0xFF; + *(pos + 563) = (cam->sensor.pix_format.width >> 8) & 0xFF; + *(pos + 562) = cam->sensor.pix_format.height & 0xFF; + *(pos + 561) = (cam->sensor.pix_format.height >> 8) & 0xFF; + *(pos + 567) = 0x21; + + f->buf.bytesused += sizeof(jpeg_header); +} + + +static void sn9c102_urb_complete(struct urb *urb) +{ + struct sn9c102_device *cam = urb->context; + struct sn9c102_frame_t **f; + size_t imagesize, soflen; + u8 i; + int err = 0; + + if (urb->status == -ENOENT) + return; + + f = &cam->frame_current; + + if (cam->stream == STREAM_INTERRUPT) { + cam->stream = STREAM_OFF; + if ((*f)) + (*f)->state = F_QUEUED; + cam->sof.bytesread = 0; + DBG(3, "Stream interrupted by application"); + wake_up(&cam->wait_stream); + } + + if (cam->state & DEV_DISCONNECTED) + return; + + if (cam->state & DEV_MISCONFIGURED) { + wake_up_interruptible(&cam->wait_frame); + return; + } + + if (cam->stream == STREAM_OFF || list_empty(&cam->inqueue)) + goto resubmit_urb; + + if (!(*f)) + (*f) = list_entry(cam->inqueue.next, struct sn9c102_frame_t, + frame); + + imagesize = (cam->sensor.pix_format.width * + cam->sensor.pix_format.height * + cam->sensor.pix_format.priv) / 8; + if (cam->sensor.pix_format.pixelformat == V4L2_PIX_FMT_JPEG) + imagesize += 589; /* length of jpeg header */ + soflen = sn9c102_sof_length(cam); + + for (i = 0; i < urb->number_of_packets; i++) { + unsigned int img, len, status; + void *pos, *sof, *eof; + + len = urb->iso_frame_desc[i].actual_length; + status = urb->iso_frame_desc[i].status; + pos = urb->iso_frame_desc[i].offset + urb->transfer_buffer; + + if (status) { + DBG(3, "Error in isochronous frame"); + (*f)->state = F_ERROR; + cam->sof.bytesread = 0; + continue; + } + + PDBGG("Isochrnous frame: length %u, #%u i", len, i); + +redo: + sof = sn9c102_find_sof_header(cam, pos, len); + if (likely(!sof)) { + eof = sn9c102_find_eof_header(cam, pos, len); + if ((*f)->state == F_GRABBING) { +end_of_frame: + img = len; + + if (eof) + img = (eof > pos) ? eof - pos - 1 : 0; + + if ((*f)->buf.bytesused + img > imagesize) { + u32 b; + b = (*f)->buf.bytesused + img - + imagesize; + img = imagesize - (*f)->buf.bytesused; + PDBGG("Expected EOF not found: video " + "frame cut"); + if (eof) + DBG(3, "Exceeded limit: +%u " + "bytes", (unsigned)(b)); + } + + memcpy((*f)->bufmem + (*f)->buf.bytesused, pos, + img); + + if ((*f)->buf.bytesused == 0) + v4l2_get_timestamp( + &(*f)->buf.timestamp); + + (*f)->buf.bytesused += img; + + if ((*f)->buf.bytesused == imagesize || + ((cam->sensor.pix_format.pixelformat == + V4L2_PIX_FMT_SN9C10X || + cam->sensor.pix_format.pixelformat == + V4L2_PIX_FMT_JPEG) && eof)) { + u32 b; + + b = (*f)->buf.bytesused; + (*f)->state = F_DONE; + (*f)->buf.sequence = ++cam->frame_count; + + spin_lock(&cam->queue_lock); + list_move_tail(&(*f)->frame, + &cam->outqueue); + if (!list_empty(&cam->inqueue)) + (*f) = list_entry( + cam->inqueue.next, + struct sn9c102_frame_t, + frame); + else + (*f) = NULL; + spin_unlock(&cam->queue_lock); + + memcpy(cam->sysfs.frame_header, + cam->sof.header, soflen); + + DBG(3, "Video frame captured: %lu " + "bytes", (unsigned long)(b)); + + if (!(*f)) + goto resubmit_urb; + + } else if (eof) { + (*f)->state = F_ERROR; + DBG(3, "Not expected EOF after %lu " + "bytes of image data", + (unsigned long) + ((*f)->buf.bytesused)); + } + + if (sof) /* (1) */ + goto start_of_frame; + + } else if (eof) { + DBG(3, "EOF without SOF"); + continue; + + } else { + PDBGG("Ignoring pointless isochronous frame"); + continue; + } + + } else if ((*f)->state == F_QUEUED || (*f)->state == F_ERROR) { +start_of_frame: + (*f)->state = F_GRABBING; + (*f)->buf.bytesused = 0; + len -= (sof - pos); + pos = sof; + if (cam->sensor.pix_format.pixelformat == + V4L2_PIX_FMT_JPEG) + sn9c102_write_jpegheader(cam, (*f)); + DBG(3, "SOF detected: new video frame"); + if (len) + goto redo; + + } else if ((*f)->state == F_GRABBING) { + eof = sn9c102_find_eof_header(cam, pos, len); + if (eof && eof < sof) + goto end_of_frame; /* (1) */ + else { + if (cam->sensor.pix_format.pixelformat == + V4L2_PIX_FMT_SN9C10X || + cam->sensor.pix_format.pixelformat == + V4L2_PIX_FMT_JPEG) { + if (sof - pos >= soflen) { + eof = sof - soflen; + } else { /* remove header */ + eof = pos; + (*f)->buf.bytesused -= + (soflen - (sof - pos)); + } + goto end_of_frame; + } else { + DBG(3, "SOF before expected EOF after " + "%lu bytes of image data", + (unsigned long) + ((*f)->buf.bytesused)); + goto start_of_frame; + } + } + } + } + +resubmit_urb: + urb->dev = cam->usbdev; + err = usb_submit_urb(urb, GFP_ATOMIC); + if (err < 0 && err != -EPERM) { + cam->state |= DEV_MISCONFIGURED; + DBG(1, "usb_submit_urb() failed"); + } + + wake_up_interruptible(&cam->wait_frame); +} + + +static int sn9c102_start_transfer(struct sn9c102_device *cam) +{ + struct usb_device *udev = cam->usbdev; + struct urb *urb; + struct usb_host_interface *altsetting = usb_altnum_to_altsetting( + usb_ifnum_to_if(udev, 0), + SN9C102_ALTERNATE_SETTING); + const unsigned int psz = le16_to_cpu(altsetting-> + endpoint[0].desc.wMaxPacketSize); + s8 i, j; + int err = 0; + + for (i = 0; i < SN9C102_URBS; i++) { + cam->transfer_buffer[i] = kzalloc(SN9C102_ISO_PACKETS * psz, + GFP_KERNEL); + if (!cam->transfer_buffer[i]) { + err = -ENOMEM; + DBG(1, "Not enough memory"); + goto free_buffers; + } + } + + for (i = 0; i < SN9C102_URBS; i++) { + urb = usb_alloc_urb(SN9C102_ISO_PACKETS, GFP_KERNEL); + cam->urb[i] = urb; + if (!urb) { + err = -ENOMEM; + DBG(1, "usb_alloc_urb() failed"); + goto free_urbs; + } + urb->dev = udev; + urb->context = cam; + urb->pipe = usb_rcvisocpipe(udev, 1); + urb->transfer_flags = URB_ISO_ASAP; + urb->number_of_packets = SN9C102_ISO_PACKETS; + urb->complete = sn9c102_urb_complete; + urb->transfer_buffer = cam->transfer_buffer[i]; + urb->transfer_buffer_length = psz * SN9C102_ISO_PACKETS; + urb->interval = 1; + for (j = 0; j < SN9C102_ISO_PACKETS; j++) { + urb->iso_frame_desc[j].offset = psz * j; + urb->iso_frame_desc[j].length = psz; + } + } + + /* Enable video */ + if (!(cam->reg[0x01] & 0x04)) { + err = sn9c102_write_reg(cam, cam->reg[0x01] | 0x04, 0x01); + if (err) { + err = -EIO; + DBG(1, "I/O hardware error"); + goto free_urbs; + } + } + + err = usb_set_interface(udev, 0, SN9C102_ALTERNATE_SETTING); + if (err) { + DBG(1, "usb_set_interface() failed"); + goto free_urbs; + } + + cam->frame_current = NULL; + cam->sof.bytesread = 0; + + for (i = 0; i < SN9C102_URBS; i++) { + err = usb_submit_urb(cam->urb[i], GFP_KERNEL); + if (err) { + for (j = i-1; j >= 0; j--) + usb_kill_urb(cam->urb[j]); + DBG(1, "usb_submit_urb() failed, error %d", err); + goto free_urbs; + } + } + + return 0; + +free_urbs: + for (i = 0; (i < SN9C102_URBS) && cam->urb[i]; i++) + usb_free_urb(cam->urb[i]); + +free_buffers: + for (i = 0; (i < SN9C102_URBS) && cam->transfer_buffer[i]; i++) + kfree(cam->transfer_buffer[i]); + + return err; +} + + +static int sn9c102_stop_transfer(struct sn9c102_device *cam) +{ + struct usb_device *udev = cam->usbdev; + s8 i; + int err = 0; + + if (cam->state & DEV_DISCONNECTED) + return 0; + + for (i = SN9C102_URBS-1; i >= 0; i--) { + usb_kill_urb(cam->urb[i]); + usb_free_urb(cam->urb[i]); + kfree(cam->transfer_buffer[i]); + } + + err = usb_set_interface(udev, 0, 0); /* 0 Mb/s */ + if (err) + DBG(3, "usb_set_interface() failed"); + + return err; +} + + +static int sn9c102_stream_interrupt(struct sn9c102_device *cam) +{ + cam->stream = STREAM_INTERRUPT; + wait_event_timeout(cam->wait_stream, + (cam->stream == STREAM_OFF) || + (cam->state & DEV_DISCONNECTED), + SN9C102_URB_TIMEOUT); + if (cam->state & DEV_DISCONNECTED) + return -ENODEV; + else if (cam->stream != STREAM_OFF) { + cam->state |= DEV_MISCONFIGURED; + DBG(1, "URB timeout reached. The camera is misconfigured. " + "To use it, close and open %s again.", + video_device_node_name(cam->v4ldev)); + return -EIO; + } + + return 0; +} + +/*****************************************************************************/ + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static u16 sn9c102_strtou16(const char *buff, size_t len, ssize_t *count) +{ + char str[7]; + char *endp; + unsigned long val; + + if (len < 6) { + strncpy(str, buff, len); + str[len] = '\0'; + } else { + strncpy(str, buff, 6); + str[6] = '\0'; + } + + val = simple_strtoul(str, &endp, 0); + + *count = 0; + if (val <= 0xffff) + *count = (ssize_t)(endp - str); + if ((*count) && (len == *count+1) && (buff[*count] == '\n')) + *count += 1; + + return (u16)val; +} + +/* + NOTE 1: being inside one of the following methods implies that the v4l + device exists for sure (see kobjects and reference counters) + NOTE 2: buffers are PAGE_SIZE long +*/ + +static ssize_t sn9c102_show_reg(struct device *cd, + struct device_attribute *attr, char *buf) +{ + struct sn9c102_device *cam; + ssize_t count; + + if (mutex_lock_interruptible(&sn9c102_sysfs_lock)) + return -ERESTARTSYS; + + cam = video_get_drvdata(container_of(cd, struct video_device, dev)); + if (!cam) { + mutex_unlock(&sn9c102_sysfs_lock); + return -ENODEV; + } + + count = sprintf(buf, "%u\n", cam->sysfs.reg); + + mutex_unlock(&sn9c102_sysfs_lock); + + return count; +} + + +static ssize_t +sn9c102_store_reg(struct device *cd, struct device_attribute *attr, + const char *buf, size_t len) +{ + struct sn9c102_device *cam; + u16 index; + ssize_t count; + + if (mutex_lock_interruptible(&sn9c102_sysfs_lock)) + return -ERESTARTSYS; + + cam = video_get_drvdata(container_of(cd, struct video_device, dev)); + if (!cam) { + mutex_unlock(&sn9c102_sysfs_lock); + return -ENODEV; + } + + index = sn9c102_strtou16(buf, len, &count); + if (index >= ARRAY_SIZE(cam->reg) || !count) { + mutex_unlock(&sn9c102_sysfs_lock); + return -EINVAL; + } + + cam->sysfs.reg = index; + + DBG(2, "Moved SN9C1XX register index to 0x%02X", cam->sysfs.reg); + DBG(3, "Written bytes: %zd", count); + + mutex_unlock(&sn9c102_sysfs_lock); + + return count; +} + + +static ssize_t sn9c102_show_val(struct device *cd, + struct device_attribute *attr, char *buf) +{ + struct sn9c102_device *cam; + ssize_t count; + int val; + + if (mutex_lock_interruptible(&sn9c102_sysfs_lock)) + return -ERESTARTSYS; + + cam = video_get_drvdata(container_of(cd, struct video_device, dev)); + if (!cam) { + mutex_unlock(&sn9c102_sysfs_lock); + return -ENODEV; + } + + val = sn9c102_read_reg(cam, cam->sysfs.reg); + if (val < 0) { + mutex_unlock(&sn9c102_sysfs_lock); + return -EIO; + } + + count = sprintf(buf, "%d\n", val); + + DBG(3, "Read bytes: %zd, value: %d", count, val); + + mutex_unlock(&sn9c102_sysfs_lock); + + return count; +} + + +static ssize_t +sn9c102_store_val(struct device *cd, struct device_attribute *attr, + const char *buf, size_t len) +{ + struct sn9c102_device *cam; + u16 value; + ssize_t count; + int err; + + if (mutex_lock_interruptible(&sn9c102_sysfs_lock)) + return -ERESTARTSYS; + + cam = video_get_drvdata(container_of(cd, struct video_device, dev)); + if (!cam) { + mutex_unlock(&sn9c102_sysfs_lock); + return -ENODEV; + } + + value = sn9c102_strtou16(buf, len, &count); + if (!count) { + mutex_unlock(&sn9c102_sysfs_lock); + return -EINVAL; + } + + err = sn9c102_write_reg(cam, value, cam->sysfs.reg); + if (err) { + mutex_unlock(&sn9c102_sysfs_lock); + return -EIO; + } + + DBG(2, "Written SN9C1XX reg. 0x%02X, val. 0x%02X", + cam->sysfs.reg, value); + DBG(3, "Written bytes: %zd", count); + + mutex_unlock(&sn9c102_sysfs_lock); + + return count; +} + + +static ssize_t sn9c102_show_i2c_reg(struct device *cd, + struct device_attribute *attr, char *buf) +{ + struct sn9c102_device *cam; + ssize_t count; + + if (mutex_lock_interruptible(&sn9c102_sysfs_lock)) + return -ERESTARTSYS; + + cam = video_get_drvdata(container_of(cd, struct video_device, dev)); + if (!cam) { + mutex_unlock(&sn9c102_sysfs_lock); + return -ENODEV; + } + + count = sprintf(buf, "%u\n", cam->sysfs.i2c_reg); + + DBG(3, "Read bytes: %zd", count); + + mutex_unlock(&sn9c102_sysfs_lock); + + return count; +} + + +static ssize_t +sn9c102_store_i2c_reg(struct device *cd, struct device_attribute *attr, + const char *buf, size_t len) +{ + struct sn9c102_device *cam; + u16 index; + ssize_t count; + + if (mutex_lock_interruptible(&sn9c102_sysfs_lock)) + return -ERESTARTSYS; + + cam = video_get_drvdata(container_of(cd, struct video_device, dev)); + if (!cam) { + mutex_unlock(&sn9c102_sysfs_lock); + return -ENODEV; + } + + index = sn9c102_strtou16(buf, len, &count); + if (!count) { + mutex_unlock(&sn9c102_sysfs_lock); + return -EINVAL; + } + + cam->sysfs.i2c_reg = index; + + DBG(2, "Moved sensor register index to 0x%02X", cam->sysfs.i2c_reg); + DBG(3, "Written bytes: %zd", count); + + mutex_unlock(&sn9c102_sysfs_lock); + + return count; +} + + +static ssize_t sn9c102_show_i2c_val(struct device *cd, + struct device_attribute *attr, char *buf) +{ + struct sn9c102_device *cam; + ssize_t count; + int val; + + if (mutex_lock_interruptible(&sn9c102_sysfs_lock)) + return -ERESTARTSYS; + + cam = video_get_drvdata(container_of(cd, struct video_device, dev)); + if (!cam) { + mutex_unlock(&sn9c102_sysfs_lock); + return -ENODEV; + } + + if (!(cam->sensor.sysfs_ops & SN9C102_I2C_READ)) { + mutex_unlock(&sn9c102_sysfs_lock); + return -ENOSYS; + } + + val = sn9c102_i2c_read(cam, cam->sysfs.i2c_reg); + if (val < 0) { + mutex_unlock(&sn9c102_sysfs_lock); + return -EIO; + } + + count = sprintf(buf, "%d\n", val); + + DBG(3, "Read bytes: %zd, value: %d", count, val); + + mutex_unlock(&sn9c102_sysfs_lock); + + return count; +} + + +static ssize_t +sn9c102_store_i2c_val(struct device *cd, struct device_attribute *attr, + const char *buf, size_t len) +{ + struct sn9c102_device *cam; + u16 value; + ssize_t count; + int err; + + if (mutex_lock_interruptible(&sn9c102_sysfs_lock)) + return -ERESTARTSYS; + + cam = video_get_drvdata(container_of(cd, struct video_device, dev)); + if (!cam) { + mutex_unlock(&sn9c102_sysfs_lock); + return -ENODEV; + } + + if (!(cam->sensor.sysfs_ops & SN9C102_I2C_WRITE)) { + mutex_unlock(&sn9c102_sysfs_lock); + return -ENOSYS; + } + + value = sn9c102_strtou16(buf, len, &count); + if (!count) { + mutex_unlock(&sn9c102_sysfs_lock); + return -EINVAL; + } + + err = sn9c102_i2c_write(cam, cam->sysfs.i2c_reg, value); + if (err) { + mutex_unlock(&sn9c102_sysfs_lock); + return -EIO; + } + + DBG(2, "Written sensor reg. 0x%02X, val. 0x%02X", + cam->sysfs.i2c_reg, value); + DBG(3, "Written bytes: %zd", count); + + mutex_unlock(&sn9c102_sysfs_lock); + + return count; +} + + +static ssize_t +sn9c102_store_green(struct device *cd, struct device_attribute *attr, + const char *buf, size_t len) +{ + struct sn9c102_device *cam; + enum sn9c102_bridge bridge; + ssize_t res = 0; + u16 value; + ssize_t count; + + if (mutex_lock_interruptible(&sn9c102_sysfs_lock)) + return -ERESTARTSYS; + + cam = video_get_drvdata(container_of(cd, struct video_device, dev)); + if (!cam) { + mutex_unlock(&sn9c102_sysfs_lock); + return -ENODEV; + } + + bridge = cam->bridge; + + mutex_unlock(&sn9c102_sysfs_lock); + + value = sn9c102_strtou16(buf, len, &count); + if (!count) + return -EINVAL; + + switch (bridge) { + case BRIDGE_SN9C101: + case BRIDGE_SN9C102: + if (value > 0x0f) + return -EINVAL; + res = sn9c102_store_reg(cd, attr, "0x11", 4); + if (res >= 0) + res = sn9c102_store_val(cd, attr, buf, len); + break; + case BRIDGE_SN9C103: + case BRIDGE_SN9C105: + case BRIDGE_SN9C120: + if (value > 0x7f) + return -EINVAL; + res = sn9c102_store_reg(cd, attr, "0x07", 4); + if (res >= 0) + res = sn9c102_store_val(cd, attr, buf, len); + break; + } + + return res; +} + + +static ssize_t +sn9c102_store_blue(struct device *cd, struct device_attribute *attr, + const char *buf, size_t len) +{ + ssize_t res = 0; + u16 value; + ssize_t count; + + value = sn9c102_strtou16(buf, len, &count); + if (!count || value > 0x7f) + return -EINVAL; + + res = sn9c102_store_reg(cd, attr, "0x06", 4); + if (res >= 0) + res = sn9c102_store_val(cd, attr, buf, len); + + return res; +} + + +static ssize_t +sn9c102_store_red(struct device *cd, struct device_attribute *attr, + const char *buf, size_t len) +{ + ssize_t res = 0; + u16 value; + ssize_t count; + + value = sn9c102_strtou16(buf, len, &count); + if (!count || value > 0x7f) + return -EINVAL; + res = sn9c102_store_reg(cd, attr, "0x05", 4); + if (res >= 0) + res = sn9c102_store_val(cd, attr, buf, len); + + return res; +} + + +static ssize_t sn9c102_show_frame_header(struct device *cd, + struct device_attribute *attr, + char *buf) +{ + struct sn9c102_device *cam; + ssize_t count; + + cam = video_get_drvdata(container_of(cd, struct video_device, dev)); + if (!cam) + return -ENODEV; + + count = sizeof(cam->sysfs.frame_header); + memcpy(buf, cam->sysfs.frame_header, count); + + DBG(3, "Frame header, read bytes: %zd", count); + + return count; +} + + +static DEVICE_ATTR(reg, S_IRUGO | S_IWUSR, sn9c102_show_reg, sn9c102_store_reg); +static DEVICE_ATTR(val, S_IRUGO | S_IWUSR, sn9c102_show_val, sn9c102_store_val); +static DEVICE_ATTR(i2c_reg, S_IRUGO | S_IWUSR, + sn9c102_show_i2c_reg, sn9c102_store_i2c_reg); +static DEVICE_ATTR(i2c_val, S_IRUGO | S_IWUSR, + sn9c102_show_i2c_val, sn9c102_store_i2c_val); +static DEVICE_ATTR(green, S_IWUSR, NULL, sn9c102_store_green); +static DEVICE_ATTR(blue, S_IWUSR, NULL, sn9c102_store_blue); +static DEVICE_ATTR(red, S_IWUSR, NULL, sn9c102_store_red); +static DEVICE_ATTR(frame_header, S_IRUGO, sn9c102_show_frame_header, NULL); + + +static int sn9c102_create_sysfs(struct sn9c102_device *cam) +{ + struct device *dev = &(cam->v4ldev->dev); + int err = 0; + + err = device_create_file(dev, &dev_attr_reg); + if (err) + goto err_out; + err = device_create_file(dev, &dev_attr_val); + if (err) + goto err_reg; + err = device_create_file(dev, &dev_attr_frame_header); + if (err) + goto err_val; + + if (cam->sensor.sysfs_ops) { + err = device_create_file(dev, &dev_attr_i2c_reg); + if (err) + goto err_frame_header; + err = device_create_file(dev, &dev_attr_i2c_val); + if (err) + goto err_i2c_reg; + } + + if (cam->bridge == BRIDGE_SN9C101 || cam->bridge == BRIDGE_SN9C102) { + err = device_create_file(dev, &dev_attr_green); + if (err) + goto err_i2c_val; + } else { + err = device_create_file(dev, &dev_attr_blue); + if (err) + goto err_i2c_val; + err = device_create_file(dev, &dev_attr_red); + if (err) + goto err_blue; + } + + return 0; + +err_blue: + device_remove_file(dev, &dev_attr_blue); +err_i2c_val: + if (cam->sensor.sysfs_ops) + device_remove_file(dev, &dev_attr_i2c_val); +err_i2c_reg: + if (cam->sensor.sysfs_ops) + device_remove_file(dev, &dev_attr_i2c_reg); +err_frame_header: + device_remove_file(dev, &dev_attr_frame_header); +err_val: + device_remove_file(dev, &dev_attr_val); +err_reg: + device_remove_file(dev, &dev_attr_reg); +err_out: + return err; +} +#endif /* CONFIG_VIDEO_ADV_DEBUG */ + +/*****************************************************************************/ + +static int +sn9c102_set_pix_format(struct sn9c102_device *cam, struct v4l2_pix_format *pix) +{ + int err = 0; + + if (pix->pixelformat == V4L2_PIX_FMT_SN9C10X || + pix->pixelformat == V4L2_PIX_FMT_JPEG) { + switch (cam->bridge) { + case BRIDGE_SN9C101: + case BRIDGE_SN9C102: + case BRIDGE_SN9C103: + err += sn9c102_write_reg(cam, cam->reg[0x18] | 0x80, + 0x18); + break; + case BRIDGE_SN9C105: + case BRIDGE_SN9C120: + err += sn9c102_write_reg(cam, cam->reg[0x18] & 0x7f, + 0x18); + break; + } + } else { + switch (cam->bridge) { + case BRIDGE_SN9C101: + case BRIDGE_SN9C102: + case BRIDGE_SN9C103: + err += sn9c102_write_reg(cam, cam->reg[0x18] & 0x7f, + 0x18); + break; + case BRIDGE_SN9C105: + case BRIDGE_SN9C120: + err += sn9c102_write_reg(cam, cam->reg[0x18] | 0x80, + 0x18); + break; + } + } + + return err ? -EIO : 0; +} + + +static int +sn9c102_set_compression(struct sn9c102_device *cam, + struct v4l2_jpegcompression *compression) +{ + int i, err = 0; + + switch (cam->bridge) { + case BRIDGE_SN9C101: + case BRIDGE_SN9C102: + case BRIDGE_SN9C103: + if (compression->quality == 0) + err += sn9c102_write_reg(cam, cam->reg[0x17] | 0x01, + 0x17); + else if (compression->quality == 1) + err += sn9c102_write_reg(cam, cam->reg[0x17] & 0xfe, + 0x17); + break; + case BRIDGE_SN9C105: + case BRIDGE_SN9C120: + if (compression->quality == 0) { + for (i = 0; i <= 63; i++) { + err += sn9c102_write_reg(cam, + SN9C102_Y_QTABLE1[i], + 0x100 + i); + err += sn9c102_write_reg(cam, + SN9C102_UV_QTABLE1[i], + 0x140 + i); + } + err += sn9c102_write_reg(cam, cam->reg[0x18] & 0xbf, + 0x18); + } else if (compression->quality == 1) { + for (i = 0; i <= 63; i++) { + err += sn9c102_write_reg(cam, + SN9C102_Y_QTABLE1[i], + 0x100 + i); + err += sn9c102_write_reg(cam, + SN9C102_UV_QTABLE1[i], + 0x140 + i); + } + err += sn9c102_write_reg(cam, cam->reg[0x18] | 0x40, + 0x18); + } + break; + } + + return err ? -EIO : 0; +} + + +static int sn9c102_set_scale(struct sn9c102_device *cam, u8 scale) +{ + u8 r = 0; + int err = 0; + + if (scale == 1) + r = cam->reg[0x18] & 0xcf; + else if (scale == 2) { + r = cam->reg[0x18] & 0xcf; + r |= 0x10; + } else if (scale == 4) + r = cam->reg[0x18] | 0x20; + + err += sn9c102_write_reg(cam, r, 0x18); + if (err) + return -EIO; + + PDBGG("Scaling factor: %u", scale); + + return 0; +} + + +static int sn9c102_set_crop(struct sn9c102_device *cam, struct v4l2_rect *rect) +{ + struct sn9c102_sensor *s = &cam->sensor; + u8 h_start = (u8)(rect->left - s->cropcap.bounds.left), + v_start = (u8)(rect->top - s->cropcap.bounds.top), + h_size = (u8)(rect->width / 16), + v_size = (u8)(rect->height / 16); + int err = 0; + + err += sn9c102_write_reg(cam, h_start, 0x12); + err += sn9c102_write_reg(cam, v_start, 0x13); + err += sn9c102_write_reg(cam, h_size, 0x15); + err += sn9c102_write_reg(cam, v_size, 0x16); + if (err) + return -EIO; + + PDBGG("h_start, v_start, h_size, v_size, ho_size, vo_size " + "%u %u %u %u", h_start, v_start, h_size, v_size); + + return 0; +} + + +static int sn9c102_init(struct sn9c102_device *cam) +{ + struct sn9c102_sensor *s = &cam->sensor; + struct v4l2_control ctrl; + struct v4l2_queryctrl *qctrl; + struct v4l2_rect *rect; + u8 i = 0; + int err = 0; + + if (!(cam->state & DEV_INITIALIZED)) { + mutex_init(&cam->open_mutex); + init_waitqueue_head(&cam->wait_open); + qctrl = s->qctrl; + rect = &(s->cropcap.defrect); + } else { /* use current values */ + qctrl = s->_qctrl; + rect = &(s->_rect); + } + + err += sn9c102_set_scale(cam, rect->width / s->pix_format.width); + err += sn9c102_set_crop(cam, rect); + if (err) + return err; + + if (s->init) { + err = s->init(cam); + if (err) { + DBG(3, "Sensor initialization failed"); + return err; + } + } + + if (!(cam->state & DEV_INITIALIZED)) + if (cam->bridge == BRIDGE_SN9C101 || + cam->bridge == BRIDGE_SN9C102 || + cam->bridge == BRIDGE_SN9C103) { + if (s->pix_format.pixelformat == V4L2_PIX_FMT_JPEG) + s->pix_format.pixelformat = V4L2_PIX_FMT_SBGGR8; + cam->compression.quality = cam->reg[0x17] & 0x01 ? + 0 : 1; + } else { + if (s->pix_format.pixelformat == V4L2_PIX_FMT_SN9C10X) + s->pix_format.pixelformat = V4L2_PIX_FMT_JPEG; + cam->compression.quality = cam->reg[0x18] & 0x40 ? + 0 : 1; + err += sn9c102_set_compression(cam, &cam->compression); + } + else + err += sn9c102_set_compression(cam, &cam->compression); + err += sn9c102_set_pix_format(cam, &s->pix_format); + if (s->set_pix_format) + err += s->set_pix_format(cam, &s->pix_format); + if (err) + return err; + + if (s->pix_format.pixelformat == V4L2_PIX_FMT_SN9C10X || + s->pix_format.pixelformat == V4L2_PIX_FMT_JPEG) + DBG(3, "Compressed video format is active, quality %d", + cam->compression.quality); + else + DBG(3, "Uncompressed video format is active"); + + if (s->set_crop) { + err = s->set_crop(cam, rect); + if (err) { + DBG(3, "set_crop() failed"); + return err; + } + } + + if (s->set_ctrl) { + for (i = 0; i < ARRAY_SIZE(s->qctrl); i++) + if (s->qctrl[i].id != 0 && + !(s->qctrl[i].flags & V4L2_CTRL_FLAG_DISABLED)) { + ctrl.id = s->qctrl[i].id; + ctrl.value = qctrl[i].default_value; + err = s->set_ctrl(cam, &ctrl); + if (err) { + DBG(3, "Set %s control failed", + s->qctrl[i].name); + return err; + } + DBG(3, "Image sensor supports '%s' control", + s->qctrl[i].name); + } + } + + if (!(cam->state & DEV_INITIALIZED)) { + mutex_init(&cam->fileop_mutex); + spin_lock_init(&cam->queue_lock); + init_waitqueue_head(&cam->wait_frame); + init_waitqueue_head(&cam->wait_stream); + cam->nreadbuffers = 2; + memcpy(s->_qctrl, s->qctrl, sizeof(s->qctrl)); + memcpy(&(s->_rect), &(s->cropcap.defrect), + sizeof(struct v4l2_rect)); + cam->state |= DEV_INITIALIZED; + } + + DBG(2, "Initialization succeeded"); + return 0; +} + +/*****************************************************************************/ + +static void sn9c102_release_resources(struct kref *kref) +{ + struct sn9c102_device *cam; + + mutex_lock(&sn9c102_sysfs_lock); + + cam = container_of(kref, struct sn9c102_device, kref); + + DBG(2, "V4L2 device %s deregistered", + video_device_node_name(cam->v4ldev)); + video_set_drvdata(cam->v4ldev, NULL); + video_unregister_device(cam->v4ldev); + v4l2_device_unregister(&cam->v4l2_dev); + usb_put_dev(cam->usbdev); + kfree(cam->control_buffer); + kfree(cam); + + mutex_unlock(&sn9c102_sysfs_lock); + +} + + +static int sn9c102_open(struct file *filp) +{ + struct sn9c102_device *cam; + int err = 0; + + /* + A read_trylock() in open() is the only safe way to prevent race + conditions with disconnect(), one close() and multiple (not + necessarily simultaneous) attempts to open(). For example, it + prevents from waiting for a second access, while the device + structure is being deallocated, after a possible disconnect() and + during a following close() holding the write lock: given that, after + this deallocation, no access will be possible anymore, using the + non-trylock version would have let open() gain the access to the + device structure improperly. + For this reason the lock must also not be per-device. + */ + if (!down_read_trylock(&sn9c102_dev_lock)) + return -ERESTARTSYS; + + cam = video_drvdata(filp); + + if (wait_for_completion_interruptible(&cam->probe)) { + up_read(&sn9c102_dev_lock); + return -ERESTARTSYS; + } + + kref_get(&cam->kref); + + /* + Make sure to isolate all the simultaneous opens. + */ + if (mutex_lock_interruptible(&cam->open_mutex)) { + kref_put(&cam->kref, sn9c102_release_resources); + up_read(&sn9c102_dev_lock); + return -ERESTARTSYS; + } + + if (cam->state & DEV_DISCONNECTED) { + DBG(1, "Device not present"); + err = -ENODEV; + goto out; + } + + if (cam->users) { + DBG(2, "Device %s is already in use", + video_device_node_name(cam->v4ldev)); + DBG(3, "Simultaneous opens are not supported"); + /* + open() must follow the open flags and should block + eventually while the device is in use. + */ + if ((filp->f_flags & O_NONBLOCK) || + (filp->f_flags & O_NDELAY)) { + err = -EWOULDBLOCK; + goto out; + } + DBG(2, "A blocking open() has been requested. Wait for the " + "device to be released..."); + up_read(&sn9c102_dev_lock); + /* + We will not release the "open_mutex" lock, so that only one + process can be in the wait queue below. This way the process + will be sleeping while holding the lock, without losing its + priority after any wake_up(). + */ + err = wait_event_interruptible_exclusive(cam->wait_open, + (cam->state & DEV_DISCONNECTED) + || !cam->users); + down_read(&sn9c102_dev_lock); + if (err) + goto out; + if (cam->state & DEV_DISCONNECTED) { + err = -ENODEV; + goto out; + } + } + + if (cam->state & DEV_MISCONFIGURED) { + err = sn9c102_init(cam); + if (err) { + DBG(1, "Initialization failed again. " + "I will retry on next open()."); + goto out; + } + cam->state &= ~DEV_MISCONFIGURED; + } + + err = sn9c102_start_transfer(cam); + if (err) + goto out; + + filp->private_data = cam; + cam->users++; + cam->io = IO_NONE; + cam->stream = STREAM_OFF; + cam->nbuffers = 0; + cam->frame_count = 0; + sn9c102_empty_framequeues(cam); + + DBG(3, "Video device %s is open", video_device_node_name(cam->v4ldev)); + +out: + mutex_unlock(&cam->open_mutex); + if (err) + kref_put(&cam->kref, sn9c102_release_resources); + + up_read(&sn9c102_dev_lock); + return err; +} + + +static int sn9c102_release(struct file *filp) +{ + struct sn9c102_device *cam; + + down_write(&sn9c102_dev_lock); + + cam = video_drvdata(filp); + + sn9c102_stop_transfer(cam); + sn9c102_release_buffers(cam); + cam->users--; + wake_up_interruptible_nr(&cam->wait_open, 1); + + DBG(3, "Video device %s closed", video_device_node_name(cam->v4ldev)); + + kref_put(&cam->kref, sn9c102_release_resources); + + up_write(&sn9c102_dev_lock); + + return 0; +} + + +static ssize_t +sn9c102_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos) +{ + struct sn9c102_device *cam = video_drvdata(filp); + struct sn9c102_frame_t *f, *i; + unsigned long lock_flags; + long timeout; + int err = 0; + + if (mutex_lock_interruptible(&cam->fileop_mutex)) + return -ERESTARTSYS; + + if (cam->state & DEV_DISCONNECTED) { + DBG(1, "Device not present"); + mutex_unlock(&cam->fileop_mutex); + return -ENODEV; + } + + if (cam->state & DEV_MISCONFIGURED) { + DBG(1, "The camera is misconfigured. Close and open it " + "again."); + mutex_unlock(&cam->fileop_mutex); + return -EIO; + } + + if (cam->io == IO_MMAP) { + DBG(3, "Close and open the device again to choose " + "the read method"); + mutex_unlock(&cam->fileop_mutex); + return -EBUSY; + } + + if (cam->io == IO_NONE) { + if (!sn9c102_request_buffers(cam, cam->nreadbuffers, IO_READ)) { + DBG(1, "read() failed, not enough memory"); + mutex_unlock(&cam->fileop_mutex); + return -ENOMEM; + } + cam->io = IO_READ; + cam->stream = STREAM_ON; + } + + if (list_empty(&cam->inqueue)) { + if (!list_empty(&cam->outqueue)) + sn9c102_empty_framequeues(cam); + sn9c102_queue_unusedframes(cam); + } + + if (!count) { + mutex_unlock(&cam->fileop_mutex); + return 0; + } + + if (list_empty(&cam->outqueue)) { + if (filp->f_flags & O_NONBLOCK) { + mutex_unlock(&cam->fileop_mutex); + return -EAGAIN; + } + if (!cam->module_param.frame_timeout) { + err = wait_event_interruptible + (cam->wait_frame, + (!list_empty(&cam->outqueue)) || + (cam->state & DEV_DISCONNECTED) || + (cam->state & DEV_MISCONFIGURED)); + if (err) { + mutex_unlock(&cam->fileop_mutex); + return err; + } + } else { + timeout = wait_event_interruptible_timeout + (cam->wait_frame, + (!list_empty(&cam->outqueue)) || + (cam->state & DEV_DISCONNECTED) || + (cam->state & DEV_MISCONFIGURED), + msecs_to_jiffies( + cam->module_param.frame_timeout * 1000 + ) + ); + if (timeout < 0) { + mutex_unlock(&cam->fileop_mutex); + return timeout; + } else if (timeout == 0 && + !(cam->state & DEV_DISCONNECTED)) { + DBG(1, "Video frame timeout elapsed"); + mutex_unlock(&cam->fileop_mutex); + return -EIO; + } + } + if (cam->state & DEV_DISCONNECTED) { + mutex_unlock(&cam->fileop_mutex); + return -ENODEV; + } + if (cam->state & DEV_MISCONFIGURED) { + mutex_unlock(&cam->fileop_mutex); + return -EIO; + } + } + + f = list_entry(cam->outqueue.prev, struct sn9c102_frame_t, frame); + + if (count > f->buf.bytesused) + count = f->buf.bytesused; + + if (copy_to_user(buf, f->bufmem, count)) { + err = -EFAULT; + goto exit; + } + *f_pos += count; + +exit: + spin_lock_irqsave(&cam->queue_lock, lock_flags); + list_for_each_entry(i, &cam->outqueue, frame) + i->state = F_UNUSED; + INIT_LIST_HEAD(&cam->outqueue); + spin_unlock_irqrestore(&cam->queue_lock, lock_flags); + + sn9c102_queue_unusedframes(cam); + + PDBGG("Frame #%lu, bytes read: %zu", + (unsigned long)f->buf.index, count); + + mutex_unlock(&cam->fileop_mutex); + + return count; +} + + +static unsigned int sn9c102_poll(struct file *filp, poll_table *wait) +{ + struct sn9c102_device *cam = video_drvdata(filp); + struct sn9c102_frame_t *f; + unsigned long lock_flags; + unsigned int mask = 0; + + if (mutex_lock_interruptible(&cam->fileop_mutex)) + return POLLERR; + + if (cam->state & DEV_DISCONNECTED) { + DBG(1, "Device not present"); + goto error; + } + + if (cam->state & DEV_MISCONFIGURED) { + DBG(1, "The camera is misconfigured. Close and open it " + "again."); + goto error; + } + + if (cam->io == IO_NONE) { + if (!sn9c102_request_buffers(cam, cam->nreadbuffers, + IO_READ)) { + DBG(1, "poll() failed, not enough memory"); + goto error; + } + cam->io = IO_READ; + cam->stream = STREAM_ON; + } + + if (cam->io == IO_READ) { + spin_lock_irqsave(&cam->queue_lock, lock_flags); + list_for_each_entry(f, &cam->outqueue, frame) + f->state = F_UNUSED; + INIT_LIST_HEAD(&cam->outqueue); + spin_unlock_irqrestore(&cam->queue_lock, lock_flags); + sn9c102_queue_unusedframes(cam); + } + + poll_wait(filp, &cam->wait_frame, wait); + + if (!list_empty(&cam->outqueue)) + mask |= POLLIN | POLLRDNORM; + + mutex_unlock(&cam->fileop_mutex); + + return mask; + +error: + mutex_unlock(&cam->fileop_mutex); + return POLLERR; +} + + +static void sn9c102_vm_open(struct vm_area_struct *vma) +{ + struct sn9c102_frame_t *f = vma->vm_private_data; + f->vma_use_count++; +} + + +static void sn9c102_vm_close(struct vm_area_struct *vma) +{ + /* NOTE: buffers are not freed here */ + struct sn9c102_frame_t *f = vma->vm_private_data; + f->vma_use_count--; +} + + +static const struct vm_operations_struct sn9c102_vm_ops = { + .open = sn9c102_vm_open, + .close = sn9c102_vm_close, +}; + + +static int sn9c102_mmap(struct file *filp, struct vm_area_struct *vma) +{ + struct sn9c102_device *cam = video_drvdata(filp); + unsigned long size = vma->vm_end - vma->vm_start, + start = vma->vm_start; + void *pos; + u32 i; + + if (mutex_lock_interruptible(&cam->fileop_mutex)) + return -ERESTARTSYS; + + if (cam->state & DEV_DISCONNECTED) { + DBG(1, "Device not present"); + mutex_unlock(&cam->fileop_mutex); + return -ENODEV; + } + + if (cam->state & DEV_MISCONFIGURED) { + DBG(1, "The camera is misconfigured. Close and open it " + "again."); + mutex_unlock(&cam->fileop_mutex); + return -EIO; + } + + if (!(vma->vm_flags & (VM_WRITE | VM_READ))) { + mutex_unlock(&cam->fileop_mutex); + return -EACCES; + } + + if (cam->io != IO_MMAP || + size != PAGE_ALIGN(cam->frame[0].buf.length)) { + mutex_unlock(&cam->fileop_mutex); + return -EINVAL; + } + + for (i = 0; i < cam->nbuffers; i++) { + if ((cam->frame[i].buf.m.offset>>PAGE_SHIFT) == vma->vm_pgoff) + break; + } + if (i == cam->nbuffers) { + mutex_unlock(&cam->fileop_mutex); + return -EINVAL; + } + + vma->vm_flags |= VM_IO | VM_DONTEXPAND | VM_DONTDUMP; + + pos = cam->frame[i].bufmem; + while (size > 0) { /* size is page-aligned */ + if (vm_insert_page(vma, start, vmalloc_to_page(pos))) { + mutex_unlock(&cam->fileop_mutex); + return -EAGAIN; + } + start += PAGE_SIZE; + pos += PAGE_SIZE; + size -= PAGE_SIZE; + } + + vma->vm_ops = &sn9c102_vm_ops; + vma->vm_private_data = &cam->frame[i]; + sn9c102_vm_open(vma); + + mutex_unlock(&cam->fileop_mutex); + + return 0; +} + +/*****************************************************************************/ + +static int +sn9c102_vidioc_querycap(struct sn9c102_device *cam, void __user *arg) +{ + struct v4l2_capability cap = { + .driver = "sn9c102", + .version = LINUX_VERSION_CODE, + .capabilities = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_READWRITE | + V4L2_CAP_STREAMING, + }; + + strlcpy(cap.card, cam->v4ldev->name, sizeof(cap.card)); + if (usb_make_path(cam->usbdev, cap.bus_info, sizeof(cap.bus_info)) < 0) + strlcpy(cap.bus_info, dev_name(&cam->usbdev->dev), + sizeof(cap.bus_info)); + + if (copy_to_user(arg, &cap, sizeof(cap))) + return -EFAULT; + + return 0; +} + + +static int +sn9c102_vidioc_enuminput(struct sn9c102_device *cam, void __user *arg) +{ + struct v4l2_input i; + + if (copy_from_user(&i, arg, sizeof(i))) + return -EFAULT; + + if (i.index) + return -EINVAL; + + memset(&i, 0, sizeof(i)); + strcpy(i.name, "Camera"); + i.type = V4L2_INPUT_TYPE_CAMERA; + i.capabilities = V4L2_IN_CAP_STD; + + if (copy_to_user(arg, &i, sizeof(i))) + return -EFAULT; + + return 0; +} + + +static int +sn9c102_vidioc_g_input(struct sn9c102_device *cam, void __user *arg) +{ + int index = 0; + + if (copy_to_user(arg, &index, sizeof(index))) + return -EFAULT; + + return 0; +} + + +static int +sn9c102_vidioc_s_input(struct sn9c102_device *cam, void __user *arg) +{ + int index; + + if (copy_from_user(&index, arg, sizeof(index))) + return -EFAULT; + + if (index != 0) + return -EINVAL; + + return 0; +} + + +static int +sn9c102_vidioc_query_ctrl(struct sn9c102_device *cam, void __user *arg) +{ + struct sn9c102_sensor *s = &cam->sensor; + struct v4l2_queryctrl qc; + u8 i; + + if (copy_from_user(&qc, arg, sizeof(qc))) + return -EFAULT; + + for (i = 0; i < ARRAY_SIZE(s->qctrl); i++) + if (qc.id && qc.id == s->qctrl[i].id) { + memcpy(&qc, &(s->qctrl[i]), sizeof(qc)); + if (copy_to_user(arg, &qc, sizeof(qc))) + return -EFAULT; + return 0; + } + + return -EINVAL; +} + + +static int +sn9c102_vidioc_g_ctrl(struct sn9c102_device *cam, void __user *arg) +{ + struct sn9c102_sensor *s = &cam->sensor; + struct v4l2_control ctrl; + int err = 0; + u8 i; + + if (!s->get_ctrl && !s->set_ctrl) + return -EINVAL; + + if (copy_from_user(&ctrl, arg, sizeof(ctrl))) + return -EFAULT; + + if (!s->get_ctrl) { + for (i = 0; i < ARRAY_SIZE(s->qctrl); i++) + if (ctrl.id && ctrl.id == s->qctrl[i].id) { + ctrl.value = s->_qctrl[i].default_value; + goto exit; + } + return -EINVAL; + } else + err = s->get_ctrl(cam, &ctrl); + +exit: + if (copy_to_user(arg, &ctrl, sizeof(ctrl))) + return -EFAULT; + + PDBGG("VIDIOC_G_CTRL: id %lu, value %lu", + (unsigned long)ctrl.id, (unsigned long)ctrl.value); + + return err; +} + + +static int +sn9c102_vidioc_s_ctrl(struct sn9c102_device *cam, void __user *arg) +{ + struct sn9c102_sensor *s = &cam->sensor; + struct v4l2_control ctrl; + u8 i; + int err = 0; + + if (!s->set_ctrl) + return -EINVAL; + + if (copy_from_user(&ctrl, arg, sizeof(ctrl))) + return -EFAULT; + + for (i = 0; i < ARRAY_SIZE(s->qctrl); i++) { + if (ctrl.id == s->qctrl[i].id) { + if (s->qctrl[i].flags & V4L2_CTRL_FLAG_DISABLED) + return -EINVAL; + if (ctrl.value < s->qctrl[i].minimum || + ctrl.value > s->qctrl[i].maximum) + return -ERANGE; + ctrl.value -= ctrl.value % s->qctrl[i].step; + break; + } + } + if (i == ARRAY_SIZE(s->qctrl)) + return -EINVAL; + err = s->set_ctrl(cam, &ctrl); + if (err) + return err; + + s->_qctrl[i].default_value = ctrl.value; + + PDBGG("VIDIOC_S_CTRL: id %lu, value %lu", + (unsigned long)ctrl.id, (unsigned long)ctrl.value); + + return 0; +} + + +static int +sn9c102_vidioc_cropcap(struct sn9c102_device *cam, void __user *arg) +{ + struct v4l2_cropcap *cc = &(cam->sensor.cropcap); + + cc->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + cc->pixelaspect.numerator = 1; + cc->pixelaspect.denominator = 1; + + if (copy_to_user(arg, cc, sizeof(*cc))) + return -EFAULT; + + return 0; +} + + +static int +sn9c102_vidioc_g_crop(struct sn9c102_device *cam, void __user *arg) +{ + struct sn9c102_sensor *s = &cam->sensor; + struct v4l2_crop crop = { + .type = V4L2_BUF_TYPE_VIDEO_CAPTURE, + }; + + memcpy(&(crop.c), &(s->_rect), sizeof(struct v4l2_rect)); + + if (copy_to_user(arg, &crop, sizeof(crop))) + return -EFAULT; + + return 0; +} + + +static int +sn9c102_vidioc_s_crop(struct sn9c102_device *cam, void __user *arg) +{ + struct sn9c102_sensor *s = &cam->sensor; + struct v4l2_crop crop; + struct v4l2_rect *rect; + struct v4l2_rect *bounds = &(s->cropcap.bounds); + struct v4l2_pix_format *pix_format = &(s->pix_format); + u8 scale; + const enum sn9c102_stream_state stream = cam->stream; + const u32 nbuffers = cam->nbuffers; + u32 i; + int err = 0; + + if (copy_from_user(&crop, arg, sizeof(crop))) + return -EFAULT; + + rect = &(crop.c); + + if (crop.type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + if (cam->module_param.force_munmap) + for (i = 0; i < cam->nbuffers; i++) + if (cam->frame[i].vma_use_count) { + DBG(3, "VIDIOC_S_CROP failed. " + "Unmap the buffers first."); + return -EBUSY; + } + + /* Preserve R,G or B origin */ + rect->left = (s->_rect.left & 1L) ? rect->left | 1L : rect->left & ~1L; + rect->top = (s->_rect.top & 1L) ? rect->top | 1L : rect->top & ~1L; + + if (rect->width < 16) + rect->width = 16; + if (rect->height < 16) + rect->height = 16; + if (rect->width > bounds->width) + rect->width = bounds->width; + if (rect->height > bounds->height) + rect->height = bounds->height; + if (rect->left < bounds->left) + rect->left = bounds->left; + if (rect->top < bounds->top) + rect->top = bounds->top; + if (rect->left + rect->width > bounds->left + bounds->width) + rect->left = bounds->left+bounds->width - rect->width; + if (rect->top + rect->height > bounds->top + bounds->height) + rect->top = bounds->top+bounds->height - rect->height; + + rect->width &= ~15L; + rect->height &= ~15L; + + if (SN9C102_PRESERVE_IMGSCALE) { + /* Calculate the actual scaling factor */ + u32 a, b; + a = rect->width * rect->height; + b = pix_format->width * pix_format->height; + scale = b ? (u8)((a / b) < 4 ? 1 : ((a / b) < 16 ? 2 : 4)) : 1; + } else + scale = 1; + + if (cam->stream == STREAM_ON) { + err = sn9c102_stream_interrupt(cam); + if (err) + return err; + } + + if (copy_to_user(arg, &crop, sizeof(crop))) { + cam->stream = stream; + return -EFAULT; + } + + if (cam->module_param.force_munmap || cam->io == IO_READ) + sn9c102_release_buffers(cam); + + err = sn9c102_set_crop(cam, rect); + if (s->set_crop) + err += s->set_crop(cam, rect); + err += sn9c102_set_scale(cam, scale); + + if (err) { /* atomic, no rollback in ioctl() */ + cam->state |= DEV_MISCONFIGURED; + DBG(1, "VIDIOC_S_CROP failed because of hardware problems. To " + "use the camera, close and open %s again.", + video_device_node_name(cam->v4ldev)); + return -EIO; + } + + s->pix_format.width = rect->width/scale; + s->pix_format.height = rect->height/scale; + memcpy(&(s->_rect), rect, sizeof(*rect)); + + if ((cam->module_param.force_munmap || cam->io == IO_READ) && + nbuffers != sn9c102_request_buffers(cam, nbuffers, cam->io)) { + cam->state |= DEV_MISCONFIGURED; + DBG(1, "VIDIOC_S_CROP failed because of not enough memory. To " + "use the camera, close and open %s again.", + video_device_node_name(cam->v4ldev)); + return -ENOMEM; + } + + if (cam->io == IO_READ) + sn9c102_empty_framequeues(cam); + else if (cam->module_param.force_munmap) + sn9c102_requeue_outqueue(cam); + + cam->stream = stream; + + return 0; +} + + +static int +sn9c102_vidioc_enum_framesizes(struct sn9c102_device *cam, void __user *arg) +{ + struct v4l2_frmsizeenum frmsize; + + if (copy_from_user(&frmsize, arg, sizeof(frmsize))) + return -EFAULT; + + if (frmsize.index != 0) + return -EINVAL; + + switch (cam->bridge) { + case BRIDGE_SN9C101: + case BRIDGE_SN9C102: + case BRIDGE_SN9C103: + if (frmsize.pixel_format != V4L2_PIX_FMT_SN9C10X && + frmsize.pixel_format != V4L2_PIX_FMT_SBGGR8) + return -EINVAL; + break; + case BRIDGE_SN9C105: + case BRIDGE_SN9C120: + if (frmsize.pixel_format != V4L2_PIX_FMT_JPEG && + frmsize.pixel_format != V4L2_PIX_FMT_SBGGR8) + return -EINVAL; + break; + } + + frmsize.type = V4L2_FRMSIZE_TYPE_STEPWISE; + frmsize.stepwise.min_width = frmsize.stepwise.step_width = 16; + frmsize.stepwise.min_height = frmsize.stepwise.step_height = 16; + frmsize.stepwise.max_width = cam->sensor.cropcap.bounds.width; + frmsize.stepwise.max_height = cam->sensor.cropcap.bounds.height; + memset(&frmsize.reserved, 0, sizeof(frmsize.reserved)); + + if (copy_to_user(arg, &frmsize, sizeof(frmsize))) + return -EFAULT; + + return 0; +} + + +static int +sn9c102_vidioc_enum_fmt(struct sn9c102_device *cam, void __user *arg) +{ + struct v4l2_fmtdesc fmtd; + + if (copy_from_user(&fmtd, arg, sizeof(fmtd))) + return -EFAULT; + + if (fmtd.type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + if (fmtd.index == 0) { + strcpy(fmtd.description, "bayer rgb"); + fmtd.pixelformat = V4L2_PIX_FMT_SBGGR8; + } else if (fmtd.index == 1) { + switch (cam->bridge) { + case BRIDGE_SN9C101: + case BRIDGE_SN9C102: + case BRIDGE_SN9C103: + strcpy(fmtd.description, "compressed"); + fmtd.pixelformat = V4L2_PIX_FMT_SN9C10X; + break; + case BRIDGE_SN9C105: + case BRIDGE_SN9C120: + strcpy(fmtd.description, "JPEG"); + fmtd.pixelformat = V4L2_PIX_FMT_JPEG; + break; + } + fmtd.flags = V4L2_FMT_FLAG_COMPRESSED; + } else + return -EINVAL; + + fmtd.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + memset(&fmtd.reserved, 0, sizeof(fmtd.reserved)); + + if (copy_to_user(arg, &fmtd, sizeof(fmtd))) + return -EFAULT; + + return 0; +} + + +static int +sn9c102_vidioc_g_fmt(struct sn9c102_device *cam, void __user *arg) +{ + struct v4l2_format format; + struct v4l2_pix_format *pfmt = &(cam->sensor.pix_format); + + if (copy_from_user(&format, arg, sizeof(format))) + return -EFAULT; + + if (format.type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + pfmt->colorspace = (pfmt->pixelformat == V4L2_PIX_FMT_JPEG) ? + V4L2_COLORSPACE_JPEG : V4L2_COLORSPACE_SRGB; + pfmt->bytesperline = (pfmt->pixelformat == V4L2_PIX_FMT_SN9C10X || + pfmt->pixelformat == V4L2_PIX_FMT_JPEG) + ? 0 : (pfmt->width * pfmt->priv) / 8; + pfmt->sizeimage = pfmt->height * ((pfmt->width*pfmt->priv)/8); + pfmt->field = V4L2_FIELD_NONE; + memcpy(&(format.fmt.pix), pfmt, sizeof(*pfmt)); + + if (copy_to_user(arg, &format, sizeof(format))) + return -EFAULT; + + return 0; +} + + +static int +sn9c102_vidioc_try_s_fmt(struct sn9c102_device *cam, unsigned int cmd, + void __user *arg) +{ + struct sn9c102_sensor *s = &cam->sensor; + struct v4l2_format format; + struct v4l2_pix_format *pix; + struct v4l2_pix_format *pfmt = &(s->pix_format); + struct v4l2_rect *bounds = &(s->cropcap.bounds); + struct v4l2_rect rect; + u8 scale; + const enum sn9c102_stream_state stream = cam->stream; + const u32 nbuffers = cam->nbuffers; + u32 i; + int err = 0; + + if (copy_from_user(&format, arg, sizeof(format))) + return -EFAULT; + + pix = &(format.fmt.pix); + + if (format.type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + memcpy(&rect, &(s->_rect), sizeof(rect)); + + { /* calculate the actual scaling factor */ + u32 a, b; + a = rect.width * rect.height; + b = pix->width * pix->height; + scale = b ? (u8)((a / b) < 4 ? 1 : ((a / b) < 16 ? 2 : 4)) : 1; + } + + rect.width = scale * pix->width; + rect.height = scale * pix->height; + + if (rect.width < 16) + rect.width = 16; + if (rect.height < 16) + rect.height = 16; + if (rect.width > bounds->left + bounds->width - rect.left) + rect.width = bounds->left + bounds->width - rect.left; + if (rect.height > bounds->top + bounds->height - rect.top) + rect.height = bounds->top + bounds->height - rect.top; + + rect.width &= ~15L; + rect.height &= ~15L; + + { /* adjust the scaling factor */ + u32 a, b; + a = rect.width * rect.height; + b = pix->width * pix->height; + scale = b ? (u8)((a / b) < 4 ? 1 : ((a / b) < 16 ? 2 : 4)) : 1; + } + + pix->width = rect.width / scale; + pix->height = rect.height / scale; + + switch (cam->bridge) { + case BRIDGE_SN9C101: + case BRIDGE_SN9C102: + case BRIDGE_SN9C103: + if (pix->pixelformat != V4L2_PIX_FMT_SN9C10X && + pix->pixelformat != V4L2_PIX_FMT_SBGGR8) + pix->pixelformat = pfmt->pixelformat; + break; + case BRIDGE_SN9C105: + case BRIDGE_SN9C120: + if (pix->pixelformat != V4L2_PIX_FMT_JPEG && + pix->pixelformat != V4L2_PIX_FMT_SBGGR8) + pix->pixelformat = pfmt->pixelformat; + break; + } + pix->priv = pfmt->priv; /* bpp */ + pix->colorspace = (pix->pixelformat == V4L2_PIX_FMT_JPEG) ? + V4L2_COLORSPACE_JPEG : V4L2_COLORSPACE_SRGB; + pix->bytesperline = (pix->pixelformat == V4L2_PIX_FMT_SN9C10X || + pix->pixelformat == V4L2_PIX_FMT_JPEG) + ? 0 : (pix->width * pix->priv) / 8; + pix->sizeimage = pix->height * ((pix->width * pix->priv) / 8); + pix->field = V4L2_FIELD_NONE; + + if (cmd == VIDIOC_TRY_FMT) { + if (copy_to_user(arg, &format, sizeof(format))) + return -EFAULT; + return 0; + } + + if (cam->module_param.force_munmap) + for (i = 0; i < cam->nbuffers; i++) + if (cam->frame[i].vma_use_count) { + DBG(3, "VIDIOC_S_FMT failed. Unmap the " + "buffers first."); + return -EBUSY; + } + + if (cam->stream == STREAM_ON) { + err = sn9c102_stream_interrupt(cam); + if (err) + return err; + } + + if (copy_to_user(arg, &format, sizeof(format))) { + cam->stream = stream; + return -EFAULT; + } + + if (cam->module_param.force_munmap || cam->io == IO_READ) + sn9c102_release_buffers(cam); + + err += sn9c102_set_pix_format(cam, pix); + err += sn9c102_set_crop(cam, &rect); + if (s->set_pix_format) + err += s->set_pix_format(cam, pix); + if (s->set_crop) + err += s->set_crop(cam, &rect); + err += sn9c102_set_scale(cam, scale); + + if (err) { /* atomic, no rollback in ioctl() */ + cam->state |= DEV_MISCONFIGURED; + DBG(1, "VIDIOC_S_FMT failed because of hardware problems. To " + "use the camera, close and open %s again.", + video_device_node_name(cam->v4ldev)); + return -EIO; + } + + memcpy(pfmt, pix, sizeof(*pix)); + memcpy(&(s->_rect), &rect, sizeof(rect)); + + if ((cam->module_param.force_munmap || cam->io == IO_READ) && + nbuffers != sn9c102_request_buffers(cam, nbuffers, cam->io)) { + cam->state |= DEV_MISCONFIGURED; + DBG(1, "VIDIOC_S_FMT failed because of not enough memory. To " + "use the camera, close and open %s again.", + video_device_node_name(cam->v4ldev)); + return -ENOMEM; + } + + if (cam->io == IO_READ) + sn9c102_empty_framequeues(cam); + else if (cam->module_param.force_munmap) + sn9c102_requeue_outqueue(cam); + + cam->stream = stream; + + return 0; +} + + +static int +sn9c102_vidioc_g_jpegcomp(struct sn9c102_device *cam, void __user *arg) +{ + if (copy_to_user(arg, &cam->compression, sizeof(cam->compression))) + return -EFAULT; + + return 0; +} + + +static int +sn9c102_vidioc_s_jpegcomp(struct sn9c102_device *cam, void __user *arg) +{ + struct v4l2_jpegcompression jc; + const enum sn9c102_stream_state stream = cam->stream; + int err = 0; + + if (copy_from_user(&jc, arg, sizeof(jc))) + return -EFAULT; + + if (jc.quality != 0 && jc.quality != 1) + return -EINVAL; + + if (cam->stream == STREAM_ON) { + err = sn9c102_stream_interrupt(cam); + if (err) + return err; + } + + err += sn9c102_set_compression(cam, &jc); + if (err) { /* atomic, no rollback in ioctl() */ + cam->state |= DEV_MISCONFIGURED; + DBG(1, "VIDIOC_S_JPEGCOMP failed because of hardware problems. " + "To use the camera, close and open %s again.", + video_device_node_name(cam->v4ldev)); + return -EIO; + } + + cam->compression.quality = jc.quality; + + cam->stream = stream; + + return 0; +} + + +static int +sn9c102_vidioc_reqbufs(struct sn9c102_device *cam, void __user *arg) +{ + struct v4l2_requestbuffers rb; + u32 i; + int err; + + if (copy_from_user(&rb, arg, sizeof(rb))) + return -EFAULT; + + if (rb.type != V4L2_BUF_TYPE_VIDEO_CAPTURE || + rb.memory != V4L2_MEMORY_MMAP) + return -EINVAL; + + if (cam->io == IO_READ) { + DBG(3, "Close and open the device again to choose the mmap " + "I/O method"); + return -EBUSY; + } + + for (i = 0; i < cam->nbuffers; i++) + if (cam->frame[i].vma_use_count) { + DBG(3, "VIDIOC_REQBUFS failed. Previous buffers are " + "still mapped."); + return -EBUSY; + } + + if (cam->stream == STREAM_ON) { + err = sn9c102_stream_interrupt(cam); + if (err) + return err; + } + + sn9c102_empty_framequeues(cam); + + sn9c102_release_buffers(cam); + if (rb.count) + rb.count = sn9c102_request_buffers(cam, rb.count, IO_MMAP); + + if (copy_to_user(arg, &rb, sizeof(rb))) { + sn9c102_release_buffers(cam); + cam->io = IO_NONE; + return -EFAULT; + } + + cam->io = rb.count ? IO_MMAP : IO_NONE; + + return 0; +} + + +static int +sn9c102_vidioc_querybuf(struct sn9c102_device *cam, void __user *arg) +{ + struct v4l2_buffer b; + + if (copy_from_user(&b, arg, sizeof(b))) + return -EFAULT; + + if (b.type != V4L2_BUF_TYPE_VIDEO_CAPTURE || + b.index >= cam->nbuffers || cam->io != IO_MMAP) + return -EINVAL; + + b = cam->frame[b.index].buf; + + if (cam->frame[b.index].vma_use_count) + b.flags |= V4L2_BUF_FLAG_MAPPED; + + if (cam->frame[b.index].state == F_DONE) + b.flags |= V4L2_BUF_FLAG_DONE; + else if (cam->frame[b.index].state != F_UNUSED) + b.flags |= V4L2_BUF_FLAG_QUEUED; + + if (copy_to_user(arg, &b, sizeof(b))) + return -EFAULT; + + return 0; +} + + +static int +sn9c102_vidioc_qbuf(struct sn9c102_device *cam, void __user *arg) +{ + struct v4l2_buffer b; + unsigned long lock_flags; + + if (copy_from_user(&b, arg, sizeof(b))) + return -EFAULT; + + if (b.type != V4L2_BUF_TYPE_VIDEO_CAPTURE || + b.index >= cam->nbuffers || cam->io != IO_MMAP) + return -EINVAL; + + if (cam->frame[b.index].state != F_UNUSED) + return -EINVAL; + + cam->frame[b.index].state = F_QUEUED; + + spin_lock_irqsave(&cam->queue_lock, lock_flags); + list_add_tail(&cam->frame[b.index].frame, &cam->inqueue); + spin_unlock_irqrestore(&cam->queue_lock, lock_flags); + + PDBGG("Frame #%lu queued", (unsigned long)b.index); + + return 0; +} + + +static int +sn9c102_vidioc_dqbuf(struct sn9c102_device *cam, struct file *filp, + void __user *arg) +{ + struct v4l2_buffer b; + struct sn9c102_frame_t *f; + unsigned long lock_flags; + long timeout; + int err = 0; + + if (copy_from_user(&b, arg, sizeof(b))) + return -EFAULT; + + if (b.type != V4L2_BUF_TYPE_VIDEO_CAPTURE || cam->io != IO_MMAP) + return -EINVAL; + + if (list_empty(&cam->outqueue)) { + if (cam->stream == STREAM_OFF) + return -EINVAL; + if (filp->f_flags & O_NONBLOCK) + return -EAGAIN; + if (!cam->module_param.frame_timeout) { + err = wait_event_interruptible + (cam->wait_frame, + (!list_empty(&cam->outqueue)) || + (cam->state & DEV_DISCONNECTED) || + (cam->state & DEV_MISCONFIGURED)); + if (err) + return err; + } else { + timeout = wait_event_interruptible_timeout + (cam->wait_frame, + (!list_empty(&cam->outqueue)) || + (cam->state & DEV_DISCONNECTED) || + (cam->state & DEV_MISCONFIGURED), + cam->module_param.frame_timeout * + 1000 * msecs_to_jiffies(1)); + if (timeout < 0) + return timeout; + else if (timeout == 0 && + !(cam->state & DEV_DISCONNECTED)) { + DBG(1, "Video frame timeout elapsed"); + return -EIO; + } + } + if (cam->state & DEV_DISCONNECTED) + return -ENODEV; + if (cam->state & DEV_MISCONFIGURED) + return -EIO; + } + + spin_lock_irqsave(&cam->queue_lock, lock_flags); + f = list_entry(cam->outqueue.next, struct sn9c102_frame_t, frame); + list_del(cam->outqueue.next); + spin_unlock_irqrestore(&cam->queue_lock, lock_flags); + + f->state = F_UNUSED; + + b = f->buf; + if (f->vma_use_count) + b.flags |= V4L2_BUF_FLAG_MAPPED; + + if (copy_to_user(arg, &b, sizeof(b))) + return -EFAULT; + + PDBGG("Frame #%lu dequeued", (unsigned long)f->buf.index); + + return 0; +} + + +static int +sn9c102_vidioc_streamon(struct sn9c102_device *cam, void __user *arg) +{ + int type; + + if (copy_from_user(&type, arg, sizeof(type))) + return -EFAULT; + + if (type != V4L2_BUF_TYPE_VIDEO_CAPTURE || cam->io != IO_MMAP) + return -EINVAL; + + cam->stream = STREAM_ON; + + DBG(3, "Stream on"); + + return 0; +} + + +static int +sn9c102_vidioc_streamoff(struct sn9c102_device *cam, void __user *arg) +{ + int type, err; + + if (copy_from_user(&type, arg, sizeof(type))) + return -EFAULT; + + if (type != V4L2_BUF_TYPE_VIDEO_CAPTURE || cam->io != IO_MMAP) + return -EINVAL; + + if (cam->stream == STREAM_ON) { + err = sn9c102_stream_interrupt(cam); + if (err) + return err; + } + + sn9c102_empty_framequeues(cam); + + DBG(3, "Stream off"); + + return 0; +} + + +static int +sn9c102_vidioc_g_parm(struct sn9c102_device *cam, void __user *arg) +{ + struct v4l2_streamparm sp; + + if (copy_from_user(&sp, arg, sizeof(sp))) + return -EFAULT; + + if (sp.type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + sp.parm.capture.extendedmode = 0; + sp.parm.capture.readbuffers = cam->nreadbuffers; + + if (copy_to_user(arg, &sp, sizeof(sp))) + return -EFAULT; + + return 0; +} + + +static int +sn9c102_vidioc_s_parm(struct sn9c102_device *cam, void __user *arg) +{ + struct v4l2_streamparm sp; + + if (copy_from_user(&sp, arg, sizeof(sp))) + return -EFAULT; + + if (sp.type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + sp.parm.capture.extendedmode = 0; + + if (sp.parm.capture.readbuffers == 0) + sp.parm.capture.readbuffers = cam->nreadbuffers; + + if (sp.parm.capture.readbuffers > SN9C102_MAX_FRAMES) + sp.parm.capture.readbuffers = SN9C102_MAX_FRAMES; + + if (copy_to_user(arg, &sp, sizeof(sp))) + return -EFAULT; + + cam->nreadbuffers = sp.parm.capture.readbuffers; + + return 0; +} + + +static int +sn9c102_vidioc_enumaudio(struct sn9c102_device *cam, void __user *arg) +{ + struct v4l2_audio audio; + + if (cam->bridge == BRIDGE_SN9C101 || cam->bridge == BRIDGE_SN9C102) + return -EINVAL; + + if (copy_from_user(&audio, arg, sizeof(audio))) + return -EFAULT; + + if (audio.index != 0) + return -EINVAL; + + strcpy(audio.name, "Microphone"); + audio.capability = 0; + audio.mode = 0; + + if (copy_to_user(arg, &audio, sizeof(audio))) + return -EFAULT; + + return 0; +} + + +static int +sn9c102_vidioc_g_audio(struct sn9c102_device *cam, void __user *arg) +{ + struct v4l2_audio audio; + + if (cam->bridge == BRIDGE_SN9C101 || cam->bridge == BRIDGE_SN9C102) + return -EINVAL; + + if (copy_from_user(&audio, arg, sizeof(audio))) + return -EFAULT; + + memset(&audio, 0, sizeof(audio)); + strcpy(audio.name, "Microphone"); + + if (copy_to_user(arg, &audio, sizeof(audio))) + return -EFAULT; + + return 0; +} + + +static int +sn9c102_vidioc_s_audio(struct sn9c102_device *cam, void __user *arg) +{ + struct v4l2_audio audio; + + if (cam->bridge == BRIDGE_SN9C101 || cam->bridge == BRIDGE_SN9C102) + return -EINVAL; + + if (copy_from_user(&audio, arg, sizeof(audio))) + return -EFAULT; + + if (audio.index != 0) + return -EINVAL; + + return 0; +} + + +static long sn9c102_ioctl_v4l2(struct file *filp, + unsigned int cmd, void __user *arg) +{ + struct sn9c102_device *cam = video_drvdata(filp); + + switch (cmd) { + + case VIDIOC_QUERYCAP: + return sn9c102_vidioc_querycap(cam, arg); + + case VIDIOC_ENUMINPUT: + return sn9c102_vidioc_enuminput(cam, arg); + + case VIDIOC_G_INPUT: + return sn9c102_vidioc_g_input(cam, arg); + + case VIDIOC_S_INPUT: + return sn9c102_vidioc_s_input(cam, arg); + + case VIDIOC_QUERYCTRL: + return sn9c102_vidioc_query_ctrl(cam, arg); + + case VIDIOC_G_CTRL: + return sn9c102_vidioc_g_ctrl(cam, arg); + + case VIDIOC_S_CTRL: + return sn9c102_vidioc_s_ctrl(cam, arg); + + case VIDIOC_CROPCAP: + return sn9c102_vidioc_cropcap(cam, arg); + + case VIDIOC_G_CROP: + return sn9c102_vidioc_g_crop(cam, arg); + + case VIDIOC_S_CROP: + return sn9c102_vidioc_s_crop(cam, arg); + + case VIDIOC_ENUM_FRAMESIZES: + return sn9c102_vidioc_enum_framesizes(cam, arg); + + case VIDIOC_ENUM_FMT: + return sn9c102_vidioc_enum_fmt(cam, arg); + + case VIDIOC_G_FMT: + return sn9c102_vidioc_g_fmt(cam, arg); + + case VIDIOC_TRY_FMT: + case VIDIOC_S_FMT: + return sn9c102_vidioc_try_s_fmt(cam, cmd, arg); + + case VIDIOC_G_JPEGCOMP: + return sn9c102_vidioc_g_jpegcomp(cam, arg); + + case VIDIOC_S_JPEGCOMP: + return sn9c102_vidioc_s_jpegcomp(cam, arg); + + case VIDIOC_REQBUFS: + return sn9c102_vidioc_reqbufs(cam, arg); + + case VIDIOC_QUERYBUF: + return sn9c102_vidioc_querybuf(cam, arg); + + case VIDIOC_QBUF: + return sn9c102_vidioc_qbuf(cam, arg); + + case VIDIOC_DQBUF: + return sn9c102_vidioc_dqbuf(cam, filp, arg); + + case VIDIOC_STREAMON: + return sn9c102_vidioc_streamon(cam, arg); + + case VIDIOC_STREAMOFF: + return sn9c102_vidioc_streamoff(cam, arg); + + case VIDIOC_G_PARM: + return sn9c102_vidioc_g_parm(cam, arg); + + case VIDIOC_S_PARM: + return sn9c102_vidioc_s_parm(cam, arg); + + case VIDIOC_ENUMAUDIO: + return sn9c102_vidioc_enumaudio(cam, arg); + + case VIDIOC_G_AUDIO: + return sn9c102_vidioc_g_audio(cam, arg); + + case VIDIOC_S_AUDIO: + return sn9c102_vidioc_s_audio(cam, arg); + + default: + return -ENOTTY; + + } +} + + +static long sn9c102_ioctl(struct file *filp, + unsigned int cmd, unsigned long arg) +{ + struct sn9c102_device *cam = video_drvdata(filp); + int err = 0; + + if (mutex_lock_interruptible(&cam->fileop_mutex)) + return -ERESTARTSYS; + + if (cam->state & DEV_DISCONNECTED) { + DBG(1, "Device not present"); + mutex_unlock(&cam->fileop_mutex); + return -ENODEV; + } + + if (cam->state & DEV_MISCONFIGURED) { + DBG(1, "The camera is misconfigured. Close and open it " + "again."); + mutex_unlock(&cam->fileop_mutex); + return -EIO; + } + + V4LDBG(3, "sn9c102", cmd); + + err = sn9c102_ioctl_v4l2(filp, cmd, (void __user *)arg); + + mutex_unlock(&cam->fileop_mutex); + + return err; +} + +/*****************************************************************************/ + +static const struct v4l2_file_operations sn9c102_fops = { + .owner = THIS_MODULE, + .open = sn9c102_open, + .release = sn9c102_release, + .unlocked_ioctl = sn9c102_ioctl, + .read = sn9c102_read, + .poll = sn9c102_poll, + .mmap = sn9c102_mmap, +}; + +/*****************************************************************************/ + +/* It exists a single interface only. We do not need to validate anything. */ +static int +sn9c102_usb_probe(struct usb_interface *intf, const struct usb_device_id *id) +{ + struct usb_device *udev = interface_to_usbdev(intf); + struct sn9c102_device *cam; + static unsigned int dev_nr; + unsigned int i; + int err = 0, r; + + cam = kzalloc(sizeof(struct sn9c102_device), GFP_KERNEL); + if (!cam) + return -ENOMEM; + + cam->usbdev = udev; + + /* register v4l2_device early so it can be used for printks */ + if (v4l2_device_register(&intf->dev, &cam->v4l2_dev)) { + dev_err(&intf->dev, "v4l2_device_register failed\n"); + err = -ENOMEM; + goto fail; + } + + cam->control_buffer = kzalloc(8, GFP_KERNEL); + if (!cam->control_buffer) { + DBG(1, "kzalloc() failed"); + err = -ENOMEM; + goto fail; + } + + cam->v4ldev = video_device_alloc(); + if (!cam->v4ldev) { + DBG(1, "video_device_alloc() failed"); + err = -ENOMEM; + goto fail; + } + + r = sn9c102_read_reg(cam, 0x00); + if (r < 0 || (r != 0x10 && r != 0x11 && r != 0x12)) { + DBG(1, "Sorry, this is not a SN9C1xx-based camera " + "(vid:pid 0x%04X:0x%04X)", id->idVendor, id->idProduct); + err = -ENODEV; + goto fail; + } + + cam->bridge = id->driver_info; + switch (cam->bridge) { + case BRIDGE_SN9C101: + case BRIDGE_SN9C102: + DBG(2, "SN9C10[12] PC Camera Controller detected " + "(vid:pid 0x%04X:0x%04X)", id->idVendor, id->idProduct); + break; + case BRIDGE_SN9C103: + DBG(2, "SN9C103 PC Camera Controller detected " + "(vid:pid 0x%04X:0x%04X)", id->idVendor, id->idProduct); + break; + case BRIDGE_SN9C105: + DBG(2, "SN9C105 PC Camera Controller detected " + "(vid:pid 0x%04X:0x%04X)", id->idVendor, id->idProduct); + break; + case BRIDGE_SN9C120: + DBG(2, "SN9C120 PC Camera Controller detected " + "(vid:pid 0x%04X:0x%04X)", id->idVendor, id->idProduct); + break; + } + + for (i = 0; i < ARRAY_SIZE(sn9c102_sensor_table); i++) { + err = sn9c102_sensor_table[i](cam); + if (!err) + break; + } + + if (!err) { + DBG(2, "%s image sensor detected", cam->sensor.name); + DBG(3, "Support for %s maintained by %s", + cam->sensor.name, cam->sensor.maintainer); + } else { + DBG(1, "No supported image sensor detected for this bridge"); + err = -ENODEV; + goto fail; + } + + if (!(cam->bridge & cam->sensor.supported_bridge)) { + DBG(1, "Bridge not supported"); + err = -ENODEV; + goto fail; + } + + if (sn9c102_init(cam)) { + DBG(1, "Initialization failed. I will retry on open()."); + cam->state |= DEV_MISCONFIGURED; + } + + strcpy(cam->v4ldev->name, "SN9C1xx PC Camera"); + cam->v4ldev->fops = &sn9c102_fops; + cam->v4ldev->release = video_device_release; + cam->v4ldev->v4l2_dev = &cam->v4l2_dev; + + init_completion(&cam->probe); + + err = video_register_device(cam->v4ldev, VFL_TYPE_GRABBER, + video_nr[dev_nr]); + if (err) { + DBG(1, "V4L2 device registration failed"); + if (err == -ENFILE && video_nr[dev_nr] == -1) + DBG(1, "Free /dev/videoX node not found"); + video_nr[dev_nr] = -1; + dev_nr = (dev_nr < SN9C102_MAX_DEVICES-1) ? dev_nr+1 : 0; + complete_all(&cam->probe); + goto fail; + } + + DBG(2, "V4L2 device registered as %s", + video_device_node_name(cam->v4ldev)); + + video_set_drvdata(cam->v4ldev, cam); + cam->module_param.force_munmap = force_munmap[dev_nr]; + cam->module_param.frame_timeout = frame_timeout[dev_nr]; + + dev_nr = (dev_nr < SN9C102_MAX_DEVICES-1) ? dev_nr+1 : 0; + +#ifdef CONFIG_VIDEO_ADV_DEBUG + err = sn9c102_create_sysfs(cam); + if (!err) + DBG(2, "Optional device control through 'sysfs' " + "interface ready"); + else + DBG(2, "Failed to create optional 'sysfs' interface for " + "device controlling. Error #%d", err); +#else + DBG(2, "Optional device control through 'sysfs' interface disabled"); + DBG(3, "Compile the kernel with the 'CONFIG_VIDEO_ADV_DEBUG' " + "configuration option to enable it."); +#endif + + usb_set_intfdata(intf, cam); + kref_init(&cam->kref); + usb_get_dev(cam->usbdev); + + complete_all(&cam->probe); + + return 0; + +fail: + if (cam) { + kfree(cam->control_buffer); + if (cam->v4ldev) + video_device_release(cam->v4ldev); + v4l2_device_unregister(&cam->v4l2_dev); + kfree(cam); + } + return err; +} + + +static void sn9c102_usb_disconnect(struct usb_interface *intf) +{ + struct sn9c102_device *cam; + + down_write(&sn9c102_dev_lock); + + cam = usb_get_intfdata(intf); + + DBG(2, "Disconnecting %s...", cam->v4ldev->name); + + if (cam->users) { + DBG(2, "Device %s is open! Deregistration and memory " + "deallocation are deferred.", + video_device_node_name(cam->v4ldev)); + cam->state |= DEV_MISCONFIGURED; + sn9c102_stop_transfer(cam); + cam->state |= DEV_DISCONNECTED; + wake_up_interruptible(&cam->wait_frame); + wake_up(&cam->wait_stream); + } else + cam->state |= DEV_DISCONNECTED; + + wake_up_interruptible_all(&cam->wait_open); + + v4l2_device_disconnect(&cam->v4l2_dev); + + kref_put(&cam->kref, sn9c102_release_resources); + + up_write(&sn9c102_dev_lock); +} + + +static struct usb_driver sn9c102_usb_driver = { + .name = "sn9c102", + .id_table = sn9c102_id_table, + .probe = sn9c102_usb_probe, + .disconnect = sn9c102_usb_disconnect, +}; + +module_usb_driver(sn9c102_usb_driver); diff --git a/drivers/staging/media/sn9c102/sn9c102_devtable.h b/drivers/staging/media/sn9c102/sn9c102_devtable.h new file mode 100644 index 00000000000..b187a8a304e --- /dev/null +++ b/drivers/staging/media/sn9c102/sn9c102_devtable.h @@ -0,0 +1,145 @@ +/*************************************************************************** + * Table of device identifiers of the SN9C1xx PC Camera Controllers * + * * + * Copyright (C) 2007 by Luca Risolia <luca.risolia@studio.unibo.it> * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the Free Software * + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * + ***************************************************************************/ + +#ifndef _SN9C102_DEVTABLE_H_ +#define _SN9C102_DEVTABLE_H_ + +#include <linux/usb.h> + +struct sn9c102_device; + +/* + Each SN9C1xx camera has proper PID/VID identifiers. + SN9C103, SN9C105, SN9C120 support multiple interfaces, but we only have to + handle the video class interface. +*/ +#define SN9C102_USB_DEVICE(vend, prod, bridge) \ + .match_flags = USB_DEVICE_ID_MATCH_DEVICE | \ + USB_DEVICE_ID_MATCH_INT_CLASS, \ + .idVendor = (vend), \ + .idProduct = (prod), \ + .bInterfaceClass = 0xff, \ + .driver_info = (bridge) + +static const struct usb_device_id sn9c102_id_table[] = { + /* SN9C101 and SN9C102 */ +#if !defined CONFIG_USB_GSPCA_SONIXB && !defined CONFIG_USB_GSPCA_SONIXB_MODULE + { SN9C102_USB_DEVICE(0x0c45, 0x6001, BRIDGE_SN9C102), }, + { SN9C102_USB_DEVICE(0x0c45, 0x6005, BRIDGE_SN9C102), }, + { SN9C102_USB_DEVICE(0x0c45, 0x6007, BRIDGE_SN9C102), }, + { SN9C102_USB_DEVICE(0x0c45, 0x6009, BRIDGE_SN9C102), }, + { SN9C102_USB_DEVICE(0x0c45, 0x600d, BRIDGE_SN9C102), }, +/* { SN9C102_USB_DEVICE(0x0c45, 0x6011, BRIDGE_SN9C102), }, OV6650 */ + { SN9C102_USB_DEVICE(0x0c45, 0x6019, BRIDGE_SN9C102), }, + { SN9C102_USB_DEVICE(0x0c45, 0x6024, BRIDGE_SN9C102), }, + { SN9C102_USB_DEVICE(0x0c45, 0x6025, BRIDGE_SN9C102), }, + { SN9C102_USB_DEVICE(0x0c45, 0x6028, BRIDGE_SN9C102), }, + { SN9C102_USB_DEVICE(0x0c45, 0x6029, BRIDGE_SN9C102), }, + { SN9C102_USB_DEVICE(0x0c45, 0x602a, BRIDGE_SN9C102), }, +#endif + { SN9C102_USB_DEVICE(0x0c45, 0x602b, BRIDGE_SN9C102), }, /* not in sonixb */ +#if !defined CONFIG_USB_GSPCA_SONIXB && !defined CONFIG_USB_GSPCA_SONIXB_MODULE + { SN9C102_USB_DEVICE(0x0c45, 0x602c, BRIDGE_SN9C102), }, +/* { SN9C102_USB_DEVICE(0x0c45, 0x602d, BRIDGE_SN9C102), }, HV7131R */ + { SN9C102_USB_DEVICE(0x0c45, 0x602e, BRIDGE_SN9C102), }, +#endif + { SN9C102_USB_DEVICE(0x0c45, 0x6030, BRIDGE_SN9C102), }, /* not in sonixb */ + /* SN9C103 */ +/* { SN9C102_USB_DEVICE(0x0c45, 0x6080, BRIDGE_SN9C103), }, non existent ? */ + { SN9C102_USB_DEVICE(0x0c45, 0x6082, BRIDGE_SN9C103), }, /* not in sonixb */ +#if !defined CONFIG_USB_GSPCA_SONIXB && !defined CONFIG_USB_GSPCA_SONIXB_MODULE +/* { SN9C102_USB_DEVICE(0x0c45, 0x6083, BRIDGE_SN9C103), }, HY7131D/E */ +/* { SN9C102_USB_DEVICE(0x0c45, 0x6088, BRIDGE_SN9C103), }, non existent ? */ +/* { SN9C102_USB_DEVICE(0x0c45, 0x608a, BRIDGE_SN9C103), }, non existent ? */ +/* { SN9C102_USB_DEVICE(0x0c45, 0x608b, BRIDGE_SN9C103), }, non existent ? */ + { SN9C102_USB_DEVICE(0x0c45, 0x608c, BRIDGE_SN9C103), }, +/* { SN9C102_USB_DEVICE(0x0c45, 0x608e, BRIDGE_SN9C103), }, CISVF10 */ + { SN9C102_USB_DEVICE(0x0c45, 0x608f, BRIDGE_SN9C103), }, +/* { SN9C102_USB_DEVICE(0x0c45, 0x60a0, BRIDGE_SN9C103), }, non existent ? */ +/* { SN9C102_USB_DEVICE(0x0c45, 0x60a2, BRIDGE_SN9C103), }, non existent ? */ +/* { SN9C102_USB_DEVICE(0x0c45, 0x60a3, BRIDGE_SN9C103), }, non existent ? */ +/* { SN9C102_USB_DEVICE(0x0c45, 0x60a8, BRIDGE_SN9C103), }, PAS106 */ +/* { SN9C102_USB_DEVICE(0x0c45, 0x60aa, BRIDGE_SN9C103), }, TAS5130 */ +/* { SN9C102_USB_DEVICE(0x0c45, 0x60ab, BRIDGE_SN9C103), }, TAS5110, non existent */ +/* { SN9C102_USB_DEVICE(0x0c45, 0x60ac, BRIDGE_SN9C103), }, non existent ? */ +/* { SN9C102_USB_DEVICE(0x0c45, 0x60ae, BRIDGE_SN9C103), }, non existent ? */ + { SN9C102_USB_DEVICE(0x0c45, 0x60af, BRIDGE_SN9C103), }, + { SN9C102_USB_DEVICE(0x0c45, 0x60b0, BRIDGE_SN9C103), }, +/* { SN9C102_USB_DEVICE(0x0c45, 0x60b2, BRIDGE_SN9C103), }, non existent ? */ +/* { SN9C102_USB_DEVICE(0x0c45, 0x60b3, BRIDGE_SN9C103), }, non existent ? */ +/* { SN9C102_USB_DEVICE(0x0c45, 0x60b8, BRIDGE_SN9C103), }, non existent ? */ +/* { SN9C102_USB_DEVICE(0x0c45, 0x60ba, BRIDGE_SN9C103), }, non existent ? */ +/* { SN9C102_USB_DEVICE(0x0c45, 0x60bb, BRIDGE_SN9C103), }, non existent ? */ +/* { SN9C102_USB_DEVICE(0x0c45, 0x60bc, BRIDGE_SN9C103), }, non existent ? */ +/* { SN9C102_USB_DEVICE(0x0c45, 0x60be, BRIDGE_SN9C103), }, non existent ? */ +#endif + /* SN9C105 */ +#if !defined CONFIG_USB_GSPCA_SONIXJ && !defined CONFIG_USB_GSPCA_SONIXJ_MODULE + { SN9C102_USB_DEVICE(0x045e, 0x00f5, BRIDGE_SN9C105), }, + { SN9C102_USB_DEVICE(0x045e, 0x00f7, BRIDGE_SN9C105), }, + { SN9C102_USB_DEVICE(0x0471, 0x0327, BRIDGE_SN9C105), }, + { SN9C102_USB_DEVICE(0x0471, 0x0328, BRIDGE_SN9C105), }, + { SN9C102_USB_DEVICE(0x0c45, 0x60c0, BRIDGE_SN9C105), }, +/* { SN9C102_USB_DEVICE(0x0c45, 0x60c2, BRIDGE_SN9C105), }, PO1030 */ +/* { SN9C102_USB_DEVICE(0x0c45, 0x60c8, BRIDGE_SN9C105), }, OM6801 */ +/* { SN9C102_USB_DEVICE(0x0c45, 0x60cc, BRIDGE_SN9C105), }, HV7131GP */ +/* { SN9C102_USB_DEVICE(0x0c45, 0x60ea, BRIDGE_SN9C105), }, non existent ? */ +/* { SN9C102_USB_DEVICE(0x0c45, 0x60ec, BRIDGE_SN9C105), }, MO4000 */ +/* { SN9C102_USB_DEVICE(0x0c45, 0x60ef, BRIDGE_SN9C105), }, ICM105C */ +/* { SN9C102_USB_DEVICE(0x0c45, 0x60fa, BRIDGE_SN9C105), }, OV7648 */ + { SN9C102_USB_DEVICE(0x0c45, 0x60fb, BRIDGE_SN9C105), }, + { SN9C102_USB_DEVICE(0x0c45, 0x60fc, BRIDGE_SN9C105), }, + { SN9C102_USB_DEVICE(0x0c45, 0x60fe, BRIDGE_SN9C105), }, + /* SN9C120 */ + { SN9C102_USB_DEVICE(0x0458, 0x7025, BRIDGE_SN9C120), }, +/* { SN9C102_USB_DEVICE(0x0c45, 0x6102, BRIDGE_SN9C120), }, po2030 */ +/* { SN9C102_USB_DEVICE(0x0c45, 0x6108, BRIDGE_SN9C120), }, om6801 */ +/* { SN9C102_USB_DEVICE(0x0c45, 0x610f, BRIDGE_SN9C120), }, S5K53BEB */ + { SN9C102_USB_DEVICE(0x0c45, 0x6130, BRIDGE_SN9C120), }, +/* { SN9C102_USB_DEVICE(0x0c45, 0x6138, BRIDGE_SN9C120), }, MO8000 */ + { SN9C102_USB_DEVICE(0x0c45, 0x613a, BRIDGE_SN9C120), }, + { SN9C102_USB_DEVICE(0x0c45, 0x613b, BRIDGE_SN9C120), }, + { SN9C102_USB_DEVICE(0x0c45, 0x613c, BRIDGE_SN9C120), }, + { SN9C102_USB_DEVICE(0x0c45, 0x613e, BRIDGE_SN9C120), }, +#endif + { } +}; + +/* + Probing functions: on success, you must attach the sensor to the camera + by calling sn9c102_attach_sensor(). + To enable the I2C communication, you might need to perform a really basic + initialization of the SN9C1XX chip. + Functions must return 0 on success, the appropriate error otherwise. +*/ +extern int sn9c102_probe_hv7131d(struct sn9c102_device *cam); +extern int sn9c102_probe_hv7131r(struct sn9c102_device *cam); +extern int sn9c102_probe_mi0343(struct sn9c102_device *cam); +extern int sn9c102_probe_mi0360(struct sn9c102_device *cam); +extern int sn9c102_probe_mt9v111(struct sn9c102_device *cam); +extern int sn9c102_probe_ov7630(struct sn9c102_device *cam); +extern int sn9c102_probe_ov7660(struct sn9c102_device *cam); +extern int sn9c102_probe_pas106b(struct sn9c102_device *cam); +extern int sn9c102_probe_pas202bcb(struct sn9c102_device *cam); +extern int sn9c102_probe_tas5110c1b(struct sn9c102_device *cam); +extern int sn9c102_probe_tas5110d(struct sn9c102_device *cam); +extern int sn9c102_probe_tas5130d1b(struct sn9c102_device *cam); + +#endif /* _SN9C102_DEVTABLE_H_ */ diff --git a/drivers/staging/media/sn9c102/sn9c102_hv7131d.c b/drivers/staging/media/sn9c102/sn9c102_hv7131d.c new file mode 100644 index 00000000000..f1d94f0190c --- /dev/null +++ b/drivers/staging/media/sn9c102/sn9c102_hv7131d.c @@ -0,0 +1,269 @@ +/*************************************************************************** + * Plug-in for HV7131D image sensor connected to the SN9C1xx PC Camera * + * Controllers * + * * + * Copyright (C) 2004-2007 by Luca Risolia <luca.risolia@studio.unibo.it> * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the Free Software * + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * + ***************************************************************************/ + +#include "sn9c102_sensor.h" +#include "sn9c102_devtable.h" + + +static int hv7131d_init(struct sn9c102_device *cam) +{ + int err; + + err = sn9c102_write_const_regs(cam, {0x00, 0x10}, {0x00, 0x11}, + {0x00, 0x14}, {0x60, 0x17}, + {0x0e, 0x18}, {0xf2, 0x19}); + + err += sn9c102_i2c_write(cam, 0x01, 0x04); + err += sn9c102_i2c_write(cam, 0x02, 0x00); + err += sn9c102_i2c_write(cam, 0x28, 0x00); + + return err; +} + + +static int hv7131d_get_ctrl(struct sn9c102_device *cam, + struct v4l2_control *ctrl) +{ + switch (ctrl->id) { + case V4L2_CID_EXPOSURE: + { + int r1 = sn9c102_i2c_read(cam, 0x26), + r2 = sn9c102_i2c_read(cam, 0x27); + if (r1 < 0 || r2 < 0) + return -EIO; + ctrl->value = (r1 << 8) | (r2 & 0xff); + } + return 0; + case V4L2_CID_RED_BALANCE: + ctrl->value = sn9c102_i2c_read(cam, 0x31); + if (ctrl->value < 0) + return -EIO; + ctrl->value = 0x3f - (ctrl->value & 0x3f); + return 0; + case V4L2_CID_BLUE_BALANCE: + ctrl->value = sn9c102_i2c_read(cam, 0x33); + if (ctrl->value < 0) + return -EIO; + ctrl->value = 0x3f - (ctrl->value & 0x3f); + return 0; + case SN9C102_V4L2_CID_GREEN_BALANCE: + ctrl->value = sn9c102_i2c_read(cam, 0x32); + if (ctrl->value < 0) + return -EIO; + ctrl->value = 0x3f - (ctrl->value & 0x3f); + return 0; + case SN9C102_V4L2_CID_RESET_LEVEL: + ctrl->value = sn9c102_i2c_read(cam, 0x30); + if (ctrl->value < 0) + return -EIO; + ctrl->value &= 0x3f; + return 0; + case SN9C102_V4L2_CID_PIXEL_BIAS_VOLTAGE: + ctrl->value = sn9c102_i2c_read(cam, 0x34); + if (ctrl->value < 0) + return -EIO; + ctrl->value &= 0x07; + return 0; + default: + return -EINVAL; + } +} + + +static int hv7131d_set_ctrl(struct sn9c102_device *cam, + const struct v4l2_control *ctrl) +{ + int err = 0; + + switch (ctrl->id) { + case V4L2_CID_EXPOSURE: + err += sn9c102_i2c_write(cam, 0x26, ctrl->value >> 8); + err += sn9c102_i2c_write(cam, 0x27, ctrl->value & 0xff); + break; + case V4L2_CID_RED_BALANCE: + err += sn9c102_i2c_write(cam, 0x31, 0x3f - ctrl->value); + break; + case V4L2_CID_BLUE_BALANCE: + err += sn9c102_i2c_write(cam, 0x33, 0x3f - ctrl->value); + break; + case SN9C102_V4L2_CID_GREEN_BALANCE: + err += sn9c102_i2c_write(cam, 0x32, 0x3f - ctrl->value); + break; + case SN9C102_V4L2_CID_RESET_LEVEL: + err += sn9c102_i2c_write(cam, 0x30, ctrl->value); + break; + case SN9C102_V4L2_CID_PIXEL_BIAS_VOLTAGE: + err += sn9c102_i2c_write(cam, 0x34, ctrl->value); + break; + default: + return -EINVAL; + } + + return err ? -EIO : 0; +} + + +static int hv7131d_set_crop(struct sn9c102_device *cam, + const struct v4l2_rect *rect) +{ + struct sn9c102_sensor *s = sn9c102_get_sensor(cam); + int err = 0; + u8 h_start = (u8)(rect->left - s->cropcap.bounds.left) + 2, + v_start = (u8)(rect->top - s->cropcap.bounds.top) + 2; + + err += sn9c102_write_reg(cam, h_start, 0x12); + err += sn9c102_write_reg(cam, v_start, 0x13); + + return err; +} + + +static int hv7131d_set_pix_format(struct sn9c102_device *cam, + const struct v4l2_pix_format *pix) +{ + int err = 0; + + if (pix->pixelformat == V4L2_PIX_FMT_SN9C10X) + err += sn9c102_write_reg(cam, 0x42, 0x19); + else + err += sn9c102_write_reg(cam, 0xf2, 0x19); + + return err; +} + + +static const struct sn9c102_sensor hv7131d = { + .name = "HV7131D", + .maintainer = "Luca Risolia <luca.risolia@studio.unibo.it>", + .supported_bridge = BRIDGE_SN9C101 | BRIDGE_SN9C102, + .sysfs_ops = SN9C102_I2C_READ | SN9C102_I2C_WRITE, + .frequency = SN9C102_I2C_100KHZ, + .interface = SN9C102_I2C_2WIRES, + .i2c_slave_id = 0x11, + .init = &hv7131d_init, + .qctrl = { + { + .id = V4L2_CID_EXPOSURE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "exposure", + .minimum = 0x0250, + .maximum = 0xffff, + .step = 0x0001, + .default_value = 0x0250, + .flags = 0, + }, + { + .id = V4L2_CID_RED_BALANCE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "red balance", + .minimum = 0x00, + .maximum = 0x3f, + .step = 0x01, + .default_value = 0x00, + .flags = 0, + }, + { + .id = V4L2_CID_BLUE_BALANCE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "blue balance", + .minimum = 0x00, + .maximum = 0x3f, + .step = 0x01, + .default_value = 0x20, + .flags = 0, + }, + { + .id = SN9C102_V4L2_CID_GREEN_BALANCE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "green balance", + .minimum = 0x00, + .maximum = 0x3f, + .step = 0x01, + .default_value = 0x1e, + .flags = 0, + }, + { + .id = SN9C102_V4L2_CID_RESET_LEVEL, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "reset level", + .minimum = 0x19, + .maximum = 0x3f, + .step = 0x01, + .default_value = 0x30, + .flags = 0, + }, + { + .id = SN9C102_V4L2_CID_PIXEL_BIAS_VOLTAGE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "pixel bias voltage", + .minimum = 0x00, + .maximum = 0x07, + .step = 0x01, + .default_value = 0x02, + .flags = 0, + }, + }, + .get_ctrl = &hv7131d_get_ctrl, + .set_ctrl = &hv7131d_set_ctrl, + .cropcap = { + .bounds = { + .left = 0, + .top = 0, + .width = 640, + .height = 480, + }, + .defrect = { + .left = 0, + .top = 0, + .width = 640, + .height = 480, + }, + }, + .set_crop = &hv7131d_set_crop, + .pix_format = { + .width = 640, + .height = 480, + .pixelformat = V4L2_PIX_FMT_SBGGR8, + .priv = 8, + }, + .set_pix_format = &hv7131d_set_pix_format +}; + + +int sn9c102_probe_hv7131d(struct sn9c102_device *cam) +{ + int r0 = 0, r1 = 0, err; + + err = sn9c102_write_const_regs(cam, {0x01, 0x01}, {0x00, 0x01}, + {0x28, 0x17}); + + r0 = sn9c102_i2c_try_read(cam, &hv7131d, 0x00); + r1 = sn9c102_i2c_try_read(cam, &hv7131d, 0x01); + if (err || r0 < 0 || r1 < 0) + return -EIO; + + if ((r0 != 0x00 && r0 != 0x01) || r1 != 0x04) + return -ENODEV; + + sn9c102_attach_sensor(cam, &hv7131d); + + return 0; +} diff --git a/drivers/staging/media/sn9c102/sn9c102_hv7131r.c b/drivers/staging/media/sn9c102/sn9c102_hv7131r.c new file mode 100644 index 00000000000..51b24e000e8 --- /dev/null +++ b/drivers/staging/media/sn9c102/sn9c102_hv7131r.c @@ -0,0 +1,369 @@ +/*************************************************************************** + * Plug-in for HV7131R image sensor connected to the SN9C1xx PC Camera * + * Controllers * + * * + * Copyright (C) 2007 by Luca Risolia <luca.risolia@studio.unibo.it> * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the Free Software * + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * + ***************************************************************************/ + +#include "sn9c102_sensor.h" +#include "sn9c102_devtable.h" + + +static int hv7131r_init(struct sn9c102_device *cam) +{ + int err = 0; + + switch (sn9c102_get_bridge(cam)) { + case BRIDGE_SN9C103: + err = sn9c102_write_const_regs(cam, {0x00, 0x03}, {0x1a, 0x04}, + {0x20, 0x05}, {0x20, 0x06}, + {0x03, 0x10}, {0x00, 0x14}, + {0x60, 0x17}, {0x0a, 0x18}, + {0xf0, 0x19}, {0x1d, 0x1a}, + {0x10, 0x1b}, {0x02, 0x1c}, + {0x03, 0x1d}, {0x0f, 0x1e}, + {0x0c, 0x1f}, {0x00, 0x20}, + {0x10, 0x21}, {0x20, 0x22}, + {0x30, 0x23}, {0x40, 0x24}, + {0x50, 0x25}, {0x60, 0x26}, + {0x70, 0x27}, {0x80, 0x28}, + {0x90, 0x29}, {0xa0, 0x2a}, + {0xb0, 0x2b}, {0xc0, 0x2c}, + {0xd0, 0x2d}, {0xe0, 0x2e}, + {0xf0, 0x2f}, {0xff, 0x30}); + break; + case BRIDGE_SN9C105: + case BRIDGE_SN9C120: + err = sn9c102_write_const_regs(cam, {0x44, 0x01}, {0x40, 0x02}, + {0x00, 0x03}, {0x1a, 0x04}, + {0x44, 0x05}, {0x3e, 0x06}, + {0x1a, 0x07}, {0x03, 0x10}, + {0x08, 0x14}, {0xa3, 0x17}, + {0x4b, 0x18}, {0x00, 0x19}, + {0x1d, 0x1a}, {0x10, 0x1b}, + {0x02, 0x1c}, {0x03, 0x1d}, + {0x0f, 0x1e}, {0x0c, 0x1f}, + {0x00, 0x20}, {0x29, 0x21}, + {0x40, 0x22}, {0x54, 0x23}, + {0x66, 0x24}, {0x76, 0x25}, + {0x85, 0x26}, {0x94, 0x27}, + {0xa1, 0x28}, {0xae, 0x29}, + {0xbb, 0x2a}, {0xc7, 0x2b}, + {0xd3, 0x2c}, {0xde, 0x2d}, + {0xea, 0x2e}, {0xf4, 0x2f}, + {0xff, 0x30}, {0x00, 0x3F}, + {0xC7, 0x40}, {0x01, 0x41}, + {0x44, 0x42}, {0x00, 0x43}, + {0x44, 0x44}, {0x00, 0x45}, + {0x44, 0x46}, {0x00, 0x47}, + {0xC7, 0x48}, {0x01, 0x49}, + {0xC7, 0x4A}, {0x01, 0x4B}, + {0xC7, 0x4C}, {0x01, 0x4D}, + {0x44, 0x4E}, {0x00, 0x4F}, + {0x44, 0x50}, {0x00, 0x51}, + {0x44, 0x52}, {0x00, 0x53}, + {0xC7, 0x54}, {0x01, 0x55}, + {0xC7, 0x56}, {0x01, 0x57}, + {0xC7, 0x58}, {0x01, 0x59}, + {0x44, 0x5A}, {0x00, 0x5B}, + {0x44, 0x5C}, {0x00, 0x5D}, + {0x44, 0x5E}, {0x00, 0x5F}, + {0xC7, 0x60}, {0x01, 0x61}, + {0xC7, 0x62}, {0x01, 0x63}, + {0xC7, 0x64}, {0x01, 0x65}, + {0x44, 0x66}, {0x00, 0x67}, + {0x44, 0x68}, {0x00, 0x69}, + {0x44, 0x6A}, {0x00, 0x6B}, + {0xC7, 0x6C}, {0x01, 0x6D}, + {0xC7, 0x6E}, {0x01, 0x6F}, + {0xC7, 0x70}, {0x01, 0x71}, + {0x44, 0x72}, {0x00, 0x73}, + {0x44, 0x74}, {0x00, 0x75}, + {0x44, 0x76}, {0x00, 0x77}, + {0xC7, 0x78}, {0x01, 0x79}, + {0xC7, 0x7A}, {0x01, 0x7B}, + {0xC7, 0x7C}, {0x01, 0x7D}, + {0x44, 0x7E}, {0x00, 0x7F}, + {0x14, 0x84}, {0x00, 0x85}, + {0x27, 0x86}, {0x00, 0x87}, + {0x07, 0x88}, {0x00, 0x89}, + {0xEC, 0x8A}, {0x0f, 0x8B}, + {0xD8, 0x8C}, {0x0f, 0x8D}, + {0x3D, 0x8E}, {0x00, 0x8F}, + {0x3D, 0x90}, {0x00, 0x91}, + {0xCD, 0x92}, {0x0f, 0x93}, + {0xf7, 0x94}, {0x0f, 0x95}, + {0x0C, 0x96}, {0x00, 0x97}, + {0x00, 0x98}, {0x66, 0x99}, + {0x05, 0x9A}, {0x00, 0x9B}, + {0x04, 0x9C}, {0x00, 0x9D}, + {0x08, 0x9E}, {0x00, 0x9F}, + {0x2D, 0xC0}, {0x2D, 0xC1}, + {0x3A, 0xC2}, {0x05, 0xC3}, + {0x04, 0xC4}, {0x3F, 0xC5}, + {0x00, 0xC6}, {0x00, 0xC7}, + {0x50, 0xC8}, {0x3C, 0xC9}, + {0x28, 0xCA}, {0xD8, 0xCB}, + {0x14, 0xCC}, {0xEC, 0xCD}, + {0x32, 0xCE}, {0xDD, 0xCF}, + {0x32, 0xD0}, {0xDD, 0xD1}, + {0x6A, 0xD2}, {0x50, 0xD3}, + {0x00, 0xD4}, {0x00, 0xD5}, + {0x00, 0xD6}); + break; + default: + break; + } + + err += sn9c102_i2c_write(cam, 0x20, 0x00); + err += sn9c102_i2c_write(cam, 0x21, 0xd6); + err += sn9c102_i2c_write(cam, 0x25, 0x06); + + return err; +} + + +static int hv7131r_get_ctrl(struct sn9c102_device *cam, + struct v4l2_control *ctrl) +{ + switch (ctrl->id) { + case V4L2_CID_GAIN: + ctrl->value = sn9c102_i2c_read(cam, 0x30); + if (ctrl->value < 0) + return -EIO; + return 0; + case V4L2_CID_RED_BALANCE: + ctrl->value = sn9c102_i2c_read(cam, 0x31); + if (ctrl->value < 0) + return -EIO; + ctrl->value = ctrl->value & 0x3f; + return 0; + case V4L2_CID_BLUE_BALANCE: + ctrl->value = sn9c102_i2c_read(cam, 0x33); + if (ctrl->value < 0) + return -EIO; + ctrl->value = ctrl->value & 0x3f; + return 0; + case SN9C102_V4L2_CID_GREEN_BALANCE: + ctrl->value = sn9c102_i2c_read(cam, 0x32); + if (ctrl->value < 0) + return -EIO; + ctrl->value = ctrl->value & 0x3f; + return 0; + case V4L2_CID_BLACK_LEVEL: + ctrl->value = sn9c102_i2c_read(cam, 0x01); + if (ctrl->value < 0) + return -EIO; + ctrl->value = (ctrl->value & 0x08) ? 1 : 0; + return 0; + default: + return -EINVAL; + } +} + + +static int hv7131r_set_ctrl(struct sn9c102_device *cam, + const struct v4l2_control *ctrl) +{ + int err = 0; + + switch (ctrl->id) { + case V4L2_CID_GAIN: + err += sn9c102_i2c_write(cam, 0x30, ctrl->value); + break; + case V4L2_CID_RED_BALANCE: + err += sn9c102_i2c_write(cam, 0x31, ctrl->value); + break; + case V4L2_CID_BLUE_BALANCE: + err += sn9c102_i2c_write(cam, 0x33, ctrl->value); + break; + case SN9C102_V4L2_CID_GREEN_BALANCE: + err += sn9c102_i2c_write(cam, 0x32, ctrl->value); + break; + case V4L2_CID_BLACK_LEVEL: + { + int r = sn9c102_i2c_read(cam, 0x01); + + if (r < 0) + return -EIO; + err += sn9c102_i2c_write(cam, 0x01, + (ctrl->value<<3) | (r&0xf7)); + } + break; + default: + return -EINVAL; + } + + return err ? -EIO : 0; +} + + +static int hv7131r_set_crop(struct sn9c102_device *cam, + const struct v4l2_rect *rect) +{ + struct sn9c102_sensor *s = sn9c102_get_sensor(cam); + int err = 0; + u8 h_start = (u8)(rect->left - s->cropcap.bounds.left) + 1, + v_start = (u8)(rect->top - s->cropcap.bounds.top) + 1; + + err += sn9c102_write_reg(cam, h_start, 0x12); + err += sn9c102_write_reg(cam, v_start, 0x13); + + return err; +} + + +static int hv7131r_set_pix_format(struct sn9c102_device *cam, + const struct v4l2_pix_format *pix) +{ + int err = 0; + + switch (sn9c102_get_bridge(cam)) { + case BRIDGE_SN9C103: + if (pix->pixelformat == V4L2_PIX_FMT_SBGGR8) { + err += sn9c102_write_reg(cam, 0xa0, 0x19); + err += sn9c102_i2c_write(cam, 0x01, 0x04); + } else { + err += sn9c102_write_reg(cam, 0x30, 0x19); + err += sn9c102_i2c_write(cam, 0x01, 0x04); + } + break; + case BRIDGE_SN9C105: + case BRIDGE_SN9C120: + if (pix->pixelformat == V4L2_PIX_FMT_SBGGR8) { + err += sn9c102_write_reg(cam, 0xa5, 0x17); + err += sn9c102_i2c_write(cam, 0x01, 0x24); + } else { + err += sn9c102_write_reg(cam, 0xa3, 0x17); + err += sn9c102_i2c_write(cam, 0x01, 0x04); + } + break; + default: + break; + } + + return err; +} + + +static const struct sn9c102_sensor hv7131r = { + .name = "HV7131R", + .maintainer = "Luca Risolia <luca.risolia@studio.unibo.it>", + .supported_bridge = BRIDGE_SN9C103 | BRIDGE_SN9C105 | BRIDGE_SN9C120, + .sysfs_ops = SN9C102_I2C_READ | SN9C102_I2C_WRITE, + .frequency = SN9C102_I2C_100KHZ, + .interface = SN9C102_I2C_2WIRES, + .i2c_slave_id = 0x11, + .init = &hv7131r_init, + .qctrl = { + { + .id = V4L2_CID_GAIN, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "global gain", + .minimum = 0x00, + .maximum = 0xff, + .step = 0x01, + .default_value = 0x40, + .flags = 0, + }, + { + .id = V4L2_CID_RED_BALANCE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "red balance", + .minimum = 0x00, + .maximum = 0x3f, + .step = 0x01, + .default_value = 0x08, + .flags = 0, + }, + { + .id = V4L2_CID_BLUE_BALANCE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "blue balance", + .minimum = 0x00, + .maximum = 0x3f, + .step = 0x01, + .default_value = 0x1a, + .flags = 0, + }, + { + .id = SN9C102_V4L2_CID_GREEN_BALANCE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "green balance", + .minimum = 0x00, + .maximum = 0x3f, + .step = 0x01, + .default_value = 0x2f, + .flags = 0, + }, + { + .id = V4L2_CID_BLACK_LEVEL, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "auto black level compensation", + .minimum = 0x00, + .maximum = 0x01, + .step = 0x01, + .default_value = 0x00, + .flags = 0, + }, + }, + .get_ctrl = &hv7131r_get_ctrl, + .set_ctrl = &hv7131r_set_ctrl, + .cropcap = { + .bounds = { + .left = 0, + .top = 0, + .width = 640, + .height = 480, + }, + .defrect = { + .left = 0, + .top = 0, + .width = 640, + .height = 480, + }, + }, + .set_crop = &hv7131r_set_crop, + .pix_format = { + .width = 640, + .height = 480, + .pixelformat = V4L2_PIX_FMT_SBGGR8, + .priv = 8, + }, + .set_pix_format = &hv7131r_set_pix_format +}; + + +int sn9c102_probe_hv7131r(struct sn9c102_device *cam) +{ + int devid, err; + + err = sn9c102_write_const_regs(cam, {0x09, 0x01}, {0x44, 0x02}, + {0x34, 0x01}, {0x20, 0x17}, + {0x34, 0x01}, {0x46, 0x01}); + + devid = sn9c102_i2c_try_read(cam, &hv7131r, 0x00); + if (err || devid < 0) + return -EIO; + + if (devid != 0x02) + return -ENODEV; + + sn9c102_attach_sensor(cam, &hv7131r); + + return 0; +} diff --git a/drivers/staging/media/sn9c102/sn9c102_mi0343.c b/drivers/staging/media/sn9c102/sn9c102_mi0343.c new file mode 100644 index 00000000000..b20fdb6541d --- /dev/null +++ b/drivers/staging/media/sn9c102/sn9c102_mi0343.c @@ -0,0 +1,352 @@ +/*************************************************************************** + * Plug-in for MI-0343 image sensor connected to the SN9C1xx PC Camera * + * Controllers * + * * + * Copyright (C) 2004-2007 by Luca Risolia <luca.risolia@studio.unibo.it> * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the Free Software * + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * + ***************************************************************************/ + +#include "sn9c102_sensor.h" +#include "sn9c102_devtable.h" + + +static int mi0343_init(struct sn9c102_device *cam) +{ + struct sn9c102_sensor *s = sn9c102_get_sensor(cam); + int err = 0; + + err = sn9c102_write_const_regs(cam, {0x00, 0x10}, {0x00, 0x11}, + {0x0a, 0x14}, {0x40, 0x01}, + {0x20, 0x17}, {0x07, 0x18}, + {0xa0, 0x19}); + + err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, 0x0d, + 0x00, 0x01, 0, 0); + err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, 0x0d, + 0x00, 0x00, 0, 0); + err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, 0x03, + 0x01, 0xe1, 0, 0); + err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, 0x04, + 0x02, 0x81, 0, 0); + err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, 0x05, + 0x00, 0x17, 0, 0); + err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, 0x06, + 0x00, 0x11, 0, 0); + err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, 0x62, + 0x04, 0x9a, 0, 0); + + return err; +} + + +static int mi0343_get_ctrl(struct sn9c102_device *cam, + struct v4l2_control *ctrl) +{ + struct sn9c102_sensor *s = sn9c102_get_sensor(cam); + u8 data[2]; + + switch (ctrl->id) { + case V4L2_CID_EXPOSURE: + if (sn9c102_i2c_try_raw_read(cam, s, s->i2c_slave_id, 0x09, 2, + data) < 0) + return -EIO; + ctrl->value = data[0]; + return 0; + case V4L2_CID_GAIN: + if (sn9c102_i2c_try_raw_read(cam, s, s->i2c_slave_id, 0x35, 2, + data) < 0) + return -EIO; + break; + case V4L2_CID_HFLIP: + if (sn9c102_i2c_try_raw_read(cam, s, s->i2c_slave_id, 0x20, 2, + data) < 0) + return -EIO; + ctrl->value = data[1] & 0x20 ? 1 : 0; + return 0; + case V4L2_CID_VFLIP: + if (sn9c102_i2c_try_raw_read(cam, s, s->i2c_slave_id, 0x20, 2, + data) < 0) + return -EIO; + ctrl->value = data[1] & 0x80 ? 1 : 0; + return 0; + case V4L2_CID_RED_BALANCE: + if (sn9c102_i2c_try_raw_read(cam, s, s->i2c_slave_id, 0x2d, 2, + data) < 0) + return -EIO; + break; + case V4L2_CID_BLUE_BALANCE: + if (sn9c102_i2c_try_raw_read(cam, s, s->i2c_slave_id, 0x2c, 2, + data) < 0) + return -EIO; + break; + case SN9C102_V4L2_CID_GREEN_BALANCE: + if (sn9c102_i2c_try_raw_read(cam, s, s->i2c_slave_id, 0x2e, 2, + data) < 0) + return -EIO; + break; + default: + return -EINVAL; + } + + switch (ctrl->id) { + case V4L2_CID_GAIN: + case V4L2_CID_RED_BALANCE: + case V4L2_CID_BLUE_BALANCE: + case SN9C102_V4L2_CID_GREEN_BALANCE: + ctrl->value = data[1] | (data[0] << 8); + if (ctrl->value >= 0x10 && ctrl->value <= 0x3f) + ctrl->value -= 0x10; + else if (ctrl->value >= 0x60 && ctrl->value <= 0x7f) + ctrl->value -= 0x60; + else if (ctrl->value >= 0xe0 && ctrl->value <= 0xff) + ctrl->value -= 0xe0; + } + + return 0; +} + + +static int mi0343_set_ctrl(struct sn9c102_device *cam, + const struct v4l2_control *ctrl) +{ + struct sn9c102_sensor *s = sn9c102_get_sensor(cam); + u16 reg = 0; + int err = 0; + + switch (ctrl->id) { + case V4L2_CID_GAIN: + case V4L2_CID_RED_BALANCE: + case V4L2_CID_BLUE_BALANCE: + case SN9C102_V4L2_CID_GREEN_BALANCE: + if (ctrl->value <= (0x3f-0x10)) + reg = 0x10 + ctrl->value; + else if (ctrl->value <= ((0x3f-0x10) + (0x7f-0x60))) + reg = 0x60 + (ctrl->value - (0x3f-0x10)); + else + reg = 0xe0 + (ctrl->value - (0x3f-0x10) - (0x7f-0x60)); + break; + } + + switch (ctrl->id) { + case V4L2_CID_EXPOSURE: + err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, + 0x09, ctrl->value, 0x00, + 0, 0); + break; + case V4L2_CID_GAIN: + err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, + 0x35, reg >> 8, reg & 0xff, + 0, 0); + break; + case V4L2_CID_HFLIP: + err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, + 0x20, ctrl->value ? 0x40:0x00, + ctrl->value ? 0x20:0x00, + 0, 0); + break; + case V4L2_CID_VFLIP: + err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, + 0x20, ctrl->value ? 0x80:0x00, + ctrl->value ? 0x80:0x00, + 0, 0); + break; + case V4L2_CID_RED_BALANCE: + err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, + 0x2d, reg >> 8, reg & 0xff, + 0, 0); + break; + case V4L2_CID_BLUE_BALANCE: + err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, + 0x2c, reg >> 8, reg & 0xff, + 0, 0); + break; + case SN9C102_V4L2_CID_GREEN_BALANCE: + err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, + 0x2b, reg >> 8, reg & 0xff, + 0, 0); + err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, + 0x2e, reg >> 8, reg & 0xff, + 0, 0); + break; + default: + return -EINVAL; + } + + return err ? -EIO : 0; +} + + +static int mi0343_set_crop(struct sn9c102_device *cam, + const struct v4l2_rect *rect) +{ + struct sn9c102_sensor *s = sn9c102_get_sensor(cam); + int err = 0; + u8 h_start = (u8)(rect->left - s->cropcap.bounds.left) + 0, + v_start = (u8)(rect->top - s->cropcap.bounds.top) + 2; + + err += sn9c102_write_reg(cam, h_start, 0x12); + err += sn9c102_write_reg(cam, v_start, 0x13); + + return err; +} + + +static int mi0343_set_pix_format(struct sn9c102_device *cam, + const struct v4l2_pix_format *pix) +{ + struct sn9c102_sensor *s = sn9c102_get_sensor(cam); + int err = 0; + + if (pix->pixelformat == V4L2_PIX_FMT_SN9C10X) { + err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, + 0x0a, 0x00, 0x03, 0, 0); + err += sn9c102_write_reg(cam, 0x20, 0x19); + } else { + err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, + 0x0a, 0x00, 0x05, 0, 0); + err += sn9c102_write_reg(cam, 0xa0, 0x19); + } + + return err; +} + + +static const struct sn9c102_sensor mi0343 = { + .name = "MI-0343", + .maintainer = "Luca Risolia <luca.risolia@studio.unibo.it>", + .supported_bridge = BRIDGE_SN9C101 | BRIDGE_SN9C102, + .frequency = SN9C102_I2C_100KHZ, + .interface = SN9C102_I2C_2WIRES, + .i2c_slave_id = 0x5d, + .init = &mi0343_init, + .qctrl = { + { + .id = V4L2_CID_EXPOSURE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "exposure", + .minimum = 0x00, + .maximum = 0x0f, + .step = 0x01, + .default_value = 0x06, + .flags = 0, + }, + { + .id = V4L2_CID_GAIN, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "global gain", + .minimum = 0x00, + .maximum = (0x3f-0x10)+(0x7f-0x60)+(0xff-0xe0),/*0x6d*/ + .step = 0x01, + .default_value = 0x00, + .flags = 0, + }, + { + .id = V4L2_CID_HFLIP, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "horizontal mirror", + .minimum = 0, + .maximum = 1, + .step = 1, + .default_value = 0, + .flags = 0, + }, + { + .id = V4L2_CID_VFLIP, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "vertical mirror", + .minimum = 0, + .maximum = 1, + .step = 1, + .default_value = 0, + .flags = 0, + }, + { + .id = V4L2_CID_RED_BALANCE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "red balance", + .minimum = 0x00, + .maximum = (0x3f-0x10)+(0x7f-0x60)+(0xff-0xe0), + .step = 0x01, + .default_value = 0x00, + .flags = 0, + }, + { + .id = V4L2_CID_BLUE_BALANCE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "blue balance", + .minimum = 0x00, + .maximum = (0x3f-0x10)+(0x7f-0x60)+(0xff-0xe0), + .step = 0x01, + .default_value = 0x00, + .flags = 0, + }, + { + .id = SN9C102_V4L2_CID_GREEN_BALANCE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "green balance", + .minimum = 0x00, + .maximum = ((0x3f-0x10)+(0x7f-0x60)+(0xff-0xe0)), + .step = 0x01, + .default_value = 0x00, + .flags = 0, + }, + }, + .get_ctrl = &mi0343_get_ctrl, + .set_ctrl = &mi0343_set_ctrl, + .cropcap = { + .bounds = { + .left = 0, + .top = 0, + .width = 640, + .height = 480, + }, + .defrect = { + .left = 0, + .top = 0, + .width = 640, + .height = 480, + }, + }, + .set_crop = &mi0343_set_crop, + .pix_format = { + .width = 640, + .height = 480, + .pixelformat = V4L2_PIX_FMT_SBGGR8, + .priv = 8, + }, + .set_pix_format = &mi0343_set_pix_format +}; + + +int sn9c102_probe_mi0343(struct sn9c102_device *cam) +{ + u8 data[2]; + + if (sn9c102_write_const_regs(cam, {0x01, 0x01}, {0x00, 0x01}, + {0x28, 0x17})) + return -EIO; + + if (sn9c102_i2c_try_raw_read(cam, &mi0343, mi0343.i2c_slave_id, 0x00, + 2, data) < 0) + return -EIO; + + if (data[1] != 0x42 || data[0] != 0xe3) + return -ENODEV; + + sn9c102_attach_sensor(cam, &mi0343); + + return 0; +} diff --git a/drivers/staging/media/sn9c102/sn9c102_mi0360.c b/drivers/staging/media/sn9c102/sn9c102_mi0360.c new file mode 100644 index 00000000000..5f21d1b43e3 --- /dev/null +++ b/drivers/staging/media/sn9c102/sn9c102_mi0360.c @@ -0,0 +1,453 @@ +/*************************************************************************** + * Plug-in for MI-0360 image sensor connected to the SN9C1xx PC Camera * + * Controllers * + * * + * Copyright (C) 2007 by Luca Risolia <luca.risolia@studio.unibo.it> * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the Free Software * + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * + ***************************************************************************/ + +#include "sn9c102_sensor.h" +#include "sn9c102_devtable.h" + + +static int mi0360_init(struct sn9c102_device *cam) +{ + struct sn9c102_sensor *s = sn9c102_get_sensor(cam); + int err = 0; + + switch (sn9c102_get_bridge(cam)) { + case BRIDGE_SN9C103: + err = sn9c102_write_const_regs(cam, {0x00, 0x10}, {0x00, 0x11}, + {0x0a, 0x14}, {0x40, 0x01}, + {0x20, 0x17}, {0x07, 0x18}, + {0xa0, 0x19}, {0x02, 0x1c}, + {0x03, 0x1d}, {0x0f, 0x1e}, + {0x0c, 0x1f}, {0x00, 0x20}, + {0x10, 0x21}, {0x20, 0x22}, + {0x30, 0x23}, {0x40, 0x24}, + {0x50, 0x25}, {0x60, 0x26}, + {0x70, 0x27}, {0x80, 0x28}, + {0x90, 0x29}, {0xa0, 0x2a}, + {0xb0, 0x2b}, {0xc0, 0x2c}, + {0xd0, 0x2d}, {0xe0, 0x2e}, + {0xf0, 0x2f}, {0xff, 0x30}); + break; + case BRIDGE_SN9C105: + case BRIDGE_SN9C120: + err = sn9c102_write_const_regs(cam, {0x44, 0x01}, {0x40, 0x02}, + {0x00, 0x03}, {0x1a, 0x04}, + {0x50, 0x05}, {0x20, 0x06}, + {0x10, 0x07}, {0x03, 0x10}, + {0x08, 0x14}, {0xa2, 0x17}, + {0x47, 0x18}, {0x00, 0x19}, + {0x1d, 0x1a}, {0x10, 0x1b}, + {0x02, 0x1c}, {0x03, 0x1d}, + {0x0f, 0x1e}, {0x0c, 0x1f}, + {0x00, 0x20}, {0x29, 0x21}, + {0x40, 0x22}, {0x54, 0x23}, + {0x66, 0x24}, {0x76, 0x25}, + {0x85, 0x26}, {0x94, 0x27}, + {0xa1, 0x28}, {0xae, 0x29}, + {0xbb, 0x2a}, {0xc7, 0x2b}, + {0xd3, 0x2c}, {0xde, 0x2d}, + {0xea, 0x2e}, {0xf4, 0x2f}, + {0xff, 0x30}, {0x00, 0x3F}, + {0xC7, 0x40}, {0x01, 0x41}, + {0x44, 0x42}, {0x00, 0x43}, + {0x44, 0x44}, {0x00, 0x45}, + {0x44, 0x46}, {0x00, 0x47}, + {0xC7, 0x48}, {0x01, 0x49}, + {0xC7, 0x4A}, {0x01, 0x4B}, + {0xC7, 0x4C}, {0x01, 0x4D}, + {0x44, 0x4E}, {0x00, 0x4F}, + {0x44, 0x50}, {0x00, 0x51}, + {0x44, 0x52}, {0x00, 0x53}, + {0xC7, 0x54}, {0x01, 0x55}, + {0xC7, 0x56}, {0x01, 0x57}, + {0xC7, 0x58}, {0x01, 0x59}, + {0x44, 0x5A}, {0x00, 0x5B}, + {0x44, 0x5C}, {0x00, 0x5D}, + {0x44, 0x5E}, {0x00, 0x5F}, + {0xC7, 0x60}, {0x01, 0x61}, + {0xC7, 0x62}, {0x01, 0x63}, + {0xC7, 0x64}, {0x01, 0x65}, + {0x44, 0x66}, {0x00, 0x67}, + {0x44, 0x68}, {0x00, 0x69}, + {0x44, 0x6A}, {0x00, 0x6B}, + {0xC7, 0x6C}, {0x01, 0x6D}, + {0xC7, 0x6E}, {0x01, 0x6F}, + {0xC7, 0x70}, {0x01, 0x71}, + {0x44, 0x72}, {0x00, 0x73}, + {0x44, 0x74}, {0x00, 0x75}, + {0x44, 0x76}, {0x00, 0x77}, + {0xC7, 0x78}, {0x01, 0x79}, + {0xC7, 0x7A}, {0x01, 0x7B}, + {0xC7, 0x7C}, {0x01, 0x7D}, + {0x44, 0x7E}, {0x00, 0x7F}, + {0x14, 0x84}, {0x00, 0x85}, + {0x27, 0x86}, {0x00, 0x87}, + {0x07, 0x88}, {0x00, 0x89}, + {0xEC, 0x8A}, {0x0f, 0x8B}, + {0xD8, 0x8C}, {0x0f, 0x8D}, + {0x3D, 0x8E}, {0x00, 0x8F}, + {0x3D, 0x90}, {0x00, 0x91}, + {0xCD, 0x92}, {0x0f, 0x93}, + {0xf7, 0x94}, {0x0f, 0x95}, + {0x0C, 0x96}, {0x00, 0x97}, + {0x00, 0x98}, {0x66, 0x99}, + {0x05, 0x9A}, {0x00, 0x9B}, + {0x04, 0x9C}, {0x00, 0x9D}, + {0x08, 0x9E}, {0x00, 0x9F}, + {0x2D, 0xC0}, {0x2D, 0xC1}, + {0x3A, 0xC2}, {0x05, 0xC3}, + {0x04, 0xC4}, {0x3F, 0xC5}, + {0x00, 0xC6}, {0x00, 0xC7}, + {0x50, 0xC8}, {0x3C, 0xC9}, + {0x28, 0xCA}, {0xD8, 0xCB}, + {0x14, 0xCC}, {0xEC, 0xCD}, + {0x32, 0xCE}, {0xDD, 0xCF}, + {0x32, 0xD0}, {0xDD, 0xD1}, + {0x6A, 0xD2}, {0x50, 0xD3}, + {0x00, 0xD4}, {0x00, 0xD5}, + {0x00, 0xD6}); + break; + default: + break; + } + + err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, 0x0d, + 0x00, 0x01, 0, 0); + err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, 0x0d, + 0x00, 0x00, 0, 0); + err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, 0x03, + 0x01, 0xe1, 0, 0); + err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, 0x04, + 0x02, 0x81, 0, 0); + err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, 0x05, + 0x00, 0x17, 0, 0); + err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, 0x06, + 0x00, 0x11, 0, 0); + err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, 0x62, + 0x04, 0x9a, 0, 0); + + return err; +} + + +static int mi0360_get_ctrl(struct sn9c102_device *cam, + struct v4l2_control *ctrl) +{ + struct sn9c102_sensor *s = sn9c102_get_sensor(cam); + u8 data[2]; + + switch (ctrl->id) { + case V4L2_CID_EXPOSURE: + if (sn9c102_i2c_try_raw_read(cam, s, s->i2c_slave_id, 0x09, 2, + data) < 0) + return -EIO; + ctrl->value = data[0]; + return 0; + case V4L2_CID_GAIN: + if (sn9c102_i2c_try_raw_read(cam, s, s->i2c_slave_id, 0x35, 2, + data) < 0) + return -EIO; + ctrl->value = data[1]; + return 0; + case V4L2_CID_RED_BALANCE: + if (sn9c102_i2c_try_raw_read(cam, s, s->i2c_slave_id, 0x2c, 2, + data) < 0) + return -EIO; + ctrl->value = data[1]; + return 0; + case V4L2_CID_BLUE_BALANCE: + if (sn9c102_i2c_try_raw_read(cam, s, s->i2c_slave_id, 0x2d, 2, + data) < 0) + return -EIO; + ctrl->value = data[1]; + return 0; + case SN9C102_V4L2_CID_GREEN_BALANCE: + if (sn9c102_i2c_try_raw_read(cam, s, s->i2c_slave_id, 0x2e, 2, + data) < 0) + return -EIO; + ctrl->value = data[1]; + return 0; + case V4L2_CID_HFLIP: + if (sn9c102_i2c_try_raw_read(cam, s, s->i2c_slave_id, 0x20, 2, + data) < 0) + return -EIO; + ctrl->value = data[1] & 0x20 ? 1 : 0; + return 0; + case V4L2_CID_VFLIP: + if (sn9c102_i2c_try_raw_read(cam, s, s->i2c_slave_id, 0x20, 2, + data) < 0) + return -EIO; + ctrl->value = data[1] & 0x80 ? 1 : 0; + return 0; + default: + return -EINVAL; + } + + return 0; +} + + +static int mi0360_set_ctrl(struct sn9c102_device *cam, + const struct v4l2_control *ctrl) +{ + struct sn9c102_sensor *s = sn9c102_get_sensor(cam); + int err = 0; + + switch (ctrl->id) { + case V4L2_CID_EXPOSURE: + err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, + 0x09, ctrl->value, 0x00, + 0, 0); + break; + case V4L2_CID_GAIN: + err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, + 0x35, 0x03, ctrl->value, + 0, 0); + break; + case V4L2_CID_RED_BALANCE: + err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, + 0x2c, 0x03, ctrl->value, + 0, 0); + break; + case V4L2_CID_BLUE_BALANCE: + err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, + 0x2d, 0x03, ctrl->value, + 0, 0); + break; + case SN9C102_V4L2_CID_GREEN_BALANCE: + err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, + 0x2b, 0x03, ctrl->value, + 0, 0); + err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, + 0x2e, 0x03, ctrl->value, + 0, 0); + break; + case V4L2_CID_HFLIP: + err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, + 0x20, ctrl->value ? 0x40:0x00, + ctrl->value ? 0x20:0x00, + 0, 0); + break; + case V4L2_CID_VFLIP: + err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, + 0x20, ctrl->value ? 0x80:0x00, + ctrl->value ? 0x80:0x00, + 0, 0); + break; + default: + return -EINVAL; + } + + return err ? -EIO : 0; +} + + +static int mi0360_set_crop(struct sn9c102_device *cam, + const struct v4l2_rect *rect) +{ + struct sn9c102_sensor *s = sn9c102_get_sensor(cam); + int err = 0; + u8 h_start = 0, v_start = (u8)(rect->top - s->cropcap.bounds.top) + 1; + + switch (sn9c102_get_bridge(cam)) { + case BRIDGE_SN9C103: + h_start = (u8)(rect->left - s->cropcap.bounds.left) + 0; + break; + case BRIDGE_SN9C105: + case BRIDGE_SN9C120: + h_start = (u8)(rect->left - s->cropcap.bounds.left) + 1; + break; + default: + break; + } + + err += sn9c102_write_reg(cam, h_start, 0x12); + err += sn9c102_write_reg(cam, v_start, 0x13); + + return err; +} + + +static int mi0360_set_pix_format(struct sn9c102_device *cam, + const struct v4l2_pix_format *pix) +{ + struct sn9c102_sensor *s = sn9c102_get_sensor(cam); + int err = 0; + + if (pix->pixelformat == V4L2_PIX_FMT_SBGGR8) { + err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, + 0x0a, 0x00, 0x05, 0, 0); + err += sn9c102_write_reg(cam, 0x60, 0x19); + if (sn9c102_get_bridge(cam) == BRIDGE_SN9C105 || + sn9c102_get_bridge(cam) == BRIDGE_SN9C120) + err += sn9c102_write_reg(cam, 0xa6, 0x17); + } else { + err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, + 0x0a, 0x00, 0x02, 0, 0); + err += sn9c102_write_reg(cam, 0x20, 0x19); + if (sn9c102_get_bridge(cam) == BRIDGE_SN9C105 || + sn9c102_get_bridge(cam) == BRIDGE_SN9C120) + err += sn9c102_write_reg(cam, 0xa2, 0x17); + } + + return err; +} + + +static const struct sn9c102_sensor mi0360 = { + .name = "MI-0360", + .maintainer = "Luca Risolia <luca.risolia@studio.unibo.it>", + .supported_bridge = BRIDGE_SN9C103 | BRIDGE_SN9C105 | BRIDGE_SN9C120, + .frequency = SN9C102_I2C_100KHZ, + .interface = SN9C102_I2C_2WIRES, + .i2c_slave_id = 0x5d, + .init = &mi0360_init, + .qctrl = { + { + .id = V4L2_CID_EXPOSURE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "exposure", + .minimum = 0x00, + .maximum = 0x0f, + .step = 0x01, + .default_value = 0x05, + .flags = 0, + }, + { + .id = V4L2_CID_GAIN, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "global gain", + .minimum = 0x00, + .maximum = 0x7f, + .step = 0x01, + .default_value = 0x25, + .flags = 0, + }, + { + .id = V4L2_CID_HFLIP, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "horizontal mirror", + .minimum = 0, + .maximum = 1, + .step = 1, + .default_value = 0, + .flags = 0, + }, + { + .id = V4L2_CID_VFLIP, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "vertical mirror", + .minimum = 0, + .maximum = 1, + .step = 1, + .default_value = 0, + .flags = 0, + }, + { + .id = V4L2_CID_BLUE_BALANCE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "blue balance", + .minimum = 0x00, + .maximum = 0x7f, + .step = 0x01, + .default_value = 0x0f, + .flags = 0, + }, + { + .id = V4L2_CID_RED_BALANCE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "red balance", + .minimum = 0x00, + .maximum = 0x7f, + .step = 0x01, + .default_value = 0x32, + .flags = 0, + }, + { + .id = SN9C102_V4L2_CID_GREEN_BALANCE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "green balance", + .minimum = 0x00, + .maximum = 0x7f, + .step = 0x01, + .default_value = 0x25, + .flags = 0, + }, + }, + .get_ctrl = &mi0360_get_ctrl, + .set_ctrl = &mi0360_set_ctrl, + .cropcap = { + .bounds = { + .left = 0, + .top = 0, + .width = 640, + .height = 480, + }, + .defrect = { + .left = 0, + .top = 0, + .width = 640, + .height = 480, + }, + }, + .set_crop = &mi0360_set_crop, + .pix_format = { + .width = 640, + .height = 480, + .pixelformat = V4L2_PIX_FMT_SBGGR8, + .priv = 8, + }, + .set_pix_format = &mi0360_set_pix_format +}; + + +int sn9c102_probe_mi0360(struct sn9c102_device *cam) +{ + + u8 data[2]; + + switch (sn9c102_get_bridge(cam)) { + case BRIDGE_SN9C103: + if (sn9c102_write_const_regs(cam, {0x01, 0x01}, {0x00, 0x01}, + {0x28, 0x17})) + return -EIO; + break; + case BRIDGE_SN9C105: + case BRIDGE_SN9C120: + if (sn9c102_write_const_regs(cam, {0x01, 0xf1}, {0x00, 0xf1}, + {0x01, 0x01}, {0x00, 0x01}, + {0x28, 0x17})) + return -EIO; + break; + default: + break; + } + + if (sn9c102_i2c_try_raw_read(cam, &mi0360, mi0360.i2c_slave_id, 0x00, + 2, data) < 0) + return -EIO; + + if (data[0] != 0x82 || data[1] != 0x43) + return -ENODEV; + + sn9c102_attach_sensor(cam, &mi0360); + + return 0; +} diff --git a/drivers/staging/media/sn9c102/sn9c102_mt9v111.c b/drivers/staging/media/sn9c102/sn9c102_mt9v111.c new file mode 100644 index 00000000000..95986eb492e --- /dev/null +++ b/drivers/staging/media/sn9c102/sn9c102_mt9v111.c @@ -0,0 +1,260 @@ +/*************************************************************************** + * Plug-in for MT9V111 image sensor connected to the SN9C1xx PC Camera * + * Controllers * + * * + * Copyright (C) 2007 by Luca Risolia <luca.risolia@studio.unibo.it> * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the Free Software * + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * + ***************************************************************************/ + +#include "sn9c102_sensor.h" +#include "sn9c102_devtable.h" + + +static int mt9v111_init(struct sn9c102_device *cam) +{ + struct sn9c102_sensor *s = sn9c102_get_sensor(cam); + int err = 0; + + err = sn9c102_write_const_regs(cam, {0x44, 0x01}, {0x40, 0x02}, + {0x00, 0x03}, {0x1a, 0x04}, + {0x1f, 0x05}, {0x20, 0x06}, + {0x1f, 0x07}, {0x81, 0x08}, + {0x5c, 0x09}, {0x00, 0x0a}, + {0x00, 0x0b}, {0x00, 0x0c}, + {0x00, 0x0d}, {0x00, 0x0e}, + {0x00, 0x0f}, {0x03, 0x10}, + {0x00, 0x11}, {0x00, 0x12}, + {0x02, 0x13}, {0x14, 0x14}, + {0x28, 0x15}, {0x1e, 0x16}, + {0xe2, 0x17}, {0x06, 0x18}, + {0x00, 0x19}, {0x00, 0x1a}, + {0x00, 0x1b}, {0x08, 0x20}, + {0x39, 0x21}, {0x51, 0x22}, + {0x63, 0x23}, {0x73, 0x24}, + {0x82, 0x25}, {0x8f, 0x26}, + {0x9b, 0x27}, {0xa7, 0x28}, + {0xb1, 0x29}, {0xbc, 0x2a}, + {0xc6, 0x2b}, {0xcf, 0x2c}, + {0xd8, 0x2d}, {0xe1, 0x2e}, + {0xea, 0x2f}, {0xf2, 0x30}, + {0x13, 0x84}, {0x00, 0x85}, + {0x25, 0x86}, {0x00, 0x87}, + {0x07, 0x88}, {0x00, 0x89}, + {0xee, 0x8a}, {0x0f, 0x8b}, + {0xe5, 0x8c}, {0x0f, 0x8d}, + {0x2e, 0x8e}, {0x00, 0x8f}, + {0x30, 0x90}, {0x00, 0x91}, + {0xd4, 0x92}, {0x0f, 0x93}, + {0xfc, 0x94}, {0x0f, 0x95}, + {0x14, 0x96}, {0x00, 0x97}, + {0x00, 0x98}, {0x60, 0x99}, + {0x07, 0x9a}, {0x40, 0x9b}, + {0x20, 0x9c}, {0x00, 0x9d}, + {0x00, 0x9e}, {0x00, 0x9f}, + {0x2d, 0xc0}, {0x2d, 0xc1}, + {0x3a, 0xc2}, {0x05, 0xc3}, + {0x04, 0xc4}, {0x3f, 0xc5}, + {0x00, 0xc6}, {0x00, 0xc7}, + {0x50, 0xc8}, {0x3c, 0xc9}, + {0x28, 0xca}, {0xd8, 0xcb}, + {0x14, 0xcc}, {0xec, 0xcd}, + {0x32, 0xce}, {0xdd, 0xcf}, + {0x2d, 0xd0}, {0xdd, 0xd1}, + {0x6a, 0xd2}, {0x50, 0xd3}, + {0x60, 0xd4}, {0x00, 0xd5}, + {0x00, 0xd6}); + + err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, 0x01, + 0x00, 0x01, 0, 0); + err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, 0x0d, + 0x00, 0x01, 0, 0); + err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, 0x0d, + 0x00, 0x00, 0, 0); + err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, 0x08, + 0x04, 0x80, 0, 0); + err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, 0x01, + 0x00, 0x04, 0, 0); + err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, 0x08, + 0x00, 0x08, 0, 0); + err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, 0x02, + 0x00, 0x16, 0, 0); + err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, 0x03, + 0x01, 0xe7, 0, 0); + err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, 0x04, + 0x02, 0x87, 0, 0); + err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, 0x06, + 0x00, 0x40, 0, 0); + err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, 0x05, + 0x00, 0x09, 0, 0); + err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, 0x07, + 0x30, 0x02, 0, 0); + err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, 0x0c, + 0x00, 0x00, 0, 0); + err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, 0x12, + 0x00, 0xb0, 0, 0); + err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, 0x13, + 0x00, 0x7c, 0, 0); + err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, 0x1e, + 0x00, 0x00, 0, 0); + err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, 0x20, + 0x00, 0x00, 0, 0); + err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, 0x20, + 0x00, 0x00, 0, 0); + err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, 0x01, + 0x00, 0x04, 0, 0); + + return err; +} + +static int mt9v111_get_ctrl(struct sn9c102_device *cam, + struct v4l2_control *ctrl) +{ + struct sn9c102_sensor *s = sn9c102_get_sensor(cam); + u8 data[2]; + int err = 0; + + switch (ctrl->id) { + case V4L2_CID_VFLIP: + if (sn9c102_i2c_try_raw_read(cam, s, s->i2c_slave_id, 0x20, 2, + data) < 0) + return -EIO; + ctrl->value = data[1] & 0x80 ? 1 : 0; + return 0; + default: + return -EINVAL; + } + + return err ? -EIO : 0; +} + +static int mt9v111_set_ctrl(struct sn9c102_device *cam, + const struct v4l2_control *ctrl) +{ + struct sn9c102_sensor *s = sn9c102_get_sensor(cam); + int err = 0; + + switch (ctrl->id) { + case V4L2_CID_VFLIP: + err += sn9c102_i2c_try_raw_write(cam, s, 4, s->i2c_slave_id, + 0x20, + ctrl->value ? 0x80 : 0x00, + ctrl->value ? 0x80 : 0x00, 0, + 0); + break; + default: + return -EINVAL; + } + + return err ? -EIO : 0; +} + +static int mt9v111_set_crop(struct sn9c102_device *cam, + const struct v4l2_rect *rect) +{ + struct sn9c102_sensor *s = sn9c102_get_sensor(cam); + int err = 0; + u8 v_start = (u8) (rect->top - s->cropcap.bounds.top) + 2; + + err += sn9c102_write_reg(cam, v_start, 0x13); + + return err; +} + +static int mt9v111_set_pix_format(struct sn9c102_device *cam, + const struct v4l2_pix_format *pix) +{ + int err = 0; + + if (pix->pixelformat == V4L2_PIX_FMT_SBGGR8) { + err += sn9c102_write_reg(cam, 0xb4, 0x17); + } else { + err += sn9c102_write_reg(cam, 0xe2, 0x17); + } + + return err; +} + + +static const struct sn9c102_sensor mt9v111 = { + .name = "MT9V111", + .maintainer = "Luca Risolia <luca.risolia@studio.unibo.it>", + .supported_bridge = BRIDGE_SN9C105 | BRIDGE_SN9C120, + .frequency = SN9C102_I2C_100KHZ, + .interface = SN9C102_I2C_2WIRES, + .i2c_slave_id = 0x5c, + .init = &mt9v111_init, + .qctrl = { + { + .id = V4L2_CID_VFLIP, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "vertical mirror", + .minimum = 0, + .maximum = 1, + .step = 1, + .default_value = 0, + .flags = 0, + }, + }, + .get_ctrl = &mt9v111_get_ctrl, + .set_ctrl = &mt9v111_set_ctrl, + .cropcap = { + .bounds = { + .left = 0, + .top = 0, + .width = 640, + .height = 480, + }, + .defrect = { + .left = 0, + .top = 0, + .width = 640, + .height = 480, + }, + }, + .set_crop = &mt9v111_set_crop, + .pix_format = { + .width = 640, + .height = 480, + .pixelformat = V4L2_PIX_FMT_SBGGR8, + .priv = 8, + }, + .set_pix_format = &mt9v111_set_pix_format +}; + + +int sn9c102_probe_mt9v111(struct sn9c102_device *cam) +{ + u8 data[2]; + int err = 0; + + err += sn9c102_write_const_regs(cam, {0x01, 0xf1}, {0x00, 0xf1}, + {0x29, 0x01}, {0x42, 0x17}, + {0x62, 0x17}, {0x08, 0x01}); + err += sn9c102_i2c_try_raw_write(cam, &mt9v111, 4, + mt9v111.i2c_slave_id, 0x01, 0x00, + 0x04, 0, 0); + if (err || sn9c102_i2c_try_raw_read(cam, &mt9v111, + mt9v111.i2c_slave_id, 0x36, 2, + data) < 0) + return -EIO; + + if (data[0] != 0x82 || data[1] != 0x3a) + return -ENODEV; + + sn9c102_attach_sensor(cam, &mt9v111); + + return 0; +} diff --git a/drivers/staging/media/sn9c102/sn9c102_ov7630.c b/drivers/staging/media/sn9c102/sn9c102_ov7630.c new file mode 100644 index 00000000000..9ec304dc470 --- /dev/null +++ b/drivers/staging/media/sn9c102/sn9c102_ov7630.c @@ -0,0 +1,634 @@ +/*************************************************************************** + * Plug-in for OV7630 image sensor connected to the SN9C1xx PC Camera * + * Controllers * + * * + * Copyright (C) 2006-2007 by Luca Risolia <luca.risolia@studio.unibo.it> * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the Free Software * + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * + ***************************************************************************/ + +#include "sn9c102_sensor.h" +#include "sn9c102_devtable.h" + + +static int ov7630_init(struct sn9c102_device *cam) +{ + int err = 0; + + switch (sn9c102_get_bridge(cam)) { + case BRIDGE_SN9C101: + case BRIDGE_SN9C102: + err = sn9c102_write_const_regs(cam, {0x00, 0x14}, {0x60, 0x17}, + {0x0f, 0x18}, {0x50, 0x19}); + + err += sn9c102_i2c_write(cam, 0x12, 0x8d); + err += sn9c102_i2c_write(cam, 0x12, 0x0d); + err += sn9c102_i2c_write(cam, 0x11, 0x00); + err += sn9c102_i2c_write(cam, 0x15, 0x35); + err += sn9c102_i2c_write(cam, 0x16, 0x03); + err += sn9c102_i2c_write(cam, 0x17, 0x1c); + err += sn9c102_i2c_write(cam, 0x18, 0xbd); + err += sn9c102_i2c_write(cam, 0x19, 0x06); + err += sn9c102_i2c_write(cam, 0x1a, 0xf6); + err += sn9c102_i2c_write(cam, 0x1b, 0x04); + err += sn9c102_i2c_write(cam, 0x20, 0x44); + err += sn9c102_i2c_write(cam, 0x23, 0xee); + err += sn9c102_i2c_write(cam, 0x26, 0xa0); + err += sn9c102_i2c_write(cam, 0x27, 0x9a); + err += sn9c102_i2c_write(cam, 0x28, 0x20); + err += sn9c102_i2c_write(cam, 0x29, 0x30); + err += sn9c102_i2c_write(cam, 0x2f, 0x3d); + err += sn9c102_i2c_write(cam, 0x30, 0x24); + err += sn9c102_i2c_write(cam, 0x32, 0x86); + err += sn9c102_i2c_write(cam, 0x60, 0xa9); + err += sn9c102_i2c_write(cam, 0x61, 0x42); + err += sn9c102_i2c_write(cam, 0x65, 0x00); + err += sn9c102_i2c_write(cam, 0x69, 0x38); + err += sn9c102_i2c_write(cam, 0x6f, 0x88); + err += sn9c102_i2c_write(cam, 0x70, 0x0b); + err += sn9c102_i2c_write(cam, 0x71, 0x00); + err += sn9c102_i2c_write(cam, 0x74, 0x21); + err += sn9c102_i2c_write(cam, 0x7d, 0xf7); + break; + case BRIDGE_SN9C103: + err = sn9c102_write_const_regs(cam, {0x00, 0x02}, {0x00, 0x03}, + {0x1a, 0x04}, {0x20, 0x05}, + {0x20, 0x06}, {0x20, 0x07}, + {0x03, 0x10}, {0x0a, 0x14}, + {0x60, 0x17}, {0x0f, 0x18}, + {0x50, 0x19}, {0x1d, 0x1a}, + {0x10, 0x1b}, {0x02, 0x1c}, + {0x03, 0x1d}, {0x0f, 0x1e}, + {0x0c, 0x1f}, {0x00, 0x20}, + {0x10, 0x21}, {0x20, 0x22}, + {0x30, 0x23}, {0x40, 0x24}, + {0x50, 0x25}, {0x60, 0x26}, + {0x70, 0x27}, {0x80, 0x28}, + {0x90, 0x29}, {0xa0, 0x2a}, + {0xb0, 0x2b}, {0xc0, 0x2c}, + {0xd0, 0x2d}, {0xe0, 0x2e}, + {0xf0, 0x2f}, {0xff, 0x30}); + + err += sn9c102_i2c_write(cam, 0x12, 0x8d); + err += sn9c102_i2c_write(cam, 0x12, 0x0d); + err += sn9c102_i2c_write(cam, 0x15, 0x34); + err += sn9c102_i2c_write(cam, 0x11, 0x01); + err += sn9c102_i2c_write(cam, 0x1b, 0x04); + err += sn9c102_i2c_write(cam, 0x20, 0x44); + err += sn9c102_i2c_write(cam, 0x23, 0xee); + err += sn9c102_i2c_write(cam, 0x26, 0xa0); + err += sn9c102_i2c_write(cam, 0x27, 0x9a); + err += sn9c102_i2c_write(cam, 0x28, 0x20); + err += sn9c102_i2c_write(cam, 0x29, 0x30); + err += sn9c102_i2c_write(cam, 0x2f, 0x3d); + err += sn9c102_i2c_write(cam, 0x30, 0x24); + err += sn9c102_i2c_write(cam, 0x32, 0x86); + err += sn9c102_i2c_write(cam, 0x60, 0xa9); + err += sn9c102_i2c_write(cam, 0x61, 0x42); + err += sn9c102_i2c_write(cam, 0x65, 0x00); + err += sn9c102_i2c_write(cam, 0x69, 0x38); + err += sn9c102_i2c_write(cam, 0x6f, 0x88); + err += sn9c102_i2c_write(cam, 0x70, 0x0b); + err += sn9c102_i2c_write(cam, 0x71, 0x00); + err += sn9c102_i2c_write(cam, 0x74, 0x21); + err += sn9c102_i2c_write(cam, 0x7d, 0xf7); + break; + case BRIDGE_SN9C105: + case BRIDGE_SN9C120: + err = sn9c102_write_const_regs(cam, {0x40, 0x02}, {0x00, 0x03}, + {0x1a, 0x04}, {0x03, 0x10}, + {0x0a, 0x14}, {0xe2, 0x17}, + {0x0b, 0x18}, {0x00, 0x19}, + {0x1d, 0x1a}, {0x10, 0x1b}, + {0x02, 0x1c}, {0x03, 0x1d}, + {0x0f, 0x1e}, {0x0c, 0x1f}, + {0x00, 0x20}, {0x24, 0x21}, + {0x3b, 0x22}, {0x47, 0x23}, + {0x60, 0x24}, {0x71, 0x25}, + {0x80, 0x26}, {0x8f, 0x27}, + {0x9d, 0x28}, {0xaa, 0x29}, + {0xb8, 0x2a}, {0xc4, 0x2b}, + {0xd1, 0x2c}, {0xdd, 0x2d}, + {0xe8, 0x2e}, {0xf4, 0x2f}, + {0xff, 0x30}, {0x00, 0x3f}, + {0xc7, 0x40}, {0x01, 0x41}, + {0x44, 0x42}, {0x00, 0x43}, + {0x44, 0x44}, {0x00, 0x45}, + {0x44, 0x46}, {0x00, 0x47}, + {0xc7, 0x48}, {0x01, 0x49}, + {0xc7, 0x4a}, {0x01, 0x4b}, + {0xc7, 0x4c}, {0x01, 0x4d}, + {0x44, 0x4e}, {0x00, 0x4f}, + {0x44, 0x50}, {0x00, 0x51}, + {0x44, 0x52}, {0x00, 0x53}, + {0xc7, 0x54}, {0x01, 0x55}, + {0xc7, 0x56}, {0x01, 0x57}, + {0xc7, 0x58}, {0x01, 0x59}, + {0x44, 0x5a}, {0x00, 0x5b}, + {0x44, 0x5c}, {0x00, 0x5d}, + {0x44, 0x5e}, {0x00, 0x5f}, + {0xc7, 0x60}, {0x01, 0x61}, + {0xc7, 0x62}, {0x01, 0x63}, + {0xc7, 0x64}, {0x01, 0x65}, + {0x44, 0x66}, {0x00, 0x67}, + {0x44, 0x68}, {0x00, 0x69}, + {0x44, 0x6a}, {0x00, 0x6b}, + {0xc7, 0x6c}, {0x01, 0x6d}, + {0xc7, 0x6e}, {0x01, 0x6f}, + {0xc7, 0x70}, {0x01, 0x71}, + {0x44, 0x72}, {0x00, 0x73}, + {0x44, 0x74}, {0x00, 0x75}, + {0x44, 0x76}, {0x00, 0x77}, + {0xc7, 0x78}, {0x01, 0x79}, + {0xc7, 0x7a}, {0x01, 0x7b}, + {0xc7, 0x7c}, {0x01, 0x7d}, + {0x44, 0x7e}, {0x00, 0x7f}, + {0x17, 0x84}, {0x00, 0x85}, + {0x2e, 0x86}, {0x00, 0x87}, + {0x09, 0x88}, {0x00, 0x89}, + {0xe8, 0x8a}, {0x0f, 0x8b}, + {0xda, 0x8c}, {0x0f, 0x8d}, + {0x40, 0x8e}, {0x00, 0x8f}, + {0x37, 0x90}, {0x00, 0x91}, + {0xcf, 0x92}, {0x0f, 0x93}, + {0xfa, 0x94}, {0x0f, 0x95}, + {0x00, 0x96}, {0x00, 0x97}, + {0x00, 0x98}, {0x66, 0x99}, + {0x00, 0x9a}, {0x40, 0x9b}, + {0x20, 0x9c}, {0x00, 0x9d}, + {0x00, 0x9e}, {0x00, 0x9f}, + {0x2d, 0xc0}, {0x2d, 0xc1}, + {0x3a, 0xc2}, {0x00, 0xc3}, + {0x04, 0xc4}, {0x3f, 0xc5}, + {0x00, 0xc6}, {0x00, 0xc7}, + {0x50, 0xc8}, {0x3c, 0xc9}, + {0x28, 0xca}, {0xd8, 0xcb}, + {0x14, 0xcc}, {0xec, 0xcd}, + {0x32, 0xce}, {0xdd, 0xcf}, + {0x32, 0xd0}, {0xdd, 0xd1}, + {0x6a, 0xd2}, {0x50, 0xd3}, + {0x60, 0xd4}, {0x00, 0xd5}, + {0x00, 0xd6}); + + err += sn9c102_i2c_write(cam, 0x12, 0x80); + err += sn9c102_i2c_write(cam, 0x12, 0x48); + err += sn9c102_i2c_write(cam, 0x01, 0x80); + err += sn9c102_i2c_write(cam, 0x02, 0x80); + err += sn9c102_i2c_write(cam, 0x03, 0x80); + err += sn9c102_i2c_write(cam, 0x04, 0x10); + err += sn9c102_i2c_write(cam, 0x05, 0x20); + err += sn9c102_i2c_write(cam, 0x06, 0x80); + err += sn9c102_i2c_write(cam, 0x11, 0x00); + err += sn9c102_i2c_write(cam, 0x0c, 0x20); + err += sn9c102_i2c_write(cam, 0x0d, 0x20); + err += sn9c102_i2c_write(cam, 0x15, 0x80); + err += sn9c102_i2c_write(cam, 0x16, 0x03); + err += sn9c102_i2c_write(cam, 0x17, 0x1b); + err += sn9c102_i2c_write(cam, 0x18, 0xbd); + err += sn9c102_i2c_write(cam, 0x19, 0x05); + err += sn9c102_i2c_write(cam, 0x1a, 0xf6); + err += sn9c102_i2c_write(cam, 0x1b, 0x04); + err += sn9c102_i2c_write(cam, 0x21, 0x1b); + err += sn9c102_i2c_write(cam, 0x22, 0x00); + err += sn9c102_i2c_write(cam, 0x23, 0xde); + err += sn9c102_i2c_write(cam, 0x24, 0x10); + err += sn9c102_i2c_write(cam, 0x25, 0x8a); + err += sn9c102_i2c_write(cam, 0x26, 0xa0); + err += sn9c102_i2c_write(cam, 0x27, 0xca); + err += sn9c102_i2c_write(cam, 0x28, 0xa2); + err += sn9c102_i2c_write(cam, 0x29, 0x74); + err += sn9c102_i2c_write(cam, 0x2a, 0x88); + err += sn9c102_i2c_write(cam, 0x2b, 0x34); + err += sn9c102_i2c_write(cam, 0x2c, 0x88); + err += sn9c102_i2c_write(cam, 0x2e, 0x00); + err += sn9c102_i2c_write(cam, 0x2f, 0x00); + err += sn9c102_i2c_write(cam, 0x30, 0x00); + err += sn9c102_i2c_write(cam, 0x32, 0xc2); + err += sn9c102_i2c_write(cam, 0x33, 0x08); + err += sn9c102_i2c_write(cam, 0x4c, 0x40); + err += sn9c102_i2c_write(cam, 0x4d, 0xf3); + err += sn9c102_i2c_write(cam, 0x60, 0x05); + err += sn9c102_i2c_write(cam, 0x61, 0x40); + err += sn9c102_i2c_write(cam, 0x62, 0x12); + err += sn9c102_i2c_write(cam, 0x63, 0x57); + err += sn9c102_i2c_write(cam, 0x64, 0x73); + err += sn9c102_i2c_write(cam, 0x65, 0x00); + err += sn9c102_i2c_write(cam, 0x66, 0x55); + err += sn9c102_i2c_write(cam, 0x67, 0x01); + err += sn9c102_i2c_write(cam, 0x68, 0xac); + err += sn9c102_i2c_write(cam, 0x69, 0x38); + err += sn9c102_i2c_write(cam, 0x6f, 0x1f); + err += sn9c102_i2c_write(cam, 0x70, 0x01); + err += sn9c102_i2c_write(cam, 0x71, 0x00); + err += sn9c102_i2c_write(cam, 0x72, 0x10); + err += sn9c102_i2c_write(cam, 0x73, 0x50); + err += sn9c102_i2c_write(cam, 0x74, 0x20); + err += sn9c102_i2c_write(cam, 0x76, 0x01); + err += sn9c102_i2c_write(cam, 0x77, 0xf3); + err += sn9c102_i2c_write(cam, 0x78, 0x90); + err += sn9c102_i2c_write(cam, 0x79, 0x98); + err += sn9c102_i2c_write(cam, 0x7a, 0x98); + err += sn9c102_i2c_write(cam, 0x7b, 0x00); + err += sn9c102_i2c_write(cam, 0x7c, 0x38); + err += sn9c102_i2c_write(cam, 0x7d, 0xff); + break; + default: + break; + } + + return err; +} + + +static int ov7630_get_ctrl(struct sn9c102_device *cam, + struct v4l2_control *ctrl) +{ + enum sn9c102_bridge bridge = sn9c102_get_bridge(cam); + int err = 0; + + switch (ctrl->id) { + case V4L2_CID_EXPOSURE: + ctrl->value = sn9c102_i2c_read(cam, 0x10); + if (ctrl->value < 0) + return -EIO; + break; + case V4L2_CID_RED_BALANCE: + if (bridge == BRIDGE_SN9C105 || bridge == BRIDGE_SN9C120) + ctrl->value = sn9c102_pread_reg(cam, 0x05); + else + ctrl->value = sn9c102_pread_reg(cam, 0x07); + break; + case V4L2_CID_BLUE_BALANCE: + ctrl->value = sn9c102_pread_reg(cam, 0x06); + break; + case SN9C102_V4L2_CID_GREEN_BALANCE: + if (bridge == BRIDGE_SN9C105 || bridge == BRIDGE_SN9C120) + ctrl->value = sn9c102_pread_reg(cam, 0x07); + else + ctrl->value = sn9c102_pread_reg(cam, 0x05); + break; + break; + case V4L2_CID_GAIN: + ctrl->value = sn9c102_i2c_read(cam, 0x00); + if (ctrl->value < 0) + return -EIO; + ctrl->value &= 0x3f; + break; + case V4L2_CID_DO_WHITE_BALANCE: + ctrl->value = sn9c102_i2c_read(cam, 0x0c); + if (ctrl->value < 0) + return -EIO; + ctrl->value &= 0x3f; + break; + case V4L2_CID_WHITENESS: + ctrl->value = sn9c102_i2c_read(cam, 0x0d); + if (ctrl->value < 0) + return -EIO; + ctrl->value &= 0x3f; + break; + case V4L2_CID_AUTOGAIN: + ctrl->value = sn9c102_i2c_read(cam, 0x13); + if (ctrl->value < 0) + return -EIO; + ctrl->value &= 0x01; + break; + case V4L2_CID_VFLIP: + ctrl->value = sn9c102_i2c_read(cam, 0x75); + if (ctrl->value < 0) + return -EIO; + ctrl->value = (ctrl->value & 0x80) ? 1 : 0; + break; + case SN9C102_V4L2_CID_GAMMA: + ctrl->value = sn9c102_i2c_read(cam, 0x14); + if (ctrl->value < 0) + return -EIO; + ctrl->value = (ctrl->value & 0x02) ? 1 : 0; + break; + case SN9C102_V4L2_CID_BAND_FILTER: + ctrl->value = sn9c102_i2c_read(cam, 0x2d); + if (ctrl->value < 0) + return -EIO; + ctrl->value = (ctrl->value & 0x02) ? 1 : 0; + break; + default: + return -EINVAL; + } + + return err ? -EIO : 0; +} + + +static int ov7630_set_ctrl(struct sn9c102_device *cam, + const struct v4l2_control *ctrl) +{ + enum sn9c102_bridge bridge = sn9c102_get_bridge(cam); + int err = 0; + + switch (ctrl->id) { + case V4L2_CID_EXPOSURE: + err += sn9c102_i2c_write(cam, 0x10, ctrl->value); + break; + case V4L2_CID_RED_BALANCE: + if (bridge == BRIDGE_SN9C105 || bridge == BRIDGE_SN9C120) + err += sn9c102_write_reg(cam, ctrl->value, 0x05); + else + err += sn9c102_write_reg(cam, ctrl->value, 0x07); + break; + case V4L2_CID_BLUE_BALANCE: + err += sn9c102_write_reg(cam, ctrl->value, 0x06); + break; + case SN9C102_V4L2_CID_GREEN_BALANCE: + if (bridge == BRIDGE_SN9C105 || bridge == BRIDGE_SN9C120) + err += sn9c102_write_reg(cam, ctrl->value, 0x07); + else + err += sn9c102_write_reg(cam, ctrl->value, 0x05); + break; + case V4L2_CID_GAIN: + err += sn9c102_i2c_write(cam, 0x00, ctrl->value); + break; + case V4L2_CID_DO_WHITE_BALANCE: + err += sn9c102_i2c_write(cam, 0x0c, ctrl->value); + break; + case V4L2_CID_WHITENESS: + err += sn9c102_i2c_write(cam, 0x0d, ctrl->value); + break; + case V4L2_CID_AUTOGAIN: + err += sn9c102_i2c_write(cam, 0x13, ctrl->value | + (ctrl->value << 1)); + break; + case V4L2_CID_VFLIP: + err += sn9c102_i2c_write(cam, 0x75, 0x0e | (ctrl->value << 7)); + break; + case SN9C102_V4L2_CID_GAMMA: + err += sn9c102_i2c_write(cam, 0x14, ctrl->value << 2); + break; + case SN9C102_V4L2_CID_BAND_FILTER: + err += sn9c102_i2c_write(cam, 0x2d, ctrl->value << 2); + break; + default: + return -EINVAL; + } + + return err ? -EIO : 0; +} + + +static int ov7630_set_crop(struct sn9c102_device *cam, + const struct v4l2_rect *rect) +{ + struct sn9c102_sensor *s = sn9c102_get_sensor(cam); + int err = 0; + u8 h_start = 0, v_start = (u8)(rect->top - s->cropcap.bounds.top) + 1; + + switch (sn9c102_get_bridge(cam)) { + case BRIDGE_SN9C101: + case BRIDGE_SN9C102: + case BRIDGE_SN9C103: + h_start = (u8)(rect->left - s->cropcap.bounds.left) + 1; + break; + case BRIDGE_SN9C105: + case BRIDGE_SN9C120: + h_start = (u8)(rect->left - s->cropcap.bounds.left) + 4; + break; + default: + break; + } + + err += sn9c102_write_reg(cam, h_start, 0x12); + err += sn9c102_write_reg(cam, v_start, 0x13); + + return err; +} + + +static int ov7630_set_pix_format(struct sn9c102_device *cam, + const struct v4l2_pix_format *pix) +{ + int err = 0; + + switch (sn9c102_get_bridge(cam)) { + case BRIDGE_SN9C101: + case BRIDGE_SN9C102: + case BRIDGE_SN9C103: + if (pix->pixelformat == V4L2_PIX_FMT_SBGGR8) + err += sn9c102_write_reg(cam, 0x50, 0x19); + else + err += sn9c102_write_reg(cam, 0x20, 0x19); + break; + case BRIDGE_SN9C105: + case BRIDGE_SN9C120: + if (pix->pixelformat == V4L2_PIX_FMT_SBGGR8) { + err += sn9c102_write_reg(cam, 0xe5, 0x17); + err += sn9c102_i2c_write(cam, 0x11, 0x04); + } else { + err += sn9c102_write_reg(cam, 0xe2, 0x17); + err += sn9c102_i2c_write(cam, 0x11, 0x02); + } + break; + default: + break; + } + + return err; +} + + +static const struct sn9c102_sensor ov7630 = { + .name = "OV7630", + .maintainer = "Luca Risolia <luca.risolia@studio.unibo.it>", + .supported_bridge = BRIDGE_SN9C101 | BRIDGE_SN9C102 | BRIDGE_SN9C103 | + BRIDGE_SN9C105 | BRIDGE_SN9C120, + .sysfs_ops = SN9C102_I2C_READ | SN9C102_I2C_WRITE, + .frequency = SN9C102_I2C_100KHZ, + .interface = SN9C102_I2C_2WIRES, + .i2c_slave_id = 0x21, + .init = &ov7630_init, + .qctrl = { + { + .id = V4L2_CID_GAIN, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "global gain", + .minimum = 0x00, + .maximum = 0x3f, + .step = 0x01, + .default_value = 0x14, + .flags = 0, + }, + { + .id = V4L2_CID_EXPOSURE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "exposure", + .minimum = 0x00, + .maximum = 0xff, + .step = 0x01, + .default_value = 0x60, + .flags = 0, + }, + { + .id = V4L2_CID_WHITENESS, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "white balance background: red", + .minimum = 0x00, + .maximum = 0x3f, + .step = 0x01, + .default_value = 0x20, + .flags = 0, + }, + { + .id = V4L2_CID_DO_WHITE_BALANCE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "white balance background: blue", + .minimum = 0x00, + .maximum = 0x3f, + .step = 0x01, + .default_value = 0x20, + .flags = 0, + }, + { + .id = V4L2_CID_RED_BALANCE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "red balance", + .minimum = 0x00, + .maximum = 0x7f, + .step = 0x01, + .default_value = 0x20, + .flags = 0, + }, + { + .id = V4L2_CID_BLUE_BALANCE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "blue balance", + .minimum = 0x00, + .maximum = 0x7f, + .step = 0x01, + .default_value = 0x20, + .flags = 0, + }, + { + .id = V4L2_CID_AUTOGAIN, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "auto adjust", + .minimum = 0x00, + .maximum = 0x01, + .step = 0x01, + .default_value = 0x00, + .flags = 0, + }, + { + .id = V4L2_CID_VFLIP, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "vertical flip", + .minimum = 0x00, + .maximum = 0x01, + .step = 0x01, + .default_value = 0x01, + .flags = 0, + }, + { + .id = SN9C102_V4L2_CID_GREEN_BALANCE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "green balance", + .minimum = 0x00, + .maximum = 0x7f, + .step = 0x01, + .default_value = 0x20, + .flags = 0, + }, + { + .id = SN9C102_V4L2_CID_BAND_FILTER, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "band filter", + .minimum = 0x00, + .maximum = 0x01, + .step = 0x01, + .default_value = 0x00, + .flags = 0, + }, + { + .id = SN9C102_V4L2_CID_GAMMA, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "rgb gamma", + .minimum = 0x00, + .maximum = 0x01, + .step = 0x01, + .default_value = 0x00, + .flags = 0, + }, + }, + .get_ctrl = &ov7630_get_ctrl, + .set_ctrl = &ov7630_set_ctrl, + .cropcap = { + .bounds = { + .left = 0, + .top = 0, + .width = 640, + .height = 480, + }, + .defrect = { + .left = 0, + .top = 0, + .width = 640, + .height = 480, + }, + }, + .set_crop = &ov7630_set_crop, + .pix_format = { + .width = 640, + .height = 480, + .pixelformat = V4L2_PIX_FMT_SN9C10X, + .priv = 8, + }, + .set_pix_format = &ov7630_set_pix_format +}; + + +int sn9c102_probe_ov7630(struct sn9c102_device *cam) +{ + int pid, ver, err = 0; + + switch (sn9c102_get_bridge(cam)) { + case BRIDGE_SN9C101: + case BRIDGE_SN9C102: + err = sn9c102_write_const_regs(cam, {0x01, 0x01}, {0x00, 0x01}, + {0x28, 0x17}); + break; + case BRIDGE_SN9C103: /* do _not_ change anything! */ + err = sn9c102_write_const_regs(cam, {0x09, 0x01}, {0x42, 0x01}, + {0x28, 0x17}, {0x44, 0x02}); + pid = sn9c102_i2c_try_read(cam, &ov7630, 0x0a); + if (err || pid < 0) /* try a different initialization */ + err += sn9c102_write_const_regs(cam, {0x01, 0x01}, + {0x00, 0x01}); + break; + case BRIDGE_SN9C105: + case BRIDGE_SN9C120: + err = sn9c102_write_const_regs(cam, {0x01, 0xf1}, {0x00, 0xf1}, + {0x29, 0x01}, {0x74, 0x02}, + {0x0e, 0x01}, {0x44, 0x01}); + break; + default: + break; + } + + pid = sn9c102_i2c_try_read(cam, &ov7630, 0x0a); + ver = sn9c102_i2c_try_read(cam, &ov7630, 0x0b); + if (err || pid < 0 || ver < 0) + return -EIO; + if (pid != 0x76 || ver != 0x31) + return -ENODEV; + sn9c102_attach_sensor(cam, &ov7630); + + return 0; +} diff --git a/drivers/staging/media/sn9c102/sn9c102_ov7660.c b/drivers/staging/media/sn9c102/sn9c102_ov7660.c new file mode 100644 index 00000000000..ac07805d122 --- /dev/null +++ b/drivers/staging/media/sn9c102/sn9c102_ov7660.c @@ -0,0 +1,546 @@ +/*************************************************************************** + * Plug-in for OV7660 image sensor connected to the SN9C1xx PC Camera * + * Controllers * + * * + * Copyright (C) 2007 by Luca Risolia <luca.risolia@studio.unibo.it> * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the Free Software * + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * + ***************************************************************************/ + +#include "sn9c102_sensor.h" +#include "sn9c102_devtable.h" + + +static int ov7660_init(struct sn9c102_device *cam) +{ + int err = 0; + + err = sn9c102_write_const_regs(cam, {0x40, 0x02}, {0x00, 0x03}, + {0x1a, 0x04}, {0x03, 0x10}, + {0x08, 0x14}, {0x20, 0x17}, + {0x8b, 0x18}, {0x00, 0x19}, + {0x1d, 0x1a}, {0x10, 0x1b}, + {0x02, 0x1c}, {0x03, 0x1d}, + {0x0f, 0x1e}, {0x0c, 0x1f}, + {0x00, 0x20}, {0x29, 0x21}, + {0x40, 0x22}, {0x54, 0x23}, + {0x66, 0x24}, {0x76, 0x25}, + {0x85, 0x26}, {0x94, 0x27}, + {0xa1, 0x28}, {0xae, 0x29}, + {0xbb, 0x2a}, {0xc7, 0x2b}, + {0xd3, 0x2c}, {0xde, 0x2d}, + {0xea, 0x2e}, {0xf4, 0x2f}, + {0xff, 0x30}, {0x00, 0x3f}, + {0xc7, 0x40}, {0x01, 0x41}, + {0x44, 0x42}, {0x00, 0x43}, + {0x44, 0x44}, {0x00, 0x45}, + {0x44, 0x46}, {0x00, 0x47}, + {0xc7, 0x48}, {0x01, 0x49}, + {0xc7, 0x4a}, {0x01, 0x4b}, + {0xc7, 0x4c}, {0x01, 0x4d}, + {0x44, 0x4e}, {0x00, 0x4f}, + {0x44, 0x50}, {0x00, 0x51}, + {0x44, 0x52}, {0x00, 0x53}, + {0xc7, 0x54}, {0x01, 0x55}, + {0xc7, 0x56}, {0x01, 0x57}, + {0xc7, 0x58}, {0x01, 0x59}, + {0x44, 0x5a}, {0x00, 0x5b}, + {0x44, 0x5c}, {0x00, 0x5d}, + {0x44, 0x5e}, {0x00, 0x5f}, + {0xc7, 0x60}, {0x01, 0x61}, + {0xc7, 0x62}, {0x01, 0x63}, + {0xc7, 0x64}, {0x01, 0x65}, + {0x44, 0x66}, {0x00, 0x67}, + {0x44, 0x68}, {0x00, 0x69}, + {0x44, 0x6a}, {0x00, 0x6b}, + {0xc7, 0x6c}, {0x01, 0x6d}, + {0xc7, 0x6e}, {0x01, 0x6f}, + {0xc7, 0x70}, {0x01, 0x71}, + {0x44, 0x72}, {0x00, 0x73}, + {0x44, 0x74}, {0x00, 0x75}, + {0x44, 0x76}, {0x00, 0x77}, + {0xc7, 0x78}, {0x01, 0x79}, + {0xc7, 0x7a}, {0x01, 0x7b}, + {0xc7, 0x7c}, {0x01, 0x7d}, + {0x44, 0x7e}, {0x00, 0x7f}, + {0x14, 0x84}, {0x00, 0x85}, + {0x27, 0x86}, {0x00, 0x87}, + {0x07, 0x88}, {0x00, 0x89}, + {0xec, 0x8a}, {0x0f, 0x8b}, + {0xd8, 0x8c}, {0x0f, 0x8d}, + {0x3d, 0x8e}, {0x00, 0x8f}, + {0x3d, 0x90}, {0x00, 0x91}, + {0xcd, 0x92}, {0x0f, 0x93}, + {0xf7, 0x94}, {0x0f, 0x95}, + {0x0c, 0x96}, {0x00, 0x97}, + {0x00, 0x98}, {0x66, 0x99}, + {0x05, 0x9a}, {0x00, 0x9b}, + {0x04, 0x9c}, {0x00, 0x9d}, + {0x08, 0x9e}, {0x00, 0x9f}, + {0x2d, 0xc0}, {0x2d, 0xc1}, + {0x3a, 0xc2}, {0x05, 0xc3}, + {0x04, 0xc4}, {0x3f, 0xc5}, + {0x00, 0xc6}, {0x00, 0xc7}, + {0x50, 0xc8}, {0x3C, 0xc9}, + {0x28, 0xca}, {0xd8, 0xcb}, + {0x14, 0xcc}, {0xec, 0xcd}, + {0x32, 0xce}, {0xdd, 0xcf}, + {0x32, 0xd0}, {0xdd, 0xd1}, + {0x6a, 0xd2}, {0x50, 0xd3}, + {0x00, 0xd4}, {0x00, 0xd5}, + {0x00, 0xd6}); + + err += sn9c102_i2c_write(cam, 0x12, 0x80); + err += sn9c102_i2c_write(cam, 0x11, 0x09); + err += sn9c102_i2c_write(cam, 0x00, 0x0A); + err += sn9c102_i2c_write(cam, 0x01, 0x80); + err += sn9c102_i2c_write(cam, 0x02, 0x80); + err += sn9c102_i2c_write(cam, 0x03, 0x00); + err += sn9c102_i2c_write(cam, 0x04, 0x00); + err += sn9c102_i2c_write(cam, 0x05, 0x08); + err += sn9c102_i2c_write(cam, 0x06, 0x0B); + err += sn9c102_i2c_write(cam, 0x07, 0x00); + err += sn9c102_i2c_write(cam, 0x08, 0x1C); + err += sn9c102_i2c_write(cam, 0x09, 0x01); + err += sn9c102_i2c_write(cam, 0x0A, 0x76); + err += sn9c102_i2c_write(cam, 0x0B, 0x60); + err += sn9c102_i2c_write(cam, 0x0C, 0x00); + err += sn9c102_i2c_write(cam, 0x0D, 0x08); + err += sn9c102_i2c_write(cam, 0x0E, 0x04); + err += sn9c102_i2c_write(cam, 0x0F, 0x6F); + err += sn9c102_i2c_write(cam, 0x10, 0x20); + err += sn9c102_i2c_write(cam, 0x11, 0x03); + err += sn9c102_i2c_write(cam, 0x12, 0x05); + err += sn9c102_i2c_write(cam, 0x13, 0xC7); + err += sn9c102_i2c_write(cam, 0x14, 0x2C); + err += sn9c102_i2c_write(cam, 0x15, 0x00); + err += sn9c102_i2c_write(cam, 0x16, 0x02); + err += sn9c102_i2c_write(cam, 0x17, 0x10); + err += sn9c102_i2c_write(cam, 0x18, 0x60); + err += sn9c102_i2c_write(cam, 0x19, 0x02); + err += sn9c102_i2c_write(cam, 0x1A, 0x7B); + err += sn9c102_i2c_write(cam, 0x1B, 0x02); + err += sn9c102_i2c_write(cam, 0x1C, 0x7F); + err += sn9c102_i2c_write(cam, 0x1D, 0xA2); + err += sn9c102_i2c_write(cam, 0x1E, 0x01); + err += sn9c102_i2c_write(cam, 0x1F, 0x0E); + err += sn9c102_i2c_write(cam, 0x20, 0x05); + err += sn9c102_i2c_write(cam, 0x21, 0x05); + err += sn9c102_i2c_write(cam, 0x22, 0x05); + err += sn9c102_i2c_write(cam, 0x23, 0x05); + err += sn9c102_i2c_write(cam, 0x24, 0x68); + err += sn9c102_i2c_write(cam, 0x25, 0x58); + err += sn9c102_i2c_write(cam, 0x26, 0xD4); + err += sn9c102_i2c_write(cam, 0x27, 0x80); + err += sn9c102_i2c_write(cam, 0x28, 0x80); + err += sn9c102_i2c_write(cam, 0x29, 0x30); + err += sn9c102_i2c_write(cam, 0x2A, 0x00); + err += sn9c102_i2c_write(cam, 0x2B, 0x00); + err += sn9c102_i2c_write(cam, 0x2C, 0x80); + err += sn9c102_i2c_write(cam, 0x2D, 0x00); + err += sn9c102_i2c_write(cam, 0x2E, 0x00); + err += sn9c102_i2c_write(cam, 0x2F, 0x0E); + err += sn9c102_i2c_write(cam, 0x30, 0x08); + err += sn9c102_i2c_write(cam, 0x31, 0x30); + err += sn9c102_i2c_write(cam, 0x32, 0xB4); + err += sn9c102_i2c_write(cam, 0x33, 0x00); + err += sn9c102_i2c_write(cam, 0x34, 0x07); + err += sn9c102_i2c_write(cam, 0x35, 0x84); + err += sn9c102_i2c_write(cam, 0x36, 0x00); + err += sn9c102_i2c_write(cam, 0x37, 0x0C); + err += sn9c102_i2c_write(cam, 0x38, 0x02); + err += sn9c102_i2c_write(cam, 0x39, 0x43); + err += sn9c102_i2c_write(cam, 0x3A, 0x00); + err += sn9c102_i2c_write(cam, 0x3B, 0x0A); + err += sn9c102_i2c_write(cam, 0x3C, 0x6C); + err += sn9c102_i2c_write(cam, 0x3D, 0x99); + err += sn9c102_i2c_write(cam, 0x3E, 0x0E); + err += sn9c102_i2c_write(cam, 0x3F, 0x41); + err += sn9c102_i2c_write(cam, 0x40, 0xC1); + err += sn9c102_i2c_write(cam, 0x41, 0x22); + err += sn9c102_i2c_write(cam, 0x42, 0x08); + err += sn9c102_i2c_write(cam, 0x43, 0xF0); + err += sn9c102_i2c_write(cam, 0x44, 0x10); + err += sn9c102_i2c_write(cam, 0x45, 0x78); + err += sn9c102_i2c_write(cam, 0x46, 0xA8); + err += sn9c102_i2c_write(cam, 0x47, 0x60); + err += sn9c102_i2c_write(cam, 0x48, 0x80); + err += sn9c102_i2c_write(cam, 0x49, 0x00); + err += sn9c102_i2c_write(cam, 0x4A, 0x00); + err += sn9c102_i2c_write(cam, 0x4B, 0x00); + err += sn9c102_i2c_write(cam, 0x4C, 0x00); + err += sn9c102_i2c_write(cam, 0x4D, 0x00); + err += sn9c102_i2c_write(cam, 0x4E, 0x00); + err += sn9c102_i2c_write(cam, 0x4F, 0x46); + err += sn9c102_i2c_write(cam, 0x50, 0x36); + err += sn9c102_i2c_write(cam, 0x51, 0x0F); + err += sn9c102_i2c_write(cam, 0x52, 0x17); + err += sn9c102_i2c_write(cam, 0x53, 0x7F); + err += sn9c102_i2c_write(cam, 0x54, 0x96); + err += sn9c102_i2c_write(cam, 0x55, 0x40); + err += sn9c102_i2c_write(cam, 0x56, 0x40); + err += sn9c102_i2c_write(cam, 0x57, 0x40); + err += sn9c102_i2c_write(cam, 0x58, 0x0F); + err += sn9c102_i2c_write(cam, 0x59, 0xBA); + err += sn9c102_i2c_write(cam, 0x5A, 0x9A); + err += sn9c102_i2c_write(cam, 0x5B, 0x22); + err += sn9c102_i2c_write(cam, 0x5C, 0xB9); + err += sn9c102_i2c_write(cam, 0x5D, 0x9B); + err += sn9c102_i2c_write(cam, 0x5E, 0x10); + err += sn9c102_i2c_write(cam, 0x5F, 0xF0); + err += sn9c102_i2c_write(cam, 0x60, 0x05); + err += sn9c102_i2c_write(cam, 0x61, 0x60); + err += sn9c102_i2c_write(cam, 0x62, 0x00); + err += sn9c102_i2c_write(cam, 0x63, 0x00); + err += sn9c102_i2c_write(cam, 0x64, 0x50); + err += sn9c102_i2c_write(cam, 0x65, 0x30); + err += sn9c102_i2c_write(cam, 0x66, 0x00); + err += sn9c102_i2c_write(cam, 0x67, 0x80); + err += sn9c102_i2c_write(cam, 0x68, 0x7A); + err += sn9c102_i2c_write(cam, 0x69, 0x90); + err += sn9c102_i2c_write(cam, 0x6A, 0x80); + err += sn9c102_i2c_write(cam, 0x6B, 0x0A); + err += sn9c102_i2c_write(cam, 0x6C, 0x30); + err += sn9c102_i2c_write(cam, 0x6D, 0x48); + err += sn9c102_i2c_write(cam, 0x6E, 0x80); + err += sn9c102_i2c_write(cam, 0x6F, 0x74); + err += sn9c102_i2c_write(cam, 0x70, 0x64); + err += sn9c102_i2c_write(cam, 0x71, 0x60); + err += sn9c102_i2c_write(cam, 0x72, 0x5C); + err += sn9c102_i2c_write(cam, 0x73, 0x58); + err += sn9c102_i2c_write(cam, 0x74, 0x54); + err += sn9c102_i2c_write(cam, 0x75, 0x4C); + err += sn9c102_i2c_write(cam, 0x76, 0x40); + err += sn9c102_i2c_write(cam, 0x77, 0x38); + err += sn9c102_i2c_write(cam, 0x78, 0x34); + err += sn9c102_i2c_write(cam, 0x79, 0x30); + err += sn9c102_i2c_write(cam, 0x7A, 0x2F); + err += sn9c102_i2c_write(cam, 0x7B, 0x2B); + err += sn9c102_i2c_write(cam, 0x7C, 0x03); + err += sn9c102_i2c_write(cam, 0x7D, 0x07); + err += sn9c102_i2c_write(cam, 0x7E, 0x17); + err += sn9c102_i2c_write(cam, 0x7F, 0x34); + err += sn9c102_i2c_write(cam, 0x80, 0x41); + err += sn9c102_i2c_write(cam, 0x81, 0x4D); + err += sn9c102_i2c_write(cam, 0x82, 0x58); + err += sn9c102_i2c_write(cam, 0x83, 0x63); + err += sn9c102_i2c_write(cam, 0x84, 0x6E); + err += sn9c102_i2c_write(cam, 0x85, 0x77); + err += sn9c102_i2c_write(cam, 0x86, 0x87); + err += sn9c102_i2c_write(cam, 0x87, 0x95); + err += sn9c102_i2c_write(cam, 0x88, 0xAF); + err += sn9c102_i2c_write(cam, 0x89, 0xC7); + err += sn9c102_i2c_write(cam, 0x8A, 0xDF); + err += sn9c102_i2c_write(cam, 0x8B, 0x99); + err += sn9c102_i2c_write(cam, 0x8C, 0x99); + err += sn9c102_i2c_write(cam, 0x8D, 0xCF); + err += sn9c102_i2c_write(cam, 0x8E, 0x20); + err += sn9c102_i2c_write(cam, 0x8F, 0x26); + err += sn9c102_i2c_write(cam, 0x90, 0x10); + err += sn9c102_i2c_write(cam, 0x91, 0x0C); + err += sn9c102_i2c_write(cam, 0x92, 0x25); + err += sn9c102_i2c_write(cam, 0x93, 0x00); + err += sn9c102_i2c_write(cam, 0x94, 0x50); + err += sn9c102_i2c_write(cam, 0x95, 0x50); + err += sn9c102_i2c_write(cam, 0x96, 0x00); + err += sn9c102_i2c_write(cam, 0x97, 0x01); + err += sn9c102_i2c_write(cam, 0x98, 0x10); + err += sn9c102_i2c_write(cam, 0x99, 0x40); + err += sn9c102_i2c_write(cam, 0x9A, 0x40); + err += sn9c102_i2c_write(cam, 0x9B, 0x20); + err += sn9c102_i2c_write(cam, 0x9C, 0x00); + err += sn9c102_i2c_write(cam, 0x9D, 0x99); + err += sn9c102_i2c_write(cam, 0x9E, 0x7F); + err += sn9c102_i2c_write(cam, 0x9F, 0x00); + err += sn9c102_i2c_write(cam, 0xA0, 0x00); + err += sn9c102_i2c_write(cam, 0xA1, 0x00); + + return err; +} + + +static int ov7660_get_ctrl(struct sn9c102_device *cam, + struct v4l2_control *ctrl) +{ + int err = 0; + + switch (ctrl->id) { + case V4L2_CID_EXPOSURE: + ctrl->value = sn9c102_i2c_read(cam, 0x10); + if (ctrl->value < 0) + return -EIO; + break; + case V4L2_CID_DO_WHITE_BALANCE: + ctrl->value = sn9c102_read_reg(cam, 0x02); + if (ctrl->value < 0) + return -EIO; + ctrl->value = (ctrl->value & 0x04) ? 1 : 0; + break; + case V4L2_CID_RED_BALANCE: + ctrl->value = sn9c102_read_reg(cam, 0x05); + if (ctrl->value < 0) + return -EIO; + ctrl->value &= 0x7f; + break; + case V4L2_CID_BLUE_BALANCE: + ctrl->value = sn9c102_read_reg(cam, 0x06); + if (ctrl->value < 0) + return -EIO; + ctrl->value &= 0x7f; + break; + case SN9C102_V4L2_CID_GREEN_BALANCE: + ctrl->value = sn9c102_read_reg(cam, 0x07); + if (ctrl->value < 0) + return -EIO; + ctrl->value &= 0x7f; + break; + case SN9C102_V4L2_CID_BAND_FILTER: + ctrl->value = sn9c102_i2c_read(cam, 0x3b); + if (ctrl->value < 0) + return -EIO; + ctrl->value &= 0x08; + break; + case V4L2_CID_GAIN: + ctrl->value = sn9c102_i2c_read(cam, 0x00); + if (ctrl->value < 0) + return -EIO; + ctrl->value &= 0x1f; + break; + case V4L2_CID_AUTOGAIN: + ctrl->value = sn9c102_i2c_read(cam, 0x13); + if (ctrl->value < 0) + return -EIO; + ctrl->value &= 0x01; + break; + default: + return -EINVAL; + } + + return err ? -EIO : 0; +} + + +static int ov7660_set_ctrl(struct sn9c102_device *cam, + const struct v4l2_control *ctrl) +{ + int err = 0; + + switch (ctrl->id) { + case V4L2_CID_EXPOSURE: + err += sn9c102_i2c_write(cam, 0x10, ctrl->value); + break; + case V4L2_CID_DO_WHITE_BALANCE: + err += sn9c102_write_reg(cam, 0x43 | (ctrl->value << 2), 0x02); + break; + case V4L2_CID_RED_BALANCE: + err += sn9c102_write_reg(cam, ctrl->value, 0x05); + break; + case V4L2_CID_BLUE_BALANCE: + err += sn9c102_write_reg(cam, ctrl->value, 0x06); + break; + case SN9C102_V4L2_CID_GREEN_BALANCE: + err += sn9c102_write_reg(cam, ctrl->value, 0x07); + break; + case SN9C102_V4L2_CID_BAND_FILTER: + err += sn9c102_i2c_write(cam, ctrl->value << 3, 0x3b); + break; + case V4L2_CID_GAIN: + err += sn9c102_i2c_write(cam, 0x00, 0x60 + ctrl->value); + break; + case V4L2_CID_AUTOGAIN: + err += sn9c102_i2c_write(cam, 0x13, 0xc0 | + (ctrl->value * 0x07)); + break; + default: + return -EINVAL; + } + + return err ? -EIO : 0; +} + + +static int ov7660_set_crop(struct sn9c102_device *cam, + const struct v4l2_rect *rect) +{ + struct sn9c102_sensor *s = sn9c102_get_sensor(cam); + int err = 0; + u8 h_start = (u8)(rect->left - s->cropcap.bounds.left) + 1, + v_start = (u8)(rect->top - s->cropcap.bounds.top) + 1; + + err += sn9c102_write_reg(cam, h_start, 0x12); + err += sn9c102_write_reg(cam, v_start, 0x13); + + return err; +} + + +static int ov7660_set_pix_format(struct sn9c102_device *cam, + const struct v4l2_pix_format *pix) +{ + int r0, err = 0; + + r0 = sn9c102_pread_reg(cam, 0x01); + + if (pix->pixelformat == V4L2_PIX_FMT_JPEG) { + err += sn9c102_write_reg(cam, r0 | 0x40, 0x01); + err += sn9c102_write_reg(cam, 0xa2, 0x17); + err += sn9c102_i2c_write(cam, 0x11, 0x00); + } else { + err += sn9c102_write_reg(cam, r0 | 0x40, 0x01); + err += sn9c102_write_reg(cam, 0xa2, 0x17); + err += sn9c102_i2c_write(cam, 0x11, 0x0d); + } + + return err; +} + + +static const struct sn9c102_sensor ov7660 = { + .name = "OV7660", + .maintainer = "Luca Risolia <luca.risolia@studio.unibo.it>", + .supported_bridge = BRIDGE_SN9C105 | BRIDGE_SN9C120, + .sysfs_ops = SN9C102_I2C_READ | SN9C102_I2C_WRITE, + .frequency = SN9C102_I2C_100KHZ, + .interface = SN9C102_I2C_2WIRES, + .i2c_slave_id = 0x21, + .init = &ov7660_init, + .qctrl = { + { + .id = V4L2_CID_GAIN, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "global gain", + .minimum = 0x00, + .maximum = 0x1f, + .step = 0x01, + .default_value = 0x09, + .flags = 0, + }, + { + .id = V4L2_CID_EXPOSURE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "exposure", + .minimum = 0x00, + .maximum = 0xff, + .step = 0x01, + .default_value = 0x27, + .flags = 0, + }, + { + .id = V4L2_CID_DO_WHITE_BALANCE, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "night mode", + .minimum = 0x00, + .maximum = 0x01, + .step = 0x01, + .default_value = 0x00, + .flags = 0, + }, + { + .id = V4L2_CID_RED_BALANCE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "red balance", + .minimum = 0x00, + .maximum = 0x7f, + .step = 0x01, + .default_value = 0x14, + .flags = 0, + }, + { + .id = V4L2_CID_BLUE_BALANCE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "blue balance", + .minimum = 0x00, + .maximum = 0x7f, + .step = 0x01, + .default_value = 0x14, + .flags = 0, + }, + { + .id = V4L2_CID_AUTOGAIN, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "auto adjust", + .minimum = 0x00, + .maximum = 0x01, + .step = 0x01, + .default_value = 0x01, + .flags = 0, + }, + { + .id = SN9C102_V4L2_CID_GREEN_BALANCE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "green balance", + .minimum = 0x00, + .maximum = 0x7f, + .step = 0x01, + .default_value = 0x14, + .flags = 0, + }, + { + .id = SN9C102_V4L2_CID_BAND_FILTER, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "band filter", + .minimum = 0x00, + .maximum = 0x01, + .step = 0x01, + .default_value = 0x00, + .flags = 0, + }, + }, + .get_ctrl = &ov7660_get_ctrl, + .set_ctrl = &ov7660_set_ctrl, + .cropcap = { + .bounds = { + .left = 0, + .top = 0, + .width = 640, + .height = 480, + }, + .defrect = { + .left = 0, + .top = 0, + .width = 640, + .height = 480, + }, + }, + .set_crop = &ov7660_set_crop, + .pix_format = { + .width = 640, + .height = 480, + .pixelformat = V4L2_PIX_FMT_JPEG, + .priv = 8, + }, + .set_pix_format = &ov7660_set_pix_format +}; + + +int sn9c102_probe_ov7660(struct sn9c102_device *cam) +{ + int pid, ver, err; + + err = sn9c102_write_const_regs(cam, {0x01, 0xf1}, {0x00, 0xf1}, + {0x01, 0x01}, {0x00, 0x01}, + {0x28, 0x17}); + + pid = sn9c102_i2c_try_read(cam, &ov7660, 0x0a); + ver = sn9c102_i2c_try_read(cam, &ov7660, 0x0b); + if (err || pid < 0 || ver < 0) + return -EIO; + if (pid != 0x76 || ver != 0x60) + return -ENODEV; + + sn9c102_attach_sensor(cam, &ov7660); + + return 0; +} diff --git a/drivers/staging/media/sn9c102/sn9c102_pas106b.c b/drivers/staging/media/sn9c102/sn9c102_pas106b.c new file mode 100644 index 00000000000..895931ecac4 --- /dev/null +++ b/drivers/staging/media/sn9c102/sn9c102_pas106b.c @@ -0,0 +1,308 @@ +/*************************************************************************** + * Plug-in for PAS106B image sensor connected to the SN9C1xx PC Camera * + * Controllers * + * * + * Copyright (C) 2004-2007 by Luca Risolia <luca.risolia@studio.unibo.it> * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the Free Software * + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * + ***************************************************************************/ + +#include <linux/delay.h> +#include "sn9c102_sensor.h" +#include "sn9c102_devtable.h" + + +static int pas106b_init(struct sn9c102_device *cam) +{ + int err = 0; + + err = sn9c102_write_const_regs(cam, {0x00, 0x10}, {0x00, 0x11}, + {0x00, 0x14}, {0x20, 0x17}, + {0x20, 0x19}, {0x09, 0x18}); + + err += sn9c102_i2c_write(cam, 0x02, 0x0c); + err += sn9c102_i2c_write(cam, 0x05, 0x5a); + err += sn9c102_i2c_write(cam, 0x06, 0x88); + err += sn9c102_i2c_write(cam, 0x07, 0x80); + err += sn9c102_i2c_write(cam, 0x10, 0x06); + err += sn9c102_i2c_write(cam, 0x11, 0x06); + err += sn9c102_i2c_write(cam, 0x12, 0x00); + err += sn9c102_i2c_write(cam, 0x14, 0x02); + err += sn9c102_i2c_write(cam, 0x13, 0x01); + + msleep(400); + + return err; +} + + +static int pas106b_get_ctrl(struct sn9c102_device *cam, + struct v4l2_control *ctrl) +{ + switch (ctrl->id) { + case V4L2_CID_EXPOSURE: + { + int r1 = sn9c102_i2c_read(cam, 0x03), + r2 = sn9c102_i2c_read(cam, 0x04); + if (r1 < 0 || r2 < 0) + return -EIO; + ctrl->value = (r1 << 4) | (r2 & 0x0f); + } + return 0; + case V4L2_CID_RED_BALANCE: + ctrl->value = sn9c102_i2c_read(cam, 0x0c); + if (ctrl->value < 0) + return -EIO; + ctrl->value &= 0x1f; + return 0; + case V4L2_CID_BLUE_BALANCE: + ctrl->value = sn9c102_i2c_read(cam, 0x09); + if (ctrl->value < 0) + return -EIO; + ctrl->value &= 0x1f; + return 0; + case V4L2_CID_GAIN: + ctrl->value = sn9c102_i2c_read(cam, 0x0e); + if (ctrl->value < 0) + return -EIO; + ctrl->value &= 0x1f; + return 0; + case V4L2_CID_CONTRAST: + ctrl->value = sn9c102_i2c_read(cam, 0x0f); + if (ctrl->value < 0) + return -EIO; + ctrl->value &= 0x07; + return 0; + case SN9C102_V4L2_CID_GREEN_BALANCE: + ctrl->value = sn9c102_i2c_read(cam, 0x0a); + if (ctrl->value < 0) + return -EIO; + ctrl->value = (ctrl->value & 0x1f) << 1; + return 0; + case SN9C102_V4L2_CID_DAC_MAGNITUDE: + ctrl->value = sn9c102_i2c_read(cam, 0x08); + if (ctrl->value < 0) + return -EIO; + ctrl->value &= 0xf8; + return 0; + default: + return -EINVAL; + } +} + + +static int pas106b_set_ctrl(struct sn9c102_device *cam, + const struct v4l2_control *ctrl) +{ + int err = 0; + + switch (ctrl->id) { + case V4L2_CID_EXPOSURE: + err += sn9c102_i2c_write(cam, 0x03, ctrl->value >> 4); + err += sn9c102_i2c_write(cam, 0x04, ctrl->value & 0x0f); + break; + case V4L2_CID_RED_BALANCE: + err += sn9c102_i2c_write(cam, 0x0c, ctrl->value); + break; + case V4L2_CID_BLUE_BALANCE: + err += sn9c102_i2c_write(cam, 0x09, ctrl->value); + break; + case V4L2_CID_GAIN: + err += sn9c102_i2c_write(cam, 0x0e, ctrl->value); + break; + case V4L2_CID_CONTRAST: + err += sn9c102_i2c_write(cam, 0x0f, ctrl->value); + break; + case SN9C102_V4L2_CID_GREEN_BALANCE: + err += sn9c102_i2c_write(cam, 0x0a, ctrl->value >> 1); + err += sn9c102_i2c_write(cam, 0x0b, ctrl->value >> 1); + break; + case SN9C102_V4L2_CID_DAC_MAGNITUDE: + err += sn9c102_i2c_write(cam, 0x08, ctrl->value << 3); + break; + default: + return -EINVAL; + } + err += sn9c102_i2c_write(cam, 0x13, 0x01); + + return err ? -EIO : 0; +} + + +static int pas106b_set_crop(struct sn9c102_device *cam, + const struct v4l2_rect *rect) +{ + struct sn9c102_sensor *s = sn9c102_get_sensor(cam); + int err = 0; + u8 h_start = (u8)(rect->left - s->cropcap.bounds.left) + 4, + v_start = (u8)(rect->top - s->cropcap.bounds.top) + 3; + + err += sn9c102_write_reg(cam, h_start, 0x12); + err += sn9c102_write_reg(cam, v_start, 0x13); + + return err; +} + + +static int pas106b_set_pix_format(struct sn9c102_device *cam, + const struct v4l2_pix_format *pix) +{ + int err = 0; + + if (pix->pixelformat == V4L2_PIX_FMT_SN9C10X) + err += sn9c102_write_reg(cam, 0x2c, 0x17); + else + err += sn9c102_write_reg(cam, 0x20, 0x17); + + return err; +} + + +static const struct sn9c102_sensor pas106b = { + .name = "PAS106B", + .maintainer = "Luca Risolia <luca.risolia@studio.unibo.it>", + .supported_bridge = BRIDGE_SN9C101 | BRIDGE_SN9C102, + .sysfs_ops = SN9C102_I2C_READ | SN9C102_I2C_WRITE, + .frequency = SN9C102_I2C_400KHZ | SN9C102_I2C_100KHZ, + .interface = SN9C102_I2C_2WIRES, + .i2c_slave_id = 0x40, + .init = &pas106b_init, + .qctrl = { + { + .id = V4L2_CID_EXPOSURE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "exposure", + .minimum = 0x125, + .maximum = 0xfff, + .step = 0x001, + .default_value = 0x140, + .flags = 0, + }, + { + .id = V4L2_CID_GAIN, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "global gain", + .minimum = 0x00, + .maximum = 0x1f, + .step = 0x01, + .default_value = 0x0d, + .flags = 0, + }, + { + .id = V4L2_CID_CONTRAST, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "contrast", + .minimum = 0x00, + .maximum = 0x07, + .step = 0x01, + .default_value = 0x00, /* 0x00~0x03 have same effect */ + .flags = 0, + }, + { + .id = V4L2_CID_RED_BALANCE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "red balance", + .minimum = 0x00, + .maximum = 0x1f, + .step = 0x01, + .default_value = 0x04, + .flags = 0, + }, + { + .id = V4L2_CID_BLUE_BALANCE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "blue balance", + .minimum = 0x00, + .maximum = 0x1f, + .step = 0x01, + .default_value = 0x06, + .flags = 0, + }, + { + .id = SN9C102_V4L2_CID_GREEN_BALANCE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "green balance", + .minimum = 0x00, + .maximum = 0x3e, + .step = 0x02, + .default_value = 0x02, + .flags = 0, + }, + { + .id = SN9C102_V4L2_CID_DAC_MAGNITUDE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "DAC magnitude", + .minimum = 0x00, + .maximum = 0x1f, + .step = 0x01, + .default_value = 0x01, + .flags = 0, + }, + }, + .get_ctrl = &pas106b_get_ctrl, + .set_ctrl = &pas106b_set_ctrl, + .cropcap = { + .bounds = { + .left = 0, + .top = 0, + .width = 352, + .height = 288, + }, + .defrect = { + .left = 0, + .top = 0, + .width = 352, + .height = 288, + }, + }, + .set_crop = &pas106b_set_crop, + .pix_format = { + .width = 352, + .height = 288, + .pixelformat = V4L2_PIX_FMT_SBGGR8, + .priv = 8, /* we use this field as 'bits per pixel' */ + }, + .set_pix_format = &pas106b_set_pix_format +}; + + +int sn9c102_probe_pas106b(struct sn9c102_device *cam) +{ + int r0 = 0, r1 = 0; + unsigned int pid = 0; + + /* + Minimal initialization to enable the I2C communication + NOTE: do NOT change the values! + */ + if (sn9c102_write_const_regs(cam, + {0x01, 0x01}, /* sensor power down */ + {0x00, 0x01}, /* sensor power on */ + {0x28, 0x17})) /* sensor clock at 24 MHz */ + return -EIO; + + r0 = sn9c102_i2c_try_read(cam, &pas106b, 0x00); + r1 = sn9c102_i2c_try_read(cam, &pas106b, 0x01); + if (r0 < 0 || r1 < 0) + return -EIO; + + pid = (r0 << 11) | ((r1 & 0xf0) >> 4); + if (pid != 0x007) + return -ENODEV; + + sn9c102_attach_sensor(cam, &pas106b); + + return 0; +} diff --git a/drivers/staging/media/sn9c102/sn9c102_pas202bcb.c b/drivers/staging/media/sn9c102/sn9c102_pas202bcb.c new file mode 100644 index 00000000000..f9e31ae2ad9 --- /dev/null +++ b/drivers/staging/media/sn9c102/sn9c102_pas202bcb.c @@ -0,0 +1,340 @@ +/*************************************************************************** + * Plug-in for PAS202BCB image sensor connected to the SN9C1xx PC Camera * + * Controllers * + * * + * Copyright (C) 2004 by Carlos Eduardo Medaglia Dyonisio * + * <medaglia@undl.org.br> * + * * + * Support for SN9C103, DAC Magnitude, exposure and green gain controls * + * added by Luca Risolia <luca.risolia@studio.unibo.it> * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the Free Software * + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * + ***************************************************************************/ + +#include <linux/delay.h> +#include "sn9c102_sensor.h" +#include "sn9c102_devtable.h" + + +static int pas202bcb_init(struct sn9c102_device *cam) +{ + int err = 0; + + switch (sn9c102_get_bridge(cam)) { + case BRIDGE_SN9C101: + case BRIDGE_SN9C102: + err = sn9c102_write_const_regs(cam, {0x00, 0x10}, {0x00, 0x11}, + {0x00, 0x14}, {0x20, 0x17}, + {0x30, 0x19}, {0x09, 0x18}); + break; + case BRIDGE_SN9C103: + err = sn9c102_write_const_regs(cam, {0x00, 0x02}, {0x00, 0x03}, + {0x1a, 0x04}, {0x20, 0x05}, + {0x20, 0x06}, {0x20, 0x07}, + {0x00, 0x10}, {0x00, 0x11}, + {0x00, 0x14}, {0x20, 0x17}, + {0x30, 0x19}, {0x09, 0x18}, + {0x02, 0x1c}, {0x03, 0x1d}, + {0x0f, 0x1e}, {0x0c, 0x1f}, + {0x00, 0x20}, {0x10, 0x21}, + {0x20, 0x22}, {0x30, 0x23}, + {0x40, 0x24}, {0x50, 0x25}, + {0x60, 0x26}, {0x70, 0x27}, + {0x80, 0x28}, {0x90, 0x29}, + {0xa0, 0x2a}, {0xb0, 0x2b}, + {0xc0, 0x2c}, {0xd0, 0x2d}, + {0xe0, 0x2e}, {0xf0, 0x2f}, + {0xff, 0x30}); + break; + default: + break; + } + + err += sn9c102_i2c_write(cam, 0x02, 0x14); + err += sn9c102_i2c_write(cam, 0x03, 0x40); + err += sn9c102_i2c_write(cam, 0x0d, 0x2c); + err += sn9c102_i2c_write(cam, 0x0e, 0x01); + err += sn9c102_i2c_write(cam, 0x0f, 0xa9); + err += sn9c102_i2c_write(cam, 0x10, 0x08); + err += sn9c102_i2c_write(cam, 0x13, 0x63); + err += sn9c102_i2c_write(cam, 0x15, 0x70); + err += sn9c102_i2c_write(cam, 0x11, 0x01); + + msleep(400); + + return err; +} + + +static int pas202bcb_get_ctrl(struct sn9c102_device *cam, + struct v4l2_control *ctrl) +{ + switch (ctrl->id) { + case V4L2_CID_EXPOSURE: + { + int r1 = sn9c102_i2c_read(cam, 0x04), + r2 = sn9c102_i2c_read(cam, 0x05); + if (r1 < 0 || r2 < 0) + return -EIO; + ctrl->value = (r1 << 6) | (r2 & 0x3f); + } + return 0; + case V4L2_CID_RED_BALANCE: + ctrl->value = sn9c102_i2c_read(cam, 0x09); + if (ctrl->value < 0) + return -EIO; + ctrl->value &= 0x0f; + return 0; + case V4L2_CID_BLUE_BALANCE: + ctrl->value = sn9c102_i2c_read(cam, 0x07); + if (ctrl->value < 0) + return -EIO; + ctrl->value &= 0x0f; + return 0; + case V4L2_CID_GAIN: + ctrl->value = sn9c102_i2c_read(cam, 0x10); + if (ctrl->value < 0) + return -EIO; + ctrl->value &= 0x1f; + return 0; + case SN9C102_V4L2_CID_GREEN_BALANCE: + ctrl->value = sn9c102_i2c_read(cam, 0x08); + if (ctrl->value < 0) + return -EIO; + ctrl->value &= 0x0f; + return 0; + case SN9C102_V4L2_CID_DAC_MAGNITUDE: + ctrl->value = sn9c102_i2c_read(cam, 0x0c); + if (ctrl->value < 0) + return -EIO; + return 0; + default: + return -EINVAL; + } +} + + +static int pas202bcb_set_pix_format(struct sn9c102_device *cam, + const struct v4l2_pix_format *pix) +{ + int err = 0; + + if (pix->pixelformat == V4L2_PIX_FMT_SN9C10X) + err += sn9c102_write_reg(cam, 0x28, 0x17); + else + err += sn9c102_write_reg(cam, 0x20, 0x17); + + return err; +} + + +static int pas202bcb_set_ctrl(struct sn9c102_device *cam, + const struct v4l2_control *ctrl) +{ + int err = 0; + + switch (ctrl->id) { + case V4L2_CID_EXPOSURE: + err += sn9c102_i2c_write(cam, 0x04, ctrl->value >> 6); + err += sn9c102_i2c_write(cam, 0x05, ctrl->value & 0x3f); + break; + case V4L2_CID_RED_BALANCE: + err += sn9c102_i2c_write(cam, 0x09, ctrl->value); + break; + case V4L2_CID_BLUE_BALANCE: + err += sn9c102_i2c_write(cam, 0x07, ctrl->value); + break; + case V4L2_CID_GAIN: + err += sn9c102_i2c_write(cam, 0x10, ctrl->value); + break; + case SN9C102_V4L2_CID_GREEN_BALANCE: + err += sn9c102_i2c_write(cam, 0x08, ctrl->value); + break; + case SN9C102_V4L2_CID_DAC_MAGNITUDE: + err += sn9c102_i2c_write(cam, 0x0c, ctrl->value); + break; + default: + return -EINVAL; + } + err += sn9c102_i2c_write(cam, 0x11, 0x01); + + return err ? -EIO : 0; +} + + +static int pas202bcb_set_crop(struct sn9c102_device *cam, + const struct v4l2_rect *rect) +{ + struct sn9c102_sensor *s = sn9c102_get_sensor(cam); + int err = 0; + u8 h_start = 0, + v_start = (u8)(rect->top - s->cropcap.bounds.top) + 3; + + switch (sn9c102_get_bridge(cam)) { + case BRIDGE_SN9C101: + case BRIDGE_SN9C102: + h_start = (u8)(rect->left - s->cropcap.bounds.left) + 4; + break; + case BRIDGE_SN9C103: + h_start = (u8)(rect->left - s->cropcap.bounds.left) + 3; + break; + default: + break; + } + + err += sn9c102_write_reg(cam, h_start, 0x12); + err += sn9c102_write_reg(cam, v_start, 0x13); + + return err; +} + + +static const struct sn9c102_sensor pas202bcb = { + .name = "PAS202BCB", + .maintainer = "Luca Risolia <luca.risolia@studio.unibo.it>", + .supported_bridge = BRIDGE_SN9C101 | BRIDGE_SN9C102 | BRIDGE_SN9C103, + .sysfs_ops = SN9C102_I2C_READ | SN9C102_I2C_WRITE, + .frequency = SN9C102_I2C_400KHZ | SN9C102_I2C_100KHZ, + .interface = SN9C102_I2C_2WIRES, + .i2c_slave_id = 0x40, + .init = &pas202bcb_init, + .qctrl = { + { + .id = V4L2_CID_EXPOSURE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "exposure", + .minimum = 0x01e5, + .maximum = 0x3fff, + .step = 0x0001, + .default_value = 0x01e5, + .flags = 0, + }, + { + .id = V4L2_CID_GAIN, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "global gain", + .minimum = 0x00, + .maximum = 0x1f, + .step = 0x01, + .default_value = 0x0b, + .flags = 0, + }, + { + .id = V4L2_CID_RED_BALANCE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "red balance", + .minimum = 0x00, + .maximum = 0x0f, + .step = 0x01, + .default_value = 0x00, + .flags = 0, + }, + { + .id = V4L2_CID_BLUE_BALANCE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "blue balance", + .minimum = 0x00, + .maximum = 0x0f, + .step = 0x01, + .default_value = 0x05, + .flags = 0, + }, + { + .id = SN9C102_V4L2_CID_GREEN_BALANCE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "green balance", + .minimum = 0x00, + .maximum = 0x0f, + .step = 0x01, + .default_value = 0x00, + .flags = 0, + }, + { + .id = SN9C102_V4L2_CID_DAC_MAGNITUDE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "DAC magnitude", + .minimum = 0x00, + .maximum = 0xff, + .step = 0x01, + .default_value = 0x04, + .flags = 0, + }, + }, + .get_ctrl = &pas202bcb_get_ctrl, + .set_ctrl = &pas202bcb_set_ctrl, + .cropcap = { + .bounds = { + .left = 0, + .top = 0, + .width = 640, + .height = 480, + }, + .defrect = { + .left = 0, + .top = 0, + .width = 640, + .height = 480, + }, + }, + .set_crop = &pas202bcb_set_crop, + .pix_format = { + .width = 640, + .height = 480, + .pixelformat = V4L2_PIX_FMT_SBGGR8, + .priv = 8, + }, + .set_pix_format = &pas202bcb_set_pix_format +}; + + +int sn9c102_probe_pas202bcb(struct sn9c102_device *cam) +{ + int r0 = 0, r1 = 0, err = 0; + unsigned int pid = 0; + + /* + * Minimal initialization to enable the I2C communication + * NOTE: do NOT change the values! + */ + switch (sn9c102_get_bridge(cam)) { + case BRIDGE_SN9C101: + case BRIDGE_SN9C102: + err = sn9c102_write_const_regs(cam, + {0x01, 0x01}, /* power down */ + {0x40, 0x01}, /* power on */ + {0x28, 0x17});/* clock 24 MHz */ + break; + case BRIDGE_SN9C103: /* do _not_ change anything! */ + err = sn9c102_write_const_regs(cam, {0x09, 0x01}, {0x44, 0x01}, + {0x44, 0x02}, {0x29, 0x17}); + break; + default: + break; + } + + r0 = sn9c102_i2c_try_read(cam, &pas202bcb, 0x00); + r1 = sn9c102_i2c_try_read(cam, &pas202bcb, 0x01); + + if (err || r0 < 0 || r1 < 0) + return -EIO; + + pid = (r0 << 4) | ((r1 & 0xf0) >> 4); + if (pid != 0x017) + return -ENODEV; + + sn9c102_attach_sensor(cam, &pas202bcb); + + return 0; +} diff --git a/drivers/staging/media/sn9c102/sn9c102_sensor.h b/drivers/staging/media/sn9c102/sn9c102_sensor.h new file mode 100644 index 00000000000..9f59c815d48 --- /dev/null +++ b/drivers/staging/media/sn9c102/sn9c102_sensor.h @@ -0,0 +1,307 @@ +/*************************************************************************** + * API for image sensors connected to the SN9C1xx PC Camera Controllers * + * * + * Copyright (C) 2004-2007 by Luca Risolia <luca.risolia@studio.unibo.it> * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the Free Software * + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * + ***************************************************************************/ + +#ifndef _SN9C102_SENSOR_H_ +#define _SN9C102_SENSOR_H_ + +#include <linux/usb.h> +#include <linux/videodev2.h> +#include <linux/device.h> +#include <linux/stddef.h> +#include <linux/errno.h> +#include <asm/types.h> + +struct sn9c102_device; +struct sn9c102_sensor; + +/*****************************************************************************/ + +/* + OVERVIEW. + This is a small interface that allows you to add support for any CCD/CMOS + image sensors connected to the SN9C1XX bridges. The entire API is documented + below. In the most general case, to support a sensor there are three steps + you have to follow: + 1) define the main "sn9c102_sensor" structure by setting the basic fields; + 2) write a probing function to be called by the core module when the USB + camera is recognized, then add both the USB ids and the name of that + function to the two corresponding tables in sn9c102_devtable.h; + 3) implement the methods that you want/need (and fill the rest of the main + structure accordingly). + "sn9c102_pas106b.c" is an example of all this stuff. Remember that you do + NOT need to touch the source code of the core module for the things to work + properly, unless you find bugs or flaws in it. Finally, do not forget to + read the V4L2 API for completeness. +*/ + +/*****************************************************************************/ + +enum sn9c102_bridge { + BRIDGE_SN9C101 = 0x01, + BRIDGE_SN9C102 = 0x02, + BRIDGE_SN9C103 = 0x04, + BRIDGE_SN9C105 = 0x08, + BRIDGE_SN9C120 = 0x10, +}; + +/* Return the bridge name */ +enum sn9c102_bridge sn9c102_get_bridge(struct sn9c102_device *cam); + +/* Return a pointer the sensor struct attached to the camera */ +struct sn9c102_sensor *sn9c102_get_sensor(struct sn9c102_device *cam); + +/* Identify a device */ +extern struct sn9c102_device* +sn9c102_match_id(struct sn9c102_device *cam, const struct usb_device_id *id); + +/* Attach a probed sensor to the camera. */ +extern void +sn9c102_attach_sensor(struct sn9c102_device *cam, + const struct sn9c102_sensor *sensor); + +/* + Read/write routines: they always return -1 on error, 0 or the read value + otherwise. NOTE that a real read operation is not supported by the SN9C1XX + chip for some of its registers. To work around this problem, a pseudo-read + call is provided instead: it returns the last successfully written value + on the register (0 if it has never been written), the usual -1 on error. +*/ + +/* The "try" I2C I/O versions are used when probing the sensor */ +extern int sn9c102_i2c_try_read(struct sn9c102_device*, + const struct sn9c102_sensor*, u8 address); + +/* + These must be used if and only if the sensor doesn't implement the standard + I2C protocol. There are a number of good reasons why you must use the + single-byte versions of these functions: do not abuse. The first function + writes n bytes, from data0 to datan, to registers 0x09 - 0x09+n of SN9C1XX + chip. The second one programs the registers 0x09 and 0x10 with data0 and + data1, and places the n bytes read from the sensor register table in the + buffer pointed by 'buffer'. Both the functions return -1 on error; the write + version returns 0 on success, while the read version returns the first read + byte. +*/ +extern int sn9c102_i2c_try_raw_write(struct sn9c102_device *cam, + const struct sn9c102_sensor *sensor, u8 n, + u8 data0, u8 data1, u8 data2, u8 data3, + u8 data4, u8 data5); +extern int sn9c102_i2c_try_raw_read(struct sn9c102_device *cam, + const struct sn9c102_sensor *sensor, + u8 data0, u8 data1, u8 n, u8 buffer[]); + +/* To be used after the sensor struct has been attached to the camera struct */ +extern int sn9c102_i2c_write(struct sn9c102_device*, u8 address, u8 value); +extern int sn9c102_i2c_read(struct sn9c102_device*, u8 address); + +/* I/O on registers in the bridge. Could be used by the sensor methods too */ +extern int sn9c102_read_reg(struct sn9c102_device*, u16 index); +extern int sn9c102_pread_reg(struct sn9c102_device*, u16 index); +extern int sn9c102_write_reg(struct sn9c102_device*, u8 value, u16 index); +extern int sn9c102_write_regs(struct sn9c102_device*, const u8 valreg[][2], + int count); +/* + Write multiple registers with constant values. For example: + sn9c102_write_const_regs(cam, {0x00, 0x14}, {0x60, 0x17}, {0x0f, 0x18}); + Register addresses must be < 256. +*/ +#define sn9c102_write_const_regs(sn9c102_device, data...) \ + ({ static const u8 _valreg[][2] = {data}; \ + sn9c102_write_regs(sn9c102_device, _valreg, ARRAY_SIZE(_valreg)); }) + +/*****************************************************************************/ + +enum sn9c102_i2c_sysfs_ops { + SN9C102_I2C_READ = 0x01, + SN9C102_I2C_WRITE = 0x02, +}; + +enum sn9c102_i2c_frequency { /* sensors may support both the frequencies */ + SN9C102_I2C_100KHZ = 0x01, + SN9C102_I2C_400KHZ = 0x02, +}; + +enum sn9c102_i2c_interface { + SN9C102_I2C_2WIRES, + SN9C102_I2C_3WIRES, +}; + +#define SN9C102_MAX_CTRLS (V4L2_CID_LASTP1-V4L2_CID_BASE+10) + +struct sn9c102_sensor { + char name[32], /* sensor name */ + maintainer[64]; /* name of the maintainer <email> */ + + enum sn9c102_bridge supported_bridge; /* supported SN9C1xx bridges */ + + /* Supported operations through the 'sysfs' interface */ + enum sn9c102_i2c_sysfs_ops sysfs_ops; + + /* + These sensor capabilities must be provided if the SN9C1XX controller + needs to communicate through the sensor serial interface by using + at least one of the i2c functions available. + */ + enum sn9c102_i2c_frequency frequency; + enum sn9c102_i2c_interface interface; + + /* + This identifier must be provided if the image sensor implements + the standard I2C protocol. + */ + u8 i2c_slave_id; /* reg. 0x09 */ + + /* + NOTE: Where not noted,most of the functions below are not mandatory. + Set to null if you do not implement them. If implemented, + they must return 0 on success, the proper error otherwise. + */ + + int (*init)(struct sn9c102_device *cam); + /* + This function will be called after the sensor has been attached. + It should be used to initialize the sensor only, but may also + configure part of the SN9C1XX chip if necessary. You don't need to + setup picture settings like brightness, contrast, etc.. here, if + the corresponding controls are implemented (see below), since + they are adjusted in the core driver by calling the set_ctrl() + method after init(), where the arguments are the default values + specified in the v4l2_queryctrl list of supported controls; + Same suggestions apply for other settings, _if_ the corresponding + methods are present; if not, the initialization must configure the + sensor according to the default configuration structures below. + */ + + struct v4l2_queryctrl qctrl[SN9C102_MAX_CTRLS]; + /* + Optional list of default controls, defined as indicated in the + V4L2 API. Menu type controls are not handled by this interface. + */ + + int (*get_ctrl)(struct sn9c102_device *cam, struct v4l2_control *ctrl); + int (*set_ctrl)(struct sn9c102_device *cam, + const struct v4l2_control *ctrl); + /* + You must implement at least the set_ctrl method if you have defined + the list above. The returned value must follow the V4L2 + specifications for the VIDIOC_G|C_CTRL ioctls. V4L2_CID_H|VCENTER + are not supported by this driver, so do not implement them. Also, + you don't have to check whether the passed values are out of bounds, + given that this is done by the core module. + */ + + struct v4l2_cropcap cropcap; + /* + Think the image sensor as a grid of R,G,B monochromatic pixels + disposed according to a particular Bayer pattern, which describes + the complete array of pixels, from (0,0) to (xmax, ymax). We will + use this coordinate system from now on. It is assumed the sensor + chip can be programmed to capture/transmit a subsection of that + array of pixels: we will call this subsection "active window". + It is not always true that the largest achievable active window can + cover the whole array of pixels. The V4L2 API defines another + area called "source rectangle", which, in turn, is a subrectangle of + the active window. The SN9C1XX chip is always programmed to read the + source rectangle. + The bounds of both the active window and the source rectangle are + specified in the cropcap substructures 'bounds' and 'defrect'. + By default, the source rectangle should cover the largest possible + area. Again, it is not always true that the largest source rectangle + can cover the entire active window, although it is a rare case for + the hardware we have. The bounds of the source rectangle _must_ be + multiple of 16 and must use the same coordinate system as indicated + before; their centers shall align initially. + If necessary, the sensor chip must be initialized during init() to + set the bounds of the active sensor window; however, by default, it + usually covers the largest achievable area (maxwidth x maxheight) + of pixels, so no particular initialization is needed, if you have + defined the correct default bounds in the structures. + See the V4L2 API for further details. + NOTE: once you have defined the bounds of the active window + (struct cropcap.bounds) you must not change them.anymore. + Only 'bounds' and 'defrect' fields are mandatory, other fields + will be ignored. + */ + + int (*set_crop)(struct sn9c102_device *cam, + const struct v4l2_rect *rect); + /* + To be called on VIDIOC_C_SETCROP. The core module always calls a + default routine which configures the appropriate SN9C1XX regs (also + scaling), but you may need to override/adjust specific stuff. + 'rect' contains width and height values that are multiple of 16: in + case you override the default function, you always have to program + the chip to match those values; on error return the corresponding + error code without rolling back. + NOTE: in case, you must program the SN9C1XX chip to get rid of + blank pixels or blank lines at the _start_ of each line or + frame after each HSYNC or VSYNC, so that the image starts with + real RGB data (see regs 0x12, 0x13) (having set H_SIZE and, + V_SIZE you don't have to care about blank pixels or blank + lines at the end of each line or frame). + */ + + struct v4l2_pix_format pix_format; + /* + What you have to define here are: 1) initial 'width' and 'height' of + the target rectangle 2) the initial 'pixelformat', which can be + either V4L2_PIX_FMT_SN9C10X, V4L2_PIX_FMT_JPEG (for ompressed video) + or V4L2_PIX_FMT_SBGGR8 3) 'priv', which we'll be used to indicate + the number of bits per pixel for uncompressed video, 8 or 9 (despite + the current value of 'pixelformat'). + NOTE 1: both 'width' and 'height' _must_ be either 1/1 or 1/2 or 1/4 + of cropcap.defrect.width and cropcap.defrect.height. I + suggest 1/1. + NOTE 2: The initial compression quality is defined by the first bit + of reg 0x17 during the initialization of the image sensor. + NOTE 3: as said above, you have to program the SN9C1XX chip to get + rid of any blank pixels, so that the output of the sensor + matches the RGB bayer sequence (i.e. BGBGBG...GRGRGR). + */ + + int (*set_pix_format)(struct sn9c102_device *cam, + const struct v4l2_pix_format *pix); + /* + To be called on VIDIOC_S_FMT, when switching from the SBGGR8 to + SN9C10X pixel format or viceversa. On error return the corresponding + error code without rolling back. + */ + + /* + Do NOT write to the data below, it's READ ONLY. It is used by the + core module to store successfully updated values of the above + settings, for rollbacks..etc..in case of errors during atomic I/O + */ + struct v4l2_queryctrl _qctrl[SN9C102_MAX_CTRLS]; + struct v4l2_rect _rect; +}; + +/*****************************************************************************/ + +/* Private ioctl's for control settings supported by some image sensors */ +#define SN9C102_V4L2_CID_DAC_MAGNITUDE (V4L2_CID_PRIVATE_BASE + 0) +#define SN9C102_V4L2_CID_GREEN_BALANCE (V4L2_CID_PRIVATE_BASE + 1) +#define SN9C102_V4L2_CID_RESET_LEVEL (V4L2_CID_PRIVATE_BASE + 2) +#define SN9C102_V4L2_CID_PIXEL_BIAS_VOLTAGE (V4L2_CID_PRIVATE_BASE + 3) +#define SN9C102_V4L2_CID_GAMMA (V4L2_CID_PRIVATE_BASE + 4) +#define SN9C102_V4L2_CID_BAND_FILTER (V4L2_CID_PRIVATE_BASE + 5) +#define SN9C102_V4L2_CID_BRIGHT_LEVEL (V4L2_CID_PRIVATE_BASE + 6) + +#endif /* _SN9C102_SENSOR_H_ */ diff --git a/drivers/staging/media/sn9c102/sn9c102_tas5110c1b.c b/drivers/staging/media/sn9c102/sn9c102_tas5110c1b.c new file mode 100644 index 00000000000..6a00b626d34 --- /dev/null +++ b/drivers/staging/media/sn9c102/sn9c102_tas5110c1b.c @@ -0,0 +1,154 @@ +/*************************************************************************** + * Plug-in for TAS5110C1B image sensor connected to the SN9C1xx PC Camera * + * Controllers * + * * + * Copyright (C) 2004-2007 by Luca Risolia <luca.risolia@studio.unibo.it> * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the Free Software * + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * + ***************************************************************************/ + +#include "sn9c102_sensor.h" +#include "sn9c102_devtable.h" + + +static int tas5110c1b_init(struct sn9c102_device *cam) +{ + int err = 0; + + err = sn9c102_write_const_regs(cam, {0x01, 0x01}, {0x44, 0x01}, + {0x00, 0x10}, {0x00, 0x11}, + {0x0a, 0x14}, {0x60, 0x17}, + {0x06, 0x18}, {0xfb, 0x19}); + + err += sn9c102_i2c_write(cam, 0xc0, 0x80); + + return err; +} + + +static int tas5110c1b_set_ctrl(struct sn9c102_device *cam, + const struct v4l2_control *ctrl) +{ + int err = 0; + + switch (ctrl->id) { + case V4L2_CID_GAIN: + err += sn9c102_i2c_write(cam, 0x20, 0xf6 - ctrl->value); + break; + default: + return -EINVAL; + } + + return err ? -EIO : 0; +} + + +static int tas5110c1b_set_crop(struct sn9c102_device *cam, + const struct v4l2_rect *rect) +{ + struct sn9c102_sensor *s = sn9c102_get_sensor(cam); + int err = 0; + u8 h_start = (u8)(rect->left - s->cropcap.bounds.left) + 69, + v_start = (u8)(rect->top - s->cropcap.bounds.top) + 9; + + err += sn9c102_write_reg(cam, h_start, 0x12); + err += sn9c102_write_reg(cam, v_start, 0x13); + + /* Don't change ! */ + err += sn9c102_write_reg(cam, 0x14, 0x1a); + err += sn9c102_write_reg(cam, 0x0a, 0x1b); + err += sn9c102_write_reg(cam, sn9c102_pread_reg(cam, 0x19), 0x19); + + return err; +} + + +static int tas5110c1b_set_pix_format(struct sn9c102_device *cam, + const struct v4l2_pix_format *pix) +{ + int err = 0; + + if (pix->pixelformat == V4L2_PIX_FMT_SN9C10X) + err += sn9c102_write_reg(cam, 0x2b, 0x19); + else + err += sn9c102_write_reg(cam, 0xfb, 0x19); + + return err; +} + + +static const struct sn9c102_sensor tas5110c1b = { + .name = "TAS5110C1B", + .maintainer = "Luca Risolia <luca.risolia@studio.unibo.it>", + .supported_bridge = BRIDGE_SN9C101 | BRIDGE_SN9C102, + .sysfs_ops = SN9C102_I2C_WRITE, + .frequency = SN9C102_I2C_100KHZ, + .interface = SN9C102_I2C_3WIRES, + .init = &tas5110c1b_init, + .qctrl = { + { + .id = V4L2_CID_GAIN, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "global gain", + .minimum = 0x00, + .maximum = 0xf6, + .step = 0x01, + .default_value = 0x40, + .flags = 0, + }, + }, + .set_ctrl = &tas5110c1b_set_ctrl, + .cropcap = { + .bounds = { + .left = 0, + .top = 0, + .width = 352, + .height = 288, + }, + .defrect = { + .left = 0, + .top = 0, + .width = 352, + .height = 288, + }, + }, + .set_crop = &tas5110c1b_set_crop, + .pix_format = { + .width = 352, + .height = 288, + .pixelformat = V4L2_PIX_FMT_SBGGR8, + .priv = 8, + }, + .set_pix_format = &tas5110c1b_set_pix_format +}; + + +int sn9c102_probe_tas5110c1b(struct sn9c102_device *cam) +{ + const struct usb_device_id tas5110c1b_id_table[] = { + { USB_DEVICE(0x0c45, 0x6001), }, + { USB_DEVICE(0x0c45, 0x6005), }, + { USB_DEVICE(0x0c45, 0x60ab), }, + { } + }; + + /* Sensor detection is based on USB pid/vid */ + if (!sn9c102_match_id(cam, tas5110c1b_id_table)) + return -ENODEV; + + sn9c102_attach_sensor(cam, &tas5110c1b); + + return 0; +} diff --git a/drivers/staging/media/sn9c102/sn9c102_tas5110d.c b/drivers/staging/media/sn9c102/sn9c102_tas5110d.c new file mode 100644 index 00000000000..eefbf8670c3 --- /dev/null +++ b/drivers/staging/media/sn9c102/sn9c102_tas5110d.c @@ -0,0 +1,119 @@ +/*************************************************************************** + * Plug-in for TAS5110D image sensor connected to the SN9C1xx PC Camera * + * Controllers * + * * + * Copyright (C) 2007 by Luca Risolia <luca.risolia@studio.unibo.it> * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the Free Software * + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * + ***************************************************************************/ + +#include "sn9c102_sensor.h" +#include "sn9c102_devtable.h" + + +static int tas5110d_init(struct sn9c102_device *cam) +{ + int err; + + err = sn9c102_write_const_regs(cam, {0x01, 0x01}, {0x04, 0x01}, + {0x0a, 0x14}, {0x60, 0x17}, + {0x06, 0x18}, {0xfb, 0x19}); + + err += sn9c102_i2c_write(cam, 0x9a, 0xca); + + return err; +} + + +static int tas5110d_set_crop(struct sn9c102_device *cam, + const struct v4l2_rect *rect) +{ + struct sn9c102_sensor *s = sn9c102_get_sensor(cam); + int err = 0; + u8 h_start = (u8)(rect->left - s->cropcap.bounds.left) + 69, + v_start = (u8)(rect->top - s->cropcap.bounds.top) + 9; + + err += sn9c102_write_reg(cam, h_start, 0x12); + err += sn9c102_write_reg(cam, v_start, 0x13); + + err += sn9c102_write_reg(cam, 0x14, 0x1a); + err += sn9c102_write_reg(cam, 0x0a, 0x1b); + + return err; +} + + +static int tas5110d_set_pix_format(struct sn9c102_device *cam, + const struct v4l2_pix_format *pix) +{ + int err = 0; + + if (pix->pixelformat == V4L2_PIX_FMT_SN9C10X) + err += sn9c102_write_reg(cam, 0x3b, 0x19); + else + err += sn9c102_write_reg(cam, 0xfb, 0x19); + + return err; +} + + +static const struct sn9c102_sensor tas5110d = { + .name = "TAS5110D", + .maintainer = "Luca Risolia <luca.risolia@studio.unibo.it>", + .supported_bridge = BRIDGE_SN9C101 | BRIDGE_SN9C102, + .sysfs_ops = SN9C102_I2C_WRITE, + .frequency = SN9C102_I2C_100KHZ, + .interface = SN9C102_I2C_2WIRES, + .i2c_slave_id = 0x61, + .init = &tas5110d_init, + .cropcap = { + .bounds = { + .left = 0, + .top = 0, + .width = 352, + .height = 288, + }, + .defrect = { + .left = 0, + .top = 0, + .width = 352, + .height = 288, + }, + }, + .set_crop = &tas5110d_set_crop, + .pix_format = { + .width = 352, + .height = 288, + .pixelformat = V4L2_PIX_FMT_SBGGR8, + .priv = 8, + }, + .set_pix_format = &tas5110d_set_pix_format +}; + + +int sn9c102_probe_tas5110d(struct sn9c102_device *cam) +{ + const struct usb_device_id tas5110d_id_table[] = { + { USB_DEVICE(0x0c45, 0x6007), }, + { } + }; + + if (!sn9c102_match_id(cam, tas5110d_id_table)) + return -ENODEV; + + sn9c102_attach_sensor(cam, &tas5110d); + + return 0; +} diff --git a/drivers/staging/media/sn9c102/sn9c102_tas5130d1b.c b/drivers/staging/media/sn9c102/sn9c102_tas5130d1b.c new file mode 100644 index 00000000000..725de857de4 --- /dev/null +++ b/drivers/staging/media/sn9c102/sn9c102_tas5130d1b.c @@ -0,0 +1,165 @@ +/*************************************************************************** + * Plug-in for TAS5130D1B image sensor connected to the SN9C1xx PC Camera * + * Controllers * + * * + * Copyright (C) 2004-2007 by Luca Risolia <luca.risolia@studio.unibo.it> * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the Free Software * + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * + ***************************************************************************/ + +#include "sn9c102_sensor.h" +#include "sn9c102_devtable.h" + + +static int tas5130d1b_init(struct sn9c102_device *cam) +{ + int err; + + err = sn9c102_write_const_regs(cam, {0x01, 0x01}, {0x20, 0x17}, + {0x04, 0x01}, {0x01, 0x10}, + {0x00, 0x11}, {0x00, 0x14}, + {0x60, 0x17}, {0x07, 0x18}); + + return err; +} + + +static int tas5130d1b_set_ctrl(struct sn9c102_device *cam, + const struct v4l2_control *ctrl) +{ + int err = 0; + + switch (ctrl->id) { + case V4L2_CID_GAIN: + err += sn9c102_i2c_write(cam, 0x20, 0xf6 - ctrl->value); + break; + case V4L2_CID_EXPOSURE: + err += sn9c102_i2c_write(cam, 0x40, 0x47 - ctrl->value); + break; + default: + return -EINVAL; + } + + return err ? -EIO : 0; +} + + +static int tas5130d1b_set_crop(struct sn9c102_device *cam, + const struct v4l2_rect *rect) +{ + struct sn9c102_sensor *s = sn9c102_get_sensor(cam); + u8 h_start = (u8)(rect->left - s->cropcap.bounds.left) + 104, + v_start = (u8)(rect->top - s->cropcap.bounds.top) + 12; + int err = 0; + + err += sn9c102_write_reg(cam, h_start, 0x12); + err += sn9c102_write_reg(cam, v_start, 0x13); + + /* Do NOT change! */ + err += sn9c102_write_reg(cam, 0x1f, 0x1a); + err += sn9c102_write_reg(cam, 0x1a, 0x1b); + err += sn9c102_write_reg(cam, sn9c102_pread_reg(cam, 0x19), 0x19); + + return err; +} + + +static int tas5130d1b_set_pix_format(struct sn9c102_device *cam, + const struct v4l2_pix_format *pix) +{ + int err = 0; + + if (pix->pixelformat == V4L2_PIX_FMT_SN9C10X) + err += sn9c102_write_reg(cam, 0x63, 0x19); + else + err += sn9c102_write_reg(cam, 0xf3, 0x19); + + return err; +} + + +static const struct sn9c102_sensor tas5130d1b = { + .name = "TAS5130D1B", + .maintainer = "Luca Risolia <luca.risolia@studio.unibo.it>", + .supported_bridge = BRIDGE_SN9C101 | BRIDGE_SN9C102, + .sysfs_ops = SN9C102_I2C_WRITE, + .frequency = SN9C102_I2C_100KHZ, + .interface = SN9C102_I2C_3WIRES, + .init = &tas5130d1b_init, + .qctrl = { + { + .id = V4L2_CID_GAIN, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "global gain", + .minimum = 0x00, + .maximum = 0xf6, + .step = 0x02, + .default_value = 0x00, + .flags = 0, + }, + { + .id = V4L2_CID_EXPOSURE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "exposure", + .minimum = 0x00, + .maximum = 0x47, + .step = 0x01, + .default_value = 0x00, + .flags = 0, + }, + }, + .set_ctrl = &tas5130d1b_set_ctrl, + .cropcap = { + .bounds = { + .left = 0, + .top = 0, + .width = 640, + .height = 480, + }, + .defrect = { + .left = 0, + .top = 0, + .width = 640, + .height = 480, + }, + }, + .set_crop = &tas5130d1b_set_crop, + .pix_format = { + .width = 640, + .height = 480, + .pixelformat = V4L2_PIX_FMT_SBGGR8, + .priv = 8, + }, + .set_pix_format = &tas5130d1b_set_pix_format +}; + + +int sn9c102_probe_tas5130d1b(struct sn9c102_device *cam) +{ + const struct usb_device_id tas5130d1b_id_table[] = { + { USB_DEVICE(0x0c45, 0x6024), }, + { USB_DEVICE(0x0c45, 0x6025), }, + { USB_DEVICE(0x0c45, 0x60aa), }, + { } + }; + + /* Sensor detection is based on USB pid/vid */ + if (!sn9c102_match_id(cam, tas5130d1b_id_table)) + return -ENODEV; + + sn9c102_attach_sensor(cam, &tas5130d1b); + + return 0; +} diff --git a/drivers/staging/media/solo6x10/Kconfig b/drivers/staging/media/solo6x10/Kconfig new file mode 100644 index 00000000000..6a1906fa111 --- /dev/null +++ b/drivers/staging/media/solo6x10/Kconfig @@ -0,0 +1,18 @@ +config SOLO6X10 + tristate "Bluecherry / Softlogic 6x10 capture cards (MPEG-4/H.264)" + depends on PCI && VIDEO_DEV && SND && I2C + select FONT_SUPPORT + select FONT_8x16 + select VIDEOBUF2_DMA_SG + select VIDEOBUF2_DMA_CONTIG + select SND_PCM + select FONT_8x16 + ---help--- + This driver supports the Bluecherry H.264 and MPEG-4 hardware + compression capture cards and other Softlogic-based ones. + + Following cards have been tested: + * Bluecherry BC-H16480A (PCIe, 16 port, H.264) + * Bluecherry BC-H04120A (PCIe, 4 port, H.264) + * Bluecherry BC-H04120A-MPCI (Mini-PCI, 4 port, H.264) + * Bluecherry BC-04120A (PCIe, 4 port, MPEG-4) diff --git a/drivers/staging/media/solo6x10/Makefile b/drivers/staging/media/solo6x10/Makefile new file mode 100644 index 00000000000..7aae118947b --- /dev/null +++ b/drivers/staging/media/solo6x10/Makefile @@ -0,0 +1,5 @@ +solo6x10-y := solo6x10-core.o solo6x10-i2c.o solo6x10-p2m.o solo6x10-v4l2.o \ + solo6x10-tw28.o solo6x10-gpio.o solo6x10-disp.o solo6x10-enc.o \ + solo6x10-v4l2-enc.o solo6x10-g723.o solo6x10-eeprom.o + +obj-$(CONFIG_SOLO6X10) += solo6x10.o diff --git a/drivers/staging/media/solo6x10/TODO b/drivers/staging/media/solo6x10/TODO new file mode 100644 index 00000000000..7b8db75b1ac --- /dev/null +++ b/drivers/staging/media/solo6x10/TODO @@ -0,0 +1,15 @@ +- batch up desc requests for more efficient use of p2m? +- encoder on/off controls +- mpeg cid bitrate mode (vbr/cbr) +- mpeg cid bitrate/bitrate-peak +- mpeg encode of user data +- mpeg decode of user data +- implement CID controls for mozaic areas + +- sound + - implement playback via external sound jack + - implement loopback of external sound jack with incoming audio? + - implement pause/resume (make use of in bc-server) + +Please send patches to the linux media list <linux-media@vger.kernel.org> and +Cc Ismael Luceno <ismael.luceno@corp.bluecherry.net>. diff --git a/drivers/staging/media/solo6x10/solo6x10-core.c b/drivers/staging/media/solo6x10/solo6x10-core.c new file mode 100644 index 00000000000..f67046955ef --- /dev/null +++ b/drivers/staging/media/solo6x10/solo6x10-core.c @@ -0,0 +1,709 @@ +/* + * Copyright (C) 2010-2013 Bluecherry, LLC <http://www.bluecherrydvr.com> + * + * Original author: + * Ben Collins <bcollins@ubuntu.com> + * + * Additional work by: + * John Brooks <john.brooks@bluecherry.net> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/interrupt.h> +#include <linux/videodev2.h> +#include <linux/delay.h> +#include <linux/sysfs.h> +#include <linux/ktime.h> +#include <linux/slab.h> + +#include "solo6x10.h" +#include "solo6x10-tw28.h" + +MODULE_DESCRIPTION("Softlogic 6x10 MPEG4/H.264/G.723 CODEC V4L2/ALSA Driver"); +MODULE_AUTHOR("Bluecherry <maintainers@bluecherrydvr.com>"); +MODULE_VERSION(SOLO6X10_VERSION); +MODULE_LICENSE("GPL"); + +static unsigned video_nr = -1; +module_param(video_nr, uint, 0644); +MODULE_PARM_DESC(video_nr, "videoX start number, -1 is autodetect (default)"); + +static int full_eeprom; /* default is only top 64B */ +module_param(full_eeprom, uint, 0644); +MODULE_PARM_DESC(full_eeprom, "Allow access to full 128B EEPROM (dangerous)"); + + +static void solo_set_time(struct solo_dev *solo_dev) +{ + struct timespec ts; + + ktime_get_ts(&ts); + + solo_reg_write(solo_dev, SOLO_TIMER_SEC, ts.tv_sec); + solo_reg_write(solo_dev, SOLO_TIMER_USEC, ts.tv_nsec / NSEC_PER_USEC); +} + +static void solo_timer_sync(struct solo_dev *solo_dev) +{ + u32 sec, usec; + struct timespec ts; + long diff; + + if (solo_dev->type != SOLO_DEV_6110) + return; + + if (++solo_dev->time_sync < 60) + return; + + solo_dev->time_sync = 0; + + sec = solo_reg_read(solo_dev, SOLO_TIMER_SEC); + usec = solo_reg_read(solo_dev, SOLO_TIMER_USEC); + + ktime_get_ts(&ts); + + diff = (long)ts.tv_sec - (long)sec; + diff = (diff * 1000000) + + ((long)(ts.tv_nsec / NSEC_PER_USEC) - (long)usec); + + if (diff > 1000 || diff < -1000) { + solo_set_time(solo_dev); + } else if (diff) { + long usec_lsb = solo_dev->usec_lsb; + + usec_lsb -= diff / 4; + if (usec_lsb < 0) + usec_lsb = 0; + else if (usec_lsb > 255) + usec_lsb = 255; + + solo_dev->usec_lsb = usec_lsb; + solo_reg_write(solo_dev, SOLO_TIMER_USEC_LSB, + solo_dev->usec_lsb); + } +} + +static irqreturn_t solo_isr(int irq, void *data) +{ + struct solo_dev *solo_dev = data; + u32 status; + int i; + + status = solo_reg_read(solo_dev, SOLO_IRQ_STAT); + if (!status) + return IRQ_NONE; + + if (status & ~solo_dev->irq_mask) { + solo_reg_write(solo_dev, SOLO_IRQ_STAT, + status & ~solo_dev->irq_mask); + status &= solo_dev->irq_mask; + } + + if (status & SOLO_IRQ_PCI_ERR) + solo_p2m_error_isr(solo_dev); + + for (i = 0; i < SOLO_NR_P2M; i++) + if (status & SOLO_IRQ_P2M(i)) + solo_p2m_isr(solo_dev, i); + + if (status & SOLO_IRQ_IIC) + solo_i2c_isr(solo_dev); + + if (status & SOLO_IRQ_VIDEO_IN) { + solo_video_in_isr(solo_dev); + solo_timer_sync(solo_dev); + } + + if (status & SOLO_IRQ_ENCODER) + solo_enc_v4l2_isr(solo_dev); + + if (status & SOLO_IRQ_G723) + solo_g723_isr(solo_dev); + + /* Clear all interrupts handled */ + solo_reg_write(solo_dev, SOLO_IRQ_STAT, status); + + return IRQ_HANDLED; +} + +static void free_solo_dev(struct solo_dev *solo_dev) +{ + struct pci_dev *pdev; + + if (!solo_dev) + return; + + if (solo_dev->dev.parent) + device_unregister(&solo_dev->dev); + + pdev = solo_dev->pdev; + + /* If we never initialized the PCI device, then nothing else + * below here needs cleanup */ + if (!pdev) { + kfree(solo_dev); + return; + } + + if (solo_dev->reg_base) { + /* Bring down the sub-devices first */ + solo_g723_exit(solo_dev); + solo_enc_v4l2_exit(solo_dev); + solo_enc_exit(solo_dev); + solo_v4l2_exit(solo_dev); + solo_disp_exit(solo_dev); + solo_gpio_exit(solo_dev); + solo_p2m_exit(solo_dev); + solo_i2c_exit(solo_dev); + + /* Now cleanup the PCI device */ + solo_irq_off(solo_dev, ~0); + pci_iounmap(pdev, solo_dev->reg_base); + if (pdev->irq) + free_irq(pdev->irq, solo_dev); + } + + pci_release_regions(pdev); + pci_disable_device(pdev); + v4l2_device_unregister(&solo_dev->v4l2_dev); + pci_set_drvdata(pdev, NULL); + + kfree(solo_dev); +} + +static ssize_t eeprom_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct solo_dev *solo_dev = + container_of(dev, struct solo_dev, dev); + unsigned short *p = (unsigned short *)buf; + int i; + + if (count & 0x1) + dev_warn(dev, "EEPROM Write not aligned (truncating)\n"); + + if (!full_eeprom && count > 64) { + dev_warn(dev, "EEPROM Write truncated to 64 bytes\n"); + count = 64; + } else if (full_eeprom && count > 128) { + dev_warn(dev, "EEPROM Write truncated to 128 bytes\n"); + count = 128; + } + + solo_eeprom_ewen(solo_dev, 1); + + for (i = full_eeprom ? 0 : 32; i < min((int)(full_eeprom ? 64 : 32), + (int)(count / 2)); i++) + solo_eeprom_write(solo_dev, i, cpu_to_be16(p[i])); + + solo_eeprom_ewen(solo_dev, 0); + + return count; +} + +static ssize_t eeprom_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct solo_dev *solo_dev = + container_of(dev, struct solo_dev, dev); + unsigned short *p = (unsigned short *)buf; + int count = (full_eeprom ? 128 : 64); + int i; + + for (i = (full_eeprom ? 0 : 32); i < (count / 2); i++) + p[i] = be16_to_cpu(solo_eeprom_read(solo_dev, i)); + + return count; +} + +static ssize_t p2m_timeouts_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct solo_dev *solo_dev = + container_of(dev, struct solo_dev, dev); + + return sprintf(buf, "%d\n", solo_dev->p2m_timeouts); +} + +static ssize_t sdram_size_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct solo_dev *solo_dev = + container_of(dev, struct solo_dev, dev); + + return sprintf(buf, "%dMegs\n", solo_dev->sdram_size >> 20); +} + +static ssize_t tw28xx_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct solo_dev *solo_dev = + container_of(dev, struct solo_dev, dev); + + return sprintf(buf, "tw2815[%d] tw2864[%d] tw2865[%d]\n", + hweight32(solo_dev->tw2815), + hweight32(solo_dev->tw2864), + hweight32(solo_dev->tw2865)); +} + +static ssize_t input_map_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct solo_dev *solo_dev = + container_of(dev, struct solo_dev, dev); + unsigned int val; + char *out = buf; + + val = solo_reg_read(solo_dev, SOLO_VI_CH_SWITCH_0); + out += sprintf(out, "Channel 0 => Input %d\n", val & 0x1f); + out += sprintf(out, "Channel 1 => Input %d\n", (val >> 5) & 0x1f); + out += sprintf(out, "Channel 2 => Input %d\n", (val >> 10) & 0x1f); + out += sprintf(out, "Channel 3 => Input %d\n", (val >> 15) & 0x1f); + out += sprintf(out, "Channel 4 => Input %d\n", (val >> 20) & 0x1f); + out += sprintf(out, "Channel 5 => Input %d\n", (val >> 25) & 0x1f); + + val = solo_reg_read(solo_dev, SOLO_VI_CH_SWITCH_1); + out += sprintf(out, "Channel 6 => Input %d\n", val & 0x1f); + out += sprintf(out, "Channel 7 => Input %d\n", (val >> 5) & 0x1f); + out += sprintf(out, "Channel 8 => Input %d\n", (val >> 10) & 0x1f); + out += sprintf(out, "Channel 9 => Input %d\n", (val >> 15) & 0x1f); + out += sprintf(out, "Channel 10 => Input %d\n", (val >> 20) & 0x1f); + out += sprintf(out, "Channel 11 => Input %d\n", (val >> 25) & 0x1f); + + val = solo_reg_read(solo_dev, SOLO_VI_CH_SWITCH_2); + out += sprintf(out, "Channel 12 => Input %d\n", val & 0x1f); + out += sprintf(out, "Channel 13 => Input %d\n", (val >> 5) & 0x1f); + out += sprintf(out, "Channel 14 => Input %d\n", (val >> 10) & 0x1f); + out += sprintf(out, "Channel 15 => Input %d\n", (val >> 15) & 0x1f); + out += sprintf(out, "Spot Output => Input %d\n", (val >> 20) & 0x1f); + + return out - buf; +} + +static ssize_t p2m_timeout_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct solo_dev *solo_dev = + container_of(dev, struct solo_dev, dev); + unsigned long ms; + + int ret = kstrtoul(buf, 10, &ms); + if (ret < 0 || ms > 200) + return -EINVAL; + solo_dev->p2m_jiffies = msecs_to_jiffies(ms); + + return count; +} + +static ssize_t p2m_timeout_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct solo_dev *solo_dev = + container_of(dev, struct solo_dev, dev); + + return sprintf(buf, "%ums\n", jiffies_to_msecs(solo_dev->p2m_jiffies)); +} + +static ssize_t intervals_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct solo_dev *solo_dev = + container_of(dev, struct solo_dev, dev); + char *out = buf; + int fps = solo_dev->fps; + int i; + + for (i = 0; i < solo_dev->nr_chans; i++) { + out += sprintf(out, "Channel %d: %d/%d (0x%08x)\n", + i, solo_dev->v4l2_enc[i]->interval, fps, + solo_reg_read(solo_dev, SOLO_CAP_CH_INTV(i))); + } + + return out - buf; +} + +static ssize_t sdram_offsets_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct solo_dev *solo_dev = + container_of(dev, struct solo_dev, dev); + char *out = buf; + + out += sprintf(out, "DISP: 0x%08x @ 0x%08x\n", + SOLO_DISP_EXT_ADDR, + SOLO_DISP_EXT_SIZE); + + out += sprintf(out, "EOSD: 0x%08x @ 0x%08x (0x%08x * %d)\n", + SOLO_EOSD_EXT_ADDR, + SOLO_EOSD_EXT_AREA(solo_dev), + SOLO_EOSD_EXT_SIZE(solo_dev), + SOLO_EOSD_EXT_AREA(solo_dev) / + SOLO_EOSD_EXT_SIZE(solo_dev)); + + out += sprintf(out, "MOTI: 0x%08x @ 0x%08x\n", + SOLO_MOTION_EXT_ADDR(solo_dev), + SOLO_MOTION_EXT_SIZE); + + out += sprintf(out, "G723: 0x%08x @ 0x%08x\n", + SOLO_G723_EXT_ADDR(solo_dev), + SOLO_G723_EXT_SIZE); + + out += sprintf(out, "CAPT: 0x%08x @ 0x%08x (0x%08x * %d)\n", + SOLO_CAP_EXT_ADDR(solo_dev), + SOLO_CAP_EXT_SIZE(solo_dev), + SOLO_CAP_PAGE_SIZE, + SOLO_CAP_EXT_SIZE(solo_dev) / SOLO_CAP_PAGE_SIZE); + + out += sprintf(out, "EREF: 0x%08x @ 0x%08x (0x%08x * %d)\n", + SOLO_EREF_EXT_ADDR(solo_dev), + SOLO_EREF_EXT_AREA(solo_dev), + SOLO_EREF_EXT_SIZE, + SOLO_EREF_EXT_AREA(solo_dev) / SOLO_EREF_EXT_SIZE); + + out += sprintf(out, "MPEG: 0x%08x @ 0x%08x\n", + SOLO_MP4E_EXT_ADDR(solo_dev), + SOLO_MP4E_EXT_SIZE(solo_dev)); + + out += sprintf(out, "JPEG: 0x%08x @ 0x%08x\n", + SOLO_JPEG_EXT_ADDR(solo_dev), + SOLO_JPEG_EXT_SIZE(solo_dev)); + + return out - buf; +} + +static ssize_t sdram_show(struct file *file, struct kobject *kobj, + struct bin_attribute *a, char *buf, + loff_t off, size_t count) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct solo_dev *solo_dev = + container_of(dev, struct solo_dev, dev); + const int size = solo_dev->sdram_size; + + if (off >= size) + return 0; + + if (off + count > size) + count = size - off; + + if (solo_p2m_dma(solo_dev, 0, buf, off, count, 0, 0)) + return -EIO; + + return count; +} + +static const struct device_attribute solo_dev_attrs[] = { + __ATTR(eeprom, 0640, eeprom_show, eeprom_store), + __ATTR(p2m_timeout, 0644, p2m_timeout_show, p2m_timeout_store), + __ATTR_RO(p2m_timeouts), + __ATTR_RO(sdram_size), + __ATTR_RO(tw28xx), + __ATTR_RO(input_map), + __ATTR_RO(intervals), + __ATTR_RO(sdram_offsets), +}; + +static void solo_device_release(struct device *dev) +{ + /* Do nothing */ +} + +static int solo_sysfs_init(struct solo_dev *solo_dev) +{ + struct bin_attribute *sdram_attr = &solo_dev->sdram_attr; + struct device *dev = &solo_dev->dev; + const char *driver; + int i; + + if (solo_dev->type == SOLO_DEV_6110) + driver = "solo6110"; + else + driver = "solo6010"; + + dev->release = solo_device_release; + dev->parent = &solo_dev->pdev->dev; + set_dev_node(dev, dev_to_node(&solo_dev->pdev->dev)); + dev_set_name(dev, "%s-%d-%d", driver, solo_dev->vfd->num, + solo_dev->nr_chans); + + if (device_register(dev)) { + dev->parent = NULL; + return -ENOMEM; + } + + for (i = 0; i < ARRAY_SIZE(solo_dev_attrs); i++) { + if (device_create_file(dev, &solo_dev_attrs[i])) { + device_unregister(dev); + return -ENOMEM; + } + } + + sysfs_attr_init(&sdram_attr->attr); + sdram_attr->attr.name = "sdram"; + sdram_attr->attr.mode = 0440; + sdram_attr->read = sdram_show; + sdram_attr->size = solo_dev->sdram_size; + + if (device_create_bin_file(dev, sdram_attr)) { + device_unregister(dev); + return -ENOMEM; + } + + return 0; +} + +static int solo_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) +{ + struct solo_dev *solo_dev; + int ret; + u8 chip_id; + + solo_dev = kzalloc(sizeof(*solo_dev), GFP_KERNEL); + if (solo_dev == NULL) + return -ENOMEM; + + if (id->driver_data == SOLO_DEV_6010) + dev_info(&pdev->dev, "Probing Softlogic 6010\n"); + else + dev_info(&pdev->dev, "Probing Softlogic 6110\n"); + + solo_dev->type = id->driver_data; + solo_dev->pdev = pdev; + spin_lock_init(&solo_dev->reg_io_lock); + ret = v4l2_device_register(&pdev->dev, &solo_dev->v4l2_dev); + if (ret) + goto fail_probe; + + /* Only for during init */ + solo_dev->p2m_jiffies = msecs_to_jiffies(100); + + ret = pci_enable_device(pdev); + if (ret) + goto fail_probe; + + pci_set_master(pdev); + + /* RETRY/TRDY Timeout disabled */ + pci_write_config_byte(pdev, 0x40, 0x00); + pci_write_config_byte(pdev, 0x41, 0x00); + + ret = pci_request_regions(pdev, SOLO6X10_NAME); + if (ret) + goto fail_probe; + + solo_dev->reg_base = pci_ioremap_bar(pdev, 0); + if (solo_dev->reg_base == NULL) { + ret = -ENOMEM; + goto fail_probe; + } + + chip_id = solo_reg_read(solo_dev, SOLO_CHIP_OPTION) & + SOLO_CHIP_ID_MASK; + switch (chip_id) { + case 7: + solo_dev->nr_chans = 16; + solo_dev->nr_ext = 5; + break; + case 6: + solo_dev->nr_chans = 8; + solo_dev->nr_ext = 2; + break; + default: + dev_warn(&pdev->dev, "Invalid chip_id 0x%02x, assuming 4 ch\n", + chip_id); + case 5: + solo_dev->nr_chans = 4; + solo_dev->nr_ext = 1; + } + + /* Disable all interrupts to start */ + solo_irq_off(solo_dev, ~0); + + /* Initial global settings */ + if (solo_dev->type == SOLO_DEV_6010) { + solo_dev->clock_mhz = 108; + solo_dev->sys_config = SOLO_SYS_CFG_SDRAM64BIT + | SOLO_SYS_CFG_INPUTDIV(25) + | SOLO_SYS_CFG_FEEDBACKDIV(solo_dev->clock_mhz * 2 - 2) + | SOLO_SYS_CFG_OUTDIV(3); + solo_reg_write(solo_dev, SOLO_SYS_CFG, solo_dev->sys_config); + } else { + u32 divq, divf; + + solo_dev->clock_mhz = 135; + + if (solo_dev->clock_mhz < 125) { + divq = 3; + divf = (solo_dev->clock_mhz * 4) / 3 - 1; + } else { + divq = 2; + divf = (solo_dev->clock_mhz * 2) / 3 - 1; + } + + solo_reg_write(solo_dev, SOLO_PLL_CONFIG, + (1 << 20) | /* PLL_RANGE */ + (8 << 15) | /* PLL_DIVR */ + (divq << 12) | + (divf << 4) | + (1 << 1) /* PLL_FSEN */); + + solo_dev->sys_config = SOLO_SYS_CFG_SDRAM64BIT; + } + + solo_reg_write(solo_dev, SOLO_SYS_CFG, solo_dev->sys_config); + solo_reg_write(solo_dev, SOLO_TIMER_CLOCK_NUM, + solo_dev->clock_mhz - 1); + + /* PLL locking time of 1ms */ + mdelay(1); + + ret = request_irq(pdev->irq, solo_isr, IRQF_SHARED, SOLO6X10_NAME, + solo_dev); + if (ret) + goto fail_probe; + + /* Handle this from the start */ + solo_irq_on(solo_dev, SOLO_IRQ_PCI_ERR); + + ret = solo_i2c_init(solo_dev); + if (ret) + goto fail_probe; + + /* Setup the DMA engine */ + solo_reg_write(solo_dev, SOLO_DMA_CTRL, + SOLO_DMA_CTRL_REFRESH_CYCLE(1) | + SOLO_DMA_CTRL_SDRAM_SIZE(2) | + SOLO_DMA_CTRL_SDRAM_CLK_INVERT | + SOLO_DMA_CTRL_READ_CLK_SELECT | + SOLO_DMA_CTRL_LATENCY(1)); + + /* Undocumented crap */ + solo_reg_write(solo_dev, SOLO_DMA_CTRL1, + solo_dev->type == SOLO_DEV_6010 ? 0x100 : 0x300); + + if (solo_dev->type != SOLO_DEV_6010) { + solo_dev->usec_lsb = 0x3f; + solo_set_time(solo_dev); + } + + /* Disable watchdog */ + solo_reg_write(solo_dev, SOLO_WATCHDOG, 0); + + /* Initialize sub components */ + + ret = solo_p2m_init(solo_dev); + if (ret) + goto fail_probe; + + ret = solo_disp_init(solo_dev); + if (ret) + goto fail_probe; + + ret = solo_gpio_init(solo_dev); + if (ret) + goto fail_probe; + + ret = solo_tw28_init(solo_dev); + if (ret) + goto fail_probe; + + ret = solo_v4l2_init(solo_dev, video_nr); + if (ret) + goto fail_probe; + + ret = solo_enc_init(solo_dev); + if (ret) + goto fail_probe; + + ret = solo_enc_v4l2_init(solo_dev, video_nr); + if (ret) + goto fail_probe; + + ret = solo_g723_init(solo_dev); + if (ret) + goto fail_probe; + + ret = solo_sysfs_init(solo_dev); + if (ret) + goto fail_probe; + + /* Now that init is over, set this lower */ + solo_dev->p2m_jiffies = msecs_to_jiffies(20); + + return 0; + +fail_probe: + free_solo_dev(solo_dev); + return ret; +} + +static void solo_pci_remove(struct pci_dev *pdev) +{ + struct v4l2_device *v4l2_dev = pci_get_drvdata(pdev); + struct solo_dev *solo_dev = container_of(v4l2_dev, struct solo_dev, v4l2_dev); + + free_solo_dev(solo_dev); +} + +static const struct pci_device_id solo_id_table[] = { + /* 6010 based cards */ + { PCI_DEVICE(PCI_VENDOR_ID_SOFTLOGIC, PCI_DEVICE_ID_SOLO6010), + .driver_data = SOLO_DEV_6010 }, + { PCI_DEVICE(PCI_VENDOR_ID_BLUECHERRY, PCI_DEVICE_ID_NEUSOLO_4), + .driver_data = SOLO_DEV_6010 }, + { PCI_DEVICE(PCI_VENDOR_ID_BLUECHERRY, PCI_DEVICE_ID_NEUSOLO_9), + .driver_data = SOLO_DEV_6010 }, + { PCI_DEVICE(PCI_VENDOR_ID_BLUECHERRY, PCI_DEVICE_ID_NEUSOLO_16), + .driver_data = SOLO_DEV_6010 }, + { PCI_DEVICE(PCI_VENDOR_ID_BLUECHERRY, PCI_DEVICE_ID_BC_SOLO_4), + .driver_data = SOLO_DEV_6010 }, + { PCI_DEVICE(PCI_VENDOR_ID_BLUECHERRY, PCI_DEVICE_ID_BC_SOLO_9), + .driver_data = SOLO_DEV_6010 }, + { PCI_DEVICE(PCI_VENDOR_ID_BLUECHERRY, PCI_DEVICE_ID_BC_SOLO_16), + .driver_data = SOLO_DEV_6010 }, + /* 6110 based cards */ + { PCI_DEVICE(PCI_VENDOR_ID_SOFTLOGIC, PCI_DEVICE_ID_SOLO6110), + .driver_data = SOLO_DEV_6110 }, + { PCI_DEVICE(PCI_VENDOR_ID_BLUECHERRY, PCI_DEVICE_ID_BC_6110_4), + .driver_data = SOLO_DEV_6110 }, + { PCI_DEVICE(PCI_VENDOR_ID_BLUECHERRY, PCI_DEVICE_ID_BC_6110_8), + .driver_data = SOLO_DEV_6110 }, + { PCI_DEVICE(PCI_VENDOR_ID_BLUECHERRY, PCI_DEVICE_ID_BC_6110_16), + .driver_data = SOLO_DEV_6110 }, + {0,} +}; + +MODULE_DEVICE_TABLE(pci, solo_id_table); + +static struct pci_driver solo_pci_driver = { + .name = SOLO6X10_NAME, + .id_table = solo_id_table, + .probe = solo_pci_probe, + .remove = solo_pci_remove, +}; + +module_pci_driver(solo_pci_driver); diff --git a/drivers/staging/media/solo6x10/solo6x10-disp.c b/drivers/staging/media/solo6x10/solo6x10-disp.c new file mode 100644 index 00000000000..145295a5db7 --- /dev/null +++ b/drivers/staging/media/solo6x10/solo6x10-disp.c @@ -0,0 +1,322 @@ +/* + * Copyright (C) 2010-2013 Bluecherry, LLC <http://www.bluecherrydvr.com> + * + * Original author: + * Ben Collins <bcollins@ubuntu.com> + * + * Additional work by: + * John Brooks <john.brooks@bluecherry.net> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/videodev2.h> +#include <media/v4l2-ioctl.h> + +#include "solo6x10.h" + +#define SOLO_VCLK_DELAY 3 +#define SOLO_PROGRESSIVE_VSIZE 1024 + +#define SOLO_MOT_THRESH_W 64 +#define SOLO_MOT_THRESH_H 64 +#define SOLO_MOT_THRESH_SIZE 8192 +#define SOLO_MOT_THRESH_REAL (SOLO_MOT_THRESH_W * SOLO_MOT_THRESH_H) +#define SOLO_MOT_FLAG_SIZE 1024 +#define SOLO_MOT_FLAG_AREA (SOLO_MOT_FLAG_SIZE * 16) + +static void solo_vin_config(struct solo_dev *solo_dev) +{ + solo_dev->vin_hstart = 8; + solo_dev->vin_vstart = 2; + + solo_reg_write(solo_dev, SOLO_SYS_VCLK, + SOLO_VCLK_SELECT(2) | + SOLO_VCLK_VIN1415_DELAY(SOLO_VCLK_DELAY) | + SOLO_VCLK_VIN1213_DELAY(SOLO_VCLK_DELAY) | + SOLO_VCLK_VIN1011_DELAY(SOLO_VCLK_DELAY) | + SOLO_VCLK_VIN0809_DELAY(SOLO_VCLK_DELAY) | + SOLO_VCLK_VIN0607_DELAY(SOLO_VCLK_DELAY) | + SOLO_VCLK_VIN0405_DELAY(SOLO_VCLK_DELAY) | + SOLO_VCLK_VIN0203_DELAY(SOLO_VCLK_DELAY) | + SOLO_VCLK_VIN0001_DELAY(SOLO_VCLK_DELAY)); + + solo_reg_write(solo_dev, SOLO_VI_ACT_I_P, + SOLO_VI_H_START(solo_dev->vin_hstart) | + SOLO_VI_V_START(solo_dev->vin_vstart) | + SOLO_VI_V_STOP(solo_dev->vin_vstart + + solo_dev->video_vsize)); + + solo_reg_write(solo_dev, SOLO_VI_ACT_I_S, + SOLO_VI_H_START(solo_dev->vout_hstart) | + SOLO_VI_V_START(solo_dev->vout_vstart) | + SOLO_VI_V_STOP(solo_dev->vout_vstart + + solo_dev->video_vsize)); + + solo_reg_write(solo_dev, SOLO_VI_ACT_P, + SOLO_VI_H_START(0) | + SOLO_VI_V_START(1) | + SOLO_VI_V_STOP(SOLO_PROGRESSIVE_VSIZE)); + + solo_reg_write(solo_dev, SOLO_VI_CH_FORMAT, + SOLO_VI_FD_SEL_MASK(0) | SOLO_VI_PROG_MASK(0)); + + /* On 6110, initialize mozaic darkness stength */ + if (solo_dev->type == SOLO_DEV_6010) + solo_reg_write(solo_dev, SOLO_VI_FMT_CFG, 0); + else + solo_reg_write(solo_dev, SOLO_VI_FMT_CFG, 16 << 22); + + solo_reg_write(solo_dev, SOLO_VI_PAGE_SW, 2); + + if (solo_dev->video_type == SOLO_VO_FMT_TYPE_NTSC) { + solo_reg_write(solo_dev, SOLO_VI_PB_CONFIG, + SOLO_VI_PB_USER_MODE); + solo_reg_write(solo_dev, SOLO_VI_PB_RANGE_HV, + SOLO_VI_PB_HSIZE(858) | SOLO_VI_PB_VSIZE(246)); + solo_reg_write(solo_dev, SOLO_VI_PB_ACT_V, + SOLO_VI_PB_VSTART(4) | + SOLO_VI_PB_VSTOP(4 + 240)); + } else { + solo_reg_write(solo_dev, SOLO_VI_PB_CONFIG, + SOLO_VI_PB_USER_MODE | SOLO_VI_PB_PAL); + solo_reg_write(solo_dev, SOLO_VI_PB_RANGE_HV, + SOLO_VI_PB_HSIZE(864) | SOLO_VI_PB_VSIZE(294)); + solo_reg_write(solo_dev, SOLO_VI_PB_ACT_V, + SOLO_VI_PB_VSTART(4) | + SOLO_VI_PB_VSTOP(4 + 288)); + } + solo_reg_write(solo_dev, SOLO_VI_PB_ACT_H, SOLO_VI_PB_HSTART(16) | + SOLO_VI_PB_HSTOP(16 + 720)); +} + +static void solo_vout_config_cursor(struct solo_dev *dev) +{ + int i; + + /* Load (blank) cursor bitmap mask (2bpp) */ + for (i = 0; i < 20; i++) + solo_reg_write(dev, SOLO_VO_CURSOR_MASK(i), 0); + + solo_reg_write(dev, SOLO_VO_CURSOR_POS, 0); + + solo_reg_write(dev, SOLO_VO_CURSOR_CLR, + (0x80 << 24) | (0x80 << 16) | (0x10 << 8) | 0x80); + solo_reg_write(dev, SOLO_VO_CURSOR_CLR2, (0xe0 << 8) | 0x80); +} + +static void solo_vout_config(struct solo_dev *solo_dev) +{ + solo_dev->vout_hstart = 6; + solo_dev->vout_vstart = 8; + + solo_reg_write(solo_dev, SOLO_VO_FMT_ENC, + solo_dev->video_type | + SOLO_VO_USER_COLOR_SET_NAV | + SOLO_VO_USER_COLOR_SET_NAH | + SOLO_VO_NA_COLOR_Y(0) | + SOLO_VO_NA_COLOR_CB(0) | + SOLO_VO_NA_COLOR_CR(0)); + + solo_reg_write(solo_dev, SOLO_VO_ACT_H, + SOLO_VO_H_START(solo_dev->vout_hstart) | + SOLO_VO_H_STOP(solo_dev->vout_hstart + + solo_dev->video_hsize)); + + solo_reg_write(solo_dev, SOLO_VO_ACT_V, + SOLO_VO_V_START(solo_dev->vout_vstart) | + SOLO_VO_V_STOP(solo_dev->vout_vstart + + solo_dev->video_vsize)); + + solo_reg_write(solo_dev, SOLO_VO_RANGE_HV, + SOLO_VO_H_LEN(solo_dev->video_hsize) | + SOLO_VO_V_LEN(solo_dev->video_vsize)); + + /* Border & background colors */ + solo_reg_write(solo_dev, SOLO_VO_BORDER_LINE_COLOR, + (0xa0 << 24) | (0x88 << 16) | (0xa0 << 8) | 0x88); + solo_reg_write(solo_dev, SOLO_VO_BORDER_FILL_COLOR, + (0x10 << 24) | (0x8f << 16) | (0x10 << 8) | 0x8f); + solo_reg_write(solo_dev, SOLO_VO_BKG_COLOR, + (16 << 24) | (128 << 16) | (16 << 8) | 128); + + solo_reg_write(solo_dev, SOLO_VO_DISP_ERASE, SOLO_VO_DISP_ERASE_ON); + + solo_reg_write(solo_dev, SOLO_VI_WIN_SW, 0); + + solo_reg_write(solo_dev, SOLO_VO_ZOOM_CTRL, 0); + solo_reg_write(solo_dev, SOLO_VO_FREEZE_CTRL, 0); + + solo_reg_write(solo_dev, SOLO_VO_DISP_CTRL, SOLO_VO_DISP_ON | + SOLO_VO_DISP_ERASE_COUNT(8) | + SOLO_VO_DISP_BASE(SOLO_DISP_EXT_ADDR)); + + + solo_vout_config_cursor(solo_dev); + + /* Enable channels we support */ + solo_reg_write(solo_dev, SOLO_VI_CH_ENA, + (1 << solo_dev->nr_chans) - 1); +} + +static int solo_dma_vin_region(struct solo_dev *solo_dev, u32 off, + u16 val, int reg_size) +{ + u16 *buf; + const int n = 64, size = n * sizeof(*buf); + int i, ret = 0; + + buf = kmalloc(size, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + for (i = 0; i < n; i++) + buf[i] = cpu_to_le16(val); + + for (i = 0; i < reg_size; i += size) { + ret = solo_p2m_dma(solo_dev, 1, buf, + SOLO_MOTION_EXT_ADDR(solo_dev) + off + i, + size, 0, 0); + + if (ret) + break; + } + + kfree(buf); + return ret; +} + +int solo_set_motion_threshold(struct solo_dev *solo_dev, u8 ch, u16 val) +{ + if (ch > solo_dev->nr_chans) + return -EINVAL; + + return solo_dma_vin_region(solo_dev, SOLO_MOT_FLAG_AREA + + (ch * SOLO_MOT_THRESH_SIZE * 2), + val, SOLO_MOT_THRESH_SIZE); +} + +int solo_set_motion_block(struct solo_dev *solo_dev, u8 ch, + const struct solo_motion_thresholds *thresholds) +{ + u32 off = SOLO_MOT_FLAG_AREA + ch * SOLO_MOT_THRESH_SIZE * 2; + u16 buf[64]; + int x, y; + int ret = 0; + + memset(buf, 0, sizeof(buf)); + for (y = 0; y < SOLO_MOTION_SZ; y++) { + for (x = 0; x < SOLO_MOTION_SZ; x++) + buf[x] = cpu_to_le16(thresholds->thresholds[y][x]); + ret |= solo_p2m_dma(solo_dev, 1, buf, + SOLO_MOTION_EXT_ADDR(solo_dev) + off + y * sizeof(buf), + sizeof(buf), 0, 0); + } + return ret; +} + +/* First 8k is motion flag (512 bytes * 16). Following that is an 8k+8k + * threshold and working table for each channel. Atleast that's what the + * spec says. However, this code (taken from rdk) has some mystery 8k + * block right after the flag area, before the first thresh table. */ +static void solo_motion_config(struct solo_dev *solo_dev) +{ + int i; + + for (i = 0; i < solo_dev->nr_chans; i++) { + /* Clear motion flag area */ + solo_dma_vin_region(solo_dev, i * SOLO_MOT_FLAG_SIZE, 0x0000, + SOLO_MOT_FLAG_SIZE); + + /* Clear working cache table */ + solo_dma_vin_region(solo_dev, SOLO_MOT_FLAG_AREA + + (i * SOLO_MOT_THRESH_SIZE * 2) + + SOLO_MOT_THRESH_SIZE, 0x0000, + SOLO_MOT_THRESH_SIZE); + + /* Set default threshold table */ + solo_set_motion_threshold(solo_dev, i, SOLO_DEF_MOT_THRESH); + } + + /* Default motion settings */ + solo_reg_write(solo_dev, SOLO_VI_MOT_ADR, SOLO_VI_MOTION_EN(0) | + (SOLO_MOTION_EXT_ADDR(solo_dev) >> 16)); + solo_reg_write(solo_dev, SOLO_VI_MOT_CTRL, + SOLO_VI_MOTION_FRAME_COUNT(3) | + SOLO_VI_MOTION_SAMPLE_LENGTH(solo_dev->video_hsize / 16) + /* | SOLO_VI_MOTION_INTR_START_STOP */ + | SOLO_VI_MOTION_SAMPLE_COUNT(10)); + + solo_reg_write(solo_dev, SOLO_VI_MOTION_BORDER, 0); + solo_reg_write(solo_dev, SOLO_VI_MOTION_BAR, 0); +} + +int solo_disp_init(struct solo_dev *solo_dev) +{ + int i; + + solo_dev->video_hsize = 704; + if (solo_dev->video_type == SOLO_VO_FMT_TYPE_NTSC) { + solo_dev->video_vsize = 240; + solo_dev->fps = 30; + } else { + solo_dev->video_vsize = 288; + solo_dev->fps = 25; + } + + solo_vin_config(solo_dev); + solo_motion_config(solo_dev); + solo_vout_config(solo_dev); + + for (i = 0; i < solo_dev->nr_chans; i++) + solo_reg_write(solo_dev, SOLO_VI_WIN_ON(i), 1); + + return 0; +} + +void solo_disp_exit(struct solo_dev *solo_dev) +{ + int i; + + solo_reg_write(solo_dev, SOLO_VO_DISP_CTRL, 0); + solo_reg_write(solo_dev, SOLO_VO_ZOOM_CTRL, 0); + solo_reg_write(solo_dev, SOLO_VO_FREEZE_CTRL, 0); + + for (i = 0; i < solo_dev->nr_chans; i++) { + solo_reg_write(solo_dev, SOLO_VI_WIN_CTRL0(i), 0); + solo_reg_write(solo_dev, SOLO_VI_WIN_CTRL1(i), 0); + solo_reg_write(solo_dev, SOLO_VI_WIN_ON(i), 0); + } + + /* Set default border */ + for (i = 0; i < 5; i++) + solo_reg_write(solo_dev, SOLO_VO_BORDER_X(i), 0); + + for (i = 0; i < 5; i++) + solo_reg_write(solo_dev, SOLO_VO_BORDER_Y(i), 0); + + solo_reg_write(solo_dev, SOLO_VO_BORDER_LINE_MASK, 0); + solo_reg_write(solo_dev, SOLO_VO_BORDER_FILL_MASK, 0); + + solo_reg_write(solo_dev, SOLO_VO_RECTANGLE_CTRL(0), 0); + solo_reg_write(solo_dev, SOLO_VO_RECTANGLE_START(0), 0); + solo_reg_write(solo_dev, SOLO_VO_RECTANGLE_STOP(0), 0); + + solo_reg_write(solo_dev, SOLO_VO_RECTANGLE_CTRL(1), 0); + solo_reg_write(solo_dev, SOLO_VO_RECTANGLE_START(1), 0); + solo_reg_write(solo_dev, SOLO_VO_RECTANGLE_STOP(1), 0); +} diff --git a/drivers/staging/media/solo6x10/solo6x10-eeprom.c b/drivers/staging/media/solo6x10/solo6x10-eeprom.c new file mode 100644 index 00000000000..9d1c9bb53d6 --- /dev/null +++ b/drivers/staging/media/solo6x10/solo6x10-eeprom.c @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2010-2013 Bluecherry, LLC <http://www.bluecherrydvr.com> + * + * Original author: + * Ben Collins <bcollins@ubuntu.com> + * + * Additional work by: + * John Brooks <john.brooks@bluecherry.net> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include <linux/kernel.h> +#include <linux/delay.h> + +#include "solo6x10.h" + +/* Control */ +#define EE_SHIFT_CLK 0x04 +#define EE_CS 0x08 +#define EE_DATA_WRITE 0x02 +#define EE_DATA_READ 0x01 +#define EE_ENB (0x80 | EE_CS) + +#define eeprom_delay() udelay(100) +#if 0 +#define eeprom_delay() solo_reg_read(solo_dev, SOLO_EEPROM_CTRL) +#define eeprom_delay() ({ \ + int i, ret; \ + udelay(100); \ + for (i = ret = 0; i < 1000 && !ret; i++) \ + ret = solo_eeprom_reg_read(solo_dev); \ +}) +#endif +#define ADDR_LEN 6 + +/* Commands */ +#define EE_EWEN_CMD 4 +#define EE_EWDS_CMD 4 +#define EE_WRITE_CMD 5 +#define EE_READ_CMD 6 +#define EE_ERASE_CMD 7 + +static unsigned int solo_eeprom_reg_read(struct solo_dev *solo_dev) +{ + return solo_reg_read(solo_dev, SOLO_EEPROM_CTRL) & EE_DATA_READ; +} + +static void solo_eeprom_reg_write(struct solo_dev *solo_dev, u32 data) +{ + solo_reg_write(solo_dev, SOLO_EEPROM_CTRL, data); + eeprom_delay(); +} + +static void solo_eeprom_cmd(struct solo_dev *solo_dev, int cmd) +{ + int i; + + solo_eeprom_reg_write(solo_dev, SOLO_EEPROM_ACCESS_EN); + solo_eeprom_reg_write(solo_dev, SOLO_EEPROM_ENABLE); + + for (i = 4 + ADDR_LEN; i >= 0; i--) { + int dataval = (cmd & (1 << i)) ? EE_DATA_WRITE : 0; + + solo_eeprom_reg_write(solo_dev, SOLO_EEPROM_ENABLE | dataval); + solo_eeprom_reg_write(solo_dev, SOLO_EEPROM_ENABLE | + EE_SHIFT_CLK | dataval); + } + + solo_eeprom_reg_write(solo_dev, SOLO_EEPROM_ENABLE); +} + +unsigned int solo_eeprom_ewen(struct solo_dev *solo_dev, int w_en) +{ + int ewen_cmd = (w_en ? 0x3f : 0) | (EE_EWEN_CMD << ADDR_LEN); + unsigned int retval = 0; + int i; + + solo_eeprom_cmd(solo_dev, ewen_cmd); + + for (i = 0; i < 16; i++) { + solo_eeprom_reg_write(solo_dev, SOLO_EEPROM_ENABLE | + EE_SHIFT_CLK); + retval = (retval << 1) | solo_eeprom_reg_read(solo_dev); + solo_eeprom_reg_write(solo_dev, SOLO_EEPROM_ENABLE); + retval = (retval << 1) | solo_eeprom_reg_read(solo_dev); + } + + solo_eeprom_reg_write(solo_dev, ~EE_CS); + retval = (retval << 1) | solo_eeprom_reg_read(solo_dev); + + return retval; +} + +unsigned short solo_eeprom_read(struct solo_dev *solo_dev, int loc) +{ + int read_cmd = loc | (EE_READ_CMD << ADDR_LEN); + unsigned short retval = 0; + int i; + + solo_eeprom_cmd(solo_dev, read_cmd); + + for (i = 0; i < 16; i++) { + solo_eeprom_reg_write(solo_dev, SOLO_EEPROM_ENABLE | + EE_SHIFT_CLK); + retval = (retval << 1) | solo_eeprom_reg_read(solo_dev); + solo_eeprom_reg_write(solo_dev, SOLO_EEPROM_ENABLE); + } + + solo_eeprom_reg_write(solo_dev, ~EE_CS); + + return retval; +} + +int solo_eeprom_write(struct solo_dev *solo_dev, int loc, + unsigned short data) +{ + int write_cmd = loc | (EE_WRITE_CMD << ADDR_LEN); + unsigned int retval; + int i; + + solo_eeprom_cmd(solo_dev, write_cmd); + + for (i = 15; i >= 0; i--) { + unsigned int dataval = (data >> i) & 1; + + solo_eeprom_reg_write(solo_dev, EE_ENB); + solo_eeprom_reg_write(solo_dev, + EE_ENB | (dataval << 1) | EE_SHIFT_CLK); + } + + solo_eeprom_reg_write(solo_dev, EE_ENB); + solo_eeprom_reg_write(solo_dev, ~EE_CS); + solo_eeprom_reg_write(solo_dev, EE_ENB); + + for (i = retval = 0; i < 10000 && !retval; i++) + retval = solo_eeprom_reg_read(solo_dev); + + solo_eeprom_reg_write(solo_dev, ~EE_CS); + + return !retval; +} diff --git a/drivers/staging/media/solo6x10/solo6x10-enc.c b/drivers/staging/media/solo6x10/solo6x10-enc.c new file mode 100644 index 00000000000..2db53b68c62 --- /dev/null +++ b/drivers/staging/media/solo6x10/solo6x10-enc.c @@ -0,0 +1,344 @@ +/* + * Copyright (C) 2010-2013 Bluecherry, LLC <http://www.bluecherrydvr.com> + * + * Original author: + * Ben Collins <bcollins@ubuntu.com> + * + * Additional work by: + * John Brooks <john.brooks@bluecherry.net> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include <linux/kernel.h> +#include <linux/font.h> +#include <linux/bitrev.h> +#include <linux/slab.h> + +#include "solo6x10.h" + +#define VI_PROG_HSIZE (1280 - 16) +#define VI_PROG_VSIZE (1024 - 16) + +#define IRQ_LEVEL 2 + +static void solo_capture_config(struct solo_dev *solo_dev) +{ + unsigned long height; + unsigned long width; + void *buf; + int i; + + solo_reg_write(solo_dev, SOLO_CAP_BASE, + SOLO_CAP_MAX_PAGE((SOLO_CAP_EXT_SIZE(solo_dev) + - SOLO_CAP_PAGE_SIZE) >> 16) + | SOLO_CAP_BASE_ADDR(SOLO_CAP_EXT_ADDR(solo_dev) >> 16)); + + /* XXX: Undocumented bits at b17 and b24 */ + if (solo_dev->type == SOLO_DEV_6110) { + /* NOTE: Ref driver has (62 << 24) here as well, but it causes + * wacked out frame timing on 4-port 6110. */ + solo_reg_write(solo_dev, SOLO_CAP_BTW, + (1 << 17) | SOLO_CAP_PROG_BANDWIDTH(2) | + SOLO_CAP_MAX_BANDWIDTH(36)); + } else { + solo_reg_write(solo_dev, SOLO_CAP_BTW, + (1 << 17) | SOLO_CAP_PROG_BANDWIDTH(2) | + SOLO_CAP_MAX_BANDWIDTH(32)); + } + + /* Set scale 1, 9 dimension */ + width = solo_dev->video_hsize; + height = solo_dev->video_vsize; + solo_reg_write(solo_dev, SOLO_DIM_SCALE1, + SOLO_DIM_H_MB_NUM(width / 16) | + SOLO_DIM_V_MB_NUM_FRAME(height / 8) | + SOLO_DIM_V_MB_NUM_FIELD(height / 16)); + + /* Set scale 2, 10 dimension */ + width = solo_dev->video_hsize / 2; + height = solo_dev->video_vsize; + solo_reg_write(solo_dev, SOLO_DIM_SCALE2, + SOLO_DIM_H_MB_NUM(width / 16) | + SOLO_DIM_V_MB_NUM_FRAME(height / 8) | + SOLO_DIM_V_MB_NUM_FIELD(height / 16)); + + /* Set scale 3, 11 dimension */ + width = solo_dev->video_hsize / 2; + height = solo_dev->video_vsize / 2; + solo_reg_write(solo_dev, SOLO_DIM_SCALE3, + SOLO_DIM_H_MB_NUM(width / 16) | + SOLO_DIM_V_MB_NUM_FRAME(height / 8) | + SOLO_DIM_V_MB_NUM_FIELD(height / 16)); + + /* Set scale 4, 12 dimension */ + width = solo_dev->video_hsize / 3; + height = solo_dev->video_vsize / 3; + solo_reg_write(solo_dev, SOLO_DIM_SCALE4, + SOLO_DIM_H_MB_NUM(width / 16) | + SOLO_DIM_V_MB_NUM_FRAME(height / 8) | + SOLO_DIM_V_MB_NUM_FIELD(height / 16)); + + /* Set scale 5, 13 dimension */ + width = solo_dev->video_hsize / 4; + height = solo_dev->video_vsize / 2; + solo_reg_write(solo_dev, SOLO_DIM_SCALE5, + SOLO_DIM_H_MB_NUM(width / 16) | + SOLO_DIM_V_MB_NUM_FRAME(height / 8) | + SOLO_DIM_V_MB_NUM_FIELD(height / 16)); + + /* Progressive */ + width = VI_PROG_HSIZE; + height = VI_PROG_VSIZE; + solo_reg_write(solo_dev, SOLO_DIM_PROG, + SOLO_DIM_H_MB_NUM(width / 16) | + SOLO_DIM_V_MB_NUM_FRAME(height / 16) | + SOLO_DIM_V_MB_NUM_FIELD(height / 16)); + + /* Clear OSD */ + solo_reg_write(solo_dev, SOLO_VE_OSD_CH, 0); + solo_reg_write(solo_dev, SOLO_VE_OSD_BASE, SOLO_EOSD_EXT_ADDR >> 16); + solo_reg_write(solo_dev, SOLO_VE_OSD_CLR, + 0xF0 << 16 | 0x80 << 8 | 0x80); + + if (solo_dev->type == SOLO_DEV_6010) + solo_reg_write(solo_dev, SOLO_VE_OSD_OPT, + SOLO_VE_OSD_H_SHADOW | SOLO_VE_OSD_V_SHADOW); + else + solo_reg_write(solo_dev, SOLO_VE_OSD_OPT, SOLO_VE_OSD_V_DOUBLE + | SOLO_VE_OSD_H_SHADOW | SOLO_VE_OSD_V_SHADOW); + + /* Clear OSG buffer */ + buf = kzalloc(SOLO_EOSD_EXT_SIZE(solo_dev), GFP_KERNEL); + if (!buf) + return; + + for (i = 0; i < solo_dev->nr_chans; i++) { + solo_p2m_dma(solo_dev, 1, buf, + SOLO_EOSD_EXT_ADDR + + (SOLO_EOSD_EXT_SIZE(solo_dev) * i), + SOLO_EOSD_EXT_SIZE(solo_dev), 0, 0); + } + kfree(buf); +} + +#define SOLO_OSD_WRITE_SIZE (16 * OSD_TEXT_MAX) + +/* Should be called with enable_lock held */ +int solo_osd_print(struct solo_enc_dev *solo_enc) +{ + struct solo_dev *solo_dev = solo_enc->solo_dev; + unsigned char *str = solo_enc->osd_text; + u8 *buf = solo_enc->osd_buf; + u32 reg; + const struct font_desc *vga = find_font("VGA8x16"); + const unsigned char *vga_data; + int i, j; + + if (WARN_ON_ONCE(!vga)) + return -ENODEV; + + reg = solo_reg_read(solo_dev, SOLO_VE_OSD_CH); + if (!*str) { + /* Disable OSD on this channel */ + reg &= ~(1 << solo_enc->ch); + goto out; + } + + memset(buf, 0, SOLO_OSD_WRITE_SIZE); + vga_data = (const unsigned char *)vga->data; + + for (i = 0; *str; i++, str++) { + for (j = 0; j < 16; j++) { + buf[(j << 1) | (i & 1) | ((i & ~1) << 4)] = + bitrev8(vga_data[(*str << 4) | j]); + } + } + + solo_p2m_dma(solo_dev, 1, buf, + SOLO_EOSD_EXT_ADDR_CHAN(solo_dev, solo_enc->ch), + SOLO_OSD_WRITE_SIZE, 0, 0); + + /* Enable OSD on this channel */ + reg |= (1 << solo_enc->ch); + +out: + solo_reg_write(solo_dev, SOLO_VE_OSD_CH, reg); + return 0; +} + +/** + * Set channel Quality Profile (0-3). + */ +void solo_s_jpeg_qp(struct solo_dev *solo_dev, unsigned int ch, + unsigned int qp) +{ + unsigned long flags; + unsigned int idx, reg; + + if ((ch > 31) || (qp > 3)) + return; + + if (solo_dev->type == SOLO_DEV_6010) + return; + + if (ch < 16) { + idx = 0; + reg = SOLO_VE_JPEG_QP_CH_L; + } else { + ch -= 16; + idx = 1; + reg = SOLO_VE_JPEG_QP_CH_H; + } + ch *= 2; + + spin_lock_irqsave(&solo_dev->jpeg_qp_lock, flags); + + solo_dev->jpeg_qp[idx] &= ~(3 << ch); + solo_dev->jpeg_qp[idx] |= (qp & 3) << ch; + + solo_reg_write(solo_dev, reg, solo_dev->jpeg_qp[idx]); + + spin_unlock_irqrestore(&solo_dev->jpeg_qp_lock, flags); +} + +int solo_g_jpeg_qp(struct solo_dev *solo_dev, unsigned int ch) +{ + int idx; + + if (solo_dev->type == SOLO_DEV_6010) + return 2; + + if (WARN_ON_ONCE(ch > 31)) + return 2; + + if (ch < 16) { + idx = 0; + } else { + ch -= 16; + idx = 1; + } + ch *= 2; + + return (solo_dev->jpeg_qp[idx] >> ch) & 3; +} + +#define SOLO_QP_INIT 0xaaaaaaaa + +static void solo_jpeg_config(struct solo_dev *solo_dev) +{ + if (solo_dev->type == SOLO_DEV_6010) { + solo_reg_write(solo_dev, SOLO_VE_JPEG_QP_TBL, + (2 << 24) | (2 << 16) | (2 << 8) | 2); + } else { + solo_reg_write(solo_dev, SOLO_VE_JPEG_QP_TBL, + (4 << 24) | (3 << 16) | (2 << 8) | 1); + } + + spin_lock_init(&solo_dev->jpeg_qp_lock); + + /* Initialize Quality Profile for all channels */ + solo_dev->jpeg_qp[0] = solo_dev->jpeg_qp[1] = SOLO_QP_INIT; + solo_reg_write(solo_dev, SOLO_VE_JPEG_QP_CH_L, SOLO_QP_INIT); + solo_reg_write(solo_dev, SOLO_VE_JPEG_QP_CH_H, SOLO_QP_INIT); + + solo_reg_write(solo_dev, SOLO_VE_JPEG_CFG, + (SOLO_JPEG_EXT_SIZE(solo_dev) & 0xffff0000) | + ((SOLO_JPEG_EXT_ADDR(solo_dev) >> 16) & 0x0000ffff)); + solo_reg_write(solo_dev, SOLO_VE_JPEG_CTRL, 0xffffffff); + if (solo_dev->type == SOLO_DEV_6110) { + solo_reg_write(solo_dev, SOLO_VE_JPEG_CFG1, + (0 << 16) | (30 << 8) | 60); + } +} + +static void solo_mp4e_config(struct solo_dev *solo_dev) +{ + int i; + u32 cfg; + + solo_reg_write(solo_dev, SOLO_VE_CFG0, + SOLO_VE_INTR_CTRL(IRQ_LEVEL) | + SOLO_VE_BLOCK_SIZE(SOLO_MP4E_EXT_SIZE(solo_dev) >> 16) | + SOLO_VE_BLOCK_BASE(SOLO_MP4E_EXT_ADDR(solo_dev) >> 16)); + + + cfg = SOLO_VE_BYTE_ALIGN(2) | SOLO_VE_INSERT_INDEX + | SOLO_VE_MOTION_MODE(0); + if (solo_dev->type != SOLO_DEV_6010) { + cfg |= SOLO_VE_MPEG_SIZE_H( + (SOLO_MP4E_EXT_SIZE(solo_dev) >> 24) & 0x0f); + cfg |= SOLO_VE_JPEG_SIZE_H( + (SOLO_JPEG_EXT_SIZE(solo_dev) >> 24) & 0x0f); + } + solo_reg_write(solo_dev, SOLO_VE_CFG1, cfg); + + solo_reg_write(solo_dev, SOLO_VE_WMRK_POLY, 0); + solo_reg_write(solo_dev, SOLO_VE_VMRK_INIT_KEY, 0); + solo_reg_write(solo_dev, SOLO_VE_WMRK_STRL, 0); + if (solo_dev->type == SOLO_DEV_6110) + solo_reg_write(solo_dev, SOLO_VE_WMRK_ENABLE, 0); + solo_reg_write(solo_dev, SOLO_VE_ENCRYP_POLY, 0); + solo_reg_write(solo_dev, SOLO_VE_ENCRYP_INIT, 0); + + solo_reg_write(solo_dev, SOLO_VE_ATTR, + SOLO_VE_LITTLE_ENDIAN | + SOLO_COMP_ATTR_FCODE(1) | + SOLO_COMP_TIME_INC(0) | + SOLO_COMP_TIME_WIDTH(15) | + SOLO_DCT_INTERVAL(solo_dev->type == SOLO_DEV_6010 ? 9 : 10)); + + for (i = 0; i < solo_dev->nr_chans; i++) { + solo_reg_write(solo_dev, SOLO_VE_CH_REF_BASE(i), + (SOLO_EREF_EXT_ADDR(solo_dev) + + (i * SOLO_EREF_EXT_SIZE)) >> 16); + solo_reg_write(solo_dev, SOLO_VE_CH_REF_BASE_E(i), + (SOLO_EREF_EXT_ADDR(solo_dev) + + ((i + 16) * SOLO_EREF_EXT_SIZE)) >> 16); + } + + if (solo_dev->type == SOLO_DEV_6110) { + solo_reg_write(solo_dev, SOLO_VE_COMPT_MOT, 0x00040008); + } else { + for (i = 0; i < solo_dev->nr_chans; i++) + solo_reg_write(solo_dev, SOLO_VE_CH_MOT(i), 0x100); + } +} + +int solo_enc_init(struct solo_dev *solo_dev) +{ + int i; + + solo_capture_config(solo_dev); + solo_mp4e_config(solo_dev); + solo_jpeg_config(solo_dev); + + for (i = 0; i < solo_dev->nr_chans; i++) { + solo_reg_write(solo_dev, SOLO_CAP_CH_SCALE(i), 0); + solo_reg_write(solo_dev, SOLO_CAP_CH_COMP_ENA_E(i), 0); + } + + return 0; +} + +void solo_enc_exit(struct solo_dev *solo_dev) +{ + int i; + + for (i = 0; i < solo_dev->nr_chans; i++) { + solo_reg_write(solo_dev, SOLO_CAP_CH_SCALE(i), 0); + solo_reg_write(solo_dev, SOLO_CAP_CH_COMP_ENA_E(i), 0); + } +} diff --git a/drivers/staging/media/solo6x10/solo6x10-g723.c b/drivers/staging/media/solo6x10/solo6x10-g723.c new file mode 100644 index 00000000000..74f037b6166 --- /dev/null +++ b/drivers/staging/media/solo6x10/solo6x10-g723.c @@ -0,0 +1,424 @@ +/* + * Copyright (C) 2010-2013 Bluecherry, LLC <http://www.bluecherrydvr.com> + * + * Original author: + * Ben Collins <bcollins@ubuntu.com> + * + * Additional work by: + * John Brooks <john.brooks@bluecherry.net> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include <linux/kernel.h> +#include <linux/mempool.h> +#include <linux/poll.h> +#include <linux/kthread.h> +#include <linux/freezer.h> +#include <linux/module.h> +#include <linux/slab.h> + +#include <sound/core.h> +#include <sound/initval.h> +#include <sound/pcm.h> +#include <sound/control.h> + +#include "solo6x10.h" +#include "solo6x10-tw28.h" + +#define G723_FDMA_PAGES 32 +#define G723_PERIOD_BYTES 48 +#define G723_PERIOD_BLOCK 1024 +#define G723_FRAMES_PER_PAGE 48 + +/* Sets up channels 16-19 for decoding and 0-15 for encoding */ +#define OUTMODE_MASK 0x300 + +#define SAMPLERATE 8000 +#define BITRATE 25 + +/* The solo writes to 1k byte pages, 32 pages, in the dma. Each 1k page + * is broken down to 20 * 48 byte regions (one for each channel possible) + * with the rest of the page being dummy data. */ +#define G723_MAX_BUFFER (G723_PERIOD_BYTES * PERIODS_MAX) +#define G723_INTR_ORDER 4 /* 0 - 4 */ +#define PERIODS_MIN (1 << G723_INTR_ORDER) +#define PERIODS_MAX G723_FDMA_PAGES + +struct solo_snd_pcm { + int on; + spinlock_t lock; + struct solo_dev *solo_dev; + unsigned char *g723_buf; + dma_addr_t g723_dma; +}; + +static void solo_g723_config(struct solo_dev *solo_dev) +{ + int clk_div; + + clk_div = (solo_dev->clock_mhz * 1000000) + / (SAMPLERATE * (BITRATE * 2) * 2); + + solo_reg_write(solo_dev, SOLO_AUDIO_SAMPLE, + SOLO_AUDIO_BITRATE(BITRATE) + | SOLO_AUDIO_CLK_DIV(clk_div)); + + solo_reg_write(solo_dev, SOLO_AUDIO_FDMA_INTR, + SOLO_AUDIO_FDMA_INTERVAL(1) + | SOLO_AUDIO_INTR_ORDER(G723_INTR_ORDER) + | SOLO_AUDIO_FDMA_BASE(SOLO_G723_EXT_ADDR(solo_dev) >> 16)); + + solo_reg_write(solo_dev, SOLO_AUDIO_CONTROL, + SOLO_AUDIO_ENABLE + | SOLO_AUDIO_I2S_MODE + | SOLO_AUDIO_I2S_MULTI(3) + | SOLO_AUDIO_MODE(OUTMODE_MASK)); +} + +void solo_g723_isr(struct solo_dev *solo_dev) +{ + struct snd_pcm_str *pstr = + &solo_dev->snd_pcm->streams[SNDRV_PCM_STREAM_CAPTURE]; + struct snd_pcm_substream *ss; + struct solo_snd_pcm *solo_pcm; + + for (ss = pstr->substream; ss != NULL; ss = ss->next) { + if (snd_pcm_substream_chip(ss) == NULL) + continue; + + /* This means open() hasn't been called on this one */ + if (snd_pcm_substream_chip(ss) == solo_dev) + continue; + + /* Haven't triggered a start yet */ + solo_pcm = snd_pcm_substream_chip(ss); + if (!solo_pcm->on) + continue; + + snd_pcm_period_elapsed(ss); + } +} + +static int snd_solo_hw_params(struct snd_pcm_substream *ss, + struct snd_pcm_hw_params *hw_params) +{ + return snd_pcm_lib_malloc_pages(ss, params_buffer_bytes(hw_params)); +} + +static int snd_solo_hw_free(struct snd_pcm_substream *ss) +{ + return snd_pcm_lib_free_pages(ss); +} + +static const struct snd_pcm_hardware snd_solo_pcm_hw = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID), + .formats = SNDRV_PCM_FMTBIT_U8, + .rates = SNDRV_PCM_RATE_8000, + .rate_min = SAMPLERATE, + .rate_max = SAMPLERATE, + .channels_min = 1, + .channels_max = 1, + .buffer_bytes_max = G723_MAX_BUFFER, + .period_bytes_min = G723_PERIOD_BYTES, + .period_bytes_max = G723_PERIOD_BYTES, + .periods_min = PERIODS_MIN, + .periods_max = PERIODS_MAX, +}; + +static int snd_solo_pcm_open(struct snd_pcm_substream *ss) +{ + struct solo_dev *solo_dev = snd_pcm_substream_chip(ss); + struct solo_snd_pcm *solo_pcm; + + solo_pcm = kzalloc(sizeof(*solo_pcm), GFP_KERNEL); + if (solo_pcm == NULL) + goto oom; + + solo_pcm->g723_buf = pci_alloc_consistent(solo_dev->pdev, + G723_PERIOD_BYTES, + &solo_pcm->g723_dma); + if (solo_pcm->g723_buf == NULL) + goto oom; + + spin_lock_init(&solo_pcm->lock); + solo_pcm->solo_dev = solo_dev; + ss->runtime->hw = snd_solo_pcm_hw; + + snd_pcm_substream_chip(ss) = solo_pcm; + + return 0; + +oom: + kfree(solo_pcm); + return -ENOMEM; +} + +static int snd_solo_pcm_close(struct snd_pcm_substream *ss) +{ + struct solo_snd_pcm *solo_pcm = snd_pcm_substream_chip(ss); + + snd_pcm_substream_chip(ss) = solo_pcm->solo_dev; + pci_free_consistent(solo_pcm->solo_dev->pdev, G723_PERIOD_BYTES, + solo_pcm->g723_buf, solo_pcm->g723_dma); + kfree(solo_pcm); + + return 0; +} + +static int snd_solo_pcm_trigger(struct snd_pcm_substream *ss, int cmd) +{ + struct solo_snd_pcm *solo_pcm = snd_pcm_substream_chip(ss); + struct solo_dev *solo_dev = solo_pcm->solo_dev; + int ret = 0; + + spin_lock(&solo_pcm->lock); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + if (solo_pcm->on == 0) { + /* If this is the first user, switch on interrupts */ + if (atomic_inc_return(&solo_dev->snd_users) == 1) + solo_irq_on(solo_dev, SOLO_IRQ_G723); + solo_pcm->on = 1; + } + break; + case SNDRV_PCM_TRIGGER_STOP: + if (solo_pcm->on) { + /* If this was our last user, switch them off */ + if (atomic_dec_return(&solo_dev->snd_users) == 0) + solo_irq_off(solo_dev, SOLO_IRQ_G723); + solo_pcm->on = 0; + } + break; + default: + ret = -EINVAL; + } + + spin_unlock(&solo_pcm->lock); + + return ret; +} + +static int snd_solo_pcm_prepare(struct snd_pcm_substream *ss) +{ + return 0; +} + +static snd_pcm_uframes_t snd_solo_pcm_pointer(struct snd_pcm_substream *ss) +{ + struct solo_snd_pcm *solo_pcm = snd_pcm_substream_chip(ss); + struct solo_dev *solo_dev = solo_pcm->solo_dev; + snd_pcm_uframes_t idx = solo_reg_read(solo_dev, SOLO_AUDIO_STA) & 0x1f; + + return idx * G723_FRAMES_PER_PAGE; +} + +static int snd_solo_pcm_copy(struct snd_pcm_substream *ss, int channel, + snd_pcm_uframes_t pos, void __user *dst, + snd_pcm_uframes_t count) +{ + struct solo_snd_pcm *solo_pcm = snd_pcm_substream_chip(ss); + struct solo_dev *solo_dev = solo_pcm->solo_dev; + int err, i; + + for (i = 0; i < (count / G723_FRAMES_PER_PAGE); i++) { + int page = (pos / G723_FRAMES_PER_PAGE) + i; + + err = solo_p2m_dma_t(solo_dev, 0, solo_pcm->g723_dma, + SOLO_G723_EXT_ADDR(solo_dev) + + (page * G723_PERIOD_BLOCK) + + (ss->number * G723_PERIOD_BYTES), + G723_PERIOD_BYTES, 0, 0); + if (err) + return err; + + err = copy_to_user(dst + (i * G723_PERIOD_BYTES), + solo_pcm->g723_buf, G723_PERIOD_BYTES); + + if (err) + return -EFAULT; + } + + return 0; +} + +static struct snd_pcm_ops snd_solo_pcm_ops = { + .open = snd_solo_pcm_open, + .close = snd_solo_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_solo_hw_params, + .hw_free = snd_solo_hw_free, + .prepare = snd_solo_pcm_prepare, + .trigger = snd_solo_pcm_trigger, + .pointer = snd_solo_pcm_pointer, + .copy = snd_solo_pcm_copy, +}; + +static int snd_solo_capture_volume_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *info) +{ + info->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + info->count = 1; + info->value.integer.min = 0; + info->value.integer.max = 15; + info->value.integer.step = 1; + + return 0; +} + +static int snd_solo_capture_volume_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *value) +{ + struct solo_dev *solo_dev = snd_kcontrol_chip(kcontrol); + u8 ch = value->id.numid - 1; + + value->value.integer.value[0] = tw28_get_audio_gain(solo_dev, ch); + + return 0; +} + +static int snd_solo_capture_volume_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *value) +{ + struct solo_dev *solo_dev = snd_kcontrol_chip(kcontrol); + u8 ch = value->id.numid - 1; + u8 old_val; + + old_val = tw28_get_audio_gain(solo_dev, ch); + if (old_val == value->value.integer.value[0]) + return 0; + + tw28_set_audio_gain(solo_dev, ch, value->value.integer.value[0]); + + return 1; +} + +static struct snd_kcontrol_new snd_solo_capture_volume = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Capture Volume", + .info = snd_solo_capture_volume_info, + .get = snd_solo_capture_volume_get, + .put = snd_solo_capture_volume_put, +}; + +static int solo_snd_pcm_init(struct solo_dev *solo_dev) +{ + struct snd_card *card = solo_dev->snd_card; + struct snd_pcm *pcm; + struct snd_pcm_substream *ss; + int ret; + int i; + + ret = snd_pcm_new(card, card->driver, 0, 0, solo_dev->nr_chans, + &pcm); + if (ret < 0) + return ret; + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, + &snd_solo_pcm_ops); + + snd_pcm_chip(pcm) = solo_dev; + pcm->info_flags = 0; + strcpy(pcm->name, card->shortname); + + for (i = 0, ss = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream; + ss; ss = ss->next, i++) + sprintf(ss->name, "Camera #%d Audio", i); + + ret = snd_pcm_lib_preallocate_pages_for_all(pcm, + SNDRV_DMA_TYPE_CONTINUOUS, + snd_dma_continuous_data(GFP_KERNEL), + G723_MAX_BUFFER, G723_MAX_BUFFER); + if (ret < 0) + return ret; + + solo_dev->snd_pcm = pcm; + + return 0; +} + +int solo_g723_init(struct solo_dev *solo_dev) +{ + static struct snd_device_ops ops = { NULL }; + struct snd_card *card; + struct snd_kcontrol_new kctl; + char name[32]; + int ret; + + atomic_set(&solo_dev->snd_users, 0); + + /* Allows for easier mapping between video and audio */ + sprintf(name, "Softlogic%d", solo_dev->vfd->num); + + ret = snd_card_new(&solo_dev->pdev->dev, + SNDRV_DEFAULT_IDX1, name, THIS_MODULE, 0, + &solo_dev->snd_card); + if (ret < 0) + return ret; + + card = solo_dev->snd_card; + + strcpy(card->driver, SOLO6X10_NAME); + strcpy(card->shortname, "SOLO-6x10 Audio"); + sprintf(card->longname, "%s on %s IRQ %d", card->shortname, + pci_name(solo_dev->pdev), solo_dev->pdev->irq); + + ret = snd_device_new(card, SNDRV_DEV_LOWLEVEL, solo_dev, &ops); + if (ret < 0) + goto snd_error; + + /* Mixer controls */ + strcpy(card->mixername, "SOLO-6x10"); + kctl = snd_solo_capture_volume; + kctl.count = solo_dev->nr_chans; + + ret = snd_ctl_add(card, snd_ctl_new1(&kctl, solo_dev)); + if (ret < 0) + return ret; + + ret = solo_snd_pcm_init(solo_dev); + if (ret < 0) + goto snd_error; + + ret = snd_card_register(card); + if (ret < 0) + goto snd_error; + + solo_g723_config(solo_dev); + + dev_info(&solo_dev->pdev->dev, "Alsa sound card as %s\n", name); + + return 0; + +snd_error: + snd_card_free(card); + return ret; +} + +void solo_g723_exit(struct solo_dev *solo_dev) +{ + if (!solo_dev->snd_card) + return; + + solo_reg_write(solo_dev, SOLO_AUDIO_CONTROL, 0); + solo_irq_off(solo_dev, SOLO_IRQ_G723); + + snd_card_free(solo_dev->snd_card); + solo_dev->snd_card = NULL; +} diff --git a/drivers/staging/media/solo6x10/solo6x10-gpio.c b/drivers/staging/media/solo6x10/solo6x10-gpio.c new file mode 100644 index 00000000000..73276dc9287 --- /dev/null +++ b/drivers/staging/media/solo6x10/solo6x10-gpio.c @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2010-2013 Bluecherry, LLC <http://www.bluecherrydvr.com> + * + * Original author: + * Ben Collins <bcollins@ubuntu.com> + * + * Additional work by: + * John Brooks <john.brooks@bluecherry.net> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include <linux/kernel.h> +#include <linux/fs.h> +#include <linux/delay.h> +#include <linux/uaccess.h> + +#include "solo6x10.h" + +static void solo_gpio_mode(struct solo_dev *solo_dev, + unsigned int port_mask, unsigned int mode) +{ + int port; + unsigned int ret; + + ret = solo_reg_read(solo_dev, SOLO_GPIO_CONFIG_0); + + /* To set gpio */ + for (port = 0; port < 16; port++) { + if (!((1 << port) & port_mask)) + continue; + + ret &= (~(3 << (port << 1))); + ret |= ((mode & 3) << (port << 1)); + } + + solo_reg_write(solo_dev, SOLO_GPIO_CONFIG_0, ret); + + /* To set extended gpio - sensor */ + ret = solo_reg_read(solo_dev, SOLO_GPIO_CONFIG_1); + + for (port = 0; port < 16; port++) { + if (!((1 << (port + 16)) & port_mask)) + continue; + + if (!mode) + ret &= ~(1 << port); + else + ret |= 1 << port; + } + + solo_reg_write(solo_dev, SOLO_GPIO_CONFIG_1, ret); +} + +static void solo_gpio_set(struct solo_dev *solo_dev, unsigned int value) +{ + solo_reg_write(solo_dev, SOLO_GPIO_DATA_OUT, + solo_reg_read(solo_dev, SOLO_GPIO_DATA_OUT) | value); +} + +static void solo_gpio_clear(struct solo_dev *solo_dev, unsigned int value) +{ + solo_reg_write(solo_dev, SOLO_GPIO_DATA_OUT, + solo_reg_read(solo_dev, SOLO_GPIO_DATA_OUT) & ~value); +} + +static void solo_gpio_config(struct solo_dev *solo_dev) +{ + /* Video reset */ + solo_gpio_mode(solo_dev, 0x30, 1); + solo_gpio_clear(solo_dev, 0x30); + udelay(100); + solo_gpio_set(solo_dev, 0x30); + udelay(100); + + /* Warning: Don't touch the next line unless you're sure of what + * you're doing: first four gpio [0-3] are used for video. */ + solo_gpio_mode(solo_dev, 0x0f, 2); + + /* We use bit 8-15 of SOLO_GPIO_CONFIG_0 for relay purposes */ + solo_gpio_mode(solo_dev, 0xff00, 1); + + /* Initially set relay status to 0 */ + solo_gpio_clear(solo_dev, 0xff00); +} + +int solo_gpio_init(struct solo_dev *solo_dev) +{ + solo_gpio_config(solo_dev); + return 0; +} + +void solo_gpio_exit(struct solo_dev *solo_dev) +{ + solo_gpio_clear(solo_dev, 0x30); + solo_gpio_config(solo_dev); +} diff --git a/drivers/staging/media/solo6x10/solo6x10-i2c.c b/drivers/staging/media/solo6x10/solo6x10-i2c.c new file mode 100644 index 00000000000..01aa417c925 --- /dev/null +++ b/drivers/staging/media/solo6x10/solo6x10-i2c.c @@ -0,0 +1,334 @@ +/* + * Copyright (C) 2010-2013 Bluecherry, LLC <http://www.bluecherrydvr.com> + * + * Original author: + * Ben Collins <bcollins@ubuntu.com> + * + * Additional work by: + * John Brooks <john.brooks@bluecherry.net> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* XXX: The SOLO6x10 i2c does not have separate interrupts for each i2c + * channel. The bus can only handle one i2c event at a time. The below handles + * this all wrong. We should be using the status registers to see if the bus + * is in use, and have a global lock to check the status register. Also, + * the bulk of the work should be handled out-of-interrupt. The ugly loops + * that occur during interrupt scare me. The ISR should merely signal + * thread context, ACK the interrupt, and move on. -- BenC */ + +#include <linux/kernel.h> + +#include "solo6x10.h" + +u8 solo_i2c_readbyte(struct solo_dev *solo_dev, int id, u8 addr, u8 off) +{ + struct i2c_msg msgs[2]; + u8 data; + + msgs[0].flags = 0; + msgs[0].addr = addr; + msgs[0].len = 1; + msgs[0].buf = &off; + + msgs[1].flags = I2C_M_RD; + msgs[1].addr = addr; + msgs[1].len = 1; + msgs[1].buf = &data; + + i2c_transfer(&solo_dev->i2c_adap[id], msgs, 2); + + return data; +} + +void solo_i2c_writebyte(struct solo_dev *solo_dev, int id, u8 addr, + u8 off, u8 data) +{ + struct i2c_msg msgs; + u8 buf[2]; + + buf[0] = off; + buf[1] = data; + msgs.flags = 0; + msgs.addr = addr; + msgs.len = 2; + msgs.buf = buf; + + i2c_transfer(&solo_dev->i2c_adap[id], &msgs, 1); +} + +static void solo_i2c_flush(struct solo_dev *solo_dev, int wr) +{ + u32 ctrl; + + ctrl = SOLO_IIC_CH_SET(solo_dev->i2c_id); + + if (solo_dev->i2c_state == IIC_STATE_START) + ctrl |= SOLO_IIC_START; + + if (wr) { + ctrl |= SOLO_IIC_WRITE; + } else { + ctrl |= SOLO_IIC_READ; + if (!(solo_dev->i2c_msg->flags & I2C_M_NO_RD_ACK)) + ctrl |= SOLO_IIC_ACK_EN; + } + + if (solo_dev->i2c_msg_ptr == solo_dev->i2c_msg->len) + ctrl |= SOLO_IIC_STOP; + + solo_reg_write(solo_dev, SOLO_IIC_CTRL, ctrl); +} + +static void solo_i2c_start(struct solo_dev *solo_dev) +{ + u32 addr = solo_dev->i2c_msg->addr << 1; + + if (solo_dev->i2c_msg->flags & I2C_M_RD) + addr |= 1; + + solo_dev->i2c_state = IIC_STATE_START; + solo_reg_write(solo_dev, SOLO_IIC_TXD, addr); + solo_i2c_flush(solo_dev, 1); +} + +static void solo_i2c_stop(struct solo_dev *solo_dev) +{ + solo_irq_off(solo_dev, SOLO_IRQ_IIC); + solo_reg_write(solo_dev, SOLO_IIC_CTRL, 0); + solo_dev->i2c_state = IIC_STATE_STOP; + wake_up(&solo_dev->i2c_wait); +} + +static int solo_i2c_handle_read(struct solo_dev *solo_dev) +{ +prepare_read: + if (solo_dev->i2c_msg_ptr != solo_dev->i2c_msg->len) { + solo_i2c_flush(solo_dev, 0); + return 0; + } + + solo_dev->i2c_msg_ptr = 0; + solo_dev->i2c_msg++; + solo_dev->i2c_msg_num--; + + if (solo_dev->i2c_msg_num == 0) { + solo_i2c_stop(solo_dev); + return 0; + } + + if (!(solo_dev->i2c_msg->flags & I2C_M_NOSTART)) { + solo_i2c_start(solo_dev); + } else { + if (solo_dev->i2c_msg->flags & I2C_M_RD) + goto prepare_read; + else + solo_i2c_stop(solo_dev); + } + + return 0; +} + +static int solo_i2c_handle_write(struct solo_dev *solo_dev) +{ +retry_write: + if (solo_dev->i2c_msg_ptr != solo_dev->i2c_msg->len) { + solo_reg_write(solo_dev, SOLO_IIC_TXD, + solo_dev->i2c_msg->buf[solo_dev->i2c_msg_ptr]); + solo_dev->i2c_msg_ptr++; + solo_i2c_flush(solo_dev, 1); + return 0; + } + + solo_dev->i2c_msg_ptr = 0; + solo_dev->i2c_msg++; + solo_dev->i2c_msg_num--; + + if (solo_dev->i2c_msg_num == 0) { + solo_i2c_stop(solo_dev); + return 0; + } + + if (!(solo_dev->i2c_msg->flags & I2C_M_NOSTART)) { + solo_i2c_start(solo_dev); + } else { + if (solo_dev->i2c_msg->flags & I2C_M_RD) + solo_i2c_stop(solo_dev); + else + goto retry_write; + } + + return 0; +} + +int solo_i2c_isr(struct solo_dev *solo_dev) +{ + u32 status = solo_reg_read(solo_dev, SOLO_IIC_CTRL); + int ret = -EINVAL; + + + if (CHK_FLAGS(status, SOLO_IIC_STATE_TRNS | SOLO_IIC_STATE_SIG_ERR) + || solo_dev->i2c_id < 0) { + solo_i2c_stop(solo_dev); + return -ENXIO; + } + + switch (solo_dev->i2c_state) { + case IIC_STATE_START: + if (solo_dev->i2c_msg->flags & I2C_M_RD) { + solo_dev->i2c_state = IIC_STATE_READ; + ret = solo_i2c_handle_read(solo_dev); + break; + } + + solo_dev->i2c_state = IIC_STATE_WRITE; + case IIC_STATE_WRITE: + ret = solo_i2c_handle_write(solo_dev); + break; + + case IIC_STATE_READ: + solo_dev->i2c_msg->buf[solo_dev->i2c_msg_ptr] = + solo_reg_read(solo_dev, SOLO_IIC_RXD); + solo_dev->i2c_msg_ptr++; + + ret = solo_i2c_handle_read(solo_dev); + break; + + default: + solo_i2c_stop(solo_dev); + } + + return ret; +} + +static int solo_i2c_master_xfer(struct i2c_adapter *adap, + struct i2c_msg msgs[], int num) +{ + struct solo_dev *solo_dev = adap->algo_data; + unsigned long timeout; + int ret; + int i; + DEFINE_WAIT(wait); + + for (i = 0; i < SOLO_I2C_ADAPTERS; i++) { + if (&solo_dev->i2c_adap[i] == adap) + break; + } + + if (i == SOLO_I2C_ADAPTERS) + return num; /* XXX Right return value for failure? */ + + mutex_lock(&solo_dev->i2c_mutex); + solo_dev->i2c_id = i; + solo_dev->i2c_msg = msgs; + solo_dev->i2c_msg_num = num; + solo_dev->i2c_msg_ptr = 0; + + solo_reg_write(solo_dev, SOLO_IIC_CTRL, 0); + solo_irq_on(solo_dev, SOLO_IRQ_IIC); + solo_i2c_start(solo_dev); + + timeout = HZ / 2; + + for (;;) { + prepare_to_wait(&solo_dev->i2c_wait, &wait, + TASK_INTERRUPTIBLE); + + if (solo_dev->i2c_state == IIC_STATE_STOP) + break; + + timeout = schedule_timeout(timeout); + if (!timeout) + break; + + if (signal_pending(current)) + break; + } + + finish_wait(&solo_dev->i2c_wait, &wait); + ret = num - solo_dev->i2c_msg_num; + solo_dev->i2c_state = IIC_STATE_IDLE; + solo_dev->i2c_id = -1; + + mutex_unlock(&solo_dev->i2c_mutex); + + return ret; +} + +static u32 solo_i2c_functionality(struct i2c_adapter *adap) +{ + return I2C_FUNC_I2C; +} + +static const struct i2c_algorithm solo_i2c_algo = { + .master_xfer = solo_i2c_master_xfer, + .functionality = solo_i2c_functionality, +}; + +int solo_i2c_init(struct solo_dev *solo_dev) +{ + int i; + int ret; + + solo_reg_write(solo_dev, SOLO_IIC_CFG, + SOLO_IIC_PRESCALE(8) | SOLO_IIC_ENABLE); + + solo_dev->i2c_id = -1; + solo_dev->i2c_state = IIC_STATE_IDLE; + init_waitqueue_head(&solo_dev->i2c_wait); + mutex_init(&solo_dev->i2c_mutex); + + for (i = 0; i < SOLO_I2C_ADAPTERS; i++) { + struct i2c_adapter *adap = &solo_dev->i2c_adap[i]; + + snprintf(adap->name, I2C_NAME_SIZE, "%s I2C %d", + SOLO6X10_NAME, i); + adap->algo = &solo_i2c_algo; + adap->algo_data = solo_dev; + adap->retries = 1; + adap->dev.parent = &solo_dev->pdev->dev; + + ret = i2c_add_adapter(adap); + if (ret) { + adap->algo_data = NULL; + break; + } + } + + if (ret) { + for (i = 0; i < SOLO_I2C_ADAPTERS; i++) { + if (!solo_dev->i2c_adap[i].algo_data) + break; + i2c_del_adapter(&solo_dev->i2c_adap[i]); + solo_dev->i2c_adap[i].algo_data = NULL; + } + return ret; + } + + return 0; +} + +void solo_i2c_exit(struct solo_dev *solo_dev) +{ + int i; + + for (i = 0; i < SOLO_I2C_ADAPTERS; i++) { + if (!solo_dev->i2c_adap[i].algo_data) + continue; + i2c_del_adapter(&solo_dev->i2c_adap[i]); + solo_dev->i2c_adap[i].algo_data = NULL; + } +} diff --git a/drivers/staging/media/solo6x10/solo6x10-jpeg.h b/drivers/staging/media/solo6x10/solo6x10-jpeg.h new file mode 100644 index 00000000000..c5218ceeabc --- /dev/null +++ b/drivers/staging/media/solo6x10/solo6x10-jpeg.h @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2010-2013 Bluecherry, LLC <http://www.bluecherrydvr.com> + * + * Original author: + * Ben Collins <bcollins@ubuntu.com> + * + * Additional work by: + * John Brooks <john.brooks@bluecherry.net> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef __SOLO6X10_JPEG_H +#define __SOLO6X10_JPEG_H + +static const unsigned char jpeg_header[] = { + 0xff, 0xd8, 0xff, 0xfe, 0x00, 0x0d, 0x42, 0x6c, + 0x75, 0x65, 0x63, 0x68, 0x65, 0x72, 0x72, 0x79, + 0x20, 0xff, 0xdb, 0x00, 0x43, 0x00, 0x20, 0x16, + 0x18, 0x1c, 0x18, 0x14, 0x20, 0x1c, 0x1a, 0x1c, + 0x24, 0x22, 0x20, 0x26, 0x30, 0x50, 0x34, 0x30, + 0x2c, 0x2c, 0x30, 0x62, 0x46, 0x4a, 0x3a, 0x50, + 0x74, 0x66, 0x7a, 0x78, 0x72, 0x66, 0x70, 0x6e, + 0x80, 0x90, 0xb8, 0x9c, 0x80, 0x88, 0xae, 0x8a, + 0x6e, 0x70, 0xa0, 0xda, 0xa2, 0xae, 0xbe, 0xc4, + 0xce, 0xd0, 0xce, 0x7c, 0x9a, 0xe2, 0xf2, 0xe0, + 0xc8, 0xf0, 0xb8, 0xca, 0xce, 0xc6, 0xff, 0xdb, + 0x00, 0x43, 0x01, 0x22, 0x24, 0x24, 0x30, 0x2a, + 0x30, 0x5e, 0x34, 0x34, 0x5e, 0xc6, 0x84, 0x70, + 0x84, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, + 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, + 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, + 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, + 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, + 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, + 0xc6, 0xc6, 0xc6, 0xff, 0xc4, 0x01, 0xa2, 0x00, + 0x00, 0x01, 0x05, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x10, 0x00, 0x02, 0x01, + 0x03, 0x03, 0x02, 0x04, 0x03, 0x05, 0x05, 0x04, + 0x04, 0x00, 0x00, 0x01, 0x7d, 0x01, 0x02, 0x03, + 0x00, 0x04, 0x11, 0x05, 0x12, 0x21, 0x31, 0x41, + 0x06, 0x13, 0x51, 0x61, 0x07, 0x22, 0x71, 0x14, + 0x32, 0x81, 0x91, 0xa1, 0x08, 0x23, 0x42, 0xb1, + 0xc1, 0x15, 0x52, 0xd1, 0xf0, 0x24, 0x33, 0x62, + 0x72, 0x82, 0x09, 0x0a, 0x16, 0x17, 0x18, 0x19, + 0x1a, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x34, + 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x43, 0x44, + 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x53, 0x54, + 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, + 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x73, 0x74, + 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x83, 0x84, + 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x92, 0x93, + 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, + 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, + 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, + 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, + 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, + 0xd8, 0xd9, 0xda, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, + 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf1, 0xf2, 0xf3, + 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0x01, + 0x00, 0x03, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x11, 0x00, 0x02, 0x01, + 0x02, 0x04, 0x04, 0x03, 0x04, 0x07, 0x05, 0x04, + 0x04, 0x00, 0x01, 0x02, 0x77, 0x00, 0x01, 0x02, + 0x03, 0x11, 0x04, 0x05, 0x21, 0x31, 0x06, 0x12, + 0x41, 0x51, 0x07, 0x61, 0x71, 0x13, 0x22, 0x32, + 0x81, 0x08, 0x14, 0x42, 0x91, 0xa1, 0xb1, 0xc1, + 0x09, 0x23, 0x33, 0x52, 0xf0, 0x15, 0x62, 0x72, + 0xd1, 0x0a, 0x16, 0x24, 0x34, 0xe1, 0x25, 0xf1, + 0x17, 0x18, 0x19, 0x1a, 0x26, 0x27, 0x28, 0x29, + 0x2a, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x43, + 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x53, + 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, + 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x73, + 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x82, + 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, + 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, + 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, + 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, + 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, + 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5, + 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe2, 0xe3, 0xe4, + 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf2, 0xf3, + 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xff, + 0xc0, 0x00, 0x11, 0x08, 0x00, 0xf0, 0x02, 0xc0, + 0x03, 0x01, 0x22, 0x00, 0x02, 0x11, 0x01, 0x03, + 0x11, 0x01, 0xff, 0xda, 0x00, 0x0c, 0x03, 0x01, + 0x00, 0x02, 0x11, 0x03, 0x11, 0x00, 0x3f, 0x00 +}; + +/* This is the byte marker for the start of SOF0: 0xffc0 marker */ +#define SOF0_START 575 + +/* This is the byte marker for the start of the DQT */ +#define DQT_START 17 +#define DQT_LEN 138 +const unsigned char jpeg_dqt[4][DQT_LEN] = { + { + 0xff, 0xdb, 0x00, 0x43, 0x00, + 0x08, 0x06, 0x06, 0x07, 0x06, 0x05, 0x08, 0x07, + 0x07, 0x07, 0x09, 0x09, 0x08, 0x0a, 0x0c, 0x14, + 0x0d, 0x0c, 0x0b, 0x0b, 0x0c, 0x19, 0x12, 0x13, + 0x0f, 0x14, 0x1d, 0x1a, 0x1f, 0x1e, 0x1d, 0x1a, + 0x1c, 0x1c, 0x20, 0x24, 0x2e, 0x27, 0x20, 0x22, + 0x2c, 0x23, 0x1c, 0x1c, 0x28, 0x37, 0x29, 0x2c, + 0x30, 0x31, 0x34, 0x34, 0x34, 0x1f, 0x27, 0x39, + 0x3d, 0x38, 0x32, 0x3c, 0x2e, 0x33, 0x34, 0x32, + 0xff, 0xdb, 0x00, 0x43, 0x01, + 0x09, 0x09, 0x09, 0x0c, 0x0b, 0x0c, 0x18, 0x0d, + 0x0d, 0x18, 0x32, 0x21, 0x1c, 0x21, 0x32, 0x32, + 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, + 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, + 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, + 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, + 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, + 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32 + }, { + 0xff, 0xdb, 0x00, 0x43, 0x00, + 0x10, 0x0b, 0x0c, 0x0e, 0x0c, 0x0a, 0x10, 0x0e, + 0x0d, 0x0e, 0x12, 0x11, 0x10, 0x13, 0x18, 0x28, + 0x1a, 0x18, 0x16, 0x16, 0x18, 0x31, 0x23, 0x25, + 0x1d, 0x28, 0x3a, 0x33, 0x3d, 0x3c, 0x39, 0x33, + 0x38, 0x37, 0x40, 0x48, 0x5c, 0x4e, 0x40, 0x44, + 0x57, 0x45, 0x37, 0x38, 0x50, 0x6d, 0x51, 0x57, + 0x5f, 0x62, 0x67, 0x68, 0x67, 0x3e, 0x4d, 0x71, + 0x79, 0x70, 0x64, 0x78, 0x5c, 0x65, 0x67, 0x63, + 0xff, 0xdb, 0x00, 0x43, 0x01, + 0x11, 0x12, 0x12, 0x18, 0x15, 0x18, 0x2f, 0x1a, + 0x1a, 0x2f, 0x63, 0x42, 0x38, 0x42, 0x63, 0x63, + 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, + 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, + 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, + 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, + 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, + 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63 + }, { + 0xff, 0xdb, 0x00, 0x43, 0x00, + 0x20, 0x16, 0x18, 0x1c, 0x18, 0x14, 0x20, 0x1c, + 0x1a, 0x1c, 0x24, 0x22, 0x20, 0x26, 0x30, 0x50, + 0x34, 0x30, 0x2c, 0x2c, 0x30, 0x62, 0x46, 0x4a, + 0x3a, 0x50, 0x74, 0x66, 0x7a, 0x78, 0x72, 0x66, + 0x70, 0x6e, 0x80, 0x90, 0xb8, 0x9c, 0x80, 0x88, + 0xae, 0x8a, 0x6e, 0x70, 0xa0, 0xda, 0xa2, 0xae, + 0xbe, 0xc4, 0xce, 0xd0, 0xce, 0x7c, 0x9a, 0xe2, + 0xf2, 0xe0, 0xc8, 0xf0, 0xb8, 0xca, 0xce, 0xc6, + 0xff, 0xdb, 0x00, 0x43, 0x01, + 0x22, 0x24, 0x24, 0x30, 0x2a, 0x30, 0x5e, 0x34, + 0x34, 0x5e, 0xc6, 0x84, 0x70, 0x84, 0xc6, 0xc6, + 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, + 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, + 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, + 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, + 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, + 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6 + }, { + 0xff, 0xdb, 0x00, 0x43, 0x00, + 0x30, 0x21, 0x24, 0x2a, 0x24, 0x1e, 0x30, 0x2a, + 0x27, 0x2a, 0x36, 0x33, 0x30, 0x39, 0x48, 0x78, + 0x4e, 0x48, 0x42, 0x42, 0x48, 0x93, 0x69, 0x6f, + 0x57, 0x78, 0xae, 0x99, 0xb7, 0xb4, 0xab, 0x99, + 0xa8, 0xa5, 0xc0, 0xd8, 0xff, 0xea, 0xc0, 0xcc, + 0xff, 0xcf, 0xa5, 0xa8, 0xf0, 0xff, 0xf3, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xba, 0xe7, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xdb, 0x00, 0x43, 0x01, + 0x33, 0x36, 0x36, 0x48, 0x3f, 0x48, 0x8d, 0x4e, + 0x4e, 0x8d, 0xff, 0xc6, 0xa8, 0xc6, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff + } +}; + +#endif /* __SOLO6X10_JPEG_H */ diff --git a/drivers/staging/media/solo6x10/solo6x10-offsets.h b/drivers/staging/media/solo6x10/solo6x10-offsets.h new file mode 100644 index 00000000000..13eeb4470dc --- /dev/null +++ b/drivers/staging/media/solo6x10/solo6x10-offsets.h @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2010-2013 Bluecherry, LLC <http://www.bluecherrydvr.com> + * + * Original author: + * Ben Collins <bcollins@ubuntu.com> + * + * Additional work by: + * John Brooks <john.brooks@bluecherry.net> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef __SOLO6X10_OFFSETS_H +#define __SOLO6X10_OFFSETS_H + +#define SOLO_DISP_EXT_ADDR 0x00000000 +#define SOLO_DISP_EXT_SIZE 0x00480000 + +#define SOLO_EOSD_EXT_ADDR \ + (SOLO_DISP_EXT_ADDR + SOLO_DISP_EXT_SIZE) +#define SOLO_EOSD_EXT_SIZE(__solo) \ + (__solo->type == SOLO_DEV_6010 ? 0x10000 : 0x20000) +#define SOLO_EOSD_EXT_SIZE_MAX 0x20000 +#define SOLO_EOSD_EXT_AREA(__solo) \ + (SOLO_EOSD_EXT_SIZE(__solo) * 32) +#define SOLO_EOSD_EXT_ADDR_CHAN(__solo, ch) \ + (SOLO_EOSD_EXT_ADDR + SOLO_EOSD_EXT_SIZE(__solo) * (ch)) + +#define SOLO_MOTION_EXT_ADDR(__solo) \ + (SOLO_EOSD_EXT_ADDR + SOLO_EOSD_EXT_AREA(__solo)) +#define SOLO_MOTION_EXT_SIZE 0x00080000 + +#define SOLO_G723_EXT_ADDR(__solo) \ + (SOLO_MOTION_EXT_ADDR(__solo) + SOLO_MOTION_EXT_SIZE) +#define SOLO_G723_EXT_SIZE 0x00010000 + +#define SOLO_CAP_EXT_ADDR(__solo) \ + (SOLO_G723_EXT_ADDR(__solo) + SOLO_G723_EXT_SIZE) + +/* 18 is the maximum number of pages required for PAL@D1, the largest frame + * possible */ +#define SOLO_CAP_PAGE_SIZE (18 << 16) + +/* Always allow the encoder enough for 16 channels, even if we have less. The + * exception is if we have card with only 32Megs of memory. */ +#define SOLO_CAP_EXT_SIZE(__solo) \ + ((((__solo->sdram_size <= (32 << 20)) ? 4 : 16) + 1) \ + * SOLO_CAP_PAGE_SIZE) + +#define SOLO_EREF_EXT_ADDR(__solo) \ + (SOLO_CAP_EXT_ADDR(__solo) + SOLO_CAP_EXT_SIZE(__solo)) +#define SOLO_EREF_EXT_SIZE 0x00140000 +#define SOLO_EREF_EXT_AREA(__solo) \ + (SOLO_EREF_EXT_SIZE * __solo->nr_chans * 2) + +#define __SOLO_JPEG_MIN_SIZE(__solo) (__solo->nr_chans * 0x00080000) + +#define SOLO_MP4E_EXT_ADDR(__solo) \ + (SOLO_EREF_EXT_ADDR(__solo) + SOLO_EREF_EXT_AREA(__solo)) +#define SOLO_MP4E_EXT_SIZE(__solo) \ + max((__solo->nr_chans * 0x00080000), \ + min(((__solo->sdram_size - SOLO_MP4E_EXT_ADDR(__solo)) - \ + __SOLO_JPEG_MIN_SIZE(__solo)), 0x00ff0000)) + +#define __SOLO_JPEG_MIN_SIZE(__solo) (__solo->nr_chans * 0x00080000) +#define SOLO_JPEG_EXT_ADDR(__solo) \ + (SOLO_MP4E_EXT_ADDR(__solo) + SOLO_MP4E_EXT_SIZE(__solo)) +#define SOLO_JPEG_EXT_SIZE(__solo) \ + max(__SOLO_JPEG_MIN_SIZE(__solo), \ + min((__solo->sdram_size - SOLO_JPEG_EXT_ADDR(__solo)), 0x00ff0000)) + +#define SOLO_SDRAM_END(__solo) \ + (SOLO_JPEG_EXT_ADDR(__solo) + SOLO_JPEG_EXT_SIZE(__solo)) + +#endif /* __SOLO6X10_OFFSETS_H */ diff --git a/drivers/staging/media/solo6x10/solo6x10-p2m.c b/drivers/staging/media/solo6x10/solo6x10-p2m.c new file mode 100644 index 00000000000..7f2f2472655 --- /dev/null +++ b/drivers/staging/media/solo6x10/solo6x10-p2m.c @@ -0,0 +1,333 @@ +/* + * Copyright (C) 2010-2013 Bluecherry, LLC <http://www.bluecherrydvr.com> + * + * Original author: + * Ben Collins <bcollins@ubuntu.com> + * + * Additional work by: + * John Brooks <john.brooks@bluecherry.net> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> + +#include "solo6x10.h" + +static int multi_p2m; +module_param(multi_p2m, uint, 0644); +MODULE_PARM_DESC(multi_p2m, + "Use multiple P2M DMA channels (default: no, 6010-only)"); + +static int desc_mode; +module_param(desc_mode, uint, 0644); +MODULE_PARM_DESC(desc_mode, + "Allow use of descriptor mode DMA (default: no, 6010-only)"); + +int solo_p2m_dma(struct solo_dev *solo_dev, int wr, + void *sys_addr, u32 ext_addr, u32 size, + int repeat, u32 ext_size) +{ + dma_addr_t dma_addr; + int ret; + + if (WARN_ON_ONCE((unsigned long)sys_addr & 0x03)) + return -EINVAL; + if (WARN_ON_ONCE(!size)) + return -EINVAL; + + dma_addr = pci_map_single(solo_dev->pdev, sys_addr, size, + wr ? PCI_DMA_TODEVICE : PCI_DMA_FROMDEVICE); + if (pci_dma_mapping_error(solo_dev->pdev, dma_addr)) + return -ENOMEM; + + ret = solo_p2m_dma_t(solo_dev, wr, dma_addr, ext_addr, size, + repeat, ext_size); + + pci_unmap_single(solo_dev->pdev, dma_addr, size, + wr ? PCI_DMA_TODEVICE : PCI_DMA_FROMDEVICE); + + return ret; +} + +/* Mutex must be held for p2m_id before calling this!! */ +int solo_p2m_dma_desc(struct solo_dev *solo_dev, + struct solo_p2m_desc *desc, dma_addr_t desc_dma, + int desc_cnt) +{ + struct solo_p2m_dev *p2m_dev; + unsigned int timeout; + unsigned int config = 0; + int ret = 0; + int p2m_id = 0; + + /* Get next ID. According to Softlogic, 6110 has problems on !=0 P2M */ + if (solo_dev->type != SOLO_DEV_6110 && multi_p2m) { + p2m_id = atomic_inc_return(&solo_dev->p2m_count) % SOLO_NR_P2M; + if (p2m_id < 0) + p2m_id = -p2m_id; + } + + p2m_dev = &solo_dev->p2m_dev[p2m_id]; + + if (mutex_lock_interruptible(&p2m_dev->mutex)) + return -EINTR; + + reinit_completion(&p2m_dev->completion); + p2m_dev->error = 0; + + if (desc_cnt > 1 && solo_dev->type != SOLO_DEV_6110 && desc_mode) { + /* For 6010 with more than one desc, we can do a one-shot */ + p2m_dev->desc_count = p2m_dev->desc_idx = 0; + config = solo_reg_read(solo_dev, SOLO_P2M_CONFIG(p2m_id)); + + solo_reg_write(solo_dev, SOLO_P2M_DES_ADR(p2m_id), desc_dma); + solo_reg_write(solo_dev, SOLO_P2M_DESC_ID(p2m_id), desc_cnt); + solo_reg_write(solo_dev, SOLO_P2M_CONFIG(p2m_id), config | + SOLO_P2M_DESC_MODE); + } else { + /* For single descriptors and 6110, we need to run each desc */ + p2m_dev->desc_count = desc_cnt; + p2m_dev->desc_idx = 1; + p2m_dev->descs = desc; + + solo_reg_write(solo_dev, SOLO_P2M_TAR_ADR(p2m_id), + desc[1].dma_addr); + solo_reg_write(solo_dev, SOLO_P2M_EXT_ADR(p2m_id), + desc[1].ext_addr); + solo_reg_write(solo_dev, SOLO_P2M_EXT_CFG(p2m_id), + desc[1].cfg); + solo_reg_write(solo_dev, SOLO_P2M_CONTROL(p2m_id), + desc[1].ctrl); + } + + timeout = wait_for_completion_timeout(&p2m_dev->completion, + solo_dev->p2m_jiffies); + + if (WARN_ON_ONCE(p2m_dev->error)) + ret = -EIO; + else if (timeout == 0) { + solo_dev->p2m_timeouts++; + ret = -EAGAIN; + } + + solo_reg_write(solo_dev, SOLO_P2M_CONTROL(p2m_id), 0); + + /* Don't write here for the no_desc_mode case, because config is 0. + * We can't test no_desc_mode again, it might race. */ + if (desc_cnt > 1 && solo_dev->type != SOLO_DEV_6110 && config) + solo_reg_write(solo_dev, SOLO_P2M_CONFIG(p2m_id), config); + + mutex_unlock(&p2m_dev->mutex); + + return ret; +} + +void solo_p2m_fill_desc(struct solo_p2m_desc *desc, int wr, + dma_addr_t dma_addr, u32 ext_addr, u32 size, + int repeat, u32 ext_size) +{ + WARN_ON_ONCE(dma_addr & 0x03); + WARN_ON_ONCE(!size); + + desc->cfg = SOLO_P2M_COPY_SIZE(size >> 2); + desc->ctrl = SOLO_P2M_BURST_SIZE(SOLO_P2M_BURST_256) | + (wr ? SOLO_P2M_WRITE : 0) | SOLO_P2M_TRANS_ON; + + if (repeat) { + desc->cfg |= SOLO_P2M_EXT_INC(ext_size >> 2); + desc->ctrl |= SOLO_P2M_PCI_INC(size >> 2) | + SOLO_P2M_REPEAT(repeat); + } + + desc->dma_addr = dma_addr; + desc->ext_addr = ext_addr; +} + +int solo_p2m_dma_t(struct solo_dev *solo_dev, int wr, + dma_addr_t dma_addr, u32 ext_addr, u32 size, + int repeat, u32 ext_size) +{ + struct solo_p2m_desc desc[2]; + + solo_p2m_fill_desc(&desc[1], wr, dma_addr, ext_addr, size, repeat, + ext_size); + + /* No need for desc_dma since we know it is a single-shot */ + return solo_p2m_dma_desc(solo_dev, desc, 0, 1); +} + +void solo_p2m_isr(struct solo_dev *solo_dev, int id) +{ + struct solo_p2m_dev *p2m_dev = &solo_dev->p2m_dev[id]; + struct solo_p2m_desc *desc; + + if (p2m_dev->desc_count <= p2m_dev->desc_idx) { + complete(&p2m_dev->completion); + return; + } + + /* Setup next descriptor */ + p2m_dev->desc_idx++; + desc = &p2m_dev->descs[p2m_dev->desc_idx]; + + solo_reg_write(solo_dev, SOLO_P2M_CONTROL(id), 0); + solo_reg_write(solo_dev, SOLO_P2M_TAR_ADR(id), desc->dma_addr); + solo_reg_write(solo_dev, SOLO_P2M_EXT_ADR(id), desc->ext_addr); + solo_reg_write(solo_dev, SOLO_P2M_EXT_CFG(id), desc->cfg); + solo_reg_write(solo_dev, SOLO_P2M_CONTROL(id), desc->ctrl); +} + +void solo_p2m_error_isr(struct solo_dev *solo_dev) +{ + unsigned int err = solo_reg_read(solo_dev, SOLO_PCI_ERR); + struct solo_p2m_dev *p2m_dev; + int i; + + if (!(err & (SOLO_PCI_ERR_P2M | SOLO_PCI_ERR_P2M_DESC))) + return; + + for (i = 0; i < SOLO_NR_P2M; i++) { + p2m_dev = &solo_dev->p2m_dev[i]; + p2m_dev->error = 1; + solo_reg_write(solo_dev, SOLO_P2M_CONTROL(i), 0); + complete(&p2m_dev->completion); + } +} + +void solo_p2m_exit(struct solo_dev *solo_dev) +{ + int i; + + for (i = 0; i < SOLO_NR_P2M; i++) + solo_irq_off(solo_dev, SOLO_IRQ_P2M(i)); +} + +static int solo_p2m_test(struct solo_dev *solo_dev, int base, int size) +{ + u32 *wr_buf; + u32 *rd_buf; + int i; + int ret = -EIO; + int order = get_order(size); + + wr_buf = (u32 *)__get_free_pages(GFP_KERNEL, order); + if (wr_buf == NULL) + return -1; + + rd_buf = (u32 *)__get_free_pages(GFP_KERNEL, order); + if (rd_buf == NULL) { + free_pages((unsigned long)wr_buf, order); + return -1; + } + + for (i = 0; i < (size >> 3); i++) + *(wr_buf + i) = (i << 16) | (i + 1); + + for (i = (size >> 3); i < (size >> 2); i++) + *(wr_buf + i) = ~((i << 16) | (i + 1)); + + memset(rd_buf, 0x55, size); + + if (solo_p2m_dma(solo_dev, 1, wr_buf, base, size, 0, 0)) + goto test_fail; + + if (solo_p2m_dma(solo_dev, 0, rd_buf, base, size, 0, 0)) + goto test_fail; + + for (i = 0; i < (size >> 2); i++) { + if (*(wr_buf + i) != *(rd_buf + i)) + goto test_fail; + } + + ret = 0; + +test_fail: + free_pages((unsigned long)wr_buf, order); + free_pages((unsigned long)rd_buf, order); + + return ret; +} + +int solo_p2m_init(struct solo_dev *solo_dev) +{ + struct solo_p2m_dev *p2m_dev; + int i; + + for (i = 0; i < SOLO_NR_P2M; i++) { + p2m_dev = &solo_dev->p2m_dev[i]; + + mutex_init(&p2m_dev->mutex); + init_completion(&p2m_dev->completion); + + solo_reg_write(solo_dev, SOLO_P2M_CONTROL(i), 0); + solo_reg_write(solo_dev, SOLO_P2M_CONFIG(i), + SOLO_P2M_CSC_16BIT_565 | + SOLO_P2M_DESC_INTR_OPT | + SOLO_P2M_DMA_INTERVAL(0) | + SOLO_P2M_PCI_MASTER_MODE); + solo_irq_on(solo_dev, SOLO_IRQ_P2M(i)); + } + + /* Find correct SDRAM size */ + for (solo_dev->sdram_size = 0, i = 2; i >= 0; i--) { + solo_reg_write(solo_dev, SOLO_DMA_CTRL, + SOLO_DMA_CTRL_REFRESH_CYCLE(1) | + SOLO_DMA_CTRL_SDRAM_SIZE(i) | + SOLO_DMA_CTRL_SDRAM_CLK_INVERT | + SOLO_DMA_CTRL_READ_CLK_SELECT | + SOLO_DMA_CTRL_LATENCY(1)); + + solo_reg_write(solo_dev, SOLO_SYS_CFG, solo_dev->sys_config | + SOLO_SYS_CFG_RESET); + solo_reg_write(solo_dev, SOLO_SYS_CFG, solo_dev->sys_config); + + switch (i) { + case 2: + if (solo_p2m_test(solo_dev, 0x07ff0000, 0x00010000) || + solo_p2m_test(solo_dev, 0x05ff0000, 0x00010000)) + continue; + break; + + case 1: + if (solo_p2m_test(solo_dev, 0x03ff0000, 0x00010000)) + continue; + break; + + default: + if (solo_p2m_test(solo_dev, 0x01ff0000, 0x00010000)) + continue; + } + + solo_dev->sdram_size = (32 << 20) << i; + break; + } + + if (!solo_dev->sdram_size) { + dev_err(&solo_dev->pdev->dev, "Error detecting SDRAM size\n"); + return -EIO; + } + + if (SOLO_SDRAM_END(solo_dev) > solo_dev->sdram_size) { + dev_err(&solo_dev->pdev->dev, + "SDRAM is not large enough (%u < %u)\n", + solo_dev->sdram_size, SOLO_SDRAM_END(solo_dev)); + return -EIO; + } + + return 0; +} diff --git a/drivers/staging/media/solo6x10/solo6x10-regs.h b/drivers/staging/media/solo6x10/solo6x10-regs.h new file mode 100644 index 00000000000..428f6c95118 --- /dev/null +++ b/drivers/staging/media/solo6x10/solo6x10-regs.h @@ -0,0 +1,639 @@ +/* + * Copyright (C) 2010-2013 Bluecherry, LLC <http://www.bluecherrydvr.com> + * + * Original author: + * Ben Collins <bcollins@ubuntu.com> + * + * Additional work by: + * John Brooks <john.brooks@bluecherry.net> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef __SOLO6X10_REGISTERS_H +#define __SOLO6X10_REGISTERS_H + +#include "solo6x10-offsets.h" + +/* Global 6010 system configuration */ +#define SOLO_SYS_CFG 0x0000 +#define SOLO_SYS_CFG_FOUT_EN 0x00000001 +#define SOLO_SYS_CFG_PLL_BYPASS 0x00000002 +#define SOLO_SYS_CFG_PLL_PWDN 0x00000004 +#define SOLO_SYS_CFG_OUTDIV(__n) (((__n) & 0x003) << 3) +#define SOLO_SYS_CFG_FEEDBACKDIV(__n) (((__n) & 0x1ff) << 5) +#define SOLO_SYS_CFG_INPUTDIV(__n) (((__n) & 0x01f) << 14) +#define SOLO_SYS_CFG_CLOCK_DIV 0x00080000 +#define SOLO_SYS_CFG_NCLK_DELAY(__n) (((__n) & 0x003) << 24) +#define SOLO_SYS_CFG_PCLK_DELAY(__n) (((__n) & 0x00f) << 26) +#define SOLO_SYS_CFG_SDRAM64BIT 0x40000000 +#define SOLO_SYS_CFG_RESET 0x80000000 + +#define SOLO_DMA_CTRL 0x0004 +#define SOLO_DMA_CTRL_REFRESH_CYCLE(n) ((n)<<8) +/* 0=16/32MB, 1=32/64MB, 2=64/128MB, 3=128/256MB */ +#define SOLO_DMA_CTRL_SDRAM_SIZE(n) ((n)<<6) +#define SOLO_DMA_CTRL_SDRAM_CLK_INVERT (1<<5) +#define SOLO_DMA_CTRL_STROBE_SELECT (1<<4) +#define SOLO_DMA_CTRL_READ_DATA_SELECT (1<<3) +#define SOLO_DMA_CTRL_READ_CLK_SELECT (1<<2) +#define SOLO_DMA_CTRL_LATENCY(n) ((n)<<0) + +/* Some things we set in this are undocumented. Why Softlogic?!?! */ +#define SOLO_DMA_CTRL1 0x0008 + +#define SOLO_SYS_VCLK 0x000C +#define SOLO_VCLK_INVERT (1<<22) +/* 0=sys_clk/4, 1=sys_clk/2, 2=clk_in/2 of system input */ +#define SOLO_VCLK_SELECT(n) ((n)<<20) +#define SOLO_VCLK_VIN1415_DELAY(n) ((n)<<14) +#define SOLO_VCLK_VIN1213_DELAY(n) ((n)<<12) +#define SOLO_VCLK_VIN1011_DELAY(n) ((n)<<10) +#define SOLO_VCLK_VIN0809_DELAY(n) ((n)<<8) +#define SOLO_VCLK_VIN0607_DELAY(n) ((n)<<6) +#define SOLO_VCLK_VIN0405_DELAY(n) ((n)<<4) +#define SOLO_VCLK_VIN0203_DELAY(n) ((n)<<2) +#define SOLO_VCLK_VIN0001_DELAY(n) ((n)<<0) + +#define SOLO_IRQ_STAT 0x0010 +#define SOLO_IRQ_MASK 0x0014 +#define SOLO_IRQ_P2M(n) (1<<((n)+17)) +#define SOLO_IRQ_GPIO (1<<16) +#define SOLO_IRQ_VIDEO_LOSS (1<<15) +#define SOLO_IRQ_VIDEO_IN (1<<14) +#define SOLO_IRQ_MOTION (1<<13) +#define SOLO_IRQ_ATA_CMD (1<<12) +#define SOLO_IRQ_ATA_DIR (1<<11) +#define SOLO_IRQ_PCI_ERR (1<<10) +#define SOLO_IRQ_PS2_1 (1<<9) +#define SOLO_IRQ_PS2_0 (1<<8) +#define SOLO_IRQ_SPI (1<<7) +#define SOLO_IRQ_IIC (1<<6) +#define SOLO_IRQ_UART(n) (1<<((n) + 4)) +#define SOLO_IRQ_G723 (1<<3) +#define SOLO_IRQ_DECODER (1<<1) +#define SOLO_IRQ_ENCODER (1<<0) + +#define SOLO_CHIP_OPTION 0x001C +#define SOLO_CHIP_ID_MASK 0x00000007 + +#define SOLO_PLL_CONFIG 0x0020 /* 6110 Only */ + +#define SOLO_EEPROM_CTRL 0x0060 +#define SOLO_EEPROM_ACCESS_EN (1<<7) +#define SOLO_EEPROM_CS (1<<3) +#define SOLO_EEPROM_CLK (1<<2) +#define SOLO_EEPROM_DO (1<<1) +#define SOLO_EEPROM_DI (1<<0) +#define SOLO_EEPROM_ENABLE (SOLO_EEPROM_ACCESS_EN | SOLO_EEPROM_CS) + +#define SOLO_PCI_ERR 0x0070 +#define SOLO_PCI_ERR_FATAL 0x00000001 +#define SOLO_PCI_ERR_PARITY 0x00000002 +#define SOLO_PCI_ERR_TARGET 0x00000004 +#define SOLO_PCI_ERR_TIMEOUT 0x00000008 +#define SOLO_PCI_ERR_P2M 0x00000010 +#define SOLO_PCI_ERR_ATA 0x00000020 +#define SOLO_PCI_ERR_P2M_DESC 0x00000040 +#define SOLO_PCI_ERR_FSM0(__s) (((__s) >> 16) & 0x0f) +#define SOLO_PCI_ERR_FSM1(__s) (((__s) >> 20) & 0x0f) +#define SOLO_PCI_ERR_FSM2(__s) (((__s) >> 24) & 0x1f) + +#define SOLO_P2M_BASE 0x0080 + +#define SOLO_P2M_CONFIG(n) (0x0080 + ((n)*0x20)) +#define SOLO_P2M_DMA_INTERVAL(n) ((n)<<6)/* N*32 clocks */ +#define SOLO_P2M_CSC_BYTE_REORDER (1<<5) /* BGR -> RGB */ +/* 0:r=[14:10] g=[9:5] b=[4:0], 1:r=[15:11] g=[10:5] b=[4:0] */ +#define SOLO_P2M_CSC_16BIT_565 (1<<4) +#define SOLO_P2M_UV_SWAP (1<<3) +#define SOLO_P2M_PCI_MASTER_MODE (1<<2) +#define SOLO_P2M_DESC_INTR_OPT (1<<1) /* 1:Empty, 0:Each */ +#define SOLO_P2M_DESC_MODE (1<<0) + +#define SOLO_P2M_DES_ADR(n) (0x0084 + ((n)*0x20)) + +#define SOLO_P2M_DESC_ID(n) (0x0088 + ((n)*0x20)) +#define SOLO_P2M_UPDATE_ID(n) ((n)<<0) + +#define SOLO_P2M_STATUS(n) (0x008C + ((n)*0x20)) +#define SOLO_P2M_COMMAND_DONE (1<<8) +#define SOLO_P2M_CURRENT_ID(stat) (0xff & (stat)) + +#define SOLO_P2M_CONTROL(n) (0x0090 + ((n)*0x20)) +#define SOLO_P2M_PCI_INC(n) ((n)<<20) +#define SOLO_P2M_REPEAT(n) ((n)<<10) +/* 0:512, 1:256, 2:128, 3:64, 4:32, 5:128(2page) */ +#define SOLO_P2M_BURST_SIZE(n) ((n)<<7) +#define SOLO_P2M_BURST_512 0 +#define SOLO_P2M_BURST_256 1 +#define SOLO_P2M_BURST_128 2 +#define SOLO_P2M_BURST_64 3 +#define SOLO_P2M_BURST_32 4 +#define SOLO_P2M_CSC_16BIT (1<<6) /* 0:24bit, 1:16bit */ +/* 0:Y[0]<-0(OFF), 1:Y[0]<-1(ON), 2:Y[0]<-G[0], 3:Y[0]<-Bit[15] */ +#define SOLO_P2M_ALPHA_MODE(n) ((n)<<4) +#define SOLO_P2M_CSC_ON (1<<3) +#define SOLO_P2M_INTERRUPT_REQ (1<<2) +#define SOLO_P2M_WRITE (1<<1) +#define SOLO_P2M_TRANS_ON (1<<0) + +#define SOLO_P2M_EXT_CFG(n) (0x0094 + ((n)*0x20)) +#define SOLO_P2M_EXT_INC(n) ((n)<<20) +#define SOLO_P2M_COPY_SIZE(n) ((n)<<0) + +#define SOLO_P2M_TAR_ADR(n) (0x0098 + ((n)*0x20)) + +#define SOLO_P2M_EXT_ADR(n) (0x009C + ((n)*0x20)) + +#define SOLO_P2M_BUFFER(i) (0x2000 + ((i)*4)) + +#define SOLO_VI_CH_SWITCH_0 0x0100 +#define SOLO_VI_CH_SWITCH_1 0x0104 +#define SOLO_VI_CH_SWITCH_2 0x0108 + +#define SOLO_VI_CH_ENA 0x010C +#define SOLO_VI_CH_FORMAT 0x0110 +#define SOLO_VI_FD_SEL_MASK(n) ((n)<<16) +#define SOLO_VI_PROG_MASK(n) ((n)<<0) + +#define SOLO_VI_FMT_CFG 0x0114 +#define SOLO_VI_FMT_CHECK_VCOUNT (1<<31) +#define SOLO_VI_FMT_CHECK_HCOUNT (1<<30) +#define SOLO_VI_FMT_TEST_SIGNAL (1<<28) + +#define SOLO_VI_PAGE_SW 0x0118 +#define SOLO_FI_INV_DISP_LIVE(n) ((n)<<8) +#define SOLO_FI_INV_DISP_OUT(n) ((n)<<7) +#define SOLO_DISP_SYNC_FI(n) ((n)<<6) +#define SOLO_PIP_PAGE_ADD(n) ((n)<<3) +#define SOLO_NORMAL_PAGE_ADD(n) ((n)<<0) + +#define SOLO_VI_ACT_I_P 0x011C +#define SOLO_VI_ACT_I_S 0x0120 +#define SOLO_VI_ACT_P 0x0124 +#define SOLO_VI_FI_INVERT (1<<31) +#define SOLO_VI_H_START(n) ((n)<<21) +#define SOLO_VI_V_START(n) ((n)<<11) +#define SOLO_VI_V_STOP(n) ((n)<<0) + +#define SOLO_VI_STATUS0 0x0128 +#define SOLO_VI_STATUS0_PAGE(__n) ((__n) & 0x07) +#define SOLO_VI_STATUS1 0x012C + +/* XXX: Might be better off in kernel level disp.h */ +#define DISP_PAGE(stat) ((stat) & 0x07) + +#define SOLO_VI_PB_CONFIG 0x0130 +#define SOLO_VI_PB_USER_MODE (1<<1) +#define SOLO_VI_PB_PAL (1<<0) +#define SOLO_VI_PB_RANGE_HV 0x0134 +#define SOLO_VI_PB_HSIZE(h) ((h)<<12) +#define SOLO_VI_PB_VSIZE(v) ((v)<<0) +#define SOLO_VI_PB_ACT_H 0x0138 +#define SOLO_VI_PB_HSTART(n) ((n)<<12) +#define SOLO_VI_PB_HSTOP(n) ((n)<<0) +#define SOLO_VI_PB_ACT_V 0x013C +#define SOLO_VI_PB_VSTART(n) ((n)<<12) +#define SOLO_VI_PB_VSTOP(n) ((n)<<0) + +#define SOLO_VI_MOSAIC(ch) (0x0140 + ((ch)*4)) +#define SOLO_VI_MOSAIC_SX(x) ((x)<<24) +#define SOLO_VI_MOSAIC_EX(x) ((x)<<16) +#define SOLO_VI_MOSAIC_SY(x) ((x)<<8) +#define SOLO_VI_MOSAIC_EY(x) ((x)<<0) + +#define SOLO_VI_WIN_CTRL0(ch) (0x0180 + ((ch)*4)) +#define SOLO_VI_WIN_CTRL1(ch) (0x01C0 + ((ch)*4)) + +#define SOLO_VI_WIN_CHANNEL(n) ((n)<<28) + +#define SOLO_VI_WIN_PIP(n) ((n)<<27) +#define SOLO_VI_WIN_SCALE(n) ((n)<<24) + +#define SOLO_VI_WIN_SX(x) ((x)<<12) +#define SOLO_VI_WIN_EX(x) ((x)<<0) + +#define SOLO_VI_WIN_SY(x) ((x)<<12) +#define SOLO_VI_WIN_EY(x) ((x)<<0) + +#define SOLO_VI_WIN_ON(ch) (0x0200 + ((ch)*4)) + +#define SOLO_VI_WIN_SW 0x0240 +#define SOLO_VI_WIN_LIVE_AUTO_MUTE 0x0244 + +#define SOLO_VI_MOT_ADR 0x0260 +#define SOLO_VI_MOTION_EN(mask) ((mask)<<16) +#define SOLO_VI_MOT_CTRL 0x0264 +#define SOLO_VI_MOTION_FRAME_COUNT(n) ((n)<<24) +#define SOLO_VI_MOTION_SAMPLE_LENGTH(n) ((n)<<16) +#define SOLO_VI_MOTION_INTR_START_STOP (1<<15) +#define SOLO_VI_MOTION_FREEZE_DATA (1<<14) +#define SOLO_VI_MOTION_SAMPLE_COUNT(n) ((n)<<0) +#define SOLO_VI_MOT_CLEAR 0x0268 +#define SOLO_VI_MOT_STATUS 0x026C +#define SOLO_VI_MOTION_CNT(n) ((n)<<0) +#define SOLO_VI_MOTION_BORDER 0x0270 +#define SOLO_VI_MOTION_BAR 0x0274 +#define SOLO_VI_MOTION_Y_SET (1<<29) +#define SOLO_VI_MOTION_Y_ADD (1<<28) +#define SOLO_VI_MOTION_CB_SET (1<<27) +#define SOLO_VI_MOTION_CB_ADD (1<<26) +#define SOLO_VI_MOTION_CR_SET (1<<25) +#define SOLO_VI_MOTION_CR_ADD (1<<24) +#define SOLO_VI_MOTION_Y_VALUE(v) ((v)<<16) +#define SOLO_VI_MOTION_CB_VALUE(v) ((v)<<8) +#define SOLO_VI_MOTION_CR_VALUE(v) ((v)<<0) + +#define SOLO_VO_FMT_ENC 0x0300 +#define SOLO_VO_SCAN_MODE_PROGRESSIVE (1<<31) +#define SOLO_VO_FMT_TYPE_PAL (1<<30) +#define SOLO_VO_FMT_TYPE_NTSC 0 +#define SOLO_VO_USER_SET (1<<29) + +#define SOLO_VO_FI_CHANGE (1<<20) +#define SOLO_VO_USER_COLOR_SET_VSYNC (1<<19) +#define SOLO_VO_USER_COLOR_SET_HSYNC (1<<18) +#define SOLO_VO_USER_COLOR_SET_NAH (1<<17) +#define SOLO_VO_USER_COLOR_SET_NAV (1<<16) +#define SOLO_VO_NA_COLOR_Y(Y) ((Y)<<8) +#define SOLO_VO_NA_COLOR_CB(CB) (((CB)/16)<<4) +#define SOLO_VO_NA_COLOR_CR(CR) (((CR)/16)<<0) + +#define SOLO_VO_ACT_H 0x0304 +#define SOLO_VO_H_BLANK(n) ((n)<<22) +#define SOLO_VO_H_START(n) ((n)<<11) +#define SOLO_VO_H_STOP(n) ((n)<<0) + +#define SOLO_VO_ACT_V 0x0308 +#define SOLO_VO_V_BLANK(n) ((n)<<22) +#define SOLO_VO_V_START(n) ((n)<<11) +#define SOLO_VO_V_STOP(n) ((n)<<0) + +#define SOLO_VO_RANGE_HV 0x030C +#define SOLO_VO_SYNC_INVERT (1<<24) +#define SOLO_VO_HSYNC_INVERT (1<<23) +#define SOLO_VO_VSYNC_INVERT (1<<22) +#define SOLO_VO_H_LEN(n) ((n)<<11) +#define SOLO_VO_V_LEN(n) ((n)<<0) + +#define SOLO_VO_DISP_CTRL 0x0310 +#define SOLO_VO_DISP_ON (1<<31) +#define SOLO_VO_DISP_ERASE_COUNT(n) ((n&0xf)<<24) +#define SOLO_VO_DISP_DOUBLE_SCAN (1<<22) +#define SOLO_VO_DISP_SINGLE_PAGE (1<<21) +#define SOLO_VO_DISP_BASE(n) (((n)>>16) & 0xffff) + +#define SOLO_VO_DISP_ERASE 0x0314 +#define SOLO_VO_DISP_ERASE_ON (1<<0) + +#define SOLO_VO_ZOOM_CTRL 0x0318 +#define SOLO_VO_ZOOM_VER_ON (1<<24) +#define SOLO_VO_ZOOM_HOR_ON (1<<23) +#define SOLO_VO_ZOOM_V_COMP (1<<22) +#define SOLO_VO_ZOOM_SX(h) (((h)/2)<<11) +#define SOLO_VO_ZOOM_SY(v) (((v)/2)<<0) + +#define SOLO_VO_FREEZE_CTRL 0x031C +#define SOLO_VO_FREEZE_ON (1<<1) +#define SOLO_VO_FREEZE_INTERPOLATION (1<<0) + +#define SOLO_VO_BKG_COLOR 0x0320 +#define SOLO_BG_Y(y) ((y)<<16) +#define SOLO_BG_U(u) ((u)<<8) +#define SOLO_BG_V(v) ((v)<<0) + +#define SOLO_VO_DEINTERLACE 0x0324 +#define SOLO_VO_DEINTERLACE_THRESHOLD(n) ((n)<<8) +#define SOLO_VO_DEINTERLACE_EDGE_VALUE(n) ((n)<<0) + +#define SOLO_VO_BORDER_LINE_COLOR 0x0330 +#define SOLO_VO_BORDER_FILL_COLOR 0x0334 +#define SOLO_VO_BORDER_LINE_MASK 0x0338 +#define SOLO_VO_BORDER_FILL_MASK 0x033c + +#define SOLO_VO_BORDER_X(n) (0x0340+((n)*4)) +#define SOLO_VO_BORDER_Y(n) (0x0354+((n)*4)) + +#define SOLO_VO_CELL_EXT_SET 0x0368 +#define SOLO_VO_CELL_EXT_START 0x036c +#define SOLO_VO_CELL_EXT_STOP 0x0370 + +#define SOLO_VO_CELL_EXT_SET2 0x0374 +#define SOLO_VO_CELL_EXT_START2 0x0378 +#define SOLO_VO_CELL_EXT_STOP2 0x037c + +#define SOLO_VO_RECTANGLE_CTRL(n) (0x0368+((n)*12)) +#define SOLO_VO_RECTANGLE_START(n) (0x036c+((n)*12)) +#define SOLO_VO_RECTANGLE_STOP(n) (0x0370+((n)*12)) + +#define SOLO_VO_CURSOR_POS (0x0380) +#define SOLO_VO_CURSOR_CLR (0x0384) +#define SOLO_VO_CURSOR_CLR2 (0x0388) +#define SOLO_VO_CURSOR_MASK(id) (0x0390+((id)*4)) + +#define SOLO_VO_EXPANSION(id) (0x0250+((id)*4)) + +#define SOLO_OSG_CONFIG 0x03E0 +#define SOLO_VO_OSG_ON (1<<31) +#define SOLO_VO_OSG_COLOR_MUTE (1<<28) +#define SOLO_VO_OSG_ALPHA_RATE(n) ((n)<<22) +#define SOLO_VO_OSG_ALPHA_BG_RATE(n) ((n)<<16) +#define SOLO_VO_OSG_BASE(offset) (((offset)>>16)&0xffff) + +#define SOLO_OSG_ERASE 0x03E4 +#define SOLO_OSG_ERASE_ON (0x80) +#define SOLO_OSG_ERASE_OFF (0x00) + +#define SOLO_VO_OSG_BLINK 0x03E8 +#define SOLO_VO_OSG_BLINK_ON (1<<1) +#define SOLO_VO_OSG_BLINK_INTREVAL18 (1<<0) + +#define SOLO_CAP_BASE 0x0400 +#define SOLO_CAP_MAX_PAGE(n) ((n)<<16) +#define SOLO_CAP_BASE_ADDR(n) ((n)<<0) +#define SOLO_CAP_BTW 0x0404 +#define SOLO_CAP_PROG_BANDWIDTH(n) ((n)<<8) +#define SOLO_CAP_MAX_BANDWIDTH(n) ((n)<<0) + +#define SOLO_DIM_SCALE1 0x0408 +#define SOLO_DIM_SCALE2 0x040C +#define SOLO_DIM_SCALE3 0x0410 +#define SOLO_DIM_SCALE4 0x0414 +#define SOLO_DIM_SCALE5 0x0418 +#define SOLO_DIM_V_MB_NUM_FRAME(n) ((n)<<16) +#define SOLO_DIM_V_MB_NUM_FIELD(n) ((n)<<8) +#define SOLO_DIM_H_MB_NUM(n) ((n)<<0) + +#define SOLO_DIM_PROG 0x041C +#define SOLO_CAP_STATUS 0x0420 + +#define SOLO_CAP_CH_SCALE(ch) (0x0440+((ch)*4)) +#define SOLO_CAP_CH_COMP_ENA_E(ch) (0x0480+((ch)*4)) +#define SOLO_CAP_CH_INTV(ch) (0x04C0+((ch)*4)) +#define SOLO_CAP_CH_INTV_E(ch) (0x0500+((ch)*4)) + + +#define SOLO_VE_CFG0 0x0610 +#define SOLO_VE_TWO_PAGE_MODE (1<<31) +#define SOLO_VE_INTR_CTRL(n) ((n)<<24) +#define SOLO_VE_BLOCK_SIZE(n) ((n)<<16) +#define SOLO_VE_BLOCK_BASE(n) ((n)<<0) + +#define SOLO_VE_CFG1 0x0614 +#define SOLO_VE_BYTE_ALIGN(n) ((n)<<24) +#define SOLO_VE_INSERT_INDEX (1<<18) +#define SOLO_VE_MOTION_MODE(n) ((n)<<16) +#define SOLO_VE_MOTION_BASE(n) ((n)<<0) +#define SOLO_VE_MPEG_SIZE_H(n) ((n)<<28) /* 6110 Only */ +#define SOLO_VE_JPEG_SIZE_H(n) ((n)<<20) /* 6110 Only */ +#define SOLO_VE_INSERT_INDEX_JPEG (1<<19) /* 6110 Only */ + +#define SOLO_VE_WMRK_POLY 0x061C +#define SOLO_VE_VMRK_INIT_KEY 0x0620 +#define SOLO_VE_WMRK_STRL 0x0624 +#define SOLO_VE_ENCRYP_POLY 0x0628 +#define SOLO_VE_ENCRYP_INIT 0x062C +#define SOLO_VE_ATTR 0x0630 +#define SOLO_VE_LITTLE_ENDIAN (1<<31) +#define SOLO_COMP_ATTR_RN (1<<30) +#define SOLO_COMP_ATTR_FCODE(n) ((n)<<27) +#define SOLO_COMP_TIME_INC(n) ((n)<<25) +#define SOLO_COMP_TIME_WIDTH(n) ((n)<<21) +#define SOLO_DCT_INTERVAL(n) ((n)<<16) +#define SOLO_VE_COMPT_MOT 0x0634 /* 6110 Only */ + +#define SOLO_VE_STATE(n) (0x0640+((n)*4)) + +#define SOLO_VE_JPEG_QP_TBL 0x0670 +#define SOLO_VE_JPEG_QP_CH_L 0x0674 +#define SOLO_VE_JPEG_QP_CH_H 0x0678 +#define SOLO_VE_JPEG_CFG 0x067C +#define SOLO_VE_JPEG_CTRL 0x0680 +#define SOLO_VE_CODE_ENCRYPT 0x0684 /* 6110 Only */ +#define SOLO_VE_JPEG_CFG1 0x0688 /* 6110 Only */ +#define SOLO_VE_WMRK_ENABLE 0x068C /* 6110 Only */ +#define SOLO_VE_OSD_CH 0x0690 +#define SOLO_VE_OSD_BASE 0x0694 +#define SOLO_VE_OSD_CLR 0x0698 +#define SOLO_VE_OSD_OPT 0x069C +#define SOLO_VE_OSD_V_DOUBLE (1<<16) /* 6110 Only */ +#define SOLO_VE_OSD_H_SHADOW (1<<15) +#define SOLO_VE_OSD_V_SHADOW (1<<14) +#define SOLO_VE_OSD_H_OFFSET(n) ((n & 0x7f)<<7) +#define SOLO_VE_OSD_V_OFFSET(n) (n & 0x7f) + +#define SOLO_VE_CH_INTL(ch) (0x0700+((ch)*4)) +#define SOLO_VE_CH_MOT(ch) (0x0740+((ch)*4)) +#define SOLO_VE_CH_QP(ch) (0x0780+((ch)*4)) +#define SOLO_VE_CH_QP_E(ch) (0x07C0+((ch)*4)) +#define SOLO_VE_CH_GOP(ch) (0x0800+((ch)*4)) +#define SOLO_VE_CH_GOP_E(ch) (0x0840+((ch)*4)) +#define SOLO_VE_CH_REF_BASE(ch) (0x0880+((ch)*4)) +#define SOLO_VE_CH_REF_BASE_E(ch) (0x08C0+((ch)*4)) + +#define SOLO_VE_MPEG4_QUE(n) (0x0A00+((n)*8)) +#define SOLO_VE_JPEG_QUE(n) (0x0A04+((n)*8)) + +#define SOLO_VD_CFG0 0x0900 +#define SOLO_VD_CFG_NO_WRITE_NO_WINDOW (1<<24) +#define SOLO_VD_CFG_BUSY_WIAT_CODE (1<<23) +#define SOLO_VD_CFG_BUSY_WIAT_REF (1<<22) +#define SOLO_VD_CFG_BUSY_WIAT_RES (1<<21) +#define SOLO_VD_CFG_BUSY_WIAT_MS (1<<20) +#define SOLO_VD_CFG_SINGLE_MODE (1<<18) +#define SOLO_VD_CFG_SCAL_MANUAL (1<<17) +#define SOLO_VD_CFG_USER_PAGE_CTRL (1<<16) +#define SOLO_VD_CFG_LITTLE_ENDIAN (1<<15) +#define SOLO_VD_CFG_START_FI (1<<14) +#define SOLO_VD_CFG_ERR_LOCK (1<<13) +#define SOLO_VD_CFG_ERR_INT_ENA (1<<12) +#define SOLO_VD_CFG_TIME_WIDTH(n) ((n)<<8) +#define SOLO_VD_CFG_DCT_INTERVAL(n) ((n)<<0) + +#define SOLO_VD_CFG1 0x0904 + +#define SOLO_VD_DEINTERLACE 0x0908 +#define SOLO_VD_DEINTERLACE_THRESHOLD(n) ((n)<<8) +#define SOLO_VD_DEINTERLACE_EDGE_VALUE(n) ((n)<<0) + +#define SOLO_VD_CODE_ADR 0x090C + +#define SOLO_VD_CTRL 0x0910 +#define SOLO_VD_OPER_ON (1<<31) +#define SOLO_VD_MAX_ITEM(n) ((n)<<0) + +#define SOLO_VD_STATUS0 0x0920 +#define SOLO_VD_STATUS0_INTR_ACK (1<<22) +#define SOLO_VD_STATUS0_INTR_EMPTY (1<<21) +#define SOLO_VD_STATUS0_INTR_ERR (1<<20) + +#define SOLO_VD_STATUS1 0x0924 + +#define SOLO_VD_IDX0 0x0930 +#define SOLO_VD_IDX_INTERLACE (1<<30) +#define SOLO_VD_IDX_CHANNEL(n) ((n)<<24) +#define SOLO_VD_IDX_SIZE(n) ((n)<<0) + +#define SOLO_VD_IDX1 0x0934 +#define SOLO_VD_IDX_SRC_SCALE(n) ((n)<<28) +#define SOLO_VD_IDX_WINDOW(n) ((n)<<24) +#define SOLO_VD_IDX_DEINTERLACE (1<<16) +#define SOLO_VD_IDX_H_BLOCK(n) ((n)<<8) +#define SOLO_VD_IDX_V_BLOCK(n) ((n)<<0) + +#define SOLO_VD_IDX2 0x0938 +#define SOLO_VD_IDX_REF_BASE_SIDE (1<<31) +#define SOLO_VD_IDX_REF_BASE(n) (((n)>>16)&0xffff) + +#define SOLO_VD_IDX3 0x093C +#define SOLO_VD_IDX_DISP_SCALE(n) ((n)<<28) +#define SOLO_VD_IDX_INTERLACE_WR (1<<27) +#define SOLO_VD_IDX_INTERPOL (1<<26) +#define SOLO_VD_IDX_HOR2X (1<<25) +#define SOLO_VD_IDX_OFFSET_X(n) ((n)<<12) +#define SOLO_VD_IDX_OFFSET_Y(n) ((n)<<0) + +#define SOLO_VD_IDX4 0x0940 +#define SOLO_VD_IDX_DEC_WR_PAGE(n) ((n)<<8) +#define SOLO_VD_IDX_DISP_RD_PAGE(n) ((n)<<0) + +#define SOLO_VD_WR_PAGE(n) (0x03F0 + ((n) * 4)) + + +#define SOLO_GPIO_CONFIG_0 0x0B00 +#define SOLO_GPIO_CONFIG_1 0x0B04 +#define SOLO_GPIO_DATA_OUT 0x0B08 +#define SOLO_GPIO_DATA_IN 0x0B0C +#define SOLO_GPIO_INT_ACK_STA 0x0B10 +#define SOLO_GPIO_INT_ENA 0x0B14 +#define SOLO_GPIO_INT_CFG_0 0x0B18 +#define SOLO_GPIO_INT_CFG_1 0x0B1C + + +#define SOLO_IIC_CFG 0x0B20 +#define SOLO_IIC_ENABLE (1<<8) +#define SOLO_IIC_PRESCALE(n) ((n)<<0) + +#define SOLO_IIC_CTRL 0x0B24 +#define SOLO_IIC_AUTO_CLEAR (1<<20) +#define SOLO_IIC_STATE_RX_ACK (1<<19) +#define SOLO_IIC_STATE_BUSY (1<<18) +#define SOLO_IIC_STATE_SIG_ERR (1<<17) +#define SOLO_IIC_STATE_TRNS (1<<16) +#define SOLO_IIC_CH_SET(n) ((n)<<5) +#define SOLO_IIC_ACK_EN (1<<4) +#define SOLO_IIC_START (1<<3) +#define SOLO_IIC_STOP (1<<2) +#define SOLO_IIC_READ (1<<1) +#define SOLO_IIC_WRITE (1<<0) + +#define SOLO_IIC_TXD 0x0B28 +#define SOLO_IIC_RXD 0x0B2C + +/* + * UART REGISTER + */ +#define SOLO_UART_CONTROL(n) (0x0BA0 + ((n)*0x20)) +#define SOLO_UART_CLK_DIV(n) ((n)<<24) +#define SOLO_MODEM_CTRL_EN (1<<20) +#define SOLO_PARITY_ERROR_DROP (1<<18) +#define SOLO_IRQ_ERR_EN (1<<17) +#define SOLO_IRQ_RX_EN (1<<16) +#define SOLO_IRQ_TX_EN (1<<15) +#define SOLO_RX_EN (1<<14) +#define SOLO_TX_EN (1<<13) +#define SOLO_UART_HALF_DUPLEX (1<<12) +#define SOLO_UART_LOOPBACK (1<<11) + +#define SOLO_BAUDRATE_230400 ((0<<9)|(0<<6)) +#define SOLO_BAUDRATE_115200 ((0<<9)|(1<<6)) +#define SOLO_BAUDRATE_57600 ((0<<9)|(2<<6)) +#define SOLO_BAUDRATE_38400 ((0<<9)|(3<<6)) +#define SOLO_BAUDRATE_19200 ((0<<9)|(4<<6)) +#define SOLO_BAUDRATE_9600 ((0<<9)|(5<<6)) +#define SOLO_BAUDRATE_4800 ((0<<9)|(6<<6)) +#define SOLO_BAUDRATE_2400 ((1<<9)|(6<<6)) +#define SOLO_BAUDRATE_1200 ((2<<9)|(6<<6)) +#define SOLO_BAUDRATE_300 ((3<<9)|(6<<6)) + +#define SOLO_UART_DATA_BIT_8 (3<<4) +#define SOLO_UART_DATA_BIT_7 (2<<4) +#define SOLO_UART_DATA_BIT_6 (1<<4) +#define SOLO_UART_DATA_BIT_5 (0<<4) + +#define SOLO_UART_STOP_BIT_1 (0<<2) +#define SOLO_UART_STOP_BIT_2 (1<<2) + +#define SOLO_UART_PARITY_NONE (0<<0) +#define SOLO_UART_PARITY_EVEN (2<<0) +#define SOLO_UART_PARITY_ODD (3<<0) + +#define SOLO_UART_STATUS(n) (0x0BA4 + ((n)*0x20)) +#define SOLO_UART_CTS (1<<15) +#define SOLO_UART_RX_BUSY (1<<14) +#define SOLO_UART_OVERRUN (1<<13) +#define SOLO_UART_FRAME_ERR (1<<12) +#define SOLO_UART_PARITY_ERR (1<<11) +#define SOLO_UART_TX_BUSY (1<<5) + +#define SOLO_UART_RX_BUFF_CNT(stat) (((stat)>>6) & 0x1f) +#define SOLO_UART_RX_BUFF_SIZE 8 +#define SOLO_UART_TX_BUFF_CNT(stat) (((stat)>>0) & 0x1f) +#define SOLO_UART_TX_BUFF_SIZE 8 + +#define SOLO_UART_TX_DATA(n) (0x0BA8 + ((n)*0x20)) +#define SOLO_UART_TX_DATA_PUSH (1<<8) +#define SOLO_UART_RX_DATA(n) (0x0BAC + ((n)*0x20)) +#define SOLO_UART_RX_DATA_POP (1<<8) + +#define SOLO_TIMER_CLOCK_NUM 0x0be0 +#define SOLO_TIMER_USEC 0x0be8 +#define SOLO_TIMER_SEC 0x0bec +#define SOLO_TIMER_USEC_LSB 0x0d20 /* 6110 Only */ + +#define SOLO_AUDIO_CONTROL 0x0D00 +#define SOLO_AUDIO_ENABLE (1<<31) +#define SOLO_AUDIO_MASTER_MODE (1<<30) +#define SOLO_AUDIO_I2S_MODE (1<<29) +#define SOLO_AUDIO_I2S_LR_SWAP (1<<27) +#define SOLO_AUDIO_I2S_8BIT (1<<26) +#define SOLO_AUDIO_I2S_MULTI(n) ((n)<<24) +#define SOLO_AUDIO_MIX_9TO0 (1<<23) +#define SOLO_AUDIO_DEC_9TO0_VOL(n) ((n)<<20) +#define SOLO_AUDIO_MIX_19TO10 (1<<19) +#define SOLO_AUDIO_DEC_19TO10_VOL(n) ((n)<<16) +#define SOLO_AUDIO_MODE(n) ((n)<<0) +#define SOLO_AUDIO_SAMPLE 0x0D04 +#define SOLO_AUDIO_EE_MODE_ON (1<<30) +#define SOLO_AUDIO_EE_ENC_CH(ch) ((ch)<<25) +#define SOLO_AUDIO_BITRATE(n) ((n)<<16) +#define SOLO_AUDIO_CLK_DIV(n) ((n)<<0) +#define SOLO_AUDIO_FDMA_INTR 0x0D08 +#define SOLO_AUDIO_FDMA_INTERVAL(n) ((n)<<19) +#define SOLO_AUDIO_INTR_ORDER(n) ((n)<<16) +#define SOLO_AUDIO_FDMA_BASE(n) ((n)<<0) +#define SOLO_AUDIO_EVOL_0 0x0D0C +#define SOLO_AUDIO_EVOL_1 0x0D10 +#define SOLO_AUDIO_EVOL(ch, value) ((value)<<((ch)%10)) +#define SOLO_AUDIO_STA 0x0D14 + +/* + * Watchdog configuration + */ +#define SOLO_WATCHDOG 0x0be4 +#define SOLO_WATCHDOG_SET(status, sec) (status << 8 | (sec & 0xff)) + +#endif /* __SOLO6X10_REGISTERS_H */ diff --git a/drivers/staging/media/solo6x10/solo6x10-tw28.c b/drivers/staging/media/solo6x10/solo6x10-tw28.c new file mode 100644 index 00000000000..36daa1720b5 --- /dev/null +++ b/drivers/staging/media/solo6x10/solo6x10-tw28.c @@ -0,0 +1,874 @@ +/* + * Copyright (C) 2010-2013 Bluecherry, LLC <http://www.bluecherrydvr.com> + * + * Original author: + * Ben Collins <bcollins@ubuntu.com> + * + * Additional work by: + * John Brooks <john.brooks@bluecherry.net> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include <linux/kernel.h> +#include <linux/delay.h> + +#include "solo6x10.h" +#include "solo6x10-tw28.h" + +#define DEFAULT_HDELAY_NTSC (32 - 8) +#define DEFAULT_HACTIVE_NTSC (720 + 16) +#define DEFAULT_VDELAY_NTSC (7 - 2) +#define DEFAULT_VACTIVE_NTSC (240 + 4) + +#define DEFAULT_HDELAY_PAL (32 + 4) +#define DEFAULT_HACTIVE_PAL (864-DEFAULT_HDELAY_PAL) +#define DEFAULT_VDELAY_PAL (6) +#define DEFAULT_VACTIVE_PAL (312-DEFAULT_VDELAY_PAL) + + +static const u8 tbl_tw2864_ntsc_template[] = { + 0x00, 0xf0, 0x70, 0x30, 0x80, 0x80, 0x00, 0x02, /* 0x00 */ + 0x12, 0xf5, 0x0c, 0xd0, 0x00, 0x00, 0x00, 0x7f, + 0x00, 0xf0, 0x70, 0x30, 0x80, 0x80, 0x00, 0x02, /* 0x10 */ + 0x12, 0xf5, 0x0c, 0xd0, 0x00, 0x00, 0x00, 0x7f, + 0x00, 0xf0, 0x70, 0x30, 0x80, 0x80, 0x00, 0x02, /* 0x20 */ + 0x12, 0xf5, 0x0c, 0xd0, 0x00, 0x00, 0x00, 0x7f, + 0x00, 0xf0, 0x70, 0x30, 0x80, 0x80, 0x00, 0x02, /* 0x30 */ + 0x12, 0xf5, 0x0c, 0xd0, 0x00, 0x00, 0x00, 0x7f, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x40 */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x50 */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x60 */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x70 */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA3, 0x00, + 0x00, 0x02, 0x00, 0xcc, 0x00, 0x80, 0x44, 0x50, /* 0x80 */ + 0x22, 0x01, 0xd8, 0xbc, 0xb8, 0x44, 0x38, 0x00, + 0x00, 0x78, 0x72, 0x3e, 0x14, 0xa5, 0xe4, 0x05, /* 0x90 */ + 0x00, 0x28, 0x44, 0x44, 0xa0, 0x88, 0x5a, 0x01, + 0x08, 0x08, 0x08, 0x08, 0x1a, 0x1a, 0x1a, 0x1a, /* 0xa0 */ + 0x00, 0x00, 0x00, 0xf0, 0xf0, 0xf0, 0xf0, 0x44, + 0x44, 0x0a, 0x00, 0xff, 0xef, 0xef, 0xef, 0xef, /* 0xb0 */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0xc0 */ + 0x00, 0x00, 0x55, 0x00, 0xb1, 0xe4, 0x40, 0x00, + 0x77, 0x77, 0x01, 0x13, 0x57, 0x9b, 0xdf, 0x20, /* 0xd0 */ + 0x64, 0xa8, 0xec, 0xc1, 0x0f, 0x11, 0x11, 0x81, + 0x00, 0xe0, 0xbb, 0xbb, 0x00, 0x11, 0x00, 0x00, /* 0xe0 */ + 0x11, 0x00, 0x00, 0x11, 0x00, 0x00, 0x11, 0x00, + 0x83, 0xb5, 0x09, 0x78, 0x85, 0x00, 0x01, 0x20, /* 0xf0 */ + 0x64, 0x11, 0x40, 0xaf, 0xff, 0x00, 0x00, 0x00, +}; + +static const u8 tbl_tw2864_pal_template[] = { + 0x00, 0xf0, 0x70, 0x30, 0x80, 0x80, 0x00, 0x12, /* 0x00 */ + 0x18, 0xf5, 0x0c, 0xd0, 0x00, 0x00, 0x01, 0x7f, + 0x00, 0xf0, 0x70, 0x30, 0x80, 0x80, 0x00, 0x12, /* 0x10 */ + 0x18, 0xf5, 0x0c, 0xd0, 0x00, 0x00, 0x01, 0x7f, + 0x00, 0xf0, 0x70, 0x30, 0x80, 0x80, 0x00, 0x12, /* 0x20 */ + 0x18, 0xf5, 0x0c, 0xd0, 0x00, 0x00, 0x01, 0x7f, + 0x00, 0xf0, 0x70, 0x30, 0x80, 0x80, 0x00, 0x12, /* 0x30 */ + 0x18, 0xf5, 0x0c, 0xd0, 0x00, 0x00, 0x01, 0x7f, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x40 */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x50 */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x60 */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x70 */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA3, 0x00, + 0x00, 0x02, 0x00, 0xcc, 0x00, 0x80, 0x44, 0x50, /* 0x80 */ + 0x22, 0x01, 0xd8, 0xbc, 0xb8, 0x44, 0x38, 0x00, + 0x00, 0x78, 0x72, 0x3e, 0x14, 0xa5, 0xe4, 0x05, /* 0x90 */ + 0x00, 0x28, 0x44, 0x44, 0xa0, 0x90, 0x5a, 0x01, + 0x0a, 0x0a, 0x0a, 0x0a, 0x1a, 0x1a, 0x1a, 0x1a, /* 0xa0 */ + 0x00, 0x00, 0x00, 0xf0, 0xf0, 0xf0, 0xf0, 0x44, + 0x44, 0x0a, 0x00, 0xff, 0xef, 0xef, 0xef, 0xef, /* 0xb0 */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0xc0 */ + 0x00, 0x00, 0x55, 0x00, 0xb1, 0xe4, 0x40, 0x00, + 0x77, 0x77, 0x01, 0x13, 0x57, 0x9b, 0xdf, 0x20, /* 0xd0 */ + 0x64, 0xa8, 0xec, 0xc1, 0x0f, 0x11, 0x11, 0x81, + 0x00, 0xe0, 0xbb, 0xbb, 0x00, 0x11, 0x00, 0x00, /* 0xe0 */ + 0x11, 0x00, 0x00, 0x11, 0x00, 0x00, 0x11, 0x00, + 0x83, 0xb5, 0x09, 0x00, 0xa0, 0x00, 0x01, 0x20, /* 0xf0 */ + 0x64, 0x11, 0x40, 0xaf, 0xff, 0x00, 0x00, 0x00, +}; + +static const u8 tbl_tw2865_ntsc_template[] = { + 0x00, 0xf0, 0x70, 0x30, 0x80, 0x80, 0x00, 0x02, /* 0x00 */ + 0x12, 0xff, 0x09, 0xd0, 0x00, 0x00, 0x00, 0x7f, + 0x00, 0xf0, 0x70, 0x30, 0x80, 0x80, 0x00, 0x02, /* 0x10 */ + 0x12, 0xff, 0x09, 0xd0, 0x00, 0x00, 0x00, 0x7f, + 0x00, 0xf0, 0x70, 0x30, 0x80, 0x80, 0x00, 0x02, /* 0x20 */ + 0x12, 0xff, 0x09, 0xd0, 0x00, 0x00, 0x00, 0x7f, + 0x00, 0xf0, 0x70, 0x48, 0x80, 0x80, 0x00, 0x02, /* 0x30 */ + 0x12, 0xff, 0x09, 0xd0, 0x00, 0x00, 0x00, 0x7f, + 0x00, 0x00, 0x90, 0x68, 0x00, 0x38, 0x80, 0x80, /* 0x40 */ + 0x80, 0x80, 0x77, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x50 */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x45, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x60 */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x43, + 0x08, 0x00, 0x00, 0x01, 0xf1, 0x03, 0xEF, 0x03, /* 0x70 */ + 0xE9, 0x03, 0xD9, 0x15, 0x15, 0xE4, 0xA3, 0x80, + 0x00, 0x02, 0x00, 0xCC, 0x00, 0x80, 0x44, 0x50, /* 0x80 */ + 0x22, 0x01, 0xD8, 0xBC, 0xB8, 0x44, 0x38, 0x00, + 0x00, 0x78, 0x44, 0x3D, 0x14, 0xA5, 0xE0, 0x05, /* 0x90 */ + 0x00, 0x28, 0x44, 0x44, 0xA0, 0x90, 0x52, 0x13, + 0x08, 0x08, 0x08, 0x08, 0x1A, 0x1A, 0x1B, 0x1A, /* 0xa0 */ + 0x00, 0x00, 0x00, 0xF0, 0xF0, 0xF0, 0xF0, 0x44, + 0x44, 0x4A, 0x00, 0xFF, 0xEF, 0xEF, 0xEF, 0xEF, /* 0xb0 */ + 0xFF, 0xE7, 0xE9, 0xE9, 0xEB, 0xFF, 0xD6, 0xD8, + 0xD8, 0xD7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0xc0 */ + 0x00, 0x00, 0x55, 0x00, 0xE4, 0x39, 0x00, 0x80, + 0x77, 0x77, 0x03, 0x20, 0x57, 0x9b, 0xdf, 0x31, /* 0xd0 */ + 0x64, 0xa8, 0xec, 0xd1, 0x0f, 0x11, 0x11, 0x81, + 0x10, 0xC0, 0xAA, 0xAA, 0x00, 0x11, 0x00, 0x00, /* 0xe0 */ + 0x11, 0x00, 0x00, 0x11, 0x00, 0x00, 0x11, 0x00, + 0x83, 0xB5, 0x09, 0x78, 0x85, 0x00, 0x01, 0x20, /* 0xf0 */ + 0x64, 0x51, 0x40, 0xaf, 0xFF, 0xF0, 0x00, 0xC0, +}; + +static const u8 tbl_tw2865_pal_template[] = { + 0x00, 0xf0, 0x70, 0x30, 0x80, 0x80, 0x00, 0x12, /* 0x00 */ + 0x11, 0xff, 0x01, 0xc3, 0x00, 0x00, 0x01, 0x7f, + 0x00, 0xf0, 0x70, 0x30, 0x80, 0x80, 0x00, 0x12, /* 0x10 */ + 0x11, 0xff, 0x01, 0xc3, 0x00, 0x00, 0x01, 0x7f, + 0x00, 0xf0, 0x70, 0x30, 0x80, 0x80, 0x00, 0x12, /* 0x20 */ + 0x11, 0xff, 0x01, 0xc3, 0x00, 0x00, 0x01, 0x7f, + 0x00, 0xf0, 0x70, 0x30, 0x80, 0x80, 0x00, 0x12, /* 0x30 */ + 0x11, 0xff, 0x01, 0xc3, 0x00, 0x00, 0x01, 0x7f, + 0x00, 0x94, 0x90, 0x48, 0x00, 0x38, 0x7F, 0x80, /* 0x40 */ + 0x80, 0x80, 0x77, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x50 */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x45, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x60 */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x43, + 0x08, 0x00, 0x00, 0x01, 0xf1, 0x03, 0xEF, 0x03, /* 0x70 */ + 0xEA, 0x03, 0xD9, 0x15, 0x15, 0xE4, 0xA3, 0x80, + 0x00, 0x02, 0x00, 0xCC, 0x00, 0x80, 0x44, 0x50, /* 0x80 */ + 0x22, 0x01, 0xD8, 0xBC, 0xB8, 0x44, 0x38, 0x00, + 0x00, 0x78, 0x44, 0x3D, 0x14, 0xA5, 0xE0, 0x05, /* 0x90 */ + 0x00, 0x28, 0x44, 0x44, 0xA0, 0x90, 0x52, 0x13, + 0x08, 0x08, 0x08, 0x08, 0x1A, 0x1A, 0x1A, 0x1A, /* 0xa0 */ + 0x00, 0x00, 0x00, 0xF0, 0xF0, 0xF0, 0xF0, 0x44, + 0x44, 0x4A, 0x00, 0xFF, 0xEF, 0xEF, 0xEF, 0xEF, /* 0xb0 */ + 0xFF, 0xE7, 0xE9, 0xE9, 0xE9, 0xFF, 0xD7, 0xD8, + 0xD9, 0xD8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0xc0 */ + 0x00, 0x00, 0x55, 0x00, 0xE4, 0x39, 0x00, 0x80, + 0x77, 0x77, 0x03, 0x20, 0x57, 0x9b, 0xdf, 0x31, /* 0xd0 */ + 0x64, 0xa8, 0xec, 0xd1, 0x0f, 0x11, 0x11, 0x81, + 0x10, 0xC0, 0xAA, 0xAA, 0x00, 0x11, 0x00, 0x00, /* 0xe0 */ + 0x11, 0x00, 0x00, 0x11, 0x00, 0x00, 0x11, 0x00, + 0x83, 0xB5, 0x09, 0x00, 0xA0, 0x00, 0x01, 0x20, /* 0xf0 */ + 0x64, 0x51, 0x40, 0xaf, 0xFF, 0xF0, 0x00, 0xC0, +}; + +#define is_tw286x(__solo, __id) (!(__solo->tw2815 & (1 << __id))) + +static u8 tw_readbyte(struct solo_dev *solo_dev, int chip_id, u8 tw6x_off, + u8 tw_off) +{ + if (is_tw286x(solo_dev, chip_id)) + return solo_i2c_readbyte(solo_dev, SOLO_I2C_TW, + TW_CHIP_OFFSET_ADDR(chip_id), + tw6x_off); + else + return solo_i2c_readbyte(solo_dev, SOLO_I2C_TW, + TW_CHIP_OFFSET_ADDR(chip_id), + tw_off); +} + +static void tw_writebyte(struct solo_dev *solo_dev, int chip_id, + u8 tw6x_off, u8 tw_off, u8 val) +{ + if (is_tw286x(solo_dev, chip_id)) + solo_i2c_writebyte(solo_dev, SOLO_I2C_TW, + TW_CHIP_OFFSET_ADDR(chip_id), + tw6x_off, val); + else + solo_i2c_writebyte(solo_dev, SOLO_I2C_TW, + TW_CHIP_OFFSET_ADDR(chip_id), + tw_off, val); +} + +static void tw_write_and_verify(struct solo_dev *solo_dev, u8 addr, u8 off, + u8 val) +{ + int i; + + for (i = 0; i < 5; i++) { + u8 rval = solo_i2c_readbyte(solo_dev, SOLO_I2C_TW, addr, off); + if (rval == val) + return; + + solo_i2c_writebyte(solo_dev, SOLO_I2C_TW, addr, off, val); + msleep_interruptible(1); + } + +/* printk("solo6x10/tw28: Error writing register: %02x->%02x [%02x]\n", */ +/* addr, off, val); */ +} + +static int tw2865_setup(struct solo_dev *solo_dev, u8 dev_addr) +{ + u8 tbl_tw2865_common[256]; + int i; + + if (solo_dev->video_type == SOLO_VO_FMT_TYPE_PAL) + memcpy(tbl_tw2865_common, tbl_tw2865_pal_template, + sizeof(tbl_tw2865_common)); + else + memcpy(tbl_tw2865_common, tbl_tw2865_ntsc_template, + sizeof(tbl_tw2865_common)); + + /* ALINK Mode */ + if (solo_dev->nr_chans == 4) { + tbl_tw2865_common[0xd2] = 0x01; + tbl_tw2865_common[0xcf] = 0x00; + } else if (solo_dev->nr_chans == 8) { + tbl_tw2865_common[0xd2] = 0x02; + if (dev_addr == TW_CHIP_OFFSET_ADDR(1)) + tbl_tw2865_common[0xcf] = 0x80; + } else if (solo_dev->nr_chans == 16) { + tbl_tw2865_common[0xd2] = 0x03; + if (dev_addr == TW_CHIP_OFFSET_ADDR(1)) + tbl_tw2865_common[0xcf] = 0x83; + else if (dev_addr == TW_CHIP_OFFSET_ADDR(2)) + tbl_tw2865_common[0xcf] = 0x83; + else if (dev_addr == TW_CHIP_OFFSET_ADDR(3)) + tbl_tw2865_common[0xcf] = 0x80; + } + + for (i = 0; i < 0xff; i++) { + /* Skip read only registers */ + switch (i) { + case 0xb8 ... 0xc1: + case 0xc4 ... 0xc7: + case 0xfd: + continue; + } + switch (i & ~0x30) { + case 0x00: + case 0x0c ... 0x0d: + continue; + } + + tw_write_and_verify(solo_dev, dev_addr, i, + tbl_tw2865_common[i]); + } + + return 0; +} + +static int tw2864_setup(struct solo_dev *solo_dev, u8 dev_addr) +{ + u8 tbl_tw2864_common[256]; + int i; + + if (solo_dev->video_type == SOLO_VO_FMT_TYPE_PAL) + memcpy(tbl_tw2864_common, tbl_tw2864_pal_template, + sizeof(tbl_tw2864_common)); + else + memcpy(tbl_tw2864_common, tbl_tw2864_ntsc_template, + sizeof(tbl_tw2864_common)); + + if (solo_dev->tw2865 == 0) { + /* IRQ Mode */ + if (solo_dev->nr_chans == 4) { + tbl_tw2864_common[0xd2] = 0x01; + tbl_tw2864_common[0xcf] = 0x00; + } else if (solo_dev->nr_chans == 8) { + tbl_tw2864_common[0xd2] = 0x02; + if (dev_addr == TW_CHIP_OFFSET_ADDR(0)) + tbl_tw2864_common[0xcf] = 0x43; + else if (dev_addr == TW_CHIP_OFFSET_ADDR(1)) + tbl_tw2864_common[0xcf] = 0x40; + } else if (solo_dev->nr_chans == 16) { + tbl_tw2864_common[0xd2] = 0x03; + if (dev_addr == TW_CHIP_OFFSET_ADDR(0)) + tbl_tw2864_common[0xcf] = 0x43; + else if (dev_addr == TW_CHIP_OFFSET_ADDR(1)) + tbl_tw2864_common[0xcf] = 0x43; + else if (dev_addr == TW_CHIP_OFFSET_ADDR(2)) + tbl_tw2864_common[0xcf] = 0x43; + else if (dev_addr == TW_CHIP_OFFSET_ADDR(3)) + tbl_tw2864_common[0xcf] = 0x40; + } + } else { + /* ALINK Mode. Assumes that the first tw28xx is a + * 2865 and these are in cascade. */ + for (i = 0; i <= 4; i++) + tbl_tw2864_common[0x08 | i << 4] = 0x12; + + if (solo_dev->nr_chans == 8) { + tbl_tw2864_common[0xd2] = 0x02; + if (dev_addr == TW_CHIP_OFFSET_ADDR(1)) + tbl_tw2864_common[0xcf] = 0x80; + } else if (solo_dev->nr_chans == 16) { + tbl_tw2864_common[0xd2] = 0x03; + if (dev_addr == TW_CHIP_OFFSET_ADDR(1)) + tbl_tw2864_common[0xcf] = 0x83; + else if (dev_addr == TW_CHIP_OFFSET_ADDR(2)) + tbl_tw2864_common[0xcf] = 0x83; + else if (dev_addr == TW_CHIP_OFFSET_ADDR(3)) + tbl_tw2864_common[0xcf] = 0x80; + } + } + + for (i = 0; i < 0xff; i++) { + /* Skip read only registers */ + switch (i) { + case 0xb8 ... 0xc1: + case 0xfd: + continue; + } + switch (i & ~0x30) { + case 0x00: + case 0x0c: + case 0x0d: + continue; + } + + tw_write_and_verify(solo_dev, dev_addr, i, + tbl_tw2864_common[i]); + } + + return 0; +} + +static int tw2815_setup(struct solo_dev *solo_dev, u8 dev_addr) +{ + u8 tbl_ntsc_tw2815_common[] = { + 0x00, 0xc8, 0x20, 0xd0, 0x06, 0xf0, 0x08, 0x80, + 0x80, 0x80, 0x80, 0x02, 0x06, 0x00, 0x11, + }; + + u8 tbl_pal_tw2815_common[] = { + 0x00, 0x88, 0x20, 0xd0, 0x05, 0x20, 0x28, 0x80, + 0x80, 0x80, 0x80, 0x82, 0x06, 0x00, 0x11, + }; + + u8 tbl_tw2815_sfr[] = { + 0x00, 0x00, 0x00, 0xc0, 0x45, 0xa0, 0xd0, 0x2f, /* 0x00 */ + 0x64, 0x80, 0x80, 0x82, 0x82, 0x00, 0x00, 0x00, + 0x00, 0x0f, 0x05, 0x00, 0x00, 0x80, 0x06, 0x00, /* 0x10 */ + 0x00, 0x00, 0x00, 0xff, 0x8f, 0x00, 0x00, 0x00, + 0x88, 0x88, 0xc0, 0x00, 0x20, 0x64, 0xa8, 0xec, /* 0x20 */ + 0x31, 0x75, 0xb9, 0xfd, 0x00, 0x00, 0x88, 0x88, + 0x88, 0x11, 0x00, 0x88, 0x88, 0x00, /* 0x30 */ + }; + u8 *tbl_tw2815_common; + int i; + int ch; + + tbl_ntsc_tw2815_common[0x06] = 0; + + /* Horizontal Delay Control */ + tbl_ntsc_tw2815_common[0x02] = DEFAULT_HDELAY_NTSC & 0xff; + tbl_ntsc_tw2815_common[0x06] |= 0x03 & (DEFAULT_HDELAY_NTSC >> 8); + + /* Horizontal Active Control */ + tbl_ntsc_tw2815_common[0x03] = DEFAULT_HACTIVE_NTSC & 0xff; + tbl_ntsc_tw2815_common[0x06] |= + ((0x03 & (DEFAULT_HACTIVE_NTSC >> 8)) << 2); + + /* Vertical Delay Control */ + tbl_ntsc_tw2815_common[0x04] = DEFAULT_VDELAY_NTSC & 0xff; + tbl_ntsc_tw2815_common[0x06] |= + ((0x01 & (DEFAULT_VDELAY_NTSC >> 8)) << 4); + + /* Vertical Active Control */ + tbl_ntsc_tw2815_common[0x05] = DEFAULT_VACTIVE_NTSC & 0xff; + tbl_ntsc_tw2815_common[0x06] |= + ((0x01 & (DEFAULT_VACTIVE_NTSC >> 8)) << 5); + + tbl_pal_tw2815_common[0x06] = 0; + + /* Horizontal Delay Control */ + tbl_pal_tw2815_common[0x02] = DEFAULT_HDELAY_PAL & 0xff; + tbl_pal_tw2815_common[0x06] |= 0x03 & (DEFAULT_HDELAY_PAL >> 8); + + /* Horizontal Active Control */ + tbl_pal_tw2815_common[0x03] = DEFAULT_HACTIVE_PAL & 0xff; + tbl_pal_tw2815_common[0x06] |= + ((0x03 & (DEFAULT_HACTIVE_PAL >> 8)) << 2); + + /* Vertical Delay Control */ + tbl_pal_tw2815_common[0x04] = DEFAULT_VDELAY_PAL & 0xff; + tbl_pal_tw2815_common[0x06] |= + ((0x01 & (DEFAULT_VDELAY_PAL >> 8)) << 4); + + /* Vertical Active Control */ + tbl_pal_tw2815_common[0x05] = DEFAULT_VACTIVE_PAL & 0xff; + tbl_pal_tw2815_common[0x06] |= + ((0x01 & (DEFAULT_VACTIVE_PAL >> 8)) << 5); + + tbl_tw2815_common = + (solo_dev->video_type == SOLO_VO_FMT_TYPE_NTSC) ? + tbl_ntsc_tw2815_common : tbl_pal_tw2815_common; + + /* Dual ITU-R BT.656 format */ + tbl_tw2815_common[0x0d] |= 0x04; + + /* Audio configuration */ + tbl_tw2815_sfr[0x62 - 0x40] &= ~(3 << 6); + + if (solo_dev->nr_chans == 4) { + tbl_tw2815_sfr[0x63 - 0x40] |= 1; + tbl_tw2815_sfr[0x62 - 0x40] |= 3 << 6; + } else if (solo_dev->nr_chans == 8) { + tbl_tw2815_sfr[0x63 - 0x40] |= 2; + if (dev_addr == TW_CHIP_OFFSET_ADDR(0)) + tbl_tw2815_sfr[0x62 - 0x40] |= 1 << 6; + else if (dev_addr == TW_CHIP_OFFSET_ADDR(1)) + tbl_tw2815_sfr[0x62 - 0x40] |= 2 << 6; + } else if (solo_dev->nr_chans == 16) { + tbl_tw2815_sfr[0x63 - 0x40] |= 3; + if (dev_addr == TW_CHIP_OFFSET_ADDR(0)) + tbl_tw2815_sfr[0x62 - 0x40] |= 1 << 6; + else if (dev_addr == TW_CHIP_OFFSET_ADDR(1)) + tbl_tw2815_sfr[0x62 - 0x40] |= 0 << 6; + else if (dev_addr == TW_CHIP_OFFSET_ADDR(2)) + tbl_tw2815_sfr[0x62 - 0x40] |= 0 << 6; + else if (dev_addr == TW_CHIP_OFFSET_ADDR(3)) + tbl_tw2815_sfr[0x62 - 0x40] |= 2 << 6; + } + + /* Output mode of R_ADATM pin (0 mixing, 1 record) */ + /* tbl_tw2815_sfr[0x63 - 0x40] |= 0 << 2; */ + + /* 8KHz, used to be 16KHz, but changed for remote client compat */ + tbl_tw2815_sfr[0x62 - 0x40] |= 0 << 2; + tbl_tw2815_sfr[0x6c - 0x40] |= 0 << 2; + + /* Playback of right channel */ + tbl_tw2815_sfr[0x6c - 0x40] |= 1 << 5; + + /* Reserved value (XXX ??) */ + tbl_tw2815_sfr[0x5c - 0x40] |= 1 << 5; + + /* Analog output gain and mix ratio playback on full */ + tbl_tw2815_sfr[0x70 - 0x40] |= 0xff; + /* Select playback audio and mute all except */ + tbl_tw2815_sfr[0x71 - 0x40] |= 0x10; + tbl_tw2815_sfr[0x6d - 0x40] |= 0x0f; + + /* End of audio configuration */ + + for (ch = 0; ch < 4; ch++) { + tbl_tw2815_common[0x0d] &= ~3; + switch (ch) { + case 0: + tbl_tw2815_common[0x0d] |= 0x21; + break; + case 1: + tbl_tw2815_common[0x0d] |= 0x20; + break; + case 2: + tbl_tw2815_common[0x0d] |= 0x23; + break; + case 3: + tbl_tw2815_common[0x0d] |= 0x22; + break; + } + + for (i = 0; i < 0x0f; i++) { + if (i == 0x00) + continue; /* read-only */ + solo_i2c_writebyte(solo_dev, SOLO_I2C_TW, + dev_addr, (ch * 0x10) + i, + tbl_tw2815_common[i]); + } + } + + for (i = 0x40; i < 0x76; i++) { + /* Skip read-only and nop registers */ + if (i == 0x40 || i == 0x59 || i == 0x5a || + i == 0x5d || i == 0x5e || i == 0x5f) + continue; + + solo_i2c_writebyte(solo_dev, SOLO_I2C_TW, dev_addr, i, + tbl_tw2815_sfr[i - 0x40]); + } + + return 0; +} + +#define FIRST_ACTIVE_LINE 0x0008 +#define LAST_ACTIVE_LINE 0x0102 + +static void saa712x_write_regs(struct solo_dev *dev, const uint8_t *vals, + int start, int n) +{ + for (; start < n; start++, vals++) { + /* Skip read-only registers */ + switch (start) { + /* case 0x00 ... 0x25: */ + case 0x2e ... 0x37: + case 0x60: + case 0x7d: + continue; + } + solo_i2c_writebyte(dev, SOLO_I2C_SAA, 0x46, start, *vals); + } +} + +#define SAA712x_reg7c (0x80 | ((LAST_ACTIVE_LINE & 0x100) >> 2) \ + | ((FIRST_ACTIVE_LINE & 0x100) >> 4)) + +static void saa712x_setup(struct solo_dev *dev) +{ + const int reg_start = 0x26; + const uint8_t saa7128_regs_ntsc[] = { + /* :0x26 */ + 0x0d, 0x00, + /* :0x28 */ + 0x59, 0x1d, 0x75, 0x3f, 0x06, 0x3f, + /* :0x2e XXX: read-only */ + 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* :0x38 */ + 0x1a, 0x1a, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, + /* :0x40 */ + 0x00, 0x00, 0x00, 0x68, 0x10, 0x97, 0x4c, 0x18, + 0x9b, 0x93, 0x9f, 0xff, 0x7c, 0x34, 0x3f, 0x3f, + /* :0x50 */ + 0x3f, 0x83, 0x83, 0x80, 0x0d, 0x0f, 0xc3, 0x06, + 0x02, 0x80, 0x71, 0x77, 0xa7, 0x67, 0x66, 0x2e, + /* :0x60 */ + 0x7b, 0x11, 0x4f, 0x1f, 0x7c, 0xf0, 0x21, 0x77, + 0x41, 0x88, 0x41, 0x52, 0xed, 0x10, 0x10, 0x00, + /* :0x70 */ + 0x41, 0xc3, 0x00, 0x3e, 0xb8, 0x02, 0x00, 0x00, + 0x00, 0x00, FIRST_ACTIVE_LINE, LAST_ACTIVE_LINE & 0xff, + SAA712x_reg7c, 0x00, 0xff, 0xff, + }, saa7128_regs_pal[] = { + /* :0x26 */ + 0x0d, 0x00, + /* :0x28 */ + 0xe1, 0x1d, 0x75, 0x3f, 0x06, 0x3f, + /* :0x2e XXX: read-only */ + 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* :0x38 */ + 0x1a, 0x1a, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, + /* :0x40 */ + 0x00, 0x00, 0x00, 0x68, 0x10, 0x97, 0x4c, 0x18, + 0x9b, 0x93, 0x9f, 0xff, 0x7c, 0x34, 0x3f, 0x3f, + /* :0x50 */ + 0x3f, 0x83, 0x83, 0x80, 0x0d, 0x0f, 0xc3, 0x06, + 0x02, 0x80, 0x0f, 0x77, 0xa7, 0x67, 0x66, 0x2e, + /* :0x60 */ + 0x7b, 0x02, 0x35, 0xcb, 0x8a, 0x09, 0x2a, 0x77, + 0x41, 0x88, 0x41, 0x52, 0xf1, 0x10, 0x20, 0x00, + /* :0x70 */ + 0x41, 0xc3, 0x00, 0x3e, 0xb8, 0x02, 0x00, 0x00, + 0x00, 0x00, 0x12, 0x30, + SAA712x_reg7c | 0x40, 0x00, 0xff, 0xff, + }; + + if (dev->video_type == SOLO_VO_FMT_TYPE_PAL) + saa712x_write_regs(dev, saa7128_regs_pal, reg_start, + sizeof(saa7128_regs_pal)); + else + saa712x_write_regs(dev, saa7128_regs_ntsc, reg_start, + sizeof(saa7128_regs_ntsc)); +} + +int solo_tw28_init(struct solo_dev *solo_dev) +{ + int i; + u8 value; + + solo_dev->tw28_cnt = 0; + + /* Detect techwell chip type(s) */ + for (i = 0; i < solo_dev->nr_chans / 4; i++) { + value = solo_i2c_readbyte(solo_dev, SOLO_I2C_TW, + TW_CHIP_OFFSET_ADDR(i), 0xFF); + + switch (value >> 3) { + case 0x18: + solo_dev->tw2865 |= 1 << i; + solo_dev->tw28_cnt++; + break; + case 0x0c: + solo_dev->tw2864 |= 1 << i; + solo_dev->tw28_cnt++; + break; + default: + value = solo_i2c_readbyte(solo_dev, SOLO_I2C_TW, + TW_CHIP_OFFSET_ADDR(i), + 0x59); + if ((value >> 3) == 0x04) { + solo_dev->tw2815 |= 1 << i; + solo_dev->tw28_cnt++; + } + } + } + + if (solo_dev->tw28_cnt != (solo_dev->nr_chans >> 2)) { + dev_err(&solo_dev->pdev->dev, + "Could not initialize any techwell chips\n"); + return -EINVAL; + } + + saa712x_setup(solo_dev); + + for (i = 0; i < solo_dev->tw28_cnt; i++) { + if ((solo_dev->tw2865 & (1 << i))) + tw2865_setup(solo_dev, TW_CHIP_OFFSET_ADDR(i)); + else if ((solo_dev->tw2864 & (1 << i))) + tw2864_setup(solo_dev, TW_CHIP_OFFSET_ADDR(i)); + else + tw2815_setup(solo_dev, TW_CHIP_OFFSET_ADDR(i)); + } + + return 0; +} + +/* + * We accessed the video status signal in the Techwell chip through + * iic/i2c because the video status reported by register REG_VI_STATUS1 + * (address 0x012C) of the SOLO6010 chip doesn't give the correct video + * status signal values. + */ +int tw28_get_video_status(struct solo_dev *solo_dev, u8 ch) +{ + u8 val, chip_num; + + /* Get the right chip and on-chip channel */ + chip_num = ch / 4; + ch %= 4; + + val = tw_readbyte(solo_dev, chip_num, TW286x_AV_STAT_ADDR, + TW_AV_STAT_ADDR) & 0x0f; + + return val & (1 << ch) ? 1 : 0; +} + +#if 0 +/* Status of audio from up to 4 techwell chips are combined into 1 variable. + * See techwell datasheet for details. */ +u16 tw28_get_audio_status(struct solo_dev *solo_dev) +{ + u8 val; + u16 status = 0; + int i; + + for (i = 0; i < solo_dev->tw28_cnt; i++) { + val = (tw_readbyte(solo_dev, i, TW286x_AV_STAT_ADDR, + TW_AV_STAT_ADDR) & 0xf0) >> 4; + status |= val << (i * 4); + } + + return status; +} +#endif + +bool tw28_has_sharpness(struct solo_dev *solo_dev, u8 ch) +{ + return is_tw286x(solo_dev, ch / 4); +} + +int tw28_set_ctrl_val(struct solo_dev *solo_dev, u32 ctrl, u8 ch, + s32 val) +{ + char sval; + u8 chip_num; + + /* Get the right chip and on-chip channel */ + chip_num = ch / 4; + ch %= 4; + + if (val > 255 || val < 0) + return -ERANGE; + + switch (ctrl) { + case V4L2_CID_SHARPNESS: + /* Only 286x has sharpness */ + if (is_tw286x(solo_dev, chip_num)) { + u8 v = solo_i2c_readbyte(solo_dev, SOLO_I2C_TW, + TW_CHIP_OFFSET_ADDR(chip_num), + TW286x_SHARPNESS(chip_num)); + v &= 0xf0; + v |= val; + solo_i2c_writebyte(solo_dev, SOLO_I2C_TW, + TW_CHIP_OFFSET_ADDR(chip_num), + TW286x_SHARPNESS(chip_num), v); + } else { + return -EINVAL; + } + break; + + case V4L2_CID_HUE: + if (is_tw286x(solo_dev, chip_num)) + sval = val - 128; + else + sval = (char)val; + tw_writebyte(solo_dev, chip_num, TW286x_HUE_ADDR(ch), + TW_HUE_ADDR(ch), sval); + + break; + + case V4L2_CID_SATURATION: + /* 286x chips have a U and V component for saturation */ + if (is_tw286x(solo_dev, chip_num)) { + solo_i2c_writebyte(solo_dev, SOLO_I2C_TW, + TW_CHIP_OFFSET_ADDR(chip_num), + TW286x_SATURATIONU_ADDR(ch), val); + } + tw_writebyte(solo_dev, chip_num, TW286x_SATURATIONV_ADDR(ch), + TW_SATURATION_ADDR(ch), val); + + break; + + case V4L2_CID_CONTRAST: + tw_writebyte(solo_dev, chip_num, TW286x_CONTRAST_ADDR(ch), + TW_CONTRAST_ADDR(ch), val); + break; + + case V4L2_CID_BRIGHTNESS: + if (is_tw286x(solo_dev, chip_num)) + sval = val - 128; + else + sval = (char)val; + tw_writebyte(solo_dev, chip_num, TW286x_BRIGHTNESS_ADDR(ch), + TW_BRIGHTNESS_ADDR(ch), sval); + + break; + default: + return -EINVAL; + } + + return 0; +} + +int tw28_get_ctrl_val(struct solo_dev *solo_dev, u32 ctrl, u8 ch, + s32 *val) +{ + u8 rval, chip_num; + + /* Get the right chip and on-chip channel */ + chip_num = ch / 4; + ch %= 4; + + switch (ctrl) { + case V4L2_CID_SHARPNESS: + /* Only 286x has sharpness */ + if (is_tw286x(solo_dev, chip_num)) { + rval = solo_i2c_readbyte(solo_dev, SOLO_I2C_TW, + TW_CHIP_OFFSET_ADDR(chip_num), + TW286x_SHARPNESS(chip_num)); + *val = rval & 0x0f; + } else + *val = 0; + break; + case V4L2_CID_HUE: + rval = tw_readbyte(solo_dev, chip_num, TW286x_HUE_ADDR(ch), + TW_HUE_ADDR(ch)); + if (is_tw286x(solo_dev, chip_num)) + *val = (s32)((char)rval) + 128; + else + *val = rval; + break; + case V4L2_CID_SATURATION: + *val = tw_readbyte(solo_dev, chip_num, + TW286x_SATURATIONU_ADDR(ch), + TW_SATURATION_ADDR(ch)); + break; + case V4L2_CID_CONTRAST: + *val = tw_readbyte(solo_dev, chip_num, + TW286x_CONTRAST_ADDR(ch), + TW_CONTRAST_ADDR(ch)); + break; + case V4L2_CID_BRIGHTNESS: + rval = tw_readbyte(solo_dev, chip_num, + TW286x_BRIGHTNESS_ADDR(ch), + TW_BRIGHTNESS_ADDR(ch)); + if (is_tw286x(solo_dev, chip_num)) + *val = (s32)((char)rval) + 128; + else + *val = rval; + break; + default: + return -EINVAL; + } + + return 0; +} + +#if 0 +/* + * For audio output volume, the output channel is only 1. In this case we + * don't need to offset TW_CHIP_OFFSET_ADDR. The TW_CHIP_OFFSET_ADDR used + * is the base address of the techwell chip. + */ +void tw2815_Set_AudioOutVol(struct solo_dev *solo_dev, unsigned int u_val) +{ + unsigned int val; + unsigned int chip_num; + + chip_num = (solo_dev->nr_chans - 1) / 4; + + val = tw_readbyte(solo_dev, chip_num, TW286x_AUDIO_OUTPUT_VOL_ADDR, + TW_AUDIO_OUTPUT_VOL_ADDR); + + u_val = (val & 0x0f) | (u_val << 4); + + tw_writebyte(solo_dev, chip_num, TW286x_AUDIO_OUTPUT_VOL_ADDR, + TW_AUDIO_OUTPUT_VOL_ADDR, u_val); +} +#endif + +u8 tw28_get_audio_gain(struct solo_dev *solo_dev, u8 ch) +{ + u8 val; + u8 chip_num; + + /* Get the right chip and on-chip channel */ + chip_num = ch / 4; + ch %= 4; + + val = tw_readbyte(solo_dev, chip_num, + TW286x_AUDIO_INPUT_GAIN_ADDR(ch), + TW_AUDIO_INPUT_GAIN_ADDR(ch)); + + return (ch % 2) ? (val >> 4) : (val & 0x0f); +} + +void tw28_set_audio_gain(struct solo_dev *solo_dev, u8 ch, u8 val) +{ + u8 old_val; + u8 chip_num; + + /* Get the right chip and on-chip channel */ + chip_num = ch / 4; + ch %= 4; + + old_val = tw_readbyte(solo_dev, chip_num, + TW286x_AUDIO_INPUT_GAIN_ADDR(ch), + TW_AUDIO_INPUT_GAIN_ADDR(ch)); + + val = (old_val & ((ch % 2) ? 0x0f : 0xf0)) | + ((ch % 2) ? (val << 4) : val); + + tw_writebyte(solo_dev, chip_num, TW286x_AUDIO_INPUT_GAIN_ADDR(ch), + TW_AUDIO_INPUT_GAIN_ADDR(ch), val); +} diff --git a/drivers/staging/media/solo6x10/solo6x10-tw28.h b/drivers/staging/media/solo6x10/solo6x10-tw28.h new file mode 100644 index 00000000000..1a02c87d4cf --- /dev/null +++ b/drivers/staging/media/solo6x10/solo6x10-tw28.h @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2010-2013 Bluecherry, LLC <http://www.bluecherrydvr.com> + * + * Original author: + * Ben Collins <bcollins@ubuntu.com> + * + * Additional work by: + * John Brooks <john.brooks@bluecherry.net> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef __SOLO6X10_TW28_H +#define __SOLO6X10_TW28_H + +#include "solo6x10.h" + +#define TW_NUM_CHIP 4 +#define TW_BASE_ADDR 0x28 +#define TW_CHIP_OFFSET_ADDR(n) (TW_BASE_ADDR + (n)) + +/* tw2815 */ +#define TW_AV_STAT_ADDR 0x5a +#define TW_HUE_ADDR(n) (0x07 | ((n) << 4)) +#define TW_SATURATION_ADDR(n) (0x08 | ((n) << 4)) +#define TW_CONTRAST_ADDR(n) (0x09 | ((n) << 4)) +#define TW_BRIGHTNESS_ADDR(n) (0x0a | ((n) << 4)) +#define TW_AUDIO_OUTPUT_VOL_ADDR 0x70 +#define TW_AUDIO_INPUT_GAIN_ADDR(n) (0x60 + ((n > 1) ? 1 : 0)) + +/* tw286x */ +#define TW286x_AV_STAT_ADDR 0xfd +#define TW286x_HUE_ADDR(n) (0x06 | ((n) << 4)) +#define TW286x_SATURATIONU_ADDR(n) (0x04 | ((n) << 4)) +#define TW286x_SATURATIONV_ADDR(n) (0x05 | ((n) << 4)) +#define TW286x_CONTRAST_ADDR(n) (0x02 | ((n) << 4)) +#define TW286x_BRIGHTNESS_ADDR(n) (0x01 | ((n) << 4)) +#define TW286x_SHARPNESS(n) (0x03 | ((n) << 4)) +#define TW286x_AUDIO_OUTPUT_VOL_ADDR 0xdf +#define TW286x_AUDIO_INPUT_GAIN_ADDR(n) (0xD0 + ((n > 1) ? 1 : 0)) + +int solo_tw28_init(struct solo_dev *solo_dev); + +int tw28_set_ctrl_val(struct solo_dev *solo_dev, u32 ctrl, u8 ch, s32 val); +int tw28_get_ctrl_val(struct solo_dev *solo_dev, u32 ctrl, u8 ch, s32 *val); +bool tw28_has_sharpness(struct solo_dev *solo_dev, u8 ch); + +u8 tw28_get_audio_gain(struct solo_dev *solo_dev, u8 ch); +void tw28_set_audio_gain(struct solo_dev *solo_dev, u8 ch, u8 val); +int tw28_get_video_status(struct solo_dev *solo_dev, u8 ch); + +#if 0 +unsigned int tw2815_get_audio_status(struct SOLO *solo); +void tw2815_Set_AudioOutVol(struct SOLO *solo, unsigned int u_val); +#endif + +#endif /* __SOLO6X10_TW28_H */ diff --git a/drivers/staging/media/solo6x10/solo6x10-v4l2-enc.c b/drivers/staging/media/solo6x10/solo6x10-v4l2-enc.c new file mode 100644 index 00000000000..b8ff113c20f --- /dev/null +++ b/drivers/staging/media/solo6x10/solo6x10-v4l2-enc.c @@ -0,0 +1,1414 @@ +/* + * Copyright (C) 2010-2013 Bluecherry, LLC <http://www.bluecherrydvr.com> + * + * Original author: + * Ben Collins <bcollins@ubuntu.com> + * + * Additional work by: + * John Brooks <john.brooks@bluecherry.net> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/kthread.h> +#include <linux/freezer.h> + +#include <media/v4l2-ioctl.h> +#include <media/v4l2-common.h> +#include <media/v4l2-event.h> +#include <media/videobuf2-dma-sg.h> + +#include "solo6x10.h" +#include "solo6x10-tw28.h" +#include "solo6x10-jpeg.h" + +#define MIN_VID_BUFFERS 2 +#define FRAME_BUF_SIZE (196 * 1024) +#define MP4_QS 16 +#define DMA_ALIGN 4096 + +/* 6010 M4V */ +static unsigned char vop_6010_ntsc_d1[] = { + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x20, + 0x02, 0x48, 0x1d, 0xc0, 0x00, 0x40, 0x00, 0x40, + 0x00, 0x40, 0x00, 0x80, 0x00, 0x97, 0x53, 0x04, + 0x1f, 0x4c, 0x58, 0x10, 0xf0, 0x71, 0x18, 0x3f, +}; + +static unsigned char vop_6010_ntsc_cif[] = { + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x20, + 0x02, 0x48, 0x1d, 0xc0, 0x00, 0x40, 0x00, 0x40, + 0x00, 0x40, 0x00, 0x80, 0x00, 0x97, 0x53, 0x04, + 0x1f, 0x4c, 0x2c, 0x10, 0x78, 0x51, 0x18, 0x3f, +}; + +static unsigned char vop_6010_pal_d1[] = { + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x20, + 0x02, 0x48, 0x15, 0xc0, 0x00, 0x40, 0x00, 0x40, + 0x00, 0x40, 0x00, 0x80, 0x00, 0x97, 0x53, 0x04, + 0x1f, 0x4c, 0x58, 0x11, 0x20, 0x71, 0x18, 0x3f, +}; + +static unsigned char vop_6010_pal_cif[] = { + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x20, + 0x02, 0x48, 0x15, 0xc0, 0x00, 0x40, 0x00, 0x40, + 0x00, 0x40, 0x00, 0x80, 0x00, 0x97, 0x53, 0x04, + 0x1f, 0x4c, 0x2c, 0x10, 0x90, 0x51, 0x18, 0x3f, +}; + +/* 6110 h.264 */ +static unsigned char vop_6110_ntsc_d1[] = { + 0x00, 0x00, 0x00, 0x01, 0x67, 0x42, 0x00, 0x1e, + 0x9a, 0x74, 0x05, 0x81, 0xec, 0x80, 0x00, 0x00, + 0x00, 0x01, 0x68, 0xce, 0x32, 0x28, 0x00, 0x00, +}; + +static unsigned char vop_6110_ntsc_cif[] = { + 0x00, 0x00, 0x00, 0x01, 0x67, 0x42, 0x00, 0x1e, + 0x9a, 0x74, 0x0b, 0x0f, 0xc8, 0x00, 0x00, 0x00, + 0x01, 0x68, 0xce, 0x32, 0x28, 0x00, 0x00, 0x00, +}; + +static unsigned char vop_6110_pal_d1[] = { + 0x00, 0x00, 0x00, 0x01, 0x67, 0x42, 0x00, 0x1e, + 0x9a, 0x74, 0x05, 0x80, 0x93, 0x20, 0x00, 0x00, + 0x00, 0x01, 0x68, 0xce, 0x32, 0x28, 0x00, 0x00, +}; + +static unsigned char vop_6110_pal_cif[] = { + 0x00, 0x00, 0x00, 0x01, 0x67, 0x42, 0x00, 0x1e, + 0x9a, 0x74, 0x0b, 0x04, 0xb2, 0x00, 0x00, 0x00, + 0x01, 0x68, 0xce, 0x32, 0x28, 0x00, 0x00, 0x00, +}; + +typedef __le32 vop_header[16]; + +struct solo_enc_buf { + enum solo_enc_types type; + const vop_header *vh; + int motion; +}; + +static int solo_is_motion_on(struct solo_enc_dev *solo_enc) +{ + struct solo_dev *solo_dev = solo_enc->solo_dev; + + return (solo_dev->motion_mask >> solo_enc->ch) & 1; +} + +static int solo_motion_detected(struct solo_enc_dev *solo_enc) +{ + struct solo_dev *solo_dev = solo_enc->solo_dev; + unsigned long flags; + u32 ch_mask = 1 << solo_enc->ch; + int ret = 0; + + spin_lock_irqsave(&solo_enc->motion_lock, flags); + if (solo_reg_read(solo_dev, SOLO_VI_MOT_STATUS) & ch_mask) { + solo_reg_write(solo_dev, SOLO_VI_MOT_CLEAR, ch_mask); + ret = 1; + } + spin_unlock_irqrestore(&solo_enc->motion_lock, flags); + + return ret; +} + +static void solo_motion_toggle(struct solo_enc_dev *solo_enc, int on) +{ + struct solo_dev *solo_dev = solo_enc->solo_dev; + u32 mask = 1 << solo_enc->ch; + unsigned long flags; + + spin_lock_irqsave(&solo_enc->motion_lock, flags); + + if (on) + solo_dev->motion_mask |= mask; + else + solo_dev->motion_mask &= ~mask; + + solo_reg_write(solo_dev, SOLO_VI_MOT_CLEAR, mask); + + solo_reg_write(solo_dev, SOLO_VI_MOT_ADR, + SOLO_VI_MOTION_EN(solo_dev->motion_mask) | + (SOLO_MOTION_EXT_ADDR(solo_dev) >> 16)); + + spin_unlock_irqrestore(&solo_enc->motion_lock, flags); +} + +void solo_update_mode(struct solo_enc_dev *solo_enc) +{ + struct solo_dev *solo_dev = solo_enc->solo_dev; + int vop_len; + unsigned char *vop; + + solo_enc->interlaced = (solo_enc->mode & 0x08) ? 1 : 0; + solo_enc->bw_weight = max(solo_dev->fps / solo_enc->interval, 1); + + if (solo_enc->mode == SOLO_ENC_MODE_CIF) { + solo_enc->width = solo_dev->video_hsize >> 1; + solo_enc->height = solo_dev->video_vsize; + if (solo_dev->type == SOLO_DEV_6110) { + if (solo_dev->video_type == SOLO_VO_FMT_TYPE_NTSC) { + vop = vop_6110_ntsc_cif; + vop_len = sizeof(vop_6110_ntsc_cif); + } else { + vop = vop_6110_pal_cif; + vop_len = sizeof(vop_6110_pal_cif); + } + } else { + if (solo_dev->video_type == SOLO_VO_FMT_TYPE_NTSC) { + vop = vop_6010_ntsc_cif; + vop_len = sizeof(vop_6010_ntsc_cif); + } else { + vop = vop_6010_pal_cif; + vop_len = sizeof(vop_6010_pal_cif); + } + } + } else { + solo_enc->width = solo_dev->video_hsize; + solo_enc->height = solo_dev->video_vsize << 1; + solo_enc->bw_weight <<= 2; + if (solo_dev->type == SOLO_DEV_6110) { + if (solo_dev->video_type == SOLO_VO_FMT_TYPE_NTSC) { + vop = vop_6110_ntsc_d1; + vop_len = sizeof(vop_6110_ntsc_d1); + } else { + vop = vop_6110_pal_d1; + vop_len = sizeof(vop_6110_pal_d1); + } + } else { + if (solo_dev->video_type == SOLO_VO_FMT_TYPE_NTSC) { + vop = vop_6010_ntsc_d1; + vop_len = sizeof(vop_6010_ntsc_d1); + } else { + vop = vop_6010_pal_d1; + vop_len = sizeof(vop_6010_pal_d1); + } + } + } + + memcpy(solo_enc->vop, vop, vop_len); + + /* Some fixups for 6010/M4V */ + if (solo_dev->type == SOLO_DEV_6010) { + u16 fps = solo_dev->fps * 1000; + u16 interval = solo_enc->interval * 1000; + + vop = solo_enc->vop; + + /* Frame rate and interval */ + vop[22] = fps >> 4; + vop[23] = ((fps << 4) & 0xf0) | 0x0c + | ((interval >> 13) & 0x3); + vop[24] = (interval >> 5) & 0xff; + vop[25] = ((interval << 3) & 0xf8) | 0x04; + } + + solo_enc->vop_len = vop_len; + + /* Now handle the jpeg header */ + vop = solo_enc->jpeg_header; + vop[SOF0_START + 5] = 0xff & (solo_enc->height >> 8); + vop[SOF0_START + 6] = 0xff & solo_enc->height; + vop[SOF0_START + 7] = 0xff & (solo_enc->width >> 8); + vop[SOF0_START + 8] = 0xff & solo_enc->width; + + memcpy(vop + DQT_START, + jpeg_dqt[solo_g_jpeg_qp(solo_dev, solo_enc->ch)], DQT_LEN); +} + +static int solo_enc_on(struct solo_enc_dev *solo_enc) +{ + u8 ch = solo_enc->ch; + struct solo_dev *solo_dev = solo_enc->solo_dev; + u8 interval; + + solo_update_mode(solo_enc); + + /* Make sure to do a bandwidth check */ + if (solo_enc->bw_weight > solo_dev->enc_bw_remain) + return -EBUSY; + solo_enc->sequence = 0; + solo_dev->enc_bw_remain -= solo_enc->bw_weight; + + if (solo_enc->type == SOLO_ENC_TYPE_EXT) + solo_reg_write(solo_dev, SOLO_CAP_CH_COMP_ENA_E(ch), 1); + + /* Disable all encoding for this channel */ + solo_reg_write(solo_dev, SOLO_CAP_CH_SCALE(ch), 0); + + /* Common for both std and ext encoding */ + solo_reg_write(solo_dev, SOLO_VE_CH_INTL(ch), + solo_enc->interlaced ? 1 : 0); + + if (solo_enc->interlaced) + interval = solo_enc->interval - 1; + else + interval = solo_enc->interval; + + /* Standard encoding only */ + solo_reg_write(solo_dev, SOLO_VE_CH_GOP(ch), solo_enc->gop); + solo_reg_write(solo_dev, SOLO_VE_CH_QP(ch), solo_enc->qp); + solo_reg_write(solo_dev, SOLO_CAP_CH_INTV(ch), interval); + + /* Extended encoding only */ + solo_reg_write(solo_dev, SOLO_VE_CH_GOP_E(ch), solo_enc->gop); + solo_reg_write(solo_dev, SOLO_VE_CH_QP_E(ch), solo_enc->qp); + solo_reg_write(solo_dev, SOLO_CAP_CH_INTV_E(ch), interval); + + /* Enables the standard encoder */ + solo_reg_write(solo_dev, SOLO_CAP_CH_SCALE(ch), solo_enc->mode); + + return 0; +} + +static void solo_enc_off(struct solo_enc_dev *solo_enc) +{ + struct solo_dev *solo_dev = solo_enc->solo_dev; + + solo_dev->enc_bw_remain += solo_enc->bw_weight; + + solo_reg_write(solo_dev, SOLO_CAP_CH_SCALE(solo_enc->ch), 0); + solo_reg_write(solo_dev, SOLO_CAP_CH_COMP_ENA_E(solo_enc->ch), 0); +} + +static int enc_get_mpeg_dma(struct solo_dev *solo_dev, dma_addr_t dma, + unsigned int off, unsigned int size) +{ + int ret; + + if (off > SOLO_MP4E_EXT_SIZE(solo_dev)) + return -EINVAL; + + /* Single shot */ + if (off + size <= SOLO_MP4E_EXT_SIZE(solo_dev)) { + return solo_p2m_dma_t(solo_dev, 0, dma, + SOLO_MP4E_EXT_ADDR(solo_dev) + off, size, + 0, 0); + } + + /* Buffer wrap */ + ret = solo_p2m_dma_t(solo_dev, 0, dma, + SOLO_MP4E_EXT_ADDR(solo_dev) + off, + SOLO_MP4E_EXT_SIZE(solo_dev) - off, 0, 0); + + if (!ret) { + ret = solo_p2m_dma_t(solo_dev, 0, + dma + SOLO_MP4E_EXT_SIZE(solo_dev) - off, + SOLO_MP4E_EXT_ADDR(solo_dev), + size + off - SOLO_MP4E_EXT_SIZE(solo_dev), 0, 0); + } + + return ret; +} + +/* Build a descriptor queue out of an SG list and send it to the P2M for + * processing. */ +static int solo_send_desc(struct solo_enc_dev *solo_enc, int skip, + struct sg_table *vbuf, int off, int size, + unsigned int base, unsigned int base_size) +{ + struct solo_dev *solo_dev = solo_enc->solo_dev; + struct scatterlist *sg; + int i; + int ret; + + if (WARN_ON_ONCE(size > FRAME_BUF_SIZE)) + return -EINVAL; + + solo_enc->desc_count = 1; + + for_each_sg(vbuf->sgl, sg, vbuf->nents, i) { + struct solo_p2m_desc *desc; + dma_addr_t dma; + int len; + int left = base_size - off; + + desc = &solo_enc->desc_items[solo_enc->desc_count++]; + dma = sg_dma_address(sg); + len = sg_dma_len(sg); + + /* We assume this is smaller than the scatter size */ + BUG_ON(skip >= len); + if (skip) { + len -= skip; + dma += skip; + size -= skip; + skip = 0; + } + + len = min(len, size); + + if (len <= left) { + /* Single descriptor */ + solo_p2m_fill_desc(desc, 0, dma, base + off, + len, 0, 0); + } else { + /* Buffer wrap */ + /* XXX: Do these as separate DMA requests, to avoid + timeout errors triggered by awkwardly sized + descriptors. See + <https://github.com/bluecherrydvr/solo6x10/issues/8> + */ + ret = solo_p2m_dma_t(solo_dev, 0, dma, base + off, + left, 0, 0); + if (ret) + return ret; + + ret = solo_p2m_dma_t(solo_dev, 0, dma + left, base, + len - left, 0, 0); + if (ret) + return ret; + + solo_enc->desc_count--; + } + + size -= len; + if (size <= 0) + break; + + off += len; + if (off >= base_size) + off -= base_size; + + /* Because we may use two descriptors per loop */ + if (solo_enc->desc_count >= (solo_enc->desc_nelts - 1)) { + ret = solo_p2m_dma_desc(solo_dev, solo_enc->desc_items, + solo_enc->desc_dma, + solo_enc->desc_count - 1); + if (ret) + return ret; + solo_enc->desc_count = 1; + } + } + + if (solo_enc->desc_count <= 1) + return 0; + + return solo_p2m_dma_desc(solo_dev, solo_enc->desc_items, + solo_enc->desc_dma, solo_enc->desc_count - 1); +} + +/* Extract values from VOP header - VE_STATUSxx */ +static inline int vop_interlaced(const vop_header *vh) +{ + return (__le32_to_cpu((*vh)[0]) >> 30) & 1; +} + +static inline u8 vop_channel(const vop_header *vh) +{ + return (__le32_to_cpu((*vh)[0]) >> 24) & 0x1F; +} + +static inline u8 vop_type(const vop_header *vh) +{ + return (__le32_to_cpu((*vh)[0]) >> 22) & 3; +} + +static inline u32 vop_mpeg_size(const vop_header *vh) +{ + return __le32_to_cpu((*vh)[0]) & 0xFFFFF; +} + +static inline u8 vop_hsize(const vop_header *vh) +{ + return (__le32_to_cpu((*vh)[1]) >> 8) & 0xFF; +} + +static inline u8 vop_vsize(const vop_header *vh) +{ + return __le32_to_cpu((*vh)[1]) & 0xFF; +} + +static inline u32 vop_mpeg_offset(const vop_header *vh) +{ + return __le32_to_cpu((*vh)[2]); +} + +static inline u32 vop_jpeg_offset(const vop_header *vh) +{ + return __le32_to_cpu((*vh)[3]); +} + +static inline u32 vop_jpeg_size(const vop_header *vh) +{ + return __le32_to_cpu((*vh)[4]) & 0xFFFFF; +} + +static inline u32 vop_sec(const vop_header *vh) +{ + return __le32_to_cpu((*vh)[5]); +} + +static inline u32 vop_usec(const vop_header *vh) +{ + return __le32_to_cpu((*vh)[6]); +} + +static int solo_fill_jpeg(struct solo_enc_dev *solo_enc, + struct vb2_buffer *vb, const vop_header *vh) +{ + struct solo_dev *solo_dev = solo_enc->solo_dev; + struct sg_table *vbuf = vb2_dma_sg_plane_desc(vb, 0); + int frame_size; + int ret; + + vb->v4l2_buf.flags |= V4L2_BUF_FLAG_KEYFRAME; + + if (vb2_plane_size(vb, 0) < vop_jpeg_size(vh) + solo_enc->jpeg_len) + return -EIO; + + frame_size = ALIGN(vop_jpeg_size(vh) + solo_enc->jpeg_len, DMA_ALIGN); + vb2_set_plane_payload(vb, 0, vop_jpeg_size(vh) + solo_enc->jpeg_len); + + /* may discard all previous data in vbuf->sgl */ + dma_map_sg(&solo_dev->pdev->dev, vbuf->sgl, vbuf->nents, + DMA_FROM_DEVICE); + ret = solo_send_desc(solo_enc, solo_enc->jpeg_len, vbuf, + vop_jpeg_offset(vh) - SOLO_JPEG_EXT_ADDR(solo_dev), + frame_size, SOLO_JPEG_EXT_ADDR(solo_dev), + SOLO_JPEG_EXT_SIZE(solo_dev)); + dma_unmap_sg(&solo_dev->pdev->dev, vbuf->sgl, vbuf->nents, + DMA_FROM_DEVICE); + + /* add the header only after dma_unmap_sg() */ + sg_copy_from_buffer(vbuf->sgl, vbuf->nents, + solo_enc->jpeg_header, solo_enc->jpeg_len); + + return ret; +} + +static int solo_fill_mpeg(struct solo_enc_dev *solo_enc, + struct vb2_buffer *vb, const vop_header *vh) +{ + struct solo_dev *solo_dev = solo_enc->solo_dev; + struct sg_table *vbuf = vb2_dma_sg_plane_desc(vb, 0); + int frame_off, frame_size; + int skip = 0; + int ret; + + if (vb2_plane_size(vb, 0) < vop_mpeg_size(vh)) + return -EIO; + + /* If this is a key frame, add extra header */ + vb->v4l2_buf.flags &= ~(V4L2_BUF_FLAG_KEYFRAME | V4L2_BUF_FLAG_PFRAME | + V4L2_BUF_FLAG_BFRAME); + if (!vop_type(vh)) { + skip = solo_enc->vop_len; + vb->v4l2_buf.flags |= V4L2_BUF_FLAG_KEYFRAME; + vb2_set_plane_payload(vb, 0, vop_mpeg_size(vh) + + solo_enc->vop_len); + } else { + vb->v4l2_buf.flags |= V4L2_BUF_FLAG_PFRAME; + vb2_set_plane_payload(vb, 0, vop_mpeg_size(vh)); + } + + /* Now get the actual mpeg payload */ + frame_off = (vop_mpeg_offset(vh) - SOLO_MP4E_EXT_ADDR(solo_dev) + + sizeof(*vh)) % SOLO_MP4E_EXT_SIZE(solo_dev); + frame_size = ALIGN(vop_mpeg_size(vh) + skip, DMA_ALIGN); + + /* may discard all previous data in vbuf->sgl */ + dma_map_sg(&solo_dev->pdev->dev, vbuf->sgl, vbuf->nents, + DMA_FROM_DEVICE); + ret = solo_send_desc(solo_enc, skip, vbuf, frame_off, frame_size, + SOLO_MP4E_EXT_ADDR(solo_dev), + SOLO_MP4E_EXT_SIZE(solo_dev)); + dma_unmap_sg(&solo_dev->pdev->dev, vbuf->sgl, vbuf->nents, + DMA_FROM_DEVICE); + + /* add the header only after dma_unmap_sg() */ + if (!vop_type(vh)) + sg_copy_from_buffer(vbuf->sgl, vbuf->nents, + solo_enc->vop, solo_enc->vop_len); + return ret; +} + +static int solo_enc_fillbuf(struct solo_enc_dev *solo_enc, + struct vb2_buffer *vb, struct solo_enc_buf *enc_buf) +{ + const vop_header *vh = enc_buf->vh; + int ret; + + /* Check for motion flags */ + vb->v4l2_buf.flags &= ~(V4L2_BUF_FLAG_MOTION_ON | + V4L2_BUF_FLAG_MOTION_DETECTED); + if (solo_is_motion_on(solo_enc)) { + vb->v4l2_buf.flags |= V4L2_BUF_FLAG_MOTION_ON; + if (enc_buf->motion) + vb->v4l2_buf.flags |= V4L2_BUF_FLAG_MOTION_DETECTED; + } + + switch (solo_enc->fmt) { + case V4L2_PIX_FMT_MPEG4: + case V4L2_PIX_FMT_H264: + ret = solo_fill_mpeg(solo_enc, vb, vh); + break; + default: /* V4L2_PIX_FMT_MJPEG */ + ret = solo_fill_jpeg(solo_enc, vb, vh); + break; + } + + if (!ret) { + vb->v4l2_buf.sequence = solo_enc->sequence++; + vb->v4l2_buf.timestamp.tv_sec = vop_sec(vh); + vb->v4l2_buf.timestamp.tv_usec = vop_usec(vh); + } + + vb2_buffer_done(vb, ret ? VB2_BUF_STATE_ERROR : VB2_BUF_STATE_DONE); + + return ret; +} + +static void solo_enc_handle_one(struct solo_enc_dev *solo_enc, + struct solo_enc_buf *enc_buf) +{ + struct solo_vb2_buf *vb; + unsigned long flags; + + mutex_lock(&solo_enc->lock); + if (solo_enc->type != enc_buf->type) + goto unlock; + + spin_lock_irqsave(&solo_enc->av_lock, flags); + if (list_empty(&solo_enc->vidq_active)) { + spin_unlock_irqrestore(&solo_enc->av_lock, flags); + goto unlock; + } + vb = list_first_entry(&solo_enc->vidq_active, struct solo_vb2_buf, + list); + list_del(&vb->list); + spin_unlock_irqrestore(&solo_enc->av_lock, flags); + + solo_enc_fillbuf(solo_enc, &vb->vb, enc_buf); +unlock: + mutex_unlock(&solo_enc->lock); +} + +void solo_enc_v4l2_isr(struct solo_dev *solo_dev) +{ + wake_up_interruptible_all(&solo_dev->ring_thread_wait); +} + +static void solo_handle_ring(struct solo_dev *solo_dev) +{ + for (;;) { + struct solo_enc_dev *solo_enc; + struct solo_enc_buf enc_buf; + u32 mpeg_current, off; + u8 ch; + u8 cur_q; + + /* Check if the hardware has any new ones in the queue */ + cur_q = solo_reg_read(solo_dev, SOLO_VE_STATE(11)) & 0xff; + if (cur_q == solo_dev->enc_idx) + break; + + mpeg_current = solo_reg_read(solo_dev, + SOLO_VE_MPEG4_QUE(solo_dev->enc_idx)); + solo_dev->enc_idx = (solo_dev->enc_idx + 1) % MP4_QS; + + ch = (mpeg_current >> 24) & 0x1f; + off = mpeg_current & 0x00ffffff; + + if (ch >= SOLO_MAX_CHANNELS) { + ch -= SOLO_MAX_CHANNELS; + enc_buf.type = SOLO_ENC_TYPE_EXT; + } else + enc_buf.type = SOLO_ENC_TYPE_STD; + + solo_enc = solo_dev->v4l2_enc[ch]; + if (solo_enc == NULL) { + dev_err(&solo_dev->pdev->dev, + "Got spurious packet for channel %d\n", ch); + continue; + } + + /* FAIL... */ + if (enc_get_mpeg_dma(solo_dev, solo_dev->vh_dma, off, + sizeof(vop_header))) + continue; + + enc_buf.vh = solo_dev->vh_buf; + + /* Sanity check */ + if (vop_mpeg_offset(enc_buf.vh) != + SOLO_MP4E_EXT_ADDR(solo_dev) + off) + continue; + + if (solo_motion_detected(solo_enc)) + enc_buf.motion = 1; + else + enc_buf.motion = 0; + + solo_enc_handle_one(solo_enc, &enc_buf); + } +} + +static int solo_ring_thread(void *data) +{ + struct solo_dev *solo_dev = data; + DECLARE_WAITQUEUE(wait, current); + + set_freezable(); + add_wait_queue(&solo_dev->ring_thread_wait, &wait); + + for (;;) { + long timeout = schedule_timeout_interruptible(HZ); + if (timeout == -ERESTARTSYS || kthread_should_stop()) + break; + solo_irq_off(solo_dev, SOLO_IRQ_ENCODER); + solo_handle_ring(solo_dev); + solo_irq_on(solo_dev, SOLO_IRQ_ENCODER); + try_to_freeze(); + } + + remove_wait_queue(&solo_dev->ring_thread_wait, &wait); + + return 0; +} + +static int solo_enc_queue_setup(struct vb2_queue *q, + const struct v4l2_format *fmt, + unsigned int *num_buffers, + unsigned int *num_planes, unsigned int sizes[], + void *alloc_ctxs[]) +{ + sizes[0] = FRAME_BUF_SIZE; + *num_planes = 1; + + if (*num_buffers < MIN_VID_BUFFERS) + *num_buffers = MIN_VID_BUFFERS; + + return 0; +} + +static void solo_enc_buf_queue(struct vb2_buffer *vb) +{ + struct vb2_queue *vq = vb->vb2_queue; + struct solo_enc_dev *solo_enc = vb2_get_drv_priv(vq); + struct solo_vb2_buf *solo_vb = + container_of(vb, struct solo_vb2_buf, vb); + + spin_lock(&solo_enc->av_lock); + list_add_tail(&solo_vb->list, &solo_enc->vidq_active); + spin_unlock(&solo_enc->av_lock); +} + +static int solo_ring_start(struct solo_dev *solo_dev) +{ + solo_dev->ring_thread = kthread_run(solo_ring_thread, solo_dev, + SOLO6X10_NAME "_ring"); + if (IS_ERR(solo_dev->ring_thread)) { + int err = PTR_ERR(solo_dev->ring_thread); + solo_dev->ring_thread = NULL; + return err; + } + + solo_irq_on(solo_dev, SOLO_IRQ_ENCODER); + + return 0; +} + +static void solo_ring_stop(struct solo_dev *solo_dev) +{ + if (solo_dev->ring_thread) { + kthread_stop(solo_dev->ring_thread); + solo_dev->ring_thread = NULL; + } + + solo_irq_off(solo_dev, SOLO_IRQ_ENCODER); +} + +static int solo_enc_start_streaming(struct vb2_queue *q, unsigned int count) +{ + struct solo_enc_dev *solo_enc = vb2_get_drv_priv(q); + int ret; + + ret = solo_enc_on(solo_enc); + if (ret) + return ret; + return solo_ring_start(solo_enc->solo_dev); +} + +static void solo_enc_stop_streaming(struct vb2_queue *q) +{ + struct solo_enc_dev *solo_enc = vb2_get_drv_priv(q); + + solo_enc_off(solo_enc); + INIT_LIST_HEAD(&solo_enc->vidq_active); + solo_ring_stop(solo_enc->solo_dev); +} + +static struct vb2_ops solo_enc_video_qops = { + .queue_setup = solo_enc_queue_setup, + .buf_queue = solo_enc_buf_queue, + .start_streaming = solo_enc_start_streaming, + .stop_streaming = solo_enc_stop_streaming, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, +}; + +static int solo_enc_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + struct solo_enc_dev *solo_enc = video_drvdata(file); + struct solo_dev *solo_dev = solo_enc->solo_dev; + + strcpy(cap->driver, SOLO6X10_NAME); + snprintf(cap->card, sizeof(cap->card), "Softlogic 6x10 Enc %d", + solo_enc->ch); + snprintf(cap->bus_info, sizeof(cap->bus_info), "PCI:%s", + pci_name(solo_dev->pdev)); + cap->device_caps = V4L2_CAP_VIDEO_CAPTURE | + V4L2_CAP_READWRITE | V4L2_CAP_STREAMING; + cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS; + return 0; +} + +static int solo_enc_enum_input(struct file *file, void *priv, + struct v4l2_input *input) +{ + struct solo_enc_dev *solo_enc = video_drvdata(file); + struct solo_dev *solo_dev = solo_enc->solo_dev; + + if (input->index) + return -EINVAL; + + snprintf(input->name, sizeof(input->name), "Encoder %d", + solo_enc->ch + 1); + input->type = V4L2_INPUT_TYPE_CAMERA; + input->std = solo_enc->vfd->tvnorms; + + if (!tw28_get_video_status(solo_dev, solo_enc->ch)) + input->status = V4L2_IN_ST_NO_SIGNAL; + + return 0; +} + +static int solo_enc_set_input(struct file *file, void *priv, + unsigned int index) +{ + if (index) + return -EINVAL; + + return 0; +} + +static int solo_enc_get_input(struct file *file, void *priv, + unsigned int *index) +{ + *index = 0; + + return 0; +} + +static int solo_enc_enum_fmt_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + struct solo_enc_dev *solo_enc = video_drvdata(file); + int dev_type = solo_enc->solo_dev->type; + + switch (f->index) { + case 0: + switch (dev_type) { + case SOLO_DEV_6010: + f->pixelformat = V4L2_PIX_FMT_MPEG4; + strcpy(f->description, "MPEG-4 part 2"); + break; + case SOLO_DEV_6110: + f->pixelformat = V4L2_PIX_FMT_H264; + strcpy(f->description, "H.264"); + break; + } + break; + case 1: + f->pixelformat = V4L2_PIX_FMT_MJPEG; + strcpy(f->description, "MJPEG"); + break; + default: + return -EINVAL; + } + + f->flags = V4L2_FMT_FLAG_COMPRESSED; + + return 0; +} + +static inline int solo_valid_pixfmt(u32 pixfmt, int dev_type) +{ + return (pixfmt == V4L2_PIX_FMT_H264 && dev_type == SOLO_DEV_6110) + || (pixfmt == V4L2_PIX_FMT_MPEG4 && dev_type == SOLO_DEV_6010) + || pixfmt == V4L2_PIX_FMT_MJPEG ? 0 : -EINVAL; +} + +static int solo_enc_try_fmt_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct solo_enc_dev *solo_enc = video_drvdata(file); + struct solo_dev *solo_dev = solo_enc->solo_dev; + struct v4l2_pix_format *pix = &f->fmt.pix; + + if (solo_valid_pixfmt(pix->pixelformat, solo_dev->type)) + return -EINVAL; + + if (pix->width < solo_dev->video_hsize || + pix->height < solo_dev->video_vsize << 1) { + /* Default to CIF 1/2 size */ + pix->width = solo_dev->video_hsize >> 1; + pix->height = solo_dev->video_vsize; + } else { + /* Full frame */ + pix->width = solo_dev->video_hsize; + pix->height = solo_dev->video_vsize << 1; + } + + switch (pix->field) { + case V4L2_FIELD_NONE: + case V4L2_FIELD_INTERLACED: + break; + case V4L2_FIELD_ANY: + default: + pix->field = V4L2_FIELD_INTERLACED; + break; + } + + /* Just set these */ + pix->colorspace = V4L2_COLORSPACE_SMPTE170M; + pix->sizeimage = FRAME_BUF_SIZE; + pix->bytesperline = 0; + pix->priv = 0; + + return 0; +} + +static int solo_enc_set_fmt_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct solo_enc_dev *solo_enc = video_drvdata(file); + struct solo_dev *solo_dev = solo_enc->solo_dev; + struct v4l2_pix_format *pix = &f->fmt.pix; + int ret; + + if (vb2_is_busy(&solo_enc->vidq)) + return -EBUSY; + + ret = solo_enc_try_fmt_cap(file, priv, f); + if (ret) + return ret; + + if (pix->width == solo_dev->video_hsize) + solo_enc->mode = SOLO_ENC_MODE_D1; + else + solo_enc->mode = SOLO_ENC_MODE_CIF; + + /* This does not change the encoder at all */ + solo_enc->fmt = pix->pixelformat; + + /* + * More information is needed about these 'extended' types. As far + * as I can tell these are basically additional video streams with + * different MPEG encoding attributes that can run in parallel with + * the main stream. If so, then this should be implemented as a + * second video node. Abusing priv like this is certainly not the + * right approach. + if (pix->priv) + solo_enc->type = SOLO_ENC_TYPE_EXT; + */ + solo_update_mode(solo_enc); + return 0; +} + +static int solo_enc_get_fmt_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct solo_enc_dev *solo_enc = video_drvdata(file); + struct v4l2_pix_format *pix = &f->fmt.pix; + + pix->width = solo_enc->width; + pix->height = solo_enc->height; + pix->pixelformat = solo_enc->fmt; + pix->field = solo_enc->interlaced ? V4L2_FIELD_INTERLACED : + V4L2_FIELD_NONE; + pix->sizeimage = FRAME_BUF_SIZE; + pix->colorspace = V4L2_COLORSPACE_SMPTE170M; + pix->priv = 0; + + return 0; +} + +static int solo_enc_g_std(struct file *file, void *priv, v4l2_std_id *i) +{ + struct solo_enc_dev *solo_enc = video_drvdata(file); + struct solo_dev *solo_dev = solo_enc->solo_dev; + + if (solo_dev->video_type == SOLO_VO_FMT_TYPE_NTSC) + *i = V4L2_STD_NTSC_M; + else + *i = V4L2_STD_PAL; + return 0; +} + +static int solo_enc_s_std(struct file *file, void *priv, v4l2_std_id std) +{ + struct solo_enc_dev *solo_enc = video_drvdata(file); + + return solo_set_video_type(solo_enc->solo_dev, std & V4L2_STD_625_50); +} + +static int solo_enum_framesizes(struct file *file, void *priv, + struct v4l2_frmsizeenum *fsize) +{ + struct solo_enc_dev *solo_enc = video_drvdata(file); + struct solo_dev *solo_dev = solo_enc->solo_dev; + + if (solo_valid_pixfmt(fsize->pixel_format, solo_dev->type)) + return -EINVAL; + + switch (fsize->index) { + case 0: + fsize->discrete.width = solo_dev->video_hsize >> 1; + fsize->discrete.height = solo_dev->video_vsize; + break; + case 1: + fsize->discrete.width = solo_dev->video_hsize; + fsize->discrete.height = solo_dev->video_vsize << 1; + break; + default: + return -EINVAL; + } + + fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE; + + return 0; +} + +static int solo_enum_frameintervals(struct file *file, void *priv, + struct v4l2_frmivalenum *fintv) +{ + struct solo_enc_dev *solo_enc = video_drvdata(file); + struct solo_dev *solo_dev = solo_enc->solo_dev; + + if (solo_valid_pixfmt(fintv->pixel_format, solo_dev->type)) + return -EINVAL; + if (fintv->index) + return -EINVAL; + if ((fintv->width != solo_dev->video_hsize >> 1 || + fintv->height != solo_dev->video_vsize) && + (fintv->width != solo_dev->video_hsize || + fintv->height != solo_dev->video_vsize << 1)) + return -EINVAL; + + fintv->type = V4L2_FRMIVAL_TYPE_STEPWISE; + + fintv->stepwise.min.numerator = 1; + fintv->stepwise.min.denominator = solo_dev->fps; + + fintv->stepwise.max.numerator = 15; + fintv->stepwise.max.denominator = solo_dev->fps; + + fintv->stepwise.step.numerator = 1; + fintv->stepwise.step.denominator = solo_dev->fps; + + return 0; +} + +static int solo_g_parm(struct file *file, void *priv, + struct v4l2_streamparm *sp) +{ + struct solo_enc_dev *solo_enc = video_drvdata(file); + struct v4l2_captureparm *cp = &sp->parm.capture; + + cp->capability = V4L2_CAP_TIMEPERFRAME; + cp->timeperframe.numerator = solo_enc->interval; + cp->timeperframe.denominator = solo_enc->solo_dev->fps; + cp->capturemode = 0; + /* XXX: Shouldn't we be able to get/set this from videobuf? */ + cp->readbuffers = 2; + + return 0; +} + +static inline int calc_interval(u8 fps, u32 n, u32 d) +{ + if (!n || !d) + return 1; + if (d == fps) + return n; + n *= fps; + return min(15U, n / d + (n % d >= (fps >> 1))); +} + +static int solo_s_parm(struct file *file, void *priv, + struct v4l2_streamparm *sp) +{ + struct solo_enc_dev *solo_enc = video_drvdata(file); + struct v4l2_fract *t = &sp->parm.capture.timeperframe; + u8 fps = solo_enc->solo_dev->fps; + + if (vb2_is_streaming(&solo_enc->vidq)) + return -EBUSY; + + solo_enc->interval = calc_interval(fps, t->numerator, t->denominator); + solo_update_mode(solo_enc); + return solo_g_parm(file, priv, sp); +} + +static long solo_enc_default(struct file *file, void *fh, + bool valid_prio, unsigned int cmd, void *arg) +{ + struct solo_enc_dev *solo_enc = video_drvdata(file); + struct solo_dev *solo_dev = solo_enc->solo_dev; + struct solo_motion_thresholds *thresholds = arg; + + switch (cmd) { + case SOLO_IOC_G_MOTION_THRESHOLDS: + *thresholds = solo_enc->motion_thresholds; + return 0; + + case SOLO_IOC_S_MOTION_THRESHOLDS: + if (!valid_prio) + return -EBUSY; + solo_enc->motion_thresholds = *thresholds; + if (solo_enc->motion_enabled && !solo_enc->motion_global) + return solo_set_motion_block(solo_dev, solo_enc->ch, + &solo_enc->motion_thresholds); + return 0; + default: + return -ENOTTY; + } +} + +static int solo_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct solo_enc_dev *solo_enc = + container_of(ctrl->handler, struct solo_enc_dev, hdl); + struct solo_dev *solo_dev = solo_enc->solo_dev; + int err; + + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + case V4L2_CID_CONTRAST: + case V4L2_CID_SATURATION: + case V4L2_CID_HUE: + case V4L2_CID_SHARPNESS: + return tw28_set_ctrl_val(solo_dev, ctrl->id, solo_enc->ch, + ctrl->val); + case V4L2_CID_MPEG_VIDEO_GOP_SIZE: + solo_enc->gop = ctrl->val; + return 0; + case V4L2_CID_MOTION_THRESHOLD: + solo_enc->motion_thresh = ctrl->val; + if (!solo_enc->motion_global || !solo_enc->motion_enabled) + return 0; + return solo_set_motion_threshold(solo_dev, solo_enc->ch, + ctrl->val); + case V4L2_CID_MOTION_MODE: + solo_enc->motion_global = ctrl->val == 1; + solo_enc->motion_enabled = ctrl->val > 0; + if (ctrl->val) { + if (solo_enc->motion_global) + solo_set_motion_threshold(solo_dev, + solo_enc->ch, solo_enc->motion_thresh); + else + solo_set_motion_block(solo_dev, solo_enc->ch, + &solo_enc->motion_thresholds); + } + solo_motion_toggle(solo_enc, ctrl->val); + return 0; + case V4L2_CID_OSD_TEXT: + strcpy(solo_enc->osd_text, ctrl->string); + err = solo_osd_print(solo_enc); + return err; + default: + return -EINVAL; + } + + return 0; +} + +static const struct v4l2_file_operations solo_enc_fops = { + .owner = THIS_MODULE, + .open = v4l2_fh_open, + .release = vb2_fop_release, + .read = vb2_fop_read, + .poll = vb2_fop_poll, + .mmap = vb2_fop_mmap, + .unlocked_ioctl = video_ioctl2, +}; + +static const struct v4l2_ioctl_ops solo_enc_ioctl_ops = { + .vidioc_querycap = solo_enc_querycap, + .vidioc_s_std = solo_enc_s_std, + .vidioc_g_std = solo_enc_g_std, + /* Input callbacks */ + .vidioc_enum_input = solo_enc_enum_input, + .vidioc_s_input = solo_enc_set_input, + .vidioc_g_input = solo_enc_get_input, + /* Video capture format callbacks */ + .vidioc_enum_fmt_vid_cap = solo_enc_enum_fmt_cap, + .vidioc_try_fmt_vid_cap = solo_enc_try_fmt_cap, + .vidioc_s_fmt_vid_cap = solo_enc_set_fmt_cap, + .vidioc_g_fmt_vid_cap = solo_enc_get_fmt_cap, + /* Streaming I/O */ + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, + /* Frame size and interval */ + .vidioc_enum_framesizes = solo_enum_framesizes, + .vidioc_enum_frameintervals = solo_enum_frameintervals, + /* Video capture parameters */ + .vidioc_s_parm = solo_s_parm, + .vidioc_g_parm = solo_g_parm, + /* Logging and events */ + .vidioc_log_status = v4l2_ctrl_log_status, + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, + .vidioc_default = solo_enc_default, +}; + +static const struct video_device solo_enc_template = { + .name = SOLO6X10_NAME, + .fops = &solo_enc_fops, + .ioctl_ops = &solo_enc_ioctl_ops, + .minor = -1, + .release = video_device_release, + .tvnorms = V4L2_STD_NTSC_M | V4L2_STD_PAL, +}; + +static const struct v4l2_ctrl_ops solo_ctrl_ops = { + .s_ctrl = solo_s_ctrl, +}; + +static const struct v4l2_ctrl_config solo_motion_threshold_ctrl = { + .ops = &solo_ctrl_ops, + .id = V4L2_CID_MOTION_THRESHOLD, + .name = "Motion Detection Threshold", + .type = V4L2_CTRL_TYPE_INTEGER, + .max = 0xffff, + .def = SOLO_DEF_MOT_THRESH, + .step = 1, + .flags = V4L2_CTRL_FLAG_SLIDER, +}; + +static const char * const solo_motion_mode_menu[] = { + "Disabled", + "Global Threshold", + "Regional Threshold", + NULL +}; + +static const struct v4l2_ctrl_config solo_motion_enable_ctrl = { + .ops = &solo_ctrl_ops, + .id = V4L2_CID_MOTION_MODE, + .name = "Motion Detection Mode", + .type = V4L2_CTRL_TYPE_MENU, + .qmenu = solo_motion_mode_menu, + .max = 2, +}; + +static const struct v4l2_ctrl_config solo_osd_text_ctrl = { + .ops = &solo_ctrl_ops, + .id = V4L2_CID_OSD_TEXT, + .name = "OSD Text", + .type = V4L2_CTRL_TYPE_STRING, + .max = OSD_TEXT_MAX, + .step = 1, +}; + +static struct solo_enc_dev *solo_enc_alloc(struct solo_dev *solo_dev, + u8 ch, unsigned nr) +{ + struct solo_enc_dev *solo_enc; + struct v4l2_ctrl_handler *hdl; + int ret; + int x, y; + + solo_enc = kzalloc(sizeof(*solo_enc), GFP_KERNEL); + if (!solo_enc) + return ERR_PTR(-ENOMEM); + + hdl = &solo_enc->hdl; + v4l2_ctrl_handler_init(hdl, 10); + v4l2_ctrl_new_std(hdl, &solo_ctrl_ops, + V4L2_CID_BRIGHTNESS, 0, 255, 1, 128); + v4l2_ctrl_new_std(hdl, &solo_ctrl_ops, + V4L2_CID_CONTRAST, 0, 255, 1, 128); + v4l2_ctrl_new_std(hdl, &solo_ctrl_ops, + V4L2_CID_SATURATION, 0, 255, 1, 128); + v4l2_ctrl_new_std(hdl, &solo_ctrl_ops, + V4L2_CID_HUE, 0, 255, 1, 128); + if (tw28_has_sharpness(solo_dev, ch)) + v4l2_ctrl_new_std(hdl, &solo_ctrl_ops, + V4L2_CID_SHARPNESS, 0, 15, 1, 0); + v4l2_ctrl_new_std(hdl, &solo_ctrl_ops, + V4L2_CID_MPEG_VIDEO_GOP_SIZE, 1, 255, 1, solo_dev->fps); + v4l2_ctrl_new_custom(hdl, &solo_motion_threshold_ctrl, NULL); + v4l2_ctrl_new_custom(hdl, &solo_motion_enable_ctrl, NULL); + v4l2_ctrl_new_custom(hdl, &solo_osd_text_ctrl, NULL); + if (hdl->error) { + ret = hdl->error; + goto hdl_free; + } + + solo_enc->solo_dev = solo_dev; + solo_enc->ch = ch; + mutex_init(&solo_enc->lock); + spin_lock_init(&solo_enc->av_lock); + INIT_LIST_HEAD(&solo_enc->vidq_active); + solo_enc->fmt = (solo_dev->type == SOLO_DEV_6010) ? + V4L2_PIX_FMT_MPEG4 : V4L2_PIX_FMT_H264; + solo_enc->type = SOLO_ENC_TYPE_STD; + + solo_enc->qp = SOLO_DEFAULT_QP; + solo_enc->gop = solo_dev->fps; + solo_enc->interval = 1; + solo_enc->mode = SOLO_ENC_MODE_CIF; + solo_enc->motion_global = true; + solo_enc->motion_thresh = SOLO_DEF_MOT_THRESH; + for (y = 0; y < SOLO_MOTION_SZ; y++) + for (x = 0; x < SOLO_MOTION_SZ; x++) + solo_enc->motion_thresholds.thresholds[y][x] = + SOLO_DEF_MOT_THRESH; + + solo_enc->vidq.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + solo_enc->vidq.io_modes = VB2_MMAP | VB2_USERPTR | VB2_READ; + solo_enc->vidq.ops = &solo_enc_video_qops; + solo_enc->vidq.mem_ops = &vb2_dma_sg_memops; + solo_enc->vidq.drv_priv = solo_enc; + solo_enc->vidq.gfp_flags = __GFP_DMA32; + solo_enc->vidq.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + solo_enc->vidq.buf_struct_size = sizeof(struct solo_vb2_buf); + solo_enc->vidq.lock = &solo_enc->lock; + ret = vb2_queue_init(&solo_enc->vidq); + if (ret) + goto hdl_free; + solo_update_mode(solo_enc); + + spin_lock_init(&solo_enc->motion_lock); + + /* Initialize this per encoder */ + solo_enc->jpeg_len = sizeof(jpeg_header); + memcpy(solo_enc->jpeg_header, jpeg_header, solo_enc->jpeg_len); + + solo_enc->desc_nelts = 32; + solo_enc->desc_items = pci_alloc_consistent(solo_dev->pdev, + sizeof(struct solo_p2m_desc) * + solo_enc->desc_nelts, + &solo_enc->desc_dma); + ret = -ENOMEM; + if (solo_enc->desc_items == NULL) + goto hdl_free; + + solo_enc->vfd = video_device_alloc(); + if (!solo_enc->vfd) + goto pci_free; + + *solo_enc->vfd = solo_enc_template; + solo_enc->vfd->v4l2_dev = &solo_dev->v4l2_dev; + solo_enc->vfd->ctrl_handler = hdl; + solo_enc->vfd->queue = &solo_enc->vidq; + solo_enc->vfd->lock = &solo_enc->lock; + set_bit(V4L2_FL_USE_FH_PRIO, &solo_enc->vfd->flags); + video_set_drvdata(solo_enc->vfd, solo_enc); + ret = video_register_device(solo_enc->vfd, VFL_TYPE_GRABBER, nr); + if (ret < 0) + goto vdev_release; + + snprintf(solo_enc->vfd->name, sizeof(solo_enc->vfd->name), + "%s-enc (%i/%i)", SOLO6X10_NAME, solo_dev->vfd->num, + solo_enc->vfd->num); + + return solo_enc; + +vdev_release: + video_device_release(solo_enc->vfd); +pci_free: + pci_free_consistent(solo_enc->solo_dev->pdev, + sizeof(struct solo_p2m_desc) * solo_enc->desc_nelts, + solo_enc->desc_items, solo_enc->desc_dma); +hdl_free: + v4l2_ctrl_handler_free(hdl); + kfree(solo_enc); + return ERR_PTR(ret); +} + +static void solo_enc_free(struct solo_enc_dev *solo_enc) +{ + if (solo_enc == NULL) + return; + + video_unregister_device(solo_enc->vfd); + v4l2_ctrl_handler_free(&solo_enc->hdl); + kfree(solo_enc); +} + +int solo_enc_v4l2_init(struct solo_dev *solo_dev, unsigned nr) +{ + int i; + + init_waitqueue_head(&solo_dev->ring_thread_wait); + + solo_dev->vh_size = sizeof(vop_header); + solo_dev->vh_buf = pci_alloc_consistent(solo_dev->pdev, + solo_dev->vh_size, + &solo_dev->vh_dma); + if (solo_dev->vh_buf == NULL) + return -ENOMEM; + + for (i = 0; i < solo_dev->nr_chans; i++) { + solo_dev->v4l2_enc[i] = solo_enc_alloc(solo_dev, i, nr); + if (IS_ERR(solo_dev->v4l2_enc[i])) + break; + } + + if (i != solo_dev->nr_chans) { + int ret = PTR_ERR(solo_dev->v4l2_enc[i]); + while (i--) + solo_enc_free(solo_dev->v4l2_enc[i]); + pci_free_consistent(solo_dev->pdev, solo_dev->vh_size, + solo_dev->vh_buf, solo_dev->vh_dma); + solo_dev->vh_buf = NULL; + return ret; + } + + if (solo_dev->type == SOLO_DEV_6010) + solo_dev->enc_bw_remain = solo_dev->fps * 4 * 4; + else + solo_dev->enc_bw_remain = solo_dev->fps * 4 * 5; + + dev_info(&solo_dev->pdev->dev, "Encoders as /dev/video%d-%d\n", + solo_dev->v4l2_enc[0]->vfd->num, + solo_dev->v4l2_enc[solo_dev->nr_chans - 1]->vfd->num); + + return 0; +} + +void solo_enc_v4l2_exit(struct solo_dev *solo_dev) +{ + int i; + + for (i = 0; i < solo_dev->nr_chans; i++) + solo_enc_free(solo_dev->v4l2_enc[i]); + + if (solo_dev->vh_buf) + pci_free_consistent(solo_dev->pdev, solo_dev->vh_size, + solo_dev->vh_buf, solo_dev->vh_dma); +} diff --git a/drivers/staging/media/solo6x10/solo6x10-v4l2.c b/drivers/staging/media/solo6x10/solo6x10-v4l2.c new file mode 100644 index 00000000000..5d0100eb38e --- /dev/null +++ b/drivers/staging/media/solo6x10/solo6x10-v4l2.c @@ -0,0 +1,734 @@ +/* + * Copyright (C) 2010-2013 Bluecherry, LLC <http://www.bluecherrydvr.com> + * + * Original author: + * Ben Collins <bcollins@ubuntu.com> + * + * Additional work by: + * John Brooks <john.brooks@bluecherry.net> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/kthread.h> +#include <linux/freezer.h> + +#include <media/v4l2-ioctl.h> +#include <media/v4l2-common.h> +#include <media/v4l2-event.h> +#include <media/videobuf2-dma-contig.h> + +#include "solo6x10.h" +#include "solo6x10-tw28.h" + +/* Image size is two fields, SOLO_HW_BPL is one horizontal line in hardware */ +#define SOLO_HW_BPL 2048 +#define solo_vlines(__solo) (__solo->video_vsize * 2) +#define solo_image_size(__solo) (solo_bytesperline(__solo) * \ + solo_vlines(__solo)) +#define solo_bytesperline(__solo) (__solo->video_hsize * 2) + +#define MIN_VID_BUFFERS 2 + +static inline void erase_on(struct solo_dev *solo_dev) +{ + solo_reg_write(solo_dev, SOLO_VO_DISP_ERASE, SOLO_VO_DISP_ERASE_ON); + solo_dev->erasing = 1; + solo_dev->frame_blank = 0; +} + +static inline int erase_off(struct solo_dev *solo_dev) +{ + if (!solo_dev->erasing) + return 0; + + /* First time around, assert erase off */ + if (!solo_dev->frame_blank) + solo_reg_write(solo_dev, SOLO_VO_DISP_ERASE, 0); + /* Keep the erasing flag on for 8 frames minimum */ + if (solo_dev->frame_blank++ >= 8) + solo_dev->erasing = 0; + + return 1; +} + +void solo_video_in_isr(struct solo_dev *solo_dev) +{ + wake_up_interruptible_all(&solo_dev->disp_thread_wait); +} + +static void solo_win_setup(struct solo_dev *solo_dev, u8 ch, + int sx, int sy, int ex, int ey, int scale) +{ + if (ch >= solo_dev->nr_chans) + return; + + /* Here, we just keep window/channel the same */ + solo_reg_write(solo_dev, SOLO_VI_WIN_CTRL0(ch), + SOLO_VI_WIN_CHANNEL(ch) | + SOLO_VI_WIN_SX(sx) | + SOLO_VI_WIN_EX(ex) | + SOLO_VI_WIN_SCALE(scale)); + + solo_reg_write(solo_dev, SOLO_VI_WIN_CTRL1(ch), + SOLO_VI_WIN_SY(sy) | + SOLO_VI_WIN_EY(ey)); +} + +static int solo_v4l2_ch_ext_4up(struct solo_dev *solo_dev, u8 idx, int on) +{ + u8 ch = idx * 4; + + if (ch >= solo_dev->nr_chans) + return -EINVAL; + + if (!on) { + u8 i; + for (i = ch; i < ch + 4; i++) + solo_win_setup(solo_dev, i, solo_dev->video_hsize, + solo_vlines(solo_dev), + solo_dev->video_hsize, + solo_vlines(solo_dev), 0); + return 0; + } + + /* Row 1 */ + solo_win_setup(solo_dev, ch, 0, 0, solo_dev->video_hsize / 2, + solo_vlines(solo_dev) / 2, 3); + solo_win_setup(solo_dev, ch + 1, solo_dev->video_hsize / 2, 0, + solo_dev->video_hsize, solo_vlines(solo_dev) / 2, 3); + /* Row 2 */ + solo_win_setup(solo_dev, ch + 2, 0, solo_vlines(solo_dev) / 2, + solo_dev->video_hsize / 2, solo_vlines(solo_dev), 3); + solo_win_setup(solo_dev, ch + 3, solo_dev->video_hsize / 2, + solo_vlines(solo_dev) / 2, solo_dev->video_hsize, + solo_vlines(solo_dev), 3); + + return 0; +} + +static int solo_v4l2_ch_ext_16up(struct solo_dev *solo_dev, int on) +{ + int sy, ysize, hsize, i; + + if (!on) { + for (i = 0; i < 16; i++) + solo_win_setup(solo_dev, i, solo_dev->video_hsize, + solo_vlines(solo_dev), + solo_dev->video_hsize, + solo_vlines(solo_dev), 0); + return 0; + } + + ysize = solo_vlines(solo_dev) / 4; + hsize = solo_dev->video_hsize / 4; + + for (sy = 0, i = 0; i < 4; i++, sy += ysize) { + solo_win_setup(solo_dev, i * 4, 0, sy, hsize, + sy + ysize, 5); + solo_win_setup(solo_dev, (i * 4) + 1, hsize, sy, + hsize * 2, sy + ysize, 5); + solo_win_setup(solo_dev, (i * 4) + 2, hsize * 2, sy, + hsize * 3, sy + ysize, 5); + solo_win_setup(solo_dev, (i * 4) + 3, hsize * 3, sy, + solo_dev->video_hsize, sy + ysize, 5); + } + + return 0; +} + +static int solo_v4l2_ch(struct solo_dev *solo_dev, u8 ch, int on) +{ + u8 ext_ch; + + if (ch < solo_dev->nr_chans) { + solo_win_setup(solo_dev, ch, on ? 0 : solo_dev->video_hsize, + on ? 0 : solo_vlines(solo_dev), + solo_dev->video_hsize, solo_vlines(solo_dev), + on ? 1 : 0); + return 0; + } + + if (ch >= solo_dev->nr_chans + solo_dev->nr_ext) + return -EINVAL; + + ext_ch = ch - solo_dev->nr_chans; + + /* 4up's first */ + if (ext_ch < 4) + return solo_v4l2_ch_ext_4up(solo_dev, ext_ch, on); + + /* Remaining case is 16up for 16-port */ + return solo_v4l2_ch_ext_16up(solo_dev, on); +} + +static int solo_v4l2_set_ch(struct solo_dev *solo_dev, u8 ch) +{ + if (ch >= solo_dev->nr_chans + solo_dev->nr_ext) + return -EINVAL; + + erase_on(solo_dev); + + solo_v4l2_ch(solo_dev, solo_dev->cur_disp_ch, 0); + solo_v4l2_ch(solo_dev, ch, 1); + + solo_dev->cur_disp_ch = ch; + + return 0; +} + +static void solo_fillbuf(struct solo_dev *solo_dev, + struct vb2_buffer *vb) +{ + dma_addr_t vbuf; + unsigned int fdma_addr; + int error = -1; + int i; + + vbuf = vb2_dma_contig_plane_dma_addr(vb, 0); + if (!vbuf) + goto finish_buf; + + if (erase_off(solo_dev)) { + void *p = vb2_plane_vaddr(vb, 0); + int image_size = solo_image_size(solo_dev); + for (i = 0; i < image_size; i += 2) { + ((u8 *)p)[i] = 0x80; + ((u8 *)p)[i + 1] = 0x00; + } + error = 0; + } else { + fdma_addr = SOLO_DISP_EXT_ADDR + (solo_dev->old_write * + (SOLO_HW_BPL * solo_vlines(solo_dev))); + + error = solo_p2m_dma_t(solo_dev, 0, vbuf, fdma_addr, + solo_bytesperline(solo_dev), + solo_vlines(solo_dev), SOLO_HW_BPL); + } + +finish_buf: + if (!error) { + vb2_set_plane_payload(vb, 0, + solo_vlines(solo_dev) * solo_bytesperline(solo_dev)); + vb->v4l2_buf.sequence = solo_dev->sequence++; + v4l2_get_timestamp(&vb->v4l2_buf.timestamp); + } + + vb2_buffer_done(vb, error ? VB2_BUF_STATE_ERROR : VB2_BUF_STATE_DONE); +} + +static void solo_thread_try(struct solo_dev *solo_dev) +{ + struct solo_vb2_buf *vb; + + /* Only "break" from this loop if slock is held, otherwise + * just return. */ + for (;;) { + unsigned int cur_write; + + cur_write = SOLO_VI_STATUS0_PAGE( + solo_reg_read(solo_dev, SOLO_VI_STATUS0)); + if (cur_write == solo_dev->old_write) + return; + + spin_lock(&solo_dev->slock); + + if (list_empty(&solo_dev->vidq_active)) + break; + + vb = list_first_entry(&solo_dev->vidq_active, struct solo_vb2_buf, + list); + + solo_dev->old_write = cur_write; + list_del(&vb->list); + + spin_unlock(&solo_dev->slock); + + solo_fillbuf(solo_dev, &vb->vb); + } + + assert_spin_locked(&solo_dev->slock); + spin_unlock(&solo_dev->slock); +} + +static int solo_thread(void *data) +{ + struct solo_dev *solo_dev = data; + DECLARE_WAITQUEUE(wait, current); + + set_freezable(); + add_wait_queue(&solo_dev->disp_thread_wait, &wait); + + for (;;) { + long timeout = schedule_timeout_interruptible(HZ); + if (timeout == -ERESTARTSYS || kthread_should_stop()) + break; + solo_thread_try(solo_dev); + try_to_freeze(); + } + + remove_wait_queue(&solo_dev->disp_thread_wait, &wait); + + return 0; +} + +static int solo_start_thread(struct solo_dev *solo_dev) +{ + int ret = 0; + + solo_dev->kthread = kthread_run(solo_thread, solo_dev, SOLO6X10_NAME "_disp"); + + if (IS_ERR(solo_dev->kthread)) { + ret = PTR_ERR(solo_dev->kthread); + solo_dev->kthread = NULL; + return ret; + } + solo_irq_on(solo_dev, SOLO_IRQ_VIDEO_IN); + + return ret; +} + +static void solo_stop_thread(struct solo_dev *solo_dev) +{ + if (!solo_dev->kthread) + return; + + solo_irq_off(solo_dev, SOLO_IRQ_VIDEO_IN); + kthread_stop(solo_dev->kthread); + solo_dev->kthread = NULL; +} + +static int solo_queue_setup(struct vb2_queue *q, const struct v4l2_format *fmt, + unsigned int *num_buffers, unsigned int *num_planes, + unsigned int sizes[], void *alloc_ctxs[]) +{ + struct solo_dev *solo_dev = vb2_get_drv_priv(q); + + sizes[0] = solo_image_size(solo_dev); + alloc_ctxs[0] = solo_dev->alloc_ctx; + *num_planes = 1; + + if (*num_buffers < MIN_VID_BUFFERS) + *num_buffers = MIN_VID_BUFFERS; + + return 0; +} + +static int solo_start_streaming(struct vb2_queue *q, unsigned int count) +{ + struct solo_dev *solo_dev = vb2_get_drv_priv(q); + + solo_dev->sequence = 0; + return solo_start_thread(solo_dev); +} + +static void solo_stop_streaming(struct vb2_queue *q) +{ + struct solo_dev *solo_dev = vb2_get_drv_priv(q); + + solo_stop_thread(solo_dev); + INIT_LIST_HEAD(&solo_dev->vidq_active); +} + +static void solo_buf_queue(struct vb2_buffer *vb) +{ + struct vb2_queue *vq = vb->vb2_queue; + struct solo_dev *solo_dev = vb2_get_drv_priv(vq); + struct solo_vb2_buf *solo_vb = + container_of(vb, struct solo_vb2_buf, vb); + + spin_lock(&solo_dev->slock); + list_add_tail(&solo_vb->list, &solo_dev->vidq_active); + spin_unlock(&solo_dev->slock); + wake_up_interruptible(&solo_dev->disp_thread_wait); +} + +static const struct vb2_ops solo_video_qops = { + .queue_setup = solo_queue_setup, + .buf_queue = solo_buf_queue, + .start_streaming = solo_start_streaming, + .stop_streaming = solo_stop_streaming, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, +}; + +static int solo_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + struct solo_dev *solo_dev = video_drvdata(file); + + strcpy(cap->driver, SOLO6X10_NAME); + strcpy(cap->card, "Softlogic 6x10"); + snprintf(cap->bus_info, sizeof(cap->bus_info), "PCI:%s", + pci_name(solo_dev->pdev)); + cap->device_caps = V4L2_CAP_VIDEO_CAPTURE | + V4L2_CAP_READWRITE | V4L2_CAP_STREAMING; + cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS; + return 0; +} + +static int solo_enum_ext_input(struct solo_dev *solo_dev, + struct v4l2_input *input) +{ + static const char * const dispnames_1[] = { "4UP" }; + static const char * const dispnames_2[] = { "4UP-1", "4UP-2" }; + static const char * const dispnames_5[] = { + "4UP-1", "4UP-2", "4UP-3", "4UP-4", "16UP" + }; + const char * const *dispnames; + + if (input->index >= (solo_dev->nr_chans + solo_dev->nr_ext)) + return -EINVAL; + + if (solo_dev->nr_ext == 5) + dispnames = dispnames_5; + else if (solo_dev->nr_ext == 2) + dispnames = dispnames_2; + else + dispnames = dispnames_1; + + snprintf(input->name, sizeof(input->name), "Multi %s", + dispnames[input->index - solo_dev->nr_chans]); + + return 0; +} + +static int solo_enum_input(struct file *file, void *priv, + struct v4l2_input *input) +{ + struct solo_dev *solo_dev = video_drvdata(file); + + if (input->index >= solo_dev->nr_chans) { + int ret = solo_enum_ext_input(solo_dev, input); + if (ret < 0) + return ret; + } else { + snprintf(input->name, sizeof(input->name), "Camera %d", + input->index + 1); + + /* We can only check this for normal inputs */ + if (!tw28_get_video_status(solo_dev, input->index)) + input->status = V4L2_IN_ST_NO_SIGNAL; + } + + input->type = V4L2_INPUT_TYPE_CAMERA; + input->std = solo_dev->vfd->tvnorms; + return 0; +} + +static int solo_set_input(struct file *file, void *priv, unsigned int index) +{ + struct solo_dev *solo_dev = video_drvdata(file); + int ret = solo_v4l2_set_ch(solo_dev, index); + + if (!ret) { + while (erase_off(solo_dev)) + /* Do nothing */; + } + + return ret; +} + +static int solo_get_input(struct file *file, void *priv, unsigned int *index) +{ + struct solo_dev *solo_dev = video_drvdata(file); + + *index = solo_dev->cur_disp_ch; + + return 0; +} + +static int solo_enum_fmt_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + if (f->index) + return -EINVAL; + + f->pixelformat = V4L2_PIX_FMT_UYVY; + strlcpy(f->description, "UYUV 4:2:2 Packed", sizeof(f->description)); + + return 0; +} + +static int solo_try_fmt_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct solo_dev *solo_dev = video_drvdata(file); + struct v4l2_pix_format *pix = &f->fmt.pix; + int image_size = solo_image_size(solo_dev); + + if (pix->pixelformat != V4L2_PIX_FMT_UYVY) + return -EINVAL; + + pix->width = solo_dev->video_hsize; + pix->height = solo_vlines(solo_dev); + pix->sizeimage = image_size; + pix->field = V4L2_FIELD_INTERLACED; + pix->pixelformat = V4L2_PIX_FMT_UYVY; + pix->colorspace = V4L2_COLORSPACE_SMPTE170M; + pix->priv = 0; + return 0; +} + +static int solo_set_fmt_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct solo_dev *solo_dev = video_drvdata(file); + + if (vb2_is_busy(&solo_dev->vidq)) + return -EBUSY; + + /* For right now, if it doesn't match our running config, + * then fail */ + return solo_try_fmt_cap(file, priv, f); +} + +static int solo_get_fmt_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct solo_dev *solo_dev = video_drvdata(file); + struct v4l2_pix_format *pix = &f->fmt.pix; + + pix->width = solo_dev->video_hsize; + pix->height = solo_vlines(solo_dev); + pix->pixelformat = V4L2_PIX_FMT_UYVY; + pix->field = V4L2_FIELD_INTERLACED; + pix->sizeimage = solo_image_size(solo_dev); + pix->colorspace = V4L2_COLORSPACE_SMPTE170M; + pix->bytesperline = solo_bytesperline(solo_dev); + pix->priv = 0; + + return 0; +} + +static int solo_g_std(struct file *file, void *priv, v4l2_std_id *i) +{ + struct solo_dev *solo_dev = video_drvdata(file); + + if (solo_dev->video_type == SOLO_VO_FMT_TYPE_NTSC) + *i = V4L2_STD_NTSC_M; + else + *i = V4L2_STD_PAL; + return 0; +} + +int solo_set_video_type(struct solo_dev *solo_dev, bool is_50hz) +{ + int i; + + /* Make sure all video nodes are idle */ + if (vb2_is_busy(&solo_dev->vidq)) + return -EBUSY; + for (i = 0; i < solo_dev->nr_chans; i++) + if (vb2_is_busy(&solo_dev->v4l2_enc[i]->vidq)) + return -EBUSY; + solo_dev->video_type = is_50hz ? SOLO_VO_FMT_TYPE_PAL : + SOLO_VO_FMT_TYPE_NTSC; + /* Reconfigure for the new standard */ + solo_disp_init(solo_dev); + solo_enc_init(solo_dev); + solo_tw28_init(solo_dev); + for (i = 0; i < solo_dev->nr_chans; i++) + solo_update_mode(solo_dev->v4l2_enc[i]); + return solo_v4l2_set_ch(solo_dev, solo_dev->cur_disp_ch); +} + +static int solo_s_std(struct file *file, void *priv, v4l2_std_id std) +{ + struct solo_dev *solo_dev = video_drvdata(file); + + return solo_set_video_type(solo_dev, std & V4L2_STD_625_50); +} + +static int solo_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct solo_dev *solo_dev = + container_of(ctrl->handler, struct solo_dev, disp_hdl); + + switch (ctrl->id) { + case V4L2_CID_MOTION_TRACE: + if (ctrl->val) { + solo_reg_write(solo_dev, SOLO_VI_MOTION_BORDER, + SOLO_VI_MOTION_Y_ADD | + SOLO_VI_MOTION_Y_VALUE(0x20) | + SOLO_VI_MOTION_CB_VALUE(0x10) | + SOLO_VI_MOTION_CR_VALUE(0x10)); + solo_reg_write(solo_dev, SOLO_VI_MOTION_BAR, + SOLO_VI_MOTION_CR_ADD | + SOLO_VI_MOTION_Y_VALUE(0x10) | + SOLO_VI_MOTION_CB_VALUE(0x80) | + SOLO_VI_MOTION_CR_VALUE(0x10)); + } else { + solo_reg_write(solo_dev, SOLO_VI_MOTION_BORDER, 0); + solo_reg_write(solo_dev, SOLO_VI_MOTION_BAR, 0); + } + return 0; + default: + break; + } + return -EINVAL; +} + +static const struct v4l2_file_operations solo_v4l2_fops = { + .owner = THIS_MODULE, + .open = v4l2_fh_open, + .release = vb2_fop_release, + .read = vb2_fop_read, + .poll = vb2_fop_poll, + .mmap = vb2_fop_mmap, + .unlocked_ioctl = video_ioctl2, +}; + +static const struct v4l2_ioctl_ops solo_v4l2_ioctl_ops = { + .vidioc_querycap = solo_querycap, + .vidioc_s_std = solo_s_std, + .vidioc_g_std = solo_g_std, + /* Input callbacks */ + .vidioc_enum_input = solo_enum_input, + .vidioc_s_input = solo_set_input, + .vidioc_g_input = solo_get_input, + /* Video capture format callbacks */ + .vidioc_enum_fmt_vid_cap = solo_enum_fmt_cap, + .vidioc_try_fmt_vid_cap = solo_try_fmt_cap, + .vidioc_s_fmt_vid_cap = solo_set_fmt_cap, + .vidioc_g_fmt_vid_cap = solo_get_fmt_cap, + /* Streaming I/O */ + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, + /* Logging and events */ + .vidioc_log_status = v4l2_ctrl_log_status, + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +static struct video_device solo_v4l2_template = { + .name = SOLO6X10_NAME, + .fops = &solo_v4l2_fops, + .ioctl_ops = &solo_v4l2_ioctl_ops, + .minor = -1, + .release = video_device_release, + .tvnorms = V4L2_STD_NTSC_M | V4L2_STD_PAL, +}; + +static const struct v4l2_ctrl_ops solo_ctrl_ops = { + .s_ctrl = solo_s_ctrl, +}; + +static const struct v4l2_ctrl_config solo_motion_trace_ctrl = { + .ops = &solo_ctrl_ops, + .id = V4L2_CID_MOTION_TRACE, + .name = "Motion Detection Trace", + .type = V4L2_CTRL_TYPE_BOOLEAN, + .max = 1, + .step = 1, +}; + +int solo_v4l2_init(struct solo_dev *solo_dev, unsigned nr) +{ + int ret; + int i; + + init_waitqueue_head(&solo_dev->disp_thread_wait); + spin_lock_init(&solo_dev->slock); + mutex_init(&solo_dev->lock); + INIT_LIST_HEAD(&solo_dev->vidq_active); + + solo_dev->vfd = video_device_alloc(); + if (!solo_dev->vfd) + return -ENOMEM; + + *solo_dev->vfd = solo_v4l2_template; + solo_dev->vfd->v4l2_dev = &solo_dev->v4l2_dev; + solo_dev->vfd->queue = &solo_dev->vidq; + solo_dev->vfd->lock = &solo_dev->lock; + v4l2_ctrl_handler_init(&solo_dev->disp_hdl, 1); + v4l2_ctrl_new_custom(&solo_dev->disp_hdl, &solo_motion_trace_ctrl, NULL); + if (solo_dev->disp_hdl.error) { + ret = solo_dev->disp_hdl.error; + goto fail; + } + solo_dev->vfd->ctrl_handler = &solo_dev->disp_hdl; + set_bit(V4L2_FL_USE_FH_PRIO, &solo_dev->vfd->flags); + + video_set_drvdata(solo_dev->vfd, solo_dev); + + solo_dev->vidq.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + solo_dev->vidq.io_modes = VB2_MMAP | VB2_USERPTR | VB2_READ; + solo_dev->vidq.ops = &solo_video_qops; + solo_dev->vidq.mem_ops = &vb2_dma_contig_memops; + solo_dev->vidq.drv_priv = solo_dev; + solo_dev->vidq.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + solo_dev->vidq.gfp_flags = __GFP_DMA32; + solo_dev->vidq.buf_struct_size = sizeof(struct solo_vb2_buf); + solo_dev->vidq.lock = &solo_dev->lock; + ret = vb2_queue_init(&solo_dev->vidq); + if (ret < 0) + goto fail; + + solo_dev->alloc_ctx = vb2_dma_contig_init_ctx(&solo_dev->pdev->dev); + if (IS_ERR(solo_dev->alloc_ctx)) { + dev_err(&solo_dev->pdev->dev, "Can't allocate buffer context"); + return PTR_ERR(solo_dev->alloc_ctx); + } + + /* Cycle all the channels and clear */ + for (i = 0; i < solo_dev->nr_chans; i++) { + solo_v4l2_set_ch(solo_dev, i); + while (erase_off(solo_dev)) + /* Do nothing */; + } + + /* Set the default display channel */ + solo_v4l2_set_ch(solo_dev, 0); + while (erase_off(solo_dev)) + /* Do nothing */; + + ret = video_register_device(solo_dev->vfd, VFL_TYPE_GRABBER, nr); + if (ret < 0) + goto fail; + + snprintf(solo_dev->vfd->name, sizeof(solo_dev->vfd->name), "%s (%i)", + SOLO6X10_NAME, solo_dev->vfd->num); + + dev_info(&solo_dev->pdev->dev, "Display as /dev/video%d with " + "%d inputs (%d extended)\n", solo_dev->vfd->num, + solo_dev->nr_chans, solo_dev->nr_ext); + + return 0; + +fail: + video_device_release(solo_dev->vfd); + vb2_dma_contig_cleanup_ctx(solo_dev->alloc_ctx); + v4l2_ctrl_handler_free(&solo_dev->disp_hdl); + solo_dev->vfd = NULL; + return ret; +} + +void solo_v4l2_exit(struct solo_dev *solo_dev) +{ + if (solo_dev->vfd == NULL) + return; + + video_unregister_device(solo_dev->vfd); + vb2_dma_contig_cleanup_ctx(solo_dev->alloc_ctx); + v4l2_ctrl_handler_free(&solo_dev->disp_hdl); + solo_dev->vfd = NULL; +} diff --git a/drivers/staging/media/solo6x10/solo6x10.h b/drivers/staging/media/solo6x10/solo6x10.h new file mode 100644 index 00000000000..8964f8be158 --- /dev/null +++ b/drivers/staging/media/solo6x10/solo6x10.h @@ -0,0 +1,426 @@ +/* + * Copyright (C) 2010-2013 Bluecherry, LLC <http://www.bluecherrydvr.com> + * + * Original author: + * Ben Collins <bcollins@ubuntu.com> + * + * Additional work by: + * John Brooks <john.brooks@bluecherry.net> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef __SOLO6X10_H +#define __SOLO6X10_H + +#include <linux/pci.h> +#include <linux/i2c.h> +#include <linux/mutex.h> +#include <linux/list.h> +#include <linux/wait.h> +#include <linux/stringify.h> +#include <linux/io.h> +#include <linux/atomic.h> +#include <linux/slab.h> +#include <linux/videodev2.h> + +#include <media/v4l2-dev.h> +#include <media/v4l2-device.h> +#include <media/v4l2-ctrls.h> +#include <media/videobuf2-core.h> + +#include "solo6x10-regs.h" + +#ifndef PCI_VENDOR_ID_SOFTLOGIC +#define PCI_VENDOR_ID_SOFTLOGIC 0x9413 +#define PCI_DEVICE_ID_SOLO6010 0x6010 +#define PCI_DEVICE_ID_SOLO6110 0x6110 +#endif + +#ifndef PCI_VENDOR_ID_BLUECHERRY +#define PCI_VENDOR_ID_BLUECHERRY 0x1BB3 +/* Neugent Softlogic 6010 based cards */ +#define PCI_DEVICE_ID_NEUSOLO_4 0x4304 +#define PCI_DEVICE_ID_NEUSOLO_9 0x4309 +#define PCI_DEVICE_ID_NEUSOLO_16 0x4310 +/* Bluecherry Softlogic 6010 based cards */ +#define PCI_DEVICE_ID_BC_SOLO_4 0x4E04 +#define PCI_DEVICE_ID_BC_SOLO_9 0x4E09 +#define PCI_DEVICE_ID_BC_SOLO_16 0x4E10 +/* Bluecherry Softlogic 6110 based cards */ +#define PCI_DEVICE_ID_BC_6110_4 0x5304 +#define PCI_DEVICE_ID_BC_6110_8 0x5308 +#define PCI_DEVICE_ID_BC_6110_16 0x5310 +#endif /* Bluecherry */ + +/* Used in pci_device_id, and solo_dev->type */ +#define SOLO_DEV_6010 0 +#define SOLO_DEV_6110 1 + +#define SOLO6X10_NAME "solo6x10" + +#define SOLO_MAX_CHANNELS 16 + +#define SOLO6X10_VERSION "3.0.0" + +/* + * The SOLO6x10 actually has 8 i2c channels, but we only use 2. + * 0 - Techwell chip(s) + * 1 - SAA7128 + */ +#define SOLO_I2C_ADAPTERS 2 +#define SOLO_I2C_TW 0 +#define SOLO_I2C_SAA 1 + +/* DMA Engine setup */ +#define SOLO_NR_P2M 4 +#define SOLO_NR_P2M_DESC 256 +#define SOLO_P2M_DESC_SIZE (SOLO_NR_P2M_DESC * 16) + +/* Encoder standard modes */ +#define SOLO_ENC_MODE_CIF 2 +#define SOLO_ENC_MODE_HD1 1 +#define SOLO_ENC_MODE_D1 9 + +#define SOLO_DEFAULT_QP 3 + +#ifndef V4L2_BUF_FLAG_MOTION_ON +#define V4L2_BUF_FLAG_MOTION_ON 0x10000 +#define V4L2_BUF_FLAG_MOTION_DETECTED 0x20000 +#endif + +#define SOLO_CID_CUSTOM_BASE (V4L2_CID_USER_BASE | 0xf000) +#define V4L2_CID_MOTION_MODE (SOLO_CID_CUSTOM_BASE+0) +#define V4L2_CID_MOTION_THRESHOLD (SOLO_CID_CUSTOM_BASE+1) +#define V4L2_CID_MOTION_TRACE (SOLO_CID_CUSTOM_BASE+2) +#define V4L2_CID_OSD_TEXT (SOLO_CID_CUSTOM_BASE+3) + +/* + * Motion thresholds are in a table of 64x64 samples, with + * each sample representing 16x16 pixels of the source. In + * effect, 44x30 samples are used for NTSC, and 44x36 for PAL. + * The 5th sample on the 10th row is (10*64)+5 = 645. + * + * Using a 64x64 array will result in a problem on some architectures like + * the powerpc where the size of the argument is limited to 13 bits. + * Since both PAL and NTSC do not use the full table anyway I've chosen + * to limit the array to 45x45 (45*16 = 720, which is the maximum PAL/NTSC + * width). + */ +#define SOLO_MOTION_SZ (45) +struct solo_motion_thresholds { + __u16 thresholds[SOLO_MOTION_SZ][SOLO_MOTION_SZ]; +}; + +#define SOLO_IOC_G_MOTION_THRESHOLDS _IOR('V', BASE_VIDIOC_PRIVATE+0, struct solo_motion_thresholds) +#define SOLO_IOC_S_MOTION_THRESHOLDS _IOW('V', BASE_VIDIOC_PRIVATE+1, struct solo_motion_thresholds) + +enum SOLO_I2C_STATE { + IIC_STATE_IDLE, + IIC_STATE_START, + IIC_STATE_READ, + IIC_STATE_WRITE, + IIC_STATE_STOP +}; + +/* Defined in Table 4-16, Page 68-69 of the 6010 Datasheet */ +struct solo_p2m_desc { + u32 ctrl; + u32 cfg; + u32 dma_addr; + u32 ext_addr; +}; + +struct solo_p2m_dev { + struct mutex mutex; + struct completion completion; + int desc_count; + int desc_idx; + struct solo_p2m_desc *descs; + int error; +}; + +#define OSD_TEXT_MAX 44 + +struct solo_vb2_buf { + struct vb2_buffer vb; + struct list_head list; +}; + +enum solo_enc_types { + SOLO_ENC_TYPE_STD, + SOLO_ENC_TYPE_EXT, +}; + +struct solo_enc_dev { + struct solo_dev *solo_dev; + /* V4L2 Items */ + struct v4l2_ctrl_handler hdl; + struct video_device *vfd; + /* General accounting */ + struct mutex lock; + spinlock_t motion_lock; + u8 ch; + u8 mode, gop, qp, interlaced, interval; + u8 bw_weight; + u16 motion_thresh; + struct solo_motion_thresholds motion_thresholds; + bool motion_global; + bool motion_enabled; + u16 width; + u16 height; + + /* OSD buffers */ + char osd_text[OSD_TEXT_MAX + 1]; + u8 osd_buf[SOLO_EOSD_EXT_SIZE_MAX] + __aligned(4); + + /* VOP stuff */ + unsigned char vop[64]; + int vop_len; + unsigned char jpeg_header[1024]; + int jpeg_len; + + u32 fmt; + enum solo_enc_types type; + u32 sequence; + struct vb2_queue vidq; + struct list_head vidq_active; + int desc_count; + int desc_nelts; + struct solo_p2m_desc *desc_items; + dma_addr_t desc_dma; + spinlock_t av_lock; +}; + +/* The SOLO6x10 PCI Device */ +struct solo_dev { + /* General stuff */ + struct pci_dev *pdev; + int type; + unsigned int time_sync; + unsigned int usec_lsb; + unsigned int clock_mhz; + u8 __iomem *reg_base; + int nr_chans; + int nr_ext; + u32 irq_mask; + u32 motion_mask; + spinlock_t reg_io_lock; + struct v4l2_device v4l2_dev; + + /* tw28xx accounting */ + u8 tw2865, tw2864, tw2815; + u8 tw28_cnt; + + /* i2c related items */ + struct i2c_adapter i2c_adap[SOLO_I2C_ADAPTERS]; + enum SOLO_I2C_STATE i2c_state; + struct mutex i2c_mutex; + int i2c_id; + wait_queue_head_t i2c_wait; + struct i2c_msg *i2c_msg; + unsigned int i2c_msg_num; + unsigned int i2c_msg_ptr; + + /* P2M DMA Engine */ + struct solo_p2m_dev p2m_dev[SOLO_NR_P2M]; + atomic_t p2m_count; + int p2m_jiffies; + unsigned int p2m_timeouts; + + /* V4L2 Display items */ + struct video_device *vfd; + unsigned int erasing; + unsigned int frame_blank; + u8 cur_disp_ch; + wait_queue_head_t disp_thread_wait; + struct v4l2_ctrl_handler disp_hdl; + + /* V4L2 Encoder items */ + struct solo_enc_dev *v4l2_enc[SOLO_MAX_CHANNELS]; + u16 enc_bw_remain; + /* IDX into hw mp4 encoder */ + u8 enc_idx; + + /* Current video settings */ + u32 video_type; + u16 video_hsize, video_vsize; + u16 vout_hstart, vout_vstart; + u16 vin_hstart, vin_vstart; + u8 fps; + + /* JPEG Qp setting */ + spinlock_t jpeg_qp_lock; + u32 jpeg_qp[2]; + + /* Audio components */ + struct snd_card *snd_card; + struct snd_pcm *snd_pcm; + atomic_t snd_users; + int g723_hw_idx; + + /* sysfs stuffs */ + struct device dev; + int sdram_size; + struct bin_attribute sdram_attr; + unsigned int sys_config; + + /* Ring thread */ + struct task_struct *ring_thread; + wait_queue_head_t ring_thread_wait; + + /* VOP_HEADER handling */ + void *vh_buf; + dma_addr_t vh_dma; + int vh_size; + + /* Buffer handling */ + struct vb2_queue vidq; + struct vb2_alloc_ctx *alloc_ctx; + u32 sequence; + struct task_struct *kthread; + struct mutex lock; + spinlock_t slock; + int old_write; + struct list_head vidq_active; +}; + +static inline u32 solo_reg_read(struct solo_dev *solo_dev, int reg) +{ + unsigned long flags; + u32 ret; + u16 val; + + spin_lock_irqsave(&solo_dev->reg_io_lock, flags); + + ret = readl(solo_dev->reg_base + reg); + rmb(); + pci_read_config_word(solo_dev->pdev, PCI_STATUS, &val); + rmb(); + + spin_unlock_irqrestore(&solo_dev->reg_io_lock, flags); + + return ret; +} + +static inline void solo_reg_write(struct solo_dev *solo_dev, int reg, + u32 data) +{ + unsigned long flags; + u16 val; + + spin_lock_irqsave(&solo_dev->reg_io_lock, flags); + + writel(data, solo_dev->reg_base + reg); + wmb(); + pci_read_config_word(solo_dev->pdev, PCI_STATUS, &val); + rmb(); + + spin_unlock_irqrestore(&solo_dev->reg_io_lock, flags); +} + +static inline void solo_irq_on(struct solo_dev *dev, u32 mask) +{ + dev->irq_mask |= mask; + solo_reg_write(dev, SOLO_IRQ_MASK, dev->irq_mask); +} + +static inline void solo_irq_off(struct solo_dev *dev, u32 mask) +{ + dev->irq_mask &= ~mask; + solo_reg_write(dev, SOLO_IRQ_MASK, dev->irq_mask); +} + +/* Init/exit routines for subsystems */ +int solo_disp_init(struct solo_dev *solo_dev); +void solo_disp_exit(struct solo_dev *solo_dev); + +int solo_gpio_init(struct solo_dev *solo_dev); +void solo_gpio_exit(struct solo_dev *solo_dev); + +int solo_i2c_init(struct solo_dev *solo_dev); +void solo_i2c_exit(struct solo_dev *solo_dev); + +int solo_p2m_init(struct solo_dev *solo_dev); +void solo_p2m_exit(struct solo_dev *solo_dev); + +int solo_v4l2_init(struct solo_dev *solo_dev, unsigned nr); +void solo_v4l2_exit(struct solo_dev *solo_dev); + +int solo_enc_init(struct solo_dev *solo_dev); +void solo_enc_exit(struct solo_dev *solo_dev); + +int solo_enc_v4l2_init(struct solo_dev *solo_dev, unsigned nr); +void solo_enc_v4l2_exit(struct solo_dev *solo_dev); + +int solo_g723_init(struct solo_dev *solo_dev); +void solo_g723_exit(struct solo_dev *solo_dev); + +/* ISR's */ +int solo_i2c_isr(struct solo_dev *solo_dev); +void solo_p2m_isr(struct solo_dev *solo_dev, int id); +void solo_p2m_error_isr(struct solo_dev *solo_dev); +void solo_enc_v4l2_isr(struct solo_dev *solo_dev); +void solo_g723_isr(struct solo_dev *solo_dev); +void solo_motion_isr(struct solo_dev *solo_dev); +void solo_video_in_isr(struct solo_dev *solo_dev); + +/* i2c read/write */ +u8 solo_i2c_readbyte(struct solo_dev *solo_dev, int id, u8 addr, u8 off); +void solo_i2c_writebyte(struct solo_dev *solo_dev, int id, u8 addr, u8 off, + u8 data); + +/* P2M DMA */ +int solo_p2m_dma_t(struct solo_dev *solo_dev, int wr, + dma_addr_t dma_addr, u32 ext_addr, u32 size, + int repeat, u32 ext_size); +int solo_p2m_dma(struct solo_dev *solo_dev, int wr, + void *sys_addr, u32 ext_addr, u32 size, + int repeat, u32 ext_size); +void solo_p2m_fill_desc(struct solo_p2m_desc *desc, int wr, + dma_addr_t dma_addr, u32 ext_addr, u32 size, + int repeat, u32 ext_size); +int solo_p2m_dma_desc(struct solo_dev *solo_dev, + struct solo_p2m_desc *desc, dma_addr_t desc_dma, + int desc_cnt); + +/* Global s_std ioctl */ +int solo_set_video_type(struct solo_dev *solo_dev, bool is_50hz); +void solo_update_mode(struct solo_enc_dev *solo_enc); + +/* Set the threshold for motion detection */ +int solo_set_motion_threshold(struct solo_dev *solo_dev, u8 ch, u16 val); +int solo_set_motion_block(struct solo_dev *solo_dev, u8 ch, + const struct solo_motion_thresholds *thresholds); +#define SOLO_DEF_MOT_THRESH 0x0300 + +/* Write text on OSD */ +int solo_osd_print(struct solo_enc_dev *solo_enc); + +/* EEPROM commands */ +unsigned int solo_eeprom_ewen(struct solo_dev *solo_dev, int w_en); +unsigned short solo_eeprom_read(struct solo_dev *solo_dev, int loc); +int solo_eeprom_write(struct solo_dev *solo_dev, int loc, + unsigned short data); + +/* JPEG Qp functions */ +void solo_s_jpeg_qp(struct solo_dev *solo_dev, unsigned int ch, + unsigned int qp); +int solo_g_jpeg_qp(struct solo_dev *solo_dev, unsigned int ch); + +#define CHK_FLAGS(v, flags) (((v) & (flags)) == (flags)) + +#endif /* __SOLO6X10_H */ |
