]> git.proxmox.com Git - mirror_ubuntu-zesty-kernel.git/commitdiff
Merge branch 'v4l_for_linus' of git://git.kernel.org/pub/scm/linux/kernel/git/mchehab...
authorLinus Torvalds <torvalds@linux-foundation.org>
Sun, 7 Oct 2012 08:49:05 +0000 (17:49 +0900)
committerLinus Torvalds <torvalds@linux-foundation.org>
Sun, 7 Oct 2012 08:49:05 +0000 (17:49 +0900)
Pull media updates from Mauro Carvalho Chehab:
 "The first part of the media updates for Kernel 3.7.

  This series contain:

   - A major tree renaming patch series: now, drivers are organized
     internally by their used bus, instead of by V4L2 and/or DVB API,
     providing a cleaner driver location for hybrid drivers that
     implement both APIs, and allowing to cleanup the Kconfig items and
     make them more intuitive for the end user;

   - Media Kernel developers are typically very lazy with their duties
     of keeping the MAINTAINERS entries for their drivers updated.  As
     now the tree is more organized, we're doing an effort to add/update
     those entries for the drivers that aren't currently orphan;

   - Several DVB USB drivers got moved to a new DVB USB v2 core; the new
     core fixes several bugs (as the existing one that got bitroted).
     Now, suspend/resume finally started to work fine (at least with
     some devices - we should expect more work with regards to it);

   - added multistream support for DVB-T2, and unified the API for
     DVB-S2 and ISDB-S.  Backward binary support is preserved;

   - as usual, a few new drivers, some V4L2 core improvements and lots
     of drivers improvements and fixes.

  There are some points to notice on this series:

   1) you should expect a trivial merge conflict on your tree, with the
      removal of Documentation/feature-removal-schedule.txt: this series
      would be adding two additional entries there.  I opted to not
      rebase it due to this recent change;

   2) With regards to the PCTV 520e udev-related breakage, I opted to
      fix it in a way that the patches can be backported to 3.5 even
      without your firmware fix patch.  This way, Greg doesn't need to
      rush backporting your patch (as there are still the firmware cache
      and firmware path customization issues to be addressed there).

      I'll send later a patch (likely after the end of the merge window)
      reverting the rest of the DRX-K async firmware request, fully
      restoring its original behaviour to allow media drivers to
      initialize everything serialized as before for 3.7 and upper.

   3) I'm planning to work on this weekend to test the DMABUF patches
      for V4L2.  The patches are on my queue for several Kernel cycles,
      but, up to now, there is/was no way to test the series locally.

      I have some concerns about this particular changeset with regards
      to security issues, and with regards to the replacement of the old
      VIDIOC_OVERLAY ioctl's that is broken on modern systems, due to
      GPU drivers change.  The Overlay API allows direct PCI2PCI
      transfers from a media capture card into the GPU framebuffer, but
      its API is crappy.  Also, the only existing X11 driver that
      implements it requires a XV extension that is not available
      anymore on modern drivers.  The DMABUF can do the same thing, but
      with it is promising to be a properly-designed API.  If I can
      successfully test this series and be happy with it, I should be
      asking you to pull them next week."

* 'v4l_for_linus' of git://git.kernel.org/pub/scm/linux/kernel/git/mchehab/linux-media: (717 commits)
  em28xx: regression fix: use DRX-K sync firmware requests on em28xx
  drxk: allow loading firmware synchrousnously
  em28xx: Make all em28xx extensions to be initialized asynchronously
  [media] tda18271: properly report read errors in tda18271_get_id
  [media] tda18271: delay IR & RF calibration until init() if delay_cal is set
  [media] MAINTAINERS: add Michael Krufky as tda827x maintainer
  [media] MAINTAINERS: add Michael Krufky as tda8290 maintainer
  [media] MAINTAINERS: add Michael Krufky as cxusb maintainer
  [media] MAINTAINERS: add Michael Krufky as lg2160 maintainer
  [media] MAINTAINERS: add Michael Krufky as lgdt3305 maintainer
  [media] MAINTAINERS: add Michael Krufky as mxl111sf maintainer
  [media] MAINTAINERS: add Michael Krufky as mxl5007t maintainer
  [media] MAINTAINERS: add Michael Krufky as tda18271 maintainer
  [media] s5p-tv: Report only multi-plane capabilities in vidioc_querycap
  [media] s5p-mfc: Fix misplaced return statement in s5p_mfc_suspend()
  [media] exynos-gsc: Add missing static storage class specifiers
  [media] exynos-gsc: Remove <linux/version.h> header file inclusion
  [media] s5p-fimc: Fix incorrect condition in fimc_lite_reqbufs()
  [media] s5p-tv: Fix potential NULL pointer dereference error
  [media] s5k6aa: Fix possible NULL pointer dereference
  ...

27 files changed:
1  2 
MAINTAINERS
arch/arm/mach-omap2/board-rx51-peripherals.c
arch/arm/plat-mxc/include/mach/devices-common.h
drivers/gpio/gpio-bt8xx.c
drivers/media/dvb-core/dvb_net.c
drivers/media/pci/bt8xx/bttv-driver.c
drivers/media/pci/cx18/cx18-driver.c
drivers/media/pci/cx23885/cx23885-input.c
drivers/media/pci/cx88/cx88-mpeg.c
drivers/media/pci/mantis/mantis_evm.c
drivers/media/pci/mantis/mantis_uart.c
drivers/media/pci/ngene/ngene-cards.c
drivers/media/pci/saa7134/saa7134-core.c
drivers/media/pci/saa7134/saa7134-empress.c
drivers/media/platform/davinci/vpbe_venc.c
drivers/media/platform/omap/omap_vout.c
drivers/media/platform/omap24xxcam.c
drivers/media/platform/omap3isp/isp.c
drivers/media/platform/s5p-fimc/mipi-csis.c
drivers/media/platform/soc_camera/mx1_camera.c
drivers/media/platform/soc_camera/mx2_camera.c
drivers/media/platform/soc_camera/mx3_camera.c
drivers/media/platform/soc_camera/pxa_camera.c
drivers/media/usb/cx231xx/cx231xx-cards.c
drivers/media/usb/em28xx/em28xx-cards.c
drivers/media/usb/tm6000/tm6000-cards.c
include/linux/Kbuild

diff --cc MAINTAINERS
Simple merge
index 3945c5017085abdb72cfd61532fb33c864df23c4,ca07264bd3ae7633d370e3ba9047988fd8153bf4..ed85fb898c7fd62b8526d0bf1e0bea1d23ff26fd
  #include "common.h"
  #include <plat/dma.h>
  #include <plat/gpmc.h>
 -#include <plat/onenand.h>
 -#include <plat/gpmc-smc91x.h>
+ #include <plat/omap-pm.h>
 +#include "gpmc-smc91x.h"
  
 -#include <mach/board-rx51.h>
 +#include "board-rx51.h"
  
  #include <sound/tlv320aic3x.h>
  #include <sound/tpa6130a2-plat.h>
Simple merge
index 0000000000000000000000000000000000000000,8766ce8c354dd908e1fbb9a493942e8669fe4259..c2117688aa23d8aad589d28a1e7a4cc1e0333024
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,1516 +1,1516 @@@
 -      flush_work_sync(&priv->set_multicast_list_wq);
 -      flush_work_sync(&priv->restart_net_feed_wq);
+ /*
+  * dvb_net.c
+  *
+  * Copyright (C) 2001 Convergence integrated media GmbH
+  *                    Ralph Metzler <ralph@convergence.de>
+  * Copyright (C) 2002 Ralph Metzler <rjkm@metzlerbros.de>
+  *
+  * ULE Decapsulation code:
+  * Copyright (C) 2003, 2004 gcs - Global Communication & Services GmbH.
+  *                      and Department of Scientific Computing
+  *                          Paris Lodron University of Salzburg.
+  *                          Hilmar Linder <hlinder@cosy.sbg.ac.at>
+  *                      and Wolfram Stering <wstering@cosy.sbg.ac.at>
+  *
+  * ULE Decaps according to RFC 4326.
+  *
+  * 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.
+  * Or, point your browser to http://www.gnu.org/copyleft/gpl.html
+  */
+ /*
+  * ULE ChangeLog:
+  * Feb 2004: hl/ws v1: Implementing draft-fair-ipdvb-ule-01.txt
+  *
+  * Dec 2004: hl/ws v2: Implementing draft-ietf-ipdvb-ule-03.txt:
+  *                       ULE Extension header handling.
+  *                     Bugreports by Moritz Vieth and Hanno Tersteegen,
+  *                       Fraunhofer Institute for Open Communication Systems
+  *                       Competence Center for Advanced Satellite Communications.
+  *                     Bugfixes and robustness improvements.
+  *                     Filtering on dest MAC addresses, if present (D-Bit = 0)
+  *                     ULE_DEBUG compile-time option.
+  * Apr 2006: cp v3:    Bugfixes and compliency with RFC 4326 (ULE) by
+  *                       Christian Praehauser <cpraehaus@cosy.sbg.ac.at>,
+  *                       Paris Lodron University of Salzburg.
+  */
+ /*
+  * FIXME / TODO (dvb_net.c):
+  *
+  * Unloading does not work for 2.6.9 kernels: a refcount doesn't go to zero.
+  *
+  */
+ #include <linux/module.h>
+ #include <linux/kernel.h>
+ #include <linux/netdevice.h>
+ #include <linux/etherdevice.h>
+ #include <linux/dvb/net.h>
+ #include <linux/uio.h>
+ #include <asm/uaccess.h>
+ #include <linux/crc32.h>
+ #include <linux/mutex.h>
+ #include <linux/sched.h>
+ #include "dvb_demux.h"
+ #include "dvb_net.h"
+ static int dvb_net_debug;
+ module_param(dvb_net_debug, int, 0444);
+ MODULE_PARM_DESC(dvb_net_debug, "enable debug messages");
+ #define dprintk(x...) do { if (dvb_net_debug) printk(x); } while (0)
+ static inline __u32 iov_crc32( __u32 c, struct kvec *iov, unsigned int cnt )
+ {
+       unsigned int j;
+       for (j = 0; j < cnt; j++)
+               c = crc32_be( c, iov[j].iov_base, iov[j].iov_len );
+       return c;
+ }
+ #define DVB_NET_MULTICAST_MAX 10
+ #undef ULE_DEBUG
+ #ifdef ULE_DEBUG
+ #define MAC_ADDR_PRINTFMT "%.2x:%.2x:%.2x:%.2x:%.2x:%.2x"
+ #define MAX_ADDR_PRINTFMT_ARGS(macap) (macap)[0],(macap)[1],(macap)[2],(macap)[3],(macap)[4],(macap)[5]
+ #define isprint(c)    ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9'))
+ static void hexdump( const unsigned char *buf, unsigned short len )
+ {
+       char str[80], octet[10];
+       int ofs, i, l;
+       for (ofs = 0; ofs < len; ofs += 16) {
+               sprintf( str, "%03d: ", ofs );
+               for (i = 0; i < 16; i++) {
+                       if ((i + ofs) < len)
+                               sprintf( octet, "%02x ", buf[ofs + i] );
+                       else
+                               strcpy( octet, "   " );
+                       strcat( str, octet );
+               }
+               strcat( str, "  " );
+               l = strlen( str );
+               for (i = 0; (i < 16) && ((i + ofs) < len); i++)
+                       str[l++] = isprint( buf[ofs + i] ) ? buf[ofs + i] : '.';
+               str[l] = '\0';
+               printk( KERN_WARNING "%s\n", str );
+       }
+ }
+ #endif
+ struct dvb_net_priv {
+       int in_use;
+       u16 pid;
+       struct net_device *net;
+       struct dvb_net *host;
+       struct dmx_demux *demux;
+       struct dmx_section_feed *secfeed;
+       struct dmx_section_filter *secfilter;
+       struct dmx_ts_feed *tsfeed;
+       int multi_num;
+       struct dmx_section_filter *multi_secfilter[DVB_NET_MULTICAST_MAX];
+       unsigned char multi_macs[DVB_NET_MULTICAST_MAX][6];
+       int rx_mode;
+ #define RX_MODE_UNI 0
+ #define RX_MODE_MULTI 1
+ #define RX_MODE_ALL_MULTI 2
+ #define RX_MODE_PROMISC 3
+       struct work_struct set_multicast_list_wq;
+       struct work_struct restart_net_feed_wq;
+       unsigned char feedtype;                 /* Either FEED_TYPE_ or FEED_TYPE_ULE */
+       int need_pusi;                          /* Set to 1, if synchronization on PUSI required. */
+       unsigned char tscc;                     /* TS continuity counter after sync on PUSI. */
+       struct sk_buff *ule_skb;                /* ULE SNDU decodes into this buffer. */
+       unsigned char *ule_next_hdr;            /* Pointer into skb to next ULE extension header. */
+       unsigned short ule_sndu_len;            /* ULE SNDU length in bytes, w/o D-Bit. */
+       unsigned short ule_sndu_type;           /* ULE SNDU type field, complete. */
+       unsigned char ule_sndu_type_1;          /* ULE SNDU type field, if split across 2 TS cells. */
+       unsigned char ule_dbit;                 /* Whether the DestMAC address present
+                                                * or not (bit is set). */
+       unsigned char ule_bridged;              /* Whether the ULE_BRIDGED extension header was found. */
+       int ule_sndu_remain;                    /* Nr. of bytes still required for current ULE SNDU. */
+       unsigned long ts_count;                 /* Current ts cell counter. */
+       struct mutex mutex;
+ };
+ /**
+  *    Determine the packet's protocol ID. The rule here is that we
+  *    assume 802.3 if the type field is short enough to be a length.
+  *    This is normal practice and works for any 'now in use' protocol.
+  *
+  *  stolen from eth.c out of the linux kernel, hacked for dvb-device
+  *  by Michael Holzt <kju@debian.org>
+  */
+ static __be16 dvb_net_eth_type_trans(struct sk_buff *skb,
+                                     struct net_device *dev)
+ {
+       struct ethhdr *eth;
+       unsigned char *rawp;
+       skb_reset_mac_header(skb);
+       skb_pull(skb,dev->hard_header_len);
+       eth = eth_hdr(skb);
+       if (*eth->h_dest & 1) {
+               if(memcmp(eth->h_dest,dev->broadcast, ETH_ALEN)==0)
+                       skb->pkt_type=PACKET_BROADCAST;
+               else
+                       skb->pkt_type=PACKET_MULTICAST;
+       }
+       if (ntohs(eth->h_proto) >= 1536)
+               return eth->h_proto;
+       rawp = skb->data;
+       /**
+        *      This is a magic hack to spot IPX packets. Older Novell breaks
+        *      the protocol design and runs IPX over 802.3 without an 802.2 LLC
+        *      layer. We look for FFFF which isn't a used 802.2 SSAP/DSAP. This
+        *      won't work for fault tolerant netware but does for the rest.
+        */
+       if (*(unsigned short *)rawp == 0xFFFF)
+               return htons(ETH_P_802_3);
+       /**
+        *      Real 802.2 LLC
+        */
+       return htons(ETH_P_802_2);
+ }
+ #define TS_SZ 188
+ #define TS_SYNC       0x47
+ #define TS_TEI        0x80
+ #define TS_SC 0xC0
+ #define TS_PUSI       0x40
+ #define TS_AF_A       0x20
+ #define TS_AF_D       0x10
+ /* ULE Extension Header handlers. */
+ #define ULE_TEST      0
+ #define ULE_BRIDGED   1
+ #define ULE_OPTEXTHDR_PADDING 0
+ static int ule_test_sndu( struct dvb_net_priv *p )
+ {
+       return -1;
+ }
+ static int ule_bridged_sndu( struct dvb_net_priv *p )
+ {
+       struct ethhdr *hdr = (struct ethhdr*) p->ule_next_hdr;
+       if(ntohs(hdr->h_proto) < 1536) {
+               int framelen = p->ule_sndu_len - ((p->ule_next_hdr+sizeof(struct ethhdr)) - p->ule_skb->data);
+               /* A frame Type < 1536 for a bridged frame, introduces a LLC Length field. */
+               if(framelen != ntohs(hdr->h_proto)) {
+                       return -1;
+               }
+       }
+       /* Note:
+        * From RFC4326:
+        *  "A bridged SNDU is a Mandatory Extension Header of Type 1.
+        *   It must be the final (or only) extension header specified in the header chain of a SNDU."
+        * The 'ule_bridged' flag will cause the extension header processing loop to terminate.
+        */
+       p->ule_bridged = 1;
+       return 0;
+ }
+ static int ule_exthdr_padding(struct dvb_net_priv *p)
+ {
+       return 0;
+ }
+ /** Handle ULE extension headers.
+  *  Function is called after a successful CRC32 verification of an ULE SNDU to complete its decoding.
+  *  Returns: >= 0: nr. of bytes consumed by next extension header
+  *         -1:   Mandatory extension header that is not recognized or TEST SNDU; discard.
+  */
+ static int handle_one_ule_extension( struct dvb_net_priv *p )
+ {
+       /* Table of mandatory extension header handlers.  The header type is the index. */
+       static int (*ule_mandatory_ext_handlers[255])( struct dvb_net_priv *p ) =
+               { [0] = ule_test_sndu, [1] = ule_bridged_sndu, [2] = NULL,  };
+       /* Table of optional extension header handlers.  The header type is the index. */
+       static int (*ule_optional_ext_handlers[255])( struct dvb_net_priv *p ) =
+               { [0] = ule_exthdr_padding, [1] = NULL, };
+       int ext_len = 0;
+       unsigned char hlen = (p->ule_sndu_type & 0x0700) >> 8;
+       unsigned char htype = p->ule_sndu_type & 0x00FF;
+       /* Discriminate mandatory and optional extension headers. */
+       if (hlen == 0) {
+               /* Mandatory extension header */
+               if (ule_mandatory_ext_handlers[htype]) {
+                       ext_len = ule_mandatory_ext_handlers[htype]( p );
+                       if(ext_len >= 0) {
+                               p->ule_next_hdr += ext_len;
+                               if (!p->ule_bridged) {
+                                       p->ule_sndu_type = ntohs(*(__be16 *)p->ule_next_hdr);
+                                       p->ule_next_hdr += 2;
+                               } else {
+                                       p->ule_sndu_type = ntohs(*(__be16 *)(p->ule_next_hdr + ((p->ule_dbit ? 2 : 3) * ETH_ALEN)));
+                                       /* This assures the extension handling loop will terminate. */
+                               }
+                       }
+                       // else: extension handler failed or SNDU should be discarded
+               } else
+                       ext_len = -1;   /* SNDU has to be discarded. */
+       } else {
+               /* Optional extension header.  Calculate the length. */
+               ext_len = hlen << 1;
+               /* Process the optional extension header according to its type. */
+               if (ule_optional_ext_handlers[htype])
+                       (void)ule_optional_ext_handlers[htype]( p );
+               p->ule_next_hdr += ext_len;
+               p->ule_sndu_type = ntohs( *(__be16 *)(p->ule_next_hdr-2) );
+               /*
+                * note: the length of the next header type is included in the
+                * length of THIS optional extension header
+                */
+       }
+       return ext_len;
+ }
+ static int handle_ule_extensions( struct dvb_net_priv *p )
+ {
+       int total_ext_len = 0, l;
+       p->ule_next_hdr = p->ule_skb->data;
+       do {
+               l = handle_one_ule_extension( p );
+               if (l < 0)
+                       return l;       /* Stop extension header processing and discard SNDU. */
+               total_ext_len += l;
+ #ifdef ULE_DEBUG
+               dprintk("handle_ule_extensions: ule_next_hdr=%p, ule_sndu_type=%i, "
+                       "l=%i, total_ext_len=%i\n", p->ule_next_hdr,
+                       (int) p->ule_sndu_type, l, total_ext_len);
+ #endif
+       } while (p->ule_sndu_type < 1536);
+       return total_ext_len;
+ }
+ /** Prepare for a new ULE SNDU: reset the decoder state. */
+ static inline void reset_ule( struct dvb_net_priv *p )
+ {
+       p->ule_skb = NULL;
+       p->ule_next_hdr = NULL;
+       p->ule_sndu_len = 0;
+       p->ule_sndu_type = 0;
+       p->ule_sndu_type_1 = 0;
+       p->ule_sndu_remain = 0;
+       p->ule_dbit = 0xFF;
+       p->ule_bridged = 0;
+ }
+ /**
+  * Decode ULE SNDUs according to draft-ietf-ipdvb-ule-03.txt from a sequence of
+  * TS cells of a single PID.
+  */
+ static void dvb_net_ule( struct net_device *dev, const u8 *buf, size_t buf_len )
+ {
+       struct dvb_net_priv *priv = netdev_priv(dev);
+       unsigned long skipped = 0L;
+       const u8 *ts, *ts_end, *from_where = NULL;
+       u8 ts_remain = 0, how_much = 0, new_ts = 1;
+       struct ethhdr *ethh = NULL;
+       bool error = false;
+ #ifdef ULE_DEBUG
+       /* The code inside ULE_DEBUG keeps a history of the last 100 TS cells processed. */
+       static unsigned char ule_hist[100*TS_SZ];
+       static unsigned char *ule_where = ule_hist, ule_dump;
+ #endif
+       /* For all TS cells in current buffer.
+        * Appearently, we are called for every single TS cell.
+        */
+       for (ts = buf, ts_end = buf + buf_len; ts < ts_end; /* no default incr. */ ) {
+               if (new_ts) {
+                       /* We are about to process a new TS cell. */
+ #ifdef ULE_DEBUG
+                       if (ule_where >= &ule_hist[100*TS_SZ]) ule_where = ule_hist;
+                       memcpy( ule_where, ts, TS_SZ );
+                       if (ule_dump) {
+                               hexdump( ule_where, TS_SZ );
+                               ule_dump = 0;
+                       }
+                       ule_where += TS_SZ;
+ #endif
+                       /* Check TS error conditions: sync_byte, transport_error_indicator, scrambling_control . */
+                       if ((ts[0] != TS_SYNC) || (ts[1] & TS_TEI) || ((ts[3] & TS_SC) != 0)) {
+                               printk(KERN_WARNING "%lu: Invalid TS cell: SYNC %#x, TEI %u, SC %#x.\n",
+                                      priv->ts_count, ts[0], ts[1] & TS_TEI >> 7, ts[3] & 0xC0 >> 6);
+                               /* Drop partly decoded SNDU, reset state, resync on PUSI. */
+                               if (priv->ule_skb) {
+                                       dev_kfree_skb( priv->ule_skb );
+                                       /* Prepare for next SNDU. */
+                                       dev->stats.rx_errors++;
+                                       dev->stats.rx_frame_errors++;
+                               }
+                               reset_ule(priv);
+                               priv->need_pusi = 1;
+                               /* Continue with next TS cell. */
+                               ts += TS_SZ;
+                               priv->ts_count++;
+                               continue;
+                       }
+                       ts_remain = 184;
+                       from_where = ts + 4;
+               }
+               /* Synchronize on PUSI, if required. */
+               if (priv->need_pusi) {
+                       if (ts[1] & TS_PUSI) {
+                               /* Find beginning of first ULE SNDU in current TS cell. */
+                               /* Synchronize continuity counter. */
+                               priv->tscc = ts[3] & 0x0F;
+                               /* There is a pointer field here. */
+                               if (ts[4] > ts_remain) {
+                                       printk(KERN_ERR "%lu: Invalid ULE packet "
+                                              "(pointer field %d)\n", priv->ts_count, ts[4]);
+                                       ts += TS_SZ;
+                                       priv->ts_count++;
+                                       continue;
+                               }
+                               /* Skip to destination of pointer field. */
+                               from_where = &ts[5] + ts[4];
+                               ts_remain -= 1 + ts[4];
+                               skipped = 0;
+                       } else {
+                               skipped++;
+                               ts += TS_SZ;
+                               priv->ts_count++;
+                               continue;
+                       }
+               }
+               if (new_ts) {
+                       /* Check continuity counter. */
+                       if ((ts[3] & 0x0F) == priv->tscc)
+                               priv->tscc = (priv->tscc + 1) & 0x0F;
+                       else {
+                               /* TS discontinuity handling: */
+                               printk(KERN_WARNING "%lu: TS discontinuity: got %#x, "
+                                      "expected %#x.\n", priv->ts_count, ts[3] & 0x0F, priv->tscc);
+                               /* Drop partly decoded SNDU, reset state, resync on PUSI. */
+                               if (priv->ule_skb) {
+                                       dev_kfree_skb( priv->ule_skb );
+                                       /* Prepare for next SNDU. */
+                                       // reset_ule(priv);  moved to below.
+                                       dev->stats.rx_errors++;
+                                       dev->stats.rx_frame_errors++;
+                               }
+                               reset_ule(priv);
+                               /* skip to next PUSI. */
+                               priv->need_pusi = 1;
+                               continue;
+                       }
+                       /* If we still have an incomplete payload, but PUSI is
+                        * set; some TS cells are missing.
+                        * This is only possible here, if we missed exactly 16 TS
+                        * cells (continuity counter wrap). */
+                       if (ts[1] & TS_PUSI) {
+                               if (! priv->need_pusi) {
+                                       if (!(*from_where < (ts_remain-1)) || *from_where != priv->ule_sndu_remain) {
+                                               /* Pointer field is invalid.  Drop this TS cell and any started ULE SNDU. */
+                                               printk(KERN_WARNING "%lu: Invalid pointer "
+                                                      "field: %u.\n", priv->ts_count, *from_where);
+                                               /* Drop partly decoded SNDU, reset state, resync on PUSI. */
+                                               if (priv->ule_skb) {
+                                                       error = true;
+                                                       dev_kfree_skb(priv->ule_skb);
+                                               }
+                                               if (error || priv->ule_sndu_remain) {
+                                                       dev->stats.rx_errors++;
+                                                       dev->stats.rx_frame_errors++;
+                                                       error = false;
+                                               }
+                                               reset_ule(priv);
+                                               priv->need_pusi = 1;
+                                               continue;
+                                       }
+                                       /* Skip pointer field (we're processing a
+                                        * packed payload). */
+                                       from_where += 1;
+                                       ts_remain -= 1;
+                               } else
+                                       priv->need_pusi = 0;
+                               if (priv->ule_sndu_remain > 183) {
+                                       /* Current SNDU lacks more data than there could be available in the
+                                        * current TS cell. */
+                                       dev->stats.rx_errors++;
+                                       dev->stats.rx_length_errors++;
+                                       printk(KERN_WARNING "%lu: Expected %d more SNDU bytes, but "
+                                              "got PUSI (pf %d, ts_remain %d).  Flushing incomplete payload.\n",
+                                              priv->ts_count, priv->ule_sndu_remain, ts[4], ts_remain);
+                                       dev_kfree_skb(priv->ule_skb);
+                                       /* Prepare for next SNDU. */
+                                       reset_ule(priv);
+                                       /* Resync: go to where pointer field points to: start of next ULE SNDU. */
+                                       from_where += ts[4];
+                                       ts_remain -= ts[4];
+                               }
+                       }
+               }
+               /* Check if new payload needs to be started. */
+               if (priv->ule_skb == NULL) {
+                       /* Start a new payload with skb.
+                        * Find ULE header.  It is only guaranteed that the
+                        * length field (2 bytes) is contained in the current
+                        * TS.
+                        * Check ts_remain has to be >= 2 here. */
+                       if (ts_remain < 2) {
+                               printk(KERN_WARNING "Invalid payload packing: only %d "
+                                      "bytes left in TS.  Resyncing.\n", ts_remain);
+                               priv->ule_sndu_len = 0;
+                               priv->need_pusi = 1;
+                               ts += TS_SZ;
+                               continue;
+                       }
+                       if (! priv->ule_sndu_len) {
+                               /* Got at least two bytes, thus extrace the SNDU length. */
+                               priv->ule_sndu_len = from_where[0] << 8 | from_where[1];
+                               if (priv->ule_sndu_len & 0x8000) {
+                                       /* D-Bit is set: no dest mac present. */
+                                       priv->ule_sndu_len &= 0x7FFF;
+                                       priv->ule_dbit = 1;
+                               } else
+                                       priv->ule_dbit = 0;
+                               if (priv->ule_sndu_len < 5) {
+                                       printk(KERN_WARNING "%lu: Invalid ULE SNDU length %u. "
+                                              "Resyncing.\n", priv->ts_count, priv->ule_sndu_len);
+                                       dev->stats.rx_errors++;
+                                       dev->stats.rx_length_errors++;
+                                       priv->ule_sndu_len = 0;
+                                       priv->need_pusi = 1;
+                                       new_ts = 1;
+                                       ts += TS_SZ;
+                                       priv->ts_count++;
+                                       continue;
+                               }
+                               ts_remain -= 2; /* consume the 2 bytes SNDU length. */
+                               from_where += 2;
+                       }
+                       priv->ule_sndu_remain = priv->ule_sndu_len + 2;
+                       /*
+                        * State of current TS:
+                        *   ts_remain (remaining bytes in the current TS cell)
+                        *   0  ule_type is not available now, we need the next TS cell
+                        *   1  the first byte of the ule_type is present
+                        * >=2  full ULE header present, maybe some payload data as well.
+                        */
+                       switch (ts_remain) {
+                               case 1:
+                                       priv->ule_sndu_remain--;
+                                       priv->ule_sndu_type = from_where[0] << 8;
+                                       priv->ule_sndu_type_1 = 1; /* first byte of ule_type is set. */
+                                       ts_remain -= 1; from_where += 1;
+                                       /* Continue w/ next TS. */
+                               case 0:
+                                       new_ts = 1;
+                                       ts += TS_SZ;
+                                       priv->ts_count++;
+                                       continue;
+                               default: /* complete ULE header is present in current TS. */
+                                       /* Extract ULE type field. */
+                                       if (priv->ule_sndu_type_1) {
+                                               priv->ule_sndu_type_1 = 0;
+                                               priv->ule_sndu_type |= from_where[0];
+                                               from_where += 1; /* points to payload start. */
+                                               ts_remain -= 1;
+                                       } else {
+                                               /* Complete type is present in new TS. */
+                                               priv->ule_sndu_type = from_where[0] << 8 | from_where[1];
+                                               from_where += 2; /* points to payload start. */
+                                               ts_remain -= 2;
+                                       }
+                                       break;
+                       }
+                       /* Allocate the skb (decoder target buffer) with the correct size, as follows:
+                        * prepare for the largest case: bridged SNDU with MAC address (dbit = 0). */
+                       priv->ule_skb = dev_alloc_skb( priv->ule_sndu_len + ETH_HLEN + ETH_ALEN );
+                       if (priv->ule_skb == NULL) {
+                               printk(KERN_NOTICE "%s: Memory squeeze, dropping packet.\n",
+                                      dev->name);
+                               dev->stats.rx_dropped++;
+                               return;
+                       }
+                       /* This includes the CRC32 _and_ dest mac, if !dbit. */
+                       priv->ule_sndu_remain = priv->ule_sndu_len;
+                       priv->ule_skb->dev = dev;
+                       /* Leave space for Ethernet or bridged SNDU header (eth hdr plus one MAC addr). */
+                       skb_reserve( priv->ule_skb, ETH_HLEN + ETH_ALEN );
+               }
+               /* Copy data into our current skb. */
+               how_much = min(priv->ule_sndu_remain, (int)ts_remain);
+               memcpy(skb_put(priv->ule_skb, how_much), from_where, how_much);
+               priv->ule_sndu_remain -= how_much;
+               ts_remain -= how_much;
+               from_where += how_much;
+               /* Check for complete payload. */
+               if (priv->ule_sndu_remain <= 0) {
+                       /* Check CRC32, we've got it in our skb already. */
+                       __be16 ulen = htons(priv->ule_sndu_len);
+                       __be16 utype = htons(priv->ule_sndu_type);
+                       const u8 *tail;
+                       struct kvec iov[3] = {
+                               { &ulen, sizeof ulen },
+                               { &utype, sizeof utype },
+                               { priv->ule_skb->data, priv->ule_skb->len - 4 }
+                       };
+                       u32 ule_crc = ~0L, expected_crc;
+                       if (priv->ule_dbit) {
+                               /* Set D-bit for CRC32 verification,
+                                * if it was set originally. */
+                               ulen |= htons(0x8000);
+                       }
+                       ule_crc = iov_crc32(ule_crc, iov, 3);
+                       tail = skb_tail_pointer(priv->ule_skb);
+                       expected_crc = *(tail - 4) << 24 |
+                                      *(tail - 3) << 16 |
+                                      *(tail - 2) << 8 |
+                                      *(tail - 1);
+                       if (ule_crc != expected_crc) {
+                               printk(KERN_WARNING "%lu: CRC32 check FAILED: %08x / %08x, SNDU len %d type %#x, ts_remain %d, next 2: %x.\n",
+                                      priv->ts_count, ule_crc, expected_crc, priv->ule_sndu_len, priv->ule_sndu_type, ts_remain, ts_remain > 2 ? *(unsigned short *)from_where : 0);
+ #ifdef ULE_DEBUG
+                               hexdump( iov[0].iov_base, iov[0].iov_len );
+                               hexdump( iov[1].iov_base, iov[1].iov_len );
+                               hexdump( iov[2].iov_base, iov[2].iov_len );
+                               if (ule_where == ule_hist) {
+                                       hexdump( &ule_hist[98*TS_SZ], TS_SZ );
+                                       hexdump( &ule_hist[99*TS_SZ], TS_SZ );
+                               } else if (ule_where == &ule_hist[TS_SZ]) {
+                                       hexdump( &ule_hist[99*TS_SZ], TS_SZ );
+                                       hexdump( ule_hist, TS_SZ );
+                               } else {
+                                       hexdump( ule_where - TS_SZ - TS_SZ, TS_SZ );
+                                       hexdump( ule_where - TS_SZ, TS_SZ );
+                               }
+                               ule_dump = 1;
+ #endif
+                               dev->stats.rx_errors++;
+                               dev->stats.rx_crc_errors++;
+                               dev_kfree_skb(priv->ule_skb);
+                       } else {
+                               /* CRC32 verified OK. */
+                               u8 dest_addr[ETH_ALEN];
+                               static const u8 bc_addr[ETH_ALEN] =
+                                       { [ 0 ... ETH_ALEN-1] = 0xff };
+                               /* CRC32 was OK. Remove it from skb. */
+                               priv->ule_skb->tail -= 4;
+                               priv->ule_skb->len -= 4;
+                               if (!priv->ule_dbit) {
+                                       /*
+                                        * The destination MAC address is the
+                                        * next data in the skb.  It comes
+                                        * before any extension headers.
+                                        *
+                                        * Check if the payload of this SNDU
+                                        * should be passed up the stack.
+                                        */
+                                       register int drop = 0;
+                                       if (priv->rx_mode != RX_MODE_PROMISC) {
+                                               if (priv->ule_skb->data[0] & 0x01) {
+                                                       /* multicast or broadcast */
+                                                       if (memcmp(priv->ule_skb->data, bc_addr, ETH_ALEN)) {
+                                                               /* multicast */
+                                                               if (priv->rx_mode == RX_MODE_MULTI) {
+                                                                       int i;
+                                                                       for(i = 0; i < priv->multi_num && memcmp(priv->ule_skb->data, priv->multi_macs[i], ETH_ALEN); i++)
+                                                                               ;
+                                                                       if (i == priv->multi_num)
+                                                                               drop = 1;
+                                                               } else if (priv->rx_mode != RX_MODE_ALL_MULTI)
+                                                                       drop = 1; /* no broadcast; */
+                                                               /* else: all multicast mode: accept all multicast packets */
+                                                       }
+                                                       /* else: broadcast */
+                                               }
+                                               else if (memcmp(priv->ule_skb->data, dev->dev_addr, ETH_ALEN))
+                                                       drop = 1;
+                                               /* else: destination address matches the MAC address of our receiver device */
+                                       }
+                                       /* else: promiscuous mode; pass everything up the stack */
+                                       if (drop) {
+ #ifdef ULE_DEBUG
+                                               dprintk("Dropping SNDU: MAC destination address does not match: dest addr: "MAC_ADDR_PRINTFMT", dev addr: "MAC_ADDR_PRINTFMT"\n",
+                                                       MAX_ADDR_PRINTFMT_ARGS(priv->ule_skb->data), MAX_ADDR_PRINTFMT_ARGS(dev->dev_addr));
+ #endif
+                                               dev_kfree_skb(priv->ule_skb);
+                                               goto sndu_done;
+                                       }
+                                       else
+                                       {
+                                               skb_copy_from_linear_data(priv->ule_skb,
+                                                             dest_addr,
+                                                             ETH_ALEN);
+                                               skb_pull(priv->ule_skb, ETH_ALEN);
+                                       }
+                               }
+                               /* Handle ULE Extension Headers. */
+                               if (priv->ule_sndu_type < 1536) {
+                                       /* There is an extension header.  Handle it accordingly. */
+                                       int l = handle_ule_extensions(priv);
+                                       if (l < 0) {
+                                               /* Mandatory extension header unknown or TEST SNDU.  Drop it. */
+                                               // printk( KERN_WARNING "Dropping SNDU, extension headers.\n" );
+                                               dev_kfree_skb(priv->ule_skb);
+                                               goto sndu_done;
+                                       }
+                                       skb_pull(priv->ule_skb, l);
+                               }
+                               /*
+                                * Construct/assure correct ethernet header.
+                                * Note: in bridged mode (priv->ule_bridged !=
+                                * 0) we already have the (original) ethernet
+                                * header at the start of the payload (after
+                                * optional dest. address and any extension
+                                * headers).
+                                */
+                               if (!priv->ule_bridged) {
+                                       skb_push(priv->ule_skb, ETH_HLEN);
+                                       ethh = (struct ethhdr *)priv->ule_skb->data;
+                                       if (!priv->ule_dbit) {
+                                                /* dest_addr buffer is only valid if priv->ule_dbit == 0 */
+                                               memcpy(ethh->h_dest, dest_addr, ETH_ALEN);
+                                               memset(ethh->h_source, 0, ETH_ALEN);
+                                       }
+                                       else /* zeroize source and dest */
+                                               memset( ethh, 0, ETH_ALEN*2 );
+                                       ethh->h_proto = htons(priv->ule_sndu_type);
+                               }
+                               /* else:  skb is in correct state; nothing to do. */
+                               priv->ule_bridged = 0;
+                               /* Stuff into kernel's protocol stack. */
+                               priv->ule_skb->protocol = dvb_net_eth_type_trans(priv->ule_skb, dev);
+                               /* If D-bit is set (i.e. destination MAC address not present),
+                                * receive the packet anyhow. */
+                               /* if (priv->ule_dbit && skb->pkt_type == PACKET_OTHERHOST)
+                                       priv->ule_skb->pkt_type = PACKET_HOST; */
+                               dev->stats.rx_packets++;
+                               dev->stats.rx_bytes += priv->ule_skb->len;
+                               netif_rx(priv->ule_skb);
+                       }
+                       sndu_done:
+                       /* Prepare for next SNDU. */
+                       reset_ule(priv);
+               }
+               /* More data in current TS (look at the bytes following the CRC32)? */
+               if (ts_remain >= 2 && *((unsigned short *)from_where) != 0xFFFF) {
+                       /* Next ULE SNDU starts right there. */
+                       new_ts = 0;
+                       priv->ule_skb = NULL;
+                       priv->ule_sndu_type_1 = 0;
+                       priv->ule_sndu_len = 0;
+                       // printk(KERN_WARNING "More data in current TS: [%#x %#x %#x %#x]\n",
+                       //      *(from_where + 0), *(from_where + 1),
+                       //      *(from_where + 2), *(from_where + 3));
+                       // printk(KERN_WARNING "ts @ %p, stopped @ %p:\n", ts, from_where + 0);
+                       // hexdump(ts, 188);
+               } else {
+                       new_ts = 1;
+                       ts += TS_SZ;
+                       priv->ts_count++;
+                       if (priv->ule_skb == NULL) {
+                               priv->need_pusi = 1;
+                               priv->ule_sndu_type_1 = 0;
+                               priv->ule_sndu_len = 0;
+                       }
+               }
+       }       /* for all available TS cells */
+ }
+ static int dvb_net_ts_callback(const u8 *buffer1, size_t buffer1_len,
+                              const u8 *buffer2, size_t buffer2_len,
+                              struct dmx_ts_feed *feed, enum dmx_success success)
+ {
+       struct net_device *dev = feed->priv;
+       if (buffer2)
+               printk(KERN_WARNING "buffer2 not NULL: %p.\n", buffer2);
+       if (buffer1_len > 32768)
+               printk(KERN_WARNING "length > 32k: %zu.\n", buffer1_len);
+       /* printk("TS callback: %u bytes, %u TS cells @ %p.\n",
+                 buffer1_len, buffer1_len / TS_SZ, buffer1); */
+       dvb_net_ule(dev, buffer1, buffer1_len);
+       return 0;
+ }
+ static void dvb_net_sec(struct net_device *dev,
+                       const u8 *pkt, int pkt_len)
+ {
+       u8 *eth;
+       struct sk_buff *skb;
+       struct net_device_stats *stats = &dev->stats;
+       int snap = 0;
+       /* note: pkt_len includes a 32bit checksum */
+       if (pkt_len < 16) {
+               printk("%s: IP/MPE packet length = %d too small.\n",
+                       dev->name, pkt_len);
+               stats->rx_errors++;
+               stats->rx_length_errors++;
+               return;
+       }
+ /* it seems some ISPs manage to screw up here, so we have to
+  * relax the error checks... */
+ #if 0
+       if ((pkt[5] & 0xfd) != 0xc1) {
+               /* drop scrambled or broken packets */
+ #else
+       if ((pkt[5] & 0x3c) != 0x00) {
+               /* drop scrambled */
+ #endif
+               stats->rx_errors++;
+               stats->rx_crc_errors++;
+               return;
+       }
+       if (pkt[5] & 0x02) {
+               /* handle LLC/SNAP, see rfc-1042 */
+               if (pkt_len < 24 || memcmp(&pkt[12], "\xaa\xaa\x03\0\0\0", 6)) {
+                       stats->rx_dropped++;
+                       return;
+               }
+               snap = 8;
+       }
+       if (pkt[7]) {
+               /* FIXME: assemble datagram from multiple sections */
+               stats->rx_errors++;
+               stats->rx_frame_errors++;
+               return;
+       }
+       /* we have 14 byte ethernet header (ip header follows);
+        * 12 byte MPE header; 4 byte checksum; + 2 byte alignment, 8 byte LLC/SNAP
+        */
+       if (!(skb = dev_alloc_skb(pkt_len - 4 - 12 + 14 + 2 - snap))) {
+               //printk(KERN_NOTICE "%s: Memory squeeze, dropping packet.\n", dev->name);
+               stats->rx_dropped++;
+               return;
+       }
+       skb_reserve(skb, 2);    /* longword align L3 header */
+       skb->dev = dev;
+       /* copy L3 payload */
+       eth = (u8 *) skb_put(skb, pkt_len - 12 - 4 + 14 - snap);
+       memcpy(eth + 14, pkt + 12 + snap, pkt_len - 12 - 4 - snap);
+       /* create ethernet header: */
+       eth[0]=pkt[0x0b];
+       eth[1]=pkt[0x0a];
+       eth[2]=pkt[0x09];
+       eth[3]=pkt[0x08];
+       eth[4]=pkt[0x04];
+       eth[5]=pkt[0x03];
+       eth[6]=eth[7]=eth[8]=eth[9]=eth[10]=eth[11]=0;
+       if (snap) {
+               eth[12] = pkt[18];
+               eth[13] = pkt[19];
+       } else {
+               /* protocol numbers are from rfc-1700 or
+                * http://www.iana.org/assignments/ethernet-numbers
+                */
+               if (pkt[12] >> 4 == 6) { /* version field from IP header */
+                       eth[12] = 0x86; /* IPv6 */
+                       eth[13] = 0xdd;
+               } else {
+                       eth[12] = 0x08; /* IPv4 */
+                       eth[13] = 0x00;
+               }
+       }
+       skb->protocol = dvb_net_eth_type_trans(skb, dev);
+       stats->rx_packets++;
+       stats->rx_bytes+=skb->len;
+       netif_rx(skb);
+ }
+ static int dvb_net_sec_callback(const u8 *buffer1, size_t buffer1_len,
+                const u8 *buffer2, size_t buffer2_len,
+                struct dmx_section_filter *filter,
+                enum dmx_success success)
+ {
+       struct net_device *dev = filter->priv;
+       /**
+        * we rely on the DVB API definition where exactly one complete
+        * section is delivered in buffer1
+        */
+       dvb_net_sec (dev, buffer1, buffer1_len);
+       return 0;
+ }
+ static int dvb_net_tx(struct sk_buff *skb, struct net_device *dev)
+ {
+       dev_kfree_skb(skb);
+       return NETDEV_TX_OK;
+ }
+ static u8 mask_normal[6]={0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
+ static u8 mask_allmulti[6]={0xff, 0xff, 0xff, 0x00, 0x00, 0x00};
+ static u8 mac_allmulti[6]={0x01, 0x00, 0x5e, 0x00, 0x00, 0x00};
+ static u8 mask_promisc[6]={0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+ static int dvb_net_filter_sec_set(struct net_device *dev,
+                  struct dmx_section_filter **secfilter,
+                  u8 *mac, u8 *mac_mask)
+ {
+       struct dvb_net_priv *priv = netdev_priv(dev);
+       int ret;
+       *secfilter=NULL;
+       ret = priv->secfeed->allocate_filter(priv->secfeed, secfilter);
+       if (ret<0) {
+               printk("%s: could not get filter\n", dev->name);
+               return ret;
+       }
+       (*secfilter)->priv=(void *) dev;
+       memset((*secfilter)->filter_value, 0x00, DMX_MAX_FILTER_SIZE);
+       memset((*secfilter)->filter_mask,  0x00, DMX_MAX_FILTER_SIZE);
+       memset((*secfilter)->filter_mode,  0xff, DMX_MAX_FILTER_SIZE);
+       (*secfilter)->filter_value[0]=0x3e;
+       (*secfilter)->filter_value[3]=mac[5];
+       (*secfilter)->filter_value[4]=mac[4];
+       (*secfilter)->filter_value[8]=mac[3];
+       (*secfilter)->filter_value[9]=mac[2];
+       (*secfilter)->filter_value[10]=mac[1];
+       (*secfilter)->filter_value[11]=mac[0];
+       (*secfilter)->filter_mask[0] = 0xff;
+       (*secfilter)->filter_mask[3] = mac_mask[5];
+       (*secfilter)->filter_mask[4] = mac_mask[4];
+       (*secfilter)->filter_mask[8] = mac_mask[3];
+       (*secfilter)->filter_mask[9] = mac_mask[2];
+       (*secfilter)->filter_mask[10] = mac_mask[1];
+       (*secfilter)->filter_mask[11]=mac_mask[0];
+       dprintk("%s: filter mac=%pM\n", dev->name, mac);
+       dprintk("%s: filter mask=%pM\n", dev->name, mac_mask);
+       return 0;
+ }
+ static int dvb_net_feed_start(struct net_device *dev)
+ {
+       int ret = 0, i;
+       struct dvb_net_priv *priv = netdev_priv(dev);
+       struct dmx_demux *demux = priv->demux;
+       unsigned char *mac = (unsigned char *) dev->dev_addr;
+       dprintk("%s: rx_mode %i\n", __func__, priv->rx_mode);
+       mutex_lock(&priv->mutex);
+       if (priv->tsfeed || priv->secfeed || priv->secfilter || priv->multi_secfilter[0])
+               printk("%s: BUG %d\n", __func__, __LINE__);
+       priv->secfeed=NULL;
+       priv->secfilter=NULL;
+       priv->tsfeed = NULL;
+       if (priv->feedtype == DVB_NET_FEEDTYPE_MPE) {
+               dprintk("%s: alloc secfeed\n", __func__);
+               ret=demux->allocate_section_feed(demux, &priv->secfeed,
+                                        dvb_net_sec_callback);
+               if (ret<0) {
+                       printk("%s: could not allocate section feed\n", dev->name);
+                       goto error;
+               }
+               ret = priv->secfeed->set(priv->secfeed, priv->pid, 32768, 1);
+               if (ret<0) {
+                       printk("%s: could not set section feed\n", dev->name);
+                       priv->demux->release_section_feed(priv->demux, priv->secfeed);
+                       priv->secfeed=NULL;
+                       goto error;
+               }
+               if (priv->rx_mode != RX_MODE_PROMISC) {
+                       dprintk("%s: set secfilter\n", __func__);
+                       dvb_net_filter_sec_set(dev, &priv->secfilter, mac, mask_normal);
+               }
+               switch (priv->rx_mode) {
+               case RX_MODE_MULTI:
+                       for (i = 0; i < priv->multi_num; i++) {
+                               dprintk("%s: set multi_secfilter[%d]\n", __func__, i);
+                               dvb_net_filter_sec_set(dev, &priv->multi_secfilter[i],
+                                                      priv->multi_macs[i], mask_normal);
+                       }
+                       break;
+               case RX_MODE_ALL_MULTI:
+                       priv->multi_num=1;
+                       dprintk("%s: set multi_secfilter[0]\n", __func__);
+                       dvb_net_filter_sec_set(dev, &priv->multi_secfilter[0],
+                                              mac_allmulti, mask_allmulti);
+                       break;
+               case RX_MODE_PROMISC:
+                       priv->multi_num=0;
+                       dprintk("%s: set secfilter\n", __func__);
+                       dvb_net_filter_sec_set(dev, &priv->secfilter, mac, mask_promisc);
+                       break;
+               }
+               dprintk("%s: start filtering\n", __func__);
+               priv->secfeed->start_filtering(priv->secfeed);
+       } else if (priv->feedtype == DVB_NET_FEEDTYPE_ULE) {
+               struct timespec timeout = { 0, 10000000 }; // 10 msec
+               /* we have payloads encapsulated in TS */
+               dprintk("%s: alloc tsfeed\n", __func__);
+               ret = demux->allocate_ts_feed(demux, &priv->tsfeed, dvb_net_ts_callback);
+               if (ret < 0) {
+                       printk("%s: could not allocate ts feed\n", dev->name);
+                       goto error;
+               }
+               /* Set netdevice pointer for ts decaps callback. */
+               priv->tsfeed->priv = (void *)dev;
+               ret = priv->tsfeed->set(priv->tsfeed,
+                                       priv->pid, /* pid */
+                                       TS_PACKET, /* type */
+                                       DMX_TS_PES_OTHER, /* pes type */
+                                       32768,     /* circular buffer size */
+                                       timeout    /* timeout */
+                                       );
+               if (ret < 0) {
+                       printk("%s: could not set ts feed\n", dev->name);
+                       priv->demux->release_ts_feed(priv->demux, priv->tsfeed);
+                       priv->tsfeed = NULL;
+                       goto error;
+               }
+               dprintk("%s: start filtering\n", __func__);
+               priv->tsfeed->start_filtering(priv->tsfeed);
+       } else
+               ret = -EINVAL;
+ error:
+       mutex_unlock(&priv->mutex);
+       return ret;
+ }
+ static int dvb_net_feed_stop(struct net_device *dev)
+ {
+       struct dvb_net_priv *priv = netdev_priv(dev);
+       int i, ret = 0;
+       dprintk("%s\n", __func__);
+       mutex_lock(&priv->mutex);
+       if (priv->feedtype == DVB_NET_FEEDTYPE_MPE) {
+               if (priv->secfeed) {
+                       if (priv->secfeed->is_filtering) {
+                               dprintk("%s: stop secfeed\n", __func__);
+                               priv->secfeed->stop_filtering(priv->secfeed);
+                       }
+                       if (priv->secfilter) {
+                               dprintk("%s: release secfilter\n", __func__);
+                               priv->secfeed->release_filter(priv->secfeed,
+                                                             priv->secfilter);
+                               priv->secfilter=NULL;
+                       }
+                       for (i=0; i<priv->multi_num; i++) {
+                               if (priv->multi_secfilter[i]) {
+                                       dprintk("%s: release multi_filter[%d]\n",
+                                               __func__, i);
+                                       priv->secfeed->release_filter(priv->secfeed,
+                                                                     priv->multi_secfilter[i]);
+                                       priv->multi_secfilter[i] = NULL;
+                               }
+                       }
+                       priv->demux->release_section_feed(priv->demux, priv->secfeed);
+                       priv->secfeed = NULL;
+               } else
+                       printk("%s: no feed to stop\n", dev->name);
+       } else if (priv->feedtype == DVB_NET_FEEDTYPE_ULE) {
+               if (priv->tsfeed) {
+                       if (priv->tsfeed->is_filtering) {
+                               dprintk("%s: stop tsfeed\n", __func__);
+                               priv->tsfeed->stop_filtering(priv->tsfeed);
+                       }
+                       priv->demux->release_ts_feed(priv->demux, priv->tsfeed);
+                       priv->tsfeed = NULL;
+               }
+               else
+                       printk("%s: no ts feed to stop\n", dev->name);
+       } else
+               ret = -EINVAL;
+       mutex_unlock(&priv->mutex);
+       return ret;
+ }
+ static int dvb_set_mc_filter(struct net_device *dev, unsigned char *addr)
+ {
+       struct dvb_net_priv *priv = netdev_priv(dev);
+       if (priv->multi_num == DVB_NET_MULTICAST_MAX)
+               return -ENOMEM;
+       memcpy(priv->multi_macs[priv->multi_num], addr, ETH_ALEN);
+       priv->multi_num++;
+       return 0;
+ }
+ static void wq_set_multicast_list (struct work_struct *work)
+ {
+       struct dvb_net_priv *priv =
+               container_of(work, struct dvb_net_priv, set_multicast_list_wq);
+       struct net_device *dev = priv->net;
+       dvb_net_feed_stop(dev);
+       priv->rx_mode = RX_MODE_UNI;
+       netif_addr_lock_bh(dev);
+       if (dev->flags & IFF_PROMISC) {
+               dprintk("%s: promiscuous mode\n", dev->name);
+               priv->rx_mode = RX_MODE_PROMISC;
+       } else if ((dev->flags & IFF_ALLMULTI)) {
+               dprintk("%s: allmulti mode\n", dev->name);
+               priv->rx_mode = RX_MODE_ALL_MULTI;
+       } else if (!netdev_mc_empty(dev)) {
+               struct netdev_hw_addr *ha;
+               dprintk("%s: set_mc_list, %d entries\n",
+                       dev->name, netdev_mc_count(dev));
+               priv->rx_mode = RX_MODE_MULTI;
+               priv->multi_num = 0;
+               netdev_for_each_mc_addr(ha, dev)
+                       dvb_set_mc_filter(dev, ha->addr);
+       }
+       netif_addr_unlock_bh(dev);
+       dvb_net_feed_start(dev);
+ }
+ static void dvb_net_set_multicast_list (struct net_device *dev)
+ {
+       struct dvb_net_priv *priv = netdev_priv(dev);
+       schedule_work(&priv->set_multicast_list_wq);
+ }
+ static void wq_restart_net_feed (struct work_struct *work)
+ {
+       struct dvb_net_priv *priv =
+               container_of(work, struct dvb_net_priv, restart_net_feed_wq);
+       struct net_device *dev = priv->net;
+       if (netif_running(dev)) {
+               dvb_net_feed_stop(dev);
+               dvb_net_feed_start(dev);
+       }
+ }
+ static int dvb_net_set_mac (struct net_device *dev, void *p)
+ {
+       struct dvb_net_priv *priv = netdev_priv(dev);
+       struct sockaddr *addr=p;
+       memcpy(dev->dev_addr, addr->sa_data, dev->addr_len);
+       if (netif_running(dev))
+               schedule_work(&priv->restart_net_feed_wq);
+       return 0;
+ }
+ static int dvb_net_open(struct net_device *dev)
+ {
+       struct dvb_net_priv *priv = netdev_priv(dev);
+       priv->in_use++;
+       dvb_net_feed_start(dev);
+       return 0;
+ }
+ static int dvb_net_stop(struct net_device *dev)
+ {
+       struct dvb_net_priv *priv = netdev_priv(dev);
+       priv->in_use--;
+       return dvb_net_feed_stop(dev);
+ }
+ static const struct header_ops dvb_header_ops = {
+       .create         = eth_header,
+       .parse          = eth_header_parse,
+       .rebuild        = eth_rebuild_header,
+ };
+ static const struct net_device_ops dvb_netdev_ops = {
+       .ndo_open               = dvb_net_open,
+       .ndo_stop               = dvb_net_stop,
+       .ndo_start_xmit         = dvb_net_tx,
+       .ndo_set_rx_mode        = dvb_net_set_multicast_list,
+       .ndo_set_mac_address    = dvb_net_set_mac,
+       .ndo_change_mtu         = eth_change_mtu,
+       .ndo_validate_addr      = eth_validate_addr,
+ };
+ static void dvb_net_setup(struct net_device *dev)
+ {
+       ether_setup(dev);
+       dev->header_ops         = &dvb_header_ops;
+       dev->netdev_ops         = &dvb_netdev_ops;
+       dev->mtu                = 4096;
+       dev->flags |= IFF_NOARP;
+ }
+ static int get_if(struct dvb_net *dvbnet)
+ {
+       int i;
+       for (i=0; i<DVB_NET_DEVICES_MAX; i++)
+               if (!dvbnet->state[i])
+                       break;
+       if (i == DVB_NET_DEVICES_MAX)
+               return -1;
+       dvbnet->state[i]=1;
+       return i;
+ }
+ static int dvb_net_add_if(struct dvb_net *dvbnet, u16 pid, u8 feedtype)
+ {
+       struct net_device *net;
+       struct dvb_net_priv *priv;
+       int result;
+       int if_num;
+       if (feedtype != DVB_NET_FEEDTYPE_MPE && feedtype != DVB_NET_FEEDTYPE_ULE)
+               return -EINVAL;
+       if ((if_num = get_if(dvbnet)) < 0)
+               return -EINVAL;
+       net = alloc_netdev(sizeof(struct dvb_net_priv), "dvb", dvb_net_setup);
+       if (!net)
+               return -ENOMEM;
+       if (dvbnet->dvbdev->id)
+               snprintf(net->name, IFNAMSIZ, "dvb%d%u%d",
+                        dvbnet->dvbdev->adapter->num, dvbnet->dvbdev->id, if_num);
+       else
+               /* compatibility fix to keep dvb0_0 format */
+               snprintf(net->name, IFNAMSIZ, "dvb%d_%d",
+                        dvbnet->dvbdev->adapter->num, if_num);
+       net->addr_len = 6;
+       memcpy(net->dev_addr, dvbnet->dvbdev->adapter->proposed_mac, 6);
+       dvbnet->device[if_num] = net;
+       priv = netdev_priv(net);
+       priv->net = net;
+       priv->demux = dvbnet->demux;
+       priv->pid = pid;
+       priv->rx_mode = RX_MODE_UNI;
+       priv->need_pusi = 1;
+       priv->tscc = 0;
+       priv->feedtype = feedtype;
+       reset_ule(priv);
+       INIT_WORK(&priv->set_multicast_list_wq, wq_set_multicast_list);
+       INIT_WORK(&priv->restart_net_feed_wq, wq_restart_net_feed);
+       mutex_init(&priv->mutex);
+       net->base_addr = pid;
+       if ((result = register_netdev(net)) < 0) {
+               dvbnet->device[if_num] = NULL;
+               free_netdev(net);
+               return result;
+       }
+       printk("dvb_net: created network interface %s\n", net->name);
+       return if_num;
+ }
+ static int dvb_net_remove_if(struct dvb_net *dvbnet, unsigned long num)
+ {
+       struct net_device *net = dvbnet->device[num];
+       struct dvb_net_priv *priv;
+       if (!dvbnet->state[num])
+               return -EINVAL;
+       priv = netdev_priv(net);
+       if (priv->in_use)
+               return -EBUSY;
+       dvb_net_stop(net);
++      flush_work(&priv->set_multicast_list_wq);
++      flush_work(&priv->restart_net_feed_wq);
+       printk("dvb_net: removed network interface %s\n", net->name);
+       unregister_netdev(net);
+       dvbnet->state[num]=0;
+       dvbnet->device[num] = NULL;
+       free_netdev(net);
+       return 0;
+ }
+ static int dvb_net_do_ioctl(struct file *file,
+                 unsigned int cmd, void *parg)
+ {
+       struct dvb_device *dvbdev = file->private_data;
+       struct dvb_net *dvbnet = dvbdev->priv;
+       if (((file->f_flags&O_ACCMODE)==O_RDONLY))
+               return -EPERM;
+       switch (cmd) {
+       case NET_ADD_IF:
+       {
+               struct dvb_net_if *dvbnetif = parg;
+               int result;
+               if (!capable(CAP_SYS_ADMIN))
+                       return -EPERM;
+               if (!try_module_get(dvbdev->adapter->module))
+                       return -EPERM;
+               result=dvb_net_add_if(dvbnet, dvbnetif->pid, dvbnetif->feedtype);
+               if (result<0) {
+                       module_put(dvbdev->adapter->module);
+                       return result;
+               }
+               dvbnetif->if_num=result;
+               break;
+       }
+       case NET_GET_IF:
+       {
+               struct net_device *netdev;
+               struct dvb_net_priv *priv_data;
+               struct dvb_net_if *dvbnetif = parg;
+               if (dvbnetif->if_num >= DVB_NET_DEVICES_MAX ||
+                   !dvbnet->state[dvbnetif->if_num])
+                       return -EINVAL;
+               netdev = dvbnet->device[dvbnetif->if_num];
+               priv_data = netdev_priv(netdev);
+               dvbnetif->pid=priv_data->pid;
+               dvbnetif->feedtype=priv_data->feedtype;
+               break;
+       }
+       case NET_REMOVE_IF:
+       {
+               int ret;
+               if (!capable(CAP_SYS_ADMIN))
+                       return -EPERM;
+               if ((unsigned long) parg >= DVB_NET_DEVICES_MAX)
+                       return -EINVAL;
+               ret = dvb_net_remove_if(dvbnet, (unsigned long) parg);
+               if (!ret)
+                       module_put(dvbdev->adapter->module);
+               return ret;
+       }
+       /* binary compatibility cruft */
+       case __NET_ADD_IF_OLD:
+       {
+               struct __dvb_net_if_old *dvbnetif = parg;
+               int result;
+               if (!capable(CAP_SYS_ADMIN))
+                       return -EPERM;
+               if (!try_module_get(dvbdev->adapter->module))
+                       return -EPERM;
+               result=dvb_net_add_if(dvbnet, dvbnetif->pid, DVB_NET_FEEDTYPE_MPE);
+               if (result<0) {
+                       module_put(dvbdev->adapter->module);
+                       return result;
+               }
+               dvbnetif->if_num=result;
+               break;
+       }
+       case __NET_GET_IF_OLD:
+       {
+               struct net_device *netdev;
+               struct dvb_net_priv *priv_data;
+               struct __dvb_net_if_old *dvbnetif = parg;
+               if (dvbnetif->if_num >= DVB_NET_DEVICES_MAX ||
+                   !dvbnet->state[dvbnetif->if_num])
+                       return -EINVAL;
+               netdev = dvbnet->device[dvbnetif->if_num];
+               priv_data = netdev_priv(netdev);
+               dvbnetif->pid=priv_data->pid;
+               break;
+       }
+       default:
+               return -ENOTTY;
+       }
+       return 0;
+ }
+ static long dvb_net_ioctl(struct file *file,
+             unsigned int cmd, unsigned long arg)
+ {
+       return dvb_usercopy(file, cmd, arg, dvb_net_do_ioctl);
+ }
+ static int dvb_net_close(struct inode *inode, struct file *file)
+ {
+       struct dvb_device *dvbdev = file->private_data;
+       struct dvb_net *dvbnet = dvbdev->priv;
+       dvb_generic_release(inode, file);
+       if(dvbdev->users == 1 && dvbnet->exit == 1) {
+               fops_put(file->f_op);
+               file->f_op = NULL;
+               wake_up(&dvbdev->wait_queue);
+       }
+       return 0;
+ }
+ static const struct file_operations dvb_net_fops = {
+       .owner = THIS_MODULE,
+       .unlocked_ioctl = dvb_net_ioctl,
+       .open = dvb_generic_open,
+       .release = dvb_net_close,
+       .llseek = noop_llseek,
+ };
+ static struct dvb_device dvbdev_net = {
+       .priv = NULL,
+       .users = 1,
+       .writers = 1,
+       .fops = &dvb_net_fops,
+ };
+ void dvb_net_release (struct dvb_net *dvbnet)
+ {
+       int i;
+       dvbnet->exit = 1;
+       if (dvbnet->dvbdev->users < 1)
+               wait_event(dvbnet->dvbdev->wait_queue,
+                               dvbnet->dvbdev->users==1);
+       dvb_unregister_device(dvbnet->dvbdev);
+       for (i=0; i<DVB_NET_DEVICES_MAX; i++) {
+               if (!dvbnet->state[i])
+                       continue;
+               dvb_net_remove_if(dvbnet, i);
+       }
+ }
+ EXPORT_SYMBOL(dvb_net_release);
+ int dvb_net_init (struct dvb_adapter *adap, struct dvb_net *dvbnet,
+                 struct dmx_demux *dmx)
+ {
+       int i;
+       dvbnet->demux = dmx;
+       for (i=0; i<DVB_NET_DEVICES_MAX; i++)
+               dvbnet->state[i] = 0;
+       return dvb_register_device(adap, &dvbnet->dvbdev, &dvbdev_net,
+                            dvbnet, DVB_DEVICE_NET);
+ }
+ EXPORT_SYMBOL(dvb_net_init);
index 0000000000000000000000000000000000000000,16f5ca23698c69af3795d5e6a3c90168fc6fee4c..b68918c97f66868baaa1ece4da01ad286f391747
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,4630 +1,4630 @@@
 -      flush_work_sync(&dev->request_module_wk);
+ /*
+     bttv - Bt848 frame grabber driver
+     Copyright (C) 1996,97,98 Ralph  Metzler <rjkm@thp.uni-koeln.de>
+                          & Marcus Metzler <mocm@thp.uni-koeln.de>
+     (c) 1999-2002 Gerd Knorr <kraxel@bytesex.org>
+     some v4l2 code lines are taken from Justin's bttv2 driver which is
+     (c) 2000 Justin Schoeman <justin@suntiger.ee.up.ac.za>
+     V4L1 removal from:
+     (c) 2005-2006 Nickolay V. Shmyrev <nshmyrev@yandex.ru>
+     Fixes to be fully V4L2 compliant by
+     (c) 2006 Mauro Carvalho Chehab <mchehab@infradead.org>
+     Cropping and overscan support
+     Copyright (C) 2005, 2006 Michael H. Schimek <mschimek@gmx.at>
+     Sponsored by OPQ Systems AB
+     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.
+ */
+ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+ #include <linux/init.h>
+ #include <linux/module.h>
+ #include <linux/delay.h>
+ #include <linux/slab.h>
+ #include <linux/errno.h>
+ #include <linux/fs.h>
+ #include <linux/kernel.h>
+ #include <linux/sched.h>
+ #include <linux/interrupt.h>
+ #include <linux/kdev_t.h>
+ #include "bttvp.h"
+ #include <media/v4l2-common.h>
+ #include <media/v4l2-ioctl.h>
+ #include <media/tvaudio.h>
+ #include <media/msp3400.h>
+ #include <linux/dma-mapping.h>
+ #include <asm/io.h>
+ #include <asm/byteorder.h>
+ #include <media/saa6588.h>
+ #define BTTV_VERSION "0.9.19"
+ unsigned int bttv_num;                        /* number of Bt848s in use */
+ struct bttv *bttvs[BTTV_MAX];
+ unsigned int bttv_debug;
+ unsigned int bttv_verbose = 1;
+ unsigned int bttv_gpio;
+ /* config variables */
+ #ifdef __BIG_ENDIAN
+ static unsigned int bigendian=1;
+ #else
+ static unsigned int bigendian;
+ #endif
+ static unsigned int radio[BTTV_MAX];
+ static unsigned int irq_debug;
+ static unsigned int gbuffers = 8;
+ static unsigned int gbufsize = 0x208000;
+ static unsigned int reset_crop = 1;
+ static int video_nr[BTTV_MAX] = { [0 ... (BTTV_MAX-1)] = -1 };
+ static int radio_nr[BTTV_MAX] = { [0 ... (BTTV_MAX-1)] = -1 };
+ static int vbi_nr[BTTV_MAX] = { [0 ... (BTTV_MAX-1)] = -1 };
+ static int debug_latency;
+ static int disable_ir;
+ static unsigned int fdsr;
+ /* options */
+ static unsigned int combfilter;
+ static unsigned int lumafilter;
+ static unsigned int automute    = 1;
+ static unsigned int chroma_agc;
+ static unsigned int adc_crush   = 1;
+ static unsigned int whitecrush_upper = 0xCF;
+ static unsigned int whitecrush_lower = 0x7F;
+ static unsigned int vcr_hack;
+ static unsigned int irq_iswitch;
+ static unsigned int uv_ratio    = 50;
+ static unsigned int full_luma_range;
+ static unsigned int coring;
+ /* API features (turn on/off stuff for testing) */
+ static unsigned int v4l2        = 1;
+ /* insmod args */
+ module_param(bttv_verbose,      int, 0644);
+ module_param(bttv_gpio,         int, 0644);
+ module_param(bttv_debug,        int, 0644);
+ module_param(irq_debug,         int, 0644);
+ module_param(debug_latency,     int, 0644);
+ module_param(disable_ir,        int, 0444);
+ module_param(fdsr,              int, 0444);
+ module_param(gbuffers,          int, 0444);
+ module_param(gbufsize,          int, 0444);
+ module_param(reset_crop,        int, 0444);
+ module_param(v4l2,              int, 0644);
+ module_param(bigendian,         int, 0644);
+ module_param(irq_iswitch,       int, 0644);
+ module_param(combfilter,        int, 0444);
+ module_param(lumafilter,        int, 0444);
+ module_param(automute,          int, 0444);
+ module_param(chroma_agc,        int, 0444);
+ module_param(adc_crush,         int, 0444);
+ module_param(whitecrush_upper,  int, 0444);
+ module_param(whitecrush_lower,  int, 0444);
+ module_param(vcr_hack,          int, 0444);
+ module_param(uv_ratio,          int, 0444);
+ module_param(full_luma_range,   int, 0444);
+ module_param(coring,            int, 0444);
+ module_param_array(radio,       int, NULL, 0444);
+ module_param_array(video_nr,    int, NULL, 0444);
+ module_param_array(radio_nr,    int, NULL, 0444);
+ module_param_array(vbi_nr,      int, NULL, 0444);
+ MODULE_PARM_DESC(radio,"The TV card supports radio, default is 0 (no)");
+ MODULE_PARM_DESC(bigendian,"byte order of the framebuffer, default is native endian");
+ MODULE_PARM_DESC(bttv_verbose,"verbose startup messages, default is 1 (yes)");
+ MODULE_PARM_DESC(bttv_gpio,"log gpio changes, default is 0 (no)");
+ MODULE_PARM_DESC(bttv_debug,"debug messages, default is 0 (no)");
+ MODULE_PARM_DESC(irq_debug,"irq handler debug messages, default is 0 (no)");
+ MODULE_PARM_DESC(disable_ir, "disable infrared remote support");
+ MODULE_PARM_DESC(gbuffers,"number of capture buffers. range 2-32, default 8");
+ MODULE_PARM_DESC(gbufsize,"size of the capture buffers, default is 0x208000");
+ MODULE_PARM_DESC(reset_crop,"reset cropping parameters at open(), default "
+                "is 1 (yes) for compatibility with older applications");
+ MODULE_PARM_DESC(automute,"mute audio on bad/missing video signal, default is 1 (yes)");
+ MODULE_PARM_DESC(chroma_agc,"enables the AGC of chroma signal, default is 0 (no)");
+ MODULE_PARM_DESC(adc_crush,"enables the luminance ADC crush, default is 1 (yes)");
+ MODULE_PARM_DESC(whitecrush_upper,"sets the white crush upper value, default is 207");
+ MODULE_PARM_DESC(whitecrush_lower,"sets the white crush lower value, default is 127");
+ MODULE_PARM_DESC(vcr_hack,"enables the VCR hack (improves synch on poor VCR tapes), default is 0 (no)");
+ MODULE_PARM_DESC(irq_iswitch,"switch inputs in irq handler");
+ MODULE_PARM_DESC(uv_ratio,"ratio between u and v gains, default is 50");
+ MODULE_PARM_DESC(full_luma_range,"use the full luma range, default is 0 (no)");
+ MODULE_PARM_DESC(coring,"set the luma coring level, default is 0 (no)");
+ MODULE_PARM_DESC(video_nr, "video device numbers");
+ MODULE_PARM_DESC(vbi_nr, "vbi device numbers");
+ MODULE_PARM_DESC(radio_nr, "radio device numbers");
+ MODULE_DESCRIPTION("bttv - v4l/v4l2 driver module for bt848/878 based cards");
+ MODULE_AUTHOR("Ralph Metzler & Marcus Metzler & Gerd Knorr");
+ MODULE_LICENSE("GPL");
+ MODULE_VERSION(BTTV_VERSION);
+ /* ----------------------------------------------------------------------- */
+ /* sysfs                                                                   */
+ static ssize_t show_card(struct device *cd,
+                        struct device_attribute *attr, char *buf)
+ {
+       struct video_device *vfd = container_of(cd, struct video_device, dev);
+       struct bttv *btv = video_get_drvdata(vfd);
+       return sprintf(buf, "%d\n", btv ? btv->c.type : UNSET);
+ }
+ static DEVICE_ATTR(card, S_IRUGO, show_card, NULL);
+ /* ----------------------------------------------------------------------- */
+ /* dvb auto-load setup                                                     */
+ #if defined(CONFIG_MODULES) && defined(MODULE)
+ static void request_module_async(struct work_struct *work)
+ {
+       request_module("dvb-bt8xx");
+ }
+ static void request_modules(struct bttv *dev)
+ {
+       INIT_WORK(&dev->request_module_wk, request_module_async);
+       schedule_work(&dev->request_module_wk);
+ }
+ static void flush_request_modules(struct bttv *dev)
+ {
++      flush_work(&dev->request_module_wk);
+ }
+ #else
+ #define request_modules(dev)
+ #define flush_request_modules(dev)
+ #endif /* CONFIG_MODULES */
+ /* ----------------------------------------------------------------------- */
+ /* static data                                                             */
+ /* special timing tables from conexant... */
+ static u8 SRAM_Table[][60] =
+ {
+       /* PAL digital input over GPIO[7:0] */
+       {
+               45, // 45 bytes following
+               0x36,0x11,0x01,0x00,0x90,0x02,0x05,0x10,0x04,0x16,
+               0x12,0x05,0x11,0x00,0x04,0x12,0xC0,0x00,0x31,0x00,
+               0x06,0x51,0x08,0x03,0x89,0x08,0x07,0xC0,0x44,0x00,
+               0x81,0x01,0x01,0xA9,0x0D,0x02,0x02,0x50,0x03,0x37,
+               0x37,0x00,0xAF,0x21,0x00
+       },
+       /* NTSC digital input over GPIO[7:0] */
+       {
+               51, // 51 bytes following
+               0x0C,0xC0,0x00,0x00,0x90,0x02,0x03,0x10,0x03,0x06,
+               0x10,0x04,0x12,0x12,0x05,0x02,0x13,0x04,0x19,0x00,
+               0x04,0x39,0x00,0x06,0x59,0x08,0x03,0x83,0x08,0x07,
+               0x03,0x50,0x00,0xC0,0x40,0x00,0x86,0x01,0x01,0xA6,
+               0x0D,0x02,0x03,0x11,0x01,0x05,0x37,0x00,0xAC,0x21,
+               0x00,
+       },
+       // TGB_NTSC392 // quartzsight
+       // This table has been modified to be used for Fusion Rev D
+       {
+               0x2A, // size of table = 42
+               0x06, 0x08, 0x04, 0x0a, 0xc0, 0x00, 0x18, 0x08, 0x03, 0x24,
+               0x08, 0x07, 0x02, 0x90, 0x02, 0x08, 0x10, 0x04, 0x0c, 0x10,
+               0x05, 0x2c, 0x11, 0x04, 0x55, 0x48, 0x00, 0x05, 0x50, 0x00,
+               0xbf, 0x0c, 0x02, 0x2f, 0x3d, 0x00, 0x2f, 0x3f, 0x00, 0xc3,
+               0x20, 0x00
+       }
+ };
+ /* minhdelayx1        first video pixel we can capture on a line and
+    hdelayx1   start of active video, both relative to rising edge of
+               /HRESET pulse (0H) in 1 / fCLKx1.
+    swidth     width of active video and
+    totalwidth total line width, both in 1 / fCLKx1.
+    sqwidth    total line width in square pixels.
+    vdelay     start of active video in 2 * field lines relative to
+               trailing edge of /VRESET pulse (VDELAY register).
+    sheight    height of active video in 2 * field lines.
+    videostart0        ITU-R frame line number of the line corresponding
+               to vdelay in the first field. */
+ #define CROPCAP(minhdelayx1, hdelayx1, swidth, totalwidth, sqwidth,    \
+               vdelay, sheight, videostart0)                            \
+       .cropcap.bounds.left = minhdelayx1,                              \
+       /* * 2 because vertically we count field lines times two, */     \
+       /* e.g. 23 * 2 to 23 * 2 + 576 in PAL-BGHI defrect. */           \
+       .cropcap.bounds.top = (videostart0) * 2 - (vdelay) + MIN_VDELAY, \
+       /* 4 is a safety margin at the end of the line. */               \
+       .cropcap.bounds.width = (totalwidth) - (minhdelayx1) - 4,        \
+       .cropcap.bounds.height = (sheight) + (vdelay) - MIN_VDELAY,      \
+       .cropcap.defrect.left = hdelayx1,                                \
+       .cropcap.defrect.top = (videostart0) * 2,                        \
+       .cropcap.defrect.width = swidth,                                 \
+       .cropcap.defrect.height = sheight,                               \
+       .cropcap.pixelaspect.numerator = totalwidth,                     \
+       .cropcap.pixelaspect.denominator = sqwidth,
+ const struct bttv_tvnorm bttv_tvnorms[] = {
+       /* PAL-BDGHI */
+       /* max. active video is actually 922, but 924 is divisible by 4 and 3! */
+       /* actually, max active PAL with HSCALE=0 is 948, NTSC is 768 - nil */
+       {
+               .v4l2_id        = V4L2_STD_PAL,
+               .name           = "PAL",
+               .Fsc            = 35468950,
+               .swidth         = 924,
+               .sheight        = 576,
+               .totalwidth     = 1135,
+               .adelay         = 0x7f,
+               .bdelay         = 0x72,
+               .iform          = (BT848_IFORM_PAL_BDGHI|BT848_IFORM_XT1),
+               .scaledtwidth   = 1135,
+               .hdelayx1       = 186,
+               .hactivex1      = 924,
+               .vdelay         = 0x20,
+               .vbipack        = 255, /* min (2048 / 4, 0x1ff) & 0xff */
+               .sram           = 0,
+               /* ITU-R frame line number of the first VBI line
+                  we can capture, of the first and second field.
+                  The last line is determined by cropcap.bounds. */
+               .vbistart       = { 7, 320 },
+               CROPCAP(/* minhdelayx1 */ 68,
+                       /* hdelayx1 */ 186,
+                       /* Should be (768 * 1135 + 944 / 2) / 944.
+                          cropcap.defrect is used for image width
+                          checks, so we keep the old value 924. */
+                       /* swidth */ 924,
+                       /* totalwidth */ 1135,
+                       /* sqwidth */ 944,
+                       /* vdelay */ 0x20,
+                       /* sheight */ 576,
+                       /* videostart0 */ 23)
+               /* bt878 (and bt848?) can capture another
+                  line below active video. */
+               .cropcap.bounds.height = (576 + 2) + 0x20 - 2,
+       },{
+               .v4l2_id        = V4L2_STD_NTSC_M | V4L2_STD_NTSC_M_KR,
+               .name           = "NTSC",
+               .Fsc            = 28636363,
+               .swidth         = 768,
+               .sheight        = 480,
+               .totalwidth     = 910,
+               .adelay         = 0x68,
+               .bdelay         = 0x5d,
+               .iform          = (BT848_IFORM_NTSC|BT848_IFORM_XT0),
+               .scaledtwidth   = 910,
+               .hdelayx1       = 128,
+               .hactivex1      = 910,
+               .vdelay         = 0x1a,
+               .vbipack        = 144, /* min (1600 / 4, 0x1ff) & 0xff */
+               .sram           = 1,
+               .vbistart       = { 10, 273 },
+               CROPCAP(/* minhdelayx1 */ 68,
+                       /* hdelayx1 */ 128,
+                       /* Should be (640 * 910 + 780 / 2) / 780? */
+                       /* swidth */ 768,
+                       /* totalwidth */ 910,
+                       /* sqwidth */ 780,
+                       /* vdelay */ 0x1a,
+                       /* sheight */ 480,
+                       /* videostart0 */ 23)
+       },{
+               .v4l2_id        = V4L2_STD_SECAM,
+               .name           = "SECAM",
+               .Fsc            = 35468950,
+               .swidth         = 924,
+               .sheight        = 576,
+               .totalwidth     = 1135,
+               .adelay         = 0x7f,
+               .bdelay         = 0xb0,
+               .iform          = (BT848_IFORM_SECAM|BT848_IFORM_XT1),
+               .scaledtwidth   = 1135,
+               .hdelayx1       = 186,
+               .hactivex1      = 922,
+               .vdelay         = 0x20,
+               .vbipack        = 255,
+               .sram           = 0, /* like PAL, correct? */
+               .vbistart       = { 7, 320 },
+               CROPCAP(/* minhdelayx1 */ 68,
+                       /* hdelayx1 */ 186,
+                       /* swidth */ 924,
+                       /* totalwidth */ 1135,
+                       /* sqwidth */ 944,
+                       /* vdelay */ 0x20,
+                       /* sheight */ 576,
+                       /* videostart0 */ 23)
+       },{
+               .v4l2_id        = V4L2_STD_PAL_Nc,
+               .name           = "PAL-Nc",
+               .Fsc            = 28636363,
+               .swidth         = 640,
+               .sheight        = 576,
+               .totalwidth     = 910,
+               .adelay         = 0x68,
+               .bdelay         = 0x5d,
+               .iform          = (BT848_IFORM_PAL_NC|BT848_IFORM_XT0),
+               .scaledtwidth   = 780,
+               .hdelayx1       = 130,
+               .hactivex1      = 734,
+               .vdelay         = 0x1a,
+               .vbipack        = 144,
+               .sram           = -1,
+               .vbistart       = { 7, 320 },
+               CROPCAP(/* minhdelayx1 */ 68,
+                       /* hdelayx1 */ 130,
+                       /* swidth */ (640 * 910 + 780 / 2) / 780,
+                       /* totalwidth */ 910,
+                       /* sqwidth */ 780,
+                       /* vdelay */ 0x1a,
+                       /* sheight */ 576,
+                       /* videostart0 */ 23)
+       },{
+               .v4l2_id        = V4L2_STD_PAL_M,
+               .name           = "PAL-M",
+               .Fsc            = 28636363,
+               .swidth         = 640,
+               .sheight        = 480,
+               .totalwidth     = 910,
+               .adelay         = 0x68,
+               .bdelay         = 0x5d,
+               .iform          = (BT848_IFORM_PAL_M|BT848_IFORM_XT0),
+               .scaledtwidth   = 780,
+               .hdelayx1       = 135,
+               .hactivex1      = 754,
+               .vdelay         = 0x1a,
+               .vbipack        = 144,
+               .sram           = -1,
+               .vbistart       = { 10, 273 },
+               CROPCAP(/* minhdelayx1 */ 68,
+                       /* hdelayx1 */ 135,
+                       /* swidth */ (640 * 910 + 780 / 2) / 780,
+                       /* totalwidth */ 910,
+                       /* sqwidth */ 780,
+                       /* vdelay */ 0x1a,
+                       /* sheight */ 480,
+                       /* videostart0 */ 23)
+       },{
+               .v4l2_id        = V4L2_STD_PAL_N,
+               .name           = "PAL-N",
+               .Fsc            = 35468950,
+               .swidth         = 768,
+               .sheight        = 576,
+               .totalwidth     = 1135,
+               .adelay         = 0x7f,
+               .bdelay         = 0x72,
+               .iform          = (BT848_IFORM_PAL_N|BT848_IFORM_XT1),
+               .scaledtwidth   = 944,
+               .hdelayx1       = 186,
+               .hactivex1      = 922,
+               .vdelay         = 0x20,
+               .vbipack        = 144,
+               .sram           = -1,
+               .vbistart       = { 7, 320 },
+               CROPCAP(/* minhdelayx1 */ 68,
+                       /* hdelayx1 */ 186,
+                       /* swidth */ (768 * 1135 + 944 / 2) / 944,
+                       /* totalwidth */ 1135,
+                       /* sqwidth */ 944,
+                       /* vdelay */ 0x20,
+                       /* sheight */ 576,
+                       /* videostart0 */ 23)
+       },{
+               .v4l2_id        = V4L2_STD_NTSC_M_JP,
+               .name           = "NTSC-JP",
+               .Fsc            = 28636363,
+               .swidth         = 640,
+               .sheight        = 480,
+               .totalwidth     = 910,
+               .adelay         = 0x68,
+               .bdelay         = 0x5d,
+               .iform          = (BT848_IFORM_NTSC_J|BT848_IFORM_XT0),
+               .scaledtwidth   = 780,
+               .hdelayx1       = 135,
+               .hactivex1      = 754,
+               .vdelay         = 0x16,
+               .vbipack        = 144,
+               .sram           = -1,
+               .vbistart       = { 10, 273 },
+               CROPCAP(/* minhdelayx1 */ 68,
+                       /* hdelayx1 */ 135,
+                       /* swidth */ (640 * 910 + 780 / 2) / 780,
+                       /* totalwidth */ 910,
+                       /* sqwidth */ 780,
+                       /* vdelay */ 0x16,
+                       /* sheight */ 480,
+                       /* videostart0 */ 23)
+       },{
+               /* that one hopefully works with the strange timing
+                * which video recorders produce when playing a NTSC
+                * tape on a PAL TV ... */
+               .v4l2_id        = V4L2_STD_PAL_60,
+               .name           = "PAL-60",
+               .Fsc            = 35468950,
+               .swidth         = 924,
+               .sheight        = 480,
+               .totalwidth     = 1135,
+               .adelay         = 0x7f,
+               .bdelay         = 0x72,
+               .iform          = (BT848_IFORM_PAL_BDGHI|BT848_IFORM_XT1),
+               .scaledtwidth   = 1135,
+               .hdelayx1       = 186,
+               .hactivex1      = 924,
+               .vdelay         = 0x1a,
+               .vbipack        = 255,
+               .vtotal         = 524,
+               .sram           = -1,
+               .vbistart       = { 10, 273 },
+               CROPCAP(/* minhdelayx1 */ 68,
+                       /* hdelayx1 */ 186,
+                       /* swidth */ 924,
+                       /* totalwidth */ 1135,
+                       /* sqwidth */ 944,
+                       /* vdelay */ 0x1a,
+                       /* sheight */ 480,
+                       /* videostart0 */ 23)
+       }
+ };
+ static const unsigned int BTTV_TVNORMS = ARRAY_SIZE(bttv_tvnorms);
+ /* ----------------------------------------------------------------------- */
+ /* bttv format list
+    packed pixel formats must come first */
+ static const struct bttv_format formats[] = {
+       {
+               .name     = "8 bpp, gray",
+               .fourcc   = V4L2_PIX_FMT_GREY,
+               .btformat = BT848_COLOR_FMT_Y8,
+               .depth    = 8,
+               .flags    = FORMAT_FLAGS_PACKED,
+       },{
+               .name     = "8 bpp, dithered color",
+               .fourcc   = V4L2_PIX_FMT_HI240,
+               .btformat = BT848_COLOR_FMT_RGB8,
+               .depth    = 8,
+               .flags    = FORMAT_FLAGS_PACKED | FORMAT_FLAGS_DITHER,
+       },{
+               .name     = "15 bpp RGB, le",
+               .fourcc   = V4L2_PIX_FMT_RGB555,
+               .btformat = BT848_COLOR_FMT_RGB15,
+               .depth    = 16,
+               .flags    = FORMAT_FLAGS_PACKED,
+       },{
+               .name     = "15 bpp RGB, be",
+               .fourcc   = V4L2_PIX_FMT_RGB555X,
+               .btformat = BT848_COLOR_FMT_RGB15,
+               .btswap   = 0x03, /* byteswap */
+               .depth    = 16,
+               .flags    = FORMAT_FLAGS_PACKED,
+       },{
+               .name     = "16 bpp RGB, le",
+               .fourcc   = V4L2_PIX_FMT_RGB565,
+               .btformat = BT848_COLOR_FMT_RGB16,
+               .depth    = 16,
+               .flags    = FORMAT_FLAGS_PACKED,
+       },{
+               .name     = "16 bpp RGB, be",
+               .fourcc   = V4L2_PIX_FMT_RGB565X,
+               .btformat = BT848_COLOR_FMT_RGB16,
+               .btswap   = 0x03, /* byteswap */
+               .depth    = 16,
+               .flags    = FORMAT_FLAGS_PACKED,
+       },{
+               .name     = "24 bpp RGB, le",
+               .fourcc   = V4L2_PIX_FMT_BGR24,
+               .btformat = BT848_COLOR_FMT_RGB24,
+               .depth    = 24,
+               .flags    = FORMAT_FLAGS_PACKED,
+       },{
+               .name     = "32 bpp RGB, le",
+               .fourcc   = V4L2_PIX_FMT_BGR32,
+               .btformat = BT848_COLOR_FMT_RGB32,
+               .depth    = 32,
+               .flags    = FORMAT_FLAGS_PACKED,
+       },{
+               .name     = "32 bpp RGB, be",
+               .fourcc   = V4L2_PIX_FMT_RGB32,
+               .btformat = BT848_COLOR_FMT_RGB32,
+               .btswap   = 0x0f, /* byte+word swap */
+               .depth    = 32,
+               .flags    = FORMAT_FLAGS_PACKED,
+       },{
+               .name     = "4:2:2, packed, YUYV",
+               .fourcc   = V4L2_PIX_FMT_YUYV,
+               .btformat = BT848_COLOR_FMT_YUY2,
+               .depth    = 16,
+               .flags    = FORMAT_FLAGS_PACKED,
+       },{
+               .name     = "4:2:2, packed, UYVY",
+               .fourcc   = V4L2_PIX_FMT_UYVY,
+               .btformat = BT848_COLOR_FMT_YUY2,
+               .btswap   = 0x03, /* byteswap */
+               .depth    = 16,
+               .flags    = FORMAT_FLAGS_PACKED,
+       },{
+               .name     = "4:2:2, planar, Y-Cb-Cr",
+               .fourcc   = V4L2_PIX_FMT_YUV422P,
+               .btformat = BT848_COLOR_FMT_YCrCb422,
+               .depth    = 16,
+               .flags    = FORMAT_FLAGS_PLANAR,
+               .hshift   = 1,
+               .vshift   = 0,
+       },{
+               .name     = "4:2:0, planar, Y-Cb-Cr",
+               .fourcc   = V4L2_PIX_FMT_YUV420,
+               .btformat = BT848_COLOR_FMT_YCrCb422,
+               .depth    = 12,
+               .flags    = FORMAT_FLAGS_PLANAR,
+               .hshift   = 1,
+               .vshift   = 1,
+       },{
+               .name     = "4:2:0, planar, Y-Cr-Cb",
+               .fourcc   = V4L2_PIX_FMT_YVU420,
+               .btformat = BT848_COLOR_FMT_YCrCb422,
+               .depth    = 12,
+               .flags    = FORMAT_FLAGS_PLANAR | FORMAT_FLAGS_CrCb,
+               .hshift   = 1,
+               .vshift   = 1,
+       },{
+               .name     = "4:1:1, planar, Y-Cb-Cr",
+               .fourcc   = V4L2_PIX_FMT_YUV411P,
+               .btformat = BT848_COLOR_FMT_YCrCb411,
+               .depth    = 12,
+               .flags    = FORMAT_FLAGS_PLANAR,
+               .hshift   = 2,
+               .vshift   = 0,
+       },{
+               .name     = "4:1:0, planar, Y-Cb-Cr",
+               .fourcc   = V4L2_PIX_FMT_YUV410,
+               .btformat = BT848_COLOR_FMT_YCrCb411,
+               .depth    = 9,
+               .flags    = FORMAT_FLAGS_PLANAR,
+               .hshift   = 2,
+               .vshift   = 2,
+       },{
+               .name     = "4:1:0, planar, Y-Cr-Cb",
+               .fourcc   = V4L2_PIX_FMT_YVU410,
+               .btformat = BT848_COLOR_FMT_YCrCb411,
+               .depth    = 9,
+               .flags    = FORMAT_FLAGS_PLANAR | FORMAT_FLAGS_CrCb,
+               .hshift   = 2,
+               .vshift   = 2,
+       },{
+               .name     = "raw scanlines",
+               .fourcc   = -1,
+               .btformat = BT848_COLOR_FMT_RAW,
+               .depth    = 8,
+               .flags    = FORMAT_FLAGS_RAW,
+       }
+ };
+ static const unsigned int FORMATS = ARRAY_SIZE(formats);
+ /* ----------------------------------------------------------------------- */
+ #define V4L2_CID_PRIVATE_CHROMA_AGC  (V4L2_CID_PRIVATE_BASE + 0)
+ #define V4L2_CID_PRIVATE_COMBFILTER  (V4L2_CID_PRIVATE_BASE + 1)
+ #define V4L2_CID_PRIVATE_AUTOMUTE    (V4L2_CID_PRIVATE_BASE + 2)
+ #define V4L2_CID_PRIVATE_LUMAFILTER  (V4L2_CID_PRIVATE_BASE + 3)
+ #define V4L2_CID_PRIVATE_AGC_CRUSH   (V4L2_CID_PRIVATE_BASE + 4)
+ #define V4L2_CID_PRIVATE_VCR_HACK    (V4L2_CID_PRIVATE_BASE + 5)
+ #define V4L2_CID_PRIVATE_WHITECRUSH_UPPER   (V4L2_CID_PRIVATE_BASE + 6)
+ #define V4L2_CID_PRIVATE_WHITECRUSH_LOWER   (V4L2_CID_PRIVATE_BASE + 7)
+ #define V4L2_CID_PRIVATE_UV_RATIO    (V4L2_CID_PRIVATE_BASE + 8)
+ #define V4L2_CID_PRIVATE_FULL_LUMA_RANGE    (V4L2_CID_PRIVATE_BASE + 9)
+ #define V4L2_CID_PRIVATE_CORING      (V4L2_CID_PRIVATE_BASE + 10)
+ #define V4L2_CID_PRIVATE_LASTP1      (V4L2_CID_PRIVATE_BASE + 11)
+ static const struct v4l2_queryctrl no_ctl = {
+       .name  = "42",
+       .flags = V4L2_CTRL_FLAG_DISABLED,
+ };
+ static const struct v4l2_queryctrl bttv_ctls[] = {
+       /* --- video --- */
+       {
+               .id            = V4L2_CID_BRIGHTNESS,
+               .name          = "Brightness",
+               .minimum       = 0,
+               .maximum       = 65535,
+               .step          = 256,
+               .default_value = 32768,
+               .type          = V4L2_CTRL_TYPE_INTEGER,
+       },{
+               .id            = V4L2_CID_CONTRAST,
+               .name          = "Contrast",
+               .minimum       = 0,
+               .maximum       = 65535,
+               .step          = 128,
+               .default_value = 27648,
+               .type          = V4L2_CTRL_TYPE_INTEGER,
+       },{
+               .id            = V4L2_CID_SATURATION,
+               .name          = "Saturation",
+               .minimum       = 0,
+               .maximum       = 65535,
+               .step          = 128,
+               .default_value = 32768,
+               .type          = V4L2_CTRL_TYPE_INTEGER,
+       },{
+               .id            = V4L2_CID_HUE,
+               .name          = "Hue",
+               .minimum       = 0,
+               .maximum       = 65535,
+               .step          = 256,
+               .default_value = 32768,
+               .type          = V4L2_CTRL_TYPE_INTEGER,
+       },
+       /* --- audio --- */
+       {
+               .id            = V4L2_CID_AUDIO_MUTE,
+               .name          = "Mute",
+               .minimum       = 0,
+               .maximum       = 1,
+               .type          = V4L2_CTRL_TYPE_BOOLEAN,
+       },{
+               .id            = V4L2_CID_AUDIO_VOLUME,
+               .name          = "Volume",
+               .minimum       = 0,
+               .maximum       = 65535,
+               .step          = 65535/100,
+               .default_value = 65535,
+               .type          = V4L2_CTRL_TYPE_INTEGER,
+       },{
+               .id            = V4L2_CID_AUDIO_BALANCE,
+               .name          = "Balance",
+               .minimum       = 0,
+               .maximum       = 65535,
+               .step          = 65535/100,
+               .default_value = 32768,
+               .type          = V4L2_CTRL_TYPE_INTEGER,
+       },{
+               .id            = V4L2_CID_AUDIO_BASS,
+               .name          = "Bass",
+               .minimum       = 0,
+               .maximum       = 65535,
+               .step          = 65535/100,
+               .default_value = 32768,
+               .type          = V4L2_CTRL_TYPE_INTEGER,
+       },{
+               .id            = V4L2_CID_AUDIO_TREBLE,
+               .name          = "Treble",
+               .minimum       = 0,
+               .maximum       = 65535,
+               .step          = 65535/100,
+               .default_value = 32768,
+               .type          = V4L2_CTRL_TYPE_INTEGER,
+       },
+       /* --- private --- */
+       {
+               .id            = V4L2_CID_PRIVATE_CHROMA_AGC,
+               .name          = "chroma agc",
+               .minimum       = 0,
+               .maximum       = 1,
+               .type          = V4L2_CTRL_TYPE_BOOLEAN,
+       },{
+               .id            = V4L2_CID_PRIVATE_COMBFILTER,
+               .name          = "combfilter",
+               .minimum       = 0,
+               .maximum       = 1,
+               .type          = V4L2_CTRL_TYPE_BOOLEAN,
+       },{
+               .id            = V4L2_CID_PRIVATE_AUTOMUTE,
+               .name          = "automute",
+               .minimum       = 0,
+               .maximum       = 1,
+               .type          = V4L2_CTRL_TYPE_BOOLEAN,
+       },{
+               .id            = V4L2_CID_PRIVATE_LUMAFILTER,
+               .name          = "luma decimation filter",
+               .minimum       = 0,
+               .maximum       = 1,
+               .type          = V4L2_CTRL_TYPE_BOOLEAN,
+       },{
+               .id            = V4L2_CID_PRIVATE_AGC_CRUSH,
+               .name          = "agc crush",
+               .minimum       = 0,
+               .maximum       = 1,
+               .type          = V4L2_CTRL_TYPE_BOOLEAN,
+       },{
+               .id            = V4L2_CID_PRIVATE_VCR_HACK,
+               .name          = "vcr hack",
+               .minimum       = 0,
+               .maximum       = 1,
+               .type          = V4L2_CTRL_TYPE_BOOLEAN,
+       },{
+               .id            = V4L2_CID_PRIVATE_WHITECRUSH_UPPER,
+               .name          = "whitecrush upper",
+               .minimum       = 0,
+               .maximum       = 255,
+               .step          = 1,
+               .default_value = 0xCF,
+               .type          = V4L2_CTRL_TYPE_INTEGER,
+       },{
+               .id            = V4L2_CID_PRIVATE_WHITECRUSH_LOWER,
+               .name          = "whitecrush lower",
+               .minimum       = 0,
+               .maximum       = 255,
+               .step          = 1,
+               .default_value = 0x7F,
+               .type          = V4L2_CTRL_TYPE_INTEGER,
+       },{
+               .id            = V4L2_CID_PRIVATE_UV_RATIO,
+               .name          = "uv ratio",
+               .minimum       = 0,
+               .maximum       = 100,
+               .step          = 1,
+               .default_value = 50,
+               .type          = V4L2_CTRL_TYPE_INTEGER,
+       },{
+               .id            = V4L2_CID_PRIVATE_FULL_LUMA_RANGE,
+               .name          = "full luma range",
+               .minimum       = 0,
+               .maximum       = 1,
+               .type          = V4L2_CTRL_TYPE_BOOLEAN,
+       },{
+               .id            = V4L2_CID_PRIVATE_CORING,
+               .name          = "coring",
+               .minimum       = 0,
+               .maximum       = 3,
+               .step          = 1,
+               .default_value = 0,
+               .type          = V4L2_CTRL_TYPE_INTEGER,
+       }
+ };
+ static const struct v4l2_queryctrl *ctrl_by_id(int id)
+ {
+       int i;
+       for (i = 0; i < ARRAY_SIZE(bttv_ctls); i++)
+               if (bttv_ctls[i].id == id)
+                       return bttv_ctls+i;
+       return NULL;
+ }
+ /* ----------------------------------------------------------------------- */
+ /* resource management                                                     */
+ /*
+    RESOURCE_    allocated by                freed by
+    VIDEO_READ   bttv_read 1)                bttv_read 2)
+    VIDEO_STREAM VIDIOC_STREAMON             VIDIOC_STREAMOFF
+                VIDIOC_QBUF 1)              bttv_release
+                VIDIOCMCAPTURE 1)
+    OVERLAY     VIDIOCCAPTURE on            VIDIOCCAPTURE off
+                VIDIOC_OVERLAY on           VIDIOC_OVERLAY off
+                3)                          bttv_release
+    VBI                 VIDIOC_STREAMON             VIDIOC_STREAMOFF
+                VIDIOC_QBUF 1)              bttv_release
+                bttv_read, bttv_poll 1) 4)
+    1) The resource must be allocated when we enter buffer prepare functions
+       and remain allocated while buffers are in the DMA queue.
+    2) This is a single frame read.
+    3) VIDIOC_S_FBUF and VIDIOC_S_FMT (OVERLAY) still work when
+       RESOURCE_OVERLAY is allocated.
+    4) This is a continuous read, implies VIDIOC_STREAMON.
+    Note this driver permits video input and standard changes regardless if
+    resources are allocated.
+ */
+ #define VBI_RESOURCES (RESOURCE_VBI)
+ #define VIDEO_RESOURCES (RESOURCE_VIDEO_READ | \
+                        RESOURCE_VIDEO_STREAM | \
+                        RESOURCE_OVERLAY)
+ static
+ int check_alloc_btres_lock(struct bttv *btv, struct bttv_fh *fh, int bit)
+ {
+       int xbits; /* mutual exclusive resources */
+       if (fh->resources & bit)
+               /* have it already allocated */
+               return 1;
+       xbits = bit;
+       if (bit & (RESOURCE_VIDEO_READ | RESOURCE_VIDEO_STREAM))
+               xbits |= RESOURCE_VIDEO_READ | RESOURCE_VIDEO_STREAM;
+       /* is it free? */
+       if (btv->resources & xbits) {
+               /* no, someone else uses it */
+               goto fail;
+       }
+       if ((bit & VIDEO_RESOURCES)
+           && 0 == (btv->resources & VIDEO_RESOURCES)) {
+               /* Do crop - use current, don't - use default parameters. */
+               __s32 top = btv->crop[!!fh->do_crop].rect.top;
+               if (btv->vbi_end > top)
+                       goto fail;
+               /* We cannot capture the same line as video and VBI data.
+                  Claim scan lines crop[].rect.top to bottom. */
+               btv->crop_start = top;
+       } else if (bit & VBI_RESOURCES) {
+               __s32 end = fh->vbi_fmt.end;
+               if (end > btv->crop_start)
+                       goto fail;
+               /* Claim scan lines above fh->vbi_fmt.end. */
+               btv->vbi_end = end;
+       }
+       /* it's free, grab it */
+       fh->resources  |= bit;
+       btv->resources |= bit;
+       return 1;
+  fail:
+       return 0;
+ }
+ static
+ int check_btres(struct bttv_fh *fh, int bit)
+ {
+       return (fh->resources & bit);
+ }
+ static
+ int locked_btres(struct bttv *btv, int bit)
+ {
+       return (btv->resources & bit);
+ }
+ /* Call with btv->lock down. */
+ static void
+ disclaim_vbi_lines(struct bttv *btv)
+ {
+       btv->vbi_end = 0;
+ }
+ /* Call with btv->lock down. */
+ static void
+ disclaim_video_lines(struct bttv *btv)
+ {
+       const struct bttv_tvnorm *tvnorm;
+       u8 crop;
+       tvnorm = &bttv_tvnorms[btv->tvnorm];
+       btv->crop_start = tvnorm->cropcap.bounds.top
+               + tvnorm->cropcap.bounds.height;
+       /* VBI capturing ends at VDELAY, start of video capturing, no
+          matter how many lines the VBI RISC program expects. When video
+          capturing is off, it shall no longer "preempt" VBI capturing,
+          so we set VDELAY to maximum. */
+       crop = btread(BT848_E_CROP) | 0xc0;
+       btwrite(crop, BT848_E_CROP);
+       btwrite(0xfe, BT848_E_VDELAY_LO);
+       btwrite(crop, BT848_O_CROP);
+       btwrite(0xfe, BT848_O_VDELAY_LO);
+ }
+ static
+ void free_btres_lock(struct bttv *btv, struct bttv_fh *fh, int bits)
+ {
+       if ((fh->resources & bits) != bits) {
+               /* trying to free resources not allocated by us ... */
+               pr_err("BUG! (btres)\n");
+       }
+       fh->resources  &= ~bits;
+       btv->resources &= ~bits;
+       bits = btv->resources;
+       if (0 == (bits & VIDEO_RESOURCES))
+               disclaim_video_lines(btv);
+       if (0 == (bits & VBI_RESOURCES))
+               disclaim_vbi_lines(btv);
+ }
+ /* ----------------------------------------------------------------------- */
+ /* If Bt848a or Bt849, use PLL for PAL/SECAM and crystal for NTSC          */
+ /* Frequency = (F_input / PLL_X) * PLL_I.PLL_F/PLL_C
+    PLL_X = Reference pre-divider (0=1, 1=2)
+    PLL_C = Post divider (0=6, 1=4)
+    PLL_I = Integer input
+    PLL_F = Fractional input
+    F_input = 28.636363 MHz:
+    PAL (CLKx2 = 35.46895 MHz): PLL_X = 1, PLL_I = 0x0E, PLL_F = 0xDCF9, PLL_C = 0
+ */
+ static void set_pll_freq(struct bttv *btv, unsigned int fin, unsigned int fout)
+ {
+       unsigned char fl, fh, fi;
+       /* prevent overflows */
+       fin/=4;
+       fout/=4;
+       fout*=12;
+       fi=fout/fin;
+       fout=(fout%fin)*256;
+       fh=fout/fin;
+       fout=(fout%fin)*256;
+       fl=fout/fin;
+       btwrite(fl, BT848_PLL_F_LO);
+       btwrite(fh, BT848_PLL_F_HI);
+       btwrite(fi|BT848_PLL_X, BT848_PLL_XCI);
+ }
+ static void set_pll(struct bttv *btv)
+ {
+       int i;
+       if (!btv->pll.pll_crystal)
+               return;
+       if (btv->pll.pll_ofreq == btv->pll.pll_current) {
+               dprintk("%d: PLL: no change required\n", btv->c.nr);
+               return;
+       }
+       if (btv->pll.pll_ifreq == btv->pll.pll_ofreq) {
+               /* no PLL needed */
+               if (btv->pll.pll_current == 0)
+                       return;
+               if (bttv_verbose)
+                       pr_info("%d: PLL can sleep, using XTAL (%d)\n",
+                               btv->c.nr, btv->pll.pll_ifreq);
+               btwrite(0x00,BT848_TGCTRL);
+               btwrite(0x00,BT848_PLL_XCI);
+               btv->pll.pll_current = 0;
+               return;
+       }
+       if (bttv_verbose)
+               pr_info("%d: Setting PLL: %d => %d (needs up to 100ms)\n",
+                       btv->c.nr,
+                       btv->pll.pll_ifreq, btv->pll.pll_ofreq);
+       set_pll_freq(btv, btv->pll.pll_ifreq, btv->pll.pll_ofreq);
+       for (i=0; i<10; i++) {
+               /*  Let other people run while the PLL stabilizes */
+               msleep(10);
+               if (btread(BT848_DSTATUS) & BT848_DSTATUS_PLOCK) {
+                       btwrite(0,BT848_DSTATUS);
+               } else {
+                       btwrite(0x08,BT848_TGCTRL);
+                       btv->pll.pll_current = btv->pll.pll_ofreq;
+                       if (bttv_verbose)
+                               pr_info("PLL set ok\n");
+                       return;
+               }
+       }
+       btv->pll.pll_current = -1;
+       if (bttv_verbose)
+               pr_info("Setting PLL failed\n");
+       return;
+ }
+ /* used to switch between the bt848's analog/digital video capture modes */
+ static void bt848A_set_timing(struct bttv *btv)
+ {
+       int i, len;
+       int table_idx = bttv_tvnorms[btv->tvnorm].sram;
+       int fsc       = bttv_tvnorms[btv->tvnorm].Fsc;
+       if (btv->input == btv->dig) {
+               dprintk("%d: load digital timing table (table_idx=%d)\n",
+                       btv->c.nr,table_idx);
+               /* timing change...reset timing generator address */
+               btwrite(0x00, BT848_TGCTRL);
+               btwrite(0x02, BT848_TGCTRL);
+               btwrite(0x00, BT848_TGCTRL);
+               len=SRAM_Table[table_idx][0];
+               for(i = 1; i <= len; i++)
+                       btwrite(SRAM_Table[table_idx][i],BT848_TGLB);
+               btv->pll.pll_ofreq = 27000000;
+               set_pll(btv);
+               btwrite(0x11, BT848_TGCTRL);
+               btwrite(0x41, BT848_DVSIF);
+       } else {
+               btv->pll.pll_ofreq = fsc;
+               set_pll(btv);
+               btwrite(0x0, BT848_DVSIF);
+       }
+ }
+ /* ----------------------------------------------------------------------- */
+ static void bt848_bright(struct bttv *btv, int bright)
+ {
+       int value;
+       // printk("set bright: %d\n", bright); // DEBUG
+       btv->bright = bright;
+       /* We want -128 to 127 we get 0-65535 */
+       value = (bright >> 8) - 128;
+       btwrite(value & 0xff, BT848_BRIGHT);
+ }
+ static void bt848_hue(struct bttv *btv, int hue)
+ {
+       int value;
+       btv->hue = hue;
+       /* -128 to 127 */
+       value = (hue >> 8) - 128;
+       btwrite(value & 0xff, BT848_HUE);
+ }
+ static void bt848_contrast(struct bttv *btv, int cont)
+ {
+       int value,hibit;
+       btv->contrast = cont;
+       /* 0-511 */
+       value = (cont  >> 7);
+       hibit = (value >> 6) & 4;
+       btwrite(value & 0xff, BT848_CONTRAST_LO);
+       btaor(hibit, ~4, BT848_E_CONTROL);
+       btaor(hibit, ~4, BT848_O_CONTROL);
+ }
+ static void bt848_sat(struct bttv *btv, int color)
+ {
+       int val_u,val_v,hibits;
+       btv->saturation = color;
+       /* 0-511 for the color */
+       val_u   = ((color * btv->opt_uv_ratio) / 50) >> 7;
+       val_v   = (((color * (100 - btv->opt_uv_ratio) / 50) >>7)*180L)/254;
+       hibits  = (val_u >> 7) & 2;
+       hibits |= (val_v >> 8) & 1;
+       btwrite(val_u & 0xff, BT848_SAT_U_LO);
+       btwrite(val_v & 0xff, BT848_SAT_V_LO);
+       btaor(hibits, ~3, BT848_E_CONTROL);
+       btaor(hibits, ~3, BT848_O_CONTROL);
+ }
+ /* ----------------------------------------------------------------------- */
+ static int
+ video_mux(struct bttv *btv, unsigned int input)
+ {
+       int mux,mask2;
+       if (input >= bttv_tvcards[btv->c.type].video_inputs)
+               return -EINVAL;
+       /* needed by RemoteVideo MX */
+       mask2 = bttv_tvcards[btv->c.type].gpiomask2;
+       if (mask2)
+               gpio_inout(mask2,mask2);
+       if (input == btv->svhs)  {
+               btor(BT848_CONTROL_COMP, BT848_E_CONTROL);
+               btor(BT848_CONTROL_COMP, BT848_O_CONTROL);
+       } else {
+               btand(~BT848_CONTROL_COMP, BT848_E_CONTROL);
+               btand(~BT848_CONTROL_COMP, BT848_O_CONTROL);
+       }
+       mux = bttv_muxsel(btv, input);
+       btaor(mux<<5, ~(3<<5), BT848_IFORM);
+       dprintk("%d: video mux: input=%d mux=%d\n", btv->c.nr, input, mux);
+       /* card specific hook */
+       if(bttv_tvcards[btv->c.type].muxsel_hook)
+               bttv_tvcards[btv->c.type].muxsel_hook (btv, input);
+       return 0;
+ }
+ static char *audio_modes[] = {
+       "audio: tuner", "audio: radio", "audio: extern",
+       "audio: intern", "audio: mute"
+ };
+ static int
+ audio_mux(struct bttv *btv, int input, int mute)
+ {
+       int gpio_val, signal;
+       struct v4l2_control ctrl;
+       gpio_inout(bttv_tvcards[btv->c.type].gpiomask,
+                  bttv_tvcards[btv->c.type].gpiomask);
+       signal = btread(BT848_DSTATUS) & BT848_DSTATUS_HLOC;
+       btv->mute = mute;
+       btv->audio = input;
+       /* automute */
+       mute = mute || (btv->opt_automute && !signal && !btv->radio_user);
+       if (mute)
+               gpio_val = bttv_tvcards[btv->c.type].gpiomute;
+       else
+               gpio_val = bttv_tvcards[btv->c.type].gpiomux[input];
+       switch (btv->c.type) {
+       case BTTV_BOARD_VOODOOTV_FM:
+       case BTTV_BOARD_VOODOOTV_200:
+               gpio_val = bttv_tda9880_setnorm(btv, gpio_val);
+               break;
+       default:
+               gpio_bits(bttv_tvcards[btv->c.type].gpiomask, gpio_val);
+       }
+       if (bttv_gpio)
+               bttv_gpio_tracking(btv, audio_modes[mute ? 4 : input]);
+       if (in_interrupt())
+               return 0;
+       ctrl.id = V4L2_CID_AUDIO_MUTE;
+       ctrl.value = btv->mute;
+       bttv_call_all(btv, core, s_ctrl, &ctrl);
+       if (btv->sd_msp34xx) {
+               u32 in;
+               /* Note: the inputs tuner/radio/extern/intern are translated
+                  to msp routings. This assumes common behavior for all msp3400
+                  based TV cards. When this assumption fails, then the
+                  specific MSP routing must be added to the card table.
+                  For now this is sufficient. */
+               switch (input) {
+               case TVAUDIO_INPUT_RADIO:
+                       /* Some boards need the msp do to the radio demod */
+                       if (btv->radio_uses_msp_demodulator) {
+                               in = MSP_INPUT_DEFAULT;
+                               break;
+                       }
+                       in = MSP_INPUT(MSP_IN_SCART2, MSP_IN_TUNER1,
+                                   MSP_DSP_IN_SCART, MSP_DSP_IN_SCART);
+                       break;
+               case TVAUDIO_INPUT_EXTERN:
+                       in = MSP_INPUT(MSP_IN_SCART1, MSP_IN_TUNER1,
+                                   MSP_DSP_IN_SCART, MSP_DSP_IN_SCART);
+                       break;
+               case TVAUDIO_INPUT_INTERN:
+                       /* Yes, this is the same input as for RADIO. I doubt
+                          if this is ever used. The only board with an INTERN
+                          input is the BTTV_BOARD_AVERMEDIA98. I wonder how
+                          that was tested. My guess is that the whole INTERN
+                          input does not work. */
+                       in = MSP_INPUT(MSP_IN_SCART2, MSP_IN_TUNER1,
+                                   MSP_DSP_IN_SCART, MSP_DSP_IN_SCART);
+                       break;
+               case TVAUDIO_INPUT_TUNER:
+               default:
+                       /* This is the only card that uses TUNER2, and afaik,
+                          is the only difference between the VOODOOTV_FM
+                          and VOODOOTV_200 */
+                       if (btv->c.type == BTTV_BOARD_VOODOOTV_200)
+                               in = MSP_INPUT(MSP_IN_SCART1, MSP_IN_TUNER2, \
+                                       MSP_DSP_IN_TUNER, MSP_DSP_IN_TUNER);
+                       else
+                               in = MSP_INPUT_DEFAULT;
+                       break;
+               }
+               v4l2_subdev_call(btv->sd_msp34xx, audio, s_routing,
+                              in, MSP_OUTPUT_DEFAULT, 0);
+       }
+       if (btv->sd_tvaudio) {
+               v4l2_subdev_call(btv->sd_tvaudio, audio, s_routing,
+                               input, 0, 0);
+       }
+       return 0;
+ }
+ static inline int
+ audio_mute(struct bttv *btv, int mute)
+ {
+       return audio_mux(btv, btv->audio, mute);
+ }
+ static inline int
+ audio_input(struct bttv *btv, int input)
+ {
+       return audio_mux(btv, input, btv->mute);
+ }
+ static void
+ bttv_crop_calc_limits(struct bttv_crop *c)
+ {
+       /* Scale factor min. 1:1, max. 16:1. Min. image size
+          48 x 32. Scaled width must be a multiple of 4. */
+       if (1) {
+               /* For bug compatibility with VIDIOCGCAP and image
+                  size checks in earlier driver versions. */
+               c->min_scaled_width = 48;
+               c->min_scaled_height = 32;
+       } else {
+               c->min_scaled_width =
+                       (max(48, c->rect.width >> 4) + 3) & ~3;
+               c->min_scaled_height =
+                       max(32, c->rect.height >> 4);
+       }
+       c->max_scaled_width  = c->rect.width & ~3;
+       c->max_scaled_height = c->rect.height;
+ }
+ static void
+ bttv_crop_reset(struct bttv_crop *c, unsigned int norm)
+ {
+       c->rect = bttv_tvnorms[norm].cropcap.defrect;
+       bttv_crop_calc_limits(c);
+ }
+ /* Call with btv->lock down. */
+ static int
+ set_tvnorm(struct bttv *btv, unsigned int norm)
+ {
+       const struct bttv_tvnorm *tvnorm;
+       v4l2_std_id id;
+       BUG_ON(norm >= BTTV_TVNORMS);
+       BUG_ON(btv->tvnorm >= BTTV_TVNORMS);
+       tvnorm = &bttv_tvnorms[norm];
+       if (memcmp(&bttv_tvnorms[btv->tvnorm].cropcap, &tvnorm->cropcap,
+                   sizeof (tvnorm->cropcap))) {
+               bttv_crop_reset(&btv->crop[0], norm);
+               btv->crop[1] = btv->crop[0]; /* current = default */
+               if (0 == (btv->resources & VIDEO_RESOURCES)) {
+                       btv->crop_start = tvnorm->cropcap.bounds.top
+                               + tvnorm->cropcap.bounds.height;
+               }
+       }
+       btv->tvnorm = norm;
+       btwrite(tvnorm->adelay, BT848_ADELAY);
+       btwrite(tvnorm->bdelay, BT848_BDELAY);
+       btaor(tvnorm->iform,~(BT848_IFORM_NORM|BT848_IFORM_XTBOTH),
+             BT848_IFORM);
+       btwrite(tvnorm->vbipack, BT848_VBI_PACK_SIZE);
+       btwrite(1, BT848_VBI_PACK_DEL);
+       bt848A_set_timing(btv);
+       switch (btv->c.type) {
+       case BTTV_BOARD_VOODOOTV_FM:
+       case BTTV_BOARD_VOODOOTV_200:
+               bttv_tda9880_setnorm(btv, gpio_read());
+               break;
+       }
+       id = tvnorm->v4l2_id;
+       bttv_call_all(btv, core, s_std, id);
+       return 0;
+ }
+ /* Call with btv->lock down. */
+ static void
+ set_input(struct bttv *btv, unsigned int input, unsigned int norm)
+ {
+       unsigned long flags;
+       btv->input = input;
+       if (irq_iswitch) {
+               spin_lock_irqsave(&btv->s_lock,flags);
+               if (btv->curr.frame_irq) {
+                       /* active capture -> delayed input switch */
+                       btv->new_input = input;
+               } else {
+                       video_mux(btv,input);
+               }
+               spin_unlock_irqrestore(&btv->s_lock,flags);
+       } else {
+               video_mux(btv,input);
+       }
+       audio_input(btv, (btv->tuner_type != TUNER_ABSENT && input == 0) ?
+                        TVAUDIO_INPUT_TUNER : TVAUDIO_INPUT_EXTERN);
+       set_tvnorm(btv, norm);
+ }
+ static void init_irqreg(struct bttv *btv)
+ {
+       /* clear status */
+       btwrite(0xfffffUL, BT848_INT_STAT);
+       if (bttv_tvcards[btv->c.type].no_video) {
+               /* i2c only */
+               btwrite(BT848_INT_I2CDONE,
+                       BT848_INT_MASK);
+       } else {
+               /* full video */
+               btwrite((btv->triton1)  |
+                       (btv->gpioirq ? BT848_INT_GPINT : 0) |
+                       BT848_INT_SCERR |
+                       (fdsr ? BT848_INT_FDSR : 0) |
+                       BT848_INT_RISCI | BT848_INT_OCERR |
+                       BT848_INT_FMTCHG|BT848_INT_HLOCK|
+                       BT848_INT_I2CDONE,
+                       BT848_INT_MASK);
+       }
+ }
+ static void init_bt848(struct bttv *btv)
+ {
+       int val;
+       if (bttv_tvcards[btv->c.type].no_video) {
+               /* very basic init only */
+               init_irqreg(btv);
+               return;
+       }
+       btwrite(0x00, BT848_CAP_CTL);
+       btwrite(BT848_COLOR_CTL_GAMMA, BT848_COLOR_CTL);
+       btwrite(BT848_IFORM_XTAUTO | BT848_IFORM_AUTO, BT848_IFORM);
+       /* set planar and packed mode trigger points and         */
+       /* set rising edge of inverted GPINTR pin as irq trigger */
+       btwrite(BT848_GPIO_DMA_CTL_PKTP_32|
+               BT848_GPIO_DMA_CTL_PLTP1_16|
+               BT848_GPIO_DMA_CTL_PLTP23_16|
+               BT848_GPIO_DMA_CTL_GPINTC|
+               BT848_GPIO_DMA_CTL_GPINTI,
+               BT848_GPIO_DMA_CTL);
+       val = btv->opt_chroma_agc ? BT848_SCLOOP_CAGC : 0;
+       btwrite(val, BT848_E_SCLOOP);
+       btwrite(val, BT848_O_SCLOOP);
+       btwrite(0x20, BT848_E_VSCALE_HI);
+       btwrite(0x20, BT848_O_VSCALE_HI);
+       btwrite(BT848_ADC_RESERVED | (btv->opt_adc_crush ? BT848_ADC_CRUSH : 0),
+               BT848_ADC);
+       btwrite(whitecrush_upper, BT848_WC_UP);
+       btwrite(whitecrush_lower, BT848_WC_DOWN);
+       if (btv->opt_lumafilter) {
+               btwrite(0, BT848_E_CONTROL);
+               btwrite(0, BT848_O_CONTROL);
+       } else {
+               btwrite(BT848_CONTROL_LDEC, BT848_E_CONTROL);
+               btwrite(BT848_CONTROL_LDEC, BT848_O_CONTROL);
+       }
+       bt848_bright(btv,   btv->bright);
+       bt848_hue(btv,      btv->hue);
+       bt848_contrast(btv, btv->contrast);
+       bt848_sat(btv,      btv->saturation);
+       /* interrupt */
+       init_irqreg(btv);
+ }
+ static void bttv_reinit_bt848(struct bttv *btv)
+ {
+       unsigned long flags;
+       if (bttv_verbose)
+               pr_info("%d: reset, reinitialize\n", btv->c.nr);
+       spin_lock_irqsave(&btv->s_lock,flags);
+       btv->errors=0;
+       bttv_set_dma(btv,0);
+       spin_unlock_irqrestore(&btv->s_lock,flags);
+       init_bt848(btv);
+       btv->pll.pll_current = -1;
+       set_input(btv, btv->input, btv->tvnorm);
+ }
+ static int bttv_g_ctrl(struct file *file, void *priv,
+                                       struct v4l2_control *c)
+ {
+       struct bttv_fh *fh = priv;
+       struct bttv *btv = fh->btv;
+       switch (c->id) {
+       case V4L2_CID_BRIGHTNESS:
+               c->value = btv->bright;
+               break;
+       case V4L2_CID_HUE:
+               c->value = btv->hue;
+               break;
+       case V4L2_CID_CONTRAST:
+               c->value = btv->contrast;
+               break;
+       case V4L2_CID_SATURATION:
+               c->value = btv->saturation;
+               break;
+       case V4L2_CID_AUDIO_MUTE:
+       case V4L2_CID_AUDIO_VOLUME:
+       case V4L2_CID_AUDIO_BALANCE:
+       case V4L2_CID_AUDIO_BASS:
+       case V4L2_CID_AUDIO_TREBLE:
+               bttv_call_all(btv, core, g_ctrl, c);
+               break;
+       case V4L2_CID_PRIVATE_CHROMA_AGC:
+               c->value = btv->opt_chroma_agc;
+               break;
+       case V4L2_CID_PRIVATE_COMBFILTER:
+               c->value = btv->opt_combfilter;
+               break;
+       case V4L2_CID_PRIVATE_LUMAFILTER:
+               c->value = btv->opt_lumafilter;
+               break;
+       case V4L2_CID_PRIVATE_AUTOMUTE:
+               c->value = btv->opt_automute;
+               break;
+       case V4L2_CID_PRIVATE_AGC_CRUSH:
+               c->value = btv->opt_adc_crush;
+               break;
+       case V4L2_CID_PRIVATE_VCR_HACK:
+               c->value = btv->opt_vcr_hack;
+               break;
+       case V4L2_CID_PRIVATE_WHITECRUSH_UPPER:
+               c->value = btv->opt_whitecrush_upper;
+               break;
+       case V4L2_CID_PRIVATE_WHITECRUSH_LOWER:
+               c->value = btv->opt_whitecrush_lower;
+               break;
+       case V4L2_CID_PRIVATE_UV_RATIO:
+               c->value = btv->opt_uv_ratio;
+               break;
+       case V4L2_CID_PRIVATE_FULL_LUMA_RANGE:
+               c->value = btv->opt_full_luma_range;
+               break;
+       case V4L2_CID_PRIVATE_CORING:
+               c->value = btv->opt_coring;
+               break;
+       default:
+               return -EINVAL;
+       }
+       return 0;
+ }
+ static int bttv_s_ctrl(struct file *file, void *f,
+                                       struct v4l2_control *c)
+ {
+       int err;
+       int val;
+       struct bttv_fh *fh = f;
+       struct bttv *btv = fh->btv;
+       err = v4l2_prio_check(&btv->prio, fh->prio);
+       if (0 != err)
+               return err;
+       switch (c->id) {
+       case V4L2_CID_BRIGHTNESS:
+               bt848_bright(btv, c->value);
+               break;
+       case V4L2_CID_HUE:
+               bt848_hue(btv, c->value);
+               break;
+       case V4L2_CID_CONTRAST:
+               bt848_contrast(btv, c->value);
+               break;
+       case V4L2_CID_SATURATION:
+               bt848_sat(btv, c->value);
+               break;
+       case V4L2_CID_AUDIO_MUTE:
+               audio_mute(btv, c->value);
+               /* fall through */
+       case V4L2_CID_AUDIO_VOLUME:
+               if (btv->volume_gpio)
+                       btv->volume_gpio(btv, c->value);
+               bttv_call_all(btv, core, s_ctrl, c);
+               break;
+       case V4L2_CID_AUDIO_BALANCE:
+       case V4L2_CID_AUDIO_BASS:
+       case V4L2_CID_AUDIO_TREBLE:
+               bttv_call_all(btv, core, s_ctrl, c);
+               break;
+       case V4L2_CID_PRIVATE_CHROMA_AGC:
+               btv->opt_chroma_agc = c->value;
+               val = btv->opt_chroma_agc ? BT848_SCLOOP_CAGC : 0;
+               btwrite(val, BT848_E_SCLOOP);
+               btwrite(val, BT848_O_SCLOOP);
+               break;
+       case V4L2_CID_PRIVATE_COMBFILTER:
+               btv->opt_combfilter = c->value;
+               break;
+       case V4L2_CID_PRIVATE_LUMAFILTER:
+               btv->opt_lumafilter = c->value;
+               if (btv->opt_lumafilter) {
+                       btand(~BT848_CONTROL_LDEC, BT848_E_CONTROL);
+                       btand(~BT848_CONTROL_LDEC, BT848_O_CONTROL);
+               } else {
+                       btor(BT848_CONTROL_LDEC, BT848_E_CONTROL);
+                       btor(BT848_CONTROL_LDEC, BT848_O_CONTROL);
+               }
+               break;
+       case V4L2_CID_PRIVATE_AUTOMUTE:
+               btv->opt_automute = c->value;
+               break;
+       case V4L2_CID_PRIVATE_AGC_CRUSH:
+               btv->opt_adc_crush = c->value;
+               btwrite(BT848_ADC_RESERVED |
+                               (btv->opt_adc_crush ? BT848_ADC_CRUSH : 0),
+                               BT848_ADC);
+               break;
+       case V4L2_CID_PRIVATE_VCR_HACK:
+               btv->opt_vcr_hack = c->value;
+               break;
+       case V4L2_CID_PRIVATE_WHITECRUSH_UPPER:
+               btv->opt_whitecrush_upper = c->value;
+               btwrite(c->value, BT848_WC_UP);
+               break;
+       case V4L2_CID_PRIVATE_WHITECRUSH_LOWER:
+               btv->opt_whitecrush_lower = c->value;
+               btwrite(c->value, BT848_WC_DOWN);
+               break;
+       case V4L2_CID_PRIVATE_UV_RATIO:
+               btv->opt_uv_ratio = c->value;
+               bt848_sat(btv, btv->saturation);
+               break;
+       case V4L2_CID_PRIVATE_FULL_LUMA_RANGE:
+               btv->opt_full_luma_range = c->value;
+               btaor((c->value<<7), ~BT848_OFORM_RANGE, BT848_OFORM);
+               break;
+       case V4L2_CID_PRIVATE_CORING:
+               btv->opt_coring = c->value;
+               btaor((c->value<<5), ~BT848_OFORM_CORE32, BT848_OFORM);
+               break;
+       default:
+               return -EINVAL;
+       }
+       return 0;
+ }
+ /* ----------------------------------------------------------------------- */
+ void bttv_gpio_tracking(struct bttv *btv, char *comment)
+ {
+       unsigned int outbits, data;
+       outbits = btread(BT848_GPIO_OUT_EN);
+       data    = btread(BT848_GPIO_DATA);
+       pr_debug("%d: gpio: en=%08x, out=%08x in=%08x [%s]\n",
+                btv->c.nr, outbits, data & outbits, data & ~outbits, comment);
+ }
+ static void bttv_field_count(struct bttv *btv)
+ {
+       int need_count = 0;
+       if (btv->users)
+               need_count++;
+       if (need_count) {
+               /* start field counter */
+               btor(BT848_INT_VSYNC,BT848_INT_MASK);
+       } else {
+               /* stop field counter */
+               btand(~BT848_INT_VSYNC,BT848_INT_MASK);
+               btv->field_count = 0;
+       }
+ }
+ static const struct bttv_format*
+ format_by_fourcc(int fourcc)
+ {
+       unsigned int i;
+       for (i = 0; i < FORMATS; i++) {
+               if (-1 == formats[i].fourcc)
+                       continue;
+               if (formats[i].fourcc == fourcc)
+                       return formats+i;
+       }
+       return NULL;
+ }
+ /* ----------------------------------------------------------------------- */
+ /* misc helpers                                                            */
+ static int
+ bttv_switch_overlay(struct bttv *btv, struct bttv_fh *fh,
+                   struct bttv_buffer *new)
+ {
+       struct bttv_buffer *old;
+       unsigned long flags;
+       int retval = 0;
+       dprintk("switch_overlay: enter [new=%p]\n", new);
+       if (new)
+               new->vb.state = VIDEOBUF_DONE;
+       spin_lock_irqsave(&btv->s_lock,flags);
+       old = btv->screen;
+       btv->screen = new;
+       btv->loop_irq |= 1;
+       bttv_set_dma(btv, 0x03);
+       spin_unlock_irqrestore(&btv->s_lock,flags);
+       if (NULL != old) {
+               dprintk("switch_overlay: old=%p state is %d\n",
+                       old, old->vb.state);
+               bttv_dma_free(&fh->cap,btv, old);
+               kfree(old);
+       }
+       if (NULL == new)
+               free_btres_lock(btv,fh,RESOURCE_OVERLAY);
+       dprintk("switch_overlay: done\n");
+       return retval;
+ }
+ /* ----------------------------------------------------------------------- */
+ /* video4linux (1) interface                                               */
+ static int bttv_prepare_buffer(struct videobuf_queue *q,struct bttv *btv,
+                              struct bttv_buffer *buf,
+                              const struct bttv_format *fmt,
+                              unsigned int width, unsigned int height,
+                              enum v4l2_field field)
+ {
+       struct bttv_fh *fh = q->priv_data;
+       int redo_dma_risc = 0;
+       struct bttv_crop c;
+       int norm;
+       int rc;
+       /* check settings */
+       if (NULL == fmt)
+               return -EINVAL;
+       if (fmt->btformat == BT848_COLOR_FMT_RAW) {
+               width  = RAW_BPL;
+               height = RAW_LINES*2;
+               if (width*height > buf->vb.bsize)
+                       return -EINVAL;
+               buf->vb.size = buf->vb.bsize;
+               /* Make sure tvnorm and vbi_end remain consistent
+                  until we're done. */
+               norm = btv->tvnorm;
+               /* In this mode capturing always starts at defrect.top
+                  (default VDELAY), ignoring cropping parameters. */
+               if (btv->vbi_end > bttv_tvnorms[norm].cropcap.defrect.top) {
+                       return -EINVAL;
+               }
+               c.rect = bttv_tvnorms[norm].cropcap.defrect;
+       } else {
+               norm = btv->tvnorm;
+               c = btv->crop[!!fh->do_crop];
+               if (width < c.min_scaled_width ||
+                   width > c.max_scaled_width ||
+                   height < c.min_scaled_height)
+                       return -EINVAL;
+               switch (field) {
+               case V4L2_FIELD_TOP:
+               case V4L2_FIELD_BOTTOM:
+               case V4L2_FIELD_ALTERNATE:
+                       /* btv->crop counts frame lines. Max. scale
+                          factor is 16:1 for frames, 8:1 for fields. */
+                       if (height * 2 > c.max_scaled_height)
+                               return -EINVAL;
+                       break;
+               default:
+                       if (height > c.max_scaled_height)
+                               return -EINVAL;
+                       break;
+               }
+               buf->vb.size = (width * height * fmt->depth) >> 3;
+               if (0 != buf->vb.baddr  &&  buf->vb.bsize < buf->vb.size)
+                       return -EINVAL;
+       }
+       /* alloc + fill struct bttv_buffer (if changed) */
+       if (buf->vb.width != width || buf->vb.height != height ||
+           buf->vb.field != field ||
+           buf->tvnorm != norm || buf->fmt != fmt ||
+           buf->crop.top != c.rect.top ||
+           buf->crop.left != c.rect.left ||
+           buf->crop.width != c.rect.width ||
+           buf->crop.height != c.rect.height) {
+               buf->vb.width  = width;
+               buf->vb.height = height;
+               buf->vb.field  = field;
+               buf->tvnorm    = norm;
+               buf->fmt       = fmt;
+               buf->crop      = c.rect;
+               redo_dma_risc = 1;
+       }
+       /* alloc risc memory */
+       if (VIDEOBUF_NEEDS_INIT == buf->vb.state) {
+               redo_dma_risc = 1;
+               if (0 != (rc = videobuf_iolock(q,&buf->vb,&btv->fbuf)))
+                       goto fail;
+       }
+       if (redo_dma_risc)
+               if (0 != (rc = bttv_buffer_risc(btv,buf)))
+                       goto fail;
+       buf->vb.state = VIDEOBUF_PREPARED;
+       return 0;
+  fail:
+       bttv_dma_free(q,btv,buf);
+       return rc;
+ }
+ static int
+ buffer_setup(struct videobuf_queue *q, unsigned int *count, unsigned int *size)
+ {
+       struct bttv_fh *fh = q->priv_data;
+       *size = fh->fmt->depth*fh->width*fh->height >> 3;
+       if (0 == *count)
+               *count = gbuffers;
+       if (*size * *count > gbuffers * gbufsize)
+               *count = (gbuffers * gbufsize) / *size;
+       return 0;
+ }
+ static int
+ buffer_prepare(struct videobuf_queue *q, struct videobuf_buffer *vb,
+              enum v4l2_field field)
+ {
+       struct bttv_buffer *buf = container_of(vb,struct bttv_buffer,vb);
+       struct bttv_fh *fh = q->priv_data;
+       return bttv_prepare_buffer(q,fh->btv, buf, fh->fmt,
+                                  fh->width, fh->height, field);
+ }
+ static void
+ buffer_queue(struct videobuf_queue *q, struct videobuf_buffer *vb)
+ {
+       struct bttv_buffer *buf = container_of(vb,struct bttv_buffer,vb);
+       struct bttv_fh *fh = q->priv_data;
+       struct bttv    *btv = fh->btv;
+       buf->vb.state = VIDEOBUF_QUEUED;
+       list_add_tail(&buf->vb.queue,&btv->capture);
+       if (!btv->curr.frame_irq) {
+               btv->loop_irq |= 1;
+               bttv_set_dma(btv, 0x03);
+       }
+ }
+ static void buffer_release(struct videobuf_queue *q, struct videobuf_buffer *vb)
+ {
+       struct bttv_buffer *buf = container_of(vb,struct bttv_buffer,vb);
+       struct bttv_fh *fh = q->priv_data;
+       bttv_dma_free(q,fh->btv,buf);
+ }
+ static struct videobuf_queue_ops bttv_video_qops = {
+       .buf_setup    = buffer_setup,
+       .buf_prepare  = buffer_prepare,
+       .buf_queue    = buffer_queue,
+       .buf_release  = buffer_release,
+ };
+ static int bttv_s_std(struct file *file, void *priv, v4l2_std_id *id)
+ {
+       struct bttv_fh *fh  = priv;
+       struct bttv *btv = fh->btv;
+       unsigned int i;
+       int err;
+       err = v4l2_prio_check(&btv->prio, fh->prio);
+       if (err)
+               goto err;
+       for (i = 0; i < BTTV_TVNORMS; i++)
+               if (*id & bttv_tvnorms[i].v4l2_id)
+                       break;
+       if (i == BTTV_TVNORMS) {
+               err = -EINVAL;
+               goto err;
+       }
+       set_tvnorm(btv, i);
+ err:
+       return err;
+ }
+ static int bttv_querystd(struct file *file, void *f, v4l2_std_id *id)
+ {
+       struct bttv_fh *fh = f;
+       struct bttv *btv = fh->btv;
+       if (btread(BT848_DSTATUS) & BT848_DSTATUS_NUML)
+               *id = V4L2_STD_625_50;
+       else
+               *id = V4L2_STD_525_60;
+       return 0;
+ }
+ static int bttv_enum_input(struct file *file, void *priv,
+                                       struct v4l2_input *i)
+ {
+       struct bttv_fh *fh = priv;
+       struct bttv *btv = fh->btv;
+       int rc = 0;
+       if (i->index >= bttv_tvcards[btv->c.type].video_inputs) {
+               rc = -EINVAL;
+               goto err;
+       }
+       i->type     = V4L2_INPUT_TYPE_CAMERA;
+       i->audioset = 1;
+       if (btv->tuner_type != TUNER_ABSENT && i->index == 0) {
+               sprintf(i->name, "Television");
+               i->type  = V4L2_INPUT_TYPE_TUNER;
+               i->tuner = 0;
+       } else if (i->index == btv->svhs) {
+               sprintf(i->name, "S-Video");
+       } else {
+               sprintf(i->name, "Composite%d", i->index);
+       }
+       if (i->index == btv->input) {
+               __u32 dstatus = btread(BT848_DSTATUS);
+               if (0 == (dstatus & BT848_DSTATUS_PRES))
+                       i->status |= V4L2_IN_ST_NO_SIGNAL;
+               if (0 == (dstatus & BT848_DSTATUS_HLOC))
+                       i->status |= V4L2_IN_ST_NO_H_LOCK;
+       }
+       i->std = BTTV_NORMS;
+ err:
+       return rc;
+ }
+ static int bttv_g_input(struct file *file, void *priv, unsigned int *i)
+ {
+       struct bttv_fh *fh = priv;
+       struct bttv *btv = fh->btv;
+       *i = btv->input;
+       return 0;
+ }
+ static int bttv_s_input(struct file *file, void *priv, unsigned int i)
+ {
+       struct bttv_fh *fh  = priv;
+       struct bttv *btv = fh->btv;
+       int err;
+       err = v4l2_prio_check(&btv->prio, fh->prio);
+       if (unlikely(err))
+               goto err;
+       if (i > bttv_tvcards[btv->c.type].video_inputs) {
+               err = -EINVAL;
+               goto err;
+       }
+       set_input(btv, i, btv->tvnorm);
+ err:
+       return 0;
+ }
+ static int bttv_s_tuner(struct file *file, void *priv,
+                                       struct v4l2_tuner *t)
+ {
+       struct bttv_fh *fh  = priv;
+       struct bttv *btv = fh->btv;
+       int err;
+       if (unlikely(0 != t->index))
+               return -EINVAL;
+       if (unlikely(btv->tuner_type == TUNER_ABSENT)) {
+               err = -EINVAL;
+               goto err;
+       }
+       err = v4l2_prio_check(&btv->prio, fh->prio);
+       if (unlikely(err))
+               goto err;
+       bttv_call_all(btv, tuner, s_tuner, t);
+       if (btv->audio_mode_gpio)
+               btv->audio_mode_gpio(btv, t, 1);
+ err:
+       return 0;
+ }
+ static int bttv_g_frequency(struct file *file, void *priv,
+                                       struct v4l2_frequency *f)
+ {
+       struct bttv_fh *fh  = priv;
+       struct bttv *btv = fh->btv;
+       f->type = btv->radio_user ? V4L2_TUNER_RADIO : V4L2_TUNER_ANALOG_TV;
+       f->frequency = btv->freq;
+       return 0;
+ }
+ static int bttv_s_frequency(struct file *file, void *priv,
+                                       struct v4l2_frequency *f)
+ {
+       struct bttv_fh *fh  = priv;
+       struct bttv *btv = fh->btv;
+       int err;
+       if (unlikely(f->tuner != 0))
+               return -EINVAL;
+       err = v4l2_prio_check(&btv->prio, fh->prio);
+       if (unlikely(err))
+               goto err;
+       if (unlikely(f->type != (btv->radio_user
+               ? V4L2_TUNER_RADIO : V4L2_TUNER_ANALOG_TV))) {
+               err = -EINVAL;
+               goto err;
+       }
+       btv->freq = f->frequency;
+       bttv_call_all(btv, tuner, s_frequency, f);
+       if (btv->has_matchbox && btv->radio_user)
+               tea5757_set_freq(btv, btv->freq);
+ err:
+       return 0;
+ }
+ static int bttv_log_status(struct file *file, void *f)
+ {
+       struct bttv_fh *fh  = f;
+       struct bttv *btv = fh->btv;
+       bttv_call_all(btv, core, log_status);
+       return 0;
+ }
+ #ifdef CONFIG_VIDEO_ADV_DEBUG
+ static int bttv_g_register(struct file *file, void *f,
+                                       struct v4l2_dbg_register *reg)
+ {
+       struct bttv_fh *fh = f;
+       struct bttv *btv = fh->btv;
+       if (!capable(CAP_SYS_ADMIN))
+               return -EPERM;
+       if (!v4l2_chip_match_host(&reg->match))
+               return -EINVAL;
+       /* bt848 has a 12-bit register space */
+       reg->reg &= 0xfff;
+       reg->val = btread(reg->reg);
+       reg->size = 1;
+       return 0;
+ }
+ static int bttv_s_register(struct file *file, void *f,
+                                       struct v4l2_dbg_register *reg)
+ {
+       struct bttv_fh *fh = f;
+       struct bttv *btv = fh->btv;
+       if (!capable(CAP_SYS_ADMIN))
+               return -EPERM;
+       if (!v4l2_chip_match_host(&reg->match))
+               return -EINVAL;
+       /* bt848 has a 12-bit register space */
+       reg->reg &= 0xfff;
+       btwrite(reg->val, reg->reg);
+       return 0;
+ }
+ #endif
+ /* Given cropping boundaries b and the scaled width and height of a
+    single field or frame, which must not exceed hardware limits, this
+    function adjusts the cropping parameters c. */
+ static void
+ bttv_crop_adjust      (struct bttv_crop *             c,
+                        const struct v4l2_rect *       b,
+                        __s32                          width,
+                        __s32                          height,
+                        enum v4l2_field                field)
+ {
+       __s32 frame_height = height << !V4L2_FIELD_HAS_BOTH(field);
+       __s32 max_left;
+       __s32 max_top;
+       if (width < c->min_scaled_width) {
+               /* Max. hor. scale factor 16:1. */
+               c->rect.width = width * 16;
+       } else if (width > c->max_scaled_width) {
+               /* Min. hor. scale factor 1:1. */
+               c->rect.width = width;
+               max_left = b->left + b->width - width;
+               max_left = min(max_left, (__s32) MAX_HDELAY);
+               if (c->rect.left > max_left)
+                       c->rect.left = max_left;
+       }
+       if (height < c->min_scaled_height) {
+               /* Max. vert. scale factor 16:1, single fields 8:1. */
+               c->rect.height = height * 16;
+       } else if (frame_height > c->max_scaled_height) {
+               /* Min. vert. scale factor 1:1.
+                  Top and height count field lines times two. */
+               c->rect.height = (frame_height + 1) & ~1;
+               max_top = b->top + b->height - c->rect.height;
+               if (c->rect.top > max_top)
+                       c->rect.top = max_top;
+       }
+       bttv_crop_calc_limits(c);
+ }
+ /* Returns an error if scaling to a frame or single field with the given
+    width and height is not possible with the current cropping parameters
+    and width aligned according to width_mask. If adjust_size is TRUE the
+    function may adjust the width and/or height instead, rounding width
+    to (width + width_bias) & width_mask. If adjust_crop is TRUE it may
+    also adjust the current cropping parameters to get closer to the
+    desired image size. */
+ static int
+ limit_scaled_size_lock       (struct bttv_fh *               fh,
+                        __s32 *                        width,
+                        __s32 *                        height,
+                        enum v4l2_field                field,
+                        unsigned int                   width_mask,
+                        unsigned int                   width_bias,
+                        int                            adjust_size,
+                        int                            adjust_crop)
+ {
+       struct bttv *btv = fh->btv;
+       const struct v4l2_rect *b;
+       struct bttv_crop *c;
+       __s32 min_width;
+       __s32 min_height;
+       __s32 max_width;
+       __s32 max_height;
+       int rc;
+       BUG_ON((int) width_mask >= 0 ||
+              width_bias >= (unsigned int) -width_mask);
+       /* Make sure tvnorm, vbi_end and the current cropping parameters
+          remain consistent until we're done. */
+       b = &bttv_tvnorms[btv->tvnorm].cropcap.bounds;
+       /* Do crop - use current, don't - use default parameters. */
+       c = &btv->crop[!!fh->do_crop];
+       if (fh->do_crop
+           && adjust_size
+           && adjust_crop
+           && !locked_btres(btv, VIDEO_RESOURCES)) {
+               min_width = 48;
+               min_height = 32;
+               /* We cannot scale up. When the scaled image is larger
+                  than crop.rect we adjust the crop.rect as required
+                  by the V4L2 spec, hence cropcap.bounds are our limit. */
+               max_width = min(b->width, (__s32) MAX_HACTIVE);
+               max_height = b->height;
+               /* We cannot capture the same line as video and VBI data.
+                  Note btv->vbi_end is really a minimum, see
+                  bttv_vbi_try_fmt(). */
+               if (btv->vbi_end > b->top) {
+                       max_height -= btv->vbi_end - b->top;
+                       rc = -EBUSY;
+                       if (min_height > max_height)
+                               goto fail;
+               }
+       } else {
+               rc = -EBUSY;
+               if (btv->vbi_end > c->rect.top)
+                       goto fail;
+               min_width  = c->min_scaled_width;
+               min_height = c->min_scaled_height;
+               max_width  = c->max_scaled_width;
+               max_height = c->max_scaled_height;
+               adjust_crop = 0;
+       }
+       min_width = (min_width - width_mask - 1) & width_mask;
+       max_width = max_width & width_mask;
+       /* Max. scale factor is 16:1 for frames, 8:1 for fields. */
+       min_height = min_height;
+       /* Min. scale factor is 1:1. */
+       max_height >>= !V4L2_FIELD_HAS_BOTH(field);
+       if (adjust_size) {
+               *width = clamp(*width, min_width, max_width);
+               *height = clamp(*height, min_height, max_height);
+               /* Round after clamping to avoid overflow. */
+               *width = (*width + width_bias) & width_mask;
+               if (adjust_crop) {
+                       bttv_crop_adjust(c, b, *width, *height, field);
+                       if (btv->vbi_end > c->rect.top) {
+                               /* Move the crop window out of the way. */
+                               c->rect.top = btv->vbi_end;
+                       }
+               }
+       } else {
+               rc = -EINVAL;
+               if (*width  < min_width ||
+                   *height < min_height ||
+                   *width  > max_width ||
+                   *height > max_height ||
+                   0 != (*width & ~width_mask))
+                       goto fail;
+       }
+       rc = 0; /* success */
+  fail:
+       return rc;
+ }
+ /* Returns an error if the given overlay window dimensions are not
+    possible with the current cropping parameters. If adjust_size is
+    TRUE the function may adjust the window width and/or height
+    instead, however it always rounds the horizontal position and
+    width as btcx_align() does. If adjust_crop is TRUE the function
+    may also adjust the current cropping parameters to get closer
+    to the desired window size. */
+ static int
+ verify_window_lock            (struct bttv_fh *               fh,
+                        struct v4l2_window *           win,
+                        int                            adjust_size,
+                        int                            adjust_crop)
+ {
+       enum v4l2_field field;
+       unsigned int width_mask;
+       int rc;
+       if (win->w.width  < 48 || win->w.height < 32)
+               return -EINVAL;
+       if (win->clipcount > 2048)
+               return -EINVAL;
+       field = win->field;
+       if (V4L2_FIELD_ANY == field) {
+               __s32 height2;
+               height2 = fh->btv->crop[!!fh->do_crop].rect.height >> 1;
+               field = (win->w.height > height2)
+                       ? V4L2_FIELD_INTERLACED
+                       : V4L2_FIELD_TOP;
+       }
+       switch (field) {
+       case V4L2_FIELD_TOP:
+       case V4L2_FIELD_BOTTOM:
+       case V4L2_FIELD_INTERLACED:
+               break;
+       default:
+               return -EINVAL;
+       }
+       /* 4-byte alignment. */
+       if (NULL == fh->ovfmt)
+               return -EINVAL;
+       width_mask = ~0;
+       switch (fh->ovfmt->depth) {
+       case 8:
+       case 24:
+               width_mask = ~3;
+               break;
+       case 16:
+               width_mask = ~1;
+               break;
+       case 32:
+               break;
+       default:
+               BUG();
+       }
+       win->w.width -= win->w.left & ~width_mask;
+       win->w.left = (win->w.left - width_mask - 1) & width_mask;
+       rc = limit_scaled_size_lock(fh, &win->w.width, &win->w.height,
+                              field, width_mask,
+                              /* width_bias: round down */ 0,
+                              adjust_size, adjust_crop);
+       if (0 != rc)
+               return rc;
+       win->field = field;
+       return 0;
+ }
+ static int setup_window_lock(struct bttv_fh *fh, struct bttv *btv,
+                       struct v4l2_window *win, int fixup)
+ {
+       struct v4l2_clip *clips = NULL;
+       int n,size,retval = 0;
+       if (NULL == fh->ovfmt)
+               return -EINVAL;
+       if (!(fh->ovfmt->flags & FORMAT_FLAGS_PACKED))
+               return -EINVAL;
+       retval = verify_window_lock(fh, win,
+                              /* adjust_size */ fixup,
+                              /* adjust_crop */ fixup);
+       if (0 != retval)
+               return retval;
+       /* copy clips  --  luckily v4l1 + v4l2 are binary
+          compatible here ...*/
+       n = win->clipcount;
+       size = sizeof(*clips)*(n+4);
+       clips = kmalloc(size,GFP_KERNEL);
+       if (NULL == clips)
+               return -ENOMEM;
+       if (n > 0) {
+               if (copy_from_user(clips,win->clips,sizeof(struct v4l2_clip)*n)) {
+                       kfree(clips);
+                       return -EFAULT;
+               }
+       }
+       /* clip against screen */
+       if (NULL != btv->fbuf.base)
+               n = btcx_screen_clips(btv->fbuf.fmt.width, btv->fbuf.fmt.height,
+                                     &win->w, clips, n);
+       btcx_sort_clips(clips,n);
+       /* 4-byte alignments */
+       switch (fh->ovfmt->depth) {
+       case 8:
+       case 24:
+               btcx_align(&win->w, clips, n, 3);
+               break;
+       case 16:
+               btcx_align(&win->w, clips, n, 1);
+               break;
+       case 32:
+               /* no alignment fixups needed */
+               break;
+       default:
+               BUG();
+       }
+       kfree(fh->ov.clips);
+       fh->ov.clips    = clips;
+       fh->ov.nclips   = n;
+       fh->ov.w        = win->w;
+       fh->ov.field    = win->field;
+       fh->ov.setup_ok = 1;
+       btv->init.ov.w.width   = win->w.width;
+       btv->init.ov.w.height  = win->w.height;
+       btv->init.ov.field     = win->field;
+       /* update overlay if needed */
+       retval = 0;
+       if (check_btres(fh, RESOURCE_OVERLAY)) {
+               struct bttv_buffer *new;
+               new = videobuf_sg_alloc(sizeof(*new));
+               new->crop = btv->crop[!!fh->do_crop].rect;
+               bttv_overlay_risc(btv, &fh->ov, fh->ovfmt, new);
+               retval = bttv_switch_overlay(btv,fh,new);
+       }
+       return retval;
+ }
+ /* ----------------------------------------------------------------------- */
+ static struct videobuf_queue* bttv_queue(struct bttv_fh *fh)
+ {
+       struct videobuf_queue* q = NULL;
+       switch (fh->type) {
+       case V4L2_BUF_TYPE_VIDEO_CAPTURE:
+               q = &fh->cap;
+               break;
+       case V4L2_BUF_TYPE_VBI_CAPTURE:
+               q = &fh->vbi;
+               break;
+       default:
+               BUG();
+       }
+       return q;
+ }
+ static int bttv_resource(struct bttv_fh *fh)
+ {
+       int res = 0;
+       switch (fh->type) {
+       case V4L2_BUF_TYPE_VIDEO_CAPTURE:
+               res = RESOURCE_VIDEO_STREAM;
+               break;
+       case V4L2_BUF_TYPE_VBI_CAPTURE:
+               res = RESOURCE_VBI;
+               break;
+       default:
+               BUG();
+       }
+       return res;
+ }
+ static int bttv_switch_type(struct bttv_fh *fh, enum v4l2_buf_type type)
+ {
+       struct videobuf_queue *q = bttv_queue(fh);
+       int res = bttv_resource(fh);
+       if (check_btres(fh,res))
+               return -EBUSY;
+       if (videobuf_queue_is_busy(q))
+               return -EBUSY;
+       fh->type = type;
+       return 0;
+ }
+ static void
+ pix_format_set_size     (struct v4l2_pix_format *       f,
+                        const struct bttv_format *     fmt,
+                        unsigned int                   width,
+                        unsigned int                   height)
+ {
+       f->width = width;
+       f->height = height;
+       if (fmt->flags & FORMAT_FLAGS_PLANAR) {
+               f->bytesperline = width; /* Y plane */
+               f->sizeimage = (width * height * fmt->depth) >> 3;
+       } else {
+               f->bytesperline = (width * fmt->depth) >> 3;
+               f->sizeimage = height * f->bytesperline;
+       }
+ }
+ static int bttv_g_fmt_vid_cap(struct file *file, void *priv,
+                                       struct v4l2_format *f)
+ {
+       struct bttv_fh *fh  = priv;
+       pix_format_set_size(&f->fmt.pix, fh->fmt,
+                               fh->width, fh->height);
+       f->fmt.pix.field        = fh->cap.field;
+       f->fmt.pix.pixelformat  = fh->fmt->fourcc;
+       return 0;
+ }
+ static int bttv_g_fmt_vid_overlay(struct file *file, void *priv,
+                                       struct v4l2_format *f)
+ {
+       struct bttv_fh *fh  = priv;
+       f->fmt.win.w     = fh->ov.w;
+       f->fmt.win.field = fh->ov.field;
+       return 0;
+ }
+ static int bttv_try_fmt_vid_cap(struct file *file, void *priv,
+                                               struct v4l2_format *f)
+ {
+       const struct bttv_format *fmt;
+       struct bttv_fh *fh = priv;
+       struct bttv *btv = fh->btv;
+       enum v4l2_field field;
+       __s32 width, height;
+       int rc;
+       fmt = format_by_fourcc(f->fmt.pix.pixelformat);
+       if (NULL == fmt)
+               return -EINVAL;
+       field = f->fmt.pix.field;
+       if (V4L2_FIELD_ANY == field) {
+               __s32 height2;
+               height2 = btv->crop[!!fh->do_crop].rect.height >> 1;
+               field = (f->fmt.pix.height > height2)
+                       ? V4L2_FIELD_INTERLACED
+                       : V4L2_FIELD_BOTTOM;
+       }
+       if (V4L2_FIELD_SEQ_BT == field)
+               field = V4L2_FIELD_SEQ_TB;
+       switch (field) {
+       case V4L2_FIELD_TOP:
+       case V4L2_FIELD_BOTTOM:
+       case V4L2_FIELD_ALTERNATE:
+       case V4L2_FIELD_INTERLACED:
+               break;
+       case V4L2_FIELD_SEQ_TB:
+               if (fmt->flags & FORMAT_FLAGS_PLANAR)
+                       return -EINVAL;
+               break;
+       default:
+               return -EINVAL;
+       }
+       width = f->fmt.pix.width;
+       height = f->fmt.pix.height;
+       rc = limit_scaled_size_lock(fh, &width, &height, field,
+                              /* width_mask: 4 pixels */ ~3,
+                              /* width_bias: nearest */ 2,
+                              /* adjust_size */ 1,
+                              /* adjust_crop */ 0);
+       if (0 != rc)
+               return rc;
+       /* update data for the application */
+       f->fmt.pix.field = field;
+       pix_format_set_size(&f->fmt.pix, fmt, width, height);
+       return 0;
+ }
+ static int bttv_try_fmt_vid_overlay(struct file *file, void *priv,
+                                               struct v4l2_format *f)
+ {
+       struct bttv_fh *fh = priv;
+       return verify_window_lock(fh, &f->fmt.win,
+                       /* adjust_size */ 1,
+                       /* adjust_crop */ 0);
+ }
+ static int bttv_s_fmt_vid_cap(struct file *file, void *priv,
+                               struct v4l2_format *f)
+ {
+       int retval;
+       const struct bttv_format *fmt;
+       struct bttv_fh *fh = priv;
+       struct bttv *btv = fh->btv;
+       __s32 width, height;
+       enum v4l2_field field;
+       retval = bttv_switch_type(fh, f->type);
+       if (0 != retval)
+               return retval;
+       retval = bttv_try_fmt_vid_cap(file, priv, f);
+       if (0 != retval)
+               return retval;
+       width = f->fmt.pix.width;
+       height = f->fmt.pix.height;
+       field = f->fmt.pix.field;
+       retval = limit_scaled_size_lock(fh, &width, &height, f->fmt.pix.field,
+                              /* width_mask: 4 pixels */ ~3,
+                              /* width_bias: nearest */ 2,
+                              /* adjust_size */ 1,
+                              /* adjust_crop */ 1);
+       if (0 != retval)
+               return retval;
+       f->fmt.pix.field = field;
+       fmt = format_by_fourcc(f->fmt.pix.pixelformat);
+       /* update our state informations */
+       fh->fmt              = fmt;
+       fh->cap.field        = f->fmt.pix.field;
+       fh->cap.last         = V4L2_FIELD_NONE;
+       fh->width            = f->fmt.pix.width;
+       fh->height           = f->fmt.pix.height;
+       btv->init.fmt        = fmt;
+       btv->init.width      = f->fmt.pix.width;
+       btv->init.height     = f->fmt.pix.height;
+       return 0;
+ }
+ static int bttv_s_fmt_vid_overlay(struct file *file, void *priv,
+                               struct v4l2_format *f)
+ {
+       struct bttv_fh *fh = priv;
+       struct bttv *btv = fh->btv;
+       if (no_overlay > 0) {
+               pr_err("V4L2_BUF_TYPE_VIDEO_OVERLAY: no_overlay\n");
+               return -EINVAL;
+       }
+       return setup_window_lock(fh, btv, &f->fmt.win, 1);
+ }
+ static int bttv_querycap(struct file *file, void  *priv,
+                               struct v4l2_capability *cap)
+ {
+       struct bttv_fh *fh = priv;
+       struct bttv *btv = fh->btv;
+       if (0 == v4l2)
+               return -EINVAL;
+       strlcpy(cap->driver, "bttv", sizeof(cap->driver));
+       strlcpy(cap->card, btv->video_dev->name, sizeof(cap->card));
+       snprintf(cap->bus_info, sizeof(cap->bus_info),
+                "PCI:%s", pci_name(btv->c.pci));
+       cap->capabilities =
+               V4L2_CAP_VIDEO_CAPTURE |
+               V4L2_CAP_VBI_CAPTURE |
+               V4L2_CAP_READWRITE |
+               V4L2_CAP_STREAMING;
+       if (no_overlay <= 0)
+               cap->capabilities |= V4L2_CAP_VIDEO_OVERLAY;
+       /*
+        * No need to lock here: those vars are initialized during board
+        * probe and remains untouched during the rest of the driver lifecycle
+        */
+       if (btv->has_saa6588)
+               cap->capabilities |= V4L2_CAP_RDS_CAPTURE;
+       if (btv->tuner_type != TUNER_ABSENT)
+               cap->capabilities |= V4L2_CAP_TUNER;
+       return 0;
+ }
+ static int bttv_enum_fmt_cap_ovr(struct v4l2_fmtdesc *f)
+ {
+       int index = -1, i;
+       for (i = 0; i < FORMATS; i++) {
+               if (formats[i].fourcc != -1)
+                       index++;
+               if ((unsigned int)index == f->index)
+                       break;
+       }
+       if (FORMATS == i)
+               return -EINVAL;
+       f->pixelformat = formats[i].fourcc;
+       strlcpy(f->description, formats[i].name, sizeof(f->description));
+       return i;
+ }
+ static int bttv_enum_fmt_vid_cap(struct file *file, void  *priv,
+                               struct v4l2_fmtdesc *f)
+ {
+       int rc = bttv_enum_fmt_cap_ovr(f);
+       if (rc < 0)
+               return rc;
+       return 0;
+ }
+ static int bttv_enum_fmt_vid_overlay(struct file *file, void  *priv,
+                                       struct v4l2_fmtdesc *f)
+ {
+       int rc;
+       if (no_overlay > 0) {
+               pr_err("V4L2_BUF_TYPE_VIDEO_OVERLAY: no_overlay\n");
+               return -EINVAL;
+       }
+       rc = bttv_enum_fmt_cap_ovr(f);
+       if (rc < 0)
+               return rc;
+       if (!(formats[rc].flags & FORMAT_FLAGS_PACKED))
+               return -EINVAL;
+       return 0;
+ }
+ static int bttv_g_fbuf(struct file *file, void *f,
+                               struct v4l2_framebuffer *fb)
+ {
+       struct bttv_fh *fh = f;
+       struct bttv *btv = fh->btv;
+       *fb = btv->fbuf;
+       fb->capability = V4L2_FBUF_CAP_LIST_CLIPPING;
+       if (fh->ovfmt)
+               fb->fmt.pixelformat  = fh->ovfmt->fourcc;
+       return 0;
+ }
+ static int bttv_overlay(struct file *file, void *f, unsigned int on)
+ {
+       struct bttv_fh *fh = f;
+       struct bttv *btv = fh->btv;
+       struct bttv_buffer *new;
+       int retval = 0;
+       if (on) {
+               /* verify args */
+               if (unlikely(!btv->fbuf.base)) {
+                       return -EINVAL;
+               }
+               if (unlikely(!fh->ov.setup_ok)) {
+                       dprintk("%d: overlay: !setup_ok\n", btv->c.nr);
+                       retval = -EINVAL;
+               }
+               if (retval)
+                       return retval;
+       }
+       if (!check_alloc_btres_lock(btv, fh, RESOURCE_OVERLAY))
+               return -EBUSY;
+       if (on) {
+               fh->ov.tvnorm = btv->tvnorm;
+               new = videobuf_sg_alloc(sizeof(*new));
+               new->crop = btv->crop[!!fh->do_crop].rect;
+               bttv_overlay_risc(btv, &fh->ov, fh->ovfmt, new);
+       } else {
+               new = NULL;
+       }
+       /* switch over */
+       retval = bttv_switch_overlay(btv, fh, new);
+       return retval;
+ }
+ static int bttv_s_fbuf(struct file *file, void *f,
+                               const struct v4l2_framebuffer *fb)
+ {
+       struct bttv_fh *fh = f;
+       struct bttv *btv = fh->btv;
+       const struct bttv_format *fmt;
+       int retval;
+       if (!capable(CAP_SYS_ADMIN) &&
+               !capable(CAP_SYS_RAWIO))
+               return -EPERM;
+       /* check args */
+       fmt = format_by_fourcc(fb->fmt.pixelformat);
+       if (NULL == fmt)
+               return -EINVAL;
+       if (0 == (fmt->flags & FORMAT_FLAGS_PACKED))
+               return -EINVAL;
+       retval = -EINVAL;
+       if (fb->flags & V4L2_FBUF_FLAG_OVERLAY) {
+               __s32 width = fb->fmt.width;
+               __s32 height = fb->fmt.height;
+               retval = limit_scaled_size_lock(fh, &width, &height,
+                                          V4L2_FIELD_INTERLACED,
+                                          /* width_mask */ ~3,
+                                          /* width_bias */ 2,
+                                          /* adjust_size */ 0,
+                                          /* adjust_crop */ 0);
+               if (0 != retval)
+                       return retval;
+       }
+       /* ok, accept it */
+       btv->fbuf.base       = fb->base;
+       btv->fbuf.fmt.width  = fb->fmt.width;
+       btv->fbuf.fmt.height = fb->fmt.height;
+       if (0 != fb->fmt.bytesperline)
+               btv->fbuf.fmt.bytesperline = fb->fmt.bytesperline;
+       else
+               btv->fbuf.fmt.bytesperline = btv->fbuf.fmt.width*fmt->depth/8;
+       retval = 0;
+       fh->ovfmt = fmt;
+       btv->init.ovfmt = fmt;
+       if (fb->flags & V4L2_FBUF_FLAG_OVERLAY) {
+               fh->ov.w.left   = 0;
+               fh->ov.w.top    = 0;
+               fh->ov.w.width  = fb->fmt.width;
+               fh->ov.w.height = fb->fmt.height;
+               btv->init.ov.w.width  = fb->fmt.width;
+               btv->init.ov.w.height = fb->fmt.height;
+                       kfree(fh->ov.clips);
+               fh->ov.clips = NULL;
+               fh->ov.nclips = 0;
+               if (check_btres(fh, RESOURCE_OVERLAY)) {
+                       struct bttv_buffer *new;
+                       new = videobuf_sg_alloc(sizeof(*new));
+                       new->crop = btv->crop[!!fh->do_crop].rect;
+                       bttv_overlay_risc(btv, &fh->ov, fh->ovfmt, new);
+                       retval = bttv_switch_overlay(btv, fh, new);
+               }
+       }
+       return retval;
+ }
+ static int bttv_reqbufs(struct file *file, void *priv,
+                               struct v4l2_requestbuffers *p)
+ {
+       struct bttv_fh *fh = priv;
+       return videobuf_reqbufs(bttv_queue(fh), p);
+ }
+ static int bttv_querybuf(struct file *file, void *priv,
+                               struct v4l2_buffer *b)
+ {
+       struct bttv_fh *fh = priv;
+       return videobuf_querybuf(bttv_queue(fh), b);
+ }
+ static int bttv_qbuf(struct file *file, void *priv, struct v4l2_buffer *b)
+ {
+       struct bttv_fh *fh = priv;
+       struct bttv *btv = fh->btv;
+       int res = bttv_resource(fh);
+       if (!check_alloc_btres_lock(btv, fh, res))
+               return -EBUSY;
+       return videobuf_qbuf(bttv_queue(fh), b);
+ }
+ static int bttv_dqbuf(struct file *file, void *priv, struct v4l2_buffer *b)
+ {
+       struct bttv_fh *fh = priv;
+       return videobuf_dqbuf(bttv_queue(fh), b,
+                       file->f_flags & O_NONBLOCK);
+ }
+ static int bttv_streamon(struct file *file, void *priv,
+                                       enum v4l2_buf_type type)
+ {
+       struct bttv_fh *fh = priv;
+       struct bttv *btv = fh->btv;
+       int res = bttv_resource(fh);
+       if (!check_alloc_btres_lock(btv, fh, res))
+               return -EBUSY;
+       return videobuf_streamon(bttv_queue(fh));
+ }
+ static int bttv_streamoff(struct file *file, void *priv,
+                                       enum v4l2_buf_type type)
+ {
+       struct bttv_fh *fh = priv;
+       struct bttv *btv = fh->btv;
+       int retval;
+       int res = bttv_resource(fh);
+       retval = videobuf_streamoff(bttv_queue(fh));
+       if (retval < 0)
+               return retval;
+       free_btres_lock(btv, fh, res);
+       return 0;
+ }
+ static int bttv_queryctrl(struct file *file, void *priv,
+                                       struct v4l2_queryctrl *c)
+ {
+       struct bttv_fh *fh = priv;
+       struct bttv *btv = fh->btv;
+       const struct v4l2_queryctrl *ctrl;
+       if ((c->id <  V4L2_CID_BASE ||
+            c->id >= V4L2_CID_LASTP1) &&
+           (c->id <  V4L2_CID_PRIVATE_BASE ||
+            c->id >= V4L2_CID_PRIVATE_LASTP1))
+               return -EINVAL;
+       if (!btv->volume_gpio && (c->id == V4L2_CID_AUDIO_VOLUME))
+               *c = no_ctl;
+       else {
+               ctrl = ctrl_by_id(c->id);
+               *c = (NULL != ctrl) ? *ctrl : no_ctl;
+       }
+       return 0;
+ }
+ static int bttv_g_parm(struct file *file, void *f,
+                               struct v4l2_streamparm *parm)
+ {
+       struct bttv_fh *fh = f;
+       struct bttv *btv = fh->btv;
+       v4l2_video_std_frame_period(bttv_tvnorms[btv->tvnorm].v4l2_id,
+                                   &parm->parm.capture.timeperframe);
+       return 0;
+ }
+ static int bttv_g_tuner(struct file *file, void *priv,
+                               struct v4l2_tuner *t)
+ {
+       struct bttv_fh *fh = priv;
+       struct bttv *btv = fh->btv;
+       if (btv->tuner_type == TUNER_ABSENT)
+               return -EINVAL;
+       if (0 != t->index)
+               return -EINVAL;
+       t->rxsubchans = V4L2_TUNER_SUB_MONO;
+       bttv_call_all(btv, tuner, g_tuner, t);
+       strcpy(t->name, "Television");
+       t->capability = V4L2_TUNER_CAP_NORM;
+       t->type       = V4L2_TUNER_ANALOG_TV;
+       if (btread(BT848_DSTATUS)&BT848_DSTATUS_HLOC)
+               t->signal = 0xffff;
+       if (btv->audio_mode_gpio)
+               btv->audio_mode_gpio(btv, t, 0);
+       return 0;
+ }
+ static int bttv_g_priority(struct file *file, void *f, enum v4l2_priority *p)
+ {
+       struct bttv_fh *fh = f;
+       struct bttv *btv = fh->btv;
+       *p = v4l2_prio_max(&btv->prio);
+       return 0;
+ }
+ static int bttv_s_priority(struct file *file, void *f,
+                                       enum v4l2_priority prio)
+ {
+       struct bttv_fh *fh = f;
+       struct bttv *btv = fh->btv;
+       int     rc;
+       rc = v4l2_prio_change(&btv->prio, &fh->prio, prio);
+       return rc;
+ }
+ static int bttv_cropcap(struct file *file, void *priv,
+                               struct v4l2_cropcap *cap)
+ {
+       struct bttv_fh *fh = priv;
+       struct bttv *btv = fh->btv;
+       if (cap->type != V4L2_BUF_TYPE_VIDEO_CAPTURE &&
+           cap->type != V4L2_BUF_TYPE_VIDEO_OVERLAY)
+               return -EINVAL;
+       *cap = bttv_tvnorms[btv->tvnorm].cropcap;
+       return 0;
+ }
+ static int bttv_g_crop(struct file *file, void *f, struct v4l2_crop *crop)
+ {
+       struct bttv_fh *fh = f;
+       struct bttv *btv = fh->btv;
+       if (crop->type != V4L2_BUF_TYPE_VIDEO_CAPTURE &&
+           crop->type != V4L2_BUF_TYPE_VIDEO_OVERLAY)
+               return -EINVAL;
+       /* No fh->do_crop = 1; because btv->crop[1] may be
+          inconsistent with fh->width or fh->height and apps
+          do not expect a change here. */
+       crop->c = btv->crop[!!fh->do_crop].rect;
+       return 0;
+ }
+ static int bttv_s_crop(struct file *file, void *f, const struct v4l2_crop *crop)
+ {
+       struct bttv_fh *fh = f;
+       struct bttv *btv = fh->btv;
+       const struct v4l2_rect *b;
+       int retval;
+       struct bttv_crop c;
+       __s32 b_left;
+       __s32 b_top;
+       __s32 b_right;
+       __s32 b_bottom;
+       if (crop->type != V4L2_BUF_TYPE_VIDEO_CAPTURE &&
+           crop->type != V4L2_BUF_TYPE_VIDEO_OVERLAY)
+               return -EINVAL;
+       /* Make sure tvnorm, vbi_end and the current cropping
+          parameters remain consistent until we're done. Note
+          read() may change vbi_end in check_alloc_btres_lock(). */
+       retval = v4l2_prio_check(&btv->prio, fh->prio);
+       if (0 != retval) {
+               return retval;
+       }
+       retval = -EBUSY;
+       if (locked_btres(fh->btv, VIDEO_RESOURCES)) {
+               return retval;
+       }
+       b = &bttv_tvnorms[btv->tvnorm].cropcap.bounds;
+       b_left = b->left;
+       b_right = b_left + b->width;
+       b_bottom = b->top + b->height;
+       b_top = max(b->top, btv->vbi_end);
+       if (b_top + 32 >= b_bottom) {
+               return retval;
+       }
+       /* Min. scaled size 48 x 32. */
+       c.rect.left = clamp_t(s32, crop->c.left, b_left, b_right - 48);
+       c.rect.left = min(c.rect.left, (__s32) MAX_HDELAY);
+       c.rect.width = clamp_t(s32, crop->c.width,
+                            48, b_right - c.rect.left);
+       c.rect.top = clamp_t(s32, crop->c.top, b_top, b_bottom - 32);
+       /* Top and height must be a multiple of two. */
+       c.rect.top = (c.rect.top + 1) & ~1;
+       c.rect.height = clamp_t(s32, crop->c.height,
+                             32, b_bottom - c.rect.top);
+       c.rect.height = (c.rect.height + 1) & ~1;
+       bttv_crop_calc_limits(&c);
+       btv->crop[1] = c;
+       fh->do_crop = 1;
+       if (fh->width < c.min_scaled_width) {
+               fh->width = c.min_scaled_width;
+               btv->init.width = c.min_scaled_width;
+       } else if (fh->width > c.max_scaled_width) {
+               fh->width = c.max_scaled_width;
+               btv->init.width = c.max_scaled_width;
+       }
+       if (fh->height < c.min_scaled_height) {
+               fh->height = c.min_scaled_height;
+               btv->init.height = c.min_scaled_height;
+       } else if (fh->height > c.max_scaled_height) {
+               fh->height = c.max_scaled_height;
+               btv->init.height = c.max_scaled_height;
+       }
+       return 0;
+ }
+ static int bttv_g_audio(struct file *file, void *priv, struct v4l2_audio *a)
+ {
+       if (unlikely(a->index))
+               return -EINVAL;
+       strcpy(a->name, "audio");
+       return 0;
+ }
+ static int bttv_s_audio(struct file *file, void *priv, const struct v4l2_audio *a)
+ {
+       if (unlikely(a->index))
+               return -EINVAL;
+       return 0;
+ }
+ static ssize_t bttv_read(struct file *file, char __user *data,
+                        size_t count, loff_t *ppos)
+ {
+       struct bttv_fh *fh = file->private_data;
+       int retval = 0;
+       if (fh->btv->errors)
+               bttv_reinit_bt848(fh->btv);
+       dprintk("%d: read count=%d type=%s\n",
+               fh->btv->c.nr, (int)count, v4l2_type_names[fh->type]);
+       switch (fh->type) {
+       case V4L2_BUF_TYPE_VIDEO_CAPTURE:
+               if (!check_alloc_btres_lock(fh->btv, fh, RESOURCE_VIDEO_READ)) {
+                       /* VIDEO_READ in use by another fh,
+                          or VIDEO_STREAM by any fh. */
+                       return -EBUSY;
+               }
+               retval = videobuf_read_one(&fh->cap, data, count, ppos,
+                                          file->f_flags & O_NONBLOCK);
+               free_btres_lock(fh->btv, fh, RESOURCE_VIDEO_READ);
+               break;
+       case V4L2_BUF_TYPE_VBI_CAPTURE:
+               if (!check_alloc_btres_lock(fh->btv,fh,RESOURCE_VBI))
+                       return -EBUSY;
+               retval = videobuf_read_stream(&fh->vbi, data, count, ppos, 1,
+                                             file->f_flags & O_NONBLOCK);
+               break;
+       default:
+               BUG();
+       }
+       return retval;
+ }
+ static unsigned int bttv_poll(struct file *file, poll_table *wait)
+ {
+       struct bttv_fh *fh = file->private_data;
+       struct bttv_buffer *buf;
+       enum v4l2_field field;
+       unsigned int rc = POLLERR;
+       if (V4L2_BUF_TYPE_VBI_CAPTURE == fh->type) {
+               if (!check_alloc_btres_lock(fh->btv,fh,RESOURCE_VBI))
+                       return POLLERR;
+               return videobuf_poll_stream(file, &fh->vbi, wait);
+       }
+       if (check_btres(fh,RESOURCE_VIDEO_STREAM)) {
+               /* streaming capture */
+               if (list_empty(&fh->cap.stream))
+                       goto err;
+               buf = list_entry(fh->cap.stream.next,struct bttv_buffer,vb.stream);
+       } else {
+               /* read() capture */
+               if (NULL == fh->cap.read_buf) {
+                       /* need to capture a new frame */
+                       if (locked_btres(fh->btv,RESOURCE_VIDEO_STREAM))
+                               goto err;
+                       fh->cap.read_buf = videobuf_sg_alloc(fh->cap.msize);
+                       if (NULL == fh->cap.read_buf)
+                               goto err;
+                       fh->cap.read_buf->memory = V4L2_MEMORY_USERPTR;
+                       field = videobuf_next_field(&fh->cap);
+                       if (0 != fh->cap.ops->buf_prepare(&fh->cap,fh->cap.read_buf,field)) {
+                               kfree (fh->cap.read_buf);
+                               fh->cap.read_buf = NULL;
+                               goto err;
+                       }
+                       fh->cap.ops->buf_queue(&fh->cap,fh->cap.read_buf);
+                       fh->cap.read_off = 0;
+               }
+               buf = (struct bttv_buffer*)fh->cap.read_buf;
+       }
+       poll_wait(file, &buf->vb.done, wait);
+       if (buf->vb.state == VIDEOBUF_DONE ||
+           buf->vb.state == VIDEOBUF_ERROR)
+               rc =  POLLIN|POLLRDNORM;
+       else
+               rc = 0;
+ err:
+       return rc;
+ }
+ static int bttv_open(struct file *file)
+ {
+       struct video_device *vdev = video_devdata(file);
+       struct bttv *btv = video_drvdata(file);
+       struct bttv_fh *fh;
+       enum v4l2_buf_type type = 0;
+       dprintk("open dev=%s\n", video_device_node_name(vdev));
+       if (vdev->vfl_type == VFL_TYPE_GRABBER) {
+               type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+       } else if (vdev->vfl_type == VFL_TYPE_VBI) {
+               type = V4L2_BUF_TYPE_VBI_CAPTURE;
+       } else {
+               WARN_ON(1);
+               return -ENODEV;
+       }
+       dprintk("%d: open called (type=%s)\n",
+               btv->c.nr, v4l2_type_names[type]);
+       /* allocate per filehandle data */
+       fh = kmalloc(sizeof(*fh), GFP_KERNEL);
+       if (unlikely(!fh))
+               return -ENOMEM;
+       file->private_data = fh;
+       *fh = btv->init;
+       fh->type = type;
+       fh->ov.setup_ok = 0;
+       v4l2_prio_open(&btv->prio, &fh->prio);
+       videobuf_queue_sg_init(&fh->cap, &bttv_video_qops,
+                           &btv->c.pci->dev, &btv->s_lock,
+                           V4L2_BUF_TYPE_VIDEO_CAPTURE,
+                           V4L2_FIELD_INTERLACED,
+                           sizeof(struct bttv_buffer),
+                           fh, &btv->lock);
+       videobuf_queue_sg_init(&fh->vbi, &bttv_vbi_qops,
+                           &btv->c.pci->dev, &btv->s_lock,
+                           V4L2_BUF_TYPE_VBI_CAPTURE,
+                           V4L2_FIELD_SEQ_TB,
+                           sizeof(struct bttv_buffer),
+                           fh, &btv->lock);
+       set_tvnorm(btv,btv->tvnorm);
+       set_input(btv, btv->input, btv->tvnorm);
+       btv->users++;
+       /* The V4L2 spec requires one global set of cropping parameters
+          which only change on request. These are stored in btv->crop[1].
+          However for compatibility with V4L apps and cropping unaware
+          V4L2 apps we now reset the cropping parameters as seen through
+          this fh, which is to say VIDIOC_G_CROP and scaling limit checks
+          will use btv->crop[0], the default cropping parameters for the
+          current video standard, and VIDIOC_S_FMT will not implicitely
+          change the cropping parameters until VIDIOC_S_CROP has been
+          called. */
+       fh->do_crop = !reset_crop; /* module parameter */
+       /* Likewise there should be one global set of VBI capture
+          parameters, but for compatibility with V4L apps and earlier
+          driver versions each fh has its own parameters. */
+       bttv_vbi_fmt_reset(&fh->vbi_fmt, btv->tvnorm);
+       bttv_field_count(btv);
+       return 0;
+ }
+ static int bttv_release(struct file *file)
+ {
+       struct bttv_fh *fh = file->private_data;
+       struct bttv *btv = fh->btv;
+       /* turn off overlay */
+       if (check_btres(fh, RESOURCE_OVERLAY))
+               bttv_switch_overlay(btv,fh,NULL);
+       /* stop video capture */
+       if (check_btres(fh, RESOURCE_VIDEO_STREAM)) {
+               videobuf_streamoff(&fh->cap);
+               free_btres_lock(btv,fh,RESOURCE_VIDEO_STREAM);
+       }
+       if (fh->cap.read_buf) {
+               buffer_release(&fh->cap,fh->cap.read_buf);
+               kfree(fh->cap.read_buf);
+       }
+       if (check_btres(fh, RESOURCE_VIDEO_READ)) {
+               free_btres_lock(btv, fh, RESOURCE_VIDEO_READ);
+       }
+       /* stop vbi capture */
+       if (check_btres(fh, RESOURCE_VBI)) {
+               videobuf_stop(&fh->vbi);
+               free_btres_lock(btv,fh,RESOURCE_VBI);
+       }
+       /* free stuff */
+       videobuf_mmap_free(&fh->cap);
+       videobuf_mmap_free(&fh->vbi);
+       v4l2_prio_close(&btv->prio, fh->prio);
+       file->private_data = NULL;
+       kfree(fh);
+       btv->users--;
+       bttv_field_count(btv);
+       if (!btv->users)
+               audio_mute(btv, 1);
+       return 0;
+ }
+ static int
+ bttv_mmap(struct file *file, struct vm_area_struct *vma)
+ {
+       struct bttv_fh *fh = file->private_data;
+       dprintk("%d: mmap type=%s 0x%lx+%ld\n",
+               fh->btv->c.nr, v4l2_type_names[fh->type],
+               vma->vm_start, vma->vm_end - vma->vm_start);
+       return videobuf_mmap_mapper(bttv_queue(fh),vma);
+ }
+ static const struct v4l2_file_operations bttv_fops =
+ {
+       .owner            = THIS_MODULE,
+       .open             = bttv_open,
+       .release          = bttv_release,
+       .unlocked_ioctl   = video_ioctl2,
+       .read             = bttv_read,
+       .mmap             = bttv_mmap,
+       .poll             = bttv_poll,
+ };
+ static const struct v4l2_ioctl_ops bttv_ioctl_ops = {
+       .vidioc_querycap                = bttv_querycap,
+       .vidioc_enum_fmt_vid_cap        = bttv_enum_fmt_vid_cap,
+       .vidioc_g_fmt_vid_cap           = bttv_g_fmt_vid_cap,
+       .vidioc_try_fmt_vid_cap         = bttv_try_fmt_vid_cap,
+       .vidioc_s_fmt_vid_cap           = bttv_s_fmt_vid_cap,
+       .vidioc_enum_fmt_vid_overlay    = bttv_enum_fmt_vid_overlay,
+       .vidioc_g_fmt_vid_overlay       = bttv_g_fmt_vid_overlay,
+       .vidioc_try_fmt_vid_overlay     = bttv_try_fmt_vid_overlay,
+       .vidioc_s_fmt_vid_overlay       = bttv_s_fmt_vid_overlay,
+       .vidioc_g_fmt_vbi_cap           = bttv_g_fmt_vbi_cap,
+       .vidioc_try_fmt_vbi_cap         = bttv_try_fmt_vbi_cap,
+       .vidioc_s_fmt_vbi_cap           = bttv_s_fmt_vbi_cap,
+       .vidioc_g_audio                 = bttv_g_audio,
+       .vidioc_s_audio                 = bttv_s_audio,
+       .vidioc_cropcap                 = bttv_cropcap,
+       .vidioc_reqbufs                 = bttv_reqbufs,
+       .vidioc_querybuf                = bttv_querybuf,
+       .vidioc_qbuf                    = bttv_qbuf,
+       .vidioc_dqbuf                   = bttv_dqbuf,
+       .vidioc_s_std                   = bttv_s_std,
+       .vidioc_enum_input              = bttv_enum_input,
+       .vidioc_g_input                 = bttv_g_input,
+       .vidioc_s_input                 = bttv_s_input,
+       .vidioc_queryctrl               = bttv_queryctrl,
+       .vidioc_g_ctrl                  = bttv_g_ctrl,
+       .vidioc_s_ctrl                  = bttv_s_ctrl,
+       .vidioc_streamon                = bttv_streamon,
+       .vidioc_streamoff               = bttv_streamoff,
+       .vidioc_g_tuner                 = bttv_g_tuner,
+       .vidioc_s_tuner                 = bttv_s_tuner,
+       .vidioc_g_crop                  = bttv_g_crop,
+       .vidioc_s_crop                  = bttv_s_crop,
+       .vidioc_g_fbuf                  = bttv_g_fbuf,
+       .vidioc_s_fbuf                  = bttv_s_fbuf,
+       .vidioc_overlay                 = bttv_overlay,
+       .vidioc_g_priority              = bttv_g_priority,
+       .vidioc_s_priority              = bttv_s_priority,
+       .vidioc_g_parm                  = bttv_g_parm,
+       .vidioc_g_frequency             = bttv_g_frequency,
+       .vidioc_s_frequency             = bttv_s_frequency,
+       .vidioc_log_status              = bttv_log_status,
+       .vidioc_querystd                = bttv_querystd,
+ #ifdef CONFIG_VIDEO_ADV_DEBUG
+       .vidioc_g_register              = bttv_g_register,
+       .vidioc_s_register              = bttv_s_register,
+ #endif
+ };
+ static struct video_device bttv_video_template = {
+       .fops         = &bttv_fops,
+       .ioctl_ops    = &bttv_ioctl_ops,
+       .tvnorms      = BTTV_NORMS,
+       .current_norm = V4L2_STD_PAL,
+ };
+ /* ----------------------------------------------------------------------- */
+ /* radio interface                                                         */
+ static int radio_open(struct file *file)
+ {
+       struct video_device *vdev = video_devdata(file);
+       struct bttv *btv = video_drvdata(file);
+       struct bttv_fh *fh;
+       dprintk("open dev=%s\n", video_device_node_name(vdev));
+       dprintk("%d: open called (radio)\n", btv->c.nr);
+       /* allocate per filehandle data */
+       fh = kmalloc(sizeof(*fh), GFP_KERNEL);
+       if (unlikely(!fh))
+               return -ENOMEM;
+       file->private_data = fh;
+       *fh = btv->init;
+       v4l2_prio_open(&btv->prio, &fh->prio);
+       btv->radio_user++;
+       bttv_call_all(btv, tuner, s_radio);
+       audio_input(btv,TVAUDIO_INPUT_RADIO);
+       return 0;
+ }
+ static int radio_release(struct file *file)
+ {
+       struct bttv_fh *fh = file->private_data;
+       struct bttv *btv = fh->btv;
+       struct saa6588_command cmd;
+       v4l2_prio_close(&btv->prio, fh->prio);
+       file->private_data = NULL;
+       kfree(fh);
+       btv->radio_user--;
+       bttv_call_all(btv, core, ioctl, SAA6588_CMD_CLOSE, &cmd);
+       return 0;
+ }
+ static int radio_querycap(struct file *file, void *priv,
+                                       struct v4l2_capability *cap)
+ {
+       struct bttv_fh *fh = priv;
+       struct bttv *btv = fh->btv;
+       strcpy(cap->driver, "bttv");
+       strlcpy(cap->card, btv->radio_dev->name, sizeof(cap->card));
+       sprintf(cap->bus_info, "PCI:%s", pci_name(btv->c.pci));
+       cap->capabilities = V4L2_CAP_TUNER;
+       return 0;
+ }
+ static int radio_g_tuner(struct file *file, void *priv, struct v4l2_tuner *t)
+ {
+       struct bttv_fh *fh = priv;
+       struct bttv *btv = fh->btv;
+       if (btv->tuner_type == TUNER_ABSENT)
+               return -EINVAL;
+       if (0 != t->index)
+               return -EINVAL;
+       strcpy(t->name, "Radio");
+       t->type = V4L2_TUNER_RADIO;
+       bttv_call_all(btv, tuner, g_tuner, t);
+       if (btv->audio_mode_gpio)
+               btv->audio_mode_gpio(btv, t, 0);
+       return 0;
+ }
+ static int radio_enum_input(struct file *file, void *priv,
+                               struct v4l2_input *i)
+ {
+       if (i->index != 0)
+               return -EINVAL;
+       strcpy(i->name, "Radio");
+       i->type = V4L2_INPUT_TYPE_TUNER;
+       return 0;
+ }
+ static int radio_g_audio(struct file *file, void *priv,
+                                       struct v4l2_audio *a)
+ {
+       if (unlikely(a->index))
+               return -EINVAL;
+       strcpy(a->name, "Radio");
+       return 0;
+ }
+ static int radio_s_tuner(struct file *file, void *priv,
+                                       struct v4l2_tuner *t)
+ {
+       struct bttv_fh *fh = priv;
+       struct bttv *btv = fh->btv;
+       if (0 != t->index)
+               return -EINVAL;
+       bttv_call_all(btv, tuner, s_tuner, t);
+       return 0;
+ }
+ static int radio_s_audio(struct file *file, void *priv,
+                                       const struct v4l2_audio *a)
+ {
+       if (unlikely(a->index))
+               return -EINVAL;
+       return 0;
+ }
+ static int radio_s_input(struct file *filp, void *priv, unsigned int i)
+ {
+       if (unlikely(i))
+               return -EINVAL;
+       return 0;
+ }
+ static int radio_s_std(struct file *file, void *fh, v4l2_std_id *norm)
+ {
+       return 0;
+ }
+ static int radio_queryctrl(struct file *file, void *priv,
+                                       struct v4l2_queryctrl *c)
+ {
+       const struct v4l2_queryctrl *ctrl;
+       if (c->id <  V4L2_CID_BASE ||
+                       c->id >= V4L2_CID_LASTP1)
+               return -EINVAL;
+       if (c->id == V4L2_CID_AUDIO_MUTE) {
+               ctrl = ctrl_by_id(c->id);
+               *c = *ctrl;
+       } else
+               *c = no_ctl;
+       return 0;
+ }
+ static int radio_g_input(struct file *filp, void *priv, unsigned int *i)
+ {
+       *i = 0;
+       return 0;
+ }
+ static ssize_t radio_read(struct file *file, char __user *data,
+                        size_t count, loff_t *ppos)
+ {
+       struct bttv_fh *fh = file->private_data;
+       struct bttv *btv = fh->btv;
+       struct saa6588_command cmd;
+       cmd.block_count = count/3;
+       cmd.buffer = data;
+       cmd.instance = file;
+       cmd.result = -ENODEV;
+       bttv_call_all(btv, core, ioctl, SAA6588_CMD_READ, &cmd);
+       return cmd.result;
+ }
+ static unsigned int radio_poll(struct file *file, poll_table *wait)
+ {
+       struct bttv_fh *fh = file->private_data;
+       struct bttv *btv = fh->btv;
+       struct saa6588_command cmd;
+       cmd.instance = file;
+       cmd.event_list = wait;
+       cmd.result = -ENODEV;
+       bttv_call_all(btv, core, ioctl, SAA6588_CMD_POLL, &cmd);
+       return cmd.result;
+ }
+ static const struct v4l2_file_operations radio_fops =
+ {
+       .owner    = THIS_MODULE,
+       .open     = radio_open,
+       .read     = radio_read,
+       .release  = radio_release,
+       .unlocked_ioctl = video_ioctl2,
+       .poll     = radio_poll,
+ };
+ static const struct v4l2_ioctl_ops radio_ioctl_ops = {
+       .vidioc_querycap        = radio_querycap,
+       .vidioc_g_tuner         = radio_g_tuner,
+       .vidioc_enum_input      = radio_enum_input,
+       .vidioc_g_audio         = radio_g_audio,
+       .vidioc_s_tuner         = radio_s_tuner,
+       .vidioc_s_audio         = radio_s_audio,
+       .vidioc_s_input         = radio_s_input,
+       .vidioc_s_std           = radio_s_std,
+       .vidioc_queryctrl       = radio_queryctrl,
+       .vidioc_g_input         = radio_g_input,
+       .vidioc_g_ctrl          = bttv_g_ctrl,
+       .vidioc_s_ctrl          = bttv_s_ctrl,
+       .vidioc_g_frequency     = bttv_g_frequency,
+       .vidioc_s_frequency     = bttv_s_frequency,
+ };
+ static struct video_device radio_template = {
+       .fops      = &radio_fops,
+       .ioctl_ops = &radio_ioctl_ops,
+ };
+ /* ----------------------------------------------------------------------- */
+ /* some debug code                                                         */
+ static int bttv_risc_decode(u32 risc)
+ {
+       static char *instr[16] = {
+               [ BT848_RISC_WRITE     >> 28 ] = "write",
+               [ BT848_RISC_SKIP      >> 28 ] = "skip",
+               [ BT848_RISC_WRITEC    >> 28 ] = "writec",
+               [ BT848_RISC_JUMP      >> 28 ] = "jump",
+               [ BT848_RISC_SYNC      >> 28 ] = "sync",
+               [ BT848_RISC_WRITE123  >> 28 ] = "write123",
+               [ BT848_RISC_SKIP123   >> 28 ] = "skip123",
+               [ BT848_RISC_WRITE1S23 >> 28 ] = "write1s23",
+       };
+       static int incr[16] = {
+               [ BT848_RISC_WRITE     >> 28 ] = 2,
+               [ BT848_RISC_JUMP      >> 28 ] = 2,
+               [ BT848_RISC_SYNC      >> 28 ] = 2,
+               [ BT848_RISC_WRITE123  >> 28 ] = 5,
+               [ BT848_RISC_SKIP123   >> 28 ] = 2,
+               [ BT848_RISC_WRITE1S23 >> 28 ] = 3,
+       };
+       static char *bits[] = {
+               "be0",  "be1",  "be2",  "be3/resync",
+               "set0", "set1", "set2", "set3",
+               "clr0", "clr1", "clr2", "clr3",
+               "irq",  "res",  "eol",  "sol",
+       };
+       int i;
+       pr_cont("0x%08x [ %s", risc,
+              instr[risc >> 28] ? instr[risc >> 28] : "INVALID");
+       for (i = ARRAY_SIZE(bits)-1; i >= 0; i--)
+               if (risc & (1 << (i + 12)))
+                       pr_cont(" %s", bits[i]);
+       pr_cont(" count=%d ]\n", risc & 0xfff);
+       return incr[risc >> 28] ? incr[risc >> 28] : 1;
+ }
+ static void bttv_risc_disasm(struct bttv *btv,
+                            struct btcx_riscmem *risc)
+ {
+       unsigned int i,j,n;
+       pr_info("%s: risc disasm: %p [dma=0x%08lx]\n",
+               btv->c.v4l2_dev.name, risc->cpu, (unsigned long)risc->dma);
+       for (i = 0; i < (risc->size >> 2); i += n) {
+               pr_info("%s:   0x%lx: ",
+                       btv->c.v4l2_dev.name,
+                       (unsigned long)(risc->dma + (i<<2)));
+               n = bttv_risc_decode(le32_to_cpu(risc->cpu[i]));
+               for (j = 1; j < n; j++)
+                       pr_info("%s:   0x%lx: 0x%08x [ arg #%d ]\n",
+                               btv->c.v4l2_dev.name,
+                               (unsigned long)(risc->dma + ((i+j)<<2)),
+                               risc->cpu[i+j], j);
+               if (0 == risc->cpu[i])
+                       break;
+       }
+ }
+ static void bttv_print_riscaddr(struct bttv *btv)
+ {
+       pr_info("  main: %08llx\n", (unsigned long long)btv->main.dma);
+       pr_info("  vbi : o=%08llx e=%08llx\n",
+               btv->cvbi ? (unsigned long long)btv->cvbi->top.dma : 0,
+               btv->cvbi ? (unsigned long long)btv->cvbi->bottom.dma : 0);
+       pr_info("  cap : o=%08llx e=%08llx\n",
+               btv->curr.top
+               ? (unsigned long long)btv->curr.top->top.dma : 0,
+               btv->curr.bottom
+               ? (unsigned long long)btv->curr.bottom->bottom.dma : 0);
+       pr_info("  scr : o=%08llx e=%08llx\n",
+               btv->screen ? (unsigned long long)btv->screen->top.dma : 0,
+               btv->screen ? (unsigned long long)btv->screen->bottom.dma : 0);
+       bttv_risc_disasm(btv, &btv->main);
+ }
+ /* ----------------------------------------------------------------------- */
+ /* irq handler                                                             */
+ static char *irq_name[] = {
+       "FMTCHG",  // format change detected (525 vs. 625)
+       "VSYNC",   // vertical sync (new field)
+       "HSYNC",   // horizontal sync
+       "OFLOW",   // chroma/luma AGC overflow
+       "HLOCK",   // horizontal lock changed
+       "VPRES",   // video presence changed
+       "6", "7",
+       "I2CDONE", // hw irc operation finished
+       "GPINT",   // gpio port triggered irq
+       "10",
+       "RISCI",   // risc instruction triggered irq
+       "FBUS",    // pixel data fifo dropped data (high pci bus latencies)
+       "FTRGT",   // pixel data fifo overrun
+       "FDSR",    // fifo data stream resyncronisation
+       "PPERR",   // parity error (data transfer)
+       "RIPERR",  // parity error (read risc instructions)
+       "PABORT",  // pci abort
+       "OCERR",   // risc instruction error
+       "SCERR",   // syncronisation error
+ };
+ static void bttv_print_irqbits(u32 print, u32 mark)
+ {
+       unsigned int i;
+       pr_cont("bits:");
+       for (i = 0; i < ARRAY_SIZE(irq_name); i++) {
+               if (print & (1 << i))
+                       pr_cont(" %s", irq_name[i]);
+               if (mark & (1 << i))
+                       pr_cont("*");
+       }
+ }
+ static void bttv_irq_debug_low_latency(struct bttv *btv, u32 rc)
+ {
+       pr_warn("%d: irq: skipped frame [main=%lx,o_vbi=%lx,o_field=%lx,rc=%lx]\n",
+               btv->c.nr,
+               (unsigned long)btv->main.dma,
+               (unsigned long)le32_to_cpu(btv->main.cpu[RISC_SLOT_O_VBI+1]),
+               (unsigned long)le32_to_cpu(btv->main.cpu[RISC_SLOT_O_FIELD+1]),
+               (unsigned long)rc);
+       if (0 == (btread(BT848_DSTATUS) & BT848_DSTATUS_HLOC)) {
+               pr_notice("%d: Oh, there (temporarily?) is no input signal. "
+                         "Ok, then this is harmless, don't worry ;)\n",
+                         btv->c.nr);
+               return;
+       }
+       pr_notice("%d: Uhm. Looks like we have unusual high IRQ latencies\n",
+                 btv->c.nr);
+       pr_notice("%d: Lets try to catch the culpit red-handed ...\n",
+                 btv->c.nr);
+       dump_stack();
+ }
+ static int
+ bttv_irq_next_video(struct bttv *btv, struct bttv_buffer_set *set)
+ {
+       struct bttv_buffer *item;
+       memset(set,0,sizeof(*set));
+       /* capture request ? */
+       if (!list_empty(&btv->capture)) {
+               set->frame_irq = 1;
+               item = list_entry(btv->capture.next, struct bttv_buffer, vb.queue);
+               if (V4L2_FIELD_HAS_TOP(item->vb.field))
+                       set->top    = item;
+               if (V4L2_FIELD_HAS_BOTTOM(item->vb.field))
+                       set->bottom = item;
+               /* capture request for other field ? */
+               if (!V4L2_FIELD_HAS_BOTH(item->vb.field) &&
+                   (item->vb.queue.next != &btv->capture)) {
+                       item = list_entry(item->vb.queue.next, struct bttv_buffer, vb.queue);
+                       /* Mike Isely <isely@pobox.com> - Only check
+                        * and set up the bottom field in the logic
+                        * below.  Don't ever do the top field.  This
+                        * of course means that if we set up the
+                        * bottom field in the above code that we'll
+                        * actually skip a field.  But that's OK.
+                        * Having processed only a single buffer this
+                        * time, then the next time around the first
+                        * available buffer should be for a top field.
+                        * That will then cause us here to set up a
+                        * top then a bottom field in the normal way.
+                        * The alternative to this understanding is
+                        * that we set up the second available buffer
+                        * as a top field, but that's out of order
+                        * since this driver always processes the top
+                        * field first - the effect will be the two
+                        * buffers being returned in the wrong order,
+                        * with the second buffer also being delayed
+                        * by one field time (owing to the fifo nature
+                        * of videobuf).  Worse still, we'll be stuck
+                        * doing fields out of order now every time
+                        * until something else causes a field to be
+                        * dropped.  By effectively forcing a field to
+                        * drop this way then we always get back into
+                        * sync within a single frame time.  (Out of
+                        * order fields can screw up deinterlacing
+                        * algorithms.) */
+                       if (!V4L2_FIELD_HAS_BOTH(item->vb.field)) {
+                               if (NULL == set->bottom &&
+                                   V4L2_FIELD_BOTTOM == item->vb.field) {
+                                       set->bottom = item;
+                               }
+                               if (NULL != set->top  &&  NULL != set->bottom)
+                                       set->top_irq = 2;
+                       }
+               }
+       }
+       /* screen overlay ? */
+       if (NULL != btv->screen) {
+               if (V4L2_FIELD_HAS_BOTH(btv->screen->vb.field)) {
+                       if (NULL == set->top && NULL == set->bottom) {
+                               set->top    = btv->screen;
+                               set->bottom = btv->screen;
+                       }
+               } else {
+                       if (V4L2_FIELD_TOP == btv->screen->vb.field &&
+                           NULL == set->top) {
+                               set->top = btv->screen;
+                       }
+                       if (V4L2_FIELD_BOTTOM == btv->screen->vb.field &&
+                           NULL == set->bottom) {
+                               set->bottom = btv->screen;
+                       }
+               }
+       }
+       dprintk("%d: next set: top=%p bottom=%p [screen=%p,irq=%d,%d]\n",
+               btv->c.nr, set->top, set->bottom,
+               btv->screen, set->frame_irq, set->top_irq);
+       return 0;
+ }
+ static void
+ bttv_irq_wakeup_video(struct bttv *btv, struct bttv_buffer_set *wakeup,
+                     struct bttv_buffer_set *curr, unsigned int state)
+ {
+       struct timeval ts;
+       do_gettimeofday(&ts);
+       if (wakeup->top == wakeup->bottom) {
+               if (NULL != wakeup->top && curr->top != wakeup->top) {
+                       if (irq_debug > 1)
+                               pr_debug("%d: wakeup: both=%p\n",
+                                        btv->c.nr, wakeup->top);
+                       wakeup->top->vb.ts = ts;
+                       wakeup->top->vb.field_count = btv->field_count;
+                       wakeup->top->vb.state = state;
+                       wake_up(&wakeup->top->vb.done);
+               }
+       } else {
+               if (NULL != wakeup->top && curr->top != wakeup->top) {
+                       if (irq_debug > 1)
+                               pr_debug("%d: wakeup: top=%p\n",
+                                        btv->c.nr, wakeup->top);
+                       wakeup->top->vb.ts = ts;
+                       wakeup->top->vb.field_count = btv->field_count;
+                       wakeup->top->vb.state = state;
+                       wake_up(&wakeup->top->vb.done);
+               }
+               if (NULL != wakeup->bottom && curr->bottom != wakeup->bottom) {
+                       if (irq_debug > 1)
+                               pr_debug("%d: wakeup: bottom=%p\n",
+                                        btv->c.nr, wakeup->bottom);
+                       wakeup->bottom->vb.ts = ts;
+                       wakeup->bottom->vb.field_count = btv->field_count;
+                       wakeup->bottom->vb.state = state;
+                       wake_up(&wakeup->bottom->vb.done);
+               }
+       }
+ }
+ static void
+ bttv_irq_wakeup_vbi(struct bttv *btv, struct bttv_buffer *wakeup,
+                   unsigned int state)
+ {
+       struct timeval ts;
+       if (NULL == wakeup)
+               return;
+       do_gettimeofday(&ts);
+       wakeup->vb.ts = ts;
+       wakeup->vb.field_count = btv->field_count;
+       wakeup->vb.state = state;
+       wake_up(&wakeup->vb.done);
+ }
+ static void bttv_irq_timeout(unsigned long data)
+ {
+       struct bttv *btv = (struct bttv *)data;
+       struct bttv_buffer_set old,new;
+       struct bttv_buffer *ovbi;
+       struct bttv_buffer *item;
+       unsigned long flags;
+       if (bttv_verbose) {
+               pr_info("%d: timeout: drop=%d irq=%d/%d, risc=%08x, ",
+                       btv->c.nr, btv->framedrop, btv->irq_me, btv->irq_total,
+                       btread(BT848_RISC_COUNT));
+               bttv_print_irqbits(btread(BT848_INT_STAT),0);
+               pr_cont("\n");
+       }
+       spin_lock_irqsave(&btv->s_lock,flags);
+       /* deactivate stuff */
+       memset(&new,0,sizeof(new));
+       old  = btv->curr;
+       ovbi = btv->cvbi;
+       btv->curr = new;
+       btv->cvbi = NULL;
+       btv->loop_irq = 0;
+       bttv_buffer_activate_video(btv, &new);
+       bttv_buffer_activate_vbi(btv,   NULL);
+       bttv_set_dma(btv, 0);
+       /* wake up */
+       bttv_irq_wakeup_video(btv, &old, &new, VIDEOBUF_ERROR);
+       bttv_irq_wakeup_vbi(btv, ovbi, VIDEOBUF_ERROR);
+       /* cancel all outstanding capture / vbi requests */
+       while (!list_empty(&btv->capture)) {
+               item = list_entry(btv->capture.next, struct bttv_buffer, vb.queue);
+               list_del(&item->vb.queue);
+               item->vb.state = VIDEOBUF_ERROR;
+               wake_up(&item->vb.done);
+       }
+       while (!list_empty(&btv->vcapture)) {
+               item = list_entry(btv->vcapture.next, struct bttv_buffer, vb.queue);
+               list_del(&item->vb.queue);
+               item->vb.state = VIDEOBUF_ERROR;
+               wake_up(&item->vb.done);
+       }
+       btv->errors++;
+       spin_unlock_irqrestore(&btv->s_lock,flags);
+ }
+ static void
+ bttv_irq_wakeup_top(struct bttv *btv)
+ {
+       struct bttv_buffer *wakeup = btv->curr.top;
+       if (NULL == wakeup)
+               return;
+       spin_lock(&btv->s_lock);
+       btv->curr.top_irq = 0;
+       btv->curr.top = NULL;
+       bttv_risc_hook(btv, RISC_SLOT_O_FIELD, NULL, 0);
+       do_gettimeofday(&wakeup->vb.ts);
+       wakeup->vb.field_count = btv->field_count;
+       wakeup->vb.state = VIDEOBUF_DONE;
+       wake_up(&wakeup->vb.done);
+       spin_unlock(&btv->s_lock);
+ }
+ static inline int is_active(struct btcx_riscmem *risc, u32 rc)
+ {
+       if (rc < risc->dma)
+               return 0;
+       if (rc > risc->dma + risc->size)
+               return 0;
+       return 1;
+ }
+ static void
+ bttv_irq_switch_video(struct bttv *btv)
+ {
+       struct bttv_buffer_set new;
+       struct bttv_buffer_set old;
+       dma_addr_t rc;
+       spin_lock(&btv->s_lock);
+       /* new buffer set */
+       bttv_irq_next_video(btv, &new);
+       rc = btread(BT848_RISC_COUNT);
+       if ((btv->curr.top    && is_active(&btv->curr.top->top,       rc)) ||
+           (btv->curr.bottom && is_active(&btv->curr.bottom->bottom, rc))) {
+               btv->framedrop++;
+               if (debug_latency)
+                       bttv_irq_debug_low_latency(btv, rc);
+               spin_unlock(&btv->s_lock);
+               return;
+       }
+       /* switch over */
+       old = btv->curr;
+       btv->curr = new;
+       btv->loop_irq &= ~1;
+       bttv_buffer_activate_video(btv, &new);
+       bttv_set_dma(btv, 0);
+       /* switch input */
+       if (UNSET != btv->new_input) {
+               video_mux(btv,btv->new_input);
+               btv->new_input = UNSET;
+       }
+       /* wake up finished buffers */
+       bttv_irq_wakeup_video(btv, &old, &new, VIDEOBUF_DONE);
+       spin_unlock(&btv->s_lock);
+ }
+ static void
+ bttv_irq_switch_vbi(struct bttv *btv)
+ {
+       struct bttv_buffer *new = NULL;
+       struct bttv_buffer *old;
+       u32 rc;
+       spin_lock(&btv->s_lock);
+       if (!list_empty(&btv->vcapture))
+               new = list_entry(btv->vcapture.next, struct bttv_buffer, vb.queue);
+       old = btv->cvbi;
+       rc = btread(BT848_RISC_COUNT);
+       if (NULL != old && (is_active(&old->top,    rc) ||
+                           is_active(&old->bottom, rc))) {
+               btv->framedrop++;
+               if (debug_latency)
+                       bttv_irq_debug_low_latency(btv, rc);
+               spin_unlock(&btv->s_lock);
+               return;
+       }
+       /* switch */
+       btv->cvbi = new;
+       btv->loop_irq &= ~4;
+       bttv_buffer_activate_vbi(btv, new);
+       bttv_set_dma(btv, 0);
+       bttv_irq_wakeup_vbi(btv, old, VIDEOBUF_DONE);
+       spin_unlock(&btv->s_lock);
+ }
+ static irqreturn_t bttv_irq(int irq, void *dev_id)
+ {
+       u32 stat,astat;
+       u32 dstat;
+       int count;
+       struct bttv *btv;
+       int handled = 0;
+       btv=(struct bttv *)dev_id;
+       count=0;
+       while (1) {
+               /* get/clear interrupt status bits */
+               stat=btread(BT848_INT_STAT);
+               astat=stat&btread(BT848_INT_MASK);
+               if (!astat)
+                       break;
+               handled = 1;
+               btwrite(stat,BT848_INT_STAT);
+               /* get device status bits */
+               dstat=btread(BT848_DSTATUS);
+               if (irq_debug) {
+                       pr_debug("%d: irq loop=%d fc=%d riscs=%x, riscc=%08x, ",
+                                btv->c.nr, count, btv->field_count,
+                                stat>>28, btread(BT848_RISC_COUNT));
+                       bttv_print_irqbits(stat,astat);
+                       if (stat & BT848_INT_HLOCK)
+                               pr_cont("   HLOC => %s",
+                                       dstat & BT848_DSTATUS_HLOC
+                                       ? "yes" : "no");
+                       if (stat & BT848_INT_VPRES)
+                               pr_cont("   PRES => %s",
+                                       dstat & BT848_DSTATUS_PRES
+                                       ? "yes" : "no");
+                       if (stat & BT848_INT_FMTCHG)
+                               pr_cont("   NUML => %s",
+                                       dstat & BT848_DSTATUS_NUML
+                                       ? "625" : "525");
+                       pr_cont("\n");
+               }
+               if (astat&BT848_INT_VSYNC)
+                       btv->field_count++;
+               if ((astat & BT848_INT_GPINT) && btv->remote) {
+                       bttv_input_irq(btv);
+               }
+               if (astat & BT848_INT_I2CDONE) {
+                       btv->i2c_done = stat;
+                       wake_up(&btv->i2c_queue);
+               }
+               if ((astat & BT848_INT_RISCI)  &&  (stat & (4<<28)))
+                       bttv_irq_switch_vbi(btv);
+               if ((astat & BT848_INT_RISCI)  &&  (stat & (2<<28)))
+                       bttv_irq_wakeup_top(btv);
+               if ((astat & BT848_INT_RISCI)  &&  (stat & (1<<28)))
+                       bttv_irq_switch_video(btv);
+               if ((astat & BT848_INT_HLOCK)  &&  btv->opt_automute)
+                       audio_mute(btv, btv->mute);  /* trigger automute */
+               if (astat & (BT848_INT_SCERR|BT848_INT_OCERR)) {
+                       pr_info("%d: %s%s @ %08x,",
+                               btv->c.nr,
+                               (astat & BT848_INT_SCERR) ? "SCERR" : "",
+                               (astat & BT848_INT_OCERR) ? "OCERR" : "",
+                               btread(BT848_RISC_COUNT));
+                       bttv_print_irqbits(stat,astat);
+                       pr_cont("\n");
+                       if (bttv_debug)
+                               bttv_print_riscaddr(btv);
+               }
+               if (fdsr && astat & BT848_INT_FDSR) {
+                       pr_info("%d: FDSR @ %08x\n",
+                               btv->c.nr, btread(BT848_RISC_COUNT));
+                       if (bttv_debug)
+                               bttv_print_riscaddr(btv);
+               }
+               count++;
+               if (count > 4) {
+                       if (count > 8 || !(astat & BT848_INT_GPINT)) {
+                               btwrite(0, BT848_INT_MASK);
+                               pr_err("%d: IRQ lockup, cleared int mask [",
+                                      btv->c.nr);
+                       } else {
+                               pr_err("%d: IRQ lockup, clearing GPINT from int mask [",
+                                      btv->c.nr);
+                               btwrite(btread(BT848_INT_MASK) & (-1 ^ BT848_INT_GPINT),
+                                               BT848_INT_MASK);
+                       };
+                       bttv_print_irqbits(stat,astat);
+                       pr_cont("]\n");
+               }
+       }
+       btv->irq_total++;
+       if (handled)
+               btv->irq_me++;
+       return IRQ_RETVAL(handled);
+ }
+ /* ----------------------------------------------------------------------- */
+ /* initialitation                                                          */
+ static struct video_device *vdev_init(struct bttv *btv,
+                                     const struct video_device *template,
+                                     const char *type_name)
+ {
+       struct video_device *vfd;
+       vfd = video_device_alloc();
+       if (NULL == vfd)
+               return NULL;
+       *vfd = *template;
+       vfd->v4l2_dev = &btv->c.v4l2_dev;
+       vfd->release = video_device_release;
+       vfd->debug   = bttv_debug;
+       video_set_drvdata(vfd, btv);
+       snprintf(vfd->name, sizeof(vfd->name), "BT%d%s %s (%s)",
+                btv->id, (btv->id==848 && btv->revision==0x12) ? "A" : "",
+                type_name, bttv_tvcards[btv->c.type].name);
+       return vfd;
+ }
+ static void bttv_unregister_video(struct bttv *btv)
+ {
+       if (btv->video_dev) {
+               if (video_is_registered(btv->video_dev))
+                       video_unregister_device(btv->video_dev);
+               else
+                       video_device_release(btv->video_dev);
+               btv->video_dev = NULL;
+       }
+       if (btv->vbi_dev) {
+               if (video_is_registered(btv->vbi_dev))
+                       video_unregister_device(btv->vbi_dev);
+               else
+                       video_device_release(btv->vbi_dev);
+               btv->vbi_dev = NULL;
+       }
+       if (btv->radio_dev) {
+               if (video_is_registered(btv->radio_dev))
+                       video_unregister_device(btv->radio_dev);
+               else
+                       video_device_release(btv->radio_dev);
+               btv->radio_dev = NULL;
+       }
+ }
+ /* register video4linux devices */
+ static int __devinit bttv_register_video(struct bttv *btv)
+ {
+       if (no_overlay > 0)
+               pr_notice("Overlay support disabled\n");
+       /* video */
+       btv->video_dev = vdev_init(btv, &bttv_video_template, "video");
+       if (NULL == btv->video_dev)
+               goto err;
+       if (video_register_device(btv->video_dev, VFL_TYPE_GRABBER,
+                                 video_nr[btv->c.nr]) < 0)
+               goto err;
+       pr_info("%d: registered device %s\n",
+               btv->c.nr, video_device_node_name(btv->video_dev));
+       if (device_create_file(&btv->video_dev->dev,
+                                    &dev_attr_card)<0) {
+               pr_err("%d: device_create_file 'card' failed\n", btv->c.nr);
+               goto err;
+       }
+       /* vbi */
+       btv->vbi_dev = vdev_init(btv, &bttv_video_template, "vbi");
+       if (NULL == btv->vbi_dev)
+               goto err;
+       if (video_register_device(btv->vbi_dev, VFL_TYPE_VBI,
+                                 vbi_nr[btv->c.nr]) < 0)
+               goto err;
+       pr_info("%d: registered device %s\n",
+               btv->c.nr, video_device_node_name(btv->vbi_dev));
+       if (!btv->has_radio)
+               return 0;
+       /* radio */
+       btv->radio_dev = vdev_init(btv, &radio_template, "radio");
+       if (NULL == btv->radio_dev)
+               goto err;
+       if (video_register_device(btv->radio_dev, VFL_TYPE_RADIO,
+                                 radio_nr[btv->c.nr]) < 0)
+               goto err;
+       pr_info("%d: registered device %s\n",
+               btv->c.nr, video_device_node_name(btv->radio_dev));
+       /* all done */
+       return 0;
+  err:
+       bttv_unregister_video(btv);
+       return -1;
+ }
+ /* on OpenFirmware machines (PowerMac at least), PCI memory cycle */
+ /* response on cards with no firmware is not enabled by OF */
+ static void pci_set_command(struct pci_dev *dev)
+ {
+ #if defined(__powerpc__)
+       unsigned int cmd;
+       pci_read_config_dword(dev, PCI_COMMAND, &cmd);
+       cmd = (cmd | PCI_COMMAND_MEMORY );
+       pci_write_config_dword(dev, PCI_COMMAND, cmd);
+ #endif
+ }
+ static int __devinit bttv_probe(struct pci_dev *dev,
+                               const struct pci_device_id *pci_id)
+ {
+       int result;
+       unsigned char lat;
+       struct bttv *btv;
+       if (bttv_num == BTTV_MAX)
+               return -ENOMEM;
+       pr_info("Bt8xx card found (%d)\n", bttv_num);
+       bttvs[bttv_num] = btv = kzalloc(sizeof(*btv), GFP_KERNEL);
+       if (btv == NULL) {
+               pr_err("out of memory\n");
+               return -ENOMEM;
+       }
+       btv->c.nr  = bttv_num;
+       snprintf(btv->c.v4l2_dev.name, sizeof(btv->c.v4l2_dev.name),
+                       "bttv%d", btv->c.nr);
+       /* initialize structs / fill in defaults */
+       mutex_init(&btv->lock);
+       spin_lock_init(&btv->s_lock);
+       spin_lock_init(&btv->gpio_lock);
+       init_waitqueue_head(&btv->i2c_queue);
+       INIT_LIST_HEAD(&btv->c.subs);
+       INIT_LIST_HEAD(&btv->capture);
+       INIT_LIST_HEAD(&btv->vcapture);
+       v4l2_prio_init(&btv->prio);
+       init_timer(&btv->timeout);
+       btv->timeout.function = bttv_irq_timeout;
+       btv->timeout.data     = (unsigned long)btv;
+       btv->i2c_rc = -1;
+       btv->tuner_type  = UNSET;
+       btv->new_input   = UNSET;
+       btv->has_radio=radio[btv->c.nr];
+       /* pci stuff (init, get irq/mmio, ... */
+       btv->c.pci = dev;
+       btv->id  = dev->device;
+       if (pci_enable_device(dev)) {
+               pr_warn("%d: Can't enable device\n", btv->c.nr);
+               return -EIO;
+       }
+       if (pci_set_dma_mask(dev, DMA_BIT_MASK(32))) {
+               pr_warn("%d: No suitable DMA available\n", btv->c.nr);
+               return -EIO;
+       }
+       if (!request_mem_region(pci_resource_start(dev,0),
+                               pci_resource_len(dev,0),
+                               btv->c.v4l2_dev.name)) {
+               pr_warn("%d: can't request iomem (0x%llx)\n",
+                       btv->c.nr,
+                       (unsigned long long)pci_resource_start(dev, 0));
+               return -EBUSY;
+       }
+       pci_set_master(dev);
+       pci_set_command(dev);
+       result = v4l2_device_register(&dev->dev, &btv->c.v4l2_dev);
+       if (result < 0) {
+               pr_warn("%d: v4l2_device_register() failed\n", btv->c.nr);
+               goto fail0;
+       }
+       btv->revision = dev->revision;
+       pci_read_config_byte(dev, PCI_LATENCY_TIMER, &lat);
+       pr_info("%d: Bt%d (rev %d) at %s, irq: %d, latency: %d, mmio: 0x%llx\n",
+               bttv_num, btv->id, btv->revision, pci_name(dev),
+               btv->c.pci->irq, lat,
+               (unsigned long long)pci_resource_start(dev, 0));
+       schedule();
+       btv->bt848_mmio = ioremap(pci_resource_start(dev, 0), 0x1000);
+       if (NULL == btv->bt848_mmio) {
+               pr_err("%d: ioremap() failed\n", btv->c.nr);
+               result = -EIO;
+               goto fail1;
+       }
+       /* identify card */
+       bttv_idcard(btv);
+       /* disable irqs, register irq handler */
+       btwrite(0, BT848_INT_MASK);
+       result = request_irq(btv->c.pci->irq, bttv_irq,
+           IRQF_SHARED | IRQF_DISABLED, btv->c.v4l2_dev.name, (void *)btv);
+       if (result < 0) {
+               pr_err("%d: can't get IRQ %d\n",
+                      bttv_num, btv->c.pci->irq);
+               goto fail1;
+       }
+       if (0 != bttv_handle_chipset(btv)) {
+               result = -EIO;
+               goto fail2;
+       }
+       /* init options from insmod args */
+       btv->opt_combfilter = combfilter;
+       btv->opt_lumafilter = lumafilter;
+       btv->opt_automute   = automute;
+       btv->opt_chroma_agc = chroma_agc;
+       btv->opt_adc_crush  = adc_crush;
+       btv->opt_vcr_hack   = vcr_hack;
+       btv->opt_whitecrush_upper  = whitecrush_upper;
+       btv->opt_whitecrush_lower  = whitecrush_lower;
+       btv->opt_uv_ratio   = uv_ratio;
+       btv->opt_full_luma_range   = full_luma_range;
+       btv->opt_coring     = coring;
+       /* fill struct bttv with some useful defaults */
+       btv->init.btv         = btv;
+       btv->init.ov.w.width  = 320;
+       btv->init.ov.w.height = 240;
+       btv->init.fmt         = format_by_fourcc(V4L2_PIX_FMT_BGR24);
+       btv->init.width       = 320;
+       btv->init.height      = 240;
+       btv->input = 0;
+       /* initialize hardware */
+       if (bttv_gpio)
+               bttv_gpio_tracking(btv,"pre-init");
+       bttv_risc_init_main(btv);
+       init_bt848(btv);
+       /* gpio */
+       btwrite(0x00, BT848_GPIO_REG_INP);
+       btwrite(0x00, BT848_GPIO_OUT_EN);
+       if (bttv_verbose)
+               bttv_gpio_tracking(btv,"init");
+       /* needs to be done before i2c is registered */
+       bttv_init_card1(btv);
+       /* register i2c + gpio */
+       init_bttv_i2c(btv);
+       /* some card-specific stuff (needs working i2c) */
+       bttv_init_card2(btv);
+       bttv_init_tuner(btv);
+       init_irqreg(btv);
+       /* register video4linux + input */
+       if (!bttv_tvcards[btv->c.type].no_video) {
+               bttv_register_video(btv);
+               bt848_bright(btv,32768);
+               bt848_contrast(btv, 27648);
+               bt848_hue(btv,32768);
+               bt848_sat(btv,32768);
+               audio_mute(btv, 1);
+               set_input(btv, 0, btv->tvnorm);
+               bttv_crop_reset(&btv->crop[0], btv->tvnorm);
+               btv->crop[1] = btv->crop[0]; /* current = default */
+               disclaim_vbi_lines(btv);
+               disclaim_video_lines(btv);
+       }
+       /* add subdevices and autoload dvb-bt8xx if needed */
+       if (bttv_tvcards[btv->c.type].has_dvb) {
+               bttv_sub_add_device(&btv->c, "dvb");
+               request_modules(btv);
+       }
+       if (!disable_ir) {
+               init_bttv_i2c_ir(btv);
+               bttv_input_init(btv);
+       }
+       /* everything is fine */
+       bttv_num++;
+       return 0;
+ fail2:
+       free_irq(btv->c.pci->irq,btv);
+ fail1:
+       v4l2_device_unregister(&btv->c.v4l2_dev);
+ fail0:
+       if (btv->bt848_mmio)
+               iounmap(btv->bt848_mmio);
+       release_mem_region(pci_resource_start(btv->c.pci,0),
+                          pci_resource_len(btv->c.pci,0));
+       return result;
+ }
+ static void __devexit bttv_remove(struct pci_dev *pci_dev)
+ {
+       struct v4l2_device *v4l2_dev = pci_get_drvdata(pci_dev);
+       struct bttv *btv = to_bttv(v4l2_dev);
+       if (bttv_verbose)
+               pr_info("%d: unloading\n", btv->c.nr);
+       if (bttv_tvcards[btv->c.type].has_dvb)
+               flush_request_modules(btv);
+       /* shutdown everything (DMA+IRQs) */
+       btand(~15, BT848_GPIO_DMA_CTL);
+       btwrite(0, BT848_INT_MASK);
+       btwrite(~0x0, BT848_INT_STAT);
+       btwrite(0x0, BT848_GPIO_OUT_EN);
+       if (bttv_gpio)
+               bttv_gpio_tracking(btv,"cleanup");
+       /* tell gpio modules we are leaving ... */
+       btv->shutdown=1;
+       bttv_input_fini(btv);
+       bttv_sub_del_devices(&btv->c);
+       /* unregister i2c_bus + input */
+       fini_bttv_i2c(btv);
+       /* unregister video4linux */
+       bttv_unregister_video(btv);
+       /* free allocated memory */
+       btcx_riscmem_free(btv->c.pci,&btv->main);
+       /* free ressources */
+       free_irq(btv->c.pci->irq,btv);
+       iounmap(btv->bt848_mmio);
+       release_mem_region(pci_resource_start(btv->c.pci,0),
+                          pci_resource_len(btv->c.pci,0));
+       v4l2_device_unregister(&btv->c.v4l2_dev);
+       bttvs[btv->c.nr] = NULL;
+       kfree(btv);
+       return;
+ }
+ #ifdef CONFIG_PM
+ static int bttv_suspend(struct pci_dev *pci_dev, pm_message_t state)
+ {
+       struct v4l2_device *v4l2_dev = pci_get_drvdata(pci_dev);
+       struct bttv *btv = to_bttv(v4l2_dev);
+       struct bttv_buffer_set idle;
+       unsigned long flags;
+       dprintk("%d: suspend %d\n", btv->c.nr, state.event);
+       /* stop dma + irqs */
+       spin_lock_irqsave(&btv->s_lock,flags);
+       memset(&idle, 0, sizeof(idle));
+       btv->state.video = btv->curr;
+       btv->state.vbi   = btv->cvbi;
+       btv->state.loop_irq = btv->loop_irq;
+       btv->curr = idle;
+       btv->loop_irq = 0;
+       bttv_buffer_activate_video(btv, &idle);
+       bttv_buffer_activate_vbi(btv, NULL);
+       bttv_set_dma(btv, 0);
+       btwrite(0, BT848_INT_MASK);
+       spin_unlock_irqrestore(&btv->s_lock,flags);
+       /* save bt878 state */
+       btv->state.gpio_enable = btread(BT848_GPIO_OUT_EN);
+       btv->state.gpio_data   = gpio_read();
+       /* save pci state */
+       pci_save_state(pci_dev);
+       if (0 != pci_set_power_state(pci_dev, pci_choose_state(pci_dev, state))) {
+               pci_disable_device(pci_dev);
+               btv->state.disabled = 1;
+       }
+       return 0;
+ }
+ static int bttv_resume(struct pci_dev *pci_dev)
+ {
+       struct v4l2_device *v4l2_dev = pci_get_drvdata(pci_dev);
+       struct bttv *btv = to_bttv(v4l2_dev);
+       unsigned long flags;
+       int err;
+       dprintk("%d: resume\n", btv->c.nr);
+       /* restore pci state */
+       if (btv->state.disabled) {
+               err=pci_enable_device(pci_dev);
+               if (err) {
+                       pr_warn("%d: Can't enable device\n", btv->c.nr);
+                       return err;
+               }
+               btv->state.disabled = 0;
+       }
+       err=pci_set_power_state(pci_dev, PCI_D0);
+       if (err) {
+               pci_disable_device(pci_dev);
+               pr_warn("%d: Can't enable device\n", btv->c.nr);
+               btv->state.disabled = 1;
+               return err;
+       }
+       pci_restore_state(pci_dev);
+       /* restore bt878 state */
+       bttv_reinit_bt848(btv);
+       gpio_inout(0xffffff, btv->state.gpio_enable);
+       gpio_write(btv->state.gpio_data);
+       /* restart dma */
+       spin_lock_irqsave(&btv->s_lock,flags);
+       btv->curr = btv->state.video;
+       btv->cvbi = btv->state.vbi;
+       btv->loop_irq = btv->state.loop_irq;
+       bttv_buffer_activate_video(btv, &btv->curr);
+       bttv_buffer_activate_vbi(btv, btv->cvbi);
+       bttv_set_dma(btv, 0);
+       spin_unlock_irqrestore(&btv->s_lock,flags);
+       return 0;
+ }
+ #endif
+ static struct pci_device_id bttv_pci_tbl[] = {
+       {PCI_VDEVICE(BROOKTREE, PCI_DEVICE_ID_BT848), 0},
+       {PCI_VDEVICE(BROOKTREE, PCI_DEVICE_ID_BT849), 0},
+       {PCI_VDEVICE(BROOKTREE, PCI_DEVICE_ID_BT878), 0},
+       {PCI_VDEVICE(BROOKTREE, PCI_DEVICE_ID_BT879), 0},
+       {PCI_VDEVICE(BROOKTREE, PCI_DEVICE_ID_FUSION879), 0},
+       {0,}
+ };
+ MODULE_DEVICE_TABLE(pci, bttv_pci_tbl);
+ static struct pci_driver bttv_pci_driver = {
+       .name     = "bttv",
+       .id_table = bttv_pci_tbl,
+       .probe    = bttv_probe,
+       .remove   = __devexit_p(bttv_remove),
+ #ifdef CONFIG_PM
+       .suspend  = bttv_suspend,
+       .resume   = bttv_resume,
+ #endif
+ };
+ static int __init bttv_init_module(void)
+ {
+       int ret;
+       bttv_num = 0;
+       pr_info("driver version %s loaded\n", BTTV_VERSION);
+       if (gbuffers < 2 || gbuffers > VIDEO_MAX_FRAME)
+               gbuffers = 2;
+       if (gbufsize > BTTV_MAX_FBUF)
+               gbufsize = BTTV_MAX_FBUF;
+       gbufsize = (gbufsize + PAGE_SIZE - 1) & PAGE_MASK;
+       if (bttv_verbose)
+               pr_info("using %d buffers with %dk (%d pages) each for capture\n",
+                       gbuffers, gbufsize >> 10, gbufsize >> PAGE_SHIFT);
+       bttv_check_chipset();
+       ret = bus_register(&bttv_sub_bus_type);
+       if (ret < 0) {
+               pr_warn("bus_register error: %d\n", ret);
+               return ret;
+       }
+       ret = pci_register_driver(&bttv_pci_driver);
+       if (ret < 0)
+               bus_unregister(&bttv_sub_bus_type);
+       return ret;
+ }
+ static void __exit bttv_cleanup_module(void)
+ {
+       pci_unregister_driver(&bttv_pci_driver);
+       bus_unregister(&bttv_sub_bus_type);
+ }
+ module_init(bttv_init_module);
+ module_exit(bttv_cleanup_module);
+ /*
+  * Local variables:
+  * c-basic-offset: 8
+  * End:
+  */
index 0000000000000000000000000000000000000000,c67733d32c8a888d61ced937bb6427c38dc830fa..039133d692e34e0f839c1496ce13cb6b869b67d8
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,1360 +1,1360 @@@
 -      flush_work_sync(&dev->request_module_wk);
+ /*
+  *  cx18 driver initialization and card probing
+  *
+  *  Derived from ivtv-driver.c
+  *
+  *  Copyright (C) 2007  Hans Verkuil <hverkuil@xs4all.nl>
+  *  Copyright (C) 2008  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 "cx18-driver.h"
+ #include "cx18-io.h"
+ #include "cx18-version.h"
+ #include "cx18-cards.h"
+ #include "cx18-i2c.h"
+ #include "cx18-irq.h"
+ #include "cx18-gpio.h"
+ #include "cx18-firmware.h"
+ #include "cx18-queue.h"
+ #include "cx18-streams.h"
+ #include "cx18-av-core.h"
+ #include "cx18-scb.h"
+ #include "cx18-mailbox.h"
+ #include "cx18-ioctl.h"
+ #include "cx18-controls.h"
+ #include "tuner-xc2028.h"
+ #include <linux/dma-mapping.h>
+ #include <media/tveeprom.h>
+ /* If you have already X v4l cards, then set this to X. This way
+    the device numbers stay matched. Example: you have a WinTV card
+    without radio and a Compro H900 with. Normally this would give a
+    video1 device together with a radio0 device for the Compro. By
+    setting this to 1 you ensure that radio0 is now also radio1. */
+ int cx18_first_minor;
+ /* Callback for registering extensions */
+ int (*cx18_ext_init)(struct cx18 *);
+ EXPORT_SYMBOL(cx18_ext_init);
+ /* add your revision and whatnot here */
+ static struct pci_device_id cx18_pci_tbl[] __devinitdata = {
+       {PCI_VENDOR_ID_CX, PCI_DEVICE_ID_CX23418,
+        PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
+       {0,}
+ };
+ MODULE_DEVICE_TABLE(pci, cx18_pci_tbl);
+ static atomic_t cx18_instance = ATOMIC_INIT(0);
+ /* Parameter declarations */
+ static int cardtype[CX18_MAX_CARDS];
+ static int tuner[CX18_MAX_CARDS] = { -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 };
+ static int radio[CX18_MAX_CARDS] = { -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 };
+ static unsigned cardtype_c = 1;
+ static unsigned tuner_c = 1;
+ static unsigned radio_c = 1;
+ static char pal[] = "--";
+ static char secam[] = "--";
+ static char ntsc[] = "-";
+ /* Buffers */
+ static int enc_ts_buffers = CX18_DEFAULT_ENC_TS_BUFFERS;
+ static int enc_mpg_buffers = CX18_DEFAULT_ENC_MPG_BUFFERS;
+ static int enc_idx_buffers = CX18_DEFAULT_ENC_IDX_BUFFERS;
+ static int enc_yuv_buffers = CX18_DEFAULT_ENC_YUV_BUFFERS;
+ static int enc_vbi_buffers = CX18_DEFAULT_ENC_VBI_BUFFERS;
+ static int enc_pcm_buffers = CX18_DEFAULT_ENC_PCM_BUFFERS;
+ static int enc_ts_bufsize = CX18_DEFAULT_ENC_TS_BUFSIZE;
+ static int enc_mpg_bufsize = CX18_DEFAULT_ENC_MPG_BUFSIZE;
+ static int enc_idx_bufsize = CX18_DEFAULT_ENC_IDX_BUFSIZE;
+ static int enc_yuv_bufsize = CX18_DEFAULT_ENC_YUV_BUFSIZE;
+ static int enc_pcm_bufsize = CX18_DEFAULT_ENC_PCM_BUFSIZE;
+ static int enc_ts_bufs = -1;
+ static int enc_mpg_bufs = -1;
+ static int enc_idx_bufs = CX18_MAX_FW_MDLS_PER_STREAM;
+ static int enc_yuv_bufs = -1;
+ static int enc_vbi_bufs = -1;
+ static int enc_pcm_bufs = -1;
+ static int cx18_pci_latency = 1;
+ static int mmio_ndelay;
+ static int retry_mmio = 1;
+ int cx18_debug;
+ module_param_array(tuner, int, &tuner_c, 0644);
+ module_param_array(radio, int, &radio_c, 0644);
+ module_param_array(cardtype, int, &cardtype_c, 0644);
+ module_param_string(pal, pal, sizeof(pal), 0644);
+ module_param_string(secam, secam, sizeof(secam), 0644);
+ module_param_string(ntsc, ntsc, sizeof(ntsc), 0644);
+ module_param_named(debug, cx18_debug, int, 0644);
+ module_param(mmio_ndelay, int, 0644);
+ module_param(retry_mmio, int, 0644);
+ module_param(cx18_pci_latency, int, 0644);
+ module_param(cx18_first_minor, int, 0644);
+ module_param(enc_ts_buffers, int, 0644);
+ module_param(enc_mpg_buffers, int, 0644);
+ module_param(enc_idx_buffers, int, 0644);
+ module_param(enc_yuv_buffers, int, 0644);
+ module_param(enc_vbi_buffers, int, 0644);
+ module_param(enc_pcm_buffers, int, 0644);
+ module_param(enc_ts_bufsize, int, 0644);
+ module_param(enc_mpg_bufsize, int, 0644);
+ module_param(enc_idx_bufsize, int, 0644);
+ module_param(enc_yuv_bufsize, int, 0644);
+ module_param(enc_pcm_bufsize, int, 0644);
+ module_param(enc_ts_bufs, int, 0644);
+ module_param(enc_mpg_bufs, int, 0644);
+ module_param(enc_idx_bufs, int, 0644);
+ module_param(enc_yuv_bufs, int, 0644);
+ module_param(enc_vbi_bufs, int, 0644);
+ module_param(enc_pcm_bufs, int, 0644);
+ MODULE_PARM_DESC(tuner, "Tuner type selection,\n"
+                       "\t\t\tsee tuner.h for values");
+ MODULE_PARM_DESC(radio,
+                "Enable or disable the radio. Use only if autodetection\n"
+                "\t\t\tfails. 0 = disable, 1 = enable");
+ MODULE_PARM_DESC(cardtype,
+                "Only use this option if your card is not detected properly.\n"
+                "\t\tSpecify card type:\n"
+                "\t\t\t 1 = Hauppauge HVR 1600 (ESMT memory)\n"
+                "\t\t\t 2 = Hauppauge HVR 1600 (Samsung memory)\n"
+                "\t\t\t 3 = Compro VideoMate H900\n"
+                "\t\t\t 4 = Yuan MPC718\n"
+                "\t\t\t 5 = Conexant Raptor PAL/SECAM\n"
+                "\t\t\t 6 = Toshiba Qosmio DVB-T/Analog\n"
+                "\t\t\t 7 = Leadtek WinFast PVR2100\n"
+                "\t\t\t 8 = Leadtek WinFast DVR3100 H\n"
+                "\t\t\t 9 = GoTView PCI DVD3 Hybrid\n"
+                "\t\t\t 10 = Hauppauge HVR 1600 (S5H1411)\n"
+                "\t\t\t 0 = Autodetect (default)\n"
+                "\t\t\t-1 = Ignore this card\n\t\t");
+ MODULE_PARM_DESC(pal, "Set PAL standard: B, G, H, D, K, I, M, N, Nc, 60");
+ MODULE_PARM_DESC(secam, "Set SECAM standard: B, G, H, D, K, L, LC");
+ MODULE_PARM_DESC(ntsc, "Set NTSC standard: M, J, K");
+ MODULE_PARM_DESC(debug,
+                "Debug level (bitmask). Default: 0\n"
+                "\t\t\t  1/0x0001: warning\n"
+                "\t\t\t  2/0x0002: info\n"
+                "\t\t\t  4/0x0004: mailbox\n"
+                "\t\t\t  8/0x0008: dma\n"
+                "\t\t\t 16/0x0010: ioctl\n"
+                "\t\t\t 32/0x0020: file\n"
+                "\t\t\t 64/0x0040: i2c\n"
+                "\t\t\t128/0x0080: irq\n"
+                "\t\t\t256/0x0100: high volume\n");
+ MODULE_PARM_DESC(cx18_pci_latency,
+                "Change the PCI latency to 64 if lower: 0 = No, 1 = Yes,\n"
+                "\t\t\tDefault: Yes");
+ MODULE_PARM_DESC(retry_mmio,
+                "(Deprecated) MMIO writes are now always checked and retried\n"
+                "\t\t\tEffectively: 1 [Yes]");
+ MODULE_PARM_DESC(mmio_ndelay,
+                "(Deprecated) MMIO accesses are now never purposely delayed\n"
+                "\t\t\tEffectively: 0 ns");
+ MODULE_PARM_DESC(enc_ts_buffers,
+                "Encoder TS buffer memory (MB). (enc_ts_bufs can override)\n"
+                "\t\t\tDefault: " __stringify(CX18_DEFAULT_ENC_TS_BUFFERS));
+ MODULE_PARM_DESC(enc_ts_bufsize,
+                "Size of an encoder TS buffer (kB)\n"
+                "\t\t\tDefault: " __stringify(CX18_DEFAULT_ENC_TS_BUFSIZE));
+ MODULE_PARM_DESC(enc_ts_bufs,
+                "Number of encoder TS buffers\n"
+                "\t\t\tDefault is computed from other enc_ts_* parameters");
+ MODULE_PARM_DESC(enc_mpg_buffers,
+                "Encoder MPG buffer memory (MB). (enc_mpg_bufs can override)\n"
+                "\t\t\tDefault: " __stringify(CX18_DEFAULT_ENC_MPG_BUFFERS));
+ MODULE_PARM_DESC(enc_mpg_bufsize,
+                "Size of an encoder MPG buffer (kB)\n"
+                "\t\t\tDefault: " __stringify(CX18_DEFAULT_ENC_MPG_BUFSIZE));
+ MODULE_PARM_DESC(enc_mpg_bufs,
+                "Number of encoder MPG buffers\n"
+                "\t\t\tDefault is computed from other enc_mpg_* parameters");
+ MODULE_PARM_DESC(enc_idx_buffers,
+                "(Deprecated) Encoder IDX buffer memory (MB)\n"
+                "\t\t\tIgnored, except 0 disables IDX buffer allocations\n"
+                "\t\t\tDefault: 1 [Enabled]");
+ MODULE_PARM_DESC(enc_idx_bufsize,
+                "Size of an encoder IDX buffer (kB)\n"
+                "\t\t\tAllowed values are multiples of 1.5 kB rounded up\n"
+                "\t\t\t(multiples of size required for 64 index entries)\n"
+                "\t\t\tDefault: 2");
+ MODULE_PARM_DESC(enc_idx_bufs,
+                "Number of encoder IDX buffers\n"
+                "\t\t\tDefault: " __stringify(CX18_MAX_FW_MDLS_PER_STREAM));
+ MODULE_PARM_DESC(enc_yuv_buffers,
+                "Encoder YUV buffer memory (MB). (enc_yuv_bufs can override)\n"
+                "\t\t\tDefault: " __stringify(CX18_DEFAULT_ENC_YUV_BUFFERS));
+ MODULE_PARM_DESC(enc_yuv_bufsize,
+                "Size of an encoder YUV buffer (kB)\n"
+                "\t\t\tAllowed values are multiples of 33.75 kB rounded up\n"
+                "\t\t\t(multiples of size required for 32 screen lines)\n"
+                "\t\t\tDefault: 102");
+ MODULE_PARM_DESC(enc_yuv_bufs,
+                "Number of encoder YUV buffers\n"
+                "\t\t\tDefault is computed from other enc_yuv_* parameters");
+ MODULE_PARM_DESC(enc_vbi_buffers,
+                "Encoder VBI buffer memory (MB). (enc_vbi_bufs can override)\n"
+                "\t\t\tDefault: " __stringify(CX18_DEFAULT_ENC_VBI_BUFFERS));
+ MODULE_PARM_DESC(enc_vbi_bufs,
+                "Number of encoder VBI buffers\n"
+                "\t\t\tDefault is computed from enc_vbi_buffers");
+ MODULE_PARM_DESC(enc_pcm_buffers,
+                "Encoder PCM buffer memory (MB). (enc_pcm_bufs can override)\n"
+                "\t\t\tDefault: " __stringify(CX18_DEFAULT_ENC_PCM_BUFFERS));
+ MODULE_PARM_DESC(enc_pcm_bufsize,
+                "Size of an encoder PCM buffer (kB)\n"
+                "\t\t\tDefault: " __stringify(CX18_DEFAULT_ENC_PCM_BUFSIZE));
+ MODULE_PARM_DESC(enc_pcm_bufs,
+                "Number of encoder PCM buffers\n"
+                "\t\t\tDefault is computed from other enc_pcm_* parameters");
+ MODULE_PARM_DESC(cx18_first_minor,
+                "Set device node number assigned to first card");
+ MODULE_AUTHOR("Hans Verkuil");
+ MODULE_DESCRIPTION("CX23418 driver");
+ MODULE_SUPPORTED_DEVICE("CX23418 MPEG2 encoder");
+ MODULE_LICENSE("GPL");
+ MODULE_VERSION(CX18_VERSION);
+ #if defined(CONFIG_MODULES) && defined(MODULE)
+ static void request_module_async(struct work_struct *work)
+ {
+       struct cx18 *dev = container_of(work, struct cx18, request_module_wk);
+       /* Make sure cx18-alsa module is loaded */
+       request_module("cx18-alsa");
+       /* Initialize cx18-alsa for this instance of the cx18 device */
+       if (cx18_ext_init != NULL)
+               cx18_ext_init(dev);
+ }
+ static void request_modules(struct cx18 *dev)
+ {
+       INIT_WORK(&dev->request_module_wk, request_module_async);
+       schedule_work(&dev->request_module_wk);
+ }
+ static void flush_request_modules(struct cx18 *dev)
+ {
++      flush_work(&dev->request_module_wk);
+ }
+ #else
+ #define request_modules(dev)
+ #define flush_request_modules(dev)
+ #endif /* CONFIG_MODULES */
+ /* Generic utility functions */
+ int cx18_msleep_timeout(unsigned int msecs, int intr)
+ {
+       long int timeout = msecs_to_jiffies(msecs);
+       int sig;
+       do {
+               set_current_state(intr ? TASK_INTERRUPTIBLE : TASK_UNINTERRUPTIBLE);
+               timeout = schedule_timeout(timeout);
+               sig = intr ? signal_pending(current) : 0;
+       } while (!sig && timeout);
+       return sig;
+ }
+ /* Release ioremapped memory */
+ static void cx18_iounmap(struct cx18 *cx)
+ {
+       if (cx == NULL)
+               return;
+       /* Release io memory */
+       if (cx->enc_mem != NULL) {
+               CX18_DEBUG_INFO("releasing enc_mem\n");
+               iounmap(cx->enc_mem);
+               cx->enc_mem = NULL;
+       }
+ }
+ static void cx18_eeprom_dump(struct cx18 *cx, unsigned char *eedata, int len)
+ {
+       int i;
+       CX18_INFO("eeprom dump:\n");
+       for (i = 0; i < len; i++) {
+               if (0 == (i % 16))
+                       CX18_INFO("eeprom %02x:", i);
+               printk(KERN_CONT " %02x", eedata[i]);
+               if (15 == (i % 16))
+                       printk(KERN_CONT "\n");
+       }
+ }
+ /* Hauppauge card? get values from tveeprom */
+ void cx18_read_eeprom(struct cx18 *cx, struct tveeprom *tv)
+ {
+       struct i2c_client c;
+       u8 eedata[256];
+       memset(&c, 0, sizeof(c));
+       strlcpy(c.name, "cx18 tveeprom tmp", sizeof(c.name));
+       c.adapter = &cx->i2c_adap[0];
+       c.addr = 0xA0 >> 1;
+       memset(tv, 0, sizeof(*tv));
+       if (tveeprom_read(&c, eedata, sizeof(eedata)))
+               return;
+       switch (cx->card->type) {
+       case CX18_CARD_HVR_1600_ESMT:
+       case CX18_CARD_HVR_1600_SAMSUNG:
+       case CX18_CARD_HVR_1600_S5H1411:
+               tveeprom_hauppauge_analog(&c, tv, eedata);
+               break;
+       case CX18_CARD_YUAN_MPC718:
+       case CX18_CARD_GOTVIEW_PCI_DVD3:
+               tv->model = 0x718;
+               cx18_eeprom_dump(cx, eedata, sizeof(eedata));
+               CX18_INFO("eeprom PCI ID: %02x%02x:%02x%02x\n",
+                         eedata[2], eedata[1], eedata[4], eedata[3]);
+               break;
+       default:
+               tv->model = 0xffffffff;
+               cx18_eeprom_dump(cx, eedata, sizeof(eedata));
+               break;
+       }
+ }
+ static void cx18_process_eeprom(struct cx18 *cx)
+ {
+       struct tveeprom tv;
+       cx18_read_eeprom(cx, &tv);
+       /* Many thanks to Steven Toth from Hauppauge for providing the
+          model numbers */
+       /* Note: the Samsung memory models cannot be reliably determined
+          from the model number. Use the cardtype module option if you
+          have one of these preproduction models. */
+       switch (tv.model) {
+       case 74301: /* Retail models */
+       case 74321:
+       case 74351: /* OEM models */
+       case 74361:
+               /* Digital side is s5h1411/tda18271 */
+               cx->card = cx18_get_card(CX18_CARD_HVR_1600_S5H1411);
+               break;
+       case 74021: /* Retail models */
+       case 74031:
+       case 74041:
+       case 74141:
+       case 74541: /* OEM models */
+       case 74551:
+       case 74591:
+       case 74651:
+       case 74691:
+       case 74751:
+       case 74891:
+               /* Digital side is s5h1409/mxl5005s */
+               cx->card = cx18_get_card(CX18_CARD_HVR_1600_ESMT);
+               break;
+       case 0x718:
+               return;
+       case 0xffffffff:
+               CX18_INFO("Unknown EEPROM encoding\n");
+               return;
+       case 0:
+               CX18_ERR("Invalid EEPROM\n");
+               return;
+       default:
+               CX18_ERR("Unknown model %d, defaulting to original HVR-1600 "
+                        "(cardtype=1)\n", tv.model);
+               cx->card = cx18_get_card(CX18_CARD_HVR_1600_ESMT);
+               break;
+       }
+       cx->v4l2_cap = cx->card->v4l2_capabilities;
+       cx->card_name = cx->card->name;
+       cx->card_i2c = cx->card->i2c;
+       CX18_INFO("Autodetected %s\n", cx->card_name);
+       if (tv.tuner_type == TUNER_ABSENT)
+               CX18_ERR("tveeprom cannot autodetect tuner!\n");
+       if (cx->options.tuner == -1)
+               cx->options.tuner = tv.tuner_type;
+       if (cx->options.radio == -1)
+               cx->options.radio = (tv.has_radio != 0);
+       if (cx->std != 0)
+               /* user specified tuner standard */
+               return;
+       /* autodetect tuner standard */
+ #define TVEEPROM_TUNER_FORMAT_ALL (V4L2_STD_B  | V4L2_STD_GH | \
+                                  V4L2_STD_MN | \
+                                  V4L2_STD_PAL_I | \
+                                  V4L2_STD_SECAM_L | V4L2_STD_SECAM_LC | \
+                                  V4L2_STD_DK)
+       if ((tv.tuner_formats & TVEEPROM_TUNER_FORMAT_ALL)
+                                       == TVEEPROM_TUNER_FORMAT_ALL) {
+               CX18_DEBUG_INFO("Worldwide tuner detected\n");
+               cx->std = V4L2_STD_ALL;
+       } else if (tv.tuner_formats & V4L2_STD_PAL) {
+               CX18_DEBUG_INFO("PAL tuner detected\n");
+               cx->std |= V4L2_STD_PAL_BG | V4L2_STD_PAL_H;
+       } else if (tv.tuner_formats & V4L2_STD_NTSC) {
+               CX18_DEBUG_INFO("NTSC tuner detected\n");
+               cx->std |= V4L2_STD_NTSC_M;
+       } else if (tv.tuner_formats & V4L2_STD_SECAM) {
+               CX18_DEBUG_INFO("SECAM tuner detected\n");
+               cx->std |= V4L2_STD_SECAM_L;
+       } else {
+               CX18_INFO("No tuner detected, default to NTSC-M\n");
+               cx->std |= V4L2_STD_NTSC_M;
+       }
+ }
+ static v4l2_std_id cx18_parse_std(struct cx18 *cx)
+ {
+       switch (pal[0]) {
+       case '6':
+               return V4L2_STD_PAL_60;
+       case 'b':
+       case 'B':
+       case 'g':
+       case 'G':
+               return V4L2_STD_PAL_BG;
+       case 'h':
+       case 'H':
+               return V4L2_STD_PAL_H;
+       case 'n':
+       case 'N':
+               if (pal[1] == 'c' || pal[1] == 'C')
+                       return V4L2_STD_PAL_Nc;
+               return V4L2_STD_PAL_N;
+       case 'i':
+       case 'I':
+               return V4L2_STD_PAL_I;
+       case 'd':
+       case 'D':
+       case 'k':
+       case 'K':
+               return V4L2_STD_PAL_DK;
+       case 'M':
+       case 'm':
+               return V4L2_STD_PAL_M;
+       case '-':
+               break;
+       default:
+               CX18_WARN("pal= argument not recognised\n");
+               return 0;
+       }
+       switch (secam[0]) {
+       case 'b':
+       case 'B':
+       case 'g':
+       case 'G':
+       case 'h':
+       case 'H':
+               return V4L2_STD_SECAM_B | V4L2_STD_SECAM_G | V4L2_STD_SECAM_H;
+       case 'd':
+       case 'D':
+       case 'k':
+       case 'K':
+               return V4L2_STD_SECAM_DK;
+       case 'l':
+       case 'L':
+               if (secam[1] == 'C' || secam[1] == 'c')
+                       return V4L2_STD_SECAM_LC;
+               return V4L2_STD_SECAM_L;
+       case '-':
+               break;
+       default:
+               CX18_WARN("secam= argument not recognised\n");
+               return 0;
+       }
+       switch (ntsc[0]) {
+       case 'm':
+       case 'M':
+               return V4L2_STD_NTSC_M;
+       case 'j':
+       case 'J':
+               return V4L2_STD_NTSC_M_JP;
+       case 'k':
+       case 'K':
+               return V4L2_STD_NTSC_M_KR;
+       case '-':
+               break;
+       default:
+               CX18_WARN("ntsc= argument not recognised\n");
+               return 0;
+       }
+       /* no match found */
+       return 0;
+ }
+ static void cx18_process_options(struct cx18 *cx)
+ {
+       int i, j;
+       cx->options.megabytes[CX18_ENC_STREAM_TYPE_TS] = enc_ts_buffers;
+       cx->options.megabytes[CX18_ENC_STREAM_TYPE_MPG] = enc_mpg_buffers;
+       cx->options.megabytes[CX18_ENC_STREAM_TYPE_IDX] = enc_idx_buffers;
+       cx->options.megabytes[CX18_ENC_STREAM_TYPE_YUV] = enc_yuv_buffers;
+       cx->options.megabytes[CX18_ENC_STREAM_TYPE_VBI] = enc_vbi_buffers;
+       cx->options.megabytes[CX18_ENC_STREAM_TYPE_PCM] = enc_pcm_buffers;
+       cx->options.megabytes[CX18_ENC_STREAM_TYPE_RAD] = 0; /* control only */
+       cx->stream_buffers[CX18_ENC_STREAM_TYPE_TS] = enc_ts_bufs;
+       cx->stream_buffers[CX18_ENC_STREAM_TYPE_MPG] = enc_mpg_bufs;
+       cx->stream_buffers[CX18_ENC_STREAM_TYPE_IDX] = enc_idx_bufs;
+       cx->stream_buffers[CX18_ENC_STREAM_TYPE_YUV] = enc_yuv_bufs;
+       cx->stream_buffers[CX18_ENC_STREAM_TYPE_VBI] = enc_vbi_bufs;
+       cx->stream_buffers[CX18_ENC_STREAM_TYPE_PCM] = enc_pcm_bufs;
+       cx->stream_buffers[CX18_ENC_STREAM_TYPE_RAD] = 0; /* control, no data */
+       cx->stream_buf_size[CX18_ENC_STREAM_TYPE_TS] = enc_ts_bufsize;
+       cx->stream_buf_size[CX18_ENC_STREAM_TYPE_MPG] = enc_mpg_bufsize;
+       cx->stream_buf_size[CX18_ENC_STREAM_TYPE_IDX] = enc_idx_bufsize;
+       cx->stream_buf_size[CX18_ENC_STREAM_TYPE_YUV] = enc_yuv_bufsize;
+       cx->stream_buf_size[CX18_ENC_STREAM_TYPE_VBI] = vbi_active_samples * 36;
+       cx->stream_buf_size[CX18_ENC_STREAM_TYPE_PCM] = enc_pcm_bufsize;
+       cx->stream_buf_size[CX18_ENC_STREAM_TYPE_RAD] = 0; /* control no data */
+       /* Ensure stream_buffers & stream_buf_size are valid */
+       for (i = 0; i < CX18_MAX_STREAMS; i++) {
+               if (cx->stream_buffers[i] == 0 ||     /* User said 0 buffers */
+                   cx->options.megabytes[i] <= 0 ||  /* User said 0 MB total */
+                   cx->stream_buf_size[i] <= 0) {    /* User said buf size 0 */
+                       cx->options.megabytes[i] = 0;
+                       cx->stream_buffers[i] = 0;
+                       cx->stream_buf_size[i] = 0;
+                       continue;
+               }
+               /*
+                * YUV is a special case where the stream_buf_size needs to be
+                * an integral multiple of 33.75 kB (storage for 32 screens
+                * lines to maintain alignment in case of lost buffers).
+                *
+                * IDX is a special case where the stream_buf_size should be
+                * an integral multiple of 1.5 kB (storage for 64 index entries
+                * to maintain alignment in case of lost buffers).
+                *
+                */
+               if (i == CX18_ENC_STREAM_TYPE_YUV) {
+                       cx->stream_buf_size[i] *= 1024;
+                       cx->stream_buf_size[i] -=
+                          (cx->stream_buf_size[i] % CX18_UNIT_ENC_YUV_BUFSIZE);
+                       if (cx->stream_buf_size[i] < CX18_UNIT_ENC_YUV_BUFSIZE)
+                               cx->stream_buf_size[i] =
+                                               CX18_UNIT_ENC_YUV_BUFSIZE;
+               } else if (i == CX18_ENC_STREAM_TYPE_IDX) {
+                       cx->stream_buf_size[i] *= 1024;
+                       cx->stream_buf_size[i] -=
+                          (cx->stream_buf_size[i] % CX18_UNIT_ENC_IDX_BUFSIZE);
+                       if (cx->stream_buf_size[i] < CX18_UNIT_ENC_IDX_BUFSIZE)
+                               cx->stream_buf_size[i] =
+                                               CX18_UNIT_ENC_IDX_BUFSIZE;
+               }
+               /*
+                * YUV and IDX are special cases where the stream_buf_size is
+                * now in bytes.
+                * VBI is a special case where the stream_buf_size is fixed
+                * and already in bytes
+                */
+               if (i == CX18_ENC_STREAM_TYPE_VBI ||
+                   i == CX18_ENC_STREAM_TYPE_YUV ||
+                   i == CX18_ENC_STREAM_TYPE_IDX) {
+                       if (cx->stream_buffers[i] < 0) {
+                               cx->stream_buffers[i] =
+                                       cx->options.megabytes[i] * 1024 * 1024
+                                       / cx->stream_buf_size[i];
+                       } else {
+                               /* N.B. This might round down to 0 */
+                               cx->options.megabytes[i] =
+                                       cx->stream_buffers[i]
+                                       * cx->stream_buf_size[i]/(1024 * 1024);
+                       }
+               } else {
+                       /* All other streams have stream_buf_size in kB here */
+                       if (cx->stream_buffers[i] < 0) {
+                               cx->stream_buffers[i] =
+                                               cx->options.megabytes[i] * 1024
+                                               / cx->stream_buf_size[i];
+                       } else {
+                               /* N.B. This might round down to 0 */
+                               cx->options.megabytes[i] =
+                                               cx->stream_buffers[i]
+                                               * cx->stream_buf_size[i] / 1024;
+                       }
+                       /* convert from kB to bytes */
+                       cx->stream_buf_size[i] *= 1024;
+               }
+               CX18_DEBUG_INFO("Stream type %d options: %d MB, %d buffers, "
+                               "%d bytes\n", i, cx->options.megabytes[i],
+                               cx->stream_buffers[i], cx->stream_buf_size[i]);
+       }
+       cx->options.cardtype = cardtype[cx->instance];
+       cx->options.tuner = tuner[cx->instance];
+       cx->options.radio = radio[cx->instance];
+       cx->std = cx18_parse_std(cx);
+       if (cx->options.cardtype == -1) {
+               CX18_INFO("Ignore card\n");
+               return;
+       }
+       cx->card = cx18_get_card(cx->options.cardtype - 1);
+       if (cx->card)
+               CX18_INFO("User specified %s card\n", cx->card->name);
+       else if (cx->options.cardtype != 0)
+               CX18_ERR("Unknown user specified type, trying to autodetect card\n");
+       if (cx->card == NULL) {
+               if (cx->pci_dev->subsystem_vendor == CX18_PCI_ID_HAUPPAUGE) {
+                       cx->card = cx18_get_card(CX18_CARD_HVR_1600_ESMT);
+                       CX18_INFO("Autodetected Hauppauge card\n");
+               }
+       }
+       if (cx->card == NULL) {
+               for (i = 0; (cx->card = cx18_get_card(i)); i++) {
+                       if (cx->card->pci_list == NULL)
+                               continue;
+                       for (j = 0; cx->card->pci_list[j].device; j++) {
+                               if (cx->pci_dev->device !=
+                                   cx->card->pci_list[j].device)
+                                       continue;
+                               if (cx->pci_dev->subsystem_vendor !=
+                                   cx->card->pci_list[j].subsystem_vendor)
+                                       continue;
+                               if (cx->pci_dev->subsystem_device !=
+                                   cx->card->pci_list[j].subsystem_device)
+                                       continue;
+                               CX18_INFO("Autodetected %s card\n", cx->card->name);
+                               goto done;
+                       }
+               }
+       }
+ done:
+       if (cx->card == NULL) {
+               cx->card = cx18_get_card(CX18_CARD_HVR_1600_ESMT);
+               CX18_ERR("Unknown card: vendor/device: [%04x:%04x]\n",
+                        cx->pci_dev->vendor, cx->pci_dev->device);
+               CX18_ERR("              subsystem vendor/device: [%04x:%04x]\n",
+                        cx->pci_dev->subsystem_vendor,
+                        cx->pci_dev->subsystem_device);
+               CX18_ERR("Defaulting to %s card\n", cx->card->name);
+               CX18_ERR("Please mail the vendor/device and subsystem vendor/device IDs and what kind of\n");
+               CX18_ERR("card you have to the ivtv-devel mailinglist (www.ivtvdriver.org)\n");
+               CX18_ERR("Prefix your subject line with [UNKNOWN CX18 CARD].\n");
+       }
+       cx->v4l2_cap = cx->card->v4l2_capabilities;
+       cx->card_name = cx->card->name;
+       cx->card_i2c = cx->card->i2c;
+ }
+ static int __devinit cx18_create_in_workq(struct cx18 *cx)
+ {
+       snprintf(cx->in_workq_name, sizeof(cx->in_workq_name), "%s-in",
+                cx->v4l2_dev.name);
+       cx->in_work_queue = alloc_ordered_workqueue(cx->in_workq_name, 0);
+       if (cx->in_work_queue == NULL) {
+               CX18_ERR("Unable to create incoming mailbox handler thread\n");
+               return -ENOMEM;
+       }
+       return 0;
+ }
+ static void __devinit cx18_init_in_work_orders(struct cx18 *cx)
+ {
+       int i;
+       for (i = 0; i < CX18_MAX_IN_WORK_ORDERS; i++) {
+               cx->in_work_order[i].cx = cx;
+               cx->in_work_order[i].str = cx->epu_debug_str;
+               INIT_WORK(&cx->in_work_order[i].work, cx18_in_work_handler);
+       }
+ }
+ /* Precondition: the cx18 structure has been memset to 0. Only
+    the dev and instance fields have been filled in.
+    No assumptions on the card type may be made here (see cx18_init_struct2
+    for that).
+  */
+ static int __devinit cx18_init_struct1(struct cx18 *cx)
+ {
+       int ret;
+       cx->base_addr = pci_resource_start(cx->pci_dev, 0);
+       mutex_init(&cx->serialize_lock);
+       mutex_init(&cx->gpio_lock);
+       mutex_init(&cx->epu2apu_mb_lock);
+       mutex_init(&cx->epu2cpu_mb_lock);
+       ret = cx18_create_in_workq(cx);
+       if (ret)
+               return ret;
+       cx18_init_in_work_orders(cx);
+       /* start counting open_id at 1 */
+       cx->open_id = 1;
+       /* Initial settings */
+       cx->cxhdl.port = CX2341X_PORT_MEMORY;
+       cx->cxhdl.capabilities = CX2341X_CAP_HAS_TS | CX2341X_CAP_HAS_SLICED_VBI;
+       cx->cxhdl.ops = &cx18_cxhdl_ops;
+       cx->cxhdl.func = cx18_api_func;
+       cx->cxhdl.priv = &cx->streams[CX18_ENC_STREAM_TYPE_MPG];
+       ret = cx2341x_handler_init(&cx->cxhdl, 50);
+       if (ret)
+               return ret;
+       cx->v4l2_dev.ctrl_handler = &cx->cxhdl.hdl;
+       cx->temporal_strength = cx->cxhdl.video_temporal_filter->cur.val;
+       cx->spatial_strength = cx->cxhdl.video_spatial_filter->cur.val;
+       cx->filter_mode = cx->cxhdl.video_spatial_filter_mode->cur.val |
+               (cx->cxhdl.video_temporal_filter_mode->cur.val << 1) |
+               (cx->cxhdl.video_median_filter_type->cur.val << 2);
+       init_waitqueue_head(&cx->cap_w);
+       init_waitqueue_head(&cx->mb_apu_waitq);
+       init_waitqueue_head(&cx->mb_cpu_waitq);
+       init_waitqueue_head(&cx->dma_waitq);
+       /* VBI */
+       cx->vbi.in.type = V4L2_BUF_TYPE_VBI_CAPTURE;
+       cx->vbi.sliced_in = &cx->vbi.in.fmt.sliced;
+       /* IVTV style VBI insertion into MPEG streams */
+       INIT_LIST_HEAD(&cx->vbi.sliced_mpeg_buf.list);
+       INIT_LIST_HEAD(&cx->vbi.sliced_mpeg_mdl.list);
+       INIT_LIST_HEAD(&cx->vbi.sliced_mpeg_mdl.buf_list);
+       list_add(&cx->vbi.sliced_mpeg_buf.list,
+                &cx->vbi.sliced_mpeg_mdl.buf_list);
+       return 0;
+ }
+ /* Second initialization part. Here the card type has been
+    autodetected. */
+ static void __devinit cx18_init_struct2(struct cx18 *cx)
+ {
+       int i;
+       for (i = 0; i < CX18_CARD_MAX_VIDEO_INPUTS; i++)
+               if (cx->card->video_inputs[i].video_type == 0)
+                       break;
+       cx->nof_inputs = i;
+       for (i = 0; i < CX18_CARD_MAX_AUDIO_INPUTS; i++)
+               if (cx->card->audio_inputs[i].audio_type == 0)
+                       break;
+       cx->nof_audio_inputs = i;
+       /* Find tuner input */
+       for (i = 0; i < cx->nof_inputs; i++) {
+               if (cx->card->video_inputs[i].video_type ==
+                               CX18_CARD_INPUT_VID_TUNER)
+                       break;
+       }
+       if (i == cx->nof_inputs)
+               i = 0;
+       cx->active_input = i;
+       cx->audio_input = cx->card->video_inputs[i].audio_index;
+ }
+ static int cx18_setup_pci(struct cx18 *cx, struct pci_dev *pci_dev,
+                         const struct pci_device_id *pci_id)
+ {
+       u16 cmd;
+       unsigned char pci_latency;
+       CX18_DEBUG_INFO("Enabling pci device\n");
+       if (pci_enable_device(pci_dev)) {
+               CX18_ERR("Can't enable device %d!\n", cx->instance);
+               return -EIO;
+       }
+       if (pci_set_dma_mask(pci_dev, DMA_BIT_MASK(32))) {
+               CX18_ERR("No suitable DMA available, card %d\n", cx->instance);
+               return -EIO;
+       }
+       if (!request_mem_region(cx->base_addr, CX18_MEM_SIZE, "cx18 encoder")) {
+               CX18_ERR("Cannot request encoder memory region, card %d\n",
+                        cx->instance);
+               return -EIO;
+       }
+       /* Enable bus mastering and memory mapped IO for the CX23418 */
+       pci_read_config_word(pci_dev, PCI_COMMAND, &cmd);
+       cmd |= PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER;
+       pci_write_config_word(pci_dev, PCI_COMMAND, cmd);
+       cx->card_rev = pci_dev->revision;
+       pci_read_config_byte(pci_dev, PCI_LATENCY_TIMER, &pci_latency);
+       if (pci_latency < 64 && cx18_pci_latency) {
+               CX18_INFO("Unreasonably low latency timer, "
+                              "setting to 64 (was %d)\n", pci_latency);
+               pci_write_config_byte(pci_dev, PCI_LATENCY_TIMER, 64);
+               pci_read_config_byte(pci_dev, PCI_LATENCY_TIMER, &pci_latency);
+       }
+       CX18_DEBUG_INFO("cx%d (rev %d) at %02x:%02x.%x, "
+                  "irq: %d, latency: %d, memory: 0x%llx\n",
+                  cx->pci_dev->device, cx->card_rev, pci_dev->bus->number,
+                  PCI_SLOT(pci_dev->devfn), PCI_FUNC(pci_dev->devfn),
+                  cx->pci_dev->irq, pci_latency, (u64)cx->base_addr);
+       return 0;
+ }
+ static void cx18_init_subdevs(struct cx18 *cx)
+ {
+       u32 hw = cx->card->hw_all;
+       u32 device;
+       int i;
+       for (i = 0, device = 1; i < 32; i++, device <<= 1) {
+               if (!(device & hw))
+                       continue;
+               switch (device) {
+               case CX18_HW_DVB:
+               case CX18_HW_TVEEPROM:
+                       /* These subordinate devices do not use probing */
+                       cx->hw_flags |= device;
+                       break;
+               case CX18_HW_418_AV:
+                       /* The A/V decoder gets probed earlier to set PLLs */
+                       /* Just note that the card uses it (i.e. has analog) */
+                       cx->hw_flags |= device;
+                       break;
+               case CX18_HW_GPIO_RESET_CTRL:
+                       /*
+                        * The Reset Controller gets probed and added to
+                        * hw_flags earlier for i2c adapter/bus initialization
+                        */
+                       break;
+               case CX18_HW_GPIO_MUX:
+                       if (cx18_gpio_register(cx, device) == 0)
+                               cx->hw_flags |= device;
+                       break;
+               default:
+                       if (cx18_i2c_register(cx, i) == 0)
+                               cx->hw_flags |= device;
+                       break;
+               }
+       }
+       if (cx->hw_flags & CX18_HW_418_AV)
+               cx->sd_av = cx18_find_hw(cx, CX18_HW_418_AV);
+       if (cx->card->hw_muxer != 0)
+               cx->sd_extmux = cx18_find_hw(cx, cx->card->hw_muxer);
+ }
+ static int __devinit cx18_probe(struct pci_dev *pci_dev,
+                               const struct pci_device_id *pci_id)
+ {
+       int retval = 0;
+       int i;
+       u32 devtype;
+       struct cx18 *cx;
+       /* FIXME - module parameter arrays constrain max instances */
+       i = atomic_inc_return(&cx18_instance) - 1;
+       if (i >= CX18_MAX_CARDS) {
+               printk(KERN_ERR "cx18: cannot manage card %d, driver has a "
+                      "limit of 0 - %d\n", i, CX18_MAX_CARDS - 1);
+               return -ENOMEM;
+       }
+       cx = kzalloc(sizeof(struct cx18), GFP_ATOMIC);
+       if (cx == NULL) {
+               printk(KERN_ERR "cx18: cannot manage card %d, out of memory\n",
+                      i);
+               return -ENOMEM;
+       }
+       cx->pci_dev = pci_dev;
+       cx->instance = i;
+       retval = v4l2_device_register(&pci_dev->dev, &cx->v4l2_dev);
+       if (retval) {
+               printk(KERN_ERR "cx18: v4l2_device_register of card %d failed"
+                      "\n", cx->instance);
+               kfree(cx);
+               return retval;
+       }
+       snprintf(cx->v4l2_dev.name, sizeof(cx->v4l2_dev.name), "cx18-%d",
+                cx->instance);
+       CX18_INFO("Initializing card %d\n", cx->instance);
+       cx18_process_options(cx);
+       if (cx->options.cardtype == -1) {
+               retval = -ENODEV;
+               goto err;
+       }
+       retval = cx18_init_struct1(cx);
+       if (retval)
+               goto err;
+       CX18_DEBUG_INFO("base addr: 0x%llx\n", (u64)cx->base_addr);
+       /* PCI Device Setup */
+       retval = cx18_setup_pci(cx, pci_dev, pci_id);
+       if (retval != 0)
+               goto free_workqueues;
+       /* map io memory */
+       CX18_DEBUG_INFO("attempting ioremap at 0x%llx len 0x%08x\n",
+                  (u64)cx->base_addr + CX18_MEM_OFFSET, CX18_MEM_SIZE);
+       cx->enc_mem = ioremap_nocache(cx->base_addr + CX18_MEM_OFFSET,
+                                      CX18_MEM_SIZE);
+       if (!cx->enc_mem) {
+               CX18_ERR("ioremap failed. Can't get a window into CX23418 "
+                        "memory and register space\n");
+               CX18_ERR("Each capture card with a CX23418 needs 64 MB of "
+                        "vmalloc address space for the window\n");
+               CX18_ERR("Check the output of 'grep Vmalloc /proc/meminfo'\n");
+               CX18_ERR("Use the vmalloc= kernel command line option to set "
+                        "VmallocTotal to a larger value\n");
+               retval = -ENOMEM;
+               goto free_mem;
+       }
+       cx->reg_mem = cx->enc_mem + CX18_REG_OFFSET;
+       devtype = cx18_read_reg(cx, 0xC72028);
+       switch (devtype & 0xff000000) {
+       case 0xff000000:
+               CX18_INFO("cx23418 revision %08x (A)\n", devtype);
+               break;
+       case 0x01000000:
+               CX18_INFO("cx23418 revision %08x (B)\n", devtype);
+               break;
+       default:
+               CX18_INFO("cx23418 revision %08x (Unknown)\n", devtype);
+               break;
+       }
+       cx18_init_power(cx, 1);
+       cx18_init_memory(cx);
+       cx->scb = (struct cx18_scb __iomem *)(cx->enc_mem + SCB_OFFSET);
+       cx18_init_scb(cx);
+       cx18_gpio_init(cx);
+       /* Initialize integrated A/V decoder early to set PLLs, just in case */
+       retval = cx18_av_probe(cx);
+       if (retval) {
+               CX18_ERR("Could not register A/V decoder subdevice\n");
+               goto free_map;
+       }
+       /* Initialize GPIO Reset Controller to do chip resets during i2c init */
+       if (cx->card->hw_all & CX18_HW_GPIO_RESET_CTRL) {
+               if (cx18_gpio_register(cx, CX18_HW_GPIO_RESET_CTRL) != 0)
+                       CX18_WARN("Could not register GPIO reset controller"
+                                 "subdevice; proceeding anyway.\n");
+               else
+                       cx->hw_flags |= CX18_HW_GPIO_RESET_CTRL;
+       }
+       /* active i2c  */
+       CX18_DEBUG_INFO("activating i2c...\n");
+       retval = init_cx18_i2c(cx);
+       if (retval) {
+               CX18_ERR("Could not initialize i2c\n");
+               goto free_map;
+       }
+       if (cx->card->hw_all & CX18_HW_TVEEPROM) {
+               /* Based on the model number the cardtype may be changed.
+                  The PCI IDs are not always reliable. */
+               const struct cx18_card *orig_card = cx->card;
+               cx18_process_eeprom(cx);
+               if (cx->card != orig_card) {
+                       /* Changed the cardtype; re-reset the I2C chips */
+                       cx18_gpio_init(cx);
+                       cx18_call_hw(cx, CX18_HW_GPIO_RESET_CTRL,
+                                       core, reset, (u32) CX18_GPIO_RESET_I2C);
+               }
+       }
+       if (cx->card->comment)
+               CX18_INFO("%s", cx->card->comment);
+       if (cx->card->v4l2_capabilities == 0) {
+               retval = -ENODEV;
+               goto free_i2c;
+       }
+       cx18_init_memory(cx);
+       cx18_init_scb(cx);
+       /* Register IRQ */
+       retval = request_irq(cx->pci_dev->irq, cx18_irq_handler,
+                            IRQF_SHARED | IRQF_DISABLED,
+                            cx->v4l2_dev.name, (void *)cx);
+       if (retval) {
+               CX18_ERR("Failed to register irq %d\n", retval);
+               goto free_i2c;
+       }
+       if (cx->std == 0)
+               cx->std = V4L2_STD_NTSC_M;
+       if (cx->options.tuner == -1) {
+               for (i = 0; i < CX18_CARD_MAX_TUNERS; i++) {
+                       if ((cx->std & cx->card->tuners[i].std) == 0)
+                               continue;
+                       cx->options.tuner = cx->card->tuners[i].tuner;
+                       break;
+               }
+       }
+       /* if no tuner was found, then pick the first tuner in the card list */
+       if (cx->options.tuner == -1 && cx->card->tuners[0].std) {
+               cx->std = cx->card->tuners[0].std;
+               if (cx->std & V4L2_STD_PAL)
+                       cx->std = V4L2_STD_PAL_BG | V4L2_STD_PAL_H;
+               else if (cx->std & V4L2_STD_NTSC)
+                       cx->std = V4L2_STD_NTSC_M;
+               else if (cx->std & V4L2_STD_SECAM)
+                       cx->std = V4L2_STD_SECAM_L;
+               cx->options.tuner = cx->card->tuners[0].tuner;
+       }
+       if (cx->options.radio == -1)
+               cx->options.radio = (cx->card->radio_input.audio_type != 0);
+       /* The card is now fully identified, continue with card-specific
+          initialization. */
+       cx18_init_struct2(cx);
+       cx18_init_subdevs(cx);
+       if (cx->std & V4L2_STD_525_60)
+               cx->is_60hz = 1;
+       else
+               cx->is_50hz = 1;
+       cx2341x_handler_set_50hz(&cx->cxhdl, !cx->is_60hz);
+       if (cx->options.radio > 0)
+               cx->v4l2_cap |= V4L2_CAP_RADIO;
+       if (cx->options.tuner > -1) {
+               struct tuner_setup setup;
+               setup.addr = ADDR_UNSET;
+               setup.type = cx->options.tuner;
+               setup.mode_mask = T_ANALOG_TV;  /* matches TV tuners */
+               if (cx->options.radio > 0)
+                       setup.mode_mask |= T_RADIO;
+               setup.tuner_callback = (setup.type == TUNER_XC2028) ?
+                       cx18_reset_tuner_gpio : NULL;
+               cx18_call_all(cx, tuner, s_type_addr, &setup);
+               if (setup.type == TUNER_XC2028) {
+                       static struct xc2028_ctrl ctrl = {
+                               .fname = XC2028_DEFAULT_FIRMWARE,
+                               .max_len = 64,
+                       };
+                       struct v4l2_priv_tun_config cfg = {
+                               .tuner = cx->options.tuner,
+                               .priv = &ctrl,
+                       };
+                       cx18_call_all(cx, tuner, s_config, &cfg);
+               }
+       }
+       /* The tuner is fixed to the standard. The other inputs (e.g. S-Video)
+          are not. */
+       cx->tuner_std = cx->std;
+       if (cx->std == V4L2_STD_ALL)
+               cx->std = V4L2_STD_NTSC_M;
+       retval = cx18_streams_setup(cx);
+       if (retval) {
+               CX18_ERR("Error %d setting up streams\n", retval);
+               goto free_irq;
+       }
+       retval = cx18_streams_register(cx);
+       if (retval) {
+               CX18_ERR("Error %d registering devices\n", retval);
+               goto free_streams;
+       }
+       CX18_INFO("Initialized card: %s\n", cx->card_name);
+       /* Load cx18 submodules (cx18-alsa) */
+       request_modules(cx);
+       return 0;
+ free_streams:
+       cx18_streams_cleanup(cx, 1);
+ free_irq:
+       free_irq(cx->pci_dev->irq, (void *)cx);
+ free_i2c:
+       exit_cx18_i2c(cx);
+ free_map:
+       cx18_iounmap(cx);
+ free_mem:
+       release_mem_region(cx->base_addr, CX18_MEM_SIZE);
+ free_workqueues:
+       destroy_workqueue(cx->in_work_queue);
+ err:
+       if (retval == 0)
+               retval = -ENODEV;
+       CX18_ERR("Error %d on initialization\n", retval);
+       v4l2_device_unregister(&cx->v4l2_dev);
+       kfree(cx);
+       return retval;
+ }
+ int cx18_init_on_first_open(struct cx18 *cx)
+ {
+       int video_input;
+       int fw_retry_count = 3;
+       struct v4l2_frequency vf;
+       struct cx18_open_id fh;
+       v4l2_std_id std;
+       fh.cx = cx;
+       if (test_bit(CX18_F_I_FAILED, &cx->i_flags))
+               return -ENXIO;
+       if (test_and_set_bit(CX18_F_I_INITED, &cx->i_flags))
+               return 0;
+       while (--fw_retry_count > 0) {
+               /* load firmware */
+               if (cx18_firmware_init(cx) == 0)
+                       break;
+               if (fw_retry_count > 1)
+                       CX18_WARN("Retry loading firmware\n");
+       }
+       if (fw_retry_count == 0) {
+               set_bit(CX18_F_I_FAILED, &cx->i_flags);
+               return -ENXIO;
+       }
+       set_bit(CX18_F_I_LOADED_FW, &cx->i_flags);
+       /*
+        * Init the firmware twice to work around a silicon bug
+        * with the digital TS.
+        *
+        * The second firmware load requires us to normalize the APU state,
+        * or the audio for the first analog capture will be badly incorrect.
+        *
+        * I can't seem to call APU_RESETAI and have it succeed without the
+        * APU capturing audio, so we start and stop it here to do the reset
+        */
+       /* MPEG Encoding, 224 kbps, MPEG Layer II, 48 ksps */
+       cx18_vapi(cx, CX18_APU_START, 2, CX18_APU_ENCODING_METHOD_MPEG|0xb9, 0);
+       cx18_vapi(cx, CX18_APU_RESETAI, 0);
+       cx18_vapi(cx, CX18_APU_STOP, 1, CX18_APU_ENCODING_METHOD_MPEG);
+       fw_retry_count = 3;
+       while (--fw_retry_count > 0) {
+               /* load firmware */
+               if (cx18_firmware_init(cx) == 0)
+                       break;
+               if (fw_retry_count > 1)
+                       CX18_WARN("Retry loading firmware\n");
+       }
+       if (fw_retry_count == 0) {
+               set_bit(CX18_F_I_FAILED, &cx->i_flags);
+               return -ENXIO;
+       }
+       /*
+        * The second firmware load requires us to normalize the APU state,
+        * or the audio for the first analog capture will be badly incorrect.
+        *
+        * I can't seem to call APU_RESETAI and have it succeed without the
+        * APU capturing audio, so we start and stop it here to do the reset
+        */
+       /* MPEG Encoding, 224 kbps, MPEG Layer II, 48 ksps */
+       cx18_vapi(cx, CX18_APU_START, 2, CX18_APU_ENCODING_METHOD_MPEG|0xb9, 0);
+       cx18_vapi(cx, CX18_APU_RESETAI, 0);
+       cx18_vapi(cx, CX18_APU_STOP, 1, CX18_APU_ENCODING_METHOD_MPEG);
+       /* Init the A/V decoder, if it hasn't been already */
+       v4l2_subdev_call(cx->sd_av, core, load_fw);
+       vf.tuner = 0;
+       vf.type = V4L2_TUNER_ANALOG_TV;
+       vf.frequency = 6400; /* the tuner 'baseline' frequency */
+       /* Set initial frequency. For PAL/SECAM broadcasts no
+          'default' channel exists AFAIK. */
+       if (cx->std == V4L2_STD_NTSC_M_JP)
+               vf.frequency = 1460;    /* ch. 1 91250*16/1000 */
+       else if (cx->std & V4L2_STD_NTSC_M)
+               vf.frequency = 1076;    /* ch. 4 67250*16/1000 */
+       video_input = cx->active_input;
+       cx->active_input++;     /* Force update of input */
+       cx18_s_input(NULL, &fh, video_input);
+       /* Let the VIDIOC_S_STD ioctl do all the work, keeps the code
+          in one place. */
+       cx->std++;              /* Force full standard initialization */
+       std = (cx->tuner_std == V4L2_STD_ALL) ? V4L2_STD_NTSC_M : cx->tuner_std;
+       cx18_s_std(NULL, &fh, &std);
+       cx18_s_frequency(NULL, &fh, &vf);
+       return 0;
+ }
+ static void cx18_cancel_in_work_orders(struct cx18 *cx)
+ {
+       int i;
+       for (i = 0; i < CX18_MAX_IN_WORK_ORDERS; i++)
+               cancel_work_sync(&cx->in_work_order[i].work);
+ }
+ static void cx18_cancel_out_work_orders(struct cx18 *cx)
+ {
+       int i;
+       for (i = 0; i < CX18_MAX_STREAMS; i++)
+               if (&cx->streams[i].video_dev != NULL)
+                       cancel_work_sync(&cx->streams[i].out_work_order);
+ }
+ static void cx18_remove(struct pci_dev *pci_dev)
+ {
+       struct v4l2_device *v4l2_dev = pci_get_drvdata(pci_dev);
+       struct cx18 *cx = to_cx18(v4l2_dev);
+       int i;
+       CX18_DEBUG_INFO("Removing Card\n");
+       flush_request_modules(cx);
+       /* Stop all captures */
+       CX18_DEBUG_INFO("Stopping all streams\n");
+       if (atomic_read(&cx->tot_capturing) > 0)
+               cx18_stop_all_captures(cx);
+       /* Stop interrupts that cause incoming work to be queued */
+       cx18_sw1_irq_disable(cx, IRQ_CPU_TO_EPU | IRQ_APU_TO_EPU);
+       /* Incoming work can cause outgoing work, so clean up incoming first */
+       cx18_cancel_in_work_orders(cx);
+       cx18_cancel_out_work_orders(cx);
+       /* Stop ack interrupts that may have been needed for work to finish */
+       cx18_sw2_irq_disable(cx, IRQ_CPU_TO_EPU_ACK | IRQ_APU_TO_EPU_ACK);
+       cx18_halt_firmware(cx);
+       destroy_workqueue(cx->in_work_queue);
+       cx18_streams_cleanup(cx, 1);
+       exit_cx18_i2c(cx);
+       free_irq(cx->pci_dev->irq, (void *)cx);
+       cx18_iounmap(cx);
+       release_mem_region(cx->base_addr, CX18_MEM_SIZE);
+       pci_disable_device(cx->pci_dev);
+       if (cx->vbi.sliced_mpeg_data[0] != NULL)
+               for (i = 0; i < CX18_VBI_FRAMES; i++)
+                       kfree(cx->vbi.sliced_mpeg_data[i]);
+       v4l2_ctrl_handler_free(&cx->av_state.hdl);
+       CX18_INFO("Removed %s\n", cx->card_name);
+       v4l2_device_unregister(v4l2_dev);
+       kfree(cx);
+ }
+ /* define a pci_driver for card detection */
+ static struct pci_driver cx18_pci_driver = {
+       .name =     "cx18",
+       .id_table = cx18_pci_tbl,
+       .probe =    cx18_probe,
+       .remove =   cx18_remove,
+ };
+ static int __init module_start(void)
+ {
+       printk(KERN_INFO "cx18:  Start initialization, version %s\n",
+              CX18_VERSION);
+       /* Validate parameters */
+       if (cx18_first_minor < 0 || cx18_first_minor >= CX18_MAX_CARDS) {
+               printk(KERN_ERR "cx18:  Exiting, cx18_first_minor must be between 0 and %d\n",
+                    CX18_MAX_CARDS - 1);
+               return -1;
+       }
+       if (cx18_debug < 0 || cx18_debug > 511) {
+               cx18_debug = 0;
+               printk(KERN_INFO "cx18:   Debug value must be >= 0 and <= 511!\n");
+       }
+       if (pci_register_driver(&cx18_pci_driver)) {
+               printk(KERN_ERR "cx18:   Error detecting PCI card\n");
+               return -ENODEV;
+       }
+       printk(KERN_INFO "cx18:  End initialization\n");
+       return 0;
+ }
+ static void __exit module_cleanup(void)
+ {
+       pci_unregister_driver(&cx18_pci_driver);
+ }
+ module_init(module_start);
+ module_exit(module_cleanup);
+ MODULE_FIRMWARE(XC2028_DEFAULT_FIRMWARE);
index 0000000000000000000000000000000000000000,56066721edc17423726188858aa66e4ea017ff0b..2c925f77cf2aa4baeb8288efba0b660448747cb8
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,365 +1,365 @@@
 -      flush_work_sync(&dev->cx25840_work);
 -      flush_work_sync(&dev->ir_rx_work);
 -      flush_work_sync(&dev->ir_tx_work);
+ /*
+  *  Driver for the Conexant CX23885/7/8 PCIe bridge
+  *
+  *  Infrared remote control input device
+  *
+  *  Most of this file is
+  *
+  *  Copyright (C) 2009  Andy Walls <awalls@md.metrocast.net>
+  *
+  *  However, the cx23885_input_{init,fini} functions contained herein are
+  *  derived from Linux kernel files linux/media/video/.../...-input.c marked as:
+  *
+  *  Copyright (C) 2008 <srinivasa.deevi at conexant dot com>
+  *  Copyright (C) 2005 Ludovico Cavedon <cavedon@sssup.it>
+  *                   Markus Rechberger <mrechberger@gmail.com>
+  *                   Mauro Carvalho Chehab <mchehab@infradead.org>
+  *                   Sascha Sommer <saschasommer@freenet.de>
+  *  Copyright (C) 2004, 2005 Chris Pascoe
+  *  Copyright (C) 2003, 2004 Gerd Knorr
+  *  Copyright (C) 2003 Pavel Machek
+  *
+  *  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.
+  */
+ #include <linux/slab.h>
+ #include <media/rc-core.h>
+ #include <media/v4l2-subdev.h>
+ #include "cx23885.h"
+ #define MODULE_NAME "cx23885"
+ static void cx23885_input_process_measurements(struct cx23885_dev *dev,
+                                              bool overrun)
+ {
+       struct cx23885_kernel_ir *kernel_ir = dev->kernel_ir;
+       ssize_t num;
+       int count, i;
+       bool handle = false;
+       struct ir_raw_event ir_core_event[64];
+       do {
+               num = 0;
+               v4l2_subdev_call(dev->sd_ir, ir, rx_read, (u8 *) ir_core_event,
+                                sizeof(ir_core_event), &num);
+               count = num / sizeof(struct ir_raw_event);
+               for (i = 0; i < count; i++) {
+                       ir_raw_event_store(kernel_ir->rc,
+                                          &ir_core_event[i]);
+                       handle = true;
+               }
+       } while (num != 0);
+       if (overrun)
+               ir_raw_event_reset(kernel_ir->rc);
+       else if (handle)
+               ir_raw_event_handle(kernel_ir->rc);
+ }
+ void cx23885_input_rx_work_handler(struct cx23885_dev *dev, u32 events)
+ {
+       struct v4l2_subdev_ir_parameters params;
+       int overrun, data_available;
+       if (dev->sd_ir == NULL || events == 0)
+               return;
+       switch (dev->board) {
+       case CX23885_BOARD_HAUPPAUGE_HVR1270:
+       case CX23885_BOARD_HAUPPAUGE_HVR1850:
+       case CX23885_BOARD_HAUPPAUGE_HVR1290:
+       case CX23885_BOARD_TERRATEC_CINERGY_T_PCIE_DUAL:
+       case CX23885_BOARD_TEVII_S470:
+       case CX23885_BOARD_HAUPPAUGE_HVR1250:
+               /*
+                * The only boards we handle right now.  However other boards
+                * using the CX2388x integrated IR controller should be similar
+                */
+               break;
+       default:
+               return;
+       }
+       overrun = events & (V4L2_SUBDEV_IR_RX_SW_FIFO_OVERRUN |
+                           V4L2_SUBDEV_IR_RX_HW_FIFO_OVERRUN);
+       data_available = events & (V4L2_SUBDEV_IR_RX_END_OF_RX_DETECTED |
+                                  V4L2_SUBDEV_IR_RX_FIFO_SERVICE_REQ);
+       if (overrun) {
+               /* If there was a FIFO overrun, stop the device */
+               v4l2_subdev_call(dev->sd_ir, ir, rx_g_parameters, &params);
+               params.enable = false;
+               /* Mitigate race with cx23885_input_ir_stop() */
+               params.shutdown = atomic_read(&dev->ir_input_stopping);
+               v4l2_subdev_call(dev->sd_ir, ir, rx_s_parameters, &params);
+       }
+       if (data_available)
+               cx23885_input_process_measurements(dev, overrun);
+       if (overrun) {
+               /* If there was a FIFO overrun, clear & restart the device */
+               params.enable = true;
+               /* Mitigate race with cx23885_input_ir_stop() */
+               params.shutdown = atomic_read(&dev->ir_input_stopping);
+               v4l2_subdev_call(dev->sd_ir, ir, rx_s_parameters, &params);
+       }
+ }
+ static int cx23885_input_ir_start(struct cx23885_dev *dev)
+ {
+       struct v4l2_subdev_ir_parameters params;
+       if (dev->sd_ir == NULL)
+               return -ENODEV;
+       atomic_set(&dev->ir_input_stopping, 0);
+       v4l2_subdev_call(dev->sd_ir, ir, rx_g_parameters, &params);
+       switch (dev->board) {
+       case CX23885_BOARD_HAUPPAUGE_HVR1270:
+       case CX23885_BOARD_HAUPPAUGE_HVR1850:
+       case CX23885_BOARD_HAUPPAUGE_HVR1290:
+       case CX23885_BOARD_HAUPPAUGE_HVR1250:
+               /*
+                * The IR controller on this board only returns pulse widths.
+                * Any other mode setting will fail to set up the device.
+               */
+               params.mode = V4L2_SUBDEV_IR_MODE_PULSE_WIDTH;
+               params.enable = true;
+               params.interrupt_enable = true;
+               params.shutdown = false;
+               /* Setup for baseband compatible with both RC-5 and RC-6A */
+               params.modulation = false;
+               /* RC-5:  2,222,222 ns = 1/36 kHz * 32 cycles * 2 marks * 1.25*/
+               /* RC-6A: 3,333,333 ns = 1/36 kHz * 16 cycles * 6 marks * 1.25*/
+               params.max_pulse_width = 3333333; /* ns */
+               /* RC-5:    666,667 ns = 1/36 kHz * 32 cycles * 1 mark * 0.75 */
+               /* RC-6A:   333,333 ns = 1/36 kHz * 16 cycles * 1 mark * 0.75 */
+               params.noise_filter_min_width = 333333; /* ns */
+               /*
+                * This board has inverted receive sense:
+                * mark is received as low logic level;
+                * falling edges are detected as rising edges; etc.
+                */
+               params.invert_level = true;
+               break;
+       case CX23885_BOARD_TERRATEC_CINERGY_T_PCIE_DUAL:
+       case CX23885_BOARD_TEVII_S470:
+               /*
+                * The IR controller on this board only returns pulse widths.
+                * Any other mode setting will fail to set up the device.
+                */
+               params.mode = V4L2_SUBDEV_IR_MODE_PULSE_WIDTH;
+               params.enable = true;
+               params.interrupt_enable = true;
+               params.shutdown = false;
+               /* Setup for a standard NEC protocol */
+               params.carrier_freq = 37917; /* Hz, 455 kHz/12 for NEC */
+               params.carrier_range_lower = 33000; /* Hz */
+               params.carrier_range_upper = 43000; /* Hz */
+               params.duty_cycle = 33; /* percent, 33 percent for NEC */
+               /*
+                * NEC max pulse width: (64/3)/(455 kHz/12) * 16 nec_units
+                * (64/3)/(455 kHz/12) * 16 nec_units * 1.375 = 12378022 ns
+                */
+               params.max_pulse_width = 12378022; /* ns */
+               /*
+                * NEC noise filter min width: (64/3)/(455 kHz/12) * 1 nec_unit
+                * (64/3)/(455 kHz/12) * 1 nec_units * 0.625 = 351648 ns
+                */
+               params.noise_filter_min_width = 351648; /* ns */
+               params.modulation = false;
+               params.invert_level = true;
+               break;
+       }
+       v4l2_subdev_call(dev->sd_ir, ir, rx_s_parameters, &params);
+       return 0;
+ }
+ static int cx23885_input_ir_open(struct rc_dev *rc)
+ {
+       struct cx23885_kernel_ir *kernel_ir = rc->priv;
+       if (kernel_ir->cx == NULL)
+               return -ENODEV;
+       return cx23885_input_ir_start(kernel_ir->cx);
+ }
+ static void cx23885_input_ir_stop(struct cx23885_dev *dev)
+ {
+       struct v4l2_subdev_ir_parameters params;
+       if (dev->sd_ir == NULL)
+               return;
+       /*
+        * Stop the sd_ir subdevice from generating notifications and
+        * scheduling work.
+        * It is shutdown this way in order to mitigate a race with
+        * cx23885_input_rx_work_handler() in the overrun case, which could
+        * re-enable the subdevice.
+        */
+       atomic_set(&dev->ir_input_stopping, 1);
+       v4l2_subdev_call(dev->sd_ir, ir, rx_g_parameters, &params);
+       while (params.shutdown == false) {
+               params.enable = false;
+               params.interrupt_enable = false;
+               params.shutdown = true;
+               v4l2_subdev_call(dev->sd_ir, ir, rx_s_parameters, &params);
+               v4l2_subdev_call(dev->sd_ir, ir, rx_g_parameters, &params);
+       }
++      flush_work(&dev->cx25840_work);
++      flush_work(&dev->ir_rx_work);
++      flush_work(&dev->ir_tx_work);
+ }
+ static void cx23885_input_ir_close(struct rc_dev *rc)
+ {
+       struct cx23885_kernel_ir *kernel_ir = rc->priv;
+       if (kernel_ir->cx != NULL)
+               cx23885_input_ir_stop(kernel_ir->cx);
+ }
+ int cx23885_input_init(struct cx23885_dev *dev)
+ {
+       struct cx23885_kernel_ir *kernel_ir;
+       struct rc_dev *rc;
+       char *rc_map;
+       enum rc_driver_type driver_type;
+       unsigned long allowed_protos;
+       int ret;
+       /*
+        * If the IR device (hardware registers, chip, GPIO lines, etc.) isn't
+        * encapsulated in a v4l2_subdev, then I'm not going to deal with it.
+        */
+       if (dev->sd_ir == NULL)
+               return -ENODEV;
+       switch (dev->board) {
+       case CX23885_BOARD_HAUPPAUGE_HVR1270:
+       case CX23885_BOARD_HAUPPAUGE_HVR1850:
+       case CX23885_BOARD_HAUPPAUGE_HVR1290:
+       case CX23885_BOARD_HAUPPAUGE_HVR1250:
+               /* Integrated CX2388[58] IR controller */
+               driver_type = RC_DRIVER_IR_RAW;
+               allowed_protos = RC_TYPE_ALL;
+               /* The grey Hauppauge RC-5 remote */
+               rc_map = RC_MAP_HAUPPAUGE;
+               break;
+       case CX23885_BOARD_TERRATEC_CINERGY_T_PCIE_DUAL:
+               /* Integrated CX23885 IR controller */
+               driver_type = RC_DRIVER_IR_RAW;
+               allowed_protos = RC_TYPE_NEC;
+               /* The grey Terratec remote with orange buttons */
+               rc_map = RC_MAP_NEC_TERRATEC_CINERGY_XS;
+               break;
+       case CX23885_BOARD_TEVII_S470:
+               /* Integrated CX23885 IR controller */
+               driver_type = RC_DRIVER_IR_RAW;
+               allowed_protos = RC_TYPE_ALL;
+               /* A guess at the remote */
+               rc_map = RC_MAP_TEVII_NEC;
+               break;
+       default:
+               return -ENODEV;
+       }
+       /* cx23885 board instance kernel IR state */
+       kernel_ir = kzalloc(sizeof(struct cx23885_kernel_ir), GFP_KERNEL);
+       if (kernel_ir == NULL)
+               return -ENOMEM;
+       kernel_ir->cx = dev;
+       kernel_ir->name = kasprintf(GFP_KERNEL, "cx23885 IR (%s)",
+                                   cx23885_boards[dev->board].name);
+       kernel_ir->phys = kasprintf(GFP_KERNEL, "pci-%s/ir0",
+                                   pci_name(dev->pci));
+       /* input device */
+       rc = rc_allocate_device();
+       if (!rc) {
+               ret = -ENOMEM;
+               goto err_out_free;
+       }
+       kernel_ir->rc = rc;
+       rc->input_name = kernel_ir->name;
+       rc->input_phys = kernel_ir->phys;
+       rc->input_id.bustype = BUS_PCI;
+       rc->input_id.version = 1;
+       if (dev->pci->subsystem_vendor) {
+               rc->input_id.vendor  = dev->pci->subsystem_vendor;
+               rc->input_id.product = dev->pci->subsystem_device;
+       } else {
+               rc->input_id.vendor  = dev->pci->vendor;
+               rc->input_id.product = dev->pci->device;
+       }
+       rc->dev.parent = &dev->pci->dev;
+       rc->driver_type = driver_type;
+       rc->allowed_protos = allowed_protos;
+       rc->priv = kernel_ir;
+       rc->open = cx23885_input_ir_open;
+       rc->close = cx23885_input_ir_close;
+       rc->map_name = rc_map;
+       rc->driver_name = MODULE_NAME;
+       /* Go */
+       dev->kernel_ir = kernel_ir;
+       ret = rc_register_device(rc);
+       if (ret)
+               goto err_out_stop;
+       return 0;
+ err_out_stop:
+       cx23885_input_ir_stop(dev);
+       dev->kernel_ir = NULL;
+       rc_free_device(rc);
+ err_out_free:
+       kfree(kernel_ir->phys);
+       kfree(kernel_ir->name);
+       kfree(kernel_ir);
+       return ret;
+ }
+ void cx23885_input_fini(struct cx23885_dev *dev)
+ {
+       /* Always stop the IR hardware from generating interrupts */
+       cx23885_input_ir_stop(dev);
+       if (dev->kernel_ir == NULL)
+               return;
+       rc_unregister_device(dev->kernel_ir->rc);
+       kfree(dev->kernel_ir->phys);
+       kfree(dev->kernel_ir->name);
+       kfree(dev->kernel_ir);
+       dev->kernel_ir = NULL;
+ }
index 0000000000000000000000000000000000000000,cd5386ee210cf02f79fbfb4abaeb86895e88ab99..c04fb618e10b78a22209575c2506e4aae270e575
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,929 +1,929 @@@
 -      flush_work_sync(&dev->request_module_wk);
+ /*
+  *
+  *  Support for the mpeg transport stream transfers
+  *  PCI function #2 of the cx2388x.
+  *
+  *    (c) 2004 Jelle Foks <jelle@foks.us>
+  *    (c) 2004 Chris Pascoe <c.pascoe@itee.uq.edu.au>
+  *    (c) 2004 Gerd Knorr <kraxel@bytesex.org>
+  *
+  *  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/slab.h>
+ #include <linux/init.h>
+ #include <linux/device.h>
+ #include <linux/dma-mapping.h>
+ #include <linux/interrupt.h>
+ #include <asm/delay.h>
+ #include "cx88.h"
+ /* ------------------------------------------------------------------ */
+ MODULE_DESCRIPTION("mpeg driver for cx2388x based TV cards");
+ MODULE_AUTHOR("Jelle Foks <jelle@foks.us>");
+ MODULE_AUTHOR("Chris Pascoe <c.pascoe@itee.uq.edu.au>");
+ MODULE_AUTHOR("Gerd Knorr <kraxel@bytesex.org> [SuSE Labs]");
+ MODULE_LICENSE("GPL");
+ MODULE_VERSION(CX88_VERSION);
+ static unsigned int debug;
+ module_param(debug,int,0644);
+ MODULE_PARM_DESC(debug,"enable debug messages [mpeg]");
+ #define dprintk(level,fmt, arg...)    if (debug >= level) \
+       printk(KERN_DEBUG "%s/2-mpeg: " fmt, dev->core->name, ## arg)
+ #define mpeg_dbg(level,fmt, arg...)   if (debug >= level) \
+       printk(KERN_DEBUG "%s/2-mpeg: " fmt, core->name, ## arg)
+ #if defined(CONFIG_MODULES) && defined(MODULE)
+ static void request_module_async(struct work_struct *work)
+ {
+       struct cx8802_dev *dev=container_of(work, struct cx8802_dev, request_module_wk);
+       if (dev->core->board.mpeg & CX88_MPEG_DVB)
+               request_module("cx88-dvb");
+       if (dev->core->board.mpeg & CX88_MPEG_BLACKBIRD)
+               request_module("cx88-blackbird");
+ }
+ static void request_modules(struct cx8802_dev *dev)
+ {
+       INIT_WORK(&dev->request_module_wk, request_module_async);
+       schedule_work(&dev->request_module_wk);
+ }
+ static void flush_request_modules(struct cx8802_dev *dev)
+ {
++      flush_work(&dev->request_module_wk);
+ }
+ #else
+ #define request_modules(dev)
+ #define flush_request_modules(dev)
+ #endif /* CONFIG_MODULES */
+ static LIST_HEAD(cx8802_devlist);
+ static DEFINE_MUTEX(cx8802_mutex);
+ /* ------------------------------------------------------------------ */
+ static int cx8802_start_dma(struct cx8802_dev    *dev,
+                           struct cx88_dmaqueue *q,
+                           struct cx88_buffer   *buf)
+ {
+       struct cx88_core *core = dev->core;
+       dprintk(1, "cx8802_start_dma w: %d, h: %d, f: %d\n",
+               buf->vb.width, buf->vb.height, buf->vb.field);
+       /* setup fifo + format */
+       cx88_sram_channel_setup(core, &cx88_sram_channels[SRAM_CH28],
+                               dev->ts_packet_size, buf->risc.dma);
+       /* write TS length to chip */
+       cx_write(MO_TS_LNGTH, buf->vb.width);
+       /* FIXME: this needs a review.
+        * also: move to cx88-blackbird + cx88-dvb source files? */
+       dprintk( 1, "core->active_type_id = 0x%08x\n", core->active_type_id);
+       if ( (core->active_type_id == CX88_MPEG_DVB) &&
+               (core->board.mpeg & CX88_MPEG_DVB) ) {
+               dprintk( 1, "cx8802_start_dma doing .dvb\n");
+               /* negedge driven & software reset */
+               cx_write(TS_GEN_CNTRL, 0x0040 | dev->ts_gen_cntrl);
+               udelay(100);
+               cx_write(MO_PINMUX_IO, 0x00);
+               cx_write(TS_HW_SOP_CNTRL, 0x47<<16|188<<4|0x01);
+               switch (core->boardnr) {
+               case CX88_BOARD_DVICO_FUSIONHDTV_3_GOLD_Q:
+               case CX88_BOARD_DVICO_FUSIONHDTV_3_GOLD_T:
+               case CX88_BOARD_DVICO_FUSIONHDTV_5_GOLD:
+               case CX88_BOARD_PCHDTV_HD5500:
+                       cx_write(TS_SOP_STAT, 1<<13);
+                       break;
+               case CX88_BOARD_SAMSUNG_SMT_7020:
+                       cx_write(TS_SOP_STAT, 0x00);
+                       break;
+               case CX88_BOARD_HAUPPAUGE_NOVASPLUS_S1:
+               case CX88_BOARD_HAUPPAUGE_NOVASE2_S1:
+                       cx_write(MO_PINMUX_IO, 0x88); /* Enable MPEG parallel IO and video signal pins */
+                       udelay(100);
+                       break;
+               case CX88_BOARD_HAUPPAUGE_HVR1300:
+                       /* Enable MPEG parallel IO and video signal pins */
+                       cx_write(MO_PINMUX_IO, 0x88);
+                       cx_write(TS_SOP_STAT, 0);
+                       cx_write(TS_VALERR_CNTRL, 0);
+                       break;
+               case CX88_BOARD_PINNACLE_PCTV_HD_800i:
+                       /* Enable MPEG parallel IO and video signal pins */
+                       cx_write(MO_PINMUX_IO, 0x88);
+                       cx_write(TS_HW_SOP_CNTRL, (0x47 << 16) | (188 << 4));
+                       dev->ts_gen_cntrl = 5;
+                       cx_write(TS_SOP_STAT, 0);
+                       cx_write(TS_VALERR_CNTRL, 0);
+                       udelay(100);
+                       break;
+               default:
+                       cx_write(TS_SOP_STAT, 0x00);
+                       break;
+               }
+               cx_write(TS_GEN_CNTRL, dev->ts_gen_cntrl);
+               udelay(100);
+       } else if ( (core->active_type_id == CX88_MPEG_BLACKBIRD) &&
+               (core->board.mpeg & CX88_MPEG_BLACKBIRD) ) {
+               dprintk( 1, "cx8802_start_dma doing .blackbird\n");
+               cx_write(MO_PINMUX_IO, 0x88); /* enable MPEG parallel IO */
+               cx_write(TS_GEN_CNTRL, 0x46); /* punctured clock TS & posedge driven & software reset */
+               udelay(100);
+               cx_write(TS_HW_SOP_CNTRL, 0x408); /* mpeg start byte */
+               cx_write(TS_VALERR_CNTRL, 0x2000);
+               cx_write(TS_GEN_CNTRL, 0x06); /* punctured clock TS & posedge driven */
+               udelay(100);
+       } else {
+               printk( "%s() Failed. Unsupported value in .mpeg (0x%08x)\n", __func__,
+                       core->board.mpeg );
+               return -EINVAL;
+       }
+       /* reset counter */
+       cx_write(MO_TS_GPCNTRL, GP_COUNT_CONTROL_RESET);
+       q->count = 1;
+       /* enable irqs */
+       dprintk( 1, "setting the interrupt mask\n" );
+       cx_set(MO_PCI_INTMSK, core->pci_irqmask | PCI_INT_TSINT);
+       cx_set(MO_TS_INTMSK,  0x1f0011);
+       /* start dma */
+       cx_set(MO_DEV_CNTRL2, (1<<5));
+       cx_set(MO_TS_DMACNTRL, 0x11);
+       return 0;
+ }
+ static int cx8802_stop_dma(struct cx8802_dev *dev)
+ {
+       struct cx88_core *core = dev->core;
+       dprintk( 1, "cx8802_stop_dma\n" );
+       /* stop dma */
+       cx_clear(MO_TS_DMACNTRL, 0x11);
+       /* disable irqs */
+       cx_clear(MO_PCI_INTMSK, PCI_INT_TSINT);
+       cx_clear(MO_TS_INTMSK, 0x1f0011);
+       /* Reset the controller */
+       cx_write(TS_GEN_CNTRL, 0xcd);
+       return 0;
+ }
+ static int cx8802_restart_queue(struct cx8802_dev    *dev,
+                               struct cx88_dmaqueue *q)
+ {
+       struct cx88_buffer *buf;
+       dprintk( 1, "cx8802_restart_queue\n" );
+       if (list_empty(&q->active))
+       {
+               struct cx88_buffer *prev;
+               prev = NULL;
+               dprintk(1, "cx8802_restart_queue: queue is empty\n" );
+               for (;;) {
+                       if (list_empty(&q->queued))
+                               return 0;
+                       buf = list_entry(q->queued.next, struct cx88_buffer, vb.queue);
+                       if (NULL == prev) {
+                               list_del(&buf->vb.queue);
+                               list_add_tail(&buf->vb.queue,&q->active);
+                               cx8802_start_dma(dev, q, buf);
+                               buf->vb.state = VIDEOBUF_ACTIVE;
+                               buf->count    = q->count++;
+                               mod_timer(&q->timeout, jiffies+BUFFER_TIMEOUT);
+                               dprintk(1,"[%p/%d] restart_queue - first active\n",
+                                       buf,buf->vb.i);
+                       } else if (prev->vb.width  == buf->vb.width  &&
+                                  prev->vb.height == buf->vb.height &&
+                                  prev->fmt       == buf->fmt) {
+                               list_del(&buf->vb.queue);
+                               list_add_tail(&buf->vb.queue,&q->active);
+                               buf->vb.state = VIDEOBUF_ACTIVE;
+                               buf->count    = q->count++;
+                               prev->risc.jmp[1] = cpu_to_le32(buf->risc.dma);
+                               dprintk(1,"[%p/%d] restart_queue - move to active\n",
+                                       buf,buf->vb.i);
+                       } else {
+                               return 0;
+                       }
+                       prev = buf;
+               }
+               return 0;
+       }
+       buf = list_entry(q->active.next, struct cx88_buffer, vb.queue);
+       dprintk(2,"restart_queue [%p/%d]: restart dma\n",
+               buf, buf->vb.i);
+       cx8802_start_dma(dev, q, buf);
+       list_for_each_entry(buf, &q->active, vb.queue)
+               buf->count = q->count++;
+       mod_timer(&q->timeout, jiffies+BUFFER_TIMEOUT);
+       return 0;
+ }
+ /* ------------------------------------------------------------------ */
+ int cx8802_buf_prepare(struct videobuf_queue *q, struct cx8802_dev *dev,
+                       struct cx88_buffer *buf, enum v4l2_field field)
+ {
+       int size = dev->ts_packet_size * dev->ts_packet_count;
+       struct videobuf_dmabuf *dma=videobuf_to_dma(&buf->vb);
+       int rc;
+       dprintk(1, "%s: %p\n", __func__, buf);
+       if (0 != buf->vb.baddr  &&  buf->vb.bsize < size)
+               return -EINVAL;
+       if (VIDEOBUF_NEEDS_INIT == buf->vb.state) {
+               buf->vb.width  = dev->ts_packet_size;
+               buf->vb.height = dev->ts_packet_count;
+               buf->vb.size   = size;
+               buf->vb.field  = field /*V4L2_FIELD_TOP*/;
+               if (0 != (rc = videobuf_iolock(q,&buf->vb,NULL)))
+                       goto fail;
+               cx88_risc_databuffer(dev->pci, &buf->risc,
+                                    dma->sglist,
+                                    buf->vb.width, buf->vb.height, 0);
+       }
+       buf->vb.state = VIDEOBUF_PREPARED;
+       return 0;
+  fail:
+       cx88_free_buffer(q,buf);
+       return rc;
+ }
+ void cx8802_buf_queue(struct cx8802_dev *dev, struct cx88_buffer *buf)
+ {
+       struct cx88_buffer    *prev;
+       struct cx88_dmaqueue  *cx88q = &dev->mpegq;
+       dprintk( 1, "cx8802_buf_queue\n" );
+       /* add jump to stopper */
+       buf->risc.jmp[0] = cpu_to_le32(RISC_JUMP | RISC_IRQ1 | RISC_CNT_INC);
+       buf->risc.jmp[1] = cpu_to_le32(cx88q->stopper.dma);
+       if (list_empty(&cx88q->active)) {
+               dprintk( 1, "queue is empty - first active\n" );
+               list_add_tail(&buf->vb.queue,&cx88q->active);
+               cx8802_start_dma(dev, cx88q, buf);
+               buf->vb.state = VIDEOBUF_ACTIVE;
+               buf->count    = cx88q->count++;
+               mod_timer(&cx88q->timeout, jiffies+BUFFER_TIMEOUT);
+               dprintk(1,"[%p/%d] %s - first active\n",
+                       buf, buf->vb.i, __func__);
+       } else {
+               dprintk( 1, "queue is not empty - append to active\n" );
+               prev = list_entry(cx88q->active.prev, struct cx88_buffer, vb.queue);
+               list_add_tail(&buf->vb.queue,&cx88q->active);
+               buf->vb.state = VIDEOBUF_ACTIVE;
+               buf->count    = cx88q->count++;
+               prev->risc.jmp[1] = cpu_to_le32(buf->risc.dma);
+               dprintk( 1, "[%p/%d] %s - append to active\n",
+                       buf, buf->vb.i, __func__);
+       }
+ }
+ /* ----------------------------------------------------------- */
+ static void do_cancel_buffers(struct cx8802_dev *dev, const char *reason, int restart)
+ {
+       struct cx88_dmaqueue *q = &dev->mpegq;
+       struct cx88_buffer *buf;
+       unsigned long flags;
+       spin_lock_irqsave(&dev->slock,flags);
+       while (!list_empty(&q->active)) {
+               buf = list_entry(q->active.next, struct cx88_buffer, vb.queue);
+               list_del(&buf->vb.queue);
+               buf->vb.state = VIDEOBUF_ERROR;
+               wake_up(&buf->vb.done);
+               dprintk(1,"[%p/%d] %s - dma=0x%08lx\n",
+                       buf, buf->vb.i, reason, (unsigned long)buf->risc.dma);
+       }
+       if (restart)
+       {
+               dprintk(1, "restarting queue\n" );
+               cx8802_restart_queue(dev,q);
+       }
+       spin_unlock_irqrestore(&dev->slock,flags);
+ }
+ void cx8802_cancel_buffers(struct cx8802_dev *dev)
+ {
+       struct cx88_dmaqueue *q = &dev->mpegq;
+       dprintk( 1, "cx8802_cancel_buffers" );
+       del_timer_sync(&q->timeout);
+       cx8802_stop_dma(dev);
+       do_cancel_buffers(dev,"cancel",0);
+ }
+ static void cx8802_timeout(unsigned long data)
+ {
+       struct cx8802_dev *dev = (struct cx8802_dev*)data;
+       dprintk(1, "%s\n",__func__);
+       if (debug)
+               cx88_sram_channel_dump(dev->core, &cx88_sram_channels[SRAM_CH28]);
+       cx8802_stop_dma(dev);
+       do_cancel_buffers(dev,"timeout",1);
+ }
+ static const char * cx88_mpeg_irqs[32] = {
+       "ts_risci1", NULL, NULL, NULL,
+       "ts_risci2", NULL, NULL, NULL,
+       "ts_oflow",  NULL, NULL, NULL,
+       "ts_sync",   NULL, NULL, NULL,
+       "opc_err", "par_err", "rip_err", "pci_abort",
+       "ts_err?",
+ };
+ static void cx8802_mpeg_irq(struct cx8802_dev *dev)
+ {
+       struct cx88_core *core = dev->core;
+       u32 status, mask, count;
+       dprintk( 1, "cx8802_mpeg_irq\n" );
+       status = cx_read(MO_TS_INTSTAT);
+       mask   = cx_read(MO_TS_INTMSK);
+       if (0 == (status & mask))
+               return;
+       cx_write(MO_TS_INTSTAT, status);
+       if (debug || (status & mask & ~0xff))
+               cx88_print_irqbits(core->name, "irq mpeg ",
+                                  cx88_mpeg_irqs, ARRAY_SIZE(cx88_mpeg_irqs),
+                                  status, mask);
+       /* risc op code error */
+       if (status & (1 << 16)) {
+               printk(KERN_WARNING "%s: mpeg risc op code error\n",core->name);
+               cx_clear(MO_TS_DMACNTRL, 0x11);
+               cx88_sram_channel_dump(dev->core, &cx88_sram_channels[SRAM_CH28]);
+       }
+       /* risc1 y */
+       if (status & 0x01) {
+               dprintk( 1, "wake up\n" );
+               spin_lock(&dev->slock);
+               count = cx_read(MO_TS_GPCNT);
+               cx88_wakeup(dev->core, &dev->mpegq, count);
+               spin_unlock(&dev->slock);
+       }
+       /* risc2 y */
+       if (status & 0x10) {
+               spin_lock(&dev->slock);
+               cx8802_restart_queue(dev,&dev->mpegq);
+               spin_unlock(&dev->slock);
+       }
+       /* other general errors */
+       if (status & 0x1f0100) {
+               dprintk( 0, "general errors: 0x%08x\n", status & 0x1f0100 );
+               spin_lock(&dev->slock);
+               cx8802_stop_dma(dev);
+               cx8802_restart_queue(dev,&dev->mpegq);
+               spin_unlock(&dev->slock);
+       }
+ }
+ #define MAX_IRQ_LOOP 10
+ static irqreturn_t cx8802_irq(int irq, void *dev_id)
+ {
+       struct cx8802_dev *dev = dev_id;
+       struct cx88_core *core = dev->core;
+       u32 status;
+       int loop, handled = 0;
+       for (loop = 0; loop < MAX_IRQ_LOOP; loop++) {
+               status = cx_read(MO_PCI_INTSTAT) &
+                       (core->pci_irqmask | PCI_INT_TSINT);
+               if (0 == status)
+                       goto out;
+               dprintk( 1, "cx8802_irq\n" );
+               dprintk( 1, "    loop: %d/%d\n", loop, MAX_IRQ_LOOP );
+               dprintk( 1, "    status: %d\n", status );
+               handled = 1;
+               cx_write(MO_PCI_INTSTAT, status);
+               if (status & core->pci_irqmask)
+                       cx88_core_irq(core,status);
+               if (status & PCI_INT_TSINT)
+                       cx8802_mpeg_irq(dev);
+       };
+       if (MAX_IRQ_LOOP == loop) {
+               dprintk( 0, "clearing mask\n" );
+               printk(KERN_WARNING "%s/0: irq loop -- clearing mask\n",
+                      core->name);
+               cx_write(MO_PCI_INTMSK,0);
+       }
+  out:
+       return IRQ_RETVAL(handled);
+ }
+ static int cx8802_init_common(struct cx8802_dev *dev)
+ {
+       struct cx88_core *core = dev->core;
+       int err;
+       /* pci init */
+       if (pci_enable_device(dev->pci))
+               return -EIO;
+       pci_set_master(dev->pci);
+       if (!pci_dma_supported(dev->pci,DMA_BIT_MASK(32))) {
+               printk("%s/2: Oops: no 32bit PCI DMA ???\n",dev->core->name);
+               return -EIO;
+       }
+       dev->pci_rev = dev->pci->revision;
+       pci_read_config_byte(dev->pci, PCI_LATENCY_TIMER,  &dev->pci_lat);
+       printk(KERN_INFO "%s/2: found at %s, rev: %d, irq: %d, "
+              "latency: %d, mmio: 0x%llx\n", dev->core->name,
+              pci_name(dev->pci), dev->pci_rev, dev->pci->irq,
+              dev->pci_lat,(unsigned long long)pci_resource_start(dev->pci,0));
+       /* initialize driver struct */
+       spin_lock_init(&dev->slock);
+       /* init dma queue */
+       INIT_LIST_HEAD(&dev->mpegq.active);
+       INIT_LIST_HEAD(&dev->mpegq.queued);
+       dev->mpegq.timeout.function = cx8802_timeout;
+       dev->mpegq.timeout.data     = (unsigned long)dev;
+       init_timer(&dev->mpegq.timeout);
+       cx88_risc_stopper(dev->pci,&dev->mpegq.stopper,
+                         MO_TS_DMACNTRL,0x11,0x00);
+       /* get irq */
+       err = request_irq(dev->pci->irq, cx8802_irq,
+                         IRQF_SHARED | IRQF_DISABLED, dev->core->name, dev);
+       if (err < 0) {
+               printk(KERN_ERR "%s: can't get IRQ %d\n",
+                      dev->core->name, dev->pci->irq);
+               return err;
+       }
+       cx_set(MO_PCI_INTMSK, core->pci_irqmask);
+       /* everything worked */
+       pci_set_drvdata(dev->pci,dev);
+       return 0;
+ }
+ static void cx8802_fini_common(struct cx8802_dev *dev)
+ {
+       dprintk( 2, "cx8802_fini_common\n" );
+       cx8802_stop_dma(dev);
+       pci_disable_device(dev->pci);
+       /* unregister stuff */
+       free_irq(dev->pci->irq, dev);
+       pci_set_drvdata(dev->pci, NULL);
+       /* free memory */
+       btcx_riscmem_free(dev->pci,&dev->mpegq.stopper);
+ }
+ /* ----------------------------------------------------------- */
+ static int cx8802_suspend_common(struct pci_dev *pci_dev, pm_message_t state)
+ {
+       struct cx8802_dev *dev = pci_get_drvdata(pci_dev);
+       struct cx88_core *core = dev->core;
+       /* stop mpeg dma */
+       spin_lock(&dev->slock);
+       if (!list_empty(&dev->mpegq.active)) {
+               dprintk( 2, "suspend\n" );
+               printk("%s: suspend mpeg\n", core->name);
+               cx8802_stop_dma(dev);
+               del_timer(&dev->mpegq.timeout);
+       }
+       spin_unlock(&dev->slock);
+       /* FIXME -- shutdown device */
+       cx88_shutdown(dev->core);
+       pci_save_state(pci_dev);
+       if (0 != pci_set_power_state(pci_dev, pci_choose_state(pci_dev, state))) {
+               pci_disable_device(pci_dev);
+               dev->state.disabled = 1;
+       }
+       return 0;
+ }
+ static int cx8802_resume_common(struct pci_dev *pci_dev)
+ {
+       struct cx8802_dev *dev = pci_get_drvdata(pci_dev);
+       struct cx88_core *core = dev->core;
+       int err;
+       if (dev->state.disabled) {
+               err=pci_enable_device(pci_dev);
+               if (err) {
+                       printk(KERN_ERR "%s: can't enable device\n",
+                                              dev->core->name);
+                       return err;
+               }
+               dev->state.disabled = 0;
+       }
+       err=pci_set_power_state(pci_dev, PCI_D0);
+       if (err) {
+               printk(KERN_ERR "%s: can't enable device\n",
+                                              dev->core->name);
+               pci_disable_device(pci_dev);
+               dev->state.disabled = 1;
+               return err;
+       }
+       pci_restore_state(pci_dev);
+       /* FIXME: re-initialize hardware */
+       cx88_reset(dev->core);
+       /* restart video+vbi capture */
+       spin_lock(&dev->slock);
+       if (!list_empty(&dev->mpegq.active)) {
+               printk("%s: resume mpeg\n", core->name);
+               cx8802_restart_queue(dev,&dev->mpegq);
+       }
+       spin_unlock(&dev->slock);
+       return 0;
+ }
+ struct cx8802_driver * cx8802_get_driver(struct cx8802_dev *dev, enum cx88_board_type btype)
+ {
+       struct cx8802_driver *d;
+       list_for_each_entry(d, &dev->drvlist, drvlist)
+               if (d->type_id == btype)
+                       return d;
+       return NULL;
+ }
+ /* Driver asked for hardware access. */
+ static int cx8802_request_acquire(struct cx8802_driver *drv)
+ {
+       struct cx88_core *core = drv->core;
+       unsigned int    i;
+       /* Fail a request for hardware if the device is busy. */
+       if (core->active_type_id != CX88_BOARD_NONE &&
+           core->active_type_id != drv->type_id)
+               return -EBUSY;
+       if (drv->type_id == CX88_MPEG_DVB) {
+               /* When switching to DVB, always set the input to the tuner */
+               core->last_analog_input = core->input;
+               core->input = 0;
+               for (i = 0;
+                    i < (sizeof(core->board.input) / sizeof(struct cx88_input));
+                    i++) {
+                       if (core->board.input[i].type == CX88_VMUX_DVB) {
+                               core->input = i;
+                               break;
+                       }
+               }
+       }
+       if (drv->advise_acquire)
+       {
+               core->active_ref++;
+               if (core->active_type_id == CX88_BOARD_NONE) {
+                       core->active_type_id = drv->type_id;
+                       drv->advise_acquire(drv);
+               }
+               mpeg_dbg(1,"%s() Post acquire GPIO=%x\n", __func__, cx_read(MO_GP0_IO));
+       }
+       return 0;
+ }
+ /* Driver asked to release hardware. */
+ static int cx8802_request_release(struct cx8802_driver *drv)
+ {
+       struct cx88_core *core = drv->core;
+       if (drv->advise_release && --core->active_ref == 0)
+       {
+               if (drv->type_id == CX88_MPEG_DVB) {
+                       /* If the DVB driver is releasing, reset the input
+                          state to the last configured analog input */
+                       core->input = core->last_analog_input;
+               }
+               drv->advise_release(drv);
+               core->active_type_id = CX88_BOARD_NONE;
+               mpeg_dbg(1,"%s() Post release GPIO=%x\n", __func__, cx_read(MO_GP0_IO));
+       }
+       return 0;
+ }
+ static int cx8802_check_driver(struct cx8802_driver *drv)
+ {
+       if (drv == NULL)
+               return -ENODEV;
+       if ((drv->type_id != CX88_MPEG_DVB) &&
+               (drv->type_id != CX88_MPEG_BLACKBIRD))
+               return -EINVAL;
+       if ((drv->hw_access != CX8802_DRVCTL_SHARED) &&
+               (drv->hw_access != CX8802_DRVCTL_EXCLUSIVE))
+               return -EINVAL;
+       if ((drv->probe == NULL) ||
+               (drv->remove == NULL) ||
+               (drv->advise_acquire == NULL) ||
+               (drv->advise_release == NULL))
+               return -EINVAL;
+       return 0;
+ }
+ int cx8802_register_driver(struct cx8802_driver *drv)
+ {
+       struct cx8802_dev *dev;
+       struct cx8802_driver *driver;
+       int err, i = 0;
+       printk(KERN_INFO
+              "cx88/2: registering cx8802 driver, type: %s access: %s\n",
+              drv->type_id == CX88_MPEG_DVB ? "dvb" : "blackbird",
+              drv->hw_access == CX8802_DRVCTL_SHARED ? "shared" : "exclusive");
+       if ((err = cx8802_check_driver(drv)) != 0) {
+               printk(KERN_ERR "cx88/2: cx8802_driver is invalid\n");
+               return err;
+       }
+       mutex_lock(&cx8802_mutex);
+       list_for_each_entry(dev, &cx8802_devlist, devlist) {
+               printk(KERN_INFO
+                      "%s/2: subsystem: %04x:%04x, board: %s [card=%d]\n",
+                      dev->core->name, dev->pci->subsystem_vendor,
+                      dev->pci->subsystem_device, dev->core->board.name,
+                      dev->core->boardnr);
+               /* Bring up a new struct for each driver instance */
+               driver = kzalloc(sizeof(*drv),GFP_KERNEL);
+               if (driver == NULL) {
+                       err = -ENOMEM;
+                       goto out;
+               }
+               /* Snapshot of the driver registration data */
+               drv->core = dev->core;
+               drv->suspend = cx8802_suspend_common;
+               drv->resume = cx8802_resume_common;
+               drv->request_acquire = cx8802_request_acquire;
+               drv->request_release = cx8802_request_release;
+               memcpy(driver, drv, sizeof(*driver));
+               mutex_lock(&drv->core->lock);
+               err = drv->probe(driver);
+               if (err == 0) {
+                       i++;
+                       list_add_tail(&driver->drvlist, &dev->drvlist);
+               } else {
+                       printk(KERN_ERR
+                              "%s/2: cx8802 probe failed, err = %d\n",
+                              dev->core->name, err);
+               }
+               mutex_unlock(&drv->core->lock);
+       }
+       err = i ? 0 : -ENODEV;
+ out:
+       mutex_unlock(&cx8802_mutex);
+       return err;
+ }
+ int cx8802_unregister_driver(struct cx8802_driver *drv)
+ {
+       struct cx8802_dev *dev;
+       struct cx8802_driver *d, *dtmp;
+       int err = 0;
+       printk(KERN_INFO
+              "cx88/2: unregistering cx8802 driver, type: %s access: %s\n",
+              drv->type_id == CX88_MPEG_DVB ? "dvb" : "blackbird",
+              drv->hw_access == CX8802_DRVCTL_SHARED ? "shared" : "exclusive");
+       mutex_lock(&cx8802_mutex);
+       list_for_each_entry(dev, &cx8802_devlist, devlist) {
+               printk(KERN_INFO
+                      "%s/2: subsystem: %04x:%04x, board: %s [card=%d]\n",
+                      dev->core->name, dev->pci->subsystem_vendor,
+                      dev->pci->subsystem_device, dev->core->board.name,
+                      dev->core->boardnr);
+               mutex_lock(&dev->core->lock);
+               list_for_each_entry_safe(d, dtmp, &dev->drvlist, drvlist) {
+                       /* only unregister the correct driver type */
+                       if (d->type_id != drv->type_id)
+                               continue;
+                       err = d->remove(d);
+                       if (err == 0) {
+                               list_del(&d->drvlist);
+                               kfree(d);
+                       } else
+                               printk(KERN_ERR "%s/2: cx8802 driver remove "
+                                      "failed (%d)\n", dev->core->name, err);
+               }
+               mutex_unlock(&dev->core->lock);
+       }
+       mutex_unlock(&cx8802_mutex);
+       return err;
+ }
+ /* ----------------------------------------------------------- */
+ static int __devinit cx8802_probe(struct pci_dev *pci_dev,
+                              const struct pci_device_id *pci_id)
+ {
+       struct cx8802_dev *dev;
+       struct cx88_core  *core;
+       int err;
+       /* general setup */
+       core = cx88_core_get(pci_dev);
+       if (NULL == core)
+               return -EINVAL;
+       printk("%s/2: cx2388x 8802 Driver Manager\n", core->name);
+       err = -ENODEV;
+       if (!core->board.mpeg)
+               goto fail_core;
+       err = -ENOMEM;
+       dev = kzalloc(sizeof(*dev),GFP_KERNEL);
+       if (NULL == dev)
+               goto fail_core;
+       dev->pci = pci_dev;
+       dev->core = core;
+       /* Maintain a reference so cx88-video can query the 8802 device. */
+       core->dvbdev = dev;
+       err = cx8802_init_common(dev);
+       if (err != 0)
+               goto fail_free;
+       INIT_LIST_HEAD(&dev->drvlist);
+       mutex_lock(&cx8802_mutex);
+       list_add_tail(&dev->devlist,&cx8802_devlist);
+       mutex_unlock(&cx8802_mutex);
+       /* now autoload cx88-dvb or cx88-blackbird */
+       request_modules(dev);
+       return 0;
+  fail_free:
+       kfree(dev);
+  fail_core:
+       core->dvbdev = NULL;
+       cx88_core_put(core,pci_dev);
+       return err;
+ }
+ static void __devexit cx8802_remove(struct pci_dev *pci_dev)
+ {
+       struct cx8802_dev *dev;
+       dev = pci_get_drvdata(pci_dev);
+       dprintk( 1, "%s\n", __func__);
+       flush_request_modules(dev);
+       mutex_lock(&dev->core->lock);
+       if (!list_empty(&dev->drvlist)) {
+               struct cx8802_driver *drv, *tmp;
+               int err;
+               printk(KERN_WARNING "%s/2: Trying to remove cx8802 driver "
+                      "while cx8802 sub-drivers still loaded?!\n",
+                      dev->core->name);
+               list_for_each_entry_safe(drv, tmp, &dev->drvlist, drvlist) {
+                       err = drv->remove(drv);
+                       if (err == 0) {
+                               list_del(&drv->drvlist);
+                       } else
+                               printk(KERN_ERR "%s/2: cx8802 driver remove "
+                                      "failed (%d)\n", dev->core->name, err);
+                       kfree(drv);
+               }
+       }
+       mutex_unlock(&dev->core->lock);
+       /* Destroy any 8802 reference. */
+       dev->core->dvbdev = NULL;
+       /* common */
+       cx8802_fini_common(dev);
+       cx88_core_put(dev->core,dev->pci);
+       kfree(dev);
+ }
+ static const struct pci_device_id cx8802_pci_tbl[] = {
+       {
+               .vendor       = 0x14f1,
+               .device       = 0x8802,
+               .subvendor    = PCI_ANY_ID,
+               .subdevice    = PCI_ANY_ID,
+       },{
+               /* --- end of list --- */
+       }
+ };
+ MODULE_DEVICE_TABLE(pci, cx8802_pci_tbl);
+ static struct pci_driver cx8802_pci_driver = {
+       .name     = "cx88-mpeg driver manager",
+       .id_table = cx8802_pci_tbl,
+       .probe    = cx8802_probe,
+       .remove   = __devexit_p(cx8802_remove),
+ };
+ static int __init cx8802_init(void)
+ {
+       printk(KERN_INFO "cx88/2: cx2388x MPEG-TS Driver Manager version %s loaded\n",
+              CX88_VERSION);
+       return pci_register_driver(&cx8802_pci_driver);
+ }
+ static void __exit cx8802_fini(void)
+ {
+       pci_unregister_driver(&cx8802_pci_driver);
+ }
+ module_init(cx8802_init);
+ module_exit(cx8802_fini);
+ EXPORT_SYMBOL(cx8802_buf_prepare);
+ EXPORT_SYMBOL(cx8802_buf_queue);
+ EXPORT_SYMBOL(cx8802_cancel_buffers);
+ EXPORT_SYMBOL(cx8802_register_driver);
+ EXPORT_SYMBOL(cx8802_unregister_driver);
+ EXPORT_SYMBOL(cx8802_get_driver);
+ /* ----------------------------------------------------------- */
+ /*
+  * Local variables:
+  * c-basic-offset: 8
+  * End:
+  * kate: eol "unix"; indent-width 3; remove-trailing-space on; replace-trailing-space-save on; tab-width 8; replace-tabs off; space-indent off; mixed-indent off
+  */
index 0000000000000000000000000000000000000000,71ce52875c38ec1fdf5b3d822fc9de159e3a5bb7..909ff54868a3b183623b250849c166c70fdcbcf2
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,117 +1,117 @@@
 -      flush_work_sync(&ca->hif_evm_work);
+ /*
+       Mantis PCI bridge driver
+       Copyright (C) Manu Abraham (abraham.manu@gmail.com)
+       This program is free software; you can redistribute it and/or modify
+       it under the terms of the GNU General Public License as published by
+       the Free Software Foundation; either version 2 of the License, or
+       (at your option) any later version.
+       This program is distributed in the hope that it will be useful,
+       but WITHOUT ANY WARRANTY; without even the implied warranty of
+       MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+       GNU General Public License for more details.
+       You should have received a copy of the GNU General Public License
+       along with this program; if not, write to the Free Software
+       Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+ #include <linux/kernel.h>
+ #include <linux/signal.h>
+ #include <linux/sched.h>
+ #include <linux/interrupt.h>
+ #include <asm/io.h>
+ #include "dmxdev.h"
+ #include "dvbdev.h"
+ #include "dvb_demux.h"
+ #include "dvb_frontend.h"
+ #include "dvb_net.h"
+ #include "mantis_common.h"
+ #include "mantis_link.h"
+ #include "mantis_hif.h"
+ #include "mantis_reg.h"
+ static void mantis_hifevm_work(struct work_struct *work)
+ {
+       struct mantis_ca *ca = container_of(work, struct mantis_ca, hif_evm_work);
+       struct mantis_pci *mantis = ca->ca_priv;
+       u32 gpif_stat;
+       gpif_stat = mmread(MANTIS_GPIF_STATUS);
+       if (gpif_stat & MANTIS_GPIF_DETSTAT) {
+               if (gpif_stat & MANTIS_CARD_PLUGIN) {
+                       dprintk(MANTIS_DEBUG, 1, "Event Mgr: Adapter(%d) Slot(0): CAM Plugin", mantis->num);
+                       mmwrite(0xdada0000, MANTIS_CARD_RESET);
+                       mantis_event_cam_plugin(ca);
+                       dvb_ca_en50221_camchange_irq(&ca->en50221,
+                                                    0,
+                                                    DVB_CA_EN50221_CAMCHANGE_INSERTED);
+               }
+       } else {
+               if (gpif_stat & MANTIS_CARD_PLUGOUT) {
+                       dprintk(MANTIS_DEBUG, 1, "Event Mgr: Adapter(%d) Slot(0): CAM Unplug", mantis->num);
+                       mmwrite(0xdada0000, MANTIS_CARD_RESET);
+                       mantis_event_cam_unplug(ca);
+                       dvb_ca_en50221_camchange_irq(&ca->en50221,
+                                                    0,
+                                                    DVB_CA_EN50221_CAMCHANGE_REMOVED);
+               }
+       }
+       if (mantis->gpif_status & MANTIS_GPIF_EXTIRQ)
+               dprintk(MANTIS_DEBUG, 1, "Event Mgr: Adapter(%d) Slot(0): Ext IRQ", mantis->num);
+       if (mantis->gpif_status & MANTIS_SBUF_WSTO)
+               dprintk(MANTIS_DEBUG, 1, "Event Mgr: Adapter(%d) Slot(0): Smart Buffer Timeout", mantis->num);
+       if (mantis->gpif_status & MANTIS_GPIF_OTHERR)
+               dprintk(MANTIS_DEBUG, 1, "Event Mgr: Adapter(%d) Slot(0): Alignment Error", mantis->num);
+       if (gpif_stat & MANTIS_SBUF_OVFLW)
+               dprintk(MANTIS_DEBUG, 1, "Event Mgr: Adapter(%d) Slot(0): Smart Buffer Overflow", mantis->num);
+       if (gpif_stat & MANTIS_GPIF_BRRDY)
+               dprintk(MANTIS_DEBUG, 1, "Event Mgr: Adapter(%d) Slot(0): Smart Buffer Read Ready", mantis->num);
+       if (gpif_stat & MANTIS_GPIF_INTSTAT)
+               dprintk(MANTIS_DEBUG, 1, "Event Mgr: Adapter(%d) Slot(0): GPIF IRQ", mantis->num);
+       if (gpif_stat & MANTIS_SBUF_EMPTY)
+               dprintk(MANTIS_DEBUG, 1, "Event Mgr: Adapter(%d) Slot(0): Smart Buffer Empty", mantis->num);
+       if (gpif_stat & MANTIS_SBUF_OPDONE) {
+               dprintk(MANTIS_DEBUG, 1, "Event Mgr: Adapter(%d) Slot(0): Smart Buffer operation complete", mantis->num);
+               ca->sbuf_status = MANTIS_SBUF_DATA_AVAIL;
+               ca->hif_event = MANTIS_SBUF_OPDONE;
+               wake_up(&ca->hif_opdone_wq);
+       }
+ }
+ int mantis_evmgr_init(struct mantis_ca *ca)
+ {
+       struct mantis_pci *mantis = ca->ca_priv;
+       dprintk(MANTIS_DEBUG, 1, "Initializing Mantis Host I/F Event manager");
+       INIT_WORK(&ca->hif_evm_work, mantis_hifevm_work);
+       mantis_pcmcia_init(ca);
+       schedule_work(&ca->hif_evm_work);
+       mantis_hif_init(ca);
+       return 0;
+ }
+ void mantis_evmgr_exit(struct mantis_ca *ca)
+ {
+       struct mantis_pci *mantis = ca->ca_priv;
+       dprintk(MANTIS_DEBUG, 1, "Mantis Host I/F Event manager exiting");
++      flush_work(&ca->hif_evm_work);
+       mantis_hif_exit(ca);
+       mantis_pcmcia_exit(ca);
+ }
index 0000000000000000000000000000000000000000,18340dafa426f3264c78688a0b3979f2972f1cf4..85e977861b4a68a8bbb44d76c9b6a010ec250c3f
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,188 +1,188 @@@
 -      flush_work_sync(&mantis->uart_work);
+ /*
+       Mantis PCI bridge driver
+       Copyright (C) Manu Abraham (abraham.manu@gmail.com)
+       This program is free software; you can redistribute it and/or modify
+       it under the terms of the GNU General Public License as published by
+       the Free Software Foundation; either version 2 of the License, or
+       (at your option) any later version.
+       This program is distributed in the hope that it will be useful,
+       but WITHOUT ANY WARRANTY; without even the implied warranty of
+       MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+       GNU General Public License for more details.
+       You should have received a copy of the GNU General Public License
+       along with this program; if not, write to the Free Software
+       Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+ #include <linux/kernel.h>
+ #include <linux/spinlock.h>
+ #include <asm/io.h>
+ #include <linux/signal.h>
+ #include <linux/sched.h>
+ #include <linux/interrupt.h>
+ #include "dmxdev.h"
+ #include "dvbdev.h"
+ #include "dvb_demux.h"
+ #include "dvb_frontend.h"
+ #include "dvb_net.h"
+ #include "mantis_common.h"
+ #include "mantis_reg.h"
+ #include "mantis_uart.h"
+ struct mantis_uart_params {
+       enum mantis_baud        baud_rate;
+       enum mantis_parity      parity;
+ };
+ static struct {
+       char string[7];
+ } rates[5] = {
+       { "9600" },
+       { "19200" },
+       { "38400" },
+       { "57600" },
+       { "115200" }
+ };
+ static struct {
+       char string[5];
+ } parity[3] = {
+       { "NONE" },
+       { "ODD" },
+       { "EVEN" }
+ };
+ #define UART_MAX_BUF                  16
+ int mantis_uart_read(struct mantis_pci *mantis, u8 *data)
+ {
+       struct mantis_hwconfig *config = mantis->hwconfig;
+       u32 stat = 0, i;
+       /* get data */
+       for (i = 0; i < (config->bytes + 1); i++) {
+               stat = mmread(MANTIS_UART_STAT);
+               if (stat & MANTIS_UART_RXFIFO_FULL) {
+                       dprintk(MANTIS_ERROR, 1, "RX Fifo FULL");
+               }
+               data[i] = mmread(MANTIS_UART_RXD) & 0x3f;
+               dprintk(MANTIS_DEBUG, 1, "Reading ... <%02x>", data[i] & 0x3f);
+               if (data[i] & (1 << 7)) {
+                       dprintk(MANTIS_ERROR, 1, "UART framing error");
+                       return -EINVAL;
+               }
+               if (data[i] & (1 << 6)) {
+                       dprintk(MANTIS_ERROR, 1, "UART parity error");
+                       return -EINVAL;
+               }
+       }
+       return 0;
+ }
+ static void mantis_uart_work(struct work_struct *work)
+ {
+       struct mantis_pci *mantis = container_of(work, struct mantis_pci, uart_work);
+       struct mantis_hwconfig *config = mantis->hwconfig;
+       u8 buf[16];
+       int i;
+       mantis_uart_read(mantis, buf);
+       for (i = 0; i < (config->bytes + 1); i++)
+               dprintk(MANTIS_INFO, 1, "UART BUF:%d <%02x> ", i, buf[i]);
+       dprintk(MANTIS_DEBUG, 0, "\n");
+ }
+ static int mantis_uart_setup(struct mantis_pci *mantis,
+                            struct mantis_uart_params *params)
+ {
+       u32 reg;
+       mmwrite((mmread(MANTIS_UART_CTL) | (params->parity & 0x3)), MANTIS_UART_CTL);
+       reg = mmread(MANTIS_UART_BAUD);
+       switch (params->baud_rate) {
+       case MANTIS_BAUD_9600:
+               reg |= 0xd8;
+               break;
+       case MANTIS_BAUD_19200:
+               reg |= 0x6c;
+               break;
+       case MANTIS_BAUD_38400:
+               reg |= 0x36;
+               break;
+       case MANTIS_BAUD_57600:
+               reg |= 0x23;
+               break;
+       case MANTIS_BAUD_115200:
+               reg |= 0x11;
+               break;
+       default:
+               return -EINVAL;
+       }
+       mmwrite(reg, MANTIS_UART_BAUD);
+       return 0;
+ }
+ int mantis_uart_init(struct mantis_pci *mantis)
+ {
+       struct mantis_hwconfig *config = mantis->hwconfig;
+       struct mantis_uart_params params;
+       /* default parity: */
+       params.baud_rate = config->baud_rate;
+       params.parity = config->parity;
+       dprintk(MANTIS_INFO, 1, "Initializing UART @ %sbps parity:%s",
+               rates[params.baud_rate].string,
+               parity[params.parity].string);
+       init_waitqueue_head(&mantis->uart_wq);
+       spin_lock_init(&mantis->uart_lock);
+       INIT_WORK(&mantis->uart_work, mantis_uart_work);
+       /* disable interrupt */
+       mmwrite(mmread(MANTIS_UART_CTL) & 0xffef, MANTIS_UART_CTL);
+       mantis_uart_setup(mantis, &params);
+       /* default 1 byte */
+       mmwrite((mmread(MANTIS_UART_BAUD) | (config->bytes << 8)), MANTIS_UART_BAUD);
+       /* flush buffer */
+       mmwrite((mmread(MANTIS_UART_CTL) | MANTIS_UART_RXFLUSH), MANTIS_UART_CTL);
+       /* enable interrupt */
+       mmwrite(mmread(MANTIS_INT_MASK) | 0x800, MANTIS_INT_MASK);
+       mmwrite(mmread(MANTIS_UART_CTL) | MANTIS_UART_RXINT, MANTIS_UART_CTL);
+       schedule_work(&mantis->uart_work);
+       dprintk(MANTIS_DEBUG, 1, "UART successfully initialized");
+       return 0;
+ }
+ EXPORT_SYMBOL_GPL(mantis_uart_init);
+ void mantis_uart_exit(struct mantis_pci *mantis)
+ {
+       /* disable interrupt */
+       mmwrite(mmread(MANTIS_UART_CTL) & 0xffef, MANTIS_UART_CTL);
++      flush_work(&mantis->uart_work);
+ }
+ EXPORT_SYMBOL_GPL(mantis_uart_exit);
index 0000000000000000000000000000000000000000,a6cd6959ad192a6207d96add36c69d6691ed3291..96a13ed197d0856389a1bd9f7cbde4ca895dfbd8
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,823 +1,823 @@@
 -static struct pci_error_handlers ngene_errors = {
+ /*
+  * ngene-cards.c: nGene PCIe bridge driver - card specific info
+  *
+  * Copyright (C) 2005-2007 Micronas
+  *
+  * Copyright (C) 2008-2009 Ralph Metzler <rjkm@metzlerbros.de>
+  *                         Modifications for new nGene firmware,
+  *                         support for EEPROM-copying,
+  *                         support for new dual DVB-S2 card prototype
+  *
+  *
+  * 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/module.h>
+ #include <linux/init.h>
+ #include <linux/pci.h>
+ #include <linux/pci_ids.h>
+ #include "ngene.h"
+ /* demods/tuners */
+ #include "stv6110x.h"
+ #include "stv090x.h"
+ #include "lnbh24.h"
+ #include "lgdt330x.h"
+ #include "mt2131.h"
+ #include "tda18271c2dd.h"
+ #include "drxk.h"
+ #include "drxd.h"
+ #include "dvb-pll.h"
+ /****************************************************************************/
+ /* Demod/tuner attachment ***************************************************/
+ /****************************************************************************/
+ static int tuner_attach_stv6110(struct ngene_channel *chan)
+ {
+       struct i2c_adapter *i2c;
+       struct stv090x_config *feconf = (struct stv090x_config *)
+               chan->dev->card_info->fe_config[chan->number];
+       struct stv6110x_config *tunerconf = (struct stv6110x_config *)
+               chan->dev->card_info->tuner_config[chan->number];
+       struct stv6110x_devctl *ctl;
+       /* tuner 1+2: i2c adapter #0, tuner 3+4: i2c adapter #1 */
+       if (chan->number < 2)
+               i2c = &chan->dev->channel[0].i2c_adapter;
+       else
+               i2c = &chan->dev->channel[1].i2c_adapter;
+       ctl = dvb_attach(stv6110x_attach, chan->fe, tunerconf, i2c);
+       if (ctl == NULL) {
+               printk(KERN_ERR DEVICE_NAME ": No STV6110X found!\n");
+               return -ENODEV;
+       }
+       feconf->tuner_init          = ctl->tuner_init;
+       feconf->tuner_sleep         = ctl->tuner_sleep;
+       feconf->tuner_set_mode      = ctl->tuner_set_mode;
+       feconf->tuner_set_frequency = ctl->tuner_set_frequency;
+       feconf->tuner_get_frequency = ctl->tuner_get_frequency;
+       feconf->tuner_set_bandwidth = ctl->tuner_set_bandwidth;
+       feconf->tuner_get_bandwidth = ctl->tuner_get_bandwidth;
+       feconf->tuner_set_bbgain    = ctl->tuner_set_bbgain;
+       feconf->tuner_get_bbgain    = ctl->tuner_get_bbgain;
+       feconf->tuner_set_refclk    = ctl->tuner_set_refclk;
+       feconf->tuner_get_status    = ctl->tuner_get_status;
+       return 0;
+ }
+ static int drxk_gate_ctrl(struct dvb_frontend *fe, int enable)
+ {
+       struct ngene_channel *chan = fe->sec_priv;
+       int status;
+       if (enable) {
+               down(&chan->dev->pll_mutex);
+               status = chan->gate_ctrl(fe, 1);
+       } else {
+               status = chan->gate_ctrl(fe, 0);
+               up(&chan->dev->pll_mutex);
+       }
+       return status;
+ }
+ static int tuner_attach_tda18271(struct ngene_channel *chan)
+ {
+       struct i2c_adapter *i2c;
+       struct dvb_frontend *fe;
+       i2c = &chan->dev->channel[0].i2c_adapter;
+       if (chan->fe->ops.i2c_gate_ctrl)
+               chan->fe->ops.i2c_gate_ctrl(chan->fe, 1);
+       fe = dvb_attach(tda18271c2dd_attach, chan->fe, i2c, 0x60);
+       if (chan->fe->ops.i2c_gate_ctrl)
+               chan->fe->ops.i2c_gate_ctrl(chan->fe, 0);
+       if (!fe) {
+               printk(KERN_ERR "No TDA18271 found!\n");
+               return -ENODEV;
+       }
+       return 0;
+ }
+ static int tuner_attach_probe(struct ngene_channel *chan)
+ {
+       if (chan->demod_type == 0)
+               return tuner_attach_stv6110(chan);
+       if (chan->demod_type == 1)
+               return tuner_attach_tda18271(chan);
+       return -EINVAL;
+ }
+ static int demod_attach_stv0900(struct ngene_channel *chan)
+ {
+       struct i2c_adapter *i2c;
+       struct stv090x_config *feconf = (struct stv090x_config *)
+               chan->dev->card_info->fe_config[chan->number];
+       /* tuner 1+2: i2c adapter #0, tuner 3+4: i2c adapter #1 */
+       /* Note: Both adapters share the same i2c bus, but the demod     */
+       /*       driver requires that each demod has its own i2c adapter */
+       if (chan->number < 2)
+               i2c = &chan->dev->channel[0].i2c_adapter;
+       else
+               i2c = &chan->dev->channel[1].i2c_adapter;
+       chan->fe = dvb_attach(stv090x_attach, feconf, i2c,
+                       (chan->number & 1) == 0 ? STV090x_DEMODULATOR_0
+                                               : STV090x_DEMODULATOR_1);
+       if (chan->fe == NULL) {
+               printk(KERN_ERR DEVICE_NAME ": No STV0900 found!\n");
+               return -ENODEV;
+       }
+       /* store channel info */
+       if (feconf->tuner_i2c_lock)
+               chan->fe->analog_demod_priv = chan;
+       if (!dvb_attach(lnbh24_attach, chan->fe, i2c, 0,
+                       0, chan->dev->card_info->lnb[chan->number])) {
+               printk(KERN_ERR DEVICE_NAME ": No LNBH24 found!\n");
+               dvb_frontend_detach(chan->fe);
+               chan->fe = NULL;
+               return -ENODEV;
+       }
+       return 0;
+ }
+ static void cineS2_tuner_i2c_lock(struct dvb_frontend *fe, int lock)
+ {
+       struct ngene_channel *chan = fe->analog_demod_priv;
+       if (lock)
+               down(&chan->dev->pll_mutex);
+       else
+               up(&chan->dev->pll_mutex);
+ }
+ static int i2c_read(struct i2c_adapter *adapter, u8 adr, u8 *val)
+ {
+       struct i2c_msg msgs[1] = {{.addr = adr,  .flags = I2C_M_RD,
+                                  .buf  = val,  .len   = 1 } };
+       return (i2c_transfer(adapter, msgs, 1) == 1) ? 0 : -1;
+ }
+ static int i2c_read_reg16(struct i2c_adapter *adapter, u8 adr,
+                         u16 reg, u8 *val)
+ {
+       u8 msg[2] = {reg>>8, reg&0xff};
+       struct i2c_msg msgs[2] = {{.addr = adr, .flags = 0,
+                                  .buf  = msg, .len   = 2},
+                                 {.addr = adr, .flags = I2C_M_RD,
+                                  .buf  = val, .len   = 1} };
+       return (i2c_transfer(adapter, msgs, 2) == 2) ? 0 : -1;
+ }
+ static int port_has_stv0900(struct i2c_adapter *i2c, int port)
+ {
+       u8 val;
+       if (i2c_read_reg16(i2c, 0x68+port/2, 0xf100, &val) < 0)
+               return 0;
+       return 1;
+ }
+ static int port_has_drxk(struct i2c_adapter *i2c, int port)
+ {
+       u8 val;
+       if (i2c_read(i2c, 0x29+port, &val) < 0)
+               return 0;
+       return 1;
+ }
+ static int demod_attach_drxk(struct ngene_channel *chan,
+                            struct i2c_adapter *i2c)
+ {
+       struct drxk_config config;
+       memset(&config, 0, sizeof(config));
+       config.microcode_name = "drxk_a3.mc";
+       config.qam_demod_parameter_count = 4;
+       config.adr = 0x29 + (chan->number ^ 2);
+       chan->fe = dvb_attach(drxk_attach, &config, i2c);
+       if (!chan->fe) {
+               printk(KERN_ERR "No DRXK found!\n");
+               return -ENODEV;
+       }
+       chan->fe->sec_priv = chan;
+       chan->gate_ctrl = chan->fe->ops.i2c_gate_ctrl;
+       chan->fe->ops.i2c_gate_ctrl = drxk_gate_ctrl;
+       return 0;
+ }
+ static int cineS2_probe(struct ngene_channel *chan)
+ {
+       struct i2c_adapter *i2c;
+       struct stv090x_config *fe_conf;
+       u8 buf[3];
+       struct i2c_msg i2c_msg = { .flags = 0, .buf = buf };
+       int rc;
+       /* tuner 1+2: i2c adapter #0, tuner 3+4: i2c adapter #1 */
+       if (chan->number < 2)
+               i2c = &chan->dev->channel[0].i2c_adapter;
+       else
+               i2c = &chan->dev->channel[1].i2c_adapter;
+       if (port_has_stv0900(i2c, chan->number)) {
+               chan->demod_type = 0;
+               fe_conf = chan->dev->card_info->fe_config[chan->number];
+               /* demod found, attach it */
+               rc = demod_attach_stv0900(chan);
+               if (rc < 0 || chan->number < 2)
+                       return rc;
+               /* demod #2: reprogram outputs DPN1 & DPN2 */
+               i2c_msg.addr = fe_conf->address;
+               i2c_msg.len = 3;
+               buf[0] = 0xf1;
+               switch (chan->number) {
+               case 2:
+                       buf[1] = 0x5c;
+                       buf[2] = 0xc2;
+                       break;
+               case 3:
+                       buf[1] = 0x61;
+                       buf[2] = 0xcc;
+                       break;
+               default:
+                       return -ENODEV;
+               }
+               rc = i2c_transfer(i2c, &i2c_msg, 1);
+               if (rc != 1) {
+                       printk(KERN_ERR DEVICE_NAME ": could not setup DPNx\n");
+                       return -EIO;
+               }
+       } else if (port_has_drxk(i2c, chan->number^2)) {
+               chan->demod_type = 1;
+               demod_attach_drxk(chan, i2c);
+       } else {
+               printk(KERN_ERR "No demod found on chan %d\n", chan->number);
+               return -ENODEV;
+       }
+       return 0;
+ }
+ static struct lgdt330x_config aver_m780 = {
+       .demod_address = 0xb2 >> 1,
+       .demod_chip    = LGDT3303,
+       .serial_mpeg   = 0x00, /* PARALLEL */
+       .clock_polarity_flip = 1,
+ };
+ static struct mt2131_config m780_tunerconfig = {
+       0xc0 >> 1
+ };
+ /* A single func to attach the demo and tuner, rather than
+  * use two sep funcs like the current design mandates.
+  */
+ static int demod_attach_lg330x(struct ngene_channel *chan)
+ {
+       chan->fe = dvb_attach(lgdt330x_attach, &aver_m780, &chan->i2c_adapter);
+       if (chan->fe == NULL) {
+               printk(KERN_ERR DEVICE_NAME ": No LGDT330x found!\n");
+               return -ENODEV;
+       }
+       dvb_attach(mt2131_attach, chan->fe, &chan->i2c_adapter,
+                  &m780_tunerconfig, 0);
+       return (chan->fe) ? 0 : -ENODEV;
+ }
+ static int demod_attach_drxd(struct ngene_channel *chan)
+ {
+       struct drxd_config *feconf;
+       feconf = chan->dev->card_info->fe_config[chan->number];
+       chan->fe = dvb_attach(drxd_attach, feconf, chan,
+                       &chan->i2c_adapter, &chan->dev->pci_dev->dev);
+       if (!chan->fe) {
+               pr_err("No DRXD found!\n");
+               return -ENODEV;
+       }
+       if (!dvb_attach(dvb_pll_attach, chan->fe, feconf->pll_address,
+                       &chan->i2c_adapter,
+                       feconf->pll_type)) {
+               pr_err("No pll(%d) found!\n", feconf->pll_type);
+               return -ENODEV;
+       }
+       return 0;
+ }
+ /****************************************************************************/
+ /* EEPROM TAGS **************************************************************/
+ /****************************************************************************/
+ #define MICNG_EE_START      0x0100
+ #define MICNG_EE_END        0x0FF0
+ #define MICNG_EETAG_END0    0x0000
+ #define MICNG_EETAG_END1    0xFFFF
+ /* 0x0001 - 0x000F reserved for housekeeping */
+ /* 0xFFFF - 0xFFFE reserved for housekeeping */
+ /* Micronas assigned tags
+    EEProm tags for hardware support */
+ #define MICNG_EETAG_DRXD1_OSCDEVIATION  0x1000  /* 2 Bytes data */
+ #define MICNG_EETAG_DRXD2_OSCDEVIATION  0x1001  /* 2 Bytes data */
+ #define MICNG_EETAG_MT2060_1_1STIF      0x1100  /* 2 Bytes data */
+ #define MICNG_EETAG_MT2060_2_1STIF      0x1101  /* 2 Bytes data */
+ /* Tag range for OEMs */
+ #define MICNG_EETAG_OEM_FIRST  0xC000
+ #define MICNG_EETAG_OEM_LAST   0xFFEF
+ static int i2c_write_eeprom(struct i2c_adapter *adapter,
+                           u8 adr, u16 reg, u8 data)
+ {
+       u8 m[3] = {(reg >> 8), (reg & 0xff), data};
+       struct i2c_msg msg = {.addr = adr, .flags = 0, .buf = m,
+                             .len = sizeof(m)};
+       if (i2c_transfer(adapter, &msg, 1) != 1) {
+               pr_err(DEVICE_NAME ": Error writing EEPROM!\n");
+               return -EIO;
+       }
+       return 0;
+ }
+ static int i2c_read_eeprom(struct i2c_adapter *adapter,
+                          u8 adr, u16 reg, u8 *data, int len)
+ {
+       u8 msg[2] = {(reg >> 8), (reg & 0xff)};
+       struct i2c_msg msgs[2] = {{.addr = adr, .flags = 0,
+                                  .buf = msg, .len = 2 },
+                                 {.addr = adr, .flags = I2C_M_RD,
+                                  .buf = data, .len = len} };
+       if (i2c_transfer(adapter, msgs, 2) != 2) {
+               pr_err(DEVICE_NAME ": Error reading EEPROM\n");
+               return -EIO;
+       }
+       return 0;
+ }
+ static int ReadEEProm(struct i2c_adapter *adapter,
+                     u16 Tag, u32 MaxLen, u8 *data, u32 *pLength)
+ {
+       int status = 0;
+       u16 Addr = MICNG_EE_START, Length, tag = 0;
+       u8  EETag[3];
+       while (Addr + sizeof(u16) + 1 < MICNG_EE_END) {
+               if (i2c_read_eeprom(adapter, 0x50, Addr, EETag, sizeof(EETag)))
+                       return -1;
+               tag = (EETag[0] << 8) | EETag[1];
+               if (tag == MICNG_EETAG_END0 || tag == MICNG_EETAG_END1)
+                       return -1;
+               if (tag == Tag)
+                       break;
+               Addr += sizeof(u16) + 1 + EETag[2];
+       }
+       if (Addr + sizeof(u16) + 1 + EETag[2] > MICNG_EE_END) {
+               pr_err(DEVICE_NAME
+                      ": Reached EOEE @ Tag = %04x Length = %3d\n",
+                      tag, EETag[2]);
+               return -1;
+       }
+       Length = EETag[2];
+       if (Length > MaxLen)
+               Length = (u16) MaxLen;
+       if (Length > 0) {
+               Addr += sizeof(u16) + 1;
+               status = i2c_read_eeprom(adapter, 0x50, Addr, data, Length);
+               if (!status) {
+                       *pLength = EETag[2];
+                       if (Length < EETag[2])
+                               ; /*status=STATUS_BUFFER_OVERFLOW; */
+               }
+       }
+       return status;
+ }
+ static int WriteEEProm(struct i2c_adapter *adapter,
+                      u16 Tag, u32 Length, u8 *data)
+ {
+       int status = 0;
+       u16 Addr = MICNG_EE_START;
+       u8 EETag[3];
+       u16 tag = 0;
+       int retry, i;
+       while (Addr + sizeof(u16) + 1 < MICNG_EE_END) {
+               if (i2c_read_eeprom(adapter, 0x50, Addr, EETag, sizeof(EETag)))
+                       return -1;
+               tag = (EETag[0] << 8) | EETag[1];
+               if (tag == MICNG_EETAG_END0 || tag == MICNG_EETAG_END1)
+                       return -1;
+               if (tag == Tag)
+                       break;
+               Addr += sizeof(u16) + 1 + EETag[2];
+       }
+       if (Addr + sizeof(u16) + 1 + EETag[2] > MICNG_EE_END) {
+               pr_err(DEVICE_NAME
+                      ": Reached EOEE @ Tag = %04x Length = %3d\n",
+                      tag, EETag[2]);
+               return -1;
+       }
+       if (Length > EETag[2])
+               return -EINVAL;
+       /* Note: We write the data one byte at a time to avoid
+          issues with page sizes. (which are different for
+          each manufacture and eeprom size)
+        */
+       Addr += sizeof(u16) + 1;
+       for (i = 0; i < Length; i++, Addr++) {
+               status = i2c_write_eeprom(adapter, 0x50, Addr, data[i]);
+               if (status)
+                       break;
+               /* Poll for finishing write cycle */
+               retry = 10;
+               while (retry) {
+                       u8 Tmp;
+                       msleep(50);
+                       status = i2c_read_eeprom(adapter, 0x50, Addr, &Tmp, 1);
+                       if (status)
+                               break;
+                       if (Tmp != data[i])
+                               pr_err(DEVICE_NAME
+                                      "eeprom write error\n");
+                       retry -= 1;
+               }
+               if (status) {
+                       pr_err(DEVICE_NAME
+                              ": Timeout polling eeprom\n");
+                       break;
+               }
+       }
+       return status;
+ }
+ static int eeprom_read_ushort(struct i2c_adapter *adapter, u16 tag, u16 *data)
+ {
+       int stat;
+       u8 buf[2];
+       u32 len = 0;
+       stat = ReadEEProm(adapter, tag, 2, buf, &len);
+       if (stat)
+               return stat;
+       if (len != 2)
+               return -EINVAL;
+       *data = (buf[0] << 8) | buf[1];
+       return 0;
+ }
+ static int eeprom_write_ushort(struct i2c_adapter *adapter, u16 tag, u16 data)
+ {
+       int stat;
+       u8 buf[2];
+       buf[0] = data >> 8;
+       buf[1] = data & 0xff;
+       stat = WriteEEProm(adapter, tag, 2, buf);
+       if (stat)
+               return stat;
+       return 0;
+ }
+ static s16 osc_deviation(void *priv, s16 deviation, int flag)
+ {
+       struct ngene_channel *chan = priv;
+       struct i2c_adapter *adap = &chan->i2c_adapter;
+       u16 data = 0;
+       if (flag) {
+               data = (u16) deviation;
+               pr_info(DEVICE_NAME ": write deviation %d\n",
+                      deviation);
+               eeprom_write_ushort(adap, 0x1000 + chan->number, data);
+       } else {
+               if (eeprom_read_ushort(adap, 0x1000 + chan->number, &data))
+                       data = 0;
+               pr_info(DEVICE_NAME ": read deviation %d\n",
+                      (s16) data);
+       }
+       return (s16) data;
+ }
+ /****************************************************************************/
+ /* Switch control (I2C gates, etc.) *****************************************/
+ /****************************************************************************/
+ static struct stv090x_config fe_cineS2 = {
+       .device         = STV0900,
+       .demod_mode     = STV090x_DUAL,
+       .clk_mode       = STV090x_CLK_EXT,
+       .xtal           = 27000000,
+       .address        = 0x68,
+       .ts1_mode       = STV090x_TSMODE_SERIAL_PUNCTURED,
+       .ts2_mode       = STV090x_TSMODE_SERIAL_PUNCTURED,
+       .repeater_level = STV090x_RPTLEVEL_16,
+       .adc1_range     = STV090x_ADC_1Vpp,
+       .adc2_range     = STV090x_ADC_1Vpp,
+       .diseqc_envelope_mode = true,
+       .tuner_i2c_lock = cineS2_tuner_i2c_lock,
+ };
+ static struct stv090x_config fe_cineS2_2 = {
+       .device         = STV0900,
+       .demod_mode     = STV090x_DUAL,
+       .clk_mode       = STV090x_CLK_EXT,
+       .xtal           = 27000000,
+       .address        = 0x69,
+       .ts1_mode       = STV090x_TSMODE_SERIAL_PUNCTURED,
+       .ts2_mode       = STV090x_TSMODE_SERIAL_PUNCTURED,
+       .repeater_level = STV090x_RPTLEVEL_16,
+       .adc1_range     = STV090x_ADC_1Vpp,
+       .adc2_range     = STV090x_ADC_1Vpp,
+       .diseqc_envelope_mode = true,
+       .tuner_i2c_lock = cineS2_tuner_i2c_lock,
+ };
+ static struct stv6110x_config tuner_cineS2_0 = {
+       .addr   = 0x60,
+       .refclk = 27000000,
+       .clk_div = 1,
+ };
+ static struct stv6110x_config tuner_cineS2_1 = {
+       .addr   = 0x63,
+       .refclk = 27000000,
+       .clk_div = 1,
+ };
+ static struct ngene_info ngene_info_cineS2 = {
+       .type           = NGENE_SIDEWINDER,
+       .name           = "Linux4Media cineS2 DVB-S2 Twin Tuner",
+       .io_type        = {NGENE_IO_TSIN, NGENE_IO_TSIN},
+       .demod_attach   = {demod_attach_stv0900, demod_attach_stv0900},
+       .tuner_attach   = {tuner_attach_stv6110, tuner_attach_stv6110},
+       .fe_config      = {&fe_cineS2, &fe_cineS2},
+       .tuner_config   = {&tuner_cineS2_0, &tuner_cineS2_1},
+       .lnb            = {0x0b, 0x08},
+       .tsf            = {3, 3},
+       .fw_version     = 18,
+       .msi_supported  = true,
+ };
+ static struct ngene_info ngene_info_satixS2 = {
+       .type           = NGENE_SIDEWINDER,
+       .name           = "Mystique SaTiX-S2 Dual",
+       .io_type        = {NGENE_IO_TSIN, NGENE_IO_TSIN},
+       .demod_attach   = {demod_attach_stv0900, demod_attach_stv0900},
+       .tuner_attach   = {tuner_attach_stv6110, tuner_attach_stv6110},
+       .fe_config      = {&fe_cineS2, &fe_cineS2},
+       .tuner_config   = {&tuner_cineS2_0, &tuner_cineS2_1},
+       .lnb            = {0x0b, 0x08},
+       .tsf            = {3, 3},
+       .fw_version     = 18,
+       .msi_supported  = true,
+ };
+ static struct ngene_info ngene_info_satixS2v2 = {
+       .type           = NGENE_SIDEWINDER,
+       .name           = "Mystique SaTiX-S2 Dual (v2)",
+       .io_type        = {NGENE_IO_TSIN, NGENE_IO_TSIN, NGENE_IO_TSIN, NGENE_IO_TSIN,
+                          NGENE_IO_TSOUT},
+       .demod_attach   = {demod_attach_stv0900, demod_attach_stv0900, cineS2_probe, cineS2_probe},
+       .tuner_attach   = {tuner_attach_stv6110, tuner_attach_stv6110, tuner_attach_probe, tuner_attach_probe},
+       .fe_config      = {&fe_cineS2, &fe_cineS2, &fe_cineS2_2, &fe_cineS2_2},
+       .tuner_config   = {&tuner_cineS2_0, &tuner_cineS2_1, &tuner_cineS2_0, &tuner_cineS2_1},
+       .lnb            = {0x0a, 0x08, 0x0b, 0x09},
+       .tsf            = {3, 3},
+       .fw_version     = 18,
+       .msi_supported  = true,
+ };
+ static struct ngene_info ngene_info_cineS2v5 = {
+       .type           = NGENE_SIDEWINDER,
+       .name           = "Linux4Media cineS2 DVB-S2 Twin Tuner (v5)",
+       .io_type        = {NGENE_IO_TSIN, NGENE_IO_TSIN, NGENE_IO_TSIN, NGENE_IO_TSIN,
+                          NGENE_IO_TSOUT},
+       .demod_attach   = {demod_attach_stv0900, demod_attach_stv0900, cineS2_probe, cineS2_probe},
+       .tuner_attach   = {tuner_attach_stv6110, tuner_attach_stv6110, tuner_attach_probe, tuner_attach_probe},
+       .fe_config      = {&fe_cineS2, &fe_cineS2, &fe_cineS2_2, &fe_cineS2_2},
+       .tuner_config   = {&tuner_cineS2_0, &tuner_cineS2_1, &tuner_cineS2_0, &tuner_cineS2_1},
+       .lnb            = {0x0a, 0x08, 0x0b, 0x09},
+       .tsf            = {3, 3},
+       .fw_version     = 18,
+       .msi_supported  = true,
+ };
+ static struct ngene_info ngene_info_duoFlex = {
+       .type           = NGENE_SIDEWINDER,
+       .name           = "Digital Devices DuoFlex PCIe or miniPCIe",
+       .io_type        = {NGENE_IO_TSIN, NGENE_IO_TSIN, NGENE_IO_TSIN, NGENE_IO_TSIN,
+                          NGENE_IO_TSOUT},
+       .demod_attach   = {cineS2_probe, cineS2_probe, cineS2_probe, cineS2_probe},
+       .tuner_attach   = {tuner_attach_probe, tuner_attach_probe, tuner_attach_probe, tuner_attach_probe},
+       .fe_config      = {&fe_cineS2, &fe_cineS2, &fe_cineS2_2, &fe_cineS2_2},
+       .tuner_config   = {&tuner_cineS2_0, &tuner_cineS2_1, &tuner_cineS2_0, &tuner_cineS2_1},
+       .lnb            = {0x0a, 0x08, 0x0b, 0x09},
+       .tsf            = {3, 3},
+       .fw_version     = 18,
+       .msi_supported  = true,
+ };
+ static struct ngene_info ngene_info_m780 = {
+       .type           = NGENE_APP,
+       .name           = "Aver M780 ATSC/QAM-B",
+       /* Channel 0 is analog, which is currently unsupported */
+       .io_type        = { NGENE_IO_NONE, NGENE_IO_TSIN },
+       .demod_attach   = { NULL, demod_attach_lg330x },
+       /* Ensure these are NULL else the frame will call them (as funcs) */
+       .tuner_attach   = { 0, 0, 0, 0 },
+       .fe_config      = { NULL, &aver_m780 },
+       .avf            = { 0 },
+       /* A custom electrical interface config for the demod to bridge */
+       .tsf            = { 4, 4 },
+       .fw_version     = 15,
+ };
+ static struct drxd_config fe_terratec_dvbt_0 = {
+       .index          = 0,
+       .demod_address  = 0x70,
+       .demod_revision = 0xa2,
+       .demoda_address = 0x00,
+       .pll_address    = 0x60,
+       .pll_type       = DVB_PLL_THOMSON_DTT7520X,
+       .clock          = 20000,
+       .osc_deviation  = osc_deviation,
+ };
+ static struct drxd_config fe_terratec_dvbt_1 = {
+       .index          = 1,
+       .demod_address  = 0x71,
+       .demod_revision = 0xa2,
+       .demoda_address = 0x00,
+       .pll_address    = 0x60,
+       .pll_type       = DVB_PLL_THOMSON_DTT7520X,
+       .clock          = 20000,
+       .osc_deviation  = osc_deviation,
+ };
+ static struct ngene_info ngene_info_terratec = {
+       .type           = NGENE_TERRATEC,
+       .name           = "Terratec Integra/Cinergy2400i Dual DVB-T",
+       .io_type        = {NGENE_IO_TSIN, NGENE_IO_TSIN},
+       .demod_attach   = {demod_attach_drxd, demod_attach_drxd},
+       .fe_config      = {&fe_terratec_dvbt_0, &fe_terratec_dvbt_1},
+       .i2c_access     = 1,
+ };
+ /****************************************************************************/
+ /****************************************************************************/
+ /* PCI Subsystem ID *********************************************************/
+ /****************************************************************************/
+ #define NGENE_ID(_subvend, _subdev, _driverdata) { \
+       .vendor = NGENE_VID, .device = NGENE_PID, \
+       .subvendor = _subvend, .subdevice = _subdev, \
+       .driver_data = (unsigned long) &_driverdata }
+ /****************************************************************************/
+ static const struct pci_device_id ngene_id_tbl[] __devinitdata = {
+       NGENE_ID(0x18c3, 0xabc3, ngene_info_cineS2),
+       NGENE_ID(0x18c3, 0xabc4, ngene_info_cineS2),
+       NGENE_ID(0x18c3, 0xdb01, ngene_info_satixS2),
+       NGENE_ID(0x18c3, 0xdb02, ngene_info_satixS2v2),
+       NGENE_ID(0x18c3, 0xdd00, ngene_info_cineS2v5),
+       NGENE_ID(0x18c3, 0xdd10, ngene_info_duoFlex),
+       NGENE_ID(0x18c3, 0xdd20, ngene_info_duoFlex),
+       NGENE_ID(0x1461, 0x062e, ngene_info_m780),
+       NGENE_ID(0x153b, 0x1167, ngene_info_terratec),
+       {0}
+ };
+ MODULE_DEVICE_TABLE(pci, ngene_id_tbl);
+ /****************************************************************************/
+ /* Init/Exit ****************************************************************/
+ /****************************************************************************/
+ static pci_ers_result_t ngene_error_detected(struct pci_dev *dev,
+                                            enum pci_channel_state state)
+ {
+       printk(KERN_ERR DEVICE_NAME ": PCI error\n");
+       if (state == pci_channel_io_perm_failure)
+               return PCI_ERS_RESULT_DISCONNECT;
+       if (state == pci_channel_io_frozen)
+               return PCI_ERS_RESULT_NEED_RESET;
+       return PCI_ERS_RESULT_CAN_RECOVER;
+ }
+ static pci_ers_result_t ngene_link_reset(struct pci_dev *dev)
+ {
+       printk(KERN_INFO DEVICE_NAME ": link reset\n");
+       return 0;
+ }
+ static pci_ers_result_t ngene_slot_reset(struct pci_dev *dev)
+ {
+       printk(KERN_INFO DEVICE_NAME ": slot reset\n");
+       return 0;
+ }
+ static void ngene_resume(struct pci_dev *dev)
+ {
+       printk(KERN_INFO DEVICE_NAME ": resume\n");
+ }
++static const struct pci_error_handlers ngene_errors = {
+       .error_detected = ngene_error_detected,
+       .link_reset = ngene_link_reset,
+       .slot_reset = ngene_slot_reset,
+       .resume = ngene_resume,
+ };
+ static struct pci_driver ngene_pci_driver = {
+       .name        = "ngene",
+       .id_table    = ngene_id_tbl,
+       .probe       = ngene_probe,
+       .remove      = __devexit_p(ngene_remove),
+       .err_handler = &ngene_errors,
+       .shutdown    = ngene_shutdown,
+ };
+ static __init int module_init_ngene(void)
+ {
+       printk(KERN_INFO
+              "nGene PCIE bridge driver, Copyright (C) 2005-2007 Micronas\n");
+       return pci_register_driver(&ngene_pci_driver);
+ }
+ static __exit void module_exit_ngene(void)
+ {
+       pci_unregister_driver(&ngene_pci_driver);
+ }
+ module_init(module_init_ngene);
+ module_exit(module_exit_ngene);
+ MODULE_DESCRIPTION("nGene");
+ MODULE_AUTHOR("Micronas, Ralph Metzler, Manfred Voelkel");
+ MODULE_LICENSE("GPL");
index 0000000000000000000000000000000000000000,5fbb4e49495c8278f25da91b4dca454c405e5dc0..f2b37e05b96479e7f534537a3208d120b408f661
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,1368 +1,1368 @@@
 -      flush_work_sync(&dev->request_module_wk);
+ /*
+  *
+  * device driver for philips saa7134 based TV cards
+  * driver core
+  *
+  * (c) 2001-03 Gerd Knorr <kraxel@bytesex.org> [SuSE Labs]
+  *
+  *  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/init.h>
+ #include <linux/list.h>
+ #include <linux/module.h>
+ #include <linux/kernel.h>
+ #include <linux/slab.h>
+ #include <linux/kmod.h>
+ #include <linux/sound.h>
+ #include <linux/interrupt.h>
+ #include <linux/delay.h>
+ #include <linux/mutex.h>
+ #include <linux/dma-mapping.h>
+ #include <linux/pm.h>
+ #include "saa7134-reg.h"
+ #include "saa7134.h"
+ MODULE_DESCRIPTION("v4l2 driver module for saa7130/34 based TV cards");
+ MODULE_AUTHOR("Gerd Knorr <kraxel@bytesex.org> [SuSE Labs]");
+ MODULE_LICENSE("GPL");
+ MODULE_VERSION(SAA7134_VERSION);
+ /* ------------------------------------------------------------------ */
+ static unsigned int irq_debug;
+ module_param(irq_debug, int, 0644);
+ MODULE_PARM_DESC(irq_debug,"enable debug messages [IRQ handler]");
+ static unsigned int core_debug;
+ module_param(core_debug, int, 0644);
+ MODULE_PARM_DESC(core_debug,"enable debug messages [core]");
+ static unsigned int gpio_tracking;
+ module_param(gpio_tracking, int, 0644);
+ MODULE_PARM_DESC(gpio_tracking,"enable debug messages [gpio]");
+ static unsigned int alsa = 1;
+ module_param(alsa, int, 0644);
+ MODULE_PARM_DESC(alsa,"enable/disable ALSA DMA sound [dmasound]");
+ static unsigned int latency = UNSET;
+ module_param(latency, int, 0444);
+ MODULE_PARM_DESC(latency,"pci latency timer");
+ int saa7134_no_overlay=-1;
+ module_param_named(no_overlay, saa7134_no_overlay, int, 0444);
+ MODULE_PARM_DESC(no_overlay,"allow override overlay default (0 disables, 1 enables)"
+               " [some VIA/SIS chipsets are known to have problem with overlay]");
+ static unsigned int video_nr[] = {[0 ... (SAA7134_MAXBOARDS - 1)] = UNSET };
+ static unsigned int vbi_nr[]   = {[0 ... (SAA7134_MAXBOARDS - 1)] = UNSET };
+ static unsigned int radio_nr[] = {[0 ... (SAA7134_MAXBOARDS - 1)] = UNSET };
+ static unsigned int tuner[]    = {[0 ... (SAA7134_MAXBOARDS - 1)] = UNSET };
+ static unsigned int card[]     = {[0 ... (SAA7134_MAXBOARDS - 1)] = UNSET };
+ module_param_array(video_nr, int, NULL, 0444);
+ module_param_array(vbi_nr,   int, NULL, 0444);
+ module_param_array(radio_nr, int, NULL, 0444);
+ module_param_array(tuner,    int, NULL, 0444);
+ module_param_array(card,     int, NULL, 0444);
+ MODULE_PARM_DESC(video_nr, "video device number");
+ MODULE_PARM_DESC(vbi_nr,   "vbi device number");
+ MODULE_PARM_DESC(radio_nr, "radio device number");
+ MODULE_PARM_DESC(tuner,    "tuner type");
+ MODULE_PARM_DESC(card,     "card type");
+ DEFINE_MUTEX(saa7134_devlist_lock);
+ EXPORT_SYMBOL(saa7134_devlist_lock);
+ LIST_HEAD(saa7134_devlist);
+ EXPORT_SYMBOL(saa7134_devlist);
+ static LIST_HEAD(mops_list);
+ static unsigned int saa7134_devcount;
+ int (*saa7134_dmasound_init)(struct saa7134_dev *dev);
+ int (*saa7134_dmasound_exit)(struct saa7134_dev *dev);
+ #define dprintk(fmt, arg...)  if (core_debug) \
+       printk(KERN_DEBUG "%s/core: " fmt, dev->name , ## arg)
+ void saa7134_track_gpio(struct saa7134_dev *dev, char *msg)
+ {
+       unsigned long mode,status;
+       if (!gpio_tracking)
+               return;
+       /* rising SAA7134_GPIO_GPRESCAN reads the status */
+       saa_andorb(SAA7134_GPIO_GPMODE3,SAA7134_GPIO_GPRESCAN,0);
+       saa_andorb(SAA7134_GPIO_GPMODE3,SAA7134_GPIO_GPRESCAN,SAA7134_GPIO_GPRESCAN);
+       mode   = saa_readl(SAA7134_GPIO_GPMODE0   >> 2) & 0xfffffff;
+       status = saa_readl(SAA7134_GPIO_GPSTATUS0 >> 2) & 0xfffffff;
+       printk(KERN_DEBUG
+              "%s: gpio: mode=0x%07lx in=0x%07lx out=0x%07lx [%s]\n",
+              dev->name, mode, (~mode) & status, mode & status, msg);
+ }
+ void saa7134_set_gpio(struct saa7134_dev *dev, int bit_no, int value)
+ {
+       u32 index, bitval;
+       index = 1 << bit_no;
+       switch (value) {
+       case 0: /* static value */
+       case 1: dprintk("setting GPIO%d to static %d\n", bit_no, value);
+               /* turn sync mode off if necessary */
+               if (index & 0x00c00000)
+                       saa_andorb(SAA7134_VIDEO_PORT_CTRL6, 0x0f, 0x00);
+               if (value)
+                       bitval = index;
+               else
+                       bitval = 0;
+               saa_andorl(SAA7134_GPIO_GPMODE0 >> 2, index, index);
+               saa_andorl(SAA7134_GPIO_GPSTATUS0 >> 2, index, bitval);
+               break;
+       case 3: /* tristate */
+               dprintk("setting GPIO%d to tristate\n", bit_no);
+               saa_andorl(SAA7134_GPIO_GPMODE0 >> 2, index, 0);
+               break;
+       }
+ }
+ /* ------------------------------------------------------------------ */
+ /* ----------------------------------------------------------- */
+ /* delayed request_module                                      */
+ #if defined(CONFIG_MODULES) && defined(MODULE)
+ static void request_module_async(struct work_struct *work){
+       struct saa7134_dev* dev = container_of(work, struct saa7134_dev, request_module_wk);
+       if (card_is_empress(dev))
+               request_module("saa7134-empress");
+       if (card_is_dvb(dev))
+               request_module("saa7134-dvb");
+       if (alsa) {
+               if (dev->pci->device != PCI_DEVICE_ID_PHILIPS_SAA7130)
+                       request_module("saa7134-alsa");
+       }
+ }
+ static void request_submodules(struct saa7134_dev *dev)
+ {
+       INIT_WORK(&dev->request_module_wk, request_module_async);
+       schedule_work(&dev->request_module_wk);
+ }
+ static void flush_request_submodules(struct saa7134_dev *dev)
+ {
++      flush_work(&dev->request_module_wk);
+ }
+ #else
+ #define request_submodules(dev)
+ #define flush_request_submodules(dev)
+ #endif /* CONFIG_MODULES */
+ /* ------------------------------------------------------------------ */
+ /* nr of (saa7134-)pages for the given buffer size */
+ static int saa7134_buffer_pages(int size)
+ {
+       size  = PAGE_ALIGN(size);
+       size += PAGE_SIZE; /* for non-page-aligned buffers */
+       size /= 4096;
+       return size;
+ }
+ /* calc max # of buffers from size (must not exceed the 4MB virtual
+  * address space per DMA channel) */
+ int saa7134_buffer_count(unsigned int size, unsigned int count)
+ {
+       unsigned int maxcount;
+       maxcount = 1024 / saa7134_buffer_pages(size);
+       if (count > maxcount)
+               count = maxcount;
+       return count;
+ }
+ int saa7134_buffer_startpage(struct saa7134_buf *buf)
+ {
+       return saa7134_buffer_pages(buf->vb.bsize) * buf->vb.i;
+ }
+ unsigned long saa7134_buffer_base(struct saa7134_buf *buf)
+ {
+       unsigned long base;
+       struct videobuf_dmabuf *dma=videobuf_to_dma(&buf->vb);
+       base  = saa7134_buffer_startpage(buf) * 4096;
+       base += dma->sglist[0].offset;
+       return base;
+ }
+ /* ------------------------------------------------------------------ */
+ int saa7134_pgtable_alloc(struct pci_dev *pci, struct saa7134_pgtable *pt)
+ {
+       __le32       *cpu;
+       dma_addr_t   dma_addr = 0;
+       cpu = pci_alloc_consistent(pci, SAA7134_PGTABLE_SIZE, &dma_addr);
+       if (NULL == cpu)
+               return -ENOMEM;
+       pt->size = SAA7134_PGTABLE_SIZE;
+       pt->cpu  = cpu;
+       pt->dma  = dma_addr;
+       return 0;
+ }
+ int saa7134_pgtable_build(struct pci_dev *pci, struct saa7134_pgtable *pt,
+                         struct scatterlist *list, unsigned int length,
+                         unsigned int startpage)
+ {
+       __le32        *ptr;
+       unsigned int  i,p;
+       BUG_ON(NULL == pt || NULL == pt->cpu);
+       ptr = pt->cpu + startpage;
+       for (i = 0; i < length; i++, list++)
+               for (p = 0; p * 4096 < list->length; p++, ptr++)
+                       *ptr = cpu_to_le32(sg_dma_address(list) - list->offset);
+       return 0;
+ }
+ void saa7134_pgtable_free(struct pci_dev *pci, struct saa7134_pgtable *pt)
+ {
+       if (NULL == pt->cpu)
+               return;
+       pci_free_consistent(pci, pt->size, pt->cpu, pt->dma);
+       pt->cpu = NULL;
+ }
+ /* ------------------------------------------------------------------ */
+ void saa7134_dma_free(struct videobuf_queue *q,struct saa7134_buf *buf)
+ {
+       struct videobuf_dmabuf *dma=videobuf_to_dma(&buf->vb);
+       BUG_ON(in_interrupt());
+       videobuf_waiton(q, &buf->vb, 0, 0);
+       videobuf_dma_unmap(q->dev, dma);
+       videobuf_dma_free(dma);
+       buf->vb.state = VIDEOBUF_NEEDS_INIT;
+ }
+ /* ------------------------------------------------------------------ */
+ int saa7134_buffer_queue(struct saa7134_dev *dev,
+                        struct saa7134_dmaqueue *q,
+                        struct saa7134_buf *buf)
+ {
+       struct saa7134_buf *next = NULL;
+       assert_spin_locked(&dev->slock);
+       dprintk("buffer_queue %p\n",buf);
+       if (NULL == q->curr) {
+               if (!q->need_two) {
+                       q->curr = buf;
+                       buf->activate(dev,buf,NULL);
+               } else if (list_empty(&q->queue)) {
+                       list_add_tail(&buf->vb.queue,&q->queue);
+                       buf->vb.state = VIDEOBUF_QUEUED;
+               } else {
+                       next = list_entry(q->queue.next,struct saa7134_buf,
+                                         vb.queue);
+                       q->curr = buf;
+                       buf->activate(dev,buf,next);
+               }
+       } else {
+               list_add_tail(&buf->vb.queue,&q->queue);
+               buf->vb.state = VIDEOBUF_QUEUED;
+       }
+       return 0;
+ }
+ void saa7134_buffer_finish(struct saa7134_dev *dev,
+                          struct saa7134_dmaqueue *q,
+                          unsigned int state)
+ {
+       assert_spin_locked(&dev->slock);
+       dprintk("buffer_finish %p\n",q->curr);
+       /* finish current buffer */
+       q->curr->vb.state = state;
+       do_gettimeofday(&q->curr->vb.ts);
+       wake_up(&q->curr->vb.done);
+       q->curr = NULL;
+ }
+ void saa7134_buffer_next(struct saa7134_dev *dev,
+                        struct saa7134_dmaqueue *q)
+ {
+       struct saa7134_buf *buf,*next = NULL;
+       assert_spin_locked(&dev->slock);
+       BUG_ON(NULL != q->curr);
+       if (!list_empty(&q->queue)) {
+               /* activate next one from queue */
+               buf = list_entry(q->queue.next,struct saa7134_buf,vb.queue);
+               dprintk("buffer_next %p [prev=%p/next=%p]\n",
+                       buf,q->queue.prev,q->queue.next);
+               list_del(&buf->vb.queue);
+               if (!list_empty(&q->queue))
+                       next = list_entry(q->queue.next,struct saa7134_buf,
+                                         vb.queue);
+               q->curr = buf;
+               buf->activate(dev,buf,next);
+               dprintk("buffer_next #2 prev=%p/next=%p\n",
+                       q->queue.prev,q->queue.next);
+       } else {
+               /* nothing to do -- just stop DMA */
+               dprintk("buffer_next %p\n",NULL);
+               saa7134_set_dmabits(dev);
+               del_timer(&q->timeout);
+               if (card_has_mpeg(dev))
+                       if (dev->ts_started)
+                               saa7134_ts_stop(dev);
+       }
+ }
+ void saa7134_buffer_timeout(unsigned long data)
+ {
+       struct saa7134_dmaqueue *q = (struct saa7134_dmaqueue*)data;
+       struct saa7134_dev *dev = q->dev;
+       unsigned long flags;
+       spin_lock_irqsave(&dev->slock,flags);
+       /* try to reset the hardware (SWRST) */
+       saa_writeb(SAA7134_REGION_ENABLE, 0x00);
+       saa_writeb(SAA7134_REGION_ENABLE, 0x80);
+       saa_writeb(SAA7134_REGION_ENABLE, 0x00);
+       /* flag current buffer as failed,
+          try to start over with the next one. */
+       if (q->curr) {
+               dprintk("timeout on %p\n",q->curr);
+               saa7134_buffer_finish(dev,q,VIDEOBUF_ERROR);
+       }
+       saa7134_buffer_next(dev,q);
+       spin_unlock_irqrestore(&dev->slock,flags);
+ }
+ /* ------------------------------------------------------------------ */
+ int saa7134_set_dmabits(struct saa7134_dev *dev)
+ {
+       u32 split, task=0, ctrl=0, irq=0;
+       enum v4l2_field cap = V4L2_FIELD_ANY;
+       enum v4l2_field ov  = V4L2_FIELD_ANY;
+       assert_spin_locked(&dev->slock);
+       if (dev->insuspend)
+               return 0;
+       /* video capture -- dma 0 + video task A */
+       if (dev->video_q.curr) {
+               task |= 0x01;
+               ctrl |= SAA7134_MAIN_CTRL_TE0;
+               irq  |= SAA7134_IRQ1_INTE_RA0_1 |
+                       SAA7134_IRQ1_INTE_RA0_0;
+               cap = dev->video_q.curr->vb.field;
+       }
+       /* video capture -- dma 1+2 (planar modes) */
+       if (dev->video_q.curr &&
+           dev->video_q.curr->fmt->planar) {
+               ctrl |= SAA7134_MAIN_CTRL_TE4 |
+                       SAA7134_MAIN_CTRL_TE5;
+       }
+       /* screen overlay -- dma 0 + video task B */
+       if (dev->ovenable) {
+               task |= 0x10;
+               ctrl |= SAA7134_MAIN_CTRL_TE1;
+               ov = dev->ovfield;
+       }
+       /* vbi capture -- dma 0 + vbi task A+B */
+       if (dev->vbi_q.curr) {
+               task |= 0x22;
+               ctrl |= SAA7134_MAIN_CTRL_TE2 |
+                       SAA7134_MAIN_CTRL_TE3;
+               irq  |= SAA7134_IRQ1_INTE_RA0_7 |
+                       SAA7134_IRQ1_INTE_RA0_6 |
+                       SAA7134_IRQ1_INTE_RA0_5 |
+                       SAA7134_IRQ1_INTE_RA0_4;
+       }
+       /* audio capture -- dma 3 */
+       if (dev->dmasound.dma_running) {
+               ctrl |= SAA7134_MAIN_CTRL_TE6;
+               irq  |= SAA7134_IRQ1_INTE_RA3_1 |
+                       SAA7134_IRQ1_INTE_RA3_0;
+       }
+       /* TS capture -- dma 5 */
+       if (dev->ts_q.curr) {
+               ctrl |= SAA7134_MAIN_CTRL_TE5;
+               irq  |= SAA7134_IRQ1_INTE_RA2_1 |
+                       SAA7134_IRQ1_INTE_RA2_0;
+       }
+       /* set task conditions + field handling */
+       if (V4L2_FIELD_HAS_BOTH(cap) || V4L2_FIELD_HAS_BOTH(ov) || cap == ov) {
+               /* default config -- use full frames */
+               saa_writeb(SAA7134_TASK_CONDITIONS(TASK_A), 0x0d);
+               saa_writeb(SAA7134_TASK_CONDITIONS(TASK_B), 0x0d);
+               saa_writeb(SAA7134_FIELD_HANDLING(TASK_A),  0x02);
+               saa_writeb(SAA7134_FIELD_HANDLING(TASK_B),  0x02);
+               split = 0;
+       } else {
+               /* split fields between tasks */
+               if (V4L2_FIELD_TOP == cap) {
+                       /* odd A, even B, repeat */
+                       saa_writeb(SAA7134_TASK_CONDITIONS(TASK_A), 0x0d);
+                       saa_writeb(SAA7134_TASK_CONDITIONS(TASK_B), 0x0e);
+               } else {
+                       /* odd B, even A, repeat */
+                       saa_writeb(SAA7134_TASK_CONDITIONS(TASK_A), 0x0e);
+                       saa_writeb(SAA7134_TASK_CONDITIONS(TASK_B), 0x0d);
+               }
+               saa_writeb(SAA7134_FIELD_HANDLING(TASK_A),  0x01);
+               saa_writeb(SAA7134_FIELD_HANDLING(TASK_B),  0x01);
+               split = 1;
+       }
+       /* irqs */
+       saa_writeb(SAA7134_REGION_ENABLE, task);
+       saa_writel(SAA7134_IRQ1,          irq);
+       saa_andorl(SAA7134_MAIN_CTRL,
+                  SAA7134_MAIN_CTRL_TE0 |
+                  SAA7134_MAIN_CTRL_TE1 |
+                  SAA7134_MAIN_CTRL_TE2 |
+                  SAA7134_MAIN_CTRL_TE3 |
+                  SAA7134_MAIN_CTRL_TE4 |
+                  SAA7134_MAIN_CTRL_TE5 |
+                  SAA7134_MAIN_CTRL_TE6,
+                  ctrl);
+       dprintk("dmabits: task=0x%02x ctrl=0x%02x irq=0x%x split=%s\n",
+               task, ctrl, irq, split ? "no" : "yes");
+       return 0;
+ }
+ /* ------------------------------------------------------------------ */
+ /* IRQ handler + helpers                                              */
+ static char *irqbits[] = {
+       "DONE_RA0", "DONE_RA1", "DONE_RA2", "DONE_RA3",
+       "AR", "PE", "PWR_ON", "RDCAP", "INTL", "FIDT", "MMC",
+       "TRIG_ERR", "CONF_ERR", "LOAD_ERR",
+       "GPIO16", "GPIO18", "GPIO22", "GPIO23"
+ };
+ #define IRQBITS ARRAY_SIZE(irqbits)
+ static void print_irqstatus(struct saa7134_dev *dev, int loop,
+                           unsigned long report, unsigned long status)
+ {
+       unsigned int i;
+       printk(KERN_DEBUG "%s/irq[%d,%ld]: r=0x%lx s=0x%02lx",
+              dev->name,loop,jiffies,report,status);
+       for (i = 0; i < IRQBITS; i++) {
+               if (!(report & (1 << i)))
+                       continue;
+               printk(" %s",irqbits[i]);
+       }
+       if (report & SAA7134_IRQ_REPORT_DONE_RA0) {
+               printk(" | RA0=%s,%s,%s,%ld",
+                      (status & 0x40) ? "vbi"  : "video",
+                      (status & 0x20) ? "b"    : "a",
+                      (status & 0x10) ? "odd"  : "even",
+                      (status & 0x0f));
+       }
+       printk("\n");
+ }
+ static irqreturn_t saa7134_irq(int irq, void *dev_id)
+ {
+       struct saa7134_dev *dev = (struct saa7134_dev*) dev_id;
+       unsigned long report,status;
+       int loop, handled = 0;
+       if (dev->insuspend)
+               goto out;
+       for (loop = 0; loop < 10; loop++) {
+               report = saa_readl(SAA7134_IRQ_REPORT);
+               status = saa_readl(SAA7134_IRQ_STATUS);
+               /* If dmasound support is active and we get a sound report,
+                * mask out the report and let the saa7134-alsa module deal
+                * with it */
+               if ((report & SAA7134_IRQ_REPORT_DONE_RA3) &&
+                       (dev->dmasound.priv_data != NULL) )
+               {
+                       if (irq_debug > 1)
+                               printk(KERN_DEBUG "%s/irq: preserving DMA sound interrupt\n",
+                                      dev->name);
+                       report &= ~SAA7134_IRQ_REPORT_DONE_RA3;
+               }
+               if (0 == report) {
+                       if (irq_debug > 1)
+                               printk(KERN_DEBUG "%s/irq: no (more) work\n",
+                                      dev->name);
+                       goto out;
+               }
+               handled = 1;
+               saa_writel(SAA7134_IRQ_REPORT,report);
+               if (irq_debug)
+                       print_irqstatus(dev,loop,report,status);
+               if ((report & SAA7134_IRQ_REPORT_RDCAP) ||
+                       (report & SAA7134_IRQ_REPORT_INTL))
+                               saa7134_irq_video_signalchange(dev);
+               if ((report & SAA7134_IRQ_REPORT_DONE_RA0) &&
+                   (status & 0x60) == 0)
+                       saa7134_irq_video_done(dev,status);
+               if ((report & SAA7134_IRQ_REPORT_DONE_RA0) &&
+                   (status & 0x40) == 0x40)
+                       saa7134_irq_vbi_done(dev,status);
+               if ((report & SAA7134_IRQ_REPORT_DONE_RA2) &&
+                   card_has_mpeg(dev))
+                       saa7134_irq_ts_done(dev,status);
+               if (report & SAA7134_IRQ_REPORT_GPIO16) {
+                       switch (dev->has_remote) {
+                               case SAA7134_REMOTE_GPIO:
+                                       if (!dev->remote)
+                                               break;
+                                       if  (dev->remote->mask_keydown & 0x10000) {
+                                               saa7134_input_irq(dev);
+                                       }
+                                       break;
+                               case SAA7134_REMOTE_I2C:
+                                       break;                  /* FIXME: invoke I2C get_key() */
+                               default:                        /* GPIO16 not used by IR remote */
+                                       break;
+                       }
+               }
+               if (report & SAA7134_IRQ_REPORT_GPIO18) {
+                       switch (dev->has_remote) {
+                               case SAA7134_REMOTE_GPIO:
+                                       if (!dev->remote)
+                                               break;
+                                       if ((dev->remote->mask_keydown & 0x40000) ||
+                                           (dev->remote->mask_keyup & 0x40000)) {
+                                               saa7134_input_irq(dev);
+                                       }
+                                       break;
+                               case SAA7134_REMOTE_I2C:
+                                       break;                  /* FIXME: invoke I2C get_key() */
+                               default:                        /* GPIO18 not used by IR remote */
+                                       break;
+                       }
+               }
+       }
+       if (10 == loop) {
+               print_irqstatus(dev,loop,report,status);
+               if (report & SAA7134_IRQ_REPORT_PE) {
+                       /* disable all parity error */
+                       printk(KERN_WARNING "%s/irq: looping -- "
+                              "clearing PE (parity error!) enable bit\n",dev->name);
+                       saa_clearl(SAA7134_IRQ2,SAA7134_IRQ2_INTE_PE);
+               } else if (report & SAA7134_IRQ_REPORT_GPIO16) {
+                       /* disable gpio16 IRQ */
+                       printk(KERN_WARNING "%s/irq: looping -- "
+                              "clearing GPIO16 enable bit\n",dev->name);
+                       saa_clearl(SAA7134_IRQ2, SAA7134_IRQ2_INTE_GPIO16_P);
+                       saa_clearl(SAA7134_IRQ2, SAA7134_IRQ2_INTE_GPIO16_N);
+               } else if (report & SAA7134_IRQ_REPORT_GPIO18) {
+                       /* disable gpio18 IRQs */
+                       printk(KERN_WARNING "%s/irq: looping -- "
+                              "clearing GPIO18 enable bit\n",dev->name);
+                       saa_clearl(SAA7134_IRQ2, SAA7134_IRQ2_INTE_GPIO18_P);
+                       saa_clearl(SAA7134_IRQ2, SAA7134_IRQ2_INTE_GPIO18_N);
+               } else {
+                       /* disable all irqs */
+                       printk(KERN_WARNING "%s/irq: looping -- "
+                              "clearing all enable bits\n",dev->name);
+                       saa_writel(SAA7134_IRQ1,0);
+                       saa_writel(SAA7134_IRQ2,0);
+               }
+       }
+  out:
+       return IRQ_RETVAL(handled);
+ }
+ /* ------------------------------------------------------------------ */
+ /* early init (no i2c, no irq) */
+ static int saa7134_hw_enable1(struct saa7134_dev *dev)
+ {
+       /* RAM FIFO config */
+       saa_writel(SAA7134_FIFO_SIZE, 0x08070503);
+       saa_writel(SAA7134_THRESHOULD, 0x02020202);
+       /* enable audio + video processing */
+       saa_writel(SAA7134_MAIN_CTRL,
+                       SAA7134_MAIN_CTRL_VPLLE |
+                       SAA7134_MAIN_CTRL_APLLE |
+                       SAA7134_MAIN_CTRL_EXOSC |
+                       SAA7134_MAIN_CTRL_EVFE1 |
+                       SAA7134_MAIN_CTRL_EVFE2 |
+                       SAA7134_MAIN_CTRL_ESFE  |
+                       SAA7134_MAIN_CTRL_EBDAC);
+       /*
+       * Initialize OSS _after_ enabling audio clock PLL and audio processing.
+       * OSS initialization writes to registers via the audio DSP; these
+       * writes will fail unless the audio clock has been started.  At worst,
+       * audio will not work.
+       */
+       /* enable peripheral devices */
+       saa_writeb(SAA7134_SPECIAL_MODE, 0x01);
+       /* set vertical line numbering start (vbi needs this) */
+       saa_writeb(SAA7134_SOURCE_TIMING2, 0x20);
+       return 0;
+ }
+ static int saa7134_hwinit1(struct saa7134_dev *dev)
+ {
+       dprintk("hwinit1\n");
+       saa_writel(SAA7134_IRQ1, 0);
+       saa_writel(SAA7134_IRQ2, 0);
+       /* Clear any stale IRQ reports */
+       saa_writel(SAA7134_IRQ_REPORT, saa_readl(SAA7134_IRQ_REPORT));
+       mutex_init(&dev->lock);
+       spin_lock_init(&dev->slock);
+       saa7134_track_gpio(dev,"pre-init");
+       saa7134_video_init1(dev);
+       saa7134_vbi_init1(dev);
+       if (card_has_mpeg(dev))
+               saa7134_ts_init1(dev);
+       saa7134_input_init1(dev);
+       saa7134_hw_enable1(dev);
+       return 0;
+ }
+ /* late init (with i2c + irq) */
+ static int saa7134_hw_enable2(struct saa7134_dev *dev)
+ {
+       unsigned int irq2_mask;
+       /* enable IRQ's */
+       irq2_mask =
+               SAA7134_IRQ2_INTE_DEC3    |
+               SAA7134_IRQ2_INTE_DEC2    |
+               SAA7134_IRQ2_INTE_DEC1    |
+               SAA7134_IRQ2_INTE_DEC0    |
+               SAA7134_IRQ2_INTE_PE      |
+               SAA7134_IRQ2_INTE_AR;
+       if (dev->has_remote == SAA7134_REMOTE_GPIO && dev->remote) {
+               if (dev->remote->mask_keydown & 0x10000)
+                       irq2_mask |= SAA7134_IRQ2_INTE_GPIO16_N;
+               else {          /* Allow enabling both IRQ edge triggers */
+                       if (dev->remote->mask_keydown & 0x40000)
+                               irq2_mask |= SAA7134_IRQ2_INTE_GPIO18_P;
+                       if (dev->remote->mask_keyup & 0x40000)
+                               irq2_mask |= SAA7134_IRQ2_INTE_GPIO18_N;
+               }
+       }
+       if (dev->has_remote == SAA7134_REMOTE_I2C) {
+               request_module("ir-kbd-i2c");
+       }
+       saa_writel(SAA7134_IRQ1, 0);
+       saa_writel(SAA7134_IRQ2, irq2_mask);
+       return 0;
+ }
+ static int saa7134_hwinit2(struct saa7134_dev *dev)
+ {
+       dprintk("hwinit2\n");
+       saa7134_video_init2(dev);
+       saa7134_tvaudio_init2(dev);
+       saa7134_hw_enable2(dev);
+       return 0;
+ }
+ /* shutdown */
+ static int saa7134_hwfini(struct saa7134_dev *dev)
+ {
+       dprintk("hwfini\n");
+       if (card_has_mpeg(dev))
+               saa7134_ts_fini(dev);
+       saa7134_input_fini(dev);
+       saa7134_vbi_fini(dev);
+       saa7134_tvaudio_fini(dev);
+       return 0;
+ }
+ static void __devinit must_configure_manually(int has_eeprom)
+ {
+       unsigned int i,p;
+       if (!has_eeprom)
+               printk(KERN_WARNING
+                      "saa7134: <rant>\n"
+                      "saa7134:  Congratulations!  Your TV card vendor saved a few\n"
+                      "saa7134:  cents for a eeprom, thus your pci board has no\n"
+                      "saa7134:  subsystem ID and I can't identify it automatically\n"
+                      "saa7134: </rant>\n"
+                      "saa7134: I feel better now.  Ok, here are the good news:\n"
+                      "saa7134: You can use the card=<nr> insmod option to specify\n"
+                      "saa7134: which board do you have.  The list:\n");
+       else
+               printk(KERN_WARNING
+                      "saa7134: Board is currently unknown. You might try to use the card=<nr>\n"
+                      "saa7134: insmod option to specify which board do you have, but this is\n"
+                      "saa7134: somewhat risky, as might damage your card. It is better to ask\n"
+                      "saa7134: for support at linux-media@vger.kernel.org.\n"
+                      "saa7134: The supported cards are:\n");
+       for (i = 0; i < saa7134_bcount; i++) {
+               printk(KERN_WARNING "saa7134:   card=%d -> %-40.40s",
+                      i,saa7134_boards[i].name);
+               for (p = 0; saa7134_pci_tbl[p].driver_data; p++) {
+                       if (saa7134_pci_tbl[p].driver_data != i)
+                               continue;
+                       printk(" %04x:%04x",
+                              saa7134_pci_tbl[p].subvendor,
+                              saa7134_pci_tbl[p].subdevice);
+               }
+               printk("\n");
+       }
+ }
+ static struct video_device *vdev_init(struct saa7134_dev *dev,
+                                     struct video_device *template,
+                                     char *type)
+ {
+       struct video_device *vfd;
+       vfd = video_device_alloc();
+       if (NULL == vfd)
+               return NULL;
+       *vfd = *template;
+       vfd->v4l2_dev  = &dev->v4l2_dev;
+       vfd->release = video_device_release;
+       vfd->debug   = video_debug;
+       snprintf(vfd->name, sizeof(vfd->name), "%s %s (%s)",
+                dev->name, type, saa7134_boards[dev->board].name);
+       video_set_drvdata(vfd, dev);
+       return vfd;
+ }
+ static void saa7134_unregister_video(struct saa7134_dev *dev)
+ {
+       if (dev->video_dev) {
+               if (video_is_registered(dev->video_dev))
+                       video_unregister_device(dev->video_dev);
+               else
+                       video_device_release(dev->video_dev);
+               dev->video_dev = NULL;
+       }
+       if (dev->vbi_dev) {
+               if (video_is_registered(dev->vbi_dev))
+                       video_unregister_device(dev->vbi_dev);
+               else
+                       video_device_release(dev->vbi_dev);
+               dev->vbi_dev = NULL;
+       }
+       if (dev->radio_dev) {
+               if (video_is_registered(dev->radio_dev))
+                       video_unregister_device(dev->radio_dev);
+               else
+                       video_device_release(dev->radio_dev);
+               dev->radio_dev = NULL;
+       }
+ }
+ static void mpeg_ops_attach(struct saa7134_mpeg_ops *ops,
+                           struct saa7134_dev *dev)
+ {
+       int err;
+       if (NULL != dev->mops)
+               return;
+       if (saa7134_boards[dev->board].mpeg != ops->type)
+               return;
+       err = ops->init(dev);
+       if (0 != err)
+               return;
+       dev->mops = ops;
+ }
+ static void mpeg_ops_detach(struct saa7134_mpeg_ops *ops,
+                           struct saa7134_dev *dev)
+ {
+       if (NULL == dev->mops)
+               return;
+       if (dev->mops != ops)
+               return;
+       dev->mops->fini(dev);
+       dev->mops = NULL;
+ }
+ static int __devinit saa7134_initdev(struct pci_dev *pci_dev,
+                                    const struct pci_device_id *pci_id)
+ {
+       struct saa7134_dev *dev;
+       struct saa7134_mpeg_ops *mops;
+       int err;
+       if (saa7134_devcount == SAA7134_MAXBOARDS)
+               return -ENOMEM;
+       dev = kzalloc(sizeof(*dev),GFP_KERNEL);
+       if (NULL == dev)
+               return -ENOMEM;
+       err = v4l2_device_register(&pci_dev->dev, &dev->v4l2_dev);
+       if (err)
+               goto fail0;
+       /* pci init */
+       dev->pci = pci_dev;
+       if (pci_enable_device(pci_dev)) {
+               err = -EIO;
+               goto fail1;
+       }
+       dev->nr = saa7134_devcount;
+       sprintf(dev->name,"saa%x[%d]",pci_dev->device,dev->nr);
+       /* pci quirks */
+       if (pci_pci_problems) {
+               if (pci_pci_problems & PCIPCI_TRITON)
+                       printk(KERN_INFO "%s: quirk: PCIPCI_TRITON\n", dev->name);
+               if (pci_pci_problems & PCIPCI_NATOMA)
+                       printk(KERN_INFO "%s: quirk: PCIPCI_NATOMA\n", dev->name);
+               if (pci_pci_problems & PCIPCI_VIAETBF)
+                       printk(KERN_INFO "%s: quirk: PCIPCI_VIAETBF\n", dev->name);
+               if (pci_pci_problems & PCIPCI_VSFX)
+                       printk(KERN_INFO "%s: quirk: PCIPCI_VSFX\n",dev->name);
+ #ifdef PCIPCI_ALIMAGIK
+               if (pci_pci_problems & PCIPCI_ALIMAGIK) {
+                       printk(KERN_INFO "%s: quirk: PCIPCI_ALIMAGIK -- latency fixup\n",
+                              dev->name);
+                       latency = 0x0A;
+               }
+ #endif
+               if (pci_pci_problems & (PCIPCI_FAIL|PCIAGP_FAIL)) {
+                       printk(KERN_INFO "%s: quirk: this driver and your "
+                                       "chipset may not work together"
+                                       " in overlay mode.\n",dev->name);
+                       if (!saa7134_no_overlay) {
+                               printk(KERN_INFO "%s: quirk: overlay "
+                                               "mode will be disabled.\n",
+                                               dev->name);
+                               saa7134_no_overlay = 1;
+                       } else {
+                               printk(KERN_INFO "%s: quirk: overlay "
+                                               "mode will be forced. Use this"
+                                               " option at your own risk.\n",
+                                               dev->name);
+                       }
+               }
+       }
+       if (UNSET != latency) {
+               printk(KERN_INFO "%s: setting pci latency timer to %d\n",
+                      dev->name,latency);
+               pci_write_config_byte(pci_dev, PCI_LATENCY_TIMER, latency);
+       }
+       /* print pci info */
+       dev->pci_rev = pci_dev->revision;
+       pci_read_config_byte(pci_dev, PCI_LATENCY_TIMER,  &dev->pci_lat);
+       printk(KERN_INFO "%s: found at %s, rev: %d, irq: %d, "
+              "latency: %d, mmio: 0x%llx\n", dev->name,
+              pci_name(pci_dev), dev->pci_rev, pci_dev->irq,
+              dev->pci_lat,(unsigned long long)pci_resource_start(pci_dev,0));
+       pci_set_master(pci_dev);
+       if (!pci_dma_supported(pci_dev, DMA_BIT_MASK(32))) {
+               printk("%s: Oops: no 32bit PCI DMA ???\n",dev->name);
+               err = -EIO;
+               goto fail1;
+       }
+       /* board config */
+       dev->board = pci_id->driver_data;
+       if (card[dev->nr] >= 0 &&
+           card[dev->nr] < saa7134_bcount)
+               dev->board = card[dev->nr];
+       if (SAA7134_BOARD_UNKNOWN == dev->board)
+               must_configure_manually(0);
+       else if (SAA7134_BOARD_NOAUTO == dev->board) {
+               must_configure_manually(1);
+               dev->board = SAA7134_BOARD_UNKNOWN;
+       }
+       dev->autodetected = card[dev->nr] != dev->board;
+       dev->tuner_type = saa7134_boards[dev->board].tuner_type;
+       dev->tuner_addr = saa7134_boards[dev->board].tuner_addr;
+       dev->radio_type = saa7134_boards[dev->board].radio_type;
+       dev->radio_addr = saa7134_boards[dev->board].radio_addr;
+       dev->tda9887_conf = saa7134_boards[dev->board].tda9887_conf;
+       if (UNSET != tuner[dev->nr])
+               dev->tuner_type = tuner[dev->nr];
+       printk(KERN_INFO "%s: subsystem: %04x:%04x, board: %s [card=%d,%s]\n",
+               dev->name,pci_dev->subsystem_vendor,
+               pci_dev->subsystem_device,saa7134_boards[dev->board].name,
+               dev->board, dev->autodetected ?
+               "autodetected" : "insmod option");
+       /* get mmio */
+       if (!request_mem_region(pci_resource_start(pci_dev,0),
+                               pci_resource_len(pci_dev,0),
+                               dev->name)) {
+               err = -EBUSY;
+               printk(KERN_ERR "%s: can't get MMIO memory @ 0x%llx\n",
+                      dev->name,(unsigned long long)pci_resource_start(pci_dev,0));
+               goto fail1;
+       }
+       dev->lmmio = ioremap(pci_resource_start(pci_dev, 0),
+                            pci_resource_len(pci_dev, 0));
+       dev->bmmio = (__u8 __iomem *)dev->lmmio;
+       if (NULL == dev->lmmio) {
+               err = -EIO;
+               printk(KERN_ERR "%s: can't ioremap() MMIO memory\n",
+                      dev->name);
+               goto fail2;
+       }
+       /* initialize hardware #1 */
+       saa7134_board_init1(dev);
+       saa7134_hwinit1(dev);
+       /* get irq */
+       err = request_irq(pci_dev->irq, saa7134_irq,
+                         IRQF_SHARED | IRQF_DISABLED, dev->name, dev);
+       if (err < 0) {
+               printk(KERN_ERR "%s: can't get IRQ %d\n",
+                      dev->name,pci_dev->irq);
+               goto fail3;
+       }
+       /* wait a bit, register i2c bus */
+       msleep(100);
+       saa7134_i2c_register(dev);
+       saa7134_board_init2(dev);
+       saa7134_hwinit2(dev);
+       /* load i2c helpers */
+       if (card_is_empress(dev)) {
+               struct v4l2_subdev *sd =
+                       v4l2_i2c_new_subdev(&dev->v4l2_dev, &dev->i2c_adap,
+                               "saa6752hs",
+                               saa7134_boards[dev->board].empress_addr, NULL);
+               if (sd)
+                       sd->grp_id = GRP_EMPRESS;
+       }
+       if (saa7134_boards[dev->board].rds_addr) {
+               struct v4l2_subdev *sd;
+               sd = v4l2_i2c_new_subdev(&dev->v4l2_dev,
+                               &dev->i2c_adap, "saa6588",
+                               0, I2C_ADDRS(saa7134_boards[dev->board].rds_addr));
+               if (sd) {
+                       printk(KERN_INFO "%s: found RDS decoder\n", dev->name);
+                       dev->has_rds = 1;
+               }
+       }
+       v4l2_prio_init(&dev->prio);
+       mutex_lock(&saa7134_devlist_lock);
+       list_for_each_entry(mops, &mops_list, next)
+               mpeg_ops_attach(mops, dev);
+       list_add_tail(&dev->devlist, &saa7134_devlist);
+       mutex_unlock(&saa7134_devlist_lock);
+       /* check for signal */
+       saa7134_irq_video_signalchange(dev);
+       if (TUNER_ABSENT != dev->tuner_type)
+               saa_call_all(dev, core, s_power, 0);
+       /* register v4l devices */
+       if (saa7134_no_overlay > 0)
+               printk(KERN_INFO "%s: Overlay support disabled.\n", dev->name);
+       dev->video_dev = vdev_init(dev,&saa7134_video_template,"video");
+       err = video_register_device(dev->video_dev,VFL_TYPE_GRABBER,
+                                   video_nr[dev->nr]);
+       if (err < 0) {
+               printk(KERN_INFO "%s: can't register video device\n",
+                      dev->name);
+               goto fail4;
+       }
+       printk(KERN_INFO "%s: registered device %s [v4l2]\n",
+              dev->name, video_device_node_name(dev->video_dev));
+       dev->vbi_dev = vdev_init(dev, &saa7134_video_template, "vbi");
+       err = video_register_device(dev->vbi_dev,VFL_TYPE_VBI,
+                                   vbi_nr[dev->nr]);
+       if (err < 0)
+               goto fail4;
+       printk(KERN_INFO "%s: registered device %s\n",
+              dev->name, video_device_node_name(dev->vbi_dev));
+       if (card_has_radio(dev)) {
+               dev->radio_dev = vdev_init(dev,&saa7134_radio_template,"radio");
+               err = video_register_device(dev->radio_dev,VFL_TYPE_RADIO,
+                                           radio_nr[dev->nr]);
+               if (err < 0)
+                       goto fail4;
+               printk(KERN_INFO "%s: registered device %s\n",
+                      dev->name, video_device_node_name(dev->radio_dev));
+       }
+       /* everything worked */
+       saa7134_devcount++;
+       if (saa7134_dmasound_init && !dev->dmasound.priv_data)
+               saa7134_dmasound_init(dev);
+       request_submodules(dev);
+       return 0;
+  fail4:
+       saa7134_unregister_video(dev);
+       saa7134_i2c_unregister(dev);
+       free_irq(pci_dev->irq, dev);
+  fail3:
+       saa7134_hwfini(dev);
+       iounmap(dev->lmmio);
+  fail2:
+       release_mem_region(pci_resource_start(pci_dev,0),
+                          pci_resource_len(pci_dev,0));
+  fail1:
+       v4l2_device_unregister(&dev->v4l2_dev);
+  fail0:
+       kfree(dev);
+       return err;
+ }
+ static void __devexit saa7134_finidev(struct pci_dev *pci_dev)
+ {
+       struct v4l2_device *v4l2_dev = pci_get_drvdata(pci_dev);
+       struct saa7134_dev *dev = container_of(v4l2_dev, struct saa7134_dev, v4l2_dev);
+       struct saa7134_mpeg_ops *mops;
+       flush_request_submodules(dev);
+       /* Release DMA sound modules if present */
+       if (saa7134_dmasound_exit && dev->dmasound.priv_data) {
+               saa7134_dmasound_exit(dev);
+       }
+       /* debugging ... */
+       if (irq_debug) {
+               u32 report = saa_readl(SAA7134_IRQ_REPORT);
+               u32 status = saa_readl(SAA7134_IRQ_STATUS);
+               print_irqstatus(dev,42,report,status);
+       }
+       /* disable peripheral devices */
+       saa_writeb(SAA7134_SPECIAL_MODE,0);
+       /* shutdown hardware */
+       saa_writel(SAA7134_IRQ1,0);
+       saa_writel(SAA7134_IRQ2,0);
+       saa_writel(SAA7134_MAIN_CTRL,0);
+       /* shutdown subsystems */
+       saa7134_hwfini(dev);
+       /* unregister */
+       mutex_lock(&saa7134_devlist_lock);
+       list_del(&dev->devlist);
+       list_for_each_entry(mops, &mops_list, next)
+               mpeg_ops_detach(mops, dev);
+       mutex_unlock(&saa7134_devlist_lock);
+       saa7134_devcount--;
+       saa7134_i2c_unregister(dev);
+       saa7134_unregister_video(dev);
+       /* the DMA sound modules should be unloaded before reaching
+          this, but just in case they are still present... */
+       if (dev->dmasound.priv_data != NULL) {
+               free_irq(pci_dev->irq, &dev->dmasound);
+               dev->dmasound.priv_data = NULL;
+       }
+       /* release resources */
+       free_irq(pci_dev->irq, dev);
+       iounmap(dev->lmmio);
+       release_mem_region(pci_resource_start(pci_dev,0),
+                          pci_resource_len(pci_dev,0));
+       v4l2_device_unregister(&dev->v4l2_dev);
+       /* free memory */
+       kfree(dev);
+ }
+ #ifdef CONFIG_PM
+ /* resends a current buffer in queue after resume */
+ static int saa7134_buffer_requeue(struct saa7134_dev *dev,
+                                 struct saa7134_dmaqueue *q)
+ {
+       struct saa7134_buf *buf, *next;
+       assert_spin_locked(&dev->slock);
+       buf  = q->curr;
+       next = buf;
+       dprintk("buffer_requeue\n");
+       if (!buf)
+               return 0;
+       dprintk("buffer_requeue : resending active buffers \n");
+       if (!list_empty(&q->queue))
+               next = list_entry(q->queue.next, struct saa7134_buf,
+                                         vb.queue);
+       buf->activate(dev, buf, next);
+       return 0;
+ }
+ static int saa7134_suspend(struct pci_dev *pci_dev , pm_message_t state)
+ {
+       struct v4l2_device *v4l2_dev = pci_get_drvdata(pci_dev);
+       struct saa7134_dev *dev = container_of(v4l2_dev, struct saa7134_dev, v4l2_dev);
+       /* disable overlay - apps should enable it explicitly on resume*/
+       dev->ovenable = 0;
+       /* Disable interrupts, DMA, and rest of the chip*/
+       saa_writel(SAA7134_IRQ1, 0);
+       saa_writel(SAA7134_IRQ2, 0);
+       saa_writel(SAA7134_MAIN_CTRL, 0);
+       dev->insuspend = 1;
+       synchronize_irq(pci_dev->irq);
+       /* ACK interrupts once more, just in case,
+               since the IRQ handler won't ack them anymore*/
+       saa_writel(SAA7134_IRQ_REPORT, saa_readl(SAA7134_IRQ_REPORT));
+       /* Disable timeout timers - if we have active buffers, we will
+          fill them on resume*/
+       del_timer(&dev->video_q.timeout);
+       del_timer(&dev->vbi_q.timeout);
+       del_timer(&dev->ts_q.timeout);
+       if (dev->remote)
+               saa7134_ir_stop(dev);
+       pci_save_state(pci_dev);
+       pci_set_power_state(pci_dev, pci_choose_state(pci_dev, state));
+       return 0;
+ }
+ static int saa7134_resume(struct pci_dev *pci_dev)
+ {
+       struct v4l2_device *v4l2_dev = pci_get_drvdata(pci_dev);
+       struct saa7134_dev *dev = container_of(v4l2_dev, struct saa7134_dev, v4l2_dev);
+       unsigned long flags;
+       pci_set_power_state(pci_dev, PCI_D0);
+       pci_restore_state(pci_dev);
+       /* Do things that are done in saa7134_initdev ,
+               except of initializing memory structures.*/
+       saa7134_board_init1(dev);
+       /* saa7134_hwinit1 */
+       if (saa7134_boards[dev->board].video_out)
+               saa7134_videoport_init(dev);
+       if (card_has_mpeg(dev))
+               saa7134_ts_init_hw(dev);
+       if (dev->remote)
+               saa7134_ir_start(dev);
+       saa7134_hw_enable1(dev);
+       msleep(100);
+       saa7134_board_init2(dev);
+       /*saa7134_hwinit2*/
+       saa7134_set_tvnorm_hw(dev);
+       saa7134_tvaudio_setmute(dev);
+       saa7134_tvaudio_setvolume(dev, dev->ctl_volume);
+       saa7134_tvaudio_init(dev);
+       saa7134_enable_i2s(dev);
+       saa7134_hw_enable2(dev);
+       saa7134_irq_video_signalchange(dev);
+       /*resume unfinished buffer(s)*/
+       spin_lock_irqsave(&dev->slock, flags);
+       saa7134_buffer_requeue(dev, &dev->video_q);
+       saa7134_buffer_requeue(dev, &dev->vbi_q);
+       saa7134_buffer_requeue(dev, &dev->ts_q);
+       /* FIXME: Disable DMA audio sound - temporary till proper support
+                 is implemented*/
+       dev->dmasound.dma_running = 0;
+       /* start DMA now*/
+       dev->insuspend = 0;
+       smp_wmb();
+       saa7134_set_dmabits(dev);
+       spin_unlock_irqrestore(&dev->slock, flags);
+       return 0;
+ }
+ #endif
+ /* ----------------------------------------------------------- */
+ int saa7134_ts_register(struct saa7134_mpeg_ops *ops)
+ {
+       struct saa7134_dev *dev;
+       mutex_lock(&saa7134_devlist_lock);
+       list_for_each_entry(dev, &saa7134_devlist, devlist)
+               mpeg_ops_attach(ops, dev);
+       list_add_tail(&ops->next,&mops_list);
+       mutex_unlock(&saa7134_devlist_lock);
+       return 0;
+ }
+ void saa7134_ts_unregister(struct saa7134_mpeg_ops *ops)
+ {
+       struct saa7134_dev *dev;
+       mutex_lock(&saa7134_devlist_lock);
+       list_del(&ops->next);
+       list_for_each_entry(dev, &saa7134_devlist, devlist)
+               mpeg_ops_detach(ops, dev);
+       mutex_unlock(&saa7134_devlist_lock);
+ }
+ EXPORT_SYMBOL(saa7134_ts_register);
+ EXPORT_SYMBOL(saa7134_ts_unregister);
+ /* ----------------------------------------------------------- */
+ static struct pci_driver saa7134_pci_driver = {
+       .name     = "saa7134",
+       .id_table = saa7134_pci_tbl,
+       .probe    = saa7134_initdev,
+       .remove   = __devexit_p(saa7134_finidev),
+ #ifdef CONFIG_PM
+       .suspend  = saa7134_suspend,
+       .resume   = saa7134_resume
+ #endif
+ };
+ static int __init saa7134_init(void)
+ {
+       INIT_LIST_HEAD(&saa7134_devlist);
+       printk(KERN_INFO "saa7130/34: v4l2 driver version %s loaded\n",
+              SAA7134_VERSION);
+       return pci_register_driver(&saa7134_pci_driver);
+ }
+ static void __exit saa7134_fini(void)
+ {
+       pci_unregister_driver(&saa7134_pci_driver);
+ }
+ module_init(saa7134_init);
+ module_exit(saa7134_fini);
+ /* ----------------------------------------------------------- */
+ EXPORT_SYMBOL(saa7134_set_gpio);
+ EXPORT_SYMBOL(saa7134_boards);
+ /* ----------------- for the DMA sound modules --------------- */
+ EXPORT_SYMBOL(saa7134_dmasound_init);
+ EXPORT_SYMBOL(saa7134_dmasound_exit);
+ EXPORT_SYMBOL(saa7134_pgtable_free);
+ EXPORT_SYMBOL(saa7134_pgtable_build);
+ EXPORT_SYMBOL(saa7134_pgtable_alloc);
+ EXPORT_SYMBOL(saa7134_set_dmabits);
+ /* ----------------------------------------------------------- */
+ /*
+  * Local variables:
+  * c-basic-offset: 8
+  * End:
+  */
index 0000000000000000000000000000000000000000,dde361a9194e003ce5cd42a22785dea9975c4458..4df79c6569094311d6dc6cee081b72f62427cd2d
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,590 +1,590 @@@
 -      flush_work_sync(&dev->empress_workqueue);
+ /*
+  *
+  * (c) 2004 Gerd Knorr <kraxel@bytesex.org> [SuSE Labs]
+  *
+  *  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/init.h>
+ #include <linux/list.h>
+ #include <linux/module.h>
+ #include <linux/kernel.h>
+ #include <linux/delay.h>
+ #include "saa7134-reg.h"
+ #include "saa7134.h"
+ #include <media/saa6752hs.h>
+ #include <media/v4l2-common.h>
+ #include <media/v4l2-chip-ident.h>
+ /* ------------------------------------------------------------------ */
+ MODULE_AUTHOR("Gerd Knorr <kraxel@bytesex.org> [SuSE Labs]");
+ MODULE_LICENSE("GPL");
+ static unsigned int empress_nr[] = {[0 ... (SAA7134_MAXBOARDS - 1)] = UNSET };
+ module_param_array(empress_nr, int, NULL, 0444);
+ MODULE_PARM_DESC(empress_nr,"ts device number");
+ static unsigned int debug;
+ module_param(debug, int, 0644);
+ MODULE_PARM_DESC(debug,"enable debug messages");
+ #define dprintk(fmt, arg...)  if (debug)                      \
+       printk(KERN_DEBUG "%s/empress: " fmt, dev->name , ## arg)
+ /* ------------------------------------------------------------------ */
+ static void ts_reset_encoder(struct saa7134_dev* dev)
+ {
+       if (!dev->empress_started)
+               return;
+       saa_writeb(SAA7134_SPECIAL_MODE, 0x00);
+       msleep(10);
+       saa_writeb(SAA7134_SPECIAL_MODE, 0x01);
+       msleep(100);
+       dev->empress_started = 0;
+ }
+ static int ts_init_encoder(struct saa7134_dev* dev)
+ {
+       u32 leading_null_bytes = 0;
+       /* If more cards start to need this, then this
+          should probably be added to the card definitions. */
+       switch (dev->board) {
+       case SAA7134_BOARD_BEHOLD_M6:
+       case SAA7134_BOARD_BEHOLD_M63:
+       case SAA7134_BOARD_BEHOLD_M6_EXTRA:
+               leading_null_bytes = 1;
+               break;
+       }
+       ts_reset_encoder(dev);
+       saa_call_all(dev, core, init, leading_null_bytes);
+       dev->empress_started = 1;
+       return 0;
+ }
+ /* ------------------------------------------------------------------ */
+ static int ts_open(struct file *file)
+ {
+       struct video_device *vdev = video_devdata(file);
+       struct saa7134_dev *dev = video_drvdata(file);
+       int err;
+       dprintk("open dev=%s\n", video_device_node_name(vdev));
+       err = -EBUSY;
+       if (!mutex_trylock(&dev->empress_tsq.vb_lock))
+               return err;
+       if (atomic_read(&dev->empress_users))
+               goto done;
+       /* Unmute audio */
+       saa_writeb(SAA7134_AUDIO_MUTE_CTRL,
+               saa_readb(SAA7134_AUDIO_MUTE_CTRL) & ~(1 << 6));
+       atomic_inc(&dev->empress_users);
+       file->private_data = dev;
+       err = 0;
+ done:
+       mutex_unlock(&dev->empress_tsq.vb_lock);
+       return err;
+ }
+ static int ts_release(struct file *file)
+ {
+       struct saa7134_dev *dev = file->private_data;
+       videobuf_stop(&dev->empress_tsq);
+       videobuf_mmap_free(&dev->empress_tsq);
+       /* stop the encoder */
+       ts_reset_encoder(dev);
+       /* Mute audio */
+       saa_writeb(SAA7134_AUDIO_MUTE_CTRL,
+               saa_readb(SAA7134_AUDIO_MUTE_CTRL) | (1 << 6));
+       atomic_dec(&dev->empress_users);
+       return 0;
+ }
+ static ssize_t
+ ts_read(struct file *file, char __user *data, size_t count, loff_t *ppos)
+ {
+       struct saa7134_dev *dev = file->private_data;
+       if (!dev->empress_started)
+               ts_init_encoder(dev);
+       return videobuf_read_stream(&dev->empress_tsq,
+                                   data, count, ppos, 0,
+                                   file->f_flags & O_NONBLOCK);
+ }
+ static unsigned int
+ ts_poll(struct file *file, struct poll_table_struct *wait)
+ {
+       struct saa7134_dev *dev = file->private_data;
+       return videobuf_poll_stream(file, &dev->empress_tsq, wait);
+ }
+ static int
+ ts_mmap(struct file *file, struct vm_area_struct * vma)
+ {
+       struct saa7134_dev *dev = file->private_data;
+       return videobuf_mmap_mapper(&dev->empress_tsq, vma);
+ }
+ /*
+  * This function is _not_ called directly, but from
+  * video_generic_ioctl (and maybe others).  userspace
+  * copying is done already, arg is a kernel pointer.
+  */
+ static int empress_querycap(struct file *file, void  *priv,
+                                       struct v4l2_capability *cap)
+ {
+       struct saa7134_dev *dev = file->private_data;
+       strcpy(cap->driver, "saa7134");
+       strlcpy(cap->card, saa7134_boards[dev->board].name,
+               sizeof(cap->card));
+       sprintf(cap->bus_info, "PCI:%s", pci_name(dev->pci));
+       cap->capabilities =
+               V4L2_CAP_VIDEO_CAPTURE |
+               V4L2_CAP_READWRITE |
+               V4L2_CAP_STREAMING;
+       return 0;
+ }
+ static int empress_enum_input(struct file *file, void *priv,
+                                       struct v4l2_input *i)
+ {
+       if (i->index != 0)
+               return -EINVAL;
+       i->type = V4L2_INPUT_TYPE_CAMERA;
+       strcpy(i->name, "CCIR656");
+       return 0;
+ }
+ static int empress_g_input(struct file *file, void *priv, unsigned int *i)
+ {
+       *i = 0;
+       return 0;
+ }
+ static int empress_s_input(struct file *file, void *priv, unsigned int i)
+ {
+       if (i != 0)
+               return -EINVAL;
+       return 0;
+ }
+ static int empress_enum_fmt_vid_cap(struct file *file, void  *priv,
+                                       struct v4l2_fmtdesc *f)
+ {
+       if (f->index != 0)
+               return -EINVAL;
+       strlcpy(f->description, "MPEG TS", sizeof(f->description));
+       f->pixelformat = V4L2_PIX_FMT_MPEG;
+       return 0;
+ }
+ static int empress_g_fmt_vid_cap(struct file *file, void *priv,
+                               struct v4l2_format *f)
+ {
+       struct saa7134_dev *dev = file->private_data;
+       struct v4l2_mbus_framefmt mbus_fmt;
+       saa_call_all(dev, video, g_mbus_fmt, &mbus_fmt);
+       v4l2_fill_pix_format(&f->fmt.pix, &mbus_fmt);
+       f->fmt.pix.pixelformat  = V4L2_PIX_FMT_MPEG;
+       f->fmt.pix.sizeimage    = TS_PACKET_SIZE * dev->ts.nr_packets;
+       return 0;
+ }
+ static int empress_s_fmt_vid_cap(struct file *file, void *priv,
+                               struct v4l2_format *f)
+ {
+       struct saa7134_dev *dev = file->private_data;
+       struct v4l2_mbus_framefmt mbus_fmt;
+       v4l2_fill_mbus_format(&mbus_fmt, &f->fmt.pix, V4L2_MBUS_FMT_FIXED);
+       saa_call_all(dev, video, s_mbus_fmt, &mbus_fmt);
+       v4l2_fill_pix_format(&f->fmt.pix, &mbus_fmt);
+       f->fmt.pix.pixelformat  = V4L2_PIX_FMT_MPEG;
+       f->fmt.pix.sizeimage    = TS_PACKET_SIZE * dev->ts.nr_packets;
+       return 0;
+ }
+ static int empress_try_fmt_vid_cap(struct file *file, void *priv,
+                               struct v4l2_format *f)
+ {
+       struct saa7134_dev *dev = file->private_data;
+       f->fmt.pix.pixelformat  = V4L2_PIX_FMT_MPEG;
+       f->fmt.pix.sizeimage    = TS_PACKET_SIZE * dev->ts.nr_packets;
+       return 0;
+ }
+ static int empress_reqbufs(struct file *file, void *priv,
+                                       struct v4l2_requestbuffers *p)
+ {
+       struct saa7134_dev *dev = file->private_data;
+       return videobuf_reqbufs(&dev->empress_tsq, p);
+ }
+ static int empress_querybuf(struct file *file, void *priv,
+                                       struct v4l2_buffer *b)
+ {
+       struct saa7134_dev *dev = file->private_data;
+       return videobuf_querybuf(&dev->empress_tsq, b);
+ }
+ static int empress_qbuf(struct file *file, void *priv, struct v4l2_buffer *b)
+ {
+       struct saa7134_dev *dev = file->private_data;
+       return videobuf_qbuf(&dev->empress_tsq, b);
+ }
+ static int empress_dqbuf(struct file *file, void *priv, struct v4l2_buffer *b)
+ {
+       struct saa7134_dev *dev = file->private_data;
+       return videobuf_dqbuf(&dev->empress_tsq, b,
+                               file->f_flags & O_NONBLOCK);
+ }
+ static int empress_streamon(struct file *file, void *priv,
+                                       enum v4l2_buf_type type)
+ {
+       struct saa7134_dev *dev = file->private_data;
+       return videobuf_streamon(&dev->empress_tsq);
+ }
+ static int empress_streamoff(struct file *file, void *priv,
+                                       enum v4l2_buf_type type)
+ {
+       struct saa7134_dev *dev = file->private_data;
+       return videobuf_streamoff(&dev->empress_tsq);
+ }
+ static int empress_s_ext_ctrls(struct file *file, void *priv,
+                              struct v4l2_ext_controls *ctrls)
+ {
+       struct saa7134_dev *dev = file->private_data;
+       int err;
+       /* count == 0 is abused in saa6752hs.c, so that special
+               case is handled here explicitly. */
+       if (ctrls->count == 0)
+               return 0;
+       if (ctrls->ctrl_class != V4L2_CTRL_CLASS_MPEG)
+               return -EINVAL;
+       err = saa_call_empress(dev, core, s_ext_ctrls, ctrls);
+       ts_init_encoder(dev);
+       return err;
+ }
+ static int empress_g_ext_ctrls(struct file *file, void *priv,
+                              struct v4l2_ext_controls *ctrls)
+ {
+       struct saa7134_dev *dev = file->private_data;
+       if (ctrls->ctrl_class != V4L2_CTRL_CLASS_MPEG)
+               return -EINVAL;
+       return saa_call_empress(dev, core, g_ext_ctrls, ctrls);
+ }
+ static int empress_g_ctrl(struct file *file, void *priv,
+                                       struct v4l2_control *c)
+ {
+       struct saa7134_dev *dev = file->private_data;
+       return saa7134_g_ctrl_internal(dev, NULL, c);
+ }
+ static int empress_s_ctrl(struct file *file, void *priv,
+                                       struct v4l2_control *c)
+ {
+       struct saa7134_dev *dev = file->private_data;
+       return saa7134_s_ctrl_internal(dev, NULL, c);
+ }
+ static int empress_queryctrl(struct file *file, void *priv,
+                                       struct v4l2_queryctrl *c)
+ {
+       /* Must be sorted from low to high control ID! */
+       static const u32 user_ctrls[] = {
+               V4L2_CID_USER_CLASS,
+               V4L2_CID_BRIGHTNESS,
+               V4L2_CID_CONTRAST,
+               V4L2_CID_SATURATION,
+               V4L2_CID_HUE,
+               V4L2_CID_AUDIO_VOLUME,
+               V4L2_CID_AUDIO_MUTE,
+               V4L2_CID_HFLIP,
+               0
+       };
+       /* Must be sorted from low to high control ID! */
+       static const u32 mpeg_ctrls[] = {
+               V4L2_CID_MPEG_CLASS,
+               V4L2_CID_MPEG_STREAM_TYPE,
+               V4L2_CID_MPEG_STREAM_PID_PMT,
+               V4L2_CID_MPEG_STREAM_PID_AUDIO,
+               V4L2_CID_MPEG_STREAM_PID_VIDEO,
+               V4L2_CID_MPEG_STREAM_PID_PCR,
+               V4L2_CID_MPEG_AUDIO_SAMPLING_FREQ,
+               V4L2_CID_MPEG_AUDIO_ENCODING,
+               V4L2_CID_MPEG_AUDIO_L2_BITRATE,
+               V4L2_CID_MPEG_VIDEO_ENCODING,
+               V4L2_CID_MPEG_VIDEO_ASPECT,
+               V4L2_CID_MPEG_VIDEO_BITRATE_MODE,
+               V4L2_CID_MPEG_VIDEO_BITRATE,
+               V4L2_CID_MPEG_VIDEO_BITRATE_PEAK,
+               0
+       };
+       static const u32 *ctrl_classes[] = {
+               user_ctrls,
+               mpeg_ctrls,
+               NULL
+       };
+       struct saa7134_dev *dev = file->private_data;
+       c->id = v4l2_ctrl_next(ctrl_classes, c->id);
+       if (c->id == 0)
+               return -EINVAL;
+       if (c->id == V4L2_CID_USER_CLASS || c->id == V4L2_CID_MPEG_CLASS)
+               return v4l2_ctrl_query_fill(c, 0, 0, 0, 0);
+       if (V4L2_CTRL_ID2CLASS(c->id) != V4L2_CTRL_CLASS_MPEG)
+               return saa7134_queryctrl(file, priv, c);
+       return saa_call_empress(dev, core, queryctrl, c);
+ }
+ static int empress_querymenu(struct file *file, void *priv,
+                                       struct v4l2_querymenu *c)
+ {
+       struct saa7134_dev *dev = file->private_data;
+       if (V4L2_CTRL_ID2CLASS(c->id) != V4L2_CTRL_CLASS_MPEG)
+               return -EINVAL;
+       return saa_call_empress(dev, core, querymenu, c);
+ }
+ static int empress_g_chip_ident(struct file *file, void *fh,
+              struct v4l2_dbg_chip_ident *chip)
+ {
+       struct saa7134_dev *dev = file->private_data;
+       chip->ident = V4L2_IDENT_NONE;
+       chip->revision = 0;
+       if (chip->match.type == V4L2_CHIP_MATCH_I2C_DRIVER &&
+           !strcmp(chip->match.name, "saa6752hs"))
+               return saa_call_empress(dev, core, g_chip_ident, chip);
+       if (chip->match.type == V4L2_CHIP_MATCH_I2C_ADDR)
+               return saa_call_empress(dev, core, g_chip_ident, chip);
+       return -EINVAL;
+ }
+ static int empress_s_std(struct file *file, void *priv, v4l2_std_id *id)
+ {
+       struct saa7134_dev *dev = file->private_data;
+       return saa7134_s_std_internal(dev, NULL, id);
+ }
+ static int empress_g_std(struct file *file, void *priv, v4l2_std_id *id)
+ {
+       struct saa7134_dev *dev = file->private_data;
+       *id = dev->tvnorm->id;
+       return 0;
+ }
+ static const struct v4l2_file_operations ts_fops =
+ {
+       .owner    = THIS_MODULE,
+       .open     = ts_open,
+       .release  = ts_release,
+       .read     = ts_read,
+       .poll     = ts_poll,
+       .mmap     = ts_mmap,
+       .ioctl    = video_ioctl2,
+ };
+ static const struct v4l2_ioctl_ops ts_ioctl_ops = {
+       .vidioc_querycap                = empress_querycap,
+       .vidioc_enum_fmt_vid_cap        = empress_enum_fmt_vid_cap,
+       .vidioc_try_fmt_vid_cap         = empress_try_fmt_vid_cap,
+       .vidioc_s_fmt_vid_cap           = empress_s_fmt_vid_cap,
+       .vidioc_g_fmt_vid_cap           = empress_g_fmt_vid_cap,
+       .vidioc_reqbufs                 = empress_reqbufs,
+       .vidioc_querybuf                = empress_querybuf,
+       .vidioc_qbuf                    = empress_qbuf,
+       .vidioc_dqbuf                   = empress_dqbuf,
+       .vidioc_streamon                = empress_streamon,
+       .vidioc_streamoff               = empress_streamoff,
+       .vidioc_s_ext_ctrls             = empress_s_ext_ctrls,
+       .vidioc_g_ext_ctrls             = empress_g_ext_ctrls,
+       .vidioc_enum_input              = empress_enum_input,
+       .vidioc_g_input                 = empress_g_input,
+       .vidioc_s_input                 = empress_s_input,
+       .vidioc_queryctrl               = empress_queryctrl,
+       .vidioc_querymenu               = empress_querymenu,
+       .vidioc_g_ctrl                  = empress_g_ctrl,
+       .vidioc_s_ctrl                  = empress_s_ctrl,
+       .vidioc_g_chip_ident            = empress_g_chip_ident,
+       .vidioc_s_std                   = empress_s_std,
+       .vidioc_g_std                   = empress_g_std,
+ };
+ /* ----------------------------------------------------------- */
+ static struct video_device saa7134_empress_template = {
+       .name          = "saa7134-empress",
+       .fops          = &ts_fops,
+       .ioctl_ops     = &ts_ioctl_ops,
+       .tvnorms                        = SAA7134_NORMS,
+       .current_norm                   = V4L2_STD_PAL,
+ };
+ static void empress_signal_update(struct work_struct *work)
+ {
+       struct saa7134_dev* dev =
+               container_of(work, struct saa7134_dev, empress_workqueue);
+       if (dev->nosignal) {
+               dprintk("no video signal\n");
+       } else {
+               dprintk("video signal acquired\n");
+       }
+ }
+ static void empress_signal_change(struct saa7134_dev *dev)
+ {
+       schedule_work(&dev->empress_workqueue);
+ }
+ static int empress_init(struct saa7134_dev *dev)
+ {
+       int err;
+       dprintk("%s: %s\n",dev->name,__func__);
+       dev->empress_dev = video_device_alloc();
+       if (NULL == dev->empress_dev)
+               return -ENOMEM;
+       *(dev->empress_dev) = saa7134_empress_template;
+       dev->empress_dev->parent  = &dev->pci->dev;
+       dev->empress_dev->release = video_device_release;
+       snprintf(dev->empress_dev->name, sizeof(dev->empress_dev->name),
+                "%s empress (%s)", dev->name,
+                saa7134_boards[dev->board].name);
+       INIT_WORK(&dev->empress_workqueue, empress_signal_update);
+       video_set_drvdata(dev->empress_dev, dev);
+       err = video_register_device(dev->empress_dev,VFL_TYPE_GRABBER,
+                                   empress_nr[dev->nr]);
+       if (err < 0) {
+               printk(KERN_INFO "%s: can't register video device\n",
+                      dev->name);
+               video_device_release(dev->empress_dev);
+               dev->empress_dev = NULL;
+               return err;
+       }
+       printk(KERN_INFO "%s: registered device %s [mpeg]\n",
+              dev->name, video_device_node_name(dev->empress_dev));
+       videobuf_queue_sg_init(&dev->empress_tsq, &saa7134_ts_qops,
+                           &dev->pci->dev, &dev->slock,
+                           V4L2_BUF_TYPE_VIDEO_CAPTURE,
+                           V4L2_FIELD_ALTERNATE,
+                           sizeof(struct saa7134_buf),
+                           dev, NULL);
+       empress_signal_update(&dev->empress_workqueue);
+       return 0;
+ }
+ static int empress_fini(struct saa7134_dev *dev)
+ {
+       dprintk("%s: %s\n",dev->name,__func__);
+       if (NULL == dev->empress_dev)
+               return 0;
++      flush_work(&dev->empress_workqueue);
+       video_unregister_device(dev->empress_dev);
+       dev->empress_dev = NULL;
+       return 0;
+ }
+ static struct saa7134_mpeg_ops empress_ops = {
+       .type          = SAA7134_MPEG_EMPRESS,
+       .init          = empress_init,
+       .fini          = empress_fini,
+       .signal_change = empress_signal_change,
+ };
+ static int __init empress_register(void)
+ {
+       return saa7134_ts_register(&empress_ops);
+ }
+ static void __exit empress_unregister(void)
+ {
+       saa7134_ts_unregister(&empress_ops);
+ }
+ module_init(empress_register);
+ module_exit(empress_unregister);
+ /* ----------------------------------------------------------- */
+ /*
+  * Local variables:
+  * c-basic-offset: 8
+  * End:
+  */
index 0000000000000000000000000000000000000000,b21ecc8d134d2bffa5794c6e6cc51477074a5ca5..0302669622d6d6e2b409be2221d4d34f4d25a5e8
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,706 +1,706 @@@
 -#include <mach/i2c.h>
+ /*
+  * Copyright (C) 2010 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
+  */
+ #include <linux/module.h>
+ #include <linux/kernel.h>
+ #include <linux/init.h>
+ #include <linux/ctype.h>
+ #include <linux/delay.h>
+ #include <linux/device.h>
+ #include <linux/interrupt.h>
+ #include <linux/platform_device.h>
+ #include <linux/videodev2.h>
+ #include <linux/slab.h>
+ #include <mach/hardware.h>
+ #include <mach/mux.h>
++#include <linux/platform_data/i2c-davinci.h>
+ #include <linux/io.h>
+ #include <media/davinci/vpbe_types.h>
+ #include <media/davinci/vpbe_venc.h>
+ #include <media/davinci/vpss.h>
+ #include <media/v4l2-device.h>
+ #include "vpbe_venc_regs.h"
+ #define MODULE_NAME   VPBE_VENC_SUBDEV_NAME
+ static int debug = 2;
+ module_param(debug, int, 0644);
+ MODULE_PARM_DESC(debug, "Debug level 0-2");
+ struct venc_state {
+       struct v4l2_subdev sd;
+       struct venc_callback *callback;
+       struct venc_platform_data *pdata;
+       struct device *pdev;
+       u32 output;
+       v4l2_std_id std;
+       spinlock_t lock;
+       void __iomem *venc_base;
+       void __iomem *vdaccfg_reg;
+ };
+ static inline struct venc_state *to_state(struct v4l2_subdev *sd)
+ {
+       return container_of(sd, struct venc_state, sd);
+ }
+ static inline u32 venc_read(struct v4l2_subdev *sd, u32 offset)
+ {
+       struct venc_state *venc = to_state(sd);
+       return readl(venc->venc_base + offset);
+ }
+ static inline u32 venc_write(struct v4l2_subdev *sd, u32 offset, u32 val)
+ {
+       struct venc_state *venc = to_state(sd);
+       writel(val, (venc->venc_base + offset));
+       return val;
+ }
+ static inline u32 venc_modify(struct v4l2_subdev *sd, u32 offset,
+                                u32 val, u32 mask)
+ {
+       u32 new_val = (venc_read(sd, offset) & ~mask) | (val & mask);
+       venc_write(sd, offset, new_val);
+       return new_val;
+ }
+ static inline u32 vdaccfg_write(struct v4l2_subdev *sd, u32 val)
+ {
+       struct venc_state *venc = to_state(sd);
+       writel(val, venc->vdaccfg_reg);
+       val = readl(venc->vdaccfg_reg);
+       return val;
+ }
+ #define VDAC_COMPONENT        0x543
+ #define VDAC_S_VIDEO  0x210
+ /* This function sets the dac of the VPBE for various outputs
+  */
+ static int venc_set_dac(struct v4l2_subdev *sd, u32 out_index)
+ {
+       switch (out_index) {
+       case 0:
+               v4l2_dbg(debug, 1, sd, "Setting output to Composite\n");
+               venc_write(sd, VENC_DACSEL, 0);
+               break;
+       case 1:
+               v4l2_dbg(debug, 1, sd, "Setting output to Component\n");
+               venc_write(sd, VENC_DACSEL, VDAC_COMPONENT);
+               break;
+       case 2:
+               v4l2_dbg(debug, 1, sd, "Setting output to S-video\n");
+               venc_write(sd, VENC_DACSEL, VDAC_S_VIDEO);
+               break;
+       default:
+               return -EINVAL;
+       }
+       return 0;
+ }
+ static void venc_enabledigitaloutput(struct v4l2_subdev *sd, int benable)
+ {
+       struct venc_state *venc = to_state(sd);
+       struct venc_platform_data *pdata = venc->pdata;
+       v4l2_dbg(debug, 2, sd, "venc_enabledigitaloutput\n");
+       if (benable) {
+               venc_write(sd, VENC_VMOD, 0);
+               venc_write(sd, VENC_CVBS, 0);
+               venc_write(sd, VENC_LCDOUT, 0);
+               venc_write(sd, VENC_HSPLS, 0);
+               venc_write(sd, VENC_HSTART, 0);
+               venc_write(sd, VENC_HVALID, 0);
+               venc_write(sd, VENC_HINT, 0);
+               venc_write(sd, VENC_VSPLS, 0);
+               venc_write(sd, VENC_VSTART, 0);
+               venc_write(sd, VENC_VVALID, 0);
+               venc_write(sd, VENC_VINT, 0);
+               venc_write(sd, VENC_YCCCTL, 0);
+               venc_write(sd, VENC_DACSEL, 0);
+       } else {
+               venc_write(sd, VENC_VMOD, 0);
+               /* disable VCLK output pin enable */
+               venc_write(sd, VENC_VIDCTL, 0x141);
+               /* Disable output sync pins */
+               venc_write(sd, VENC_SYNCCTL, 0);
+               /* Disable DCLOCK */
+               venc_write(sd, VENC_DCLKCTL, 0);
+               venc_write(sd, VENC_DRGBX1, 0x0000057C);
+               /* Disable LCD output control (accepting default polarity) */
+               venc_write(sd, VENC_LCDOUT, 0);
+               if (pdata->venc_type != VPBE_VERSION_3)
+                       venc_write(sd, VENC_CMPNT, 0x100);
+               venc_write(sd, VENC_HSPLS, 0);
+               venc_write(sd, VENC_HINT, 0);
+               venc_write(sd, VENC_HSTART, 0);
+               venc_write(sd, VENC_HVALID, 0);
+               venc_write(sd, VENC_VSPLS, 0);
+               venc_write(sd, VENC_VINT, 0);
+               venc_write(sd, VENC_VSTART, 0);
+               venc_write(sd, VENC_VVALID, 0);
+               venc_write(sd, VENC_HSDLY, 0);
+               venc_write(sd, VENC_VSDLY, 0);
+               venc_write(sd, VENC_YCCCTL, 0);
+               venc_write(sd, VENC_VSTARTA, 0);
+               /* Set OSD clock and OSD Sync Adavance registers */
+               venc_write(sd, VENC_OSDCLK0, 1);
+               venc_write(sd, VENC_OSDCLK1, 2);
+       }
+ }
+ #define VDAC_CONFIG_SD_V3     0x0E21A6B6
+ #define VDAC_CONFIG_SD_V2     0x081141CF
+ /*
+  * setting NTSC mode
+  */
+ static int venc_set_ntsc(struct v4l2_subdev *sd)
+ {
+       u32 val;
+       struct venc_state *venc = to_state(sd);
+       struct venc_platform_data *pdata = venc->pdata;
+       v4l2_dbg(debug, 2, sd, "venc_set_ntsc\n");
+       /* Setup clock at VPSS & VENC for SD */
+       vpss_enable_clock(VPSS_VENC_CLOCK_SEL, 1);
+       if (pdata->setup_clock(VPBE_ENC_STD, V4L2_STD_525_60) < 0)
+               return -EINVAL;
+       venc_enabledigitaloutput(sd, 0);
+       if (pdata->venc_type == VPBE_VERSION_3) {
+               venc_write(sd, VENC_CLKCTL, 0x01);
+               venc_write(sd, VENC_VIDCTL, 0);
+               val = vdaccfg_write(sd, VDAC_CONFIG_SD_V3);
+       } else if (pdata->venc_type == VPBE_VERSION_2) {
+               venc_write(sd, VENC_CLKCTL, 0x01);
+               venc_write(sd, VENC_VIDCTL, 0);
+               vdaccfg_write(sd, VDAC_CONFIG_SD_V2);
+       } else {
+               /* to set VENC CLK DIV to 1 - final clock is 54 MHz */
+               venc_modify(sd, VENC_VIDCTL, 0, 1 << 1);
+               /* Set REC656 Mode */
+               venc_write(sd, VENC_YCCCTL, 0x1);
+               venc_modify(sd, VENC_VDPRO, 0, VENC_VDPRO_DAFRQ);
+               venc_modify(sd, VENC_VDPRO, 0, VENC_VDPRO_DAUPS);
+       }
+       venc_write(sd, VENC_VMOD, 0);
+       venc_modify(sd, VENC_VMOD, (1 << VENC_VMOD_VIE_SHIFT),
+                       VENC_VMOD_VIE);
+       venc_modify(sd, VENC_VMOD, (0 << VENC_VMOD_VMD), VENC_VMOD_VMD);
+       venc_modify(sd, VENC_VMOD, (0 << VENC_VMOD_TVTYP_SHIFT),
+                       VENC_VMOD_TVTYP);
+       venc_write(sd, VENC_DACTST, 0x0);
+       venc_modify(sd, VENC_VMOD, VENC_VMOD_VENC, VENC_VMOD_VENC);
+       return 0;
+ }
+ /*
+  * setting PAL mode
+  */
+ static int venc_set_pal(struct v4l2_subdev *sd)
+ {
+       struct venc_state *venc = to_state(sd);
+       struct venc_platform_data *pdata = venc->pdata;
+       v4l2_dbg(debug, 2, sd, "venc_set_pal\n");
+       /* Setup clock at VPSS & VENC for SD */
+       vpss_enable_clock(VPSS_VENC_CLOCK_SEL, 1);
+       if (venc->pdata->setup_clock(VPBE_ENC_STD, V4L2_STD_625_50) < 0)
+               return -EINVAL;
+       venc_enabledigitaloutput(sd, 0);
+       if (pdata->venc_type == VPBE_VERSION_3) {
+               venc_write(sd, VENC_CLKCTL, 0x1);
+               venc_write(sd, VENC_VIDCTL, 0);
+               vdaccfg_write(sd, VDAC_CONFIG_SD_V3);
+       } else if (pdata->venc_type == VPBE_VERSION_2) {
+               venc_write(sd, VENC_CLKCTL, 0x1);
+               venc_write(sd, VENC_VIDCTL, 0);
+               vdaccfg_write(sd, VDAC_CONFIG_SD_V2);
+       } else {
+               /* to set VENC CLK DIV to 1 - final clock is 54 MHz */
+               venc_modify(sd, VENC_VIDCTL, 0, 1 << 1);
+               /* Set REC656 Mode */
+               venc_write(sd, VENC_YCCCTL, 0x1);
+       }
+       venc_modify(sd, VENC_SYNCCTL, 1 << VENC_SYNCCTL_OVD_SHIFT,
+                       VENC_SYNCCTL_OVD);
+       venc_write(sd, VENC_VMOD, 0);
+       venc_modify(sd, VENC_VMOD,
+                       (1 << VENC_VMOD_VIE_SHIFT),
+                       VENC_VMOD_VIE);
+       venc_modify(sd, VENC_VMOD,
+                       (0 << VENC_VMOD_VMD), VENC_VMOD_VMD);
+       venc_modify(sd, VENC_VMOD,
+                       (1 << VENC_VMOD_TVTYP_SHIFT),
+                       VENC_VMOD_TVTYP);
+       venc_write(sd, VENC_DACTST, 0x0);
+       venc_modify(sd, VENC_VMOD, VENC_VMOD_VENC, VENC_VMOD_VENC);
+       return 0;
+ }
+ #define VDAC_CONFIG_HD_V2     0x081141EF
+ /*
+  * venc_set_480p59_94
+  *
+  * This function configures the video encoder to EDTV(525p) component setting.
+  */
+ static int venc_set_480p59_94(struct v4l2_subdev *sd)
+ {
+       struct venc_state *venc = to_state(sd);
+       struct venc_platform_data *pdata = venc->pdata;
+       v4l2_dbg(debug, 2, sd, "venc_set_480p59_94\n");
+       if ((pdata->venc_type != VPBE_VERSION_1) &&
+           (pdata->venc_type != VPBE_VERSION_2))
+               return -EINVAL;
+       /* Setup clock at VPSS & VENC for SD */
+       if (pdata->setup_clock(VPBE_ENC_DV_PRESET, V4L2_DV_480P59_94) < 0)
+               return -EINVAL;
+       venc_enabledigitaloutput(sd, 0);
+       if (pdata->venc_type == VPBE_VERSION_2)
+               vdaccfg_write(sd, VDAC_CONFIG_HD_V2);
+       venc_write(sd, VENC_OSDCLK0, 0);
+       venc_write(sd, VENC_OSDCLK1, 1);
+       if (pdata->venc_type == VPBE_VERSION_1) {
+               venc_modify(sd, VENC_VDPRO, VENC_VDPRO_DAFRQ,
+                           VENC_VDPRO_DAFRQ);
+               venc_modify(sd, VENC_VDPRO, VENC_VDPRO_DAUPS,
+                           VENC_VDPRO_DAUPS);
+       }
+       venc_write(sd, VENC_VMOD, 0);
+       venc_modify(sd, VENC_VMOD, (1 << VENC_VMOD_VIE_SHIFT),
+                   VENC_VMOD_VIE);
+       venc_modify(sd, VENC_VMOD, VENC_VMOD_HDMD, VENC_VMOD_HDMD);
+       venc_modify(sd, VENC_VMOD, (HDTV_525P << VENC_VMOD_TVTYP_SHIFT),
+                   VENC_VMOD_TVTYP);
+       venc_modify(sd, VENC_VMOD, VENC_VMOD_VDMD_YCBCR8 <<
+                   VENC_VMOD_VDMD_SHIFT, VENC_VMOD_VDMD);
+       venc_modify(sd, VENC_VMOD, VENC_VMOD_VENC, VENC_VMOD_VENC);
+       return 0;
+ }
+ /*
+  * venc_set_625p
+  *
+  * This function configures the video encoder to HDTV(625p) component setting
+  */
+ static int venc_set_576p50(struct v4l2_subdev *sd)
+ {
+       struct venc_state *venc = to_state(sd);
+       struct venc_platform_data *pdata = venc->pdata;
+       v4l2_dbg(debug, 2, sd, "venc_set_576p50\n");
+       if ((pdata->venc_type != VPBE_VERSION_1) &&
+         (pdata->venc_type != VPBE_VERSION_2))
+               return -EINVAL;
+       /* Setup clock at VPSS & VENC for SD */
+       if (pdata->setup_clock(VPBE_ENC_DV_PRESET, V4L2_DV_576P50) < 0)
+               return -EINVAL;
+       venc_enabledigitaloutput(sd, 0);
+       if (pdata->venc_type == VPBE_VERSION_2)
+               vdaccfg_write(sd, VDAC_CONFIG_HD_V2);
+       venc_write(sd, VENC_OSDCLK0, 0);
+       venc_write(sd, VENC_OSDCLK1, 1);
+       if (pdata->venc_type == VPBE_VERSION_1) {
+               venc_modify(sd, VENC_VDPRO, VENC_VDPRO_DAFRQ,
+                           VENC_VDPRO_DAFRQ);
+               venc_modify(sd, VENC_VDPRO, VENC_VDPRO_DAUPS,
+                           VENC_VDPRO_DAUPS);
+       }
+       venc_write(sd, VENC_VMOD, 0);
+       venc_modify(sd, VENC_VMOD, (1 << VENC_VMOD_VIE_SHIFT),
+                   VENC_VMOD_VIE);
+       venc_modify(sd, VENC_VMOD, VENC_VMOD_HDMD, VENC_VMOD_HDMD);
+       venc_modify(sd, VENC_VMOD, (HDTV_625P << VENC_VMOD_TVTYP_SHIFT),
+                   VENC_VMOD_TVTYP);
+       venc_modify(sd, VENC_VMOD, VENC_VMOD_VDMD_YCBCR8 <<
+                   VENC_VMOD_VDMD_SHIFT, VENC_VMOD_VDMD);
+       venc_modify(sd, VENC_VMOD, VENC_VMOD_VENC, VENC_VMOD_VENC);
+       return 0;
+ }
+ /*
+  * venc_set_720p60_internal - Setup 720p60 in venc for dm365 only
+  */
+ static int venc_set_720p60_internal(struct v4l2_subdev *sd)
+ {
+       struct venc_state *venc = to_state(sd);
+       struct venc_platform_data *pdata = venc->pdata;
+       if (pdata->setup_clock(VPBE_ENC_DV_PRESET, V4L2_DV_720P60) < 0)
+               return -EINVAL;
+       venc_enabledigitaloutput(sd, 0);
+       venc_write(sd, VENC_OSDCLK0, 0);
+       venc_write(sd, VENC_OSDCLK1, 1);
+       venc_write(sd, VENC_VMOD, 0);
+       /* DM365 component HD mode */
+       venc_modify(sd, VENC_VMOD, (1 << VENC_VMOD_VIE_SHIFT),
+           VENC_VMOD_VIE);
+       venc_modify(sd, VENC_VMOD, VENC_VMOD_HDMD, VENC_VMOD_HDMD);
+       venc_modify(sd, VENC_VMOD, (HDTV_720P << VENC_VMOD_TVTYP_SHIFT),
+                   VENC_VMOD_TVTYP);
+       venc_modify(sd, VENC_VMOD, VENC_VMOD_VENC, VENC_VMOD_VENC);
+       venc_write(sd, VENC_XHINTVL, 0);
+       return 0;
+ }
+ /*
+  * venc_set_1080i30_internal - Setup 1080i30 in venc for dm365 only
+  */
+ static int venc_set_1080i30_internal(struct v4l2_subdev *sd)
+ {
+       struct venc_state *venc = to_state(sd);
+       struct venc_platform_data *pdata = venc->pdata;
+       if (pdata->setup_clock(VPBE_ENC_DV_PRESET, V4L2_DV_1080P30) < 0)
+               return -EINVAL;
+       venc_enabledigitaloutput(sd, 0);
+       venc_write(sd, VENC_OSDCLK0, 0);
+       venc_write(sd, VENC_OSDCLK1, 1);
+       venc_write(sd, VENC_VMOD, 0);
+       /* DM365 component HD mode */
+       venc_modify(sd, VENC_VMOD, (1 << VENC_VMOD_VIE_SHIFT),
+                   VENC_VMOD_VIE);
+       venc_modify(sd, VENC_VMOD, VENC_VMOD_HDMD, VENC_VMOD_HDMD);
+       venc_modify(sd, VENC_VMOD, (HDTV_1080I << VENC_VMOD_TVTYP_SHIFT),
+                   VENC_VMOD_TVTYP);
+       venc_modify(sd, VENC_VMOD, VENC_VMOD_VENC, VENC_VMOD_VENC);
+       venc_write(sd, VENC_XHINTVL, 0);
+       return 0;
+ }
+ static int venc_s_std_output(struct v4l2_subdev *sd, v4l2_std_id norm)
+ {
+       v4l2_dbg(debug, 1, sd, "venc_s_std_output\n");
+       if (norm & V4L2_STD_525_60)
+               return venc_set_ntsc(sd);
+       else if (norm & V4L2_STD_625_50)
+               return venc_set_pal(sd);
+       return -EINVAL;
+ }
+ static int venc_s_dv_preset(struct v4l2_subdev *sd,
+                           struct v4l2_dv_preset *dv_preset)
+ {
+       struct venc_state *venc = to_state(sd);
+       int ret;
+       v4l2_dbg(debug, 1, sd, "venc_s_dv_preset\n");
+       if (dv_preset->preset == V4L2_DV_576P50)
+               return venc_set_576p50(sd);
+       else if (dv_preset->preset == V4L2_DV_480P59_94)
+               return venc_set_480p59_94(sd);
+       else if ((dv_preset->preset == V4L2_DV_720P60) &&
+                       (venc->pdata->venc_type == VPBE_VERSION_2)) {
+               /* TBD setup internal 720p mode here */
+               ret = venc_set_720p60_internal(sd);
+               /* for DM365 VPBE, there is DAC inside */
+               vdaccfg_write(sd, VDAC_CONFIG_HD_V2);
+               return ret;
+       } else if ((dv_preset->preset == V4L2_DV_1080I30) &&
+               (venc->pdata->venc_type == VPBE_VERSION_2)) {
+               /* TBD setup internal 1080i mode here */
+               ret = venc_set_1080i30_internal(sd);
+               /* for DM365 VPBE, there is DAC inside */
+               vdaccfg_write(sd, VDAC_CONFIG_HD_V2);
+               return ret;
+       }
+       return -EINVAL;
+ }
+ static int venc_s_routing(struct v4l2_subdev *sd, u32 input, u32 output,
+                         u32 config)
+ {
+       struct venc_state *venc = to_state(sd);
+       int ret;
+       v4l2_dbg(debug, 1, sd, "venc_s_routing\n");
+       ret = venc_set_dac(sd, output);
+       if (!ret)
+               venc->output = output;
+       return ret;
+ }
+ static long venc_ioctl(struct v4l2_subdev *sd,
+                       unsigned int cmd,
+                       void *arg)
+ {
+       u32 val;
+       switch (cmd) {
+       case VENC_GET_FLD:
+               val = venc_read(sd, VENC_VSTAT);
+               *((int *)arg) = ((val & VENC_VSTAT_FIDST) ==
+               VENC_VSTAT_FIDST);
+               break;
+       default:
+               v4l2_err(sd, "Wrong IOCTL cmd\n");
+               break;
+       }
+       return 0;
+ }
+ static const struct v4l2_subdev_core_ops venc_core_ops = {
+       .ioctl      = venc_ioctl,
+ };
+ static const struct v4l2_subdev_video_ops venc_video_ops = {
+       .s_routing = venc_s_routing,
+       .s_std_output = venc_s_std_output,
+       .s_dv_preset = venc_s_dv_preset,
+ };
+ static const struct v4l2_subdev_ops venc_ops = {
+       .core = &venc_core_ops,
+       .video = &venc_video_ops,
+ };
+ static int venc_initialize(struct v4l2_subdev *sd)
+ {
+       struct venc_state *venc = to_state(sd);
+       int ret;
+       /* Set default to output to composite and std to NTSC */
+       venc->output = 0;
+       venc->std = V4L2_STD_525_60;
+       ret = venc_s_routing(sd, 0, venc->output, 0);
+       if (ret < 0) {
+               v4l2_err(sd, "Error setting output during init\n");
+               return -EINVAL;
+       }
+       ret = venc_s_std_output(sd, venc->std);
+       if (ret < 0) {
+               v4l2_err(sd, "Error setting std during init\n");
+               return -EINVAL;
+       }
+       return ret;
+ }
+ static int venc_device_get(struct device *dev, void *data)
+ {
+       struct platform_device *pdev = to_platform_device(dev);
+       struct venc_state **venc = data;
+       if (strcmp(MODULE_NAME, pdev->name) == 0)
+               *venc = platform_get_drvdata(pdev);
+       return 0;
+ }
+ struct v4l2_subdev *venc_sub_dev_init(struct v4l2_device *v4l2_dev,
+               const char *venc_name)
+ {
+       struct venc_state *venc;
+       int err;
+       err = bus_for_each_dev(&platform_bus_type, NULL, &venc,
+                       venc_device_get);
+       if (venc == NULL)
+               return NULL;
+       v4l2_subdev_init(&venc->sd, &venc_ops);
+       strcpy(venc->sd.name, venc_name);
+       if (v4l2_device_register_subdev(v4l2_dev, &venc->sd) < 0) {
+               v4l2_err(v4l2_dev,
+                       "vpbe unable to register venc sub device\n");
+               return NULL;
+       }
+       if (venc_initialize(&venc->sd)) {
+               v4l2_err(v4l2_dev,
+                       "vpbe venc initialization failed\n");
+               return NULL;
+       }
+       return &venc->sd;
+ }
+ EXPORT_SYMBOL(venc_sub_dev_init);
+ static int venc_probe(struct platform_device *pdev)
+ {
+       struct venc_state *venc;
+       struct resource *res;
+       int ret;
+       venc = kzalloc(sizeof(struct venc_state), GFP_KERNEL);
+       if (venc == NULL)
+               return -ENOMEM;
+       venc->pdev = &pdev->dev;
+       venc->pdata = pdev->dev.platform_data;
+       if (NULL == venc->pdata) {
+               dev_err(venc->pdev, "Unable to get platform data for"
+                       " VENC sub device");
+               ret = -ENOENT;
+               goto free_mem;
+       }
+       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       if (!res) {
+               dev_err(venc->pdev,
+                       "Unable to get VENC register address map\n");
+               ret = -ENODEV;
+               goto free_mem;
+       }
+       if (!request_mem_region(res->start, resource_size(res), "venc")) {
+               dev_err(venc->pdev, "Unable to reserve VENC MMIO region\n");
+               ret = -ENODEV;
+               goto free_mem;
+       }
+       venc->venc_base = ioremap_nocache(res->start, resource_size(res));
+       if (!venc->venc_base) {
+               dev_err(venc->pdev, "Unable to map VENC IO space\n");
+               ret = -ENODEV;
+               goto release_venc_mem_region;
+       }
+       if (venc->pdata->venc_type != VPBE_VERSION_1) {
+               res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+               if (!res) {
+                       dev_err(venc->pdev,
+                               "Unable to get VDAC_CONFIG address map\n");
+                       ret = -ENODEV;
+                       goto unmap_venc_io;
+               }
+               if (!request_mem_region(res->start,
+                                       resource_size(res), "venc")) {
+                       dev_err(venc->pdev,
+                               "Unable to reserve VDAC_CONFIG  MMIO region\n");
+                       ret = -ENODEV;
+                       goto unmap_venc_io;
+               }
+               venc->vdaccfg_reg = ioremap_nocache(res->start,
+                                                   resource_size(res));
+               if (!venc->vdaccfg_reg) {
+                       dev_err(venc->pdev,
+                               "Unable to map VDAC_CONFIG IO space\n");
+                       ret = -ENODEV;
+                       goto release_vdaccfg_mem_region;
+               }
+       }
+       spin_lock_init(&venc->lock);
+       platform_set_drvdata(pdev, venc);
+       dev_notice(venc->pdev, "VENC sub device probe success\n");
+       return 0;
+ release_vdaccfg_mem_region:
+       release_mem_region(res->start, resource_size(res));
+ unmap_venc_io:
+       iounmap(venc->venc_base);
+ release_venc_mem_region:
+       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       release_mem_region(res->start, resource_size(res));
+ free_mem:
+       kfree(venc);
+       return ret;
+ }
+ static int venc_remove(struct platform_device *pdev)
+ {
+       struct venc_state *venc = platform_get_drvdata(pdev);
+       struct resource *res;
+       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       iounmap((void *)venc->venc_base);
+       release_mem_region(res->start, resource_size(res));
+       if (venc->pdata->venc_type != VPBE_VERSION_1) {
+               res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+               iounmap((void *)venc->vdaccfg_reg);
+               release_mem_region(res->start, resource_size(res));
+       }
+       kfree(venc);
+       return 0;
+ }
+ static struct platform_driver venc_driver = {
+       .probe          = venc_probe,
+       .remove         = venc_remove,
+       .driver         = {
+               .name   = MODULE_NAME,
+               .owner  = THIS_MODULE,
+       },
+ };
+ module_platform_driver(venc_driver);
+ MODULE_LICENSE("GPL");
+ MODULE_DESCRIPTION("VPBE VENC Driver");
+ MODULE_AUTHOR("Texas Instruments");
index 0000000000000000000000000000000000000000,196e5167005081f5d3c424928a3e71b83274fded..66ac21d466afa29a6975fb0cabf61b1e69b8b356
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,2290 +1,2291 @@@
+ /*
+  * omap_vout.c
+  *
+  * Copyright (C) 2005-2010 Texas Instruments.
+  *
+  * 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.
+  *
+  * Leveraged code from the OMAP2 camera driver
+  * Video-for-Linux (Version 2) camera capture driver for
+  * the OMAP24xx camera controller.
+  *
+  * Author: Andy Lowe (source@mvista.com)
+  *
+  * Copyright (C) 2004 MontaVista Software, Inc.
+  * Copyright (C) 2010 Texas Instruments.
+  *
+  * History:
+  * 20-APR-2006 Khasim         Modified VRFB based Rotation,
+  *                            The image data is always read from 0 degree
+  *                            view and written
+  *                            to the virtual space of desired rotation angle
+  * 4-DEC-2006  Jian           Changed to support better memory management
+  *
+  * 17-Nov-2008 Hardik         Changed driver to use video_ioctl2
+  *
+  * 23-Feb-2010 Vaibhav H      Modified to use new DSS2 interface
+  *
+  */
+ #include <linux/init.h>
+ #include <linux/module.h>
+ #include <linux/vmalloc.h>
+ #include <linux/sched.h>
+ #include <linux/types.h>
+ #include <linux/platform_device.h>
+ #include <linux/irq.h>
+ #include <linux/videodev2.h>
+ #include <linux/dma-mapping.h>
+ #include <linux/slab.h>
+ #include <media/videobuf-dma-contig.h>
+ #include <media/v4l2-device.h>
+ #include <media/v4l2-ioctl.h>
++#include <plat/cpu.h>
+ #include <plat/dma.h>
+ #include <plat/vrfb.h>
+ #include <video/omapdss.h>
+ #include "omap_voutlib.h"
+ #include "omap_voutdef.h"
+ #include "omap_vout_vrfb.h"
+ MODULE_AUTHOR("Texas Instruments");
+ MODULE_DESCRIPTION("OMAP Video for Linux Video out driver");
+ MODULE_LICENSE("GPL");
+ /* Driver Configuration macros */
+ #define VOUT_NAME             "omap_vout"
+ enum omap_vout_channels {
+       OMAP_VIDEO1,
+       OMAP_VIDEO2,
+ };
+ static struct videobuf_queue_ops video_vbq_ops;
+ /* Variables configurable through module params*/
+ static u32 video1_numbuffers = 3;
+ static u32 video2_numbuffers = 3;
+ static u32 video1_bufsize = OMAP_VOUT_MAX_BUF_SIZE;
+ static u32 video2_bufsize = OMAP_VOUT_MAX_BUF_SIZE;
+ static bool vid1_static_vrfb_alloc;
+ static bool vid2_static_vrfb_alloc;
+ static bool debug;
+ /* Module parameters */
+ module_param(video1_numbuffers, uint, S_IRUGO);
+ MODULE_PARM_DESC(video1_numbuffers,
+       "Number of buffers to be allocated at init time for Video1 device.");
+ module_param(video2_numbuffers, uint, S_IRUGO);
+ MODULE_PARM_DESC(video2_numbuffers,
+       "Number of buffers to be allocated at init time for Video2 device.");
+ module_param(video1_bufsize, uint, S_IRUGO);
+ MODULE_PARM_DESC(video1_bufsize,
+       "Size of the buffer to be allocated for video1 device");
+ module_param(video2_bufsize, uint, S_IRUGO);
+ MODULE_PARM_DESC(video2_bufsize,
+       "Size of the buffer to be allocated for video2 device");
+ module_param(vid1_static_vrfb_alloc, bool, S_IRUGO);
+ MODULE_PARM_DESC(vid1_static_vrfb_alloc,
+       "Static allocation of the VRFB buffer for video1 device");
+ module_param(vid2_static_vrfb_alloc, bool, S_IRUGO);
+ MODULE_PARM_DESC(vid2_static_vrfb_alloc,
+       "Static allocation of the VRFB buffer for video2 device");
+ module_param(debug, bool, S_IRUGO);
+ MODULE_PARM_DESC(debug, "Debug level (0-1)");
+ /* list of image formats supported by OMAP2 video pipelines */
+ static const struct v4l2_fmtdesc omap_formats[] = {
+       {
+               /* 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,
+       },
+       {
+               /* Note:  V4L2 defines RGB32 as: RGB-8-8-8-8  we use
+                *  this for RGB24 unpack mode, the last 8 bits are ignored
+                * */
+               .description = "RGB32, le",
+               .pixelformat = V4L2_PIX_FMT_RGB32,
+       },
+       {
+               /* Note:  V4L2 defines RGB24 as: RGB-8-8-8  we use
+                *        this for RGB24 packed mode
+                *
+                */
+               .description = "RGB24, le",
+               .pixelformat = V4L2_PIX_FMT_RGB24,
+       },
+       {
+               .description = "YUYV (YUV 4:2:2), packed",
+               .pixelformat = V4L2_PIX_FMT_YUYV,
+       },
+       {
+               .description = "UYVY, packed",
+               .pixelformat = V4L2_PIX_FMT_UYVY,
+       },
+ };
+ #define NUM_OUTPUT_FORMATS (ARRAY_SIZE(omap_formats))
+ /*
+  * Try format
+  */
+ static int omap_vout_try_format(struct v4l2_pix_format *pix)
+ {
+       int ifmt, bpp = 0;
+       pix->height = clamp(pix->height, (u32)VID_MIN_HEIGHT,
+                                               (u32)VID_MAX_HEIGHT);
+       pix->width = clamp(pix->width, (u32)VID_MIN_WIDTH, (u32)VID_MAX_WIDTH);
+       for (ifmt = 0; ifmt < NUM_OUTPUT_FORMATS; ifmt++) {
+               if (pix->pixelformat == omap_formats[ifmt].pixelformat)
+                       break;
+       }
+       if (ifmt == NUM_OUTPUT_FORMATS)
+               ifmt = 0;
+       pix->pixelformat = omap_formats[ifmt].pixelformat;
+       pix->field = V4L2_FIELD_ANY;
+       pix->priv = 0;
+       switch (pix->pixelformat) {
+       case V4L2_PIX_FMT_YUYV:
+       case V4L2_PIX_FMT_UYVY:
+       default:
+               pix->colorspace = V4L2_COLORSPACE_JPEG;
+               bpp = YUYV_BPP;
+               break;
+       case V4L2_PIX_FMT_RGB565:
+       case V4L2_PIX_FMT_RGB565X:
+               pix->colorspace = V4L2_COLORSPACE_SRGB;
+               bpp = RGB565_BPP;
+               break;
+       case V4L2_PIX_FMT_RGB24:
+               pix->colorspace = V4L2_COLORSPACE_SRGB;
+               bpp = RGB24_BPP;
+               break;
+       case V4L2_PIX_FMT_RGB32:
+       case V4L2_PIX_FMT_BGR32:
+               pix->colorspace = V4L2_COLORSPACE_SRGB;
+               bpp = RGB32_BPP;
+               break;
+       }
+       pix->bytesperline = pix->width * bpp;
+       pix->sizeimage = pix->bytesperline * pix->height;
+       return bpp;
+ }
+ /*
+  * omap_vout_uservirt_to_phys: This inline function is used to convert user
+  * space virtual address to physical address.
+  */
+ static u32 omap_vout_uservirt_to_phys(u32 virtp)
+ {
+       unsigned long physp = 0;
+       struct vm_area_struct *vma;
+       struct mm_struct *mm = current->mm;
+       vma = find_vma(mm, virtp);
+       /* For kernel direct-mapped memory, take the easy way */
+       if (virtp >= PAGE_OFFSET) {
+               physp = virt_to_phys((void *) virtp);
+       } else if (vma && (vma->vm_flags & VM_IO) && vma->vm_pgoff) {
+               /* this will catch, kernel-allocated, mmaped-to-usermode
+                  addresses */
+               physp = (vma->vm_pgoff << PAGE_SHIFT) + (virtp - vma->vm_start);
+       } else {
+               /* otherwise, use get_user_pages() for general userland pages */
+               int res, nr_pages = 1;
+               struct page *pages;
+               down_read(&current->mm->mmap_sem);
+               res = get_user_pages(current, current->mm, virtp, nr_pages, 1,
+                               0, &pages, NULL);
+               up_read(&current->mm->mmap_sem);
+               if (res == nr_pages) {
+                       physp =  __pa(page_address(&pages[0]) +
+                                       (virtp & ~PAGE_MASK));
+               } else {
+                       printk(KERN_WARNING VOUT_NAME
+                                       "get_user_pages failed\n");
+                       return 0;
+               }
+       }
+       return physp;
+ }
+ /*
+  * Free the V4L2 buffers
+  */
+ void omap_vout_free_buffers(struct omap_vout_device *vout)
+ {
+       int i, numbuffers;
+       /* Allocate memory for the buffers */
+       numbuffers = (vout->vid) ?  video2_numbuffers : video1_numbuffers;
+       vout->buffer_size = (vout->vid) ? video2_bufsize : video1_bufsize;
+       for (i = 0; i < numbuffers; i++) {
+               omap_vout_free_buffer(vout->buf_virt_addr[i],
+                               vout->buffer_size);
+               vout->buf_phy_addr[i] = 0;
+               vout->buf_virt_addr[i] = 0;
+       }
+ }
+ /*
+  * Convert V4L2 rotation to DSS rotation
+  *    V4L2 understand 0, 90, 180, 270.
+  *    Convert to 0, 1, 2 and 3 respectively for DSS
+  */
+ static int v4l2_rot_to_dss_rot(int v4l2_rotation,
+                       enum dss_rotation *rotation, bool mirror)
+ {
+       int ret = 0;
+       switch (v4l2_rotation) {
+       case 90:
+               *rotation = dss_rotation_90_degree;
+               break;
+       case 180:
+               *rotation = dss_rotation_180_degree;
+               break;
+       case 270:
+               *rotation = dss_rotation_270_degree;
+               break;
+       case 0:
+               *rotation = dss_rotation_0_degree;
+               break;
+       default:
+               ret = -EINVAL;
+       }
+       return ret;
+ }
+ static int omap_vout_calculate_offset(struct omap_vout_device *vout)
+ {
+       struct omapvideo_info *ovid;
+       struct v4l2_rect *crop = &vout->crop;
+       struct v4l2_pix_format *pix = &vout->pix;
+       int *cropped_offset = &vout->cropped_offset;
+       int ps = 2, line_length = 0;
+       ovid = &vout->vid_info;
+       if (ovid->rotation_type == VOUT_ROT_VRFB) {
+               omap_vout_calculate_vrfb_offset(vout);
+       } else {
+               vout->line_length = line_length = pix->width;
+               if (V4L2_PIX_FMT_YUYV == pix->pixelformat ||
+                       V4L2_PIX_FMT_UYVY == pix->pixelformat)
+                       ps = 2;
+               else if (V4L2_PIX_FMT_RGB32 == pix->pixelformat)
+                       ps = 4;
+               else if (V4L2_PIX_FMT_RGB24 == pix->pixelformat)
+                       ps = 3;
+               vout->ps = ps;
+               *cropped_offset = (line_length * ps) *
+                       crop->top + crop->left * ps;
+       }
+       v4l2_dbg(1, debug, &vout->vid_dev->v4l2_dev, "%s Offset:%x\n",
+                       __func__, vout->cropped_offset);
+       return 0;
+ }
+ /*
+  * Convert V4L2 pixel format to DSS pixel format
+  */
+ static int video_mode_to_dss_mode(struct omap_vout_device *vout)
+ {
+       struct omap_overlay *ovl;
+       struct omapvideo_info *ovid;
+       struct v4l2_pix_format *pix = &vout->pix;
+       enum omap_color_mode mode;
+       ovid = &vout->vid_info;
+       ovl = ovid->overlays[0];
+       switch (pix->pixelformat) {
+       case 0:
+               break;
+       case V4L2_PIX_FMT_YUYV:
+               mode = OMAP_DSS_COLOR_YUV2;
+               break;
+       case V4L2_PIX_FMT_UYVY:
+               mode = OMAP_DSS_COLOR_UYVY;
+               break;
+       case V4L2_PIX_FMT_RGB565:
+               mode = OMAP_DSS_COLOR_RGB16;
+               break;
+       case V4L2_PIX_FMT_RGB24:
+               mode = OMAP_DSS_COLOR_RGB24P;
+               break;
+       case V4L2_PIX_FMT_RGB32:
+               mode = (ovl->id == OMAP_DSS_VIDEO1) ?
+                       OMAP_DSS_COLOR_RGB24U : OMAP_DSS_COLOR_ARGB32;
+               break;
+       case V4L2_PIX_FMT_BGR32:
+               mode = OMAP_DSS_COLOR_RGBX32;
+               break;
+       default:
+               mode = -EINVAL;
+       }
+       return mode;
+ }
+ /*
+  * Setup the overlay
+  */
+ static int omapvid_setup_overlay(struct omap_vout_device *vout,
+               struct omap_overlay *ovl, int posx, int posy, int outw,
+               int outh, u32 addr)
+ {
+       int ret = 0;
+       struct omap_overlay_info info;
+       int cropheight, cropwidth, pixheight, pixwidth;
+       if ((ovl->caps & OMAP_DSS_OVL_CAP_SCALE) == 0 &&
+                       (outw != vout->pix.width || outh != vout->pix.height)) {
+               ret = -EINVAL;
+               goto setup_ovl_err;
+       }
+       vout->dss_mode = video_mode_to_dss_mode(vout);
+       if (vout->dss_mode == -EINVAL) {
+               ret = -EINVAL;
+               goto setup_ovl_err;
+       }
+       /* Setup the input plane parameters according to
+        * rotation value selected.
+        */
+       if (is_rotation_90_or_270(vout)) {
+               cropheight = vout->crop.width;
+               cropwidth = vout->crop.height;
+               pixheight = vout->pix.width;
+               pixwidth = vout->pix.height;
+       } else {
+               cropheight = vout->crop.height;
+               cropwidth = vout->crop.width;
+               pixheight = vout->pix.height;
+               pixwidth = vout->pix.width;
+       }
+       ovl->get_overlay_info(ovl, &info);
+       info.paddr = addr;
+       info.width = cropwidth;
+       info.height = cropheight;
+       info.color_mode = vout->dss_mode;
+       info.mirror = vout->mirror;
+       info.pos_x = posx;
+       info.pos_y = posy;
+       info.out_width = outw;
+       info.out_height = outh;
+       info.global_alpha = vout->win.global_alpha;
+       if (!is_rotation_enabled(vout)) {
+               info.rotation = 0;
+               info.rotation_type = OMAP_DSS_ROT_DMA;
+               info.screen_width = pixwidth;
+       } else {
+               info.rotation = vout->rotation;
+               info.rotation_type = OMAP_DSS_ROT_VRFB;
+               info.screen_width = 2048;
+       }
+       v4l2_dbg(1, debug, &vout->vid_dev->v4l2_dev,
+               "%s enable=%d addr=%x width=%d\n height=%d color_mode=%d\n"
+               "rotation=%d mirror=%d posx=%d posy=%d out_width = %d \n"
+               "out_height=%d rotation_type=%d screen_width=%d\n",
+               __func__, ovl->is_enabled(ovl), info.paddr, info.width, info.height,
+               info.color_mode, info.rotation, info.mirror, info.pos_x,
+               info.pos_y, info.out_width, info.out_height, info.rotation_type,
+               info.screen_width);
+       ret = ovl->set_overlay_info(ovl, &info);
+       if (ret)
+               goto setup_ovl_err;
+       return 0;
+ setup_ovl_err:
+       v4l2_warn(&vout->vid_dev->v4l2_dev, "setup_overlay failed\n");
+       return ret;
+ }
+ /*
+  * Initialize the overlay structure
+  */
+ static int omapvid_init(struct omap_vout_device *vout, u32 addr)
+ {
+       int ret = 0, i;
+       struct v4l2_window *win;
+       struct omap_overlay *ovl;
+       int posx, posy, outw, outh, temp;
+       struct omap_video_timings *timing;
+       struct omapvideo_info *ovid = &vout->vid_info;
+       win = &vout->win;
+       for (i = 0; i < ovid->num_overlays; i++) {
+               ovl = ovid->overlays[i];
+               if (!ovl->manager || !ovl->manager->device)
+                       return -EINVAL;
+               timing = &ovl->manager->device->panel.timings;
+               outw = win->w.width;
+               outh = win->w.height;
+               switch (vout->rotation) {
+               case dss_rotation_90_degree:
+                       /* Invert the height and width for 90
+                        * and 270 degree rotation
+                        */
+                       temp = outw;
+                       outw = outh;
+                       outh = temp;
+                       posy = (timing->y_res - win->w.width) - win->w.left;
+                       posx = win->w.top;
+                       break;
+               case dss_rotation_180_degree:
+                       posx = (timing->x_res - win->w.width) - win->w.left;
+                       posy = (timing->y_res - win->w.height) - win->w.top;
+                       break;
+               case dss_rotation_270_degree:
+                       temp = outw;
+                       outw = outh;
+                       outh = temp;
+                       posy = win->w.left;
+                       posx = (timing->x_res - win->w.height) - win->w.top;
+                       break;
+               default:
+                       posx = win->w.left;
+                       posy = win->w.top;
+                       break;
+               }
+               ret = omapvid_setup_overlay(vout, ovl, posx, posy,
+                               outw, outh, addr);
+               if (ret)
+                       goto omapvid_init_err;
+       }
+       return 0;
+ omapvid_init_err:
+       v4l2_warn(&vout->vid_dev->v4l2_dev, "apply_changes failed\n");
+       return ret;
+ }
+ /*
+  * Apply the changes set the go bit of DSS
+  */
+ static int omapvid_apply_changes(struct omap_vout_device *vout)
+ {
+       int i;
+       struct omap_overlay *ovl;
+       struct omapvideo_info *ovid = &vout->vid_info;
+       for (i = 0; i < ovid->num_overlays; i++) {
+               ovl = ovid->overlays[i];
+               if (!ovl->manager || !ovl->manager->device)
+                       return -EINVAL;
+               ovl->manager->apply(ovl->manager);
+       }
+       return 0;
+ }
+ static int omapvid_handle_interlace_display(struct omap_vout_device *vout,
+               unsigned int irqstatus, struct timeval timevalue)
+ {
+       u32 fid;
+       if (vout->first_int) {
+               vout->first_int = 0;
+               goto err;
+       }
+       if (irqstatus & DISPC_IRQ_EVSYNC_ODD)
+               fid = 1;
+       else if (irqstatus & DISPC_IRQ_EVSYNC_EVEN)
+               fid = 0;
+       else
+               goto err;
+       vout->field_id ^= 1;
+       if (fid != vout->field_id) {
+               if (fid == 0)
+                       vout->field_id = fid;
+       } else if (0 == fid) {
+               if (vout->cur_frm == vout->next_frm)
+                       goto err;
+               vout->cur_frm->ts = timevalue;
+               vout->cur_frm->state = VIDEOBUF_DONE;
+               wake_up_interruptible(&vout->cur_frm->done);
+               vout->cur_frm = vout->next_frm;
+       } else {
+               if (list_empty(&vout->dma_queue) ||
+                               (vout->cur_frm != vout->next_frm))
+                       goto err;
+       }
+       return vout->field_id;
+ err:
+       return 0;
+ }
+ static void omap_vout_isr(void *arg, unsigned int irqstatus)
+ {
+       int ret, fid, mgr_id;
+       u32 addr, irq;
+       struct omap_overlay *ovl;
+       struct timeval timevalue;
+       struct omapvideo_info *ovid;
+       struct omap_dss_device *cur_display;
+       struct omap_vout_device *vout = (struct omap_vout_device *)arg;
+       if (!vout->streaming)
+               return;
+       ovid = &vout->vid_info;
+       ovl = ovid->overlays[0];
+       /* get the display device attached to the overlay */
+       if (!ovl->manager || !ovl->manager->device)
+               return;
+       mgr_id = ovl->manager->id;
+       cur_display = ovl->manager->device;
+       spin_lock(&vout->vbq_lock);
+       do_gettimeofday(&timevalue);
+       switch (cur_display->type) {
+       case OMAP_DISPLAY_TYPE_DSI:
+       case OMAP_DISPLAY_TYPE_DPI:
+               if (mgr_id == OMAP_DSS_CHANNEL_LCD)
+                       irq = DISPC_IRQ_VSYNC;
+               else if (mgr_id == OMAP_DSS_CHANNEL_LCD2)
+                       irq = DISPC_IRQ_VSYNC2;
+               else
+                       goto vout_isr_err;
+               if (!(irqstatus & irq))
+                       goto vout_isr_err;
+               break;
+       case OMAP_DISPLAY_TYPE_VENC:
+               fid = omapvid_handle_interlace_display(vout, irqstatus,
+                               timevalue);
+               if (!fid)
+                       goto vout_isr_err;
+               break;
+       case OMAP_DISPLAY_TYPE_HDMI:
+               if (!(irqstatus & DISPC_IRQ_EVSYNC_EVEN))
+                       goto vout_isr_err;
+               break;
+       default:
+               goto vout_isr_err;
+       }
+       if (!vout->first_int && (vout->cur_frm != vout->next_frm)) {
+               vout->cur_frm->ts = timevalue;
+               vout->cur_frm->state = VIDEOBUF_DONE;
+               wake_up_interruptible(&vout->cur_frm->done);
+               vout->cur_frm = vout->next_frm;
+       }
+       vout->first_int = 0;
+       if (list_empty(&vout->dma_queue))
+               goto vout_isr_err;
+       vout->next_frm = list_entry(vout->dma_queue.next,
+                       struct videobuf_buffer, queue);
+       list_del(&vout->next_frm->queue);
+       vout->next_frm->state = VIDEOBUF_ACTIVE;
+       addr = (unsigned long) vout->queued_buf_addr[vout->next_frm->i]
+               + vout->cropped_offset;
+       /* First save the configuration in ovelray structure */
+       ret = omapvid_init(vout, addr);
+       if (ret)
+               printk(KERN_ERR VOUT_NAME
+                       "failed to set overlay info\n");
+       /* Enable the pipeline and set the Go bit */
+       ret = omapvid_apply_changes(vout);
+       if (ret)
+               printk(KERN_ERR VOUT_NAME "failed to change mode\n");
+ vout_isr_err:
+       spin_unlock(&vout->vbq_lock);
+ }
+ /* Video buffer call backs */
+ /*
+  * Buffer setup function is called by videobuf layer when REQBUF ioctl is
+  * called. This is used to setup buffers and return size and count of
+  * buffers allocated. After the call to this buffer, videobuf layer will
+  * setup buffer queue depending on the size and count of buffers
+  */
+ static int omap_vout_buffer_setup(struct videobuf_queue *q, unsigned int *count,
+                         unsigned int *size)
+ {
+       int startindex = 0, i, j;
+       u32 phy_addr = 0, virt_addr = 0;
+       struct omap_vout_device *vout = q->priv_data;
+       struct omapvideo_info *ovid = &vout->vid_info;
+       int vid_max_buf_size;
+       if (!vout)
+               return -EINVAL;
+       vid_max_buf_size = vout->vid == OMAP_VIDEO1 ? video1_bufsize :
+               video2_bufsize;
+       if (V4L2_BUF_TYPE_VIDEO_OUTPUT != q->type)
+               return -EINVAL;
+       startindex = (vout->vid == OMAP_VIDEO1) ?
+               video1_numbuffers : video2_numbuffers;
+       if (V4L2_MEMORY_MMAP == vout->memory && *count < startindex)
+               *count = startindex;
+       if (ovid->rotation_type == VOUT_ROT_VRFB) {
+               if (omap_vout_vrfb_buffer_setup(vout, count, startindex))
+                       return -ENOMEM;
+       }
+       if (V4L2_MEMORY_MMAP != vout->memory)
+               return 0;
+       /* Now allocated the V4L2 buffers */
+       *size = PAGE_ALIGN(vout->pix.width * vout->pix.height * vout->bpp);
+       startindex = (vout->vid == OMAP_VIDEO1) ?
+               video1_numbuffers : video2_numbuffers;
+       /* Check the size of the buffer */
+       if (*size > vid_max_buf_size) {
+               v4l2_err(&vout->vid_dev->v4l2_dev,
+                               "buffer allocation mismatch [%u] [%u]\n",
+                               *size, vout->buffer_size);
+               return -ENOMEM;
+       }
+       for (i = startindex; i < *count; i++) {
+               vout->buffer_size = *size;
+               virt_addr = omap_vout_alloc_buffer(vout->buffer_size,
+                               &phy_addr);
+               if (!virt_addr) {
+                       if (ovid->rotation_type == VOUT_ROT_NONE) {
+                               break;
+                       } else {
+                               if (!is_rotation_enabled(vout))
+                                       break;
+                       /* Free the VRFB buffers if no space for V4L2 buffers */
+                       for (j = i; j < *count; j++) {
+                               omap_vout_free_buffer(
+                                               vout->smsshado_virt_addr[j],
+                                               vout->smsshado_size);
+                               vout->smsshado_virt_addr[j] = 0;
+                               vout->smsshado_phy_addr[j] = 0;
+                               }
+                       }
+               }
+               vout->buf_virt_addr[i] = virt_addr;
+               vout->buf_phy_addr[i] = phy_addr;
+       }
+       *count = vout->buffer_allocated = i;
+       return 0;
+ }
+ /*
+  * Free the V4L2 buffers additionally allocated than default
+  * number of buffers
+  */
+ static void omap_vout_free_extra_buffers(struct omap_vout_device *vout)
+ {
+       int num_buffers = 0, i;
+       num_buffers = (vout->vid == OMAP_VIDEO1) ?
+               video1_numbuffers : video2_numbuffers;
+       for (i = num_buffers; i < vout->buffer_allocated; i++) {
+               if (vout->buf_virt_addr[i])
+                       omap_vout_free_buffer(vout->buf_virt_addr[i],
+                                       vout->buffer_size);
+               vout->buf_virt_addr[i] = 0;
+               vout->buf_phy_addr[i] = 0;
+       }
+       vout->buffer_allocated = num_buffers;
+ }
+ /*
+  * This function will be called when VIDIOC_QBUF ioctl is called.
+  * It prepare buffers before give out for the display. This function
+  * converts user space virtual address into physical address if userptr memory
+  * exchange mechanism is used. If rotation is enabled, it copies entire
+  * buffer into VRFB memory space before giving it to the DSS.
+  */
+ static int omap_vout_buffer_prepare(struct videobuf_queue *q,
+                       struct videobuf_buffer *vb,
+                       enum v4l2_field field)
+ {
+       struct omap_vout_device *vout = q->priv_data;
+       struct omapvideo_info *ovid = &vout->vid_info;
+       if (VIDEOBUF_NEEDS_INIT == vb->state) {
+               vb->width = vout->pix.width;
+               vb->height = vout->pix.height;
+               vb->size = vb->width * vb->height * vout->bpp;
+               vb->field = field;
+       }
+       vb->state = VIDEOBUF_PREPARED;
+       /* if user pointer memory mechanism is used, get the physical
+        * address of the buffer
+        */
+       if (V4L2_MEMORY_USERPTR == vb->memory) {
+               if (0 == vb->baddr)
+                       return -EINVAL;
+               /* Physical address */
+               vout->queued_buf_addr[vb->i] = (u8 *)
+                       omap_vout_uservirt_to_phys(vb->baddr);
+       } else {
+               u32 addr, dma_addr;
+               unsigned long size;
+               addr = (unsigned long) vout->buf_virt_addr[vb->i];
+               size = (unsigned long) vb->size;
+               dma_addr = dma_map_single(vout->vid_dev->v4l2_dev.dev, (void *) addr,
+                               size, DMA_TO_DEVICE);
+               if (dma_mapping_error(vout->vid_dev->v4l2_dev.dev, dma_addr))
+                       v4l2_err(&vout->vid_dev->v4l2_dev, "dma_map_single failed\n");
+               vout->queued_buf_addr[vb->i] = (u8 *)vout->buf_phy_addr[vb->i];
+       }
+       if (ovid->rotation_type == VOUT_ROT_VRFB)
+               return omap_vout_prepare_vrfb(vout, vb);
+       else
+               return 0;
+ }
+ /*
+  * Buffer queue function will be called from the videobuf layer when _QBUF
+  * ioctl is called. It is used to enqueue buffer, which is ready to be
+  * displayed.
+  */
+ static void omap_vout_buffer_queue(struct videobuf_queue *q,
+                         struct videobuf_buffer *vb)
+ {
+       struct omap_vout_device *vout = q->priv_data;
+       /* Driver is also maintainig a queue. So enqueue buffer in the driver
+        * queue */
+       list_add_tail(&vb->queue, &vout->dma_queue);
+       vb->state = VIDEOBUF_QUEUED;
+ }
+ /*
+  * Buffer release function is called from videobuf layer to release buffer
+  * which are already allocated
+  */
+ static void omap_vout_buffer_release(struct videobuf_queue *q,
+                           struct videobuf_buffer *vb)
+ {
+       struct omap_vout_device *vout = q->priv_data;
+       vb->state = VIDEOBUF_NEEDS_INIT;
+       if (V4L2_MEMORY_MMAP != vout->memory)
+               return;
+ }
+ /*
+  *  File operations
+  */
+ static unsigned int omap_vout_poll(struct file *file,
+                                  struct poll_table_struct *wait)
+ {
+       struct omap_vout_device *vout = file->private_data;
+       struct videobuf_queue *q = &vout->vbq;
+       return videobuf_poll_stream(file, q, wait);
+ }
+ static void omap_vout_vm_open(struct vm_area_struct *vma)
+ {
+       struct omap_vout_device *vout = vma->vm_private_data;
+       v4l2_dbg(1, debug, &vout->vid_dev->v4l2_dev,
+               "vm_open [vma=%08lx-%08lx]\n", vma->vm_start, vma->vm_end);
+       vout->mmap_count++;
+ }
+ static void omap_vout_vm_close(struct vm_area_struct *vma)
+ {
+       struct omap_vout_device *vout = vma->vm_private_data;
+       v4l2_dbg(1, debug, &vout->vid_dev->v4l2_dev,
+               "vm_close [vma=%08lx-%08lx]\n", vma->vm_start, vma->vm_end);
+       vout->mmap_count--;
+ }
+ static struct vm_operations_struct omap_vout_vm_ops = {
+       .open   = omap_vout_vm_open,
+       .close  = omap_vout_vm_close,
+ };
+ static int omap_vout_mmap(struct file *file, struct vm_area_struct *vma)
+ {
+       int i;
+       void *pos;
+       unsigned long start = vma->vm_start;
+       unsigned long size = (vma->vm_end - vma->vm_start);
+       struct omap_vout_device *vout = file->private_data;
+       struct videobuf_queue *q = &vout->vbq;
+       v4l2_dbg(1, debug, &vout->vid_dev->v4l2_dev,
+                       " %s pgoff=0x%lx, start=0x%lx, end=0x%lx\n", __func__,
+                       vma->vm_pgoff, vma->vm_start, vma->vm_end);
+       /* look for the buffer to map */
+       for (i = 0; i < VIDEO_MAX_FRAME; i++) {
+               if (NULL == q->bufs[i])
+                       continue;
+               if (V4L2_MEMORY_MMAP != q->bufs[i]->memory)
+                       continue;
+               if (q->bufs[i]->boff == (vma->vm_pgoff << PAGE_SHIFT))
+                       break;
+       }
+       if (VIDEO_MAX_FRAME == i) {
+               v4l2_dbg(1, debug, &vout->vid_dev->v4l2_dev,
+                               "offset invalid [offset=0x%lx]\n",
+                               (vma->vm_pgoff << PAGE_SHIFT));
+               return -EINVAL;
+       }
+       /* Check the size of the buffer */
+       if (size > vout->buffer_size) {
+               v4l2_err(&vout->vid_dev->v4l2_dev,
+                               "insufficient memory [%lu] [%u]\n",
+                               size, vout->buffer_size);
+               return -ENOMEM;
+       }
+       q->bufs[i]->baddr = vma->vm_start;
+       vma->vm_flags |= VM_RESERVED;
+       vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot);
+       vma->vm_ops = &omap_vout_vm_ops;
+       vma->vm_private_data = (void *) vout;
+       pos = (void *)vout->buf_virt_addr[i];
+       vma->vm_pgoff = virt_to_phys((void *)pos) >> PAGE_SHIFT;
+       while (size > 0) {
+               unsigned long pfn;
+               pfn = virt_to_phys((void *) pos) >> PAGE_SHIFT;
+               if (remap_pfn_range(vma, start, pfn, PAGE_SIZE, PAGE_SHARED))
+                       return -EAGAIN;
+               start += PAGE_SIZE;
+               pos += PAGE_SIZE;
+               size -= PAGE_SIZE;
+       }
+       vout->mmap_count++;
+       v4l2_dbg(1, debug, &vout->vid_dev->v4l2_dev, "Exiting %s\n", __func__);
+       return 0;
+ }
+ static int omap_vout_release(struct file *file)
+ {
+       unsigned int ret, i;
+       struct videobuf_queue *q;
+       struct omapvideo_info *ovid;
+       struct omap_vout_device *vout = file->private_data;
+       v4l2_dbg(1, debug, &vout->vid_dev->v4l2_dev, "Entering %s\n", __func__);
+       ovid = &vout->vid_info;
+       if (!vout)
+               return 0;
+       q = &vout->vbq;
+       /* Disable all the overlay managers connected with this interface */
+       for (i = 0; i < ovid->num_overlays; i++) {
+               struct omap_overlay *ovl = ovid->overlays[i];
+               if (ovl->manager && ovl->manager->device)
+                       ovl->disable(ovl);
+       }
+       /* Turn off the pipeline */
+       ret = omapvid_apply_changes(vout);
+       if (ret)
+               v4l2_warn(&vout->vid_dev->v4l2_dev,
+                               "Unable to apply changes\n");
+       /* Free all buffers */
+       omap_vout_free_extra_buffers(vout);
+       /* Free the VRFB buffers only if they are allocated
+        * during reqbufs.  Don't free if init time allocated
+        */
+       if (ovid->rotation_type == VOUT_ROT_VRFB) {
+               if (!vout->vrfb_static_allocation)
+                       omap_vout_free_vrfb_buffers(vout);
+       }
+       videobuf_mmap_free(q);
+       /* Even if apply changes fails we should continue
+          freeing allocated memory */
+       if (vout->streaming) {
+               u32 mask = 0;
+               mask = DISPC_IRQ_VSYNC | DISPC_IRQ_EVSYNC_EVEN |
+                       DISPC_IRQ_EVSYNC_ODD | DISPC_IRQ_VSYNC2;
+               omap_dispc_unregister_isr(omap_vout_isr, vout, mask);
+               vout->streaming = 0;
+               videobuf_streamoff(q);
+               videobuf_queue_cancel(q);
+       }
+       if (vout->mmap_count != 0)
+               vout->mmap_count = 0;
+       vout->opened -= 1;
+       file->private_data = NULL;
+       if (vout->buffer_allocated)
+               videobuf_mmap_free(q);
+       v4l2_dbg(1, debug, &vout->vid_dev->v4l2_dev, "Exiting %s\n", __func__);
+       return ret;
+ }
+ static int omap_vout_open(struct file *file)
+ {
+       struct videobuf_queue *q;
+       struct omap_vout_device *vout = NULL;
+       vout = video_drvdata(file);
+       v4l2_dbg(1, debug, &vout->vid_dev->v4l2_dev, "Entering %s\n", __func__);
+       if (vout == NULL)
+               return -ENODEV;
+       /* for now, we only support single open */
+       if (vout->opened)
+               return -EBUSY;
+       vout->opened += 1;
+       file->private_data = vout;
+       vout->type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
+       q = &vout->vbq;
+       video_vbq_ops.buf_setup = omap_vout_buffer_setup;
+       video_vbq_ops.buf_prepare = omap_vout_buffer_prepare;
+       video_vbq_ops.buf_release = omap_vout_buffer_release;
+       video_vbq_ops.buf_queue = omap_vout_buffer_queue;
+       spin_lock_init(&vout->vbq_lock);
+       videobuf_queue_dma_contig_init(q, &video_vbq_ops, q->dev,
+                       &vout->vbq_lock, vout->type, V4L2_FIELD_NONE,
+                       sizeof(struct videobuf_buffer), vout, NULL);
+       v4l2_dbg(1, debug, &vout->vid_dev->v4l2_dev, "Exiting %s\n", __func__);
+       return 0;
+ }
+ /*
+  * V4L2 ioctls
+  */
+ static int vidioc_querycap(struct file *file, void *fh,
+               struct v4l2_capability *cap)
+ {
+       struct omap_vout_device *vout = fh;
+       strlcpy(cap->driver, VOUT_NAME, sizeof(cap->driver));
+       strlcpy(cap->card, vout->vfd->name, sizeof(cap->card));
+       cap->bus_info[0] = '\0';
+       cap->capabilities = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_OUTPUT |
+               V4L2_CAP_VIDEO_OUTPUT_OVERLAY;
+       return 0;
+ }
+ static int vidioc_enum_fmt_vid_out(struct file *file, void *fh,
+                       struct v4l2_fmtdesc *fmt)
+ {
+       int index = fmt->index;
+       if (index >= NUM_OUTPUT_FORMATS)
+               return -EINVAL;
+       fmt->flags = omap_formats[index].flags;
+       strlcpy(fmt->description, omap_formats[index].description,
+                       sizeof(fmt->description));
+       fmt->pixelformat = omap_formats[index].pixelformat;
+       return 0;
+ }
+ static int vidioc_g_fmt_vid_out(struct file *file, void *fh,
+                       struct v4l2_format *f)
+ {
+       struct omap_vout_device *vout = fh;
+       f->fmt.pix = vout->pix;
+       return 0;
+ }
+ static int vidioc_try_fmt_vid_out(struct file *file, void *fh,
+                       struct v4l2_format *f)
+ {
+       struct omap_overlay *ovl;
+       struct omapvideo_info *ovid;
+       struct omap_video_timings *timing;
+       struct omap_vout_device *vout = fh;
+       ovid = &vout->vid_info;
+       ovl = ovid->overlays[0];
+       if (!ovl->manager || !ovl->manager->device)
+               return -EINVAL;
+       /* get the display device attached to the overlay */
+       timing = &ovl->manager->device->panel.timings;
+       vout->fbuf.fmt.height = timing->y_res;
+       vout->fbuf.fmt.width = timing->x_res;
+       omap_vout_try_format(&f->fmt.pix);
+       return 0;
+ }
+ static int vidioc_s_fmt_vid_out(struct file *file, void *fh,
+                       struct v4l2_format *f)
+ {
+       int ret, bpp;
+       struct omap_overlay *ovl;
+       struct omapvideo_info *ovid;
+       struct omap_video_timings *timing;
+       struct omap_vout_device *vout = fh;
+       if (vout->streaming)
+               return -EBUSY;
+       mutex_lock(&vout->lock);
+       ovid = &vout->vid_info;
+       ovl = ovid->overlays[0];
+       /* get the display device attached to the overlay */
+       if (!ovl->manager || !ovl->manager->device) {
+               ret = -EINVAL;
+               goto s_fmt_vid_out_exit;
+       }
+       timing = &ovl->manager->device->panel.timings;
+       /* We dont support RGB24-packed mode if vrfb rotation
+        * is enabled*/
+       if ((is_rotation_enabled(vout)) &&
+                       f->fmt.pix.pixelformat == V4L2_PIX_FMT_RGB24) {
+               ret = -EINVAL;
+               goto s_fmt_vid_out_exit;
+       }
+       /* get the framebuffer parameters */
+       if (is_rotation_90_or_270(vout)) {
+               vout->fbuf.fmt.height = timing->x_res;
+               vout->fbuf.fmt.width = timing->y_res;
+       } else {
+               vout->fbuf.fmt.height = timing->y_res;
+               vout->fbuf.fmt.width = timing->x_res;
+       }
+       /* change to samller size is OK */
+       bpp = omap_vout_try_format(&f->fmt.pix);
+       f->fmt.pix.sizeimage = f->fmt.pix.width * f->fmt.pix.height * bpp;
+       /* try & set the new output format */
+       vout->bpp = bpp;
+       vout->pix = f->fmt.pix;
+       vout->vrfb_bpp = 1;
+       /* If YUYV then vrfb bpp is 2, for  others its 1 */
+       if (V4L2_PIX_FMT_YUYV == vout->pix.pixelformat ||
+                       V4L2_PIX_FMT_UYVY == vout->pix.pixelformat)
+               vout->vrfb_bpp = 2;
+       /* set default crop and win */
+       omap_vout_new_format(&vout->pix, &vout->fbuf, &vout->crop, &vout->win);
+       /* Save the changes in the overlay strcuture */
+       ret = omapvid_init(vout, 0);
+       if (ret) {
+               v4l2_err(&vout->vid_dev->v4l2_dev, "failed to change mode\n");
+               goto s_fmt_vid_out_exit;
+       }
+       ret = 0;
+ s_fmt_vid_out_exit:
+       mutex_unlock(&vout->lock);
+       return ret;
+ }
+ static int vidioc_try_fmt_vid_overlay(struct file *file, void *fh,
+                       struct v4l2_format *f)
+ {
+       int ret = 0;
+       struct omap_vout_device *vout = fh;
+       struct omap_overlay *ovl;
+       struct omapvideo_info *ovid;
+       struct v4l2_window *win = &f->fmt.win;
+       ovid = &vout->vid_info;
+       ovl = ovid->overlays[0];
+       ret = omap_vout_try_window(&vout->fbuf, win);
+       if (!ret) {
+               if ((ovl->caps & OMAP_DSS_OVL_CAP_GLOBAL_ALPHA) == 0)
+                       win->global_alpha = 255;
+               else
+                       win->global_alpha = f->fmt.win.global_alpha;
+       }
+       return ret;
+ }
+ static int vidioc_s_fmt_vid_overlay(struct file *file, void *fh,
+                       struct v4l2_format *f)
+ {
+       int ret = 0;
+       struct omap_overlay *ovl;
+       struct omapvideo_info *ovid;
+       struct omap_vout_device *vout = fh;
+       struct v4l2_window *win = &f->fmt.win;
+       mutex_lock(&vout->lock);
+       ovid = &vout->vid_info;
+       ovl = ovid->overlays[0];
+       ret = omap_vout_new_window(&vout->crop, &vout->win, &vout->fbuf, win);
+       if (!ret) {
+               /* Video1 plane does not support global alpha on OMAP3 */
+               if ((ovl->caps & OMAP_DSS_OVL_CAP_GLOBAL_ALPHA) == 0)
+                       vout->win.global_alpha = 255;
+               else
+                       vout->win.global_alpha = f->fmt.win.global_alpha;
+               vout->win.chromakey = f->fmt.win.chromakey;
+       }
+       mutex_unlock(&vout->lock);
+       return ret;
+ }
+ static int vidioc_enum_fmt_vid_overlay(struct file *file, void *fh,
+                       struct v4l2_fmtdesc *fmt)
+ {
+       int index = fmt->index;
+       if (index >= NUM_OUTPUT_FORMATS)
+               return -EINVAL;
+       fmt->flags = omap_formats[index].flags;
+       strlcpy(fmt->description, omap_formats[index].description,
+                       sizeof(fmt->description));
+       fmt->pixelformat = omap_formats[index].pixelformat;
+       return 0;
+ }
+ static int vidioc_g_fmt_vid_overlay(struct file *file, void *fh,
+                       struct v4l2_format *f)
+ {
+       u32 key_value =  0;
+       struct omap_overlay *ovl;
+       struct omapvideo_info *ovid;
+       struct omap_vout_device *vout = fh;
+       struct omap_overlay_manager_info info;
+       struct v4l2_window *win = &f->fmt.win;
+       ovid = &vout->vid_info;
+       ovl = ovid->overlays[0];
+       win->w = vout->win.w;
+       win->field = vout->win.field;
+       win->global_alpha = vout->win.global_alpha;
+       if (ovl->manager && ovl->manager->get_manager_info) {
+               ovl->manager->get_manager_info(ovl->manager, &info);
+               key_value = info.trans_key;
+       }
+       win->chromakey = key_value;
+       return 0;
+ }
+ static int vidioc_cropcap(struct file *file, void *fh,
+               struct v4l2_cropcap *cropcap)
+ {
+       struct omap_vout_device *vout = fh;
+       struct v4l2_pix_format *pix = &vout->pix;
+       if (cropcap->type != V4L2_BUF_TYPE_VIDEO_OUTPUT)
+               return -EINVAL;
+       /* Width and height are always even */
+       cropcap->bounds.width = pix->width & ~1;
+       cropcap->bounds.height = pix->height & ~1;
+       omap_vout_default_crop(&vout->pix, &vout->fbuf, &cropcap->defrect);
+       cropcap->pixelaspect.numerator = 1;
+       cropcap->pixelaspect.denominator = 1;
+       return 0;
+ }
+ static int vidioc_g_crop(struct file *file, void *fh, struct v4l2_crop *crop)
+ {
+       struct omap_vout_device *vout = fh;
+       if (crop->type != V4L2_BUF_TYPE_VIDEO_OUTPUT)
+               return -EINVAL;
+       crop->c = vout->crop;
+       return 0;
+ }
+ static int vidioc_s_crop(struct file *file, void *fh, const struct v4l2_crop *crop)
+ {
+       int ret = -EINVAL;
+       struct omap_vout_device *vout = fh;
+       struct omapvideo_info *ovid;
+       struct omap_overlay *ovl;
+       struct omap_video_timings *timing;
+       if (vout->streaming)
+               return -EBUSY;
+       mutex_lock(&vout->lock);
+       ovid = &vout->vid_info;
+       ovl = ovid->overlays[0];
+       if (!ovl->manager || !ovl->manager->device) {
+               ret = -EINVAL;
+               goto s_crop_err;
+       }
+       /* get the display device attached to the overlay */
+       timing = &ovl->manager->device->panel.timings;
+       if (is_rotation_90_or_270(vout)) {
+               vout->fbuf.fmt.height = timing->x_res;
+               vout->fbuf.fmt.width = timing->y_res;
+       } else {
+               vout->fbuf.fmt.height = timing->y_res;
+               vout->fbuf.fmt.width = timing->x_res;
+       }
+       if (crop->type == V4L2_BUF_TYPE_VIDEO_OUTPUT)
+               ret = omap_vout_new_crop(&vout->pix, &vout->crop, &vout->win,
+                               &vout->fbuf, &crop->c);
+ s_crop_err:
+       mutex_unlock(&vout->lock);
+       return ret;
+ }
+ static int vidioc_queryctrl(struct file *file, void *fh,
+               struct v4l2_queryctrl *ctrl)
+ {
+       int ret = 0;
+       switch (ctrl->id) {
+       case V4L2_CID_ROTATE:
+               ret = v4l2_ctrl_query_fill(ctrl, 0, 270, 90, 0);
+               break;
+       case V4L2_CID_BG_COLOR:
+               ret = v4l2_ctrl_query_fill(ctrl, 0, 0xFFFFFF, 1, 0);
+               break;
+       case V4L2_CID_VFLIP:
+               ret = v4l2_ctrl_query_fill(ctrl, 0, 1, 1, 0);
+               break;
+       default:
+               ctrl->name[0] = '\0';
+               ret = -EINVAL;
+       }
+       return ret;
+ }
+ static int vidioc_g_ctrl(struct file *file, void *fh, struct v4l2_control *ctrl)
+ {
+       int ret = 0;
+       struct omap_vout_device *vout = fh;
+       switch (ctrl->id) {
+       case V4L2_CID_ROTATE:
+               ctrl->value = vout->control[0].value;
+               break;
+       case V4L2_CID_BG_COLOR:
+       {
+               struct omap_overlay_manager_info info;
+               struct omap_overlay *ovl;
+               ovl = vout->vid_info.overlays[0];
+               if (!ovl->manager || !ovl->manager->get_manager_info) {
+                       ret = -EINVAL;
+                       break;
+               }
+               ovl->manager->get_manager_info(ovl->manager, &info);
+               ctrl->value = info.default_color;
+               break;
+       }
+       case V4L2_CID_VFLIP:
+               ctrl->value = vout->control[2].value;
+               break;
+       default:
+               ret = -EINVAL;
+       }
+       return ret;
+ }
+ static int vidioc_s_ctrl(struct file *file, void *fh, struct v4l2_control *a)
+ {
+       int ret = 0;
+       struct omap_vout_device *vout = fh;
+       switch (a->id) {
+       case V4L2_CID_ROTATE:
+       {
+               struct omapvideo_info *ovid;
+               int rotation = a->value;
+               ovid = &vout->vid_info;
+               mutex_lock(&vout->lock);
+               if (rotation && ovid->rotation_type == VOUT_ROT_NONE) {
+                       mutex_unlock(&vout->lock);
+                       ret = -ERANGE;
+                       break;
+               }
+               if (rotation && vout->pix.pixelformat == V4L2_PIX_FMT_RGB24) {
+                       mutex_unlock(&vout->lock);
+                       ret = -EINVAL;
+                       break;
+               }
+               if (v4l2_rot_to_dss_rot(rotation, &vout->rotation,
+                                                       vout->mirror)) {
+                       mutex_unlock(&vout->lock);
+                       ret = -EINVAL;
+                       break;
+               }
+               vout->control[0].value = rotation;
+               mutex_unlock(&vout->lock);
+               break;
+       }
+       case V4L2_CID_BG_COLOR:
+       {
+               struct omap_overlay *ovl;
+               unsigned int  color = a->value;
+               struct omap_overlay_manager_info info;
+               ovl = vout->vid_info.overlays[0];
+               mutex_lock(&vout->lock);
+               if (!ovl->manager || !ovl->manager->get_manager_info) {
+                       mutex_unlock(&vout->lock);
+                       ret = -EINVAL;
+                       break;
+               }
+               ovl->manager->get_manager_info(ovl->manager, &info);
+               info.default_color = color;
+               if (ovl->manager->set_manager_info(ovl->manager, &info)) {
+                       mutex_unlock(&vout->lock);
+                       ret = -EINVAL;
+                       break;
+               }
+               vout->control[1].value = color;
+               mutex_unlock(&vout->lock);
+               break;
+       }
+       case V4L2_CID_VFLIP:
+       {
+               struct omap_overlay *ovl;
+               struct omapvideo_info *ovid;
+               unsigned int  mirror = a->value;
+               ovid = &vout->vid_info;
+               ovl = ovid->overlays[0];
+               mutex_lock(&vout->lock);
+               if (mirror && ovid->rotation_type == VOUT_ROT_NONE) {
+                       mutex_unlock(&vout->lock);
+                       ret = -ERANGE;
+                       break;
+               }
+               if (mirror  && vout->pix.pixelformat == V4L2_PIX_FMT_RGB24) {
+                       mutex_unlock(&vout->lock);
+                       ret = -EINVAL;
+                       break;
+               }
+               vout->mirror = mirror;
+               vout->control[2].value = mirror;
+               mutex_unlock(&vout->lock);
+               break;
+       }
+       default:
+               ret = -EINVAL;
+       }
+       return ret;
+ }
+ static int vidioc_reqbufs(struct file *file, void *fh,
+                       struct v4l2_requestbuffers *req)
+ {
+       int ret = 0;
+       unsigned int i, num_buffers = 0;
+       struct omap_vout_device *vout = fh;
+       struct videobuf_queue *q = &vout->vbq;
+       if ((req->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) || (req->count < 0))
+               return -EINVAL;
+       /* if memory is not mmp or userptr
+          return error */
+       if ((V4L2_MEMORY_MMAP != req->memory) &&
+                       (V4L2_MEMORY_USERPTR != req->memory))
+               return -EINVAL;
+       mutex_lock(&vout->lock);
+       /* Cannot be requested when streaming is on */
+       if (vout->streaming) {
+               ret = -EBUSY;
+               goto reqbuf_err;
+       }
+       /* If buffers are already allocated free them */
+       if (q->bufs[0] && (V4L2_MEMORY_MMAP == q->bufs[0]->memory)) {
+               if (vout->mmap_count) {
+                       ret = -EBUSY;
+                       goto reqbuf_err;
+               }
+               num_buffers = (vout->vid == OMAP_VIDEO1) ?
+                       video1_numbuffers : video2_numbuffers;
+               for (i = num_buffers; i < vout->buffer_allocated; i++) {
+                       omap_vout_free_buffer(vout->buf_virt_addr[i],
+                                       vout->buffer_size);
+                       vout->buf_virt_addr[i] = 0;
+                       vout->buf_phy_addr[i] = 0;
+               }
+               vout->buffer_allocated = num_buffers;
+               videobuf_mmap_free(q);
+       } else if (q->bufs[0] && (V4L2_MEMORY_USERPTR == q->bufs[0]->memory)) {
+               if (vout->buffer_allocated) {
+                       videobuf_mmap_free(q);
+                       for (i = 0; i < vout->buffer_allocated; i++) {
+                               kfree(q->bufs[i]);
+                               q->bufs[i] = NULL;
+                       }
+                       vout->buffer_allocated = 0;
+               }
+       }
+       /*store the memory type in data structure */
+       vout->memory = req->memory;
+       INIT_LIST_HEAD(&vout->dma_queue);
+       /* call videobuf_reqbufs api */
+       ret = videobuf_reqbufs(q, req);
+       if (ret < 0)
+               goto reqbuf_err;
+       vout->buffer_allocated = req->count;
+ reqbuf_err:
+       mutex_unlock(&vout->lock);
+       return ret;
+ }
+ static int vidioc_querybuf(struct file *file, void *fh,
+                       struct v4l2_buffer *b)
+ {
+       struct omap_vout_device *vout = fh;
+       return videobuf_querybuf(&vout->vbq, b);
+ }
+ static int vidioc_qbuf(struct file *file, void *fh,
+                       struct v4l2_buffer *buffer)
+ {
+       struct omap_vout_device *vout = fh;
+       struct videobuf_queue *q = &vout->vbq;
+       if ((V4L2_BUF_TYPE_VIDEO_OUTPUT != buffer->type) ||
+                       (buffer->index >= vout->buffer_allocated) ||
+                       (q->bufs[buffer->index]->memory != buffer->memory)) {
+               return -EINVAL;
+       }
+       if (V4L2_MEMORY_USERPTR == buffer->memory) {
+               if ((buffer->length < vout->pix.sizeimage) ||
+                               (0 == buffer->m.userptr)) {
+                       return -EINVAL;
+               }
+       }
+       if ((is_rotation_enabled(vout)) &&
+                       vout->vrfb_dma_tx.req_status == DMA_CHAN_NOT_ALLOTED) {
+               v4l2_warn(&vout->vid_dev->v4l2_dev,
+                               "DMA Channel not allocated for Rotation\n");
+               return -EINVAL;
+       }
+       return videobuf_qbuf(q, buffer);
+ }
+ static int vidioc_dqbuf(struct file *file, void *fh, struct v4l2_buffer *b)
+ {
+       struct omap_vout_device *vout = fh;
+       struct videobuf_queue *q = &vout->vbq;
+       int ret;
+       u32 addr;
+       unsigned long size;
+       struct videobuf_buffer *vb;
+       vb = q->bufs[b->index];
+       if (!vout->streaming)
+               return -EINVAL;
+       if (file->f_flags & O_NONBLOCK)
+               /* Call videobuf_dqbuf for non blocking mode */
+               ret = videobuf_dqbuf(q, (struct v4l2_buffer *)b, 1);
+       else
+               /* Call videobuf_dqbuf for  blocking mode */
+               ret = videobuf_dqbuf(q, (struct v4l2_buffer *)b, 0);
+       addr = (unsigned long) vout->buf_phy_addr[vb->i];
+       size = (unsigned long) vb->size;
+       dma_unmap_single(vout->vid_dev->v4l2_dev.dev,  addr,
+                               size, DMA_TO_DEVICE);
+       return ret;
+ }
+ static int vidioc_streamon(struct file *file, void *fh, enum v4l2_buf_type i)
+ {
+       int ret = 0, j;
+       u32 addr = 0, mask = 0;
+       struct omap_vout_device *vout = fh;
+       struct videobuf_queue *q = &vout->vbq;
+       struct omapvideo_info *ovid = &vout->vid_info;
+       mutex_lock(&vout->lock);
+       if (vout->streaming) {
+               ret = -EBUSY;
+               goto streamon_err;
+       }
+       ret = videobuf_streamon(q);
+       if (ret)
+               goto streamon_err;
+       if (list_empty(&vout->dma_queue)) {
+               ret = -EIO;
+               goto streamon_err1;
+       }
+       /* Get the next frame from the buffer queue */
+       vout->next_frm = vout->cur_frm = list_entry(vout->dma_queue.next,
+                       struct videobuf_buffer, queue);
+       /* Remove buffer from the buffer queue */
+       list_del(&vout->cur_frm->queue);
+       /* Mark state of the current frame to active */
+       vout->cur_frm->state = VIDEOBUF_ACTIVE;
+       /* Initialize field_id and started member */
+       vout->field_id = 0;
+       /* set flag here. Next QBUF will start DMA */
+       vout->streaming = 1;
+       vout->first_int = 1;
+       if (omap_vout_calculate_offset(vout)) {
+               ret = -EINVAL;
+               goto streamon_err1;
+       }
+       addr = (unsigned long) vout->queued_buf_addr[vout->cur_frm->i]
+               + vout->cropped_offset;
+       mask = DISPC_IRQ_VSYNC | DISPC_IRQ_EVSYNC_EVEN | DISPC_IRQ_EVSYNC_ODD
+               | DISPC_IRQ_VSYNC2;
+       omap_dispc_register_isr(omap_vout_isr, vout, mask);
+       for (j = 0; j < ovid->num_overlays; j++) {
+               struct omap_overlay *ovl = ovid->overlays[j];
+               if (ovl->manager && ovl->manager->device) {
+                       struct omap_overlay_info info;
+                       ovl->get_overlay_info(ovl, &info);
+                       info.paddr = addr;
+                       if (ovl->set_overlay_info(ovl, &info)) {
+                               ret = -EINVAL;
+                               goto streamon_err1;
+                       }
+               }
+       }
+       /* First save the configuration in ovelray structure */
+       ret = omapvid_init(vout, addr);
+       if (ret)
+               v4l2_err(&vout->vid_dev->v4l2_dev,
+                               "failed to set overlay info\n");
+       /* Enable the pipeline and set the Go bit */
+       ret = omapvid_apply_changes(vout);
+       if (ret)
+               v4l2_err(&vout->vid_dev->v4l2_dev, "failed to change mode\n");
+       for (j = 0; j < ovid->num_overlays; j++) {
+               struct omap_overlay *ovl = ovid->overlays[j];
+               if (ovl->manager && ovl->manager->device) {
+                       ret = ovl->enable(ovl);
+                       if (ret)
+                               goto streamon_err1;
+               }
+       }
+       ret = 0;
+ streamon_err1:
+       if (ret)
+               ret = videobuf_streamoff(q);
+ streamon_err:
+       mutex_unlock(&vout->lock);
+       return ret;
+ }
+ static int vidioc_streamoff(struct file *file, void *fh, enum v4l2_buf_type i)
+ {
+       u32 mask = 0;
+       int ret = 0, j;
+       struct omap_vout_device *vout = fh;
+       struct omapvideo_info *ovid = &vout->vid_info;
+       if (!vout->streaming)
+               return -EINVAL;
+       vout->streaming = 0;
+       mask = DISPC_IRQ_VSYNC | DISPC_IRQ_EVSYNC_EVEN | DISPC_IRQ_EVSYNC_ODD
+               | DISPC_IRQ_VSYNC2;
+       omap_dispc_unregister_isr(omap_vout_isr, vout, mask);
+       for (j = 0; j < ovid->num_overlays; j++) {
+               struct omap_overlay *ovl = ovid->overlays[j];
+               if (ovl->manager && ovl->manager->device)
+                       ovl->disable(ovl);
+       }
+       /* Turn of the pipeline */
+       ret = omapvid_apply_changes(vout);
+       if (ret)
+               v4l2_err(&vout->vid_dev->v4l2_dev, "failed to change mode in"
+                               " streamoff\n");
+       INIT_LIST_HEAD(&vout->dma_queue);
+       ret = videobuf_streamoff(&vout->vbq);
+       return ret;
+ }
+ static int vidioc_s_fbuf(struct file *file, void *fh,
+                               const struct v4l2_framebuffer *a)
+ {
+       int enable = 0;
+       struct omap_overlay *ovl;
+       struct omapvideo_info *ovid;
+       struct omap_vout_device *vout = fh;
+       struct omap_overlay_manager_info info;
+       enum omap_dss_trans_key_type key_type = OMAP_DSS_COLOR_KEY_GFX_DST;
+       ovid = &vout->vid_info;
+       ovl = ovid->overlays[0];
+       /* OMAP DSS doesn't support Source and Destination color
+          key together */
+       if ((a->flags & V4L2_FBUF_FLAG_SRC_CHROMAKEY) &&
+                       (a->flags & V4L2_FBUF_FLAG_CHROMAKEY))
+               return -EINVAL;
+       /* OMAP DSS Doesn't support the Destination color key
+          and alpha blending together */
+       if ((a->flags & V4L2_FBUF_FLAG_CHROMAKEY) &&
+                       (a->flags & V4L2_FBUF_FLAG_LOCAL_ALPHA))
+               return -EINVAL;
+       if ((a->flags & V4L2_FBUF_FLAG_SRC_CHROMAKEY)) {
+               vout->fbuf.flags |= V4L2_FBUF_FLAG_SRC_CHROMAKEY;
+               key_type =  OMAP_DSS_COLOR_KEY_VID_SRC;
+       } else
+               vout->fbuf.flags &= ~V4L2_FBUF_FLAG_SRC_CHROMAKEY;
+       if ((a->flags & V4L2_FBUF_FLAG_CHROMAKEY)) {
+               vout->fbuf.flags |= V4L2_FBUF_FLAG_CHROMAKEY;
+               key_type =  OMAP_DSS_COLOR_KEY_GFX_DST;
+       } else
+               vout->fbuf.flags &=  ~V4L2_FBUF_FLAG_CHROMAKEY;
+       if (a->flags & (V4L2_FBUF_FLAG_CHROMAKEY |
+                               V4L2_FBUF_FLAG_SRC_CHROMAKEY))
+               enable = 1;
+       else
+               enable = 0;
+       if (ovl->manager && ovl->manager->get_manager_info &&
+                       ovl->manager->set_manager_info) {
+               ovl->manager->get_manager_info(ovl->manager, &info);
+               info.trans_enabled = enable;
+               info.trans_key_type = key_type;
+               info.trans_key = vout->win.chromakey;
+               if (ovl->manager->set_manager_info(ovl->manager, &info))
+                       return -EINVAL;
+       }
+       if (a->flags & V4L2_FBUF_FLAG_LOCAL_ALPHA) {
+               vout->fbuf.flags |= V4L2_FBUF_FLAG_LOCAL_ALPHA;
+               enable = 1;
+       } else {
+               vout->fbuf.flags &= ~V4L2_FBUF_FLAG_LOCAL_ALPHA;
+               enable = 0;
+       }
+       if (ovl->manager && ovl->manager->get_manager_info &&
+                       ovl->manager->set_manager_info) {
+               ovl->manager->get_manager_info(ovl->manager, &info);
+               /* enable this only if there is no zorder cap */
+               if ((ovl->caps & OMAP_DSS_OVL_CAP_ZORDER) == 0)
+                       info.partial_alpha_enabled = enable;
+               if (ovl->manager->set_manager_info(ovl->manager, &info))
+                       return -EINVAL;
+       }
+       return 0;
+ }
+ static int vidioc_g_fbuf(struct file *file, void *fh,
+               struct v4l2_framebuffer *a)
+ {
+       struct omap_overlay *ovl;
+       struct omapvideo_info *ovid;
+       struct omap_vout_device *vout = fh;
+       struct omap_overlay_manager_info info;
+       ovid = &vout->vid_info;
+       ovl = ovid->overlays[0];
+       /* The video overlay must stay within the framebuffer and can't be
+          positioned independently. */
+       a->flags = V4L2_FBUF_FLAG_OVERLAY;
+       a->capability = V4L2_FBUF_CAP_LOCAL_ALPHA | V4L2_FBUF_CAP_CHROMAKEY
+               | V4L2_FBUF_CAP_SRC_CHROMAKEY;
+       if (ovl->manager && ovl->manager->get_manager_info) {
+               ovl->manager->get_manager_info(ovl->manager, &info);
+               if (info.trans_key_type == OMAP_DSS_COLOR_KEY_VID_SRC)
+                       a->flags |= V4L2_FBUF_FLAG_SRC_CHROMAKEY;
+               if (info.trans_key_type == OMAP_DSS_COLOR_KEY_GFX_DST)
+                       a->flags |= V4L2_FBUF_FLAG_CHROMAKEY;
+       }
+       if (ovl->manager && ovl->manager->get_manager_info) {
+               ovl->manager->get_manager_info(ovl->manager, &info);
+               if (info.partial_alpha_enabled)
+                       a->flags |= V4L2_FBUF_FLAG_LOCAL_ALPHA;
+       }
+       return 0;
+ }
+ static const struct v4l2_ioctl_ops vout_ioctl_ops = {
+       .vidioc_querycap                        = vidioc_querycap,
+       .vidioc_enum_fmt_vid_out                = vidioc_enum_fmt_vid_out,
+       .vidioc_g_fmt_vid_out                   = vidioc_g_fmt_vid_out,
+       .vidioc_try_fmt_vid_out                 = vidioc_try_fmt_vid_out,
+       .vidioc_s_fmt_vid_out                   = vidioc_s_fmt_vid_out,
+       .vidioc_queryctrl                       = vidioc_queryctrl,
+       .vidioc_g_ctrl                          = vidioc_g_ctrl,
+       .vidioc_s_fbuf                          = vidioc_s_fbuf,
+       .vidioc_g_fbuf                          = vidioc_g_fbuf,
+       .vidioc_s_ctrl                          = vidioc_s_ctrl,
+       .vidioc_try_fmt_vid_overlay             = vidioc_try_fmt_vid_overlay,
+       .vidioc_s_fmt_vid_overlay               = vidioc_s_fmt_vid_overlay,
+       .vidioc_enum_fmt_vid_overlay            = vidioc_enum_fmt_vid_overlay,
+       .vidioc_g_fmt_vid_overlay               = vidioc_g_fmt_vid_overlay,
+       .vidioc_cropcap                         = vidioc_cropcap,
+       .vidioc_g_crop                          = vidioc_g_crop,
+       .vidioc_s_crop                          = vidioc_s_crop,
+       .vidioc_reqbufs                         = vidioc_reqbufs,
+       .vidioc_querybuf                        = vidioc_querybuf,
+       .vidioc_qbuf                            = vidioc_qbuf,
+       .vidioc_dqbuf                           = vidioc_dqbuf,
+       .vidioc_streamon                        = vidioc_streamon,
+       .vidioc_streamoff                       = vidioc_streamoff,
+ };
+ static const struct v4l2_file_operations omap_vout_fops = {
+       .owner          = THIS_MODULE,
+       .poll           = omap_vout_poll,
+       .unlocked_ioctl = video_ioctl2,
+       .mmap           = omap_vout_mmap,
+       .open           = omap_vout_open,
+       .release        = omap_vout_release,
+ };
+ /* Init functions used during driver initialization */
+ /* Initial setup of video_data */
+ static int __init omap_vout_setup_video_data(struct omap_vout_device *vout)
+ {
+       struct video_device *vfd;
+       struct v4l2_pix_format *pix;
+       struct v4l2_control *control;
+       struct omap_dss_device *display =
+               vout->vid_info.overlays[0]->manager->device;
+       /* set the default pix */
+       pix = &vout->pix;
+       /* Set the default picture of QVGA  */
+       pix->width = QQVGA_WIDTH;
+       pix->height = QQVGA_HEIGHT;
+       /* Default pixel format is RGB 5-6-5 */
+       pix->pixelformat = V4L2_PIX_FMT_RGB565;
+       pix->field = V4L2_FIELD_ANY;
+       pix->bytesperline = pix->width * 2;
+       pix->sizeimage = pix->bytesperline * pix->height;
+       pix->priv = 0;
+       pix->colorspace = V4L2_COLORSPACE_JPEG;
+       vout->bpp = RGB565_BPP;
+       vout->fbuf.fmt.width  =  display->panel.timings.x_res;
+       vout->fbuf.fmt.height =  display->panel.timings.y_res;
+       /* Set the data structures for the overlay parameters*/
+       vout->win.global_alpha = 255;
+       vout->fbuf.flags = 0;
+       vout->fbuf.capability = V4L2_FBUF_CAP_LOCAL_ALPHA |
+               V4L2_FBUF_CAP_SRC_CHROMAKEY | V4L2_FBUF_CAP_CHROMAKEY;
+       vout->win.chromakey = 0;
+       omap_vout_new_format(pix, &vout->fbuf, &vout->crop, &vout->win);
+       /*Initialize the control variables for
+         rotation, flipping and background color. */
+       control = vout->control;
+       control[0].id = V4L2_CID_ROTATE;
+       control[0].value = 0;
+       vout->rotation = 0;
+       vout->mirror = 0;
+       vout->control[2].id = V4L2_CID_HFLIP;
+       vout->control[2].value = 0;
+       if (vout->vid_info.rotation_type == VOUT_ROT_VRFB)
+               vout->vrfb_bpp = 2;
+       control[1].id = V4L2_CID_BG_COLOR;
+       control[1].value = 0;
+       /* initialize the video_device struct */
+       vfd = vout->vfd = video_device_alloc();
+       if (!vfd) {
+               printk(KERN_ERR VOUT_NAME ": could not allocate"
+                               " video device struct\n");
+               return -ENOMEM;
+       }
+       vfd->release = video_device_release;
+       vfd->ioctl_ops = &vout_ioctl_ops;
+       strlcpy(vfd->name, VOUT_NAME, sizeof(vfd->name));
+       vfd->fops = &omap_vout_fops;
+       vfd->v4l2_dev = &vout->vid_dev->v4l2_dev;
+       vfd->vfl_dir = VFL_DIR_TX;
+       mutex_init(&vout->lock);
+       vfd->minor = -1;
+       return 0;
+ }
+ /* Setup video buffers */
+ static int __init omap_vout_setup_video_bufs(struct platform_device *pdev,
+               int vid_num)
+ {
+       u32 numbuffers;
+       int ret = 0, i;
+       struct omapvideo_info *ovid;
+       struct omap_vout_device *vout;
+       struct v4l2_device *v4l2_dev = platform_get_drvdata(pdev);
+       struct omap2video_device *vid_dev =
+               container_of(v4l2_dev, struct omap2video_device, v4l2_dev);
+       vout = vid_dev->vouts[vid_num];
+       ovid = &vout->vid_info;
+       numbuffers = (vid_num == 0) ? video1_numbuffers : video2_numbuffers;
+       vout->buffer_size = (vid_num == 0) ? video1_bufsize : video2_bufsize;
+       dev_info(&pdev->dev, "Buffer Size = %d\n", vout->buffer_size);
+       for (i = 0; i < numbuffers; i++) {
+               vout->buf_virt_addr[i] =
+                       omap_vout_alloc_buffer(vout->buffer_size,
+                                       (u32 *) &vout->buf_phy_addr[i]);
+               if (!vout->buf_virt_addr[i]) {
+                       numbuffers = i;
+                       ret = -ENOMEM;
+                       goto free_buffers;
+               }
+       }
+       vout->cropped_offset = 0;
+       if (ovid->rotation_type == VOUT_ROT_VRFB) {
+               int static_vrfb_allocation = (vid_num == 0) ?
+                       vid1_static_vrfb_alloc : vid2_static_vrfb_alloc;
+               ret = omap_vout_setup_vrfb_bufs(pdev, vid_num,
+                               static_vrfb_allocation);
+       }
+       return ret;
+ free_buffers:
+       for (i = 0; i < numbuffers; i++) {
+               omap_vout_free_buffer(vout->buf_virt_addr[i],
+                                               vout->buffer_size);
+               vout->buf_virt_addr[i] = 0;
+               vout->buf_phy_addr[i] = 0;
+       }
+       return ret;
+ }
+ /* Create video out devices */
+ static int __init omap_vout_create_video_devices(struct platform_device *pdev)
+ {
+       int ret = 0, k;
+       struct omap_vout_device *vout;
+       struct video_device *vfd = NULL;
+       struct v4l2_device *v4l2_dev = platform_get_drvdata(pdev);
+       struct omap2video_device *vid_dev = container_of(v4l2_dev,
+                       struct omap2video_device, v4l2_dev);
+       for (k = 0; k < pdev->num_resources; k++) {
+               vout = kzalloc(sizeof(struct omap_vout_device), GFP_KERNEL);
+               if (!vout) {
+                       dev_err(&pdev->dev, ": could not allocate memory\n");
+                       return -ENOMEM;
+               }
+               vout->vid = k;
+               vid_dev->vouts[k] = vout;
+               vout->vid_dev = vid_dev;
+               /* Select video2 if only 1 overlay is controlled by V4L2 */
+               if (pdev->num_resources == 1)
+                       vout->vid_info.overlays[0] = vid_dev->overlays[k + 2];
+               else
+                       /* Else select video1 and video2 one by one. */
+                       vout->vid_info.overlays[0] = vid_dev->overlays[k + 1];
+               vout->vid_info.num_overlays = 1;
+               vout->vid_info.id = k + 1;
+               /* Set VRFB as rotation_type for omap2 and omap3 */
+               if (cpu_is_omap24xx() || cpu_is_omap34xx())
+                       vout->vid_info.rotation_type = VOUT_ROT_VRFB;
+               /* Setup the default configuration for the video devices
+                */
+               if (omap_vout_setup_video_data(vout) != 0) {
+                       ret = -ENOMEM;
+                       goto error;
+               }
+               /* Allocate default number of buffers for the video streaming
+                * and reserve the VRFB space for rotation
+                */
+               if (omap_vout_setup_video_bufs(pdev, k) != 0) {
+                       ret = -ENOMEM;
+                       goto error1;
+               }
+               /* Register the Video device with V4L2
+                */
+               vfd = vout->vfd;
+               if (video_register_device(vfd, VFL_TYPE_GRABBER, -1) < 0) {
+                       dev_err(&pdev->dev, ": Could not register "
+                                       "Video for Linux device\n");
+                       vfd->minor = -1;
+                       ret = -ENODEV;
+                       goto error2;
+               }
+               video_set_drvdata(vfd, vout);
+               /* Configure the overlay structure */
+               ret = omapvid_init(vid_dev->vouts[k], 0);
+               if (!ret)
+                       goto success;
+ error2:
+               if (vout->vid_info.rotation_type == VOUT_ROT_VRFB)
+                       omap_vout_release_vrfb(vout);
+               omap_vout_free_buffers(vout);
+ error1:
+               video_device_release(vfd);
+ error:
+               kfree(vout);
+               return ret;
+ success:
+               dev_info(&pdev->dev, ": registered and initialized"
+                               " video device %d\n", vfd->minor);
+               if (k == (pdev->num_resources - 1))
+                       return 0;
+       }
+       return -ENODEV;
+ }
+ /* Driver functions */
+ static void omap_vout_cleanup_device(struct omap_vout_device *vout)
+ {
+       struct video_device *vfd;
+       struct omapvideo_info *ovid;
+       if (!vout)
+               return;
+       vfd = vout->vfd;
+       ovid = &vout->vid_info;
+       if (vfd) {
+               if (!video_is_registered(vfd)) {
+                       /*
+                        * The device was never registered, so release the
+                        * video_device struct directly.
+                        */
+                       video_device_release(vfd);
+               } else {
+                       /*
+                        * The unregister function will release the video_device
+                        * struct as well as unregistering it.
+                        */
+                       video_unregister_device(vfd);
+               }
+       }
+       if (ovid->rotation_type == VOUT_ROT_VRFB) {
+               omap_vout_release_vrfb(vout);
+               /* Free the VRFB buffer if allocated
+                * init time
+                */
+               if (vout->vrfb_static_allocation)
+                       omap_vout_free_vrfb_buffers(vout);
+       }
+       omap_vout_free_buffers(vout);
+       kfree(vout);
+ }
+ static int omap_vout_remove(struct platform_device *pdev)
+ {
+       int k;
+       struct v4l2_device *v4l2_dev = platform_get_drvdata(pdev);
+       struct omap2video_device *vid_dev = container_of(v4l2_dev, struct
+                       omap2video_device, v4l2_dev);
+       v4l2_device_unregister(v4l2_dev);
+       for (k = 0; k < pdev->num_resources; k++)
+               omap_vout_cleanup_device(vid_dev->vouts[k]);
+       for (k = 0; k < vid_dev->num_displays; k++) {
+               if (vid_dev->displays[k]->state != OMAP_DSS_DISPLAY_DISABLED)
+                       vid_dev->displays[k]->driver->disable(vid_dev->displays[k]);
+               omap_dss_put_device(vid_dev->displays[k]);
+       }
+       kfree(vid_dev);
+       return 0;
+ }
+ static int __init omap_vout_probe(struct platform_device *pdev)
+ {
+       int ret = 0, i;
+       struct omap_overlay *ovl;
+       struct omap_dss_device *dssdev = NULL;
+       struct omap_dss_device *def_display;
+       struct omap2video_device *vid_dev = NULL;
+       if (pdev->num_resources == 0) {
+               dev_err(&pdev->dev, "probed for an unknown device\n");
+               return -ENODEV;
+       }
+       vid_dev = kzalloc(sizeof(struct omap2video_device), GFP_KERNEL);
+       if (vid_dev == NULL)
+               return -ENOMEM;
+       vid_dev->num_displays = 0;
+       for_each_dss_dev(dssdev) {
+               omap_dss_get_device(dssdev);
+               if (!dssdev->driver) {
+                       dev_warn(&pdev->dev, "no driver for display: %s\n",
+                                       dssdev->name);
+                       omap_dss_put_device(dssdev);
+                       continue;
+               }
+               vid_dev->displays[vid_dev->num_displays++] = dssdev;
+       }
+       if (vid_dev->num_displays == 0) {
+               dev_err(&pdev->dev, "no displays\n");
+               ret = -EINVAL;
+               goto probe_err0;
+       }
+       vid_dev->num_overlays = omap_dss_get_num_overlays();
+       for (i = 0; i < vid_dev->num_overlays; i++)
+               vid_dev->overlays[i] = omap_dss_get_overlay(i);
+       vid_dev->num_managers = omap_dss_get_num_overlay_managers();
+       for (i = 0; i < vid_dev->num_managers; i++)
+               vid_dev->managers[i] = omap_dss_get_overlay_manager(i);
+       /* Get the Video1 overlay and video2 overlay.
+        * Setup the Display attached to that overlays
+        */
+       for (i = 1; i < vid_dev->num_overlays; i++) {
+               ovl = omap_dss_get_overlay(i);
+               if (ovl->manager && ovl->manager->device) {
+                       def_display = ovl->manager->device;
+               } else {
+                       dev_warn(&pdev->dev, "cannot find display\n");
+                       def_display = NULL;
+               }
+               if (def_display) {
+                       struct omap_dss_driver *dssdrv = def_display->driver;
+                       ret = dssdrv->enable(def_display);
+                       if (ret) {
+                               /* Here we are not considering a error
+                                *  as display may be enabled by frame
+                                *  buffer driver
+                                */
+                               dev_warn(&pdev->dev,
+                                       "'%s' Display already enabled\n",
+                                       def_display->name);
+                       }
+               }
+       }
+       if (v4l2_device_register(&pdev->dev, &vid_dev->v4l2_dev) < 0) {
+               dev_err(&pdev->dev, "v4l2_device_register failed\n");
+               ret = -ENODEV;
+               goto probe_err1;
+       }
+       ret = omap_vout_create_video_devices(pdev);
+       if (ret)
+               goto probe_err2;
+       for (i = 0; i < vid_dev->num_displays; i++) {
+               struct omap_dss_device *display = vid_dev->displays[i];
+               if (display->driver->update)
+                       display->driver->update(display, 0, 0,
+                                       display->panel.timings.x_res,
+                                       display->panel.timings.y_res);
+       }
+       return 0;
+ probe_err2:
+       v4l2_device_unregister(&vid_dev->v4l2_dev);
+ probe_err1:
+       for (i = 1; i < vid_dev->num_overlays; i++) {
+               def_display = NULL;
+               ovl = omap_dss_get_overlay(i);
+               if (ovl->manager && ovl->manager->device)
+                       def_display = ovl->manager->device;
+               if (def_display && def_display->driver)
+                       def_display->driver->disable(def_display);
+       }
+ probe_err0:
+       kfree(vid_dev);
+       return ret;
+ }
+ static struct platform_driver omap_vout_driver = {
+       .driver = {
+               .name = VOUT_NAME,
+       },
+       .remove = omap_vout_remove,
+ };
+ static int __init omap_vout_init(void)
+ {
+       if (platform_driver_probe(&omap_vout_driver, omap_vout_probe) != 0) {
+               printk(KERN_ERR VOUT_NAME ":Could not register Video driver\n");
+               return -EINVAL;
+       }
+       return 0;
+ }
+ static void omap_vout_cleanup(void)
+ {
+       platform_driver_unregister(&omap_vout_driver);
+ }
+ late_initcall(omap_vout_init);
+ module_exit(omap_vout_cleanup);
index 0000000000000000000000000000000000000000,fde2e66f6f332e3a4f7800f1b8570fe9a039a033..70f45c381318929f4b80b70025fb368305a0b788
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,1881 +1,1881 @@@
 -      flush_work_sync(&cam->sensor_reset_work);
+ /*
+  * 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);
+       do_gettimeofday(&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_sync(&cam->sensor_reset_work);
++      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_sync(&cam->sensor_reset_work);
++      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->parent = cam->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 __devinit 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;
+       /*
+        * 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;
+       }
+       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)");
index 0000000000000000000000000000000000000000,d7aa513dcc8deace0704b37c5c378968ff558b37..99640d8c1db0a51d9e2fa2d9a6e64f23f2f1b2ee
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,2243 +1,2245 @@@
+ /*
+  * isp.c
+  *
+  * TI OMAP3 ISP - Core
+  *
+  * Copyright (C) 2006-2010 Nokia Corporation
+  * Copyright (C) 2007-2009 Texas Instruments, Inc.
+  *
+  * Contacts: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
+  *         Sakari Ailus <sakari.ailus@iki.fi>
+  *
+  * Contributors:
+  *    Laurent Pinchart <laurent.pinchart@ideasonboard.com>
+  *    Sakari Ailus <sakari.ailus@iki.fi>
+  *    David Cohen <dacohen@gmail.com>
+  *    Stanimir Varbanov <svarbanov@mm-sol.com>
+  *    Vimarsh Zutshi <vimarsh.zutshi@gmail.com>
+  *    Tuukka Toivonen <tuukkat76@gmail.com>
+  *    Sergio Aguirre <saaguirre@ti.com>
+  *    Antti Koskipaa <akoskipa@gmail.com>
+  *    Ivan T. Ivanov <iivanov@mm-sol.com>
+  *    RaniSuneela <r-m@ti.com>
+  *    Atanas Filipov <afilipov@mm-sol.com>
+  *    Gjorgji Rosikopulos <grosikopulos@mm-sol.com>
+  *    Hiroshi DOYU <hiroshi.doyu@nokia.com>
+  *    Nayden Kanchev <nkanchev@mm-sol.com>
+  *    Phil Carmody <ext-phil.2.carmody@nokia.com>
+  *    Artem Bityutskiy <artem.bityutskiy@nokia.com>
+  *    Dominic Curran <dcurran@ti.com>
+  *    Ilkka Myllyperkio <ilkka.myllyperkio@sofica.fi>
+  *    Pallavi Kulkarni <p-kulkarni@ti.com>
+  *    Vaibhav Hiremath <hvaibhav@ti.com>
+  *    Mohit Jalori <mjalori@ti.com>
+  *    Sameer Venkatraman <sameerv@ti.com>
+  *    Senthilvadivu Guruswamy <svadivu@ti.com>
+  *    Thara Gopinath <thara@ti.com>
+  *    Toni Leinonen <toni.leinonen@nokia.com>
+  *    Troy Laramy <t-laramy@ti.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 <asm/cacheflush.h>
+ #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/regulator/consumer.h>
+ #include <linux/slab.h>
+ #include <linux/sched.h>
+ #include <linux/vmalloc.h>
+ #include <media/v4l2-common.h>
+ #include <media/v4l2-device.h>
++#include <plat/cpu.h>
++
+ #include "isp.h"
+ #include "ispreg.h"
+ #include "ispccdc.h"
+ #include "isppreview.h"
+ #include "ispresizer.h"
+ #include "ispcsi2.h"
+ #include "ispccp2.h"
+ #include "isph3a.h"
+ #include "isphist.h"
+ static unsigned int autoidle;
+ module_param(autoidle, int, 0444);
+ MODULE_PARM_DESC(autoidle, "Enable OMAP3ISP AUTOIDLE support");
+ static void isp_save_ctx(struct isp_device *isp);
+ static void isp_restore_ctx(struct isp_device *isp);
+ static const struct isp_res_mapping isp_res_maps[] = {
+       {
+               .isp_rev = ISP_REVISION_2_0,
+               .map = 1 << OMAP3_ISP_IOMEM_MAIN |
+                      1 << OMAP3_ISP_IOMEM_CCP2 |
+                      1 << OMAP3_ISP_IOMEM_CCDC |
+                      1 << OMAP3_ISP_IOMEM_HIST |
+                      1 << OMAP3_ISP_IOMEM_H3A |
+                      1 << OMAP3_ISP_IOMEM_PREV |
+                      1 << OMAP3_ISP_IOMEM_RESZ |
+                      1 << OMAP3_ISP_IOMEM_SBL |
+                      1 << OMAP3_ISP_IOMEM_CSI2A_REGS1 |
+                      1 << OMAP3_ISP_IOMEM_CSIPHY2,
+       },
+       {
+               .isp_rev = ISP_REVISION_15_0,
+               .map = 1 << OMAP3_ISP_IOMEM_MAIN |
+                      1 << OMAP3_ISP_IOMEM_CCP2 |
+                      1 << OMAP3_ISP_IOMEM_CCDC |
+                      1 << OMAP3_ISP_IOMEM_HIST |
+                      1 << OMAP3_ISP_IOMEM_H3A |
+                      1 << OMAP3_ISP_IOMEM_PREV |
+                      1 << OMAP3_ISP_IOMEM_RESZ |
+                      1 << OMAP3_ISP_IOMEM_SBL |
+                      1 << OMAP3_ISP_IOMEM_CSI2A_REGS1 |
+                      1 << OMAP3_ISP_IOMEM_CSIPHY2 |
+                      1 << OMAP3_ISP_IOMEM_CSI2A_REGS2 |
+                      1 << OMAP3_ISP_IOMEM_CSI2C_REGS1 |
+                      1 << OMAP3_ISP_IOMEM_CSIPHY1 |
+                      1 << OMAP3_ISP_IOMEM_CSI2C_REGS2,
+       },
+ };
+ /* Structure for saving/restoring ISP module registers */
+ static struct isp_reg isp_reg_list[] = {
+       {OMAP3_ISP_IOMEM_MAIN, ISP_SYSCONFIG, 0},
+       {OMAP3_ISP_IOMEM_MAIN, ISP_CTRL, 0},
+       {OMAP3_ISP_IOMEM_MAIN, ISP_TCTRL_CTRL, 0},
+       {0, ISP_TOK_TERM, 0}
+ };
+ /*
+  * omap3isp_flush - Post pending L3 bus writes by doing a register readback
+  * @isp: OMAP3 ISP 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 omap3isp_flush(struct isp_device *isp)
+ {
+       isp_reg_writel(isp, 0, OMAP3_ISP_IOMEM_MAIN, ISP_REVISION);
+       isp_reg_readl(isp, OMAP3_ISP_IOMEM_MAIN, ISP_REVISION);
+ }
+ /*
+  * isp_enable_interrupts - Enable ISP interrupts.
+  * @isp: OMAP3 ISP device
+  */
+ static void isp_enable_interrupts(struct isp_device *isp)
+ {
+       static const u32 irq = IRQ0ENABLE_CSIA_IRQ
+                            | IRQ0ENABLE_CSIB_IRQ
+                            | IRQ0ENABLE_CCDC_LSC_PREF_ERR_IRQ
+                            | IRQ0ENABLE_CCDC_LSC_DONE_IRQ
+                            | IRQ0ENABLE_CCDC_VD0_IRQ
+                            | IRQ0ENABLE_CCDC_VD1_IRQ
+                            | IRQ0ENABLE_HS_VS_IRQ
+                            | IRQ0ENABLE_HIST_DONE_IRQ
+                            | IRQ0ENABLE_H3A_AWB_DONE_IRQ
+                            | IRQ0ENABLE_H3A_AF_DONE_IRQ
+                            | IRQ0ENABLE_PRV_DONE_IRQ
+                            | IRQ0ENABLE_RSZ_DONE_IRQ;
+       isp_reg_writel(isp, irq, OMAP3_ISP_IOMEM_MAIN, ISP_IRQ0STATUS);
+       isp_reg_writel(isp, irq, OMAP3_ISP_IOMEM_MAIN, ISP_IRQ0ENABLE);
+ }
+ /*
+  * isp_disable_interrupts - Disable ISP interrupts.
+  * @isp: OMAP3 ISP device
+  */
+ static void isp_disable_interrupts(struct isp_device *isp)
+ {
+       isp_reg_writel(isp, 0, OMAP3_ISP_IOMEM_MAIN, ISP_IRQ0ENABLE);
+ }
+ /**
+  * isp_set_xclk - Configures the specified cam_xclk to the desired frequency.
+  * @isp: OMAP3 ISP device
+  * @xclk: Desired frequency of the clock in Hz. 0 = stable low, 1 is stable high
+  * @xclksel: XCLK to configure (0 = A, 1 = B).
+  *
+  * Configures the specified MCLK divisor in the ISP timing control register
+  * (TCTRL_CTRL) to generate the desired xclk clock value.
+  *
+  * Divisor = cam_mclk_hz / xclk
+  *
+  * Returns the final frequency that is actually being generated
+  **/
+ static u32 isp_set_xclk(struct isp_device *isp, u32 xclk, u8 xclksel)
+ {
+       u32 divisor;
+       u32 currentxclk;
+       unsigned long mclk_hz;
+       if (!omap3isp_get(isp))
+               return 0;
+       mclk_hz = clk_get_rate(isp->clock[ISP_CLK_CAM_MCLK]);
+       if (xclk >= mclk_hz) {
+               divisor = ISPTCTRL_CTRL_DIV_BYPASS;
+               currentxclk = mclk_hz;
+       } else if (xclk >= 2) {
+               divisor = mclk_hz / xclk;
+               if (divisor >= ISPTCTRL_CTRL_DIV_BYPASS)
+                       divisor = ISPTCTRL_CTRL_DIV_BYPASS - 1;
+               currentxclk = mclk_hz / divisor;
+       } else {
+               divisor = xclk;
+               currentxclk = 0;
+       }
+       switch (xclksel) {
+       case ISP_XCLK_A:
+               isp_reg_clr_set(isp, OMAP3_ISP_IOMEM_MAIN, ISP_TCTRL_CTRL,
+                               ISPTCTRL_CTRL_DIVA_MASK,
+                               divisor << ISPTCTRL_CTRL_DIVA_SHIFT);
+               dev_dbg(isp->dev, "isp_set_xclk(): cam_xclka set to %d Hz\n",
+                       currentxclk);
+               break;
+       case ISP_XCLK_B:
+               isp_reg_clr_set(isp, OMAP3_ISP_IOMEM_MAIN, ISP_TCTRL_CTRL,
+                               ISPTCTRL_CTRL_DIVB_MASK,
+                               divisor << ISPTCTRL_CTRL_DIVB_SHIFT);
+               dev_dbg(isp->dev, "isp_set_xclk(): cam_xclkb set to %d Hz\n",
+                       currentxclk);
+               break;
+       case ISP_XCLK_NONE:
+       default:
+               omap3isp_put(isp);
+               dev_dbg(isp->dev, "ISP_ERR: isp_set_xclk(): Invalid requested "
+                       "xclk. Must be 0 (A) or 1 (B).\n");
+               return -EINVAL;
+       }
+       /* Do we go from stable whatever to clock? */
+       if (divisor >= 2 && isp->xclk_divisor[xclksel - 1] < 2)
+               omap3isp_get(isp);
+       /* Stopping the clock. */
+       else if (divisor < 2 && isp->xclk_divisor[xclksel - 1] >= 2)
+               omap3isp_put(isp);
+       isp->xclk_divisor[xclksel - 1] = divisor;
+       omap3isp_put(isp);
+       return currentxclk;
+ }
+ /*
+  * isp_core_init - ISP core settings
+  * @isp: OMAP3 ISP device
+  * @idle: Consider idle state.
+  *
+  * Set the power settings for the ISP and SBL bus and cConfigure the HS/VS
+  * interrupt source.
+  *
+  * We need to configure the HS/VS interrupt source before interrupts get
+  * enabled, as the sensor might be free-running and the ISP default setting
+  * (HS edge) would put an unnecessary burden on the CPU.
+  */
+ static void isp_core_init(struct isp_device *isp, int idle)
+ {
+       isp_reg_writel(isp,
+                      ((idle ? ISP_SYSCONFIG_MIDLEMODE_SMARTSTANDBY :
+                               ISP_SYSCONFIG_MIDLEMODE_FORCESTANDBY) <<
+                       ISP_SYSCONFIG_MIDLEMODE_SHIFT) |
+                       ((isp->revision == ISP_REVISION_15_0) ?
+                         ISP_SYSCONFIG_AUTOIDLE : 0),
+                      OMAP3_ISP_IOMEM_MAIN, ISP_SYSCONFIG);
+       isp_reg_writel(isp,
+                      (isp->autoidle ? ISPCTRL_SBL_AUTOIDLE : 0) |
+                      ISPCTRL_SYNC_DETECT_VSRISE,
+                      OMAP3_ISP_IOMEM_MAIN, ISP_CTRL);
+ }
+ /*
+  * Configure the bridge and lane shifter. Valid inputs are
+  *
+  * CCDC_INPUT_PARALLEL: Parallel interface
+  * CCDC_INPUT_CSI2A: CSI2a receiver
+  * CCDC_INPUT_CCP2B: CCP2b receiver
+  * CCDC_INPUT_CSI2C: CSI2c receiver
+  *
+  * The bridge and lane shifter are configured according to the selected input
+  * and the ISP platform data.
+  */
+ void omap3isp_configure_bridge(struct isp_device *isp,
+                              enum ccdc_input_entity input,
+                              const struct isp_parallel_platform_data *pdata,
+                              unsigned int shift, unsigned int bridge)
+ {
+       u32 ispctrl_val;
+       ispctrl_val  = isp_reg_readl(isp, OMAP3_ISP_IOMEM_MAIN, ISP_CTRL);
+       ispctrl_val &= ~ISPCTRL_SHIFT_MASK;
+       ispctrl_val &= ~ISPCTRL_PAR_CLK_POL_INV;
+       ispctrl_val &= ~ISPCTRL_PAR_SER_CLK_SEL_MASK;
+       ispctrl_val &= ~ISPCTRL_PAR_BRIDGE_MASK;
+       ispctrl_val |= bridge;
+       switch (input) {
+       case CCDC_INPUT_PARALLEL:
+               ispctrl_val |= ISPCTRL_PAR_SER_CLK_SEL_PARALLEL;
+               ispctrl_val |= pdata->clk_pol << ISPCTRL_PAR_CLK_POL_SHIFT;
+               shift += pdata->data_lane_shift * 2;
+               break;
+       case CCDC_INPUT_CSI2A:
+               ispctrl_val |= ISPCTRL_PAR_SER_CLK_SEL_CSIA;
+               break;
+       case CCDC_INPUT_CCP2B:
+               ispctrl_val |= ISPCTRL_PAR_SER_CLK_SEL_CSIB;
+               break;
+       case CCDC_INPUT_CSI2C:
+               ispctrl_val |= ISPCTRL_PAR_SER_CLK_SEL_CSIC;
+               break;
+       default:
+               return;
+       }
+       ispctrl_val |= ((shift/2) << ISPCTRL_SHIFT_SHIFT) & ISPCTRL_SHIFT_MASK;
+       isp_reg_writel(isp, ispctrl_val, OMAP3_ISP_IOMEM_MAIN, ISP_CTRL);
+ }
+ void omap3isp_hist_dma_done(struct isp_device *isp)
+ {
+       if (omap3isp_ccdc_busy(&isp->isp_ccdc) ||
+           omap3isp_stat_pcr_busy(&isp->isp_hist)) {
+               /* Histogram cannot be enabled in this frame anymore */
+               atomic_set(&isp->isp_hist.buf_err, 1);
+               dev_dbg(isp->dev, "hist: Out of synchronization with "
+                                 "CCDC. Ignoring next buffer.\n");
+       }
+ }
+ static inline void isp_isr_dbg(struct isp_device *isp, u32 irqstatus)
+ {
+       static const char *name[] = {
+               "CSIA_IRQ",
+               "res1",
+               "res2",
+               "CSIB_LCM_IRQ",
+               "CSIB_IRQ",
+               "res5",
+               "res6",
+               "res7",
+               "CCDC_VD0_IRQ",
+               "CCDC_VD1_IRQ",
+               "CCDC_VD2_IRQ",
+               "CCDC_ERR_IRQ",
+               "H3A_AF_DONE_IRQ",
+               "H3A_AWB_DONE_IRQ",
+               "res14",
+               "res15",
+               "HIST_DONE_IRQ",
+               "CCDC_LSC_DONE",
+               "CCDC_LSC_PREFETCH_COMPLETED",
+               "CCDC_LSC_PREFETCH_ERROR",
+               "PRV_DONE_IRQ",
+               "CBUFF_IRQ",
+               "res22",
+               "res23",
+               "RSZ_DONE_IRQ",
+               "OVF_IRQ",
+               "res26",
+               "res27",
+               "MMU_ERR_IRQ",
+               "OCP_ERR_IRQ",
+               "SEC_ERR_IRQ",
+               "HS_VS_IRQ",
+       };
+       int i;
+       dev_dbg(isp->dev, "ISP IRQ: ");
+       for (i = 0; i < ARRAY_SIZE(name); i++) {
+               if ((1 << i) & irqstatus)
+                       printk(KERN_CONT "%s ", name[i]);
+       }
+       printk(KERN_CONT "\n");
+ }
+ static void isp_isr_sbl(struct isp_device *isp)
+ {
+       struct device *dev = isp->dev;
+       struct isp_pipeline *pipe;
+       u32 sbl_pcr;
+       /*
+        * Handle shared buffer logic overflows for video buffers.
+        * ISPSBL_PCR_CCDCPRV_2_RSZ_OVF can be safely ignored.
+        */
+       sbl_pcr = isp_reg_readl(isp, OMAP3_ISP_IOMEM_SBL, ISPSBL_PCR);
+       isp_reg_writel(isp, sbl_pcr, OMAP3_ISP_IOMEM_SBL, ISPSBL_PCR);
+       sbl_pcr &= ~ISPSBL_PCR_CCDCPRV_2_RSZ_OVF;
+       if (sbl_pcr)
+               dev_dbg(dev, "SBL overflow (PCR = 0x%08x)\n", sbl_pcr);
+       if (sbl_pcr & ISPSBL_PCR_CSIB_WBL_OVF) {
+               pipe = to_isp_pipeline(&isp->isp_ccp2.subdev.entity);
+               if (pipe != NULL)
+                       pipe->error = true;
+       }
+       if (sbl_pcr & ISPSBL_PCR_CSIA_WBL_OVF) {
+               pipe = to_isp_pipeline(&isp->isp_csi2a.subdev.entity);
+               if (pipe != NULL)
+                       pipe->error = true;
+       }
+       if (sbl_pcr & ISPSBL_PCR_CCDC_WBL_OVF) {
+               pipe = to_isp_pipeline(&isp->isp_ccdc.subdev.entity);
+               if (pipe != NULL)
+                       pipe->error = true;
+       }
+       if (sbl_pcr & ISPSBL_PCR_PRV_WBL_OVF) {
+               pipe = to_isp_pipeline(&isp->isp_prev.subdev.entity);
+               if (pipe != NULL)
+                       pipe->error = true;
+       }
+       if (sbl_pcr & (ISPSBL_PCR_RSZ1_WBL_OVF
+                      | ISPSBL_PCR_RSZ2_WBL_OVF
+                      | ISPSBL_PCR_RSZ3_WBL_OVF
+                      | ISPSBL_PCR_RSZ4_WBL_OVF)) {
+               pipe = to_isp_pipeline(&isp->isp_res.subdev.entity);
+               if (pipe != NULL)
+                       pipe->error = true;
+       }
+       if (sbl_pcr & ISPSBL_PCR_H3A_AF_WBL_OVF)
+               omap3isp_stat_sbl_overflow(&isp->isp_af);
+       if (sbl_pcr & ISPSBL_PCR_H3A_AEAWB_WBL_OVF)
+               omap3isp_stat_sbl_overflow(&isp->isp_aewb);
+ }
+ /*
+  * isp_isr - Interrupt Service Routine for Camera ISP module.
+  * @irq: Not used currently.
+  * @_isp: Pointer to the OMAP3 ISP 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 isp_isr(int irq, void *_isp)
+ {
+       static const u32 ccdc_events = IRQ0STATUS_CCDC_LSC_PREF_ERR_IRQ |
+                                      IRQ0STATUS_CCDC_LSC_DONE_IRQ |
+                                      IRQ0STATUS_CCDC_VD0_IRQ |
+                                      IRQ0STATUS_CCDC_VD1_IRQ |
+                                      IRQ0STATUS_HS_VS_IRQ;
+       struct isp_device *isp = _isp;
+       u32 irqstatus;
+       irqstatus = isp_reg_readl(isp, OMAP3_ISP_IOMEM_MAIN, ISP_IRQ0STATUS);
+       isp_reg_writel(isp, irqstatus, OMAP3_ISP_IOMEM_MAIN, ISP_IRQ0STATUS);
+       isp_isr_sbl(isp);
+       if (irqstatus & IRQ0STATUS_CSIA_IRQ)
+               omap3isp_csi2_isr(&isp->isp_csi2a);
+       if (irqstatus & IRQ0STATUS_CSIB_IRQ)
+               omap3isp_ccp2_isr(&isp->isp_ccp2);
+       if (irqstatus & IRQ0STATUS_CCDC_VD0_IRQ) {
+               if (isp->isp_ccdc.output & CCDC_OUTPUT_PREVIEW)
+                       omap3isp_preview_isr_frame_sync(&isp->isp_prev);
+               if (isp->isp_ccdc.output & CCDC_OUTPUT_RESIZER)
+                       omap3isp_resizer_isr_frame_sync(&isp->isp_res);
+               omap3isp_stat_isr_frame_sync(&isp->isp_aewb);
+               omap3isp_stat_isr_frame_sync(&isp->isp_af);
+               omap3isp_stat_isr_frame_sync(&isp->isp_hist);
+       }
+       if (irqstatus & ccdc_events)
+               omap3isp_ccdc_isr(&isp->isp_ccdc, irqstatus & ccdc_events);
+       if (irqstatus & IRQ0STATUS_PRV_DONE_IRQ) {
+               if (isp->isp_prev.output & PREVIEW_OUTPUT_RESIZER)
+                       omap3isp_resizer_isr_frame_sync(&isp->isp_res);
+               omap3isp_preview_isr(&isp->isp_prev);
+       }
+       if (irqstatus & IRQ0STATUS_RSZ_DONE_IRQ)
+               omap3isp_resizer_isr(&isp->isp_res);
+       if (irqstatus & IRQ0STATUS_H3A_AWB_DONE_IRQ)
+               omap3isp_stat_isr(&isp->isp_aewb);
+       if (irqstatus & IRQ0STATUS_H3A_AF_DONE_IRQ)
+               omap3isp_stat_isr(&isp->isp_af);
+       if (irqstatus & IRQ0STATUS_HIST_DONE_IRQ)
+               omap3isp_stat_isr(&isp->isp_hist);
+       omap3isp_flush(isp);
+ #if defined(DEBUG) && defined(ISP_ISR_DEBUG)
+       isp_isr_dbg(isp, 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 omap3isp_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.
+  */
+ /*
+  * isp_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 isp_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;
+ }
+ /*
+  * isp_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 isp_pipeline_pm_power_one(struct media_entity *entity, int change)
+ {
+       struct v4l2_subdev *subdev;
+       int ret;
+       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) {
+               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;
+ }
+ /*
+  * isp_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 isp_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 = isp_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)
+                       isp_pipeline_pm_power_one(first, -change);
+       return ret;
+ }
+ /*
+  * omap3isp_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 omap3isp_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 = isp_pipeline_pm_power(entity, change);
+       if (ret < 0)
+               entity->use_count -= change;
+       mutex_unlock(&entity->parent->graph_mutex);
+       return ret;
+ }
+ /*
+  * isp_pipeline_link_notify - Link management notification callback
+  * @source: Pad at the start of the link
+  * @sink: Pad at the end of 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 isp_pipeline_link_notify(struct media_pad *source,
+                                   struct media_pad *sink, u32 flags)
+ {
+       int source_use = isp_pipeline_pm_use_count(source->entity);
+       int sink_use = isp_pipeline_pm_use_count(sink->entity);
+       int ret;
+       if (!(flags & MEDIA_LNK_FL_ENABLED)) {
+               /* Powering off entities is assumed to never fail. */
+               isp_pipeline_pm_power(source->entity, -sink_use);
+               isp_pipeline_pm_power(sink->entity, -source_use);
+               return 0;
+       }
+       ret = isp_pipeline_pm_power(source->entity, sink_use);
+       if (ret < 0)
+               return ret;
+       ret = isp_pipeline_pm_power(sink->entity, source_use);
+       if (ret < 0)
+               isp_pipeline_pm_power(source->entity, -sink_use);
+       return ret;
+ }
+ /* -----------------------------------------------------------------------------
+  * Pipeline stream management
+  */
+ /*
+  * isp_pipeline_enable - Enable streaming on a pipeline
+  * @pipe: ISP 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 isp_pipeline_enable(struct isp_pipeline *pipe,
+                              enum isp_pipeline_stream_state mode)
+ {
+       struct isp_device *isp = pipe->output->isp;
+       struct media_entity *entity;
+       struct media_pad *pad;
+       struct v4l2_subdev *subdev;
+       unsigned long flags;
+       int ret;
+       /* If the preview engine crashed it might not respond to read/write
+        * operations on the L4 bus. This would result in a bus fault and a
+        * kernel oops. 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 & isp->crashed) &
+           (1U << isp->isp_prev.subdev.entity.id))
+               return -EIO;
+       spin_lock_irqsave(&pipe->lock, flags);
+       pipe->state &= ~(ISP_PIPELINE_IDLE_INPUT | ISP_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_source(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;
+               if (subdev == &isp->isp_ccdc.subdev) {
+                       v4l2_subdev_call(&isp->isp_aewb.subdev, video,
+                                       s_stream, mode);
+                       v4l2_subdev_call(&isp->isp_af.subdev, video,
+                                       s_stream, mode);
+                       v4l2_subdev_call(&isp->isp_hist.subdev, video,
+                                       s_stream, mode);
+                       pipe->do_propagation = true;
+               }
+       }
+       return 0;
+ }
+ static int isp_pipeline_wait_resizer(struct isp_device *isp)
+ {
+       return omap3isp_resizer_busy(&isp->isp_res);
+ }
+ static int isp_pipeline_wait_preview(struct isp_device *isp)
+ {
+       return omap3isp_preview_busy(&isp->isp_prev);
+ }
+ static int isp_pipeline_wait_ccdc(struct isp_device *isp)
+ {
+       return omap3isp_stat_busy(&isp->isp_af)
+           || omap3isp_stat_busy(&isp->isp_aewb)
+           || omap3isp_stat_busy(&isp->isp_hist)
+           || omap3isp_ccdc_busy(&isp->isp_ccdc);
+ }
+ #define ISP_STOP_TIMEOUT      msecs_to_jiffies(1000)
+ static int isp_pipeline_wait(struct isp_device *isp,
+                            int(*busy)(struct isp_device *isp))
+ {
+       unsigned long timeout = jiffies + ISP_STOP_TIMEOUT;
+       while (!time_after(jiffies, timeout)) {
+               if (!busy(isp))
+                       return 0;
+       }
+       return 1;
+ }
+ /*
+  * isp_pipeline_disable - Disable streaming on a pipeline
+  * @pipe: ISP 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.
+  *
+  * Return 0 if all modules have been properly stopped, or -ETIMEDOUT if a module
+  * can't be stopped (in which case a software reset of the ISP is probably
+  * necessary).
+  */
+ static int isp_pipeline_disable(struct isp_pipeline *pipe)
+ {
+       struct isp_device *isp = pipe->output->isp;
+       struct media_entity *entity;
+       struct media_pad *pad;
+       struct v4l2_subdev *subdev;
+       int failure = 0;
+       int ret;
+       /*
+        * We need to stop all the modules after CCDC first or they'll
+        * never stop since they may not get a full frame from CCDC.
+        */
+       entity = &pipe->output->video.entity;
+       while (1) {
+               pad = &entity->pads[0];
+               if (!(pad->flags & MEDIA_PAD_FL_SINK))
+                       break;
+               pad = media_entity_remote_source(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);
+               if (subdev == &isp->isp_ccdc.subdev) {
+                       v4l2_subdev_call(&isp->isp_aewb.subdev,
+                                        video, s_stream, 0);
+                       v4l2_subdev_call(&isp->isp_af.subdev,
+                                        video, s_stream, 0);
+                       v4l2_subdev_call(&isp->isp_hist.subdev,
+                                        video, s_stream, 0);
+               }
+               v4l2_subdev_call(subdev, video, s_stream, 0);
+               if (subdev == &isp->isp_res.subdev)
+                       ret = isp_pipeline_wait(isp, isp_pipeline_wait_resizer);
+               else if (subdev == &isp->isp_prev.subdev)
+                       ret = isp_pipeline_wait(isp, isp_pipeline_wait_preview);
+               else if (subdev == &isp->isp_ccdc.subdev)
+                       ret = isp_pipeline_wait(isp, isp_pipeline_wait_ccdc);
+               else
+                       ret = 0;
+               if (ret) {
+                       dev_info(isp->dev, "Unable to stop %s\n", subdev->name);
+                       /* If the entity failed to stopped, assume it has
+                        * crashed. Mark it as such, the ISP will be reset when
+                        * applications will release it.
+                        */
+                       isp->crashed |= 1U << subdev->entity.id;
+                       failure = -ETIMEDOUT;
+               }
+       }
+       return failure;
+ }
+ /*
+  * omap3isp_pipeline_set_stream - Enable/disable streaming on a pipeline
+  * @pipe: ISP 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 omap3isp_pipeline_set_stream(struct isp_pipeline *pipe,
+                                enum isp_pipeline_stream_state state)
+ {
+       int ret;
+       if (state == ISP_PIPELINE_STREAM_STOPPED)
+               ret = isp_pipeline_disable(pipe);
+       else
+               ret = isp_pipeline_enable(pipe, state);
+       if (ret == 0 || state == ISP_PIPELINE_STREAM_STOPPED)
+               pipe->stream_state = state;
+       return ret;
+ }
+ /*
+  * isp_pipeline_resume - Resume streaming on a pipeline
+  * @pipe: ISP pipeline
+  *
+  * Resume video output and input and re-enable pipeline.
+  */
+ static void isp_pipeline_resume(struct isp_pipeline *pipe)
+ {
+       int singleshot = pipe->stream_state == ISP_PIPELINE_STREAM_SINGLESHOT;
+       omap3isp_video_resume(pipe->output, !singleshot);
+       if (singleshot)
+               omap3isp_video_resume(pipe->input, 0);
+       isp_pipeline_enable(pipe, pipe->stream_state);
+ }
+ /*
+  * isp_pipeline_suspend - Suspend streaming on a pipeline
+  * @pipe: ISP pipeline
+  *
+  * Suspend pipeline.
+  */
+ static void isp_pipeline_suspend(struct isp_pipeline *pipe)
+ {
+       isp_pipeline_disable(pipe);
+ }
+ /*
+  * isp_pipeline_is_last - Verify if entity has an enabled link to the output
+  *                      video node
+  * @me: ISP 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 isp_pipeline_is_last(struct media_entity *me)
+ {
+       struct isp_pipeline *pipe;
+       struct media_pad *pad;
+       if (!me->pipe)
+               return 0;
+       pipe = to_isp_pipeline(me);
+       if (pipe->stream_state == ISP_PIPELINE_STREAM_STOPPED)
+               return 0;
+       pad = media_entity_remote_source(&pipe->output->pad);
+       return pad->entity == me;
+ }
+ /*
+  * isp_suspend_module_pipeline - Suspend pipeline to which belongs the module
+  * @me: ISP module's media entity
+  *
+  * Suspend the whole pipeline if module's entity has an enabled link to the
+  * output video node. It works only while pipeline can have no more than one
+  * output node.
+  */
+ static void isp_suspend_module_pipeline(struct media_entity *me)
+ {
+       if (isp_pipeline_is_last(me))
+               isp_pipeline_suspend(to_isp_pipeline(me));
+ }
+ /*
+  * isp_resume_module_pipeline - Resume pipeline to which belongs the module
+  * @me: ISP module's media entity
+  *
+  * Resume the whole pipeline if module's entity has an enabled link to the
+  * output video node. It works only while pipeline can have no more than one
+  * output node.
+  */
+ static void isp_resume_module_pipeline(struct media_entity *me)
+ {
+       if (isp_pipeline_is_last(me))
+               isp_pipeline_resume(to_isp_pipeline(me));
+ }
+ /*
+  * isp_suspend_modules - Suspend ISP submodules.
+  * @isp: OMAP3 ISP device
+  *
+  * Returns 0 if suspend left in idle state all the submodules properly,
+  * or returns 1 if a general Reset is required to suspend the submodules.
+  */
+ static int isp_suspend_modules(struct isp_device *isp)
+ {
+       unsigned long timeout;
+       omap3isp_stat_suspend(&isp->isp_aewb);
+       omap3isp_stat_suspend(&isp->isp_af);
+       omap3isp_stat_suspend(&isp->isp_hist);
+       isp_suspend_module_pipeline(&isp->isp_res.subdev.entity);
+       isp_suspend_module_pipeline(&isp->isp_prev.subdev.entity);
+       isp_suspend_module_pipeline(&isp->isp_ccdc.subdev.entity);
+       isp_suspend_module_pipeline(&isp->isp_csi2a.subdev.entity);
+       isp_suspend_module_pipeline(&isp->isp_ccp2.subdev.entity);
+       timeout = jiffies + ISP_STOP_TIMEOUT;
+       while (omap3isp_stat_busy(&isp->isp_af)
+           || omap3isp_stat_busy(&isp->isp_aewb)
+           || omap3isp_stat_busy(&isp->isp_hist)
+           || omap3isp_preview_busy(&isp->isp_prev)
+           || omap3isp_resizer_busy(&isp->isp_res)
+           || omap3isp_ccdc_busy(&isp->isp_ccdc)) {
+               if (time_after(jiffies, timeout)) {
+                       dev_info(isp->dev, "can't stop modules.\n");
+                       return 1;
+               }
+               msleep(1);
+       }
+       return 0;
+ }
+ /*
+  * isp_resume_modules - Resume ISP submodules.
+  * @isp: OMAP3 ISP device
+  */
+ static void isp_resume_modules(struct isp_device *isp)
+ {
+       omap3isp_stat_resume(&isp->isp_aewb);
+       omap3isp_stat_resume(&isp->isp_af);
+       omap3isp_stat_resume(&isp->isp_hist);
+       isp_resume_module_pipeline(&isp->isp_res.subdev.entity);
+       isp_resume_module_pipeline(&isp->isp_prev.subdev.entity);
+       isp_resume_module_pipeline(&isp->isp_ccdc.subdev.entity);
+       isp_resume_module_pipeline(&isp->isp_csi2a.subdev.entity);
+       isp_resume_module_pipeline(&isp->isp_ccp2.subdev.entity);
+ }
+ /*
+  * isp_reset - Reset ISP with a timeout wait for idle.
+  * @isp: OMAP3 ISP device
+  */
+ static int isp_reset(struct isp_device *isp)
+ {
+       unsigned long timeout = 0;
+       isp_reg_writel(isp,
+                      isp_reg_readl(isp, OMAP3_ISP_IOMEM_MAIN, ISP_SYSCONFIG)
+                      | ISP_SYSCONFIG_SOFTRESET,
+                      OMAP3_ISP_IOMEM_MAIN, ISP_SYSCONFIG);
+       while (!(isp_reg_readl(isp, OMAP3_ISP_IOMEM_MAIN,
+                              ISP_SYSSTATUS) & 0x1)) {
+               if (timeout++ > 10000) {
+                       dev_alert(isp->dev, "cannot reset ISP\n");
+                       return -ETIMEDOUT;
+               }
+               udelay(1);
+       }
+       isp->crashed = 0;
+       return 0;
+ }
+ /*
+  * isp_save_context - Saves the values of the ISP module registers.
+  * @isp: OMAP3 ISP device
+  * @reg_list: Structure containing pairs of register address and value to
+  *            modify on OMAP.
+  */
+ static void
+ isp_save_context(struct isp_device *isp, struct isp_reg *reg_list)
+ {
+       struct isp_reg *next = reg_list;
+       for (; next->reg != ISP_TOK_TERM; next++)
+               next->val = isp_reg_readl(isp, next->mmio_range, next->reg);
+ }
+ /*
+  * isp_restore_context - Restores the values of the ISP module registers.
+  * @isp: OMAP3 ISP device
+  * @reg_list: Structure containing pairs of register address and value to
+  *            modify on OMAP.
+  */
+ static void
+ isp_restore_context(struct isp_device *isp, struct isp_reg *reg_list)
+ {
+       struct isp_reg *next = reg_list;
+       for (; next->reg != ISP_TOK_TERM; next++)
+               isp_reg_writel(isp, next->val, next->mmio_range, next->reg);
+ }
+ /*
+  * isp_save_ctx - Saves ISP, CCDC, HIST, H3A, PREV, RESZ & MMU context.
+  * @isp: OMAP3 ISP device
+  *
+  * Routine for saving the context of each module in the ISP.
+  * CCDC, HIST, H3A, PREV, RESZ and MMU.
+  */
+ static void isp_save_ctx(struct isp_device *isp)
+ {
+       isp_save_context(isp, isp_reg_list);
+       omap_iommu_save_ctx(isp->dev);
+ }
+ /*
+  * isp_restore_ctx - Restores ISP, CCDC, HIST, H3A, PREV, RESZ & MMU context.
+  * @isp: OMAP3 ISP device
+  *
+  * Routine for restoring the context of each module in the ISP.
+  * CCDC, HIST, H3A, PREV, RESZ and MMU.
+  */
+ static void isp_restore_ctx(struct isp_device *isp)
+ {
+       isp_restore_context(isp, isp_reg_list);
+       omap_iommu_restore_ctx(isp->dev);
+       omap3isp_ccdc_restore_context(isp);
+       omap3isp_preview_restore_context(isp);
+ }
+ /* -----------------------------------------------------------------------------
+  * SBL resources management
+  */
+ #define OMAP3_ISP_SBL_READ    (OMAP3_ISP_SBL_CSI1_READ | \
+                                OMAP3_ISP_SBL_CCDC_LSC_READ | \
+                                OMAP3_ISP_SBL_PREVIEW_READ | \
+                                OMAP3_ISP_SBL_RESIZER_READ)
+ #define OMAP3_ISP_SBL_WRITE   (OMAP3_ISP_SBL_CSI1_WRITE | \
+                                OMAP3_ISP_SBL_CSI2A_WRITE | \
+                                OMAP3_ISP_SBL_CSI2C_WRITE | \
+                                OMAP3_ISP_SBL_CCDC_WRITE | \
+                                OMAP3_ISP_SBL_PREVIEW_WRITE)
+ void omap3isp_sbl_enable(struct isp_device *isp, enum isp_sbl_resource res)
+ {
+       u32 sbl = 0;
+       isp->sbl_resources |= res;
+       if (isp->sbl_resources & OMAP3_ISP_SBL_CSI1_READ)
+               sbl |= ISPCTRL_SBL_SHARED_RPORTA;
+       if (isp->sbl_resources & OMAP3_ISP_SBL_CCDC_LSC_READ)
+               sbl |= ISPCTRL_SBL_SHARED_RPORTB;
+       if (isp->sbl_resources & OMAP3_ISP_SBL_CSI2C_WRITE)
+               sbl |= ISPCTRL_SBL_SHARED_WPORTC;
+       if (isp->sbl_resources & OMAP3_ISP_SBL_RESIZER_WRITE)
+               sbl |= ISPCTRL_SBL_WR0_RAM_EN;
+       if (isp->sbl_resources & OMAP3_ISP_SBL_WRITE)
+               sbl |= ISPCTRL_SBL_WR1_RAM_EN;
+       if (isp->sbl_resources & OMAP3_ISP_SBL_READ)
+               sbl |= ISPCTRL_SBL_RD_RAM_EN;
+       isp_reg_set(isp, OMAP3_ISP_IOMEM_MAIN, ISP_CTRL, sbl);
+ }
+ void omap3isp_sbl_disable(struct isp_device *isp, enum isp_sbl_resource res)
+ {
+       u32 sbl = 0;
+       isp->sbl_resources &= ~res;
+       if (!(isp->sbl_resources & OMAP3_ISP_SBL_CSI1_READ))
+               sbl |= ISPCTRL_SBL_SHARED_RPORTA;
+       if (!(isp->sbl_resources & OMAP3_ISP_SBL_CCDC_LSC_READ))
+               sbl |= ISPCTRL_SBL_SHARED_RPORTB;
+       if (!(isp->sbl_resources & OMAP3_ISP_SBL_CSI2C_WRITE))
+               sbl |= ISPCTRL_SBL_SHARED_WPORTC;
+       if (!(isp->sbl_resources & OMAP3_ISP_SBL_RESIZER_WRITE))
+               sbl |= ISPCTRL_SBL_WR0_RAM_EN;
+       if (!(isp->sbl_resources & OMAP3_ISP_SBL_WRITE))
+               sbl |= ISPCTRL_SBL_WR1_RAM_EN;
+       if (!(isp->sbl_resources & OMAP3_ISP_SBL_READ))
+               sbl |= ISPCTRL_SBL_RD_RAM_EN;
+       isp_reg_clr(isp, OMAP3_ISP_IOMEM_MAIN, ISP_CTRL, sbl);
+ }
+ /*
+  * isp_module_sync_idle - Helper to sync module with its idle state
+  * @me: ISP submodule's media entity
+  * @wait: ISP submodule's wait queue for streamoff/interrupt synchronization
+  * @stopping: flag which tells module wants to stop
+  *
+  * This function checks if ISP submodule needs to wait for next interrupt. If
+  * yes, makes the caller to sleep while waiting for such event.
+  */
+ int omap3isp_module_sync_idle(struct media_entity *me, wait_queue_head_t *wait,
+                             atomic_t *stopping)
+ {
+       struct isp_pipeline *pipe = to_isp_pipeline(me);
+       if (pipe->stream_state == ISP_PIPELINE_STREAM_STOPPED ||
+           (pipe->stream_state == ISP_PIPELINE_STREAM_SINGLESHOT &&
+            !isp_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_mb();
+       /*
+        * 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 (isp_pipeline_is_last(me)) {
+               struct isp_video *video = pipe->output;
+               unsigned long flags;
+               spin_lock_irqsave(&video->queue->irqlock, flags);
+               if (video->dmaqueue_flags & ISP_VIDEO_DMAQUEUE_UNDERRUN) {
+                       spin_unlock_irqrestore(&video->queue->irqlock, flags);
+                       atomic_set(stopping, 0);
+                       smp_mb();
+                       return 0;
+               }
+               spin_unlock_irqrestore(&video->queue->irqlock, flags);
+               if (!wait_event_timeout(*wait, !atomic_read(stopping),
+                                       msecs_to_jiffies(1000))) {
+                       atomic_set(stopping, 0);
+                       smp_mb();
+                       return -ETIMEDOUT;
+               }
+       }
+       return 0;
+ }
+ /*
+  * omap3isp_module_sync_is_stopped - Helper to verify if module was stopping
+  * @wait: ISP submodule's wait queue for streamoff/interrupt synchronization
+  * @stopping: flag which tells module wants to stop
+  *
+  * This function checks if ISP 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 omap3isp_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 ISPCTRL_CLKS_MASK     (ISPCTRL_H3A_CLK_EN | \
+                                ISPCTRL_HIST_CLK_EN | \
+                                ISPCTRL_RSZ_CLK_EN | \
+                                (ISPCTRL_CCDC_CLK_EN | ISPCTRL_CCDC_RAM_EN) | \
+                                (ISPCTRL_PREV_CLK_EN | ISPCTRL_PREV_RAM_EN))
+ static void __isp_subclk_update(struct isp_device *isp)
+ {
+       u32 clk = 0;
+       /* AEWB and AF share the same clock. */
+       if (isp->subclk_resources &
+           (OMAP3_ISP_SUBCLK_AEWB | OMAP3_ISP_SUBCLK_AF))
+               clk |= ISPCTRL_H3A_CLK_EN;
+       if (isp->subclk_resources & OMAP3_ISP_SUBCLK_HIST)
+               clk |= ISPCTRL_HIST_CLK_EN;
+       if (isp->subclk_resources & OMAP3_ISP_SUBCLK_RESIZER)
+               clk |= ISPCTRL_RSZ_CLK_EN;
+       /* NOTE: For CCDC & Preview submodules, we need to affect internal
+        *       RAM as well.
+        */
+       if (isp->subclk_resources & OMAP3_ISP_SUBCLK_CCDC)
+               clk |= ISPCTRL_CCDC_CLK_EN | ISPCTRL_CCDC_RAM_EN;
+       if (isp->subclk_resources & OMAP3_ISP_SUBCLK_PREVIEW)
+               clk |= ISPCTRL_PREV_CLK_EN | ISPCTRL_PREV_RAM_EN;
+       isp_reg_clr_set(isp, OMAP3_ISP_IOMEM_MAIN, ISP_CTRL,
+                       ISPCTRL_CLKS_MASK, clk);
+ }
+ void omap3isp_subclk_enable(struct isp_device *isp,
+                           enum isp_subclk_resource res)
+ {
+       isp->subclk_resources |= res;
+       __isp_subclk_update(isp);
+ }
+ void omap3isp_subclk_disable(struct isp_device *isp,
+                            enum isp_subclk_resource res)
+ {
+       isp->subclk_resources &= ~res;
+       __isp_subclk_update(isp);
+ }
+ /*
+  * isp_enable_clocks - Enable ISP clocks
+  * @isp: OMAP3 ISP device
+  *
+  * Return 0 if successful, or clk_enable return value if any of tthem fails.
+  */
+ static int isp_enable_clocks(struct isp_device *isp)
+ {
+       int r;
+       unsigned long rate;
+       int divisor;
+       /*
+        * cam_mclk clock chain:
+        *   dpll4 -> dpll4_m5 -> dpll4_m5x2 -> cam_mclk
+        *
+        * In OMAP3630 dpll4_m5x2 != 2 x dpll4_m5 but both are
+        * set to the same value. Hence the rate set for dpll4_m5
+        * has to be twice of what is set on OMAP3430 to get
+        * the required value for cam_mclk
+        */
+       if (cpu_is_omap3630())
+               divisor = 1;
+       else
+               divisor = 2;
+       r = clk_enable(isp->clock[ISP_CLK_CAM_ICK]);
+       if (r) {
+               dev_err(isp->dev, "clk_enable cam_ick failed\n");
+               goto out_clk_enable_ick;
+       }
+       r = clk_set_rate(isp->clock[ISP_CLK_DPLL4_M5_CK],
+                        CM_CAM_MCLK_HZ/divisor);
+       if (r) {
+               dev_err(isp->dev, "clk_set_rate for dpll4_m5_ck failed\n");
+               goto out_clk_enable_mclk;
+       }
+       r = clk_enable(isp->clock[ISP_CLK_CAM_MCLK]);
+       if (r) {
+               dev_err(isp->dev, "clk_enable cam_mclk failed\n");
+               goto out_clk_enable_mclk;
+       }
+       rate = clk_get_rate(isp->clock[ISP_CLK_CAM_MCLK]);
+       if (rate != CM_CAM_MCLK_HZ)
+               dev_warn(isp->dev, "unexpected cam_mclk rate:\n"
+                                  " expected : %d\n"
+                                  " actual   : %ld\n", CM_CAM_MCLK_HZ, rate);
+       r = clk_enable(isp->clock[ISP_CLK_CSI2_FCK]);
+       if (r) {
+               dev_err(isp->dev, "clk_enable csi2_fck failed\n");
+               goto out_clk_enable_csi2_fclk;
+       }
+       return 0;
+ out_clk_enable_csi2_fclk:
+       clk_disable(isp->clock[ISP_CLK_CAM_MCLK]);
+ out_clk_enable_mclk:
+       clk_disable(isp->clock[ISP_CLK_CAM_ICK]);
+ out_clk_enable_ick:
+       return r;
+ }
+ /*
+  * isp_disable_clocks - Disable ISP clocks
+  * @isp: OMAP3 ISP device
+  */
+ static void isp_disable_clocks(struct isp_device *isp)
+ {
+       clk_disable(isp->clock[ISP_CLK_CAM_ICK]);
+       clk_disable(isp->clock[ISP_CLK_CAM_MCLK]);
+       clk_disable(isp->clock[ISP_CLK_CSI2_FCK]);
+ }
+ static const char *isp_clocks[] = {
+       "cam_ick",
+       "cam_mclk",
+       "dpll4_m5_ck",
+       "csi2_96m_fck",
+       "l3_ick",
+ };
+ static void isp_put_clocks(struct isp_device *isp)
+ {
+       unsigned int i;
+       for (i = 0; i < ARRAY_SIZE(isp_clocks); ++i) {
+               if (isp->clock[i]) {
+                       clk_put(isp->clock[i]);
+                       isp->clock[i] = NULL;
+               }
+       }
+ }
+ static int isp_get_clocks(struct isp_device *isp)
+ {
+       struct clk *clk;
+       unsigned int i;
+       for (i = 0; i < ARRAY_SIZE(isp_clocks); ++i) {
+               clk = clk_get(isp->dev, isp_clocks[i]);
+               if (IS_ERR(clk)) {
+                       dev_err(isp->dev, "clk_get %s failed\n", isp_clocks[i]);
+                       isp_put_clocks(isp);
+                       return PTR_ERR(clk);
+               }
+               isp->clock[i] = clk;
+       }
+       return 0;
+ }
+ /*
+  * omap3isp_get - Acquire the ISP resource.
+  *
+  * Initializes the clocks for the first acquire.
+  *
+  * Increment the reference count on the ISP. If the first reference is taken,
+  * enable clocks and power-up all submodules.
+  *
+  * Return a pointer to the ISP device structure, or NULL if an error occurred.
+  */
+ static struct isp_device *__omap3isp_get(struct isp_device *isp, bool irq)
+ {
+       struct isp_device *__isp = isp;
+       if (isp == NULL)
+               return NULL;
+       mutex_lock(&isp->isp_mutex);
+       if (isp->ref_count > 0)
+               goto out;
+       if (isp_enable_clocks(isp) < 0) {
+               __isp = NULL;
+               goto out;
+       }
+       /* We don't want to restore context before saving it! */
+       if (isp->has_context)
+               isp_restore_ctx(isp);
+       if (irq)
+               isp_enable_interrupts(isp);
+ out:
+       if (__isp != NULL)
+               isp->ref_count++;
+       mutex_unlock(&isp->isp_mutex);
+       return __isp;
+ }
+ struct isp_device *omap3isp_get(struct isp_device *isp)
+ {
+       return __omap3isp_get(isp, true);
+ }
+ /*
+  * omap3isp_put - Release the ISP
+  *
+  * Decrement the reference count on the ISP. If the last reference is released,
+  * power-down all submodules, disable clocks and free temporary buffers.
+  */
+ void omap3isp_put(struct isp_device *isp)
+ {
+       if (isp == NULL)
+               return;
+       mutex_lock(&isp->isp_mutex);
+       BUG_ON(isp->ref_count == 0);
+       if (--isp->ref_count == 0) {
+               isp_disable_interrupts(isp);
+               if (isp->domain) {
+                       isp_save_ctx(isp);
+                       isp->has_context = 1;
+               }
+               /* Reset the ISP if an entity has failed to stop. This is the
+                * only way to recover from such conditions.
+                */
+               if (isp->crashed)
+                       isp_reset(isp);
+               isp_disable_clocks(isp);
+       }
+       mutex_unlock(&isp->isp_mutex);
+ }
+ /* --------------------------------------------------------------------------
+  * Platform device driver
+  */
+ /*
+  * omap3isp_print_status - Prints the values of the ISP Control Module registers
+  * @isp: OMAP3 ISP device
+  */
+ #define ISP_PRINT_REGISTER(isp, name)\
+       dev_dbg(isp->dev, "###ISP " #name "=0x%08x\n", \
+               isp_reg_readl(isp, OMAP3_ISP_IOMEM_MAIN, ISP_##name))
+ #define SBL_PRINT_REGISTER(isp, name)\
+       dev_dbg(isp->dev, "###SBL " #name "=0x%08x\n", \
+               isp_reg_readl(isp, OMAP3_ISP_IOMEM_SBL, ISPSBL_##name))
+ void omap3isp_print_status(struct isp_device *isp)
+ {
+       dev_dbg(isp->dev, "-------------ISP Register dump--------------\n");
+       ISP_PRINT_REGISTER(isp, SYSCONFIG);
+       ISP_PRINT_REGISTER(isp, SYSSTATUS);
+       ISP_PRINT_REGISTER(isp, IRQ0ENABLE);
+       ISP_PRINT_REGISTER(isp, IRQ0STATUS);
+       ISP_PRINT_REGISTER(isp, TCTRL_GRESET_LENGTH);
+       ISP_PRINT_REGISTER(isp, TCTRL_PSTRB_REPLAY);
+       ISP_PRINT_REGISTER(isp, CTRL);
+       ISP_PRINT_REGISTER(isp, TCTRL_CTRL);
+       ISP_PRINT_REGISTER(isp, TCTRL_FRAME);
+       ISP_PRINT_REGISTER(isp, TCTRL_PSTRB_DELAY);
+       ISP_PRINT_REGISTER(isp, TCTRL_STRB_DELAY);
+       ISP_PRINT_REGISTER(isp, TCTRL_SHUT_DELAY);
+       ISP_PRINT_REGISTER(isp, TCTRL_PSTRB_LENGTH);
+       ISP_PRINT_REGISTER(isp, TCTRL_STRB_LENGTH);
+       ISP_PRINT_REGISTER(isp, TCTRL_SHUT_LENGTH);
+       SBL_PRINT_REGISTER(isp, PCR);
+       SBL_PRINT_REGISTER(isp, SDR_REQ_EXP);
+       dev_dbg(isp->dev, "--------------------------------------------\n");
+ }
+ #ifdef CONFIG_PM
+ /*
+  * Power management support.
+  *
+  * As the ISP can't properly handle an input video stream interruption on a non
+  * frame boundary, the ISP pipelines need to be stopped before sensors get
+  * suspended. However, as suspending the sensors can require a running clock,
+  * which can be provided by the ISP, the ISP can't be completely suspended
+  * before the sensor.
+  *
+  * To solve this problem power management support is split into prepare/complete
+  * and suspend/resume operations. The pipelines are stopped in prepare() and the
+  * ISP clocks get disabled in suspend(). Similarly, the clocks are reenabled in
+  * resume(), and the the pipelines are restarted in complete().
+  *
+  * TODO: PM dependencies between the ISP and sensors are not modeled explicitly
+  * yet.
+  */
+ static int isp_pm_prepare(struct device *dev)
+ {
+       struct isp_device *isp = dev_get_drvdata(dev);
+       int reset;
+       WARN_ON(mutex_is_locked(&isp->isp_mutex));
+       if (isp->ref_count == 0)
+               return 0;
+       reset = isp_suspend_modules(isp);
+       isp_disable_interrupts(isp);
+       isp_save_ctx(isp);
+       if (reset)
+               isp_reset(isp);
+       return 0;
+ }
+ static int isp_pm_suspend(struct device *dev)
+ {
+       struct isp_device *isp = dev_get_drvdata(dev);
+       WARN_ON(mutex_is_locked(&isp->isp_mutex));
+       if (isp->ref_count)
+               isp_disable_clocks(isp);
+       return 0;
+ }
+ static int isp_pm_resume(struct device *dev)
+ {
+       struct isp_device *isp = dev_get_drvdata(dev);
+       if (isp->ref_count == 0)
+               return 0;
+       return isp_enable_clocks(isp);
+ }
+ static void isp_pm_complete(struct device *dev)
+ {
+       struct isp_device *isp = dev_get_drvdata(dev);
+       if (isp->ref_count == 0)
+               return;
+       isp_restore_ctx(isp);
+       isp_enable_interrupts(isp);
+       isp_resume_modules(isp);
+ }
+ #else
+ #define isp_pm_prepare        NULL
+ #define isp_pm_suspend        NULL
+ #define isp_pm_resume NULL
+ #define isp_pm_complete       NULL
+ #endif /* CONFIG_PM */
+ static void isp_unregister_entities(struct isp_device *isp)
+ {
+       omap3isp_csi2_unregister_entities(&isp->isp_csi2a);
+       omap3isp_ccp2_unregister_entities(&isp->isp_ccp2);
+       omap3isp_ccdc_unregister_entities(&isp->isp_ccdc);
+       omap3isp_preview_unregister_entities(&isp->isp_prev);
+       omap3isp_resizer_unregister_entities(&isp->isp_res);
+       omap3isp_stat_unregister_entities(&isp->isp_aewb);
+       omap3isp_stat_unregister_entities(&isp->isp_af);
+       omap3isp_stat_unregister_entities(&isp->isp_hist);
+       v4l2_device_unregister(&isp->v4l2_dev);
+       media_device_unregister(&isp->media_dev);
+ }
+ /*
+  * isp_register_subdev_group - Register a group of subdevices
+  * @isp: OMAP3 ISP 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 *
+ isp_register_subdev_group(struct isp_device *isp,
+                    struct isp_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) {
+                       printk(KERN_ERR "%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(&isp->v4l2_dev, adapter,
+                               board_info->board_info, NULL);
+               if (subdev == NULL) {
+                       printk(KERN_ERR "%s: Unable to register subdev %s\n",
+                               __func__, board_info->board_info->type);
+                       continue;
+               }
+               if (first)
+                       sensor = subdev;
+       }
+       return sensor;
+ }
+ static int isp_register_entities(struct isp_device *isp)
+ {
+       struct isp_platform_data *pdata = isp->pdata;
+       struct isp_v4l2_subdevs_group *subdevs;
+       int ret;
+       isp->media_dev.dev = isp->dev;
+       strlcpy(isp->media_dev.model, "TI OMAP3 ISP",
+               sizeof(isp->media_dev.model));
+       isp->media_dev.hw_revision = isp->revision;
+       isp->media_dev.link_notify = isp_pipeline_link_notify;
+       ret = media_device_register(&isp->media_dev);
+       if (ret < 0) {
+               printk(KERN_ERR "%s: Media device registration failed (%d)\n",
+                       __func__, ret);
+               return ret;
+       }
+       isp->v4l2_dev.mdev = &isp->media_dev;
+       ret = v4l2_device_register(isp->dev, &isp->v4l2_dev);
+       if (ret < 0) {
+               printk(KERN_ERR "%s: V4L2 device registration failed (%d)\n",
+                       __func__, ret);
+               goto done;
+       }
+       /* Register internal entities */
+       ret = omap3isp_ccp2_register_entities(&isp->isp_ccp2, &isp->v4l2_dev);
+       if (ret < 0)
+               goto done;
+       ret = omap3isp_csi2_register_entities(&isp->isp_csi2a, &isp->v4l2_dev);
+       if (ret < 0)
+               goto done;
+       ret = omap3isp_ccdc_register_entities(&isp->isp_ccdc, &isp->v4l2_dev);
+       if (ret < 0)
+               goto done;
+       ret = omap3isp_preview_register_entities(&isp->isp_prev,
+                                                &isp->v4l2_dev);
+       if (ret < 0)
+               goto done;
+       ret = omap3isp_resizer_register_entities(&isp->isp_res, &isp->v4l2_dev);
+       if (ret < 0)
+               goto done;
+       ret = omap3isp_stat_register_entities(&isp->isp_aewb, &isp->v4l2_dev);
+       if (ret < 0)
+               goto done;
+       ret = omap3isp_stat_register_entities(&isp->isp_af, &isp->v4l2_dev);
+       if (ret < 0)
+               goto done;
+       ret = omap3isp_stat_register_entities(&isp->isp_hist, &isp->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 = isp_register_subdev_group(isp, subdevs->subdevs);
+               if (sensor == NULL)
+                       continue;
+               sensor->host_priv = subdevs;
+               /* Connect the sensor to the correct interface module. Parallel
+                * sensors are connected directly to the CCDC, while serial
+                * sensors are connected to the CSI2a, CCP2b or CSI2c receiver
+                * through CSIPHY1 or CSIPHY2.
+                */
+               switch (subdevs->interface) {
+               case ISP_INTERFACE_PARALLEL:
+                       input = &isp->isp_ccdc.subdev.entity;
+                       pad = CCDC_PAD_SINK;
+                       flags = 0;
+                       break;
+               case ISP_INTERFACE_CSI2A_PHY2:
+                       input = &isp->isp_csi2a.subdev.entity;
+                       pad = CSI2_PAD_SINK;
+                       flags = MEDIA_LNK_FL_IMMUTABLE
+                             | MEDIA_LNK_FL_ENABLED;
+                       break;
+               case ISP_INTERFACE_CCP2B_PHY1:
+               case ISP_INTERFACE_CCP2B_PHY2:
+                       input = &isp->isp_ccp2.subdev.entity;
+                       pad = CCP2_PAD_SINK;
+                       flags = 0;
+                       break;
+               case ISP_INTERFACE_CSI2C_PHY1:
+                       input = &isp->isp_csi2c.subdev.entity;
+                       pad = CSI2_PAD_SINK;
+                       flags = MEDIA_LNK_FL_IMMUTABLE
+                             | MEDIA_LNK_FL_ENABLED;
+                       break;
+               default:
+                       printk(KERN_ERR "%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(&isp->v4l2_dev);
+ done:
+       if (ret < 0)
+               isp_unregister_entities(isp);
+       return ret;
+ }
+ static void isp_cleanup_modules(struct isp_device *isp)
+ {
+       omap3isp_h3a_aewb_cleanup(isp);
+       omap3isp_h3a_af_cleanup(isp);
+       omap3isp_hist_cleanup(isp);
+       omap3isp_resizer_cleanup(isp);
+       omap3isp_preview_cleanup(isp);
+       omap3isp_ccdc_cleanup(isp);
+       omap3isp_ccp2_cleanup(isp);
+       omap3isp_csi2_cleanup(isp);
+ }
+ static int isp_initialize_modules(struct isp_device *isp)
+ {
+       int ret;
+       ret = omap3isp_csiphy_init(isp);
+       if (ret < 0) {
+               dev_err(isp->dev, "CSI PHY initialization failed\n");
+               goto error_csiphy;
+       }
+       ret = omap3isp_csi2_init(isp);
+       if (ret < 0) {
+               dev_err(isp->dev, "CSI2 initialization failed\n");
+               goto error_csi2;
+       }
+       ret = omap3isp_ccp2_init(isp);
+       if (ret < 0) {
+               dev_err(isp->dev, "CCP2 initialization failed\n");
+               goto error_ccp2;
+       }
+       ret = omap3isp_ccdc_init(isp);
+       if (ret < 0) {
+               dev_err(isp->dev, "CCDC initialization failed\n");
+               goto error_ccdc;
+       }
+       ret = omap3isp_preview_init(isp);
+       if (ret < 0) {
+               dev_err(isp->dev, "Preview initialization failed\n");
+               goto error_preview;
+       }
+       ret = omap3isp_resizer_init(isp);
+       if (ret < 0) {
+               dev_err(isp->dev, "Resizer initialization failed\n");
+               goto error_resizer;
+       }
+       ret = omap3isp_hist_init(isp);
+       if (ret < 0) {
+               dev_err(isp->dev, "Histogram initialization failed\n");
+               goto error_hist;
+       }
+       ret = omap3isp_h3a_aewb_init(isp);
+       if (ret < 0) {
+               dev_err(isp->dev, "H3A AEWB initialization failed\n");
+               goto error_h3a_aewb;
+       }
+       ret = omap3isp_h3a_af_init(isp);
+       if (ret < 0) {
+               dev_err(isp->dev, "H3A AF initialization failed\n");
+               goto error_h3a_af;
+       }
+       /* Connect the submodules. */
+       ret = media_entity_create_link(
+                       &isp->isp_csi2a.subdev.entity, CSI2_PAD_SOURCE,
+                       &isp->isp_ccdc.subdev.entity, CCDC_PAD_SINK, 0);
+       if (ret < 0)
+               goto error_link;
+       ret = media_entity_create_link(
+                       &isp->isp_ccp2.subdev.entity, CCP2_PAD_SOURCE,
+                       &isp->isp_ccdc.subdev.entity, CCDC_PAD_SINK, 0);
+       if (ret < 0)
+               goto error_link;
+       ret = media_entity_create_link(
+                       &isp->isp_ccdc.subdev.entity, CCDC_PAD_SOURCE_VP,
+                       &isp->isp_prev.subdev.entity, PREV_PAD_SINK, 0);
+       if (ret < 0)
+               goto error_link;
+       ret = media_entity_create_link(
+                       &isp->isp_ccdc.subdev.entity, CCDC_PAD_SOURCE_OF,
+                       &isp->isp_res.subdev.entity, RESZ_PAD_SINK, 0);
+       if (ret < 0)
+               goto error_link;
+       ret = media_entity_create_link(
+                       &isp->isp_prev.subdev.entity, PREV_PAD_SOURCE,
+                       &isp->isp_res.subdev.entity, RESZ_PAD_SINK, 0);
+       if (ret < 0)
+               goto error_link;
+       ret = media_entity_create_link(
+                       &isp->isp_ccdc.subdev.entity, CCDC_PAD_SOURCE_VP,
+                       &isp->isp_aewb.subdev.entity, 0,
+                       MEDIA_LNK_FL_ENABLED | MEDIA_LNK_FL_IMMUTABLE);
+       if (ret < 0)
+               goto error_link;
+       ret = media_entity_create_link(
+                       &isp->isp_ccdc.subdev.entity, CCDC_PAD_SOURCE_VP,
+                       &isp->isp_af.subdev.entity, 0,
+                       MEDIA_LNK_FL_ENABLED | MEDIA_LNK_FL_IMMUTABLE);
+       if (ret < 0)
+               goto error_link;
+       ret = media_entity_create_link(
+                       &isp->isp_ccdc.subdev.entity, CCDC_PAD_SOURCE_VP,
+                       &isp->isp_hist.subdev.entity, 0,
+                       MEDIA_LNK_FL_ENABLED | MEDIA_LNK_FL_IMMUTABLE);
+       if (ret < 0)
+               goto error_link;
+       return 0;
+ error_link:
+       omap3isp_h3a_af_cleanup(isp);
+ error_h3a_af:
+       omap3isp_h3a_aewb_cleanup(isp);
+ error_h3a_aewb:
+       omap3isp_hist_cleanup(isp);
+ error_hist:
+       omap3isp_resizer_cleanup(isp);
+ error_resizer:
+       omap3isp_preview_cleanup(isp);
+ error_preview:
+       omap3isp_ccdc_cleanup(isp);
+ error_ccdc:
+       omap3isp_ccp2_cleanup(isp);
+ error_ccp2:
+       omap3isp_csi2_cleanup(isp);
+ error_csi2:
+ error_csiphy:
+       return ret;
+ }
+ /*
+  * isp_remove - Remove ISP platform device
+  * @pdev: Pointer to ISP platform device
+  *
+  * Always returns 0.
+  */
+ static int __devexit isp_remove(struct platform_device *pdev)
+ {
+       struct isp_device *isp = platform_get_drvdata(pdev);
+       int i;
+       isp_unregister_entities(isp);
+       isp_cleanup_modules(isp);
+       __omap3isp_get(isp, false);
+       iommu_detach_device(isp->domain, &pdev->dev);
+       iommu_domain_free(isp->domain);
+       isp->domain = NULL;
+       omap3isp_put(isp);
+       free_irq(isp->irq_num, isp);
+       isp_put_clocks(isp);
+       for (i = 0; i < OMAP3_ISP_IOMEM_LAST; i++) {
+               if (isp->mmio_base[i]) {
+                       iounmap(isp->mmio_base[i]);
+                       isp->mmio_base[i] = NULL;
+               }
+               if (isp->mmio_base_phys[i]) {
+                       release_mem_region(isp->mmio_base_phys[i],
+                                          isp->mmio_size[i]);
+                       isp->mmio_base_phys[i] = 0;
+               }
+       }
+       regulator_put(isp->isp_csiphy1.vdd);
+       regulator_put(isp->isp_csiphy2.vdd);
+       kfree(isp);
+       return 0;
+ }
+ static int isp_map_mem_resource(struct platform_device *pdev,
+                               struct isp_device *isp,
+                               enum isp_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(isp->dev, "no mem resource?\n");
+               return -ENODEV;
+       }
+       if (!request_mem_region(mem->start, resource_size(mem), pdev->name)) {
+               dev_err(isp->dev,
+                       "cannot reserve camera register I/O region\n");
+               return -ENODEV;
+       }
+       isp->mmio_base_phys[res] = mem->start;
+       isp->mmio_size[res] = resource_size(mem);
+       /* map the region */
+       isp->mmio_base[res] = ioremap_nocache(isp->mmio_base_phys[res],
+                                             isp->mmio_size[res]);
+       if (!isp->mmio_base[res]) {
+               dev_err(isp->dev, "cannot map camera register I/O region\n");
+               return -ENODEV;
+       }
+       return 0;
+ }
+ /*
+  * isp_probe - Probe ISP platform device
+  * @pdev: Pointer to ISP platform device
+  *
+  * Returns 0 if successful,
+  *   -ENOMEM if no memory available,
+  *   -ENODEV if no platform device resources found
+  *     or no space for remapping registers,
+  *   -EINVAL if couldn't install ISR,
+  *   or clk_get return error value.
+  */
+ static int __devinit isp_probe(struct platform_device *pdev)
+ {
+       struct isp_platform_data *pdata = pdev->dev.platform_data;
+       struct isp_device *isp;
+       int ret;
+       int i, m;
+       if (pdata == NULL)
+               return -EINVAL;
+       isp = kzalloc(sizeof(*isp), GFP_KERNEL);
+       if (!isp) {
+               dev_err(&pdev->dev, "could not allocate memory\n");
+               return -ENOMEM;
+       }
+       isp->autoidle = autoidle;
+       isp->platform_cb.set_xclk = isp_set_xclk;
+       mutex_init(&isp->isp_mutex);
+       spin_lock_init(&isp->stat_lock);
+       isp->dev = &pdev->dev;
+       isp->pdata = pdata;
+       isp->ref_count = 0;
+       isp->raw_dmamask = DMA_BIT_MASK(32);
+       isp->dev->dma_mask = &isp->raw_dmamask;
+       isp->dev->coherent_dma_mask = DMA_BIT_MASK(32);
+       platform_set_drvdata(pdev, isp);
+       /* Regulators */
+       isp->isp_csiphy1.vdd = regulator_get(&pdev->dev, "VDD_CSIPHY1");
+       isp->isp_csiphy2.vdd = regulator_get(&pdev->dev, "VDD_CSIPHY2");
+       /* Clocks */
+       ret = isp_map_mem_resource(pdev, isp, OMAP3_ISP_IOMEM_MAIN);
+       if (ret < 0)
+               goto error;
+       ret = isp_get_clocks(isp);
+       if (ret < 0)
+               goto error;
+       if (__omap3isp_get(isp, false) == NULL) {
+               ret = -ENODEV;
+               goto error;
+       }
+       ret = isp_reset(isp);
+       if (ret < 0)
+               goto error_isp;
+       /* Memory resources */
+       isp->revision = isp_reg_readl(isp, OMAP3_ISP_IOMEM_MAIN, ISP_REVISION);
+       dev_info(isp->dev, "Revision %d.%d found\n",
+                (isp->revision & 0xf0) >> 4, isp->revision & 0x0f);
+       for (m = 0; m < ARRAY_SIZE(isp_res_maps); m++)
+               if (isp->revision == isp_res_maps[m].isp_rev)
+                       break;
+       if (m == ARRAY_SIZE(isp_res_maps)) {
+               dev_err(isp->dev, "No resource map found for ISP rev %d.%d\n",
+                       (isp->revision & 0xf0) >> 4, isp->revision & 0xf);
+               ret = -ENODEV;
+               goto error_isp;
+       }
+       for (i = 1; i < OMAP3_ISP_IOMEM_LAST; i++) {
+               if (isp_res_maps[m].map & 1 << i) {
+                       ret = isp_map_mem_resource(pdev, isp, i);
+                       if (ret)
+                               goto error_isp;
+               }
+       }
+       isp->domain = iommu_domain_alloc(pdev->dev.bus);
+       if (!isp->domain) {
+               dev_err(isp->dev, "can't alloc iommu domain\n");
+               ret = -ENOMEM;
+               goto error_isp;
+       }
+       ret = iommu_attach_device(isp->domain, &pdev->dev);
+       if (ret) {
+               dev_err(&pdev->dev, "can't attach iommu device: %d\n", ret);
+               goto free_domain;
+       }
+       /* Interrupt */
+       isp->irq_num = platform_get_irq(pdev, 0);
+       if (isp->irq_num <= 0) {
+               dev_err(isp->dev, "No IRQ resource\n");
+               ret = -ENODEV;
+               goto detach_dev;
+       }
+       if (request_irq(isp->irq_num, isp_isr, IRQF_SHARED, "OMAP3 ISP", isp)) {
+               dev_err(isp->dev, "Unable to request IRQ\n");
+               ret = -EINVAL;
+               goto detach_dev;
+       }
+       /* Entities */
+       ret = isp_initialize_modules(isp);
+       if (ret < 0)
+               goto error_irq;
+       ret = isp_register_entities(isp);
+       if (ret < 0)
+               goto error_modules;
+       isp_core_init(isp, 1);
+       omap3isp_put(isp);
+       return 0;
+ error_modules:
+       isp_cleanup_modules(isp);
+ error_irq:
+       free_irq(isp->irq_num, isp);
+ detach_dev:
+       iommu_detach_device(isp->domain, &pdev->dev);
+ free_domain:
+       iommu_domain_free(isp->domain);
+ error_isp:
+       omap3isp_put(isp);
+ error:
+       isp_put_clocks(isp);
+       for (i = 0; i < OMAP3_ISP_IOMEM_LAST; i++) {
+               if (isp->mmio_base[i]) {
+                       iounmap(isp->mmio_base[i]);
+                       isp->mmio_base[i] = NULL;
+               }
+               if (isp->mmio_base_phys[i]) {
+                       release_mem_region(isp->mmio_base_phys[i],
+                                          isp->mmio_size[i]);
+                       isp->mmio_base_phys[i] = 0;
+               }
+       }
+       regulator_put(isp->isp_csiphy2.vdd);
+       regulator_put(isp->isp_csiphy1.vdd);
+       platform_set_drvdata(pdev, NULL);
+       mutex_destroy(&isp->isp_mutex);
+       kfree(isp);
+       return ret;
+ }
+ static const struct dev_pm_ops omap3isp_pm_ops = {
+       .prepare = isp_pm_prepare,
+       .suspend = isp_pm_suspend,
+       .resume = isp_pm_resume,
+       .complete = isp_pm_complete,
+ };
+ static struct platform_device_id omap3isp_id_table[] = {
+       { "omap3isp", 0 },
+       { },
+ };
+ MODULE_DEVICE_TABLE(platform, omap3isp_id_table);
+ static struct platform_driver omap3isp_driver = {
+       .probe = isp_probe,
+       .remove = __devexit_p(isp_remove),
+       .id_table = omap3isp_id_table,
+       .driver = {
+               .owner = THIS_MODULE,
+               .name = "omap3isp",
+               .pm     = &omap3isp_pm_ops,
+       },
+ };
+ module_platform_driver(omap3isp_driver);
+ MODULE_AUTHOR("Nokia Corporation");
+ MODULE_DESCRIPTION("TI OMAP3 ISP driver");
+ MODULE_LICENSE("GPL");
+ MODULE_VERSION(ISP_VIDEO_DRIVER_VERSION);
index 0000000000000000000000000000000000000000,8e4eed8414bd3e3e69b5b67562caaa738be11abc..e92236ac5cfe86e5787f0f95bc057a920d3d4255
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,842 +1,842 @@@
 -#include <plat/mipi_csis.h>
+ /*
+  * Samsung S5P/EXYNOS4 SoC series MIPI-CSI receiver driver
+  *
+  * Copyright (C) 2011 - 2012 Samsung Electronics Co., Ltd.
+  * Sylwester Nawrocki, <s.nawrocki@samsung.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.
+  */
+ #include <linux/clk.h>
+ #include <linux/delay.h>
+ #include <linux/device.h>
+ #include <linux/errno.h>
+ #include <linux/interrupt.h>
+ #include <linux/io.h>
+ #include <linux/irq.h>
+ #include <linux/kernel.h>
+ #include <linux/memory.h>
+ #include <linux/module.h>
+ #include <linux/platform_device.h>
+ #include <linux/pm_runtime.h>
+ #include <linux/regulator/consumer.h>
+ #include <linux/slab.h>
+ #include <linux/spinlock.h>
+ #include <linux/videodev2.h>
+ #include <media/v4l2-subdev.h>
++#include <linux/platform_data/mipi-csis.h>
+ #include "mipi-csis.h"
+ static int debug;
+ module_param(debug, int, 0644);
+ MODULE_PARM_DESC(debug, "Debug level (0-2)");
+ /* Register map definition */
+ /* CSIS global control */
+ #define S5PCSIS_CTRL                  0x00
+ #define S5PCSIS_CTRL_DPDN_DEFAULT     (0 << 31)
+ #define S5PCSIS_CTRL_DPDN_SWAP                (1 << 31)
+ #define S5PCSIS_CTRL_ALIGN_32BIT      (1 << 20)
+ #define S5PCSIS_CTRL_UPDATE_SHADOW    (1 << 16)
+ #define S5PCSIS_CTRL_WCLK_EXTCLK      (1 << 8)
+ #define S5PCSIS_CTRL_RESET            (1 << 4)
+ #define S5PCSIS_CTRL_ENABLE           (1 << 0)
+ /* D-PHY control */
+ #define S5PCSIS_DPHYCTRL              0x04
+ #define S5PCSIS_DPHYCTRL_HSS_MASK     (0x1f << 27)
+ #define S5PCSIS_DPHYCTRL_ENABLE               (0x1f << 0)
+ #define S5PCSIS_CONFIG                        0x08
+ #define S5PCSIS_CFG_FMT_YCBCR422_8BIT (0x1e << 2)
+ #define S5PCSIS_CFG_FMT_RAW8          (0x2a << 2)
+ #define S5PCSIS_CFG_FMT_RAW10         (0x2b << 2)
+ #define S5PCSIS_CFG_FMT_RAW12         (0x2c << 2)
+ /* User defined formats, x = 1...4 */
+ #define S5PCSIS_CFG_FMT_USER(x)               ((0x30 + x - 1) << 2)
+ #define S5PCSIS_CFG_FMT_MASK          (0x3f << 2)
+ #define S5PCSIS_CFG_NR_LANE_MASK      3
+ /* Interrupt mask */
+ #define S5PCSIS_INTMSK                        0x10
+ #define S5PCSIS_INTMSK_EN_ALL         0xf000103f
+ #define S5PCSIS_INTMSK_EVEN_BEFORE    (1 << 31)
+ #define S5PCSIS_INTMSK_EVEN_AFTER     (1 << 30)
+ #define S5PCSIS_INTMSK_ODD_BEFORE     (1 << 29)
+ #define S5PCSIS_INTMSK_ODD_AFTER      (1 << 28)
+ #define S5PCSIS_INTMSK_ERR_SOT_HS     (1 << 12)
+ #define S5PCSIS_INTMSK_ERR_LOST_FS    (1 << 5)
+ #define S5PCSIS_INTMSK_ERR_LOST_FE    (1 << 4)
+ #define S5PCSIS_INTMSK_ERR_OVER               (1 << 3)
+ #define S5PCSIS_INTMSK_ERR_ECC                (1 << 2)
+ #define S5PCSIS_INTMSK_ERR_CRC                (1 << 1)
+ #define S5PCSIS_INTMSK_ERR_UNKNOWN    (1 << 0)
+ /* Interrupt source */
+ #define S5PCSIS_INTSRC                        0x14
+ #define S5PCSIS_INTSRC_EVEN_BEFORE    (1 << 31)
+ #define S5PCSIS_INTSRC_EVEN_AFTER     (1 << 30)
+ #define S5PCSIS_INTSRC_EVEN           (0x3 << 30)
+ #define S5PCSIS_INTSRC_ODD_BEFORE     (1 << 29)
+ #define S5PCSIS_INTSRC_ODD_AFTER      (1 << 28)
+ #define S5PCSIS_INTSRC_ODD            (0x3 << 28)
+ #define S5PCSIS_INTSRC_NON_IMAGE_DATA (0xff << 28)
+ #define S5PCSIS_INTSRC_ERR_SOT_HS     (0xf << 12)
+ #define S5PCSIS_INTSRC_ERR_LOST_FS    (1 << 5)
+ #define S5PCSIS_INTSRC_ERR_LOST_FE    (1 << 4)
+ #define S5PCSIS_INTSRC_ERR_OVER               (1 << 3)
+ #define S5PCSIS_INTSRC_ERR_ECC                (1 << 2)
+ #define S5PCSIS_INTSRC_ERR_CRC                (1 << 1)
+ #define S5PCSIS_INTSRC_ERR_UNKNOWN    (1 << 0)
+ #define S5PCSIS_INTSRC_ERRORS         0xf03f
+ /* Pixel resolution */
+ #define S5PCSIS_RESOL                 0x2c
+ #define CSIS_MAX_PIX_WIDTH            0xffff
+ #define CSIS_MAX_PIX_HEIGHT           0xffff
+ enum {
+       CSIS_CLK_MUX,
+       CSIS_CLK_GATE,
+ };
+ static char *csi_clock_name[] = {
+       [CSIS_CLK_MUX]  = "sclk_csis",
+       [CSIS_CLK_GATE] = "csis",
+ };
+ #define NUM_CSIS_CLOCKS       ARRAY_SIZE(csi_clock_name)
+ static const char * const csis_supply_name[] = {
+       "vdd11", /* 1.1V or 1.2V (s5pc100) MIPI CSI suppply */
+       "vdd18", /* VDD 1.8V and MIPI CSI PLL supply */
+ };
+ #define CSIS_NUM_SUPPLIES ARRAY_SIZE(csis_supply_name)
+ enum {
+       ST_POWERED      = 1,
+       ST_STREAMING    = 2,
+       ST_SUSPENDED    = 4,
+ };
+ struct s5pcsis_event {
+       u32 mask;
+       const char * const name;
+       unsigned int counter;
+ };
+ static const struct s5pcsis_event s5pcsis_events[] = {
+       /* Errors */
+       { S5PCSIS_INTSRC_ERR_SOT_HS,    "SOT Error" },
+       { S5PCSIS_INTSRC_ERR_LOST_FS,   "Lost Frame Start Error" },
+       { S5PCSIS_INTSRC_ERR_LOST_FE,   "Lost Frame End Error" },
+       { S5PCSIS_INTSRC_ERR_OVER,      "FIFO Overflow Error" },
+       { S5PCSIS_INTSRC_ERR_ECC,       "ECC Error" },
+       { S5PCSIS_INTSRC_ERR_CRC,       "CRC Error" },
+       { S5PCSIS_INTSRC_ERR_UNKNOWN,   "Unknown Error" },
+       /* Non-image data receive events */
+       { S5PCSIS_INTSRC_EVEN_BEFORE,   "Non-image data before even frame" },
+       { S5PCSIS_INTSRC_EVEN_AFTER,    "Non-image data after even frame" },
+       { S5PCSIS_INTSRC_ODD_BEFORE,    "Non-image data before odd frame" },
+       { S5PCSIS_INTSRC_ODD_AFTER,     "Non-image data after odd frame" },
+ };
+ #define S5PCSIS_NUM_EVENTS ARRAY_SIZE(s5pcsis_events)
+ /**
+  * struct csis_state - the driver's internal state data structure
+  * @lock: mutex serializing the subdev and power management operations,
+  *        protecting @format and @flags members
+  * @pads: CSIS pads array
+  * @sd: v4l2_subdev associated with CSIS device instance
+  * @pdev: CSIS platform device
+  * @regs: mmaped I/O registers memory
+  * @supplies: CSIS regulator supplies
+  * @clock: CSIS clocks
+  * @irq: requested s5p-mipi-csis irq number
+  * @flags: the state variable for power and streaming control
+  * @csis_fmt: current CSIS pixel format
+  * @format: common media bus format for the source and sink pad
+  * @slock: spinlock protecting structure members below
+  * @events: MIPI-CSIS event (error) counters
+  */
+ struct csis_state {
+       struct mutex lock;
+       struct media_pad pads[CSIS_PADS_NUM];
+       struct v4l2_subdev sd;
+       struct platform_device *pdev;
+       void __iomem *regs;
+       struct regulator_bulk_data supplies[CSIS_NUM_SUPPLIES];
+       struct clk *clock[NUM_CSIS_CLOCKS];
+       int irq;
+       u32 flags;
+       const struct csis_pix_format *csis_fmt;
+       struct v4l2_mbus_framefmt format;
+       struct spinlock slock;
+       struct s5pcsis_event events[S5PCSIS_NUM_EVENTS];
+ };
+ /**
+  * struct csis_pix_format - CSIS pixel format description
+  * @pix_width_alignment: horizontal pixel alignment, width will be
+  *                       multiple of 2^pix_width_alignment
+  * @code: corresponding media bus code
+  * @fmt_reg: S5PCSIS_CONFIG register value
+  * @data_alignment: MIPI-CSI data alignment in bits
+  */
+ struct csis_pix_format {
+       unsigned int pix_width_alignment;
+       enum v4l2_mbus_pixelcode code;
+       u32 fmt_reg;
+       u8 data_alignment;
+ };
+ static const struct csis_pix_format s5pcsis_formats[] = {
+       {
+               .code = V4L2_MBUS_FMT_VYUY8_2X8,
+               .fmt_reg = S5PCSIS_CFG_FMT_YCBCR422_8BIT,
+               .data_alignment = 32,
+       }, {
+               .code = V4L2_MBUS_FMT_JPEG_1X8,
+               .fmt_reg = S5PCSIS_CFG_FMT_USER(1),
+               .data_alignment = 32,
+       },
+ };
+ #define s5pcsis_write(__csis, __r, __v) writel(__v, __csis->regs + __r)
+ #define s5pcsis_read(__csis, __r) readl(__csis->regs + __r)
+ static struct csis_state *sd_to_csis_state(struct v4l2_subdev *sdev)
+ {
+       return container_of(sdev, struct csis_state, sd);
+ }
+ static const struct csis_pix_format *find_csis_format(
+       struct v4l2_mbus_framefmt *mf)
+ {
+       int i;
+       for (i = 0; i < ARRAY_SIZE(s5pcsis_formats); i++)
+               if (mf->code == s5pcsis_formats[i].code)
+                       return &s5pcsis_formats[i];
+       return NULL;
+ }
+ static void s5pcsis_enable_interrupts(struct csis_state *state, bool on)
+ {
+       u32 val = s5pcsis_read(state, S5PCSIS_INTMSK);
+       val = on ? val | S5PCSIS_INTMSK_EN_ALL :
+                  val & ~S5PCSIS_INTMSK_EN_ALL;
+       s5pcsis_write(state, S5PCSIS_INTMSK, val);
+ }
+ static void s5pcsis_reset(struct csis_state *state)
+ {
+       u32 val = s5pcsis_read(state, S5PCSIS_CTRL);
+       s5pcsis_write(state, S5PCSIS_CTRL, val | S5PCSIS_CTRL_RESET);
+       udelay(10);
+ }
+ static void s5pcsis_system_enable(struct csis_state *state, int on)
+ {
+       u32 val;
+       val = s5pcsis_read(state, S5PCSIS_CTRL);
+       if (on)
+               val |= S5PCSIS_CTRL_ENABLE;
+       else
+               val &= ~S5PCSIS_CTRL_ENABLE;
+       s5pcsis_write(state, S5PCSIS_CTRL, val);
+       val = s5pcsis_read(state, S5PCSIS_DPHYCTRL);
+       if (on)
+               val |= S5PCSIS_DPHYCTRL_ENABLE;
+       else
+               val &= ~S5PCSIS_DPHYCTRL_ENABLE;
+       s5pcsis_write(state, S5PCSIS_DPHYCTRL, val);
+ }
+ /* Called with the state.lock mutex held */
+ static void __s5pcsis_set_format(struct csis_state *state)
+ {
+       struct v4l2_mbus_framefmt *mf = &state->format;
+       u32 val;
+       v4l2_dbg(1, debug, &state->sd, "fmt: %d, %d x %d\n",
+                mf->code, mf->width, mf->height);
+       /* Color format */
+       val = s5pcsis_read(state, S5PCSIS_CONFIG);
+       val = (val & ~S5PCSIS_CFG_FMT_MASK) | state->csis_fmt->fmt_reg;
+       s5pcsis_write(state, S5PCSIS_CONFIG, val);
+       /* Pixel resolution */
+       val = (mf->width << 16) | mf->height;
+       s5pcsis_write(state, S5PCSIS_RESOL, val);
+ }
+ static void s5pcsis_set_hsync_settle(struct csis_state *state, int settle)
+ {
+       u32 val = s5pcsis_read(state, S5PCSIS_DPHYCTRL);
+       val = (val & ~S5PCSIS_DPHYCTRL_HSS_MASK) | (settle << 27);
+       s5pcsis_write(state, S5PCSIS_DPHYCTRL, val);
+ }
+ static void s5pcsis_set_params(struct csis_state *state)
+ {
+       struct s5p_platform_mipi_csis *pdata = state->pdev->dev.platform_data;
+       u32 val;
+       val = s5pcsis_read(state, S5PCSIS_CONFIG);
+       val = (val & ~S5PCSIS_CFG_NR_LANE_MASK) | (pdata->lanes - 1);
+       s5pcsis_write(state, S5PCSIS_CONFIG, val);
+       __s5pcsis_set_format(state);
+       s5pcsis_set_hsync_settle(state, pdata->hs_settle);
+       val = s5pcsis_read(state, S5PCSIS_CTRL);
+       if (state->csis_fmt->data_alignment == 32)
+               val |= S5PCSIS_CTRL_ALIGN_32BIT;
+       else /* 24-bits */
+               val &= ~S5PCSIS_CTRL_ALIGN_32BIT;
+       /* Not using external clock. */
+       val &= ~S5PCSIS_CTRL_WCLK_EXTCLK;
+       s5pcsis_write(state, S5PCSIS_CTRL, val);
+       /* Update the shadow register. */
+       val = s5pcsis_read(state, S5PCSIS_CTRL);
+       s5pcsis_write(state, S5PCSIS_CTRL, val | S5PCSIS_CTRL_UPDATE_SHADOW);
+ }
+ static void s5pcsis_clk_put(struct csis_state *state)
+ {
+       int i;
+       for (i = 0; i < NUM_CSIS_CLOCKS; i++) {
+               if (IS_ERR_OR_NULL(state->clock[i]))
+                       continue;
+               clk_unprepare(state->clock[i]);
+               clk_put(state->clock[i]);
+               state->clock[i] = NULL;
+       }
+ }
+ static int s5pcsis_clk_get(struct csis_state *state)
+ {
+       struct device *dev = &state->pdev->dev;
+       int i, ret;
+       for (i = 0; i < NUM_CSIS_CLOCKS; i++) {
+               state->clock[i] = clk_get(dev, csi_clock_name[i]);
+               if (IS_ERR(state->clock[i]))
+                       goto err;
+               ret = clk_prepare(state->clock[i]);
+               if (ret < 0) {
+                       clk_put(state->clock[i]);
+                       state->clock[i] = NULL;
+                       goto err;
+               }
+       }
+       return 0;
+ err:
+       s5pcsis_clk_put(state);
+       dev_err(dev, "failed to get clock: %s\n", csi_clock_name[i]);
+       return -ENXIO;
+ }
+ static void s5pcsis_start_stream(struct csis_state *state)
+ {
+       s5pcsis_reset(state);
+       s5pcsis_set_params(state);
+       s5pcsis_system_enable(state, true);
+       s5pcsis_enable_interrupts(state, true);
+ }
+ static void s5pcsis_stop_stream(struct csis_state *state)
+ {
+       s5pcsis_enable_interrupts(state, false);
+       s5pcsis_system_enable(state, false);
+ }
+ static void s5pcsis_clear_counters(struct csis_state *state)
+ {
+       unsigned long flags;
+       int i;
+       spin_lock_irqsave(&state->slock, flags);
+       for (i = 0; i < S5PCSIS_NUM_EVENTS; i++)
+               state->events[i].counter = 0;
+       spin_unlock_irqrestore(&state->slock, flags);
+ }
+ static void s5pcsis_log_counters(struct csis_state *state, bool non_errors)
+ {
+       int i = non_errors ? S5PCSIS_NUM_EVENTS : S5PCSIS_NUM_EVENTS - 4;
+       unsigned long flags;
+       spin_lock_irqsave(&state->slock, flags);
+       for (i--; i >= 0; i--)
+               if (state->events[i].counter >= 0)
+                       v4l2_info(&state->sd, "%s events: %d\n",
+                                 state->events[i].name,
+                                 state->events[i].counter);
+       spin_unlock_irqrestore(&state->slock, flags);
+ }
+ /*
+  * V4L2 subdev operations
+  */
+ static int s5pcsis_s_power(struct v4l2_subdev *sd, int on)
+ {
+       struct csis_state *state = sd_to_csis_state(sd);
+       struct device *dev = &state->pdev->dev;
+       if (on)
+               return pm_runtime_get_sync(dev);
+       return pm_runtime_put_sync(dev);
+ }
+ static int s5pcsis_s_stream(struct v4l2_subdev *sd, int enable)
+ {
+       struct csis_state *state = sd_to_csis_state(sd);
+       int ret = 0;
+       v4l2_dbg(1, debug, sd, "%s: %d, state: 0x%x\n",
+                __func__, enable, state->flags);
+       if (enable) {
+               s5pcsis_clear_counters(state);
+               ret = pm_runtime_get_sync(&state->pdev->dev);
+               if (ret && ret != 1)
+                       return ret;
+       }
+       mutex_lock(&state->lock);
+       if (enable) {
+               if (state->flags & ST_SUSPENDED) {
+                       ret = -EBUSY;
+                       goto unlock;
+               }
+               s5pcsis_start_stream(state);
+               state->flags |= ST_STREAMING;
+       } else {
+               s5pcsis_stop_stream(state);
+               state->flags &= ~ST_STREAMING;
+               if (debug > 0)
+                       s5pcsis_log_counters(state, true);
+       }
+ unlock:
+       mutex_unlock(&state->lock);
+       if (!enable)
+               pm_runtime_put(&state->pdev->dev);
+       return ret == 1 ? 0 : ret;
+ }
+ static int s5pcsis_enum_mbus_code(struct v4l2_subdev *sd,
+                                 struct v4l2_subdev_fh *fh,
+                                 struct v4l2_subdev_mbus_code_enum *code)
+ {
+       if (code->index >= ARRAY_SIZE(s5pcsis_formats))
+               return -EINVAL;
+       code->code = s5pcsis_formats[code->index].code;
+       return 0;
+ }
+ static struct csis_pix_format const *s5pcsis_try_format(
+       struct v4l2_mbus_framefmt *mf)
+ {
+       struct csis_pix_format const *csis_fmt;
+       csis_fmt = find_csis_format(mf);
+       if (csis_fmt == NULL)
+               csis_fmt = &s5pcsis_formats[0];
+       mf->code = csis_fmt->code;
+       v4l_bound_align_image(&mf->width, 1, CSIS_MAX_PIX_WIDTH,
+                             csis_fmt->pix_width_alignment,
+                             &mf->height, 1, CSIS_MAX_PIX_HEIGHT, 1,
+                             0);
+       return csis_fmt;
+ }
+ static struct v4l2_mbus_framefmt *__s5pcsis_get_format(
+               struct csis_state *state, struct v4l2_subdev_fh *fh,
+               u32 pad, enum v4l2_subdev_format_whence which)
+ {
+       if (which == V4L2_SUBDEV_FORMAT_TRY)
+               return fh ? v4l2_subdev_get_try_format(fh, pad) : NULL;
+       return &state->format;
+ }
+ static int s5pcsis_set_fmt(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh,
+                          struct v4l2_subdev_format *fmt)
+ {
+       struct csis_state *state = sd_to_csis_state(sd);
+       struct csis_pix_format const *csis_fmt;
+       struct v4l2_mbus_framefmt *mf;
+       if (fmt->pad != CSIS_PAD_SOURCE && fmt->pad != CSIS_PAD_SINK)
+               return -EINVAL;
+       mf = __s5pcsis_get_format(state, fh, fmt->pad, fmt->which);
+       if (fmt->pad == CSIS_PAD_SOURCE) {
+               if (mf) {
+                       mutex_lock(&state->lock);
+                       fmt->format = *mf;
+                       mutex_unlock(&state->lock);
+               }
+               return 0;
+       }
+       csis_fmt = s5pcsis_try_format(&fmt->format);
+       if (mf) {
+               mutex_lock(&state->lock);
+               *mf = fmt->format;
+               if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE)
+                       state->csis_fmt = csis_fmt;
+               mutex_unlock(&state->lock);
+       }
+       return 0;
+ }
+ static int s5pcsis_get_fmt(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh,
+                          struct v4l2_subdev_format *fmt)
+ {
+       struct csis_state *state = sd_to_csis_state(sd);
+       struct v4l2_mbus_framefmt *mf;
+       if (fmt->pad != CSIS_PAD_SOURCE && fmt->pad != CSIS_PAD_SINK)
+               return -EINVAL;
+       mf = __s5pcsis_get_format(state, fh, fmt->pad, fmt->which);
+       if (!mf)
+               return -EINVAL;
+       mutex_lock(&state->lock);
+       fmt->format = *mf;
+       mutex_unlock(&state->lock);
+       return 0;
+ }
+ static int s5pcsis_log_status(struct v4l2_subdev *sd)
+ {
+       struct csis_state *state = sd_to_csis_state(sd);
+       s5pcsis_log_counters(state, true);
+       return 0;
+ }
+ static int s5pcsis_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh)
+ {
+       struct v4l2_mbus_framefmt *format = v4l2_subdev_get_try_format(fh, 0);
+       format->colorspace = V4L2_COLORSPACE_JPEG;
+       format->code = s5pcsis_formats[0].code;
+       format->width = S5PCSIS_DEF_PIX_WIDTH;
+       format->height = S5PCSIS_DEF_PIX_HEIGHT;
+       format->field = V4L2_FIELD_NONE;
+       return 0;
+ }
+ static const struct v4l2_subdev_internal_ops s5pcsis_sd_internal_ops = {
+       .open = s5pcsis_open,
+ };
+ static struct v4l2_subdev_core_ops s5pcsis_core_ops = {
+       .s_power = s5pcsis_s_power,
+       .log_status = s5pcsis_log_status,
+ };
+ static struct v4l2_subdev_pad_ops s5pcsis_pad_ops = {
+       .enum_mbus_code = s5pcsis_enum_mbus_code,
+       .get_fmt = s5pcsis_get_fmt,
+       .set_fmt = s5pcsis_set_fmt,
+ };
+ static struct v4l2_subdev_video_ops s5pcsis_video_ops = {
+       .s_stream = s5pcsis_s_stream,
+ };
+ static struct v4l2_subdev_ops s5pcsis_subdev_ops = {
+       .core = &s5pcsis_core_ops,
+       .pad = &s5pcsis_pad_ops,
+       .video = &s5pcsis_video_ops,
+ };
+ static irqreturn_t s5pcsis_irq_handler(int irq, void *dev_id)
+ {
+       struct csis_state *state = dev_id;
+       unsigned long flags;
+       u32 status;
+       status = s5pcsis_read(state, S5PCSIS_INTSRC);
+       spin_lock_irqsave(&state->slock, flags);
+       /* Update the event/error counters */
+       if ((status & S5PCSIS_INTSRC_ERRORS) || debug) {
+               int i;
+               for (i = 0; i < S5PCSIS_NUM_EVENTS; i++) {
+                       if (!(status & state->events[i].mask))
+                               continue;
+                       state->events[i].counter++;
+                       v4l2_dbg(2, debug, &state->sd, "%s: %d\n",
+                                state->events[i].name,
+                                state->events[i].counter);
+               }
+               v4l2_dbg(2, debug, &state->sd, "status: %08x\n", status);
+       }
+       spin_unlock_irqrestore(&state->slock, flags);
+       s5pcsis_write(state, S5PCSIS_INTSRC, status);
+       return IRQ_HANDLED;
+ }
+ static int __devinit s5pcsis_probe(struct platform_device *pdev)
+ {
+       struct s5p_platform_mipi_csis *pdata;
+       struct resource *mem_res;
+       struct csis_state *state;
+       int ret = -ENOMEM;
+       int i;
+       state = devm_kzalloc(&pdev->dev, sizeof(*state), GFP_KERNEL);
+       if (!state)
+               return -ENOMEM;
+       mutex_init(&state->lock);
+       spin_lock_init(&state->slock);
+       state->pdev = pdev;
+       pdata = pdev->dev.platform_data;
+       if (pdata == NULL || pdata->phy_enable == NULL) {
+               dev_err(&pdev->dev, "Platform data not fully specified\n");
+               return -EINVAL;
+       }
+       if ((pdev->id == 1 && pdata->lanes > CSIS1_MAX_LANES) ||
+           pdata->lanes > CSIS0_MAX_LANES) {
+               dev_err(&pdev->dev, "Unsupported number of data lanes: %d\n",
+                       pdata->lanes);
+               return -EINVAL;
+       }
+       mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       state->regs = devm_request_and_ioremap(&pdev->dev, mem_res);
+       if (state->regs == NULL) {
+               dev_err(&pdev->dev, "Failed to request and remap io memory\n");
+               return -ENXIO;
+       }
+       state->irq = platform_get_irq(pdev, 0);
+       if (state->irq < 0) {
+               dev_err(&pdev->dev, "Failed to get irq\n");
+               return state->irq;
+       }
+       for (i = 0; i < CSIS_NUM_SUPPLIES; i++)
+               state->supplies[i].supply = csis_supply_name[i];
+       ret = regulator_bulk_get(&pdev->dev, CSIS_NUM_SUPPLIES,
+                                state->supplies);
+       if (ret)
+               return ret;
+       ret = s5pcsis_clk_get(state);
+       if (ret)
+               goto e_clkput;
+       clk_enable(state->clock[CSIS_CLK_MUX]);
+       if (pdata->clk_rate)
+               clk_set_rate(state->clock[CSIS_CLK_MUX], pdata->clk_rate);
+       else
+               dev_WARN(&pdev->dev, "No clock frequency specified!\n");
+       ret = devm_request_irq(&pdev->dev, state->irq, s5pcsis_irq_handler,
+                              0, dev_name(&pdev->dev), state);
+       if (ret) {
+               dev_err(&pdev->dev, "Interrupt request failed\n");
+               goto e_regput;
+       }
+       v4l2_subdev_init(&state->sd, &s5pcsis_subdev_ops);
+       state->sd.owner = THIS_MODULE;
+       strlcpy(state->sd.name, dev_name(&pdev->dev), sizeof(state->sd.name));
+       state->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
+       state->csis_fmt = &s5pcsis_formats[0];
+       state->format.code = s5pcsis_formats[0].code;
+       state->format.width = S5PCSIS_DEF_PIX_WIDTH;
+       state->format.height = S5PCSIS_DEF_PIX_HEIGHT;
+       state->pads[CSIS_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
+       state->pads[CSIS_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
+       ret = media_entity_init(&state->sd.entity,
+                               CSIS_PADS_NUM, state->pads, 0);
+       if (ret < 0)
+               goto e_clkput;
+       /* This allows to retrieve the platform device id by the host driver */
+       v4l2_set_subdevdata(&state->sd, pdev);
+       /* .. and a pointer to the subdev. */
+       platform_set_drvdata(pdev, &state->sd);
+       memcpy(state->events, s5pcsis_events, sizeof(state->events));
+       pm_runtime_enable(&pdev->dev);
+       return 0;
+ e_regput:
+       regulator_bulk_free(CSIS_NUM_SUPPLIES, state->supplies);
+ e_clkput:
+       clk_disable(state->clock[CSIS_CLK_MUX]);
+       s5pcsis_clk_put(state);
+       return ret;
+ }
+ static int s5pcsis_pm_suspend(struct device *dev, bool runtime)
+ {
+       struct s5p_platform_mipi_csis *pdata = dev->platform_data;
+       struct platform_device *pdev = to_platform_device(dev);
+       struct v4l2_subdev *sd = platform_get_drvdata(pdev);
+       struct csis_state *state = sd_to_csis_state(sd);
+       int ret = 0;
+       v4l2_dbg(1, debug, sd, "%s: flags: 0x%x\n",
+                __func__, state->flags);
+       mutex_lock(&state->lock);
+       if (state->flags & ST_POWERED) {
+               s5pcsis_stop_stream(state);
+               ret = pdata->phy_enable(state->pdev, false);
+               if (ret)
+                       goto unlock;
+               ret = regulator_bulk_disable(CSIS_NUM_SUPPLIES,
+                                            state->supplies);
+               if (ret)
+                       goto unlock;
+               clk_disable(state->clock[CSIS_CLK_GATE]);
+               state->flags &= ~ST_POWERED;
+               if (!runtime)
+                       state->flags |= ST_SUSPENDED;
+       }
+  unlock:
+       mutex_unlock(&state->lock);
+       return ret ? -EAGAIN : 0;
+ }
+ static int s5pcsis_pm_resume(struct device *dev, bool runtime)
+ {
+       struct s5p_platform_mipi_csis *pdata = dev->platform_data;
+       struct platform_device *pdev = to_platform_device(dev);
+       struct v4l2_subdev *sd = platform_get_drvdata(pdev);
+       struct csis_state *state = sd_to_csis_state(sd);
+       int ret = 0;
+       v4l2_dbg(1, debug, sd, "%s: flags: 0x%x\n",
+                __func__, state->flags);
+       mutex_lock(&state->lock);
+       if (!runtime && !(state->flags & ST_SUSPENDED))
+               goto unlock;
+       if (!(state->flags & ST_POWERED)) {
+               ret = regulator_bulk_enable(CSIS_NUM_SUPPLIES,
+                                           state->supplies);
+               if (ret)
+                       goto unlock;
+               ret = pdata->phy_enable(state->pdev, true);
+               if (!ret) {
+                       state->flags |= ST_POWERED;
+               } else {
+                       regulator_bulk_disable(CSIS_NUM_SUPPLIES,
+                                              state->supplies);
+                       goto unlock;
+               }
+               clk_enable(state->clock[CSIS_CLK_GATE]);
+       }
+       if (state->flags & ST_STREAMING)
+               s5pcsis_start_stream(state);
+       state->flags &= ~ST_SUSPENDED;
+  unlock:
+       mutex_unlock(&state->lock);
+       return ret ? -EAGAIN : 0;
+ }
+ #ifdef CONFIG_PM_SLEEP
+ static int s5pcsis_suspend(struct device *dev)
+ {
+       return s5pcsis_pm_suspend(dev, false);
+ }
+ static int s5pcsis_resume(struct device *dev)
+ {
+       return s5pcsis_pm_resume(dev, false);
+ }
+ #endif
+ #ifdef CONFIG_PM_RUNTIME
+ static int s5pcsis_runtime_suspend(struct device *dev)
+ {
+       return s5pcsis_pm_suspend(dev, true);
+ }
+ static int s5pcsis_runtime_resume(struct device *dev)
+ {
+       return s5pcsis_pm_resume(dev, true);
+ }
+ #endif
+ static int __devexit s5pcsis_remove(struct platform_device *pdev)
+ {
+       struct v4l2_subdev *sd = platform_get_drvdata(pdev);
+       struct csis_state *state = sd_to_csis_state(sd);
+       pm_runtime_disable(&pdev->dev);
+       s5pcsis_pm_suspend(&pdev->dev, false);
+       clk_disable(state->clock[CSIS_CLK_MUX]);
+       pm_runtime_set_suspended(&pdev->dev);
+       s5pcsis_clk_put(state);
+       regulator_bulk_free(CSIS_NUM_SUPPLIES, state->supplies);
+       media_entity_cleanup(&state->sd.entity);
+       return 0;
+ }
+ static const struct dev_pm_ops s5pcsis_pm_ops = {
+       SET_RUNTIME_PM_OPS(s5pcsis_runtime_suspend, s5pcsis_runtime_resume,
+                          NULL)
+       SET_SYSTEM_SLEEP_PM_OPS(s5pcsis_suspend, s5pcsis_resume)
+ };
+ static struct platform_driver s5pcsis_driver = {
+       .probe          = s5pcsis_probe,
+       .remove         = __devexit_p(s5pcsis_remove),
+       .driver         = {
+               .name   = CSIS_DRIVER_NAME,
+               .owner  = THIS_MODULE,
+               .pm     = &s5pcsis_pm_ops,
+       },
+ };
+ module_platform_driver(s5pcsis_driver);
+ MODULE_AUTHOR("Sylwester Nawrocki <s.nawrocki@samsung.com>");
+ MODULE_DESCRIPTION("Samsung S5P/EXYNOS SoC MIPI-CSI2 receiver driver");
+ MODULE_LICENSE("GPL");
index 0000000000000000000000000000000000000000,560a65aa7038dd3cded8d08395958c70e3c537bb..bbe70991d30b6ac0b2ef598ccafba7519ab138b9
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,889 +1,889 @@@
 -#include <mach/mx1_camera.h>
+ /*
+  * V4L2 Driver for i.MXL/i.MXL camera (CSI) host
+  *
+  * Copyright (C) 2008, Paulius Zaleckas <paulius.zaleckas@teltonika.lt>
+  * Copyright (C) 2009, Darius Augulis <augulis.darius@gmail.com>
+  *
+  * Based on PXA SoC camera driver
+  * Copyright (C) 2006, Sascha Hauer, Pengutronix
+  * Copyright (C) 2008, Guennadi Liakhovetski <kernel@pengutronix.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.
+  */
+ #include <linux/clk.h>
+ #include <linux/delay.h>
+ #include <linux/device.h>
+ #include <linux/dma-mapping.h>
+ #include <linux/errno.h>
+ #include <linux/fs.h>
+ #include <linux/init.h>
+ #include <linux/interrupt.h>
+ #include <linux/io.h>
+ #include <linux/kernel.h>
+ #include <linux/mm.h>
+ #include <linux/module.h>
+ #include <linux/moduleparam.h>
+ #include <linux/mutex.h>
+ #include <linux/platform_device.h>
+ #include <linux/sched.h>
+ #include <linux/slab.h>
+ #include <linux/time.h>
+ #include <linux/videodev2.h>
+ #include <media/soc_camera.h>
+ #include <media/v4l2-common.h>
+ #include <media/v4l2-dev.h>
+ #include <media/videobuf-dma-contig.h>
+ #include <media/soc_mediabus.h>
+ #include <asm/dma.h>
+ #include <asm/fiq.h>
+ #include <mach/dma-mx1-mx2.h>
+ #include <mach/hardware.h>
+ #include <mach/irqs.h>
++#include <linux/platform_data/camera-mx1.h>
+ /*
+  * CSI registers
+  */
+ #define CSICR1                0x00                    /* CSI Control Register 1 */
+ #define CSISR         0x08                    /* CSI Status Register */
+ #define CSIRXR                0x10                    /* CSI RxFIFO Register */
+ #define CSICR1_RXFF_LEVEL(x)  (((x) & 0x3) << 19)
+ #define CSICR1_SOF_POL                (1 << 17)
+ #define CSICR1_SOF_INTEN      (1 << 16)
+ #define CSICR1_MCLKDIV(x)     (((x) & 0xf) << 12)
+ #define CSICR1_MCLKEN         (1 << 9)
+ #define CSICR1_FCC            (1 << 8)
+ #define CSICR1_BIG_ENDIAN     (1 << 7)
+ #define CSICR1_CLR_RXFIFO     (1 << 5)
+ #define CSICR1_GCLK_MODE      (1 << 4)
+ #define CSICR1_DATA_POL               (1 << 2)
+ #define CSICR1_REDGE          (1 << 1)
+ #define CSICR1_EN             (1 << 0)
+ #define CSISR_SFF_OR_INT      (1 << 25)
+ #define CSISR_RFF_OR_INT      (1 << 24)
+ #define CSISR_STATFF_INT      (1 << 21)
+ #define CSISR_RXFF_INT                (1 << 18)
+ #define CSISR_SOF_INT         (1 << 16)
+ #define CSISR_DRDY            (1 << 0)
+ #define DRIVER_VERSION "0.0.2"
+ #define DRIVER_NAME "mx1-camera"
+ #define CSI_IRQ_MASK  (CSISR_SFF_OR_INT | CSISR_RFF_OR_INT | \
+                       CSISR_STATFF_INT | CSISR_RXFF_INT | CSISR_SOF_INT)
+ #define CSI_BUS_FLAGS (V4L2_MBUS_MASTER | V4L2_MBUS_HSYNC_ACTIVE_HIGH | \
+                       V4L2_MBUS_VSYNC_ACTIVE_HIGH | V4L2_MBUS_VSYNC_ACTIVE_LOW | \
+                       V4L2_MBUS_PCLK_SAMPLE_RISING | V4L2_MBUS_PCLK_SAMPLE_FALLING | \
+                       V4L2_MBUS_DATA_ACTIVE_HIGH | V4L2_MBUS_DATA_ACTIVE_LOW)
+ #define MAX_VIDEO_MEM 16      /* Video memory limit in megabytes */
+ /*
+  * Structures
+  */
+ /* buffer for one video frame */
+ struct mx1_buffer {
+       /* common v4l buffer stuff -- must be first */
+       struct videobuf_buffer          vb;
+       enum v4l2_mbus_pixelcode        code;
+       int                             inwork;
+ };
+ /*
+  * i.MX1/i.MXL is only supposed to handle one camera on its Camera Sensor
+  * Interface. If anyone ever builds hardware to enable more than
+  * one camera, they will have to modify this driver too
+  */
+ struct mx1_camera_dev {
+       struct soc_camera_host          soc_host;
+       struct soc_camera_device        *icd;
+       struct mx1_camera_pdata         *pdata;
+       struct mx1_buffer               *active;
+       struct resource                 *res;
+       struct clk                      *clk;
+       struct list_head                capture;
+       void __iomem                    *base;
+       int                             dma_chan;
+       unsigned int                    irq;
+       unsigned long                   mclk;
+       spinlock_t                      lock;
+ };
+ /*
+  *  Videobuf operations
+  */
+ static int mx1_videobuf_setup(struct videobuf_queue *vq, unsigned int *count,
+                             unsigned int *size)
+ {
+       struct soc_camera_device *icd = vq->priv_data;
+       *size = icd->sizeimage;
+       if (!*count)
+               *count = 32;
+       if (*size * *count > MAX_VIDEO_MEM * 1024 * 1024)
+               *count = (MAX_VIDEO_MEM * 1024 * 1024) / *size;
+       dev_dbg(icd->parent, "count=%d, size=%d\n", *count, *size);
+       return 0;
+ }
+ static void free_buffer(struct videobuf_queue *vq, struct mx1_buffer *buf)
+ {
+       struct soc_camera_device *icd = vq->priv_data;
+       struct videobuf_buffer *vb = &buf->vb;
+       BUG_ON(in_interrupt());
+       dev_dbg(icd->parent, "%s (vb=0x%p) 0x%08lx %d\n", __func__,
+               vb, vb->baddr, vb->bsize);
+       /*
+        * This waits until this buffer is out of danger, i.e., until it is no
+        * longer in STATE_QUEUED or STATE_ACTIVE
+        */
+       videobuf_waiton(vq, vb, 0, 0);
+       videobuf_dma_contig_free(vq, vb);
+       vb->state = VIDEOBUF_NEEDS_INIT;
+ }
+ static int mx1_videobuf_prepare(struct videobuf_queue *vq,
+               struct videobuf_buffer *vb, enum v4l2_field field)
+ {
+       struct soc_camera_device *icd = vq->priv_data;
+       struct mx1_buffer *buf = container_of(vb, struct mx1_buffer, vb);
+       int ret;
+       dev_dbg(icd->parent, "%s (vb=0x%p) 0x%08lx %d\n", __func__,
+               vb, vb->baddr, vb->bsize);
+       /* Added list head initialization on alloc */
+       WARN_ON(!list_empty(&vb->queue));
+       BUG_ON(NULL == icd->current_fmt);
+       /*
+        * I think, in buf_prepare you only have to protect global data,
+        * the actual buffer is yours
+        */
+       buf->inwork = 1;
+       if (buf->code   != icd->current_fmt->code ||
+           vb->width   != icd->user_width ||
+           vb->height  != icd->user_height ||
+           vb->field   != field) {
+               buf->code       = icd->current_fmt->code;
+               vb->width       = icd->user_width;
+               vb->height      = icd->user_height;
+               vb->field       = field;
+               vb->state       = VIDEOBUF_NEEDS_INIT;
+       }
+       vb->size = icd->sizeimage;
+       if (0 != vb->baddr && vb->bsize < vb->size) {
+               ret = -EINVAL;
+               goto out;
+       }
+       if (vb->state == VIDEOBUF_NEEDS_INIT) {
+               ret = videobuf_iolock(vq, vb, NULL);
+               if (ret)
+                       goto fail;
+               vb->state = VIDEOBUF_PREPARED;
+       }
+       buf->inwork = 0;
+       return 0;
+ fail:
+       free_buffer(vq, buf);
+ out:
+       buf->inwork = 0;
+       return ret;
+ }
+ static int mx1_camera_setup_dma(struct mx1_camera_dev *pcdev)
+ {
+       struct videobuf_buffer *vbuf = &pcdev->active->vb;
+       struct device *dev = pcdev->icd->parent;
+       int ret;
+       if (unlikely(!pcdev->active)) {
+               dev_err(dev, "DMA End IRQ with no active buffer\n");
+               return -EFAULT;
+       }
+       /* setup sg list for future DMA */
+       ret = imx_dma_setup_single(pcdev->dma_chan,
+               videobuf_to_dma_contig(vbuf),
+               vbuf->size, pcdev->res->start +
+               CSIRXR, DMA_MODE_READ);
+       if (unlikely(ret))
+               dev_err(dev, "Failed to setup DMA sg list\n");
+       return ret;
+ }
+ /* Called under spinlock_irqsave(&pcdev->lock, ...) */
+ static void mx1_videobuf_queue(struct videobuf_queue *vq,
+                                               struct videobuf_buffer *vb)
+ {
+       struct soc_camera_device *icd = vq->priv_data;
+       struct soc_camera_host *ici = to_soc_camera_host(icd->parent);
+       struct mx1_camera_dev *pcdev = ici->priv;
+       struct mx1_buffer *buf = container_of(vb, struct mx1_buffer, vb);
+       dev_dbg(icd->parent, "%s (vb=0x%p) 0x%08lx %d\n", __func__,
+               vb, vb->baddr, vb->bsize);
+       list_add_tail(&vb->queue, &pcdev->capture);
+       vb->state = VIDEOBUF_ACTIVE;
+       if (!pcdev->active) {
+               pcdev->active = buf;
+               /* setup sg list for future DMA */
+               if (!mx1_camera_setup_dma(pcdev)) {
+                       unsigned int temp;
+                       /* enable SOF irq */
+                       temp = __raw_readl(pcdev->base + CSICR1) |
+                                                       CSICR1_SOF_INTEN;
+                       __raw_writel(temp, pcdev->base + CSICR1);
+               }
+       }
+ }
+ static void mx1_videobuf_release(struct videobuf_queue *vq,
+                                struct videobuf_buffer *vb)
+ {
+       struct mx1_buffer *buf = container_of(vb, struct mx1_buffer, vb);
+ #ifdef DEBUG
+       struct soc_camera_device *icd = vq->priv_data;
+       struct device *dev = icd->parent;
+       dev_dbg(dev, "%s (vb=0x%p) 0x%08lx %d\n", __func__,
+               vb, vb->baddr, vb->bsize);
+       switch (vb->state) {
+       case VIDEOBUF_ACTIVE:
+               dev_dbg(dev, "%s (active)\n", __func__);
+               break;
+       case VIDEOBUF_QUEUED:
+               dev_dbg(dev, "%s (queued)\n", __func__);
+               break;
+       case VIDEOBUF_PREPARED:
+               dev_dbg(dev, "%s (prepared)\n", __func__);
+               break;
+       default:
+               dev_dbg(dev, "%s (unknown)\n", __func__);
+               break;
+       }
+ #endif
+       free_buffer(vq, buf);
+ }
+ static void mx1_camera_wakeup(struct mx1_camera_dev *pcdev,
+                             struct videobuf_buffer *vb,
+                             struct mx1_buffer *buf)
+ {
+       /* _init is used to debug races, see comment in mx1_camera_reqbufs() */
+       list_del_init(&vb->queue);
+       vb->state = VIDEOBUF_DONE;
+       do_gettimeofday(&vb->ts);
+       vb->field_count++;
+       wake_up(&vb->done);
+       if (list_empty(&pcdev->capture)) {
+               pcdev->active = NULL;
+               return;
+       }
+       pcdev->active = list_entry(pcdev->capture.next,
+                                  struct mx1_buffer, vb.queue);
+       /* setup sg list for future DMA */
+       if (likely(!mx1_camera_setup_dma(pcdev))) {
+               unsigned int temp;
+               /* enable SOF irq */
+               temp = __raw_readl(pcdev->base + CSICR1) | CSICR1_SOF_INTEN;
+               __raw_writel(temp, pcdev->base + CSICR1);
+       }
+ }
+ static void mx1_camera_dma_irq(int channel, void *data)
+ {
+       struct mx1_camera_dev *pcdev = data;
+       struct device *dev = pcdev->icd->parent;
+       struct mx1_buffer *buf;
+       struct videobuf_buffer *vb;
+       unsigned long flags;
+       spin_lock_irqsave(&pcdev->lock, flags);
+       imx_dma_disable(channel);
+       if (unlikely(!pcdev->active)) {
+               dev_err(dev, "DMA End IRQ with no active buffer\n");
+               goto out;
+       }
+       vb = &pcdev->active->vb;
+       buf = container_of(vb, struct mx1_buffer, vb);
+       WARN_ON(buf->inwork || list_empty(&vb->queue));
+       dev_dbg(dev, "%s (vb=0x%p) 0x%08lx %d\n", __func__,
+               vb, vb->baddr, vb->bsize);
+       mx1_camera_wakeup(pcdev, vb, buf);
+ out:
+       spin_unlock_irqrestore(&pcdev->lock, flags);
+ }
+ static struct videobuf_queue_ops mx1_videobuf_ops = {
+       .buf_setup      = mx1_videobuf_setup,
+       .buf_prepare    = mx1_videobuf_prepare,
+       .buf_queue      = mx1_videobuf_queue,
+       .buf_release    = mx1_videobuf_release,
+ };
+ static void mx1_camera_init_videobuf(struct videobuf_queue *q,
+                                    struct soc_camera_device *icd)
+ {
+       struct soc_camera_host *ici = to_soc_camera_host(icd->parent);
+       struct mx1_camera_dev *pcdev = ici->priv;
+       videobuf_queue_dma_contig_init(q, &mx1_videobuf_ops, icd->parent,
+                               &pcdev->lock, V4L2_BUF_TYPE_VIDEO_CAPTURE,
+                               V4L2_FIELD_NONE,
+                               sizeof(struct mx1_buffer), icd, &icd->video_lock);
+ }
+ static int mclk_get_divisor(struct mx1_camera_dev *pcdev)
+ {
+       unsigned int mclk = pcdev->mclk;
+       unsigned long div;
+       unsigned long lcdclk;
+       lcdclk = clk_get_rate(pcdev->clk);
+       /*
+        * We verify platform_mclk_10khz != 0, so if anyone breaks it, here
+        * they get a nice Oops
+        */
+       div = (lcdclk + 2 * mclk - 1) / (2 * mclk) - 1;
+       dev_dbg(pcdev->icd->parent,
+               "System clock %lukHz, target freq %dkHz, divisor %lu\n",
+               lcdclk / 1000, mclk / 1000, div);
+       return div;
+ }
+ static void mx1_camera_activate(struct mx1_camera_dev *pcdev)
+ {
+       unsigned int csicr1 = CSICR1_EN;
+       dev_dbg(pcdev->icd->parent, "Activate device\n");
+       clk_prepare_enable(pcdev->clk);
+       /* enable CSI before doing anything else */
+       __raw_writel(csicr1, pcdev->base + CSICR1);
+       csicr1 |= CSICR1_MCLKEN | CSICR1_FCC | CSICR1_GCLK_MODE;
+       csicr1 |= CSICR1_MCLKDIV(mclk_get_divisor(pcdev));
+       csicr1 |= CSICR1_RXFF_LEVEL(2); /* 16 words */
+       __raw_writel(csicr1, pcdev->base + CSICR1);
+ }
+ static void mx1_camera_deactivate(struct mx1_camera_dev *pcdev)
+ {
+       dev_dbg(pcdev->icd->parent, "Deactivate device\n");
+       /* Disable all CSI interface */
+       __raw_writel(0x00, pcdev->base + CSICR1);
+       clk_disable_unprepare(pcdev->clk);
+ }
+ /*
+  * The following two functions absolutely depend on the fact, that
+  * there can be only one camera on i.MX1/i.MXL camera sensor interface
+  */
+ static int mx1_camera_add_device(struct soc_camera_device *icd)
+ {
+       struct soc_camera_host *ici = to_soc_camera_host(icd->parent);
+       struct mx1_camera_dev *pcdev = ici->priv;
+       if (pcdev->icd)
+               return -EBUSY;
+       dev_info(icd->parent, "MX1 Camera driver attached to camera %d\n",
+                icd->devnum);
+       mx1_camera_activate(pcdev);
+       pcdev->icd = icd;
+       return 0;
+ }
+ static void mx1_camera_remove_device(struct soc_camera_device *icd)
+ {
+       struct soc_camera_host *ici = to_soc_camera_host(icd->parent);
+       struct mx1_camera_dev *pcdev = ici->priv;
+       unsigned int csicr1;
+       BUG_ON(icd != pcdev->icd);
+       /* disable interrupts */
+       csicr1 = __raw_readl(pcdev->base + CSICR1) & ~CSI_IRQ_MASK;
+       __raw_writel(csicr1, pcdev->base + CSICR1);
+       /* Stop DMA engine */
+       imx_dma_disable(pcdev->dma_chan);
+       dev_info(icd->parent, "MX1 Camera driver detached from camera %d\n",
+                icd->devnum);
+       mx1_camera_deactivate(pcdev);
+       pcdev->icd = NULL;
+ }
+ static int mx1_camera_set_crop(struct soc_camera_device *icd,
+                              struct v4l2_crop *a)
+ {
+       struct v4l2_subdev *sd = soc_camera_to_subdev(icd);
+       return v4l2_subdev_call(sd, video, s_crop, a);
+ }
+ static int mx1_camera_set_bus_param(struct soc_camera_device *icd)
+ {
+       struct v4l2_subdev *sd = soc_camera_to_subdev(icd);
+       struct soc_camera_host *ici = to_soc_camera_host(icd->parent);
+       struct mx1_camera_dev *pcdev = ici->priv;
+       struct v4l2_mbus_config cfg = {.type = V4L2_MBUS_PARALLEL,};
+       unsigned long common_flags;
+       unsigned int csicr1;
+       int ret;
+       /* MX1 supports only 8bit buswidth */
+       ret = v4l2_subdev_call(sd, video, g_mbus_config, &cfg);
+       if (!ret) {
+               common_flags = soc_mbus_config_compatible(&cfg, CSI_BUS_FLAGS);
+               if (!common_flags) {
+                       dev_warn(icd->parent,
+                                "Flags incompatible: camera 0x%x, host 0x%x\n",
+                                cfg.flags, CSI_BUS_FLAGS);
+                       return -EINVAL;
+               }
+       } else if (ret != -ENOIOCTLCMD) {
+               return ret;
+       } else {
+               common_flags = CSI_BUS_FLAGS;
+       }
+       /* Make choises, based on platform choice */
+       if ((common_flags & V4L2_MBUS_VSYNC_ACTIVE_HIGH) &&
+               (common_flags & V4L2_MBUS_VSYNC_ACTIVE_LOW)) {
+                       if (!pcdev->pdata ||
+                            pcdev->pdata->flags & MX1_CAMERA_VSYNC_HIGH)
+                               common_flags &= ~V4L2_MBUS_VSYNC_ACTIVE_LOW;
+                       else
+                               common_flags &= ~V4L2_MBUS_VSYNC_ACTIVE_HIGH;
+       }
+       if ((common_flags & V4L2_MBUS_PCLK_SAMPLE_RISING) &&
+               (common_flags & V4L2_MBUS_PCLK_SAMPLE_FALLING)) {
+                       if (!pcdev->pdata ||
+                            pcdev->pdata->flags & MX1_CAMERA_PCLK_RISING)
+                               common_flags &= ~V4L2_MBUS_PCLK_SAMPLE_FALLING;
+                       else
+                               common_flags &= ~V4L2_MBUS_PCLK_SAMPLE_RISING;
+       }
+       if ((common_flags & V4L2_MBUS_DATA_ACTIVE_HIGH) &&
+               (common_flags & V4L2_MBUS_DATA_ACTIVE_LOW)) {
+                       if (!pcdev->pdata ||
+                            pcdev->pdata->flags & MX1_CAMERA_DATA_HIGH)
+                               common_flags &= ~V4L2_MBUS_DATA_ACTIVE_LOW;
+                       else
+                               common_flags &= ~V4L2_MBUS_DATA_ACTIVE_HIGH;
+       }
+       cfg.flags = common_flags;
+       ret = v4l2_subdev_call(sd, video, s_mbus_config, &cfg);
+       if (ret < 0 && ret != -ENOIOCTLCMD) {
+               dev_dbg(icd->parent, "camera s_mbus_config(0x%lx) returned %d\n",
+                       common_flags, ret);
+               return ret;
+       }
+       csicr1 = __raw_readl(pcdev->base + CSICR1);
+       if (common_flags & V4L2_MBUS_PCLK_SAMPLE_RISING)
+               csicr1 |= CSICR1_REDGE;
+       if (common_flags & V4L2_MBUS_VSYNC_ACTIVE_HIGH)
+               csicr1 |= CSICR1_SOF_POL;
+       if (common_flags & V4L2_MBUS_DATA_ACTIVE_LOW)
+               csicr1 |= CSICR1_DATA_POL;
+       __raw_writel(csicr1, pcdev->base + CSICR1);
+       return 0;
+ }
+ static int mx1_camera_set_fmt(struct soc_camera_device *icd,
+                             struct v4l2_format *f)
+ {
+       struct v4l2_subdev *sd = soc_camera_to_subdev(icd);
+       const struct soc_camera_format_xlate *xlate;
+       struct v4l2_pix_format *pix = &f->fmt.pix;
+       struct v4l2_mbus_framefmt mf;
+       int ret, buswidth;
+       xlate = soc_camera_xlate_by_fourcc(icd, pix->pixelformat);
+       if (!xlate) {
+               dev_warn(icd->parent, "Format %x not found\n",
+                        pix->pixelformat);
+               return -EINVAL;
+       }
+       buswidth = xlate->host_fmt->bits_per_sample;
+       if (buswidth > 8) {
+               dev_warn(icd->parent,
+                        "bits-per-sample %d for format %x unsupported\n",
+                        buswidth, pix->pixelformat);
+               return -EINVAL;
+       }
+       mf.width        = pix->width;
+       mf.height       = pix->height;
+       mf.field        = pix->field;
+       mf.colorspace   = pix->colorspace;
+       mf.code         = xlate->code;
+       ret = v4l2_subdev_call(sd, video, s_mbus_fmt, &mf);
+       if (ret < 0)
+               return ret;
+       if (mf.code != xlate->code)
+               return -EINVAL;
+       pix->width              = mf.width;
+       pix->height             = mf.height;
+       pix->field              = mf.field;
+       pix->colorspace         = mf.colorspace;
+       icd->current_fmt        = xlate;
+       return ret;
+ }
+ static int mx1_camera_try_fmt(struct soc_camera_device *icd,
+                             struct v4l2_format *f)
+ {
+       struct v4l2_subdev *sd = soc_camera_to_subdev(icd);
+       const struct soc_camera_format_xlate *xlate;
+       struct v4l2_pix_format *pix = &f->fmt.pix;
+       struct v4l2_mbus_framefmt mf;
+       int ret;
+       /* TODO: limit to mx1 hardware capabilities */
+       xlate = soc_camera_xlate_by_fourcc(icd, pix->pixelformat);
+       if (!xlate) {
+               dev_warn(icd->parent, "Format %x not found\n",
+                        pix->pixelformat);
+               return -EINVAL;
+       }
+       mf.width        = pix->width;
+       mf.height       = pix->height;
+       mf.field        = pix->field;
+       mf.colorspace   = pix->colorspace;
+       mf.code         = xlate->code;
+       /* limit to sensor capabilities */
+       ret = v4l2_subdev_call(sd, video, try_mbus_fmt, &mf);
+       if (ret < 0)
+               return ret;
+       pix->width      = mf.width;
+       pix->height     = mf.height;
+       pix->field      = mf.field;
+       pix->colorspace = mf.colorspace;
+       return 0;
+ }
+ static int mx1_camera_reqbufs(struct soc_camera_device *icd,
+                             struct v4l2_requestbuffers *p)
+ {
+       int i;
+       /*
+        * This is for locking debugging only. I removed spinlocks and now I
+        * check whether .prepare is ever called on a linked buffer, or whether
+        * a dma IRQ can occur for an in-work or unlinked buffer. Until now
+        * it hadn't triggered
+        */
+       for (i = 0; i < p->count; i++) {
+               struct mx1_buffer *buf = container_of(icd->vb_vidq.bufs[i],
+                                                     struct mx1_buffer, vb);
+               buf->inwork = 0;
+               INIT_LIST_HEAD(&buf->vb.queue);
+       }
+       return 0;
+ }
+ static unsigned int mx1_camera_poll(struct file *file, poll_table *pt)
+ {
+       struct soc_camera_device *icd = file->private_data;
+       struct mx1_buffer *buf;
+       buf = list_entry(icd->vb_vidq.stream.next, struct mx1_buffer,
+                        vb.stream);
+       poll_wait(file, &buf->vb.done, pt);
+       if (buf->vb.state == VIDEOBUF_DONE ||
+           buf->vb.state == VIDEOBUF_ERROR)
+               return POLLIN | POLLRDNORM;
+       return 0;
+ }
+ static int mx1_camera_querycap(struct soc_camera_host *ici,
+                              struct v4l2_capability *cap)
+ {
+       /* cap->name is set by the friendly caller:-> */
+       strlcpy(cap->card, "i.MX1/i.MXL Camera", sizeof(cap->card));
+       cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;
+       return 0;
+ }
+ static struct soc_camera_host_ops mx1_soc_camera_host_ops = {
+       .owner          = THIS_MODULE,
+       .add            = mx1_camera_add_device,
+       .remove         = mx1_camera_remove_device,
+       .set_bus_param  = mx1_camera_set_bus_param,
+       .set_crop       = mx1_camera_set_crop,
+       .set_fmt        = mx1_camera_set_fmt,
+       .try_fmt        = mx1_camera_try_fmt,
+       .init_videobuf  = mx1_camera_init_videobuf,
+       .reqbufs        = mx1_camera_reqbufs,
+       .poll           = mx1_camera_poll,
+       .querycap       = mx1_camera_querycap,
+ };
+ static struct fiq_handler fh = {
+       .name           = "csi_sof"
+ };
+ static int __init mx1_camera_probe(struct platform_device *pdev)
+ {
+       struct mx1_camera_dev *pcdev;
+       struct resource *res;
+       struct pt_regs regs;
+       struct clk *clk;
+       void __iomem *base;
+       unsigned int irq;
+       int err = 0;
+       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       irq = platform_get_irq(pdev, 0);
+       if (!res || (int)irq <= 0) {
+               err = -ENODEV;
+               goto exit;
+       }
+       clk = clk_get(&pdev->dev, "csi_clk");
+       if (IS_ERR(clk)) {
+               err = PTR_ERR(clk);
+               goto exit;
+       }
+       pcdev = kzalloc(sizeof(*pcdev), GFP_KERNEL);
+       if (!pcdev) {
+               dev_err(&pdev->dev, "Could not allocate pcdev\n");
+               err = -ENOMEM;
+               goto exit_put_clk;
+       }
+       pcdev->res = res;
+       pcdev->clk = clk;
+       pcdev->pdata = pdev->dev.platform_data;
+       if (pcdev->pdata)
+               pcdev->mclk = pcdev->pdata->mclk_10khz * 10000;
+       if (!pcdev->mclk) {
+               dev_warn(&pdev->dev,
+                        "mclk_10khz == 0! Please, fix your platform data. "
+                        "Using default 20MHz\n");
+               pcdev->mclk = 20000000;
+       }
+       INIT_LIST_HEAD(&pcdev->capture);
+       spin_lock_init(&pcdev->lock);
+       /*
+        * Request the regions.
+        */
+       if (!request_mem_region(res->start, resource_size(res), DRIVER_NAME)) {
+               err = -EBUSY;
+               goto exit_kfree;
+       }
+       base = ioremap(res->start, resource_size(res));
+       if (!base) {
+               err = -ENOMEM;
+               goto exit_release;
+       }
+       pcdev->irq = irq;
+       pcdev->base = base;
+       /* request dma */
+       pcdev->dma_chan = imx_dma_request_by_prio(DRIVER_NAME, DMA_PRIO_HIGH);
+       if (pcdev->dma_chan < 0) {
+               dev_err(&pdev->dev, "Can't request DMA for MX1 CSI\n");
+               err = -EBUSY;
+               goto exit_iounmap;
+       }
+       dev_dbg(&pdev->dev, "got DMA channel %d\n", pcdev->dma_chan);
+       imx_dma_setup_handlers(pcdev->dma_chan, mx1_camera_dma_irq, NULL,
+                              pcdev);
+       imx_dma_config_channel(pcdev->dma_chan, IMX_DMA_TYPE_FIFO,
+                              IMX_DMA_MEMSIZE_32, MX1_DMA_REQ_CSI_R, 0);
+       /* burst length : 16 words = 64 bytes */
+       imx_dma_config_burstlen(pcdev->dma_chan, 0);
+       /* request irq */
+       err = claim_fiq(&fh);
+       if (err) {
+               dev_err(&pdev->dev, "Camera interrupt register failed \n");
+               goto exit_free_dma;
+       }
+       set_fiq_handler(&mx1_camera_sof_fiq_start, &mx1_camera_sof_fiq_end -
+                                                  &mx1_camera_sof_fiq_start);
+       regs.ARM_r8 = (long)MX1_DMA_DIMR;
+       regs.ARM_r9 = (long)MX1_DMA_CCR(pcdev->dma_chan);
+       regs.ARM_r10 = (long)pcdev->base + CSICR1;
+       regs.ARM_fp = (long)pcdev->base + CSISR;
+       regs.ARM_sp = 1 << pcdev->dma_chan;
+       set_fiq_regs(&regs);
+       mxc_set_irq_fiq(irq, 1);
+       enable_fiq(irq);
+       pcdev->soc_host.drv_name        = DRIVER_NAME;
+       pcdev->soc_host.ops             = &mx1_soc_camera_host_ops;
+       pcdev->soc_host.priv            = pcdev;
+       pcdev->soc_host.v4l2_dev.dev    = &pdev->dev;
+       pcdev->soc_host.nr              = pdev->id;
+       err = soc_camera_host_register(&pcdev->soc_host);
+       if (err)
+               goto exit_free_irq;
+       dev_info(&pdev->dev, "MX1 Camera driver loaded\n");
+       return 0;
+ exit_free_irq:
+       disable_fiq(irq);
+       mxc_set_irq_fiq(irq, 0);
+       release_fiq(&fh);
+ exit_free_dma:
+       imx_dma_free(pcdev->dma_chan);
+ exit_iounmap:
+       iounmap(base);
+ exit_release:
+       release_mem_region(res->start, resource_size(res));
+ exit_kfree:
+       kfree(pcdev);
+ exit_put_clk:
+       clk_put(clk);
+ exit:
+       return err;
+ }
+ static int __exit mx1_camera_remove(struct platform_device *pdev)
+ {
+       struct soc_camera_host *soc_host = to_soc_camera_host(&pdev->dev);
+       struct mx1_camera_dev *pcdev = container_of(soc_host,
+                                       struct mx1_camera_dev, soc_host);
+       struct resource *res;
+       imx_dma_free(pcdev->dma_chan);
+       disable_fiq(pcdev->irq);
+       mxc_set_irq_fiq(pcdev->irq, 0);
+       release_fiq(&fh);
+       clk_put(pcdev->clk);
+       soc_camera_host_unregister(soc_host);
+       iounmap(pcdev->base);
+       res = pcdev->res;
+       release_mem_region(res->start, resource_size(res));
+       kfree(pcdev);
+       dev_info(&pdev->dev, "MX1 Camera driver unloaded\n");
+       return 0;
+ }
+ static struct platform_driver mx1_camera_driver = {
+       .driver         = {
+               .name   = DRIVER_NAME,
+       },
+       .remove         = __exit_p(mx1_camera_remove),
+ };
+ static int __init mx1_camera_init(void)
+ {
+       return platform_driver_probe(&mx1_camera_driver, mx1_camera_probe);
+ }
+ static void __exit mx1_camera_exit(void)
+ {
+       return platform_driver_unregister(&mx1_camera_driver);
+ }
+ module_init(mx1_camera_init);
+ module_exit(mx1_camera_exit);
+ MODULE_DESCRIPTION("i.MX1/i.MXL SoC Camera Host driver");
+ MODULE_AUTHOR("Paulius Zaleckas <paulius.zaleckas@teltonika.lt>");
+ MODULE_LICENSE("GPL v2");
+ MODULE_VERSION(DRIVER_VERSION);
+ MODULE_ALIAS("platform:" DRIVER_NAME);
index 0000000000000000000000000000000000000000,619df35de3da18d0691c2ea1438699b952cdc72e..403d7f17bfab1277adba82ed59d0650f99dd16aa
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,1863 +1,1863 @@@
 -#include <mach/mx2_cam.h>
+ /*
+  * V4L2 Driver for i.MX27/i.MX25 camera host
+  *
+  * Copyright (C) 2008, Sascha Hauer, Pengutronix
+  * Copyright (C) 2010, Baruch Siach, Orex Computed Radiography
+  * Copyright (C) 2012, Javier Martin, Vista Silicon S.L.
+  *
+  * 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/init.h>
+ #include <linux/module.h>
+ #include <linux/io.h>
+ #include <linux/delay.h>
+ #include <linux/slab.h>
+ #include <linux/dma-mapping.h>
+ #include <linux/errno.h>
+ #include <linux/fs.h>
+ #include <linux/gcd.h>
+ #include <linux/interrupt.h>
+ #include <linux/kernel.h>
+ #include <linux/math64.h>
+ #include <linux/mm.h>
+ #include <linux/moduleparam.h>
+ #include <linux/time.h>
+ #include <linux/device.h>
+ #include <linux/platform_device.h>
+ #include <linux/mutex.h>
+ #include <linux/clk.h>
+ #include <media/v4l2-common.h>
+ #include <media/v4l2-dev.h>
+ #include <media/videobuf2-core.h>
+ #include <media/videobuf2-dma-contig.h>
+ #include <media/soc_camera.h>
+ #include <media/soc_mediabus.h>
+ #include <linux/videodev2.h>
++#include <linux/platform_data/camera-mx2.h>
+ #include <mach/hardware.h>
+ #include <asm/dma.h>
+ #define MX2_CAM_DRV_NAME "mx2-camera"
+ #define MX2_CAM_VERSION "0.0.6"
+ #define MX2_CAM_DRIVER_DESCRIPTION "i.MX2x_Camera"
+ /* reset values */
+ #define CSICR1_RESET_VAL      0x40000800
+ #define CSICR2_RESET_VAL      0x0
+ #define CSICR3_RESET_VAL      0x0
+ /* csi control reg 1 */
+ #define CSICR1_SWAP16_EN      (1 << 31)
+ #define CSICR1_EXT_VSYNC      (1 << 30)
+ #define CSICR1_EOF_INTEN      (1 << 29)
+ #define CSICR1_PRP_IF_EN      (1 << 28)
+ #define CSICR1_CCIR_MODE      (1 << 27)
+ #define CSICR1_COF_INTEN      (1 << 26)
+ #define CSICR1_SF_OR_INTEN    (1 << 25)
+ #define CSICR1_RF_OR_INTEN    (1 << 24)
+ #define CSICR1_STATFF_LEVEL   (3 << 22)
+ #define CSICR1_STATFF_INTEN   (1 << 21)
+ #define CSICR1_RXFF_LEVEL(l)  (((l) & 3) << 19)       /* MX27 */
+ #define CSICR1_FB2_DMA_INTEN  (1 << 20)               /* MX25 */
+ #define CSICR1_FB1_DMA_INTEN  (1 << 19)               /* MX25 */
+ #define CSICR1_RXFF_INTEN     (1 << 18)
+ #define CSICR1_SOF_POL                (1 << 17)
+ #define CSICR1_SOF_INTEN      (1 << 16)
+ #define CSICR1_MCLKDIV(d)     (((d) & 0xF) << 12)
+ #define CSICR1_HSYNC_POL      (1 << 11)
+ #define CSICR1_CCIR_EN                (1 << 10)
+ #define CSICR1_MCLKEN         (1 << 9)
+ #define CSICR1_FCC            (1 << 8)
+ #define CSICR1_PACK_DIR               (1 << 7)
+ #define CSICR1_CLR_STATFIFO   (1 << 6)
+ #define CSICR1_CLR_RXFIFO     (1 << 5)
+ #define CSICR1_GCLK_MODE      (1 << 4)
+ #define CSICR1_INV_DATA               (1 << 3)
+ #define CSICR1_INV_PCLK               (1 << 2)
+ #define CSICR1_REDGE          (1 << 1)
+ #define CSICR1_FMT_MASK               (CSICR1_PACK_DIR | CSICR1_SWAP16_EN)
+ #define SHIFT_STATFF_LEVEL    22
+ #define SHIFT_RXFF_LEVEL      19
+ #define SHIFT_MCLKDIV         12
+ /* control reg 3 */
+ #define CSICR3_FRMCNT         (0xFFFF << 16)
+ #define CSICR3_FRMCNT_RST     (1 << 15)
+ #define CSICR3_DMA_REFLASH_RFF        (1 << 14)
+ #define CSICR3_DMA_REFLASH_SFF        (1 << 13)
+ #define CSICR3_DMA_REQ_EN_RFF (1 << 12)
+ #define CSICR3_DMA_REQ_EN_SFF (1 << 11)
+ #define CSICR3_RXFF_LEVEL(l)  (((l) & 7) << 4)        /* MX25 */
+ #define CSICR3_CSI_SUP                (1 << 3)
+ #define CSICR3_ZERO_PACK_EN   (1 << 2)
+ #define CSICR3_ECC_INT_EN     (1 << 1)
+ #define CSICR3_ECC_AUTO_EN    (1 << 0)
+ #define SHIFT_FRMCNT          16
+ /* csi status reg */
+ #define CSISR_SFF_OR_INT      (1 << 25)
+ #define CSISR_RFF_OR_INT      (1 << 24)
+ #define CSISR_STATFF_INT      (1 << 21)
+ #define CSISR_DMA_TSF_FB2_INT (1 << 20)       /* MX25 */
+ #define CSISR_DMA_TSF_FB1_INT (1 << 19)       /* MX25 */
+ #define CSISR_RXFF_INT                (1 << 18)
+ #define CSISR_EOF_INT         (1 << 17)
+ #define CSISR_SOF_INT         (1 << 16)
+ #define CSISR_F2_INT          (1 << 15)
+ #define CSISR_F1_INT          (1 << 14)
+ #define CSISR_COF_INT         (1 << 13)
+ #define CSISR_ECC_INT         (1 << 1)
+ #define CSISR_DRDY            (1 << 0)
+ #define CSICR1                        0x00
+ #define CSICR2                        0x04
+ #define CSISR                 (cpu_is_mx27() ? 0x08 : 0x18)
+ #define CSISTATFIFO           0x0c
+ #define CSIRFIFO              0x10
+ #define CSIRXCNT              0x14
+ #define CSICR3                        (cpu_is_mx27() ? 0x1C : 0x08)
+ #define CSIDMASA_STATFIFO     0x20
+ #define CSIDMATA_STATFIFO     0x24
+ #define CSIDMASA_FB1          0x28
+ #define CSIDMASA_FB2          0x2c
+ #define CSIFBUF_PARA          0x30
+ #define CSIIMAG_PARA          0x34
+ /* EMMA PrP */
+ #define PRP_CNTL                      0x00
+ #define PRP_INTR_CNTL                 0x04
+ #define PRP_INTRSTATUS                        0x08
+ #define PRP_SOURCE_Y_PTR              0x0c
+ #define PRP_SOURCE_CB_PTR             0x10
+ #define PRP_SOURCE_CR_PTR             0x14
+ #define PRP_DEST_RGB1_PTR             0x18
+ #define PRP_DEST_RGB2_PTR             0x1c
+ #define PRP_DEST_Y_PTR                        0x20
+ #define PRP_DEST_CB_PTR                       0x24
+ #define PRP_DEST_CR_PTR                       0x28
+ #define PRP_SRC_FRAME_SIZE            0x2c
+ #define PRP_DEST_CH1_LINE_STRIDE      0x30
+ #define PRP_SRC_PIXEL_FORMAT_CNTL     0x34
+ #define PRP_CH1_PIXEL_FORMAT_CNTL     0x38
+ #define PRP_CH1_OUT_IMAGE_SIZE                0x3c
+ #define PRP_CH2_OUT_IMAGE_SIZE                0x40
+ #define PRP_SRC_LINE_STRIDE           0x44
+ #define PRP_CSC_COEF_012              0x48
+ #define PRP_CSC_COEF_345              0x4c
+ #define PRP_CSC_COEF_678              0x50
+ #define PRP_CH1_RZ_HORI_COEF1         0x54
+ #define PRP_CH1_RZ_HORI_COEF2         0x58
+ #define PRP_CH1_RZ_HORI_VALID         0x5c
+ #define PRP_CH1_RZ_VERT_COEF1         0x60
+ #define PRP_CH1_RZ_VERT_COEF2         0x64
+ #define PRP_CH1_RZ_VERT_VALID         0x68
+ #define PRP_CH2_RZ_HORI_COEF1         0x6c
+ #define PRP_CH2_RZ_HORI_COEF2         0x70
+ #define PRP_CH2_RZ_HORI_VALID         0x74
+ #define PRP_CH2_RZ_VERT_COEF1         0x78
+ #define PRP_CH2_RZ_VERT_COEF2         0x7c
+ #define PRP_CH2_RZ_VERT_VALID         0x80
+ #define PRP_CNTL_CH1EN                (1 << 0)
+ #define PRP_CNTL_CH2EN                (1 << 1)
+ #define PRP_CNTL_CSIEN                (1 << 2)
+ #define PRP_CNTL_DATA_IN_YUV420       (0 << 3)
+ #define PRP_CNTL_DATA_IN_YUV422       (1 << 3)
+ #define PRP_CNTL_DATA_IN_RGB16        (2 << 3)
+ #define PRP_CNTL_DATA_IN_RGB32        (3 << 3)
+ #define PRP_CNTL_CH1_OUT_RGB8 (0 << 5)
+ #define PRP_CNTL_CH1_OUT_RGB16        (1 << 5)
+ #define PRP_CNTL_CH1_OUT_RGB32        (2 << 5)
+ #define PRP_CNTL_CH1_OUT_YUV422       (3 << 5)
+ #define PRP_CNTL_CH2_OUT_YUV420       (0 << 7)
+ #define PRP_CNTL_CH2_OUT_YUV422 (1 << 7)
+ #define PRP_CNTL_CH2_OUT_YUV444       (2 << 7)
+ #define PRP_CNTL_CH1_LEN      (1 << 9)
+ #define PRP_CNTL_CH2_LEN      (1 << 10)
+ #define PRP_CNTL_SKIP_FRAME   (1 << 11)
+ #define PRP_CNTL_SWRST                (1 << 12)
+ #define PRP_CNTL_CLKEN                (1 << 13)
+ #define PRP_CNTL_WEN          (1 << 14)
+ #define PRP_CNTL_CH1BYP               (1 << 15)
+ #define PRP_CNTL_IN_TSKIP(x)  ((x) << 16)
+ #define PRP_CNTL_CH1_TSKIP(x) ((x) << 19)
+ #define PRP_CNTL_CH2_TSKIP(x) ((x) << 22)
+ #define PRP_CNTL_INPUT_FIFO_LEVEL(x)  ((x) << 25)
+ #define PRP_CNTL_RZ_FIFO_LEVEL(x)     ((x) << 27)
+ #define PRP_CNTL_CH2B1EN      (1 << 29)
+ #define PRP_CNTL_CH2B2EN      (1 << 30)
+ #define PRP_CNTL_CH2FEN               (1 << 31)
+ /* IRQ Enable and status register */
+ #define PRP_INTR_RDERR                (1 << 0)
+ #define PRP_INTR_CH1WERR      (1 << 1)
+ #define PRP_INTR_CH2WERR      (1 << 2)
+ #define PRP_INTR_CH1FC                (1 << 3)
+ #define PRP_INTR_CH2FC                (1 << 5)
+ #define PRP_INTR_LBOVF                (1 << 7)
+ #define PRP_INTR_CH2OVF               (1 << 8)
+ /* Resizing registers */
+ #define PRP_RZ_VALID_TBL_LEN(x)       ((x) << 24)
+ #define PRP_RZ_VALID_BILINEAR (1 << 31)
+ #define MAX_VIDEO_MEM 16
+ #define RESIZE_NUM_MIN        1
+ #define RESIZE_NUM_MAX        20
+ #define BC_COEF               3
+ #define SZ_COEF               (1 << BC_COEF)
+ #define RESIZE_DIR_H  0
+ #define RESIZE_DIR_V  1
+ #define RESIZE_ALGO_BILINEAR 0
+ #define RESIZE_ALGO_AVERAGING 1
+ struct mx2_prp_cfg {
+       int channel;
+       u32 in_fmt;
+       u32 out_fmt;
+       u32 src_pixel;
+       u32 ch1_pixel;
+       u32 irq_flags;
+       u32 csicr1;
+ };
+ /* prp resizing parameters */
+ struct emma_prp_resize {
+       int             algo; /* type of algorithm used */
+       int             len; /* number of coefficients */
+       unsigned char   s[RESIZE_NUM_MAX]; /* table of coefficients */
+ };
+ /* prp configuration for a client-host fmt pair */
+ struct mx2_fmt_cfg {
+       enum v4l2_mbus_pixelcode        in_fmt;
+       u32                             out_fmt;
+       struct mx2_prp_cfg              cfg;
+ };
+ enum mx2_buffer_state {
+       MX2_STATE_QUEUED,
+       MX2_STATE_ACTIVE,
+       MX2_STATE_DONE,
+ };
+ struct mx2_buf_internal {
+       struct list_head        queue;
+       int                     bufnum;
+       bool                    discard;
+ };
+ /* buffer for one video frame */
+ struct mx2_buffer {
+       /* common v4l buffer stuff -- must be first */
+       struct vb2_buffer               vb;
+       enum mx2_buffer_state           state;
+       struct mx2_buf_internal         internal;
+ };
+ struct mx2_camera_dev {
+       struct device           *dev;
+       struct soc_camera_host  soc_host;
+       struct soc_camera_device *icd;
+       struct clk              *clk_csi, *clk_emma_ahb, *clk_emma_ipg;
+       void __iomem            *base_csi, *base_emma;
+       struct mx2_camera_platform_data *pdata;
+       unsigned long           platform_flags;
+       struct list_head        capture;
+       struct list_head        active_bufs;
+       struct list_head        discard;
+       spinlock_t              lock;
+       int                     dma;
+       struct mx2_buffer       *active;
+       struct mx2_buffer       *fb1_active;
+       struct mx2_buffer       *fb2_active;
+       u32                     csicr1;
+       struct mx2_buf_internal buf_discard[2];
+       void                    *discard_buffer;
+       dma_addr_t              discard_buffer_dma;
+       size_t                  discard_size;
+       struct mx2_fmt_cfg      *emma_prp;
+       struct emma_prp_resize  resizing[2];
+       unsigned int            s_width, s_height;
+       u32                     frame_count;
+       struct vb2_alloc_ctx    *alloc_ctx;
+ };
+ static struct mx2_buffer *mx2_ibuf_to_buf(struct mx2_buf_internal *int_buf)
+ {
+       return container_of(int_buf, struct mx2_buffer, internal);
+ }
+ static struct mx2_fmt_cfg mx27_emma_prp_table[] = {
+       /*
+        * This is a generic configuration which is valid for most
+        * prp input-output format combinations.
+        * We set the incomming and outgoing pixelformat to a
+        * 16 Bit wide format and adjust the bytesperline
+        * accordingly. With this configuration the inputdata
+        * will not be changed by the emma and could be any type
+        * of 16 Bit Pixelformat.
+        */
+       {
+               .in_fmt         = 0,
+               .out_fmt        = 0,
+               .cfg            = {
+                       .channel        = 1,
+                       .in_fmt         = PRP_CNTL_DATA_IN_RGB16,
+                       .out_fmt        = PRP_CNTL_CH1_OUT_RGB16,
+                       .src_pixel      = 0x2ca00565, /* RGB565 */
+                       .ch1_pixel      = 0x2ca00565, /* RGB565 */
+                       .irq_flags      = PRP_INTR_RDERR | PRP_INTR_CH1WERR |
+                                               PRP_INTR_CH1FC | PRP_INTR_LBOVF,
+                       .csicr1         = 0,
+               }
+       },
+       {
+               .in_fmt         = V4L2_MBUS_FMT_UYVY8_2X8,
+               .out_fmt        = V4L2_PIX_FMT_YUYV,
+               .cfg            = {
+                       .channel        = 1,
+                       .in_fmt         = PRP_CNTL_DATA_IN_YUV422,
+                       .out_fmt        = PRP_CNTL_CH1_OUT_YUV422,
+                       .src_pixel      = 0x22000888, /* YUV422 (YUYV) */
+                       .ch1_pixel      = 0x62000888, /* YUV422 (YUYV) */
+                       .irq_flags      = PRP_INTR_RDERR | PRP_INTR_CH1WERR |
+                                               PRP_INTR_CH1FC | PRP_INTR_LBOVF,
+                       .csicr1         = CSICR1_SWAP16_EN,
+               }
+       },
+       {
+               .in_fmt         = V4L2_MBUS_FMT_YUYV8_2X8,
+               .out_fmt        = V4L2_PIX_FMT_YUYV,
+               .cfg            = {
+                       .channel        = 1,
+                       .in_fmt         = PRP_CNTL_DATA_IN_YUV422,
+                       .out_fmt        = PRP_CNTL_CH1_OUT_YUV422,
+                       .src_pixel      = 0x22000888, /* YUV422 (YUYV) */
+                       .ch1_pixel      = 0x62000888, /* YUV422 (YUYV) */
+                       .irq_flags      = PRP_INTR_RDERR | PRP_INTR_CH1WERR |
+                                               PRP_INTR_CH1FC | PRP_INTR_LBOVF,
+                       .csicr1         = CSICR1_PACK_DIR,
+               }
+       },
+       {
+               .in_fmt         = V4L2_MBUS_FMT_YUYV8_2X8,
+               .out_fmt        = V4L2_PIX_FMT_YUV420,
+               .cfg            = {
+                       .channel        = 2,
+                       .in_fmt         = PRP_CNTL_DATA_IN_YUV422,
+                       .out_fmt        = PRP_CNTL_CH2_OUT_YUV420,
+                       .src_pixel      = 0x22000888, /* YUV422 (YUYV) */
+                       .irq_flags      = PRP_INTR_RDERR | PRP_INTR_CH2WERR |
+                                       PRP_INTR_CH2FC | PRP_INTR_LBOVF |
+                                       PRP_INTR_CH2OVF,
+                       .csicr1         = CSICR1_PACK_DIR,
+               }
+       },
+       {
+               .in_fmt         = V4L2_MBUS_FMT_UYVY8_2X8,
+               .out_fmt        = V4L2_PIX_FMT_YUV420,
+               .cfg            = {
+                       .channel        = 2,
+                       .in_fmt         = PRP_CNTL_DATA_IN_YUV422,
+                       .out_fmt        = PRP_CNTL_CH2_OUT_YUV420,
+                       .src_pixel      = 0x22000888, /* YUV422 (YUYV) */
+                       .irq_flags      = PRP_INTR_RDERR | PRP_INTR_CH2WERR |
+                                       PRP_INTR_CH2FC | PRP_INTR_LBOVF |
+                                       PRP_INTR_CH2OVF,
+                       .csicr1         = CSICR1_SWAP16_EN,
+               }
+       },
+ };
+ static struct mx2_fmt_cfg *mx27_emma_prp_get_format(
+                                       enum v4l2_mbus_pixelcode in_fmt,
+                                       u32 out_fmt)
+ {
+       int i;
+       for (i = 1; i < ARRAY_SIZE(mx27_emma_prp_table); i++)
+               if ((mx27_emma_prp_table[i].in_fmt == in_fmt) &&
+                               (mx27_emma_prp_table[i].out_fmt == out_fmt)) {
+                       return &mx27_emma_prp_table[i];
+               }
+       /* If no match return the most generic configuration */
+       return &mx27_emma_prp_table[0];
+ };
+ static void mx27_update_emma_buf(struct mx2_camera_dev *pcdev,
+                                unsigned long phys, int bufnum)
+ {
+       struct mx2_fmt_cfg *prp = pcdev->emma_prp;
+       if (prp->cfg.channel == 1) {
+               writel(phys, pcdev->base_emma +
+                               PRP_DEST_RGB1_PTR + 4 * bufnum);
+       } else {
+               writel(phys, pcdev->base_emma +
+                       PRP_DEST_Y_PTR - 0x14 * bufnum);
+               if (prp->out_fmt == V4L2_PIX_FMT_YUV420) {
+                       u32 imgsize = pcdev->icd->user_height *
+                                       pcdev->icd->user_width;
+                       writel(phys + imgsize, pcdev->base_emma +
+                               PRP_DEST_CB_PTR - 0x14 * bufnum);
+                       writel(phys + ((5 * imgsize) / 4), pcdev->base_emma +
+                               PRP_DEST_CR_PTR - 0x14 * bufnum);
+               }
+       }
+ }
+ static void mx2_camera_deactivate(struct mx2_camera_dev *pcdev)
+ {
+       unsigned long flags;
+       clk_disable_unprepare(pcdev->clk_csi);
+       writel(0, pcdev->base_csi + CSICR1);
+       if (cpu_is_mx27()) {
+               writel(0, pcdev->base_emma + PRP_CNTL);
+       } else if (cpu_is_mx25()) {
+               spin_lock_irqsave(&pcdev->lock, flags);
+               pcdev->fb1_active = NULL;
+               pcdev->fb2_active = NULL;
+               writel(0, pcdev->base_csi + CSIDMASA_FB1);
+               writel(0, pcdev->base_csi + CSIDMASA_FB2);
+               spin_unlock_irqrestore(&pcdev->lock, flags);
+       }
+ }
+ /*
+  * The following two functions absolutely depend on the fact, that
+  * there can be only one camera on mx2 camera sensor interface
+  */
+ static int mx2_camera_add_device(struct soc_camera_device *icd)
+ {
+       struct soc_camera_host *ici = to_soc_camera_host(icd->parent);
+       struct mx2_camera_dev *pcdev = ici->priv;
+       int ret;
+       u32 csicr1;
+       if (pcdev->icd)
+               return -EBUSY;
+       ret = clk_prepare_enable(pcdev->clk_csi);
+       if (ret < 0)
+               return ret;
+       csicr1 = CSICR1_MCLKEN;
+       if (cpu_is_mx27())
+               csicr1 |= CSICR1_PRP_IF_EN | CSICR1_FCC |
+                       CSICR1_RXFF_LEVEL(0);
+       pcdev->csicr1 = csicr1;
+       writel(pcdev->csicr1, pcdev->base_csi + CSICR1);
+       pcdev->icd = icd;
+       pcdev->frame_count = 0;
+       dev_info(icd->parent, "Camera driver attached to camera %d\n",
+                icd->devnum);
+       return 0;
+ }
+ static void mx2_camera_remove_device(struct soc_camera_device *icd)
+ {
+       struct soc_camera_host *ici = to_soc_camera_host(icd->parent);
+       struct mx2_camera_dev *pcdev = ici->priv;
+       BUG_ON(icd != pcdev->icd);
+       dev_info(icd->parent, "Camera driver detached from camera %d\n",
+                icd->devnum);
+       mx2_camera_deactivate(pcdev);
+       pcdev->icd = NULL;
+ }
+ static void mx25_camera_frame_done(struct mx2_camera_dev *pcdev, int fb,
+               int state)
+ {
+       struct vb2_buffer *vb;
+       struct mx2_buffer *buf;
+       struct mx2_buffer **fb_active = fb == 1 ? &pcdev->fb1_active :
+               &pcdev->fb2_active;
+       u32 fb_reg = fb == 1 ? CSIDMASA_FB1 : CSIDMASA_FB2;
+       unsigned long flags;
+       spin_lock_irqsave(&pcdev->lock, flags);
+       if (*fb_active == NULL)
+               goto out;
+       vb = &(*fb_active)->vb;
+       dev_dbg(pcdev->dev, "%s (vb=0x%p) 0x%p %lu\n", __func__,
+               vb, vb2_plane_vaddr(vb, 0), vb2_get_plane_payload(vb, 0));
+       do_gettimeofday(&vb->v4l2_buf.timestamp);
+       vb->v4l2_buf.sequence++;
+       vb2_buffer_done(vb, VB2_BUF_STATE_DONE);
+       if (list_empty(&pcdev->capture)) {
+               buf = NULL;
+               writel(0, pcdev->base_csi + fb_reg);
+       } else {
+               buf = list_first_entry(&pcdev->capture, struct mx2_buffer,
+                               internal.queue);
+               vb = &buf->vb;
+               list_del(&buf->internal.queue);
+               buf->state = MX2_STATE_ACTIVE;
+               writel(vb2_dma_contig_plane_dma_addr(vb, 0),
+                      pcdev->base_csi + fb_reg);
+       }
+       *fb_active = buf;
+ out:
+       spin_unlock_irqrestore(&pcdev->lock, flags);
+ }
+ static irqreturn_t mx25_camera_irq(int irq_csi, void *data)
+ {
+       struct mx2_camera_dev *pcdev = data;
+       u32 status = readl(pcdev->base_csi + CSISR);
+       if (status & CSISR_DMA_TSF_FB1_INT)
+               mx25_camera_frame_done(pcdev, 1, MX2_STATE_DONE);
+       else if (status & CSISR_DMA_TSF_FB2_INT)
+               mx25_camera_frame_done(pcdev, 2, MX2_STATE_DONE);
+       /* FIXME: handle CSISR_RFF_OR_INT */
+       writel(status, pcdev->base_csi + CSISR);
+       return IRQ_HANDLED;
+ }
+ /*
+  *  Videobuf operations
+  */
+ static int mx2_videobuf_setup(struct vb2_queue *vq,
+                       const struct v4l2_format *fmt,
+                       unsigned int *count, unsigned int *num_planes,
+                       unsigned int sizes[], void *alloc_ctxs[])
+ {
+       struct soc_camera_device *icd = soc_camera_from_vb2q(vq);
+       struct soc_camera_host *ici = to_soc_camera_host(icd->parent);
+       struct mx2_camera_dev *pcdev = ici->priv;
+       dev_dbg(icd->parent, "count=%d, size=%d\n", *count, sizes[0]);
+       /* TODO: support for VIDIOC_CREATE_BUFS not ready */
+       if (fmt != NULL)
+               return -ENOTTY;
+       alloc_ctxs[0] = pcdev->alloc_ctx;
+       sizes[0] = icd->sizeimage;
+       if (0 == *count)
+               *count = 32;
+       if (!*num_planes &&
+           sizes[0] * *count > MAX_VIDEO_MEM * 1024 * 1024)
+               *count = (MAX_VIDEO_MEM * 1024 * 1024) / sizes[0];
+       *num_planes = 1;
+       return 0;
+ }
+ static int mx2_videobuf_prepare(struct vb2_buffer *vb)
+ {
+       struct soc_camera_device *icd = soc_camera_from_vb2q(vb->vb2_queue);
+       int ret = 0;
+       dev_dbg(icd->parent, "%s (vb=0x%p) 0x%p %lu\n", __func__,
+               vb, vb2_plane_vaddr(vb, 0), vb2_get_plane_payload(vb, 0));
+ #ifdef DEBUG
+       /*
+        * This can be useful if you want to see if we actually fill
+        * the buffer with something
+        */
+       memset((void *)vb2_plane_vaddr(vb, 0),
+              0xaa, vb2_get_plane_payload(vb, 0));
+ #endif
+       vb2_set_plane_payload(vb, 0, icd->sizeimage);
+       if (vb2_plane_vaddr(vb, 0) &&
+           vb2_get_plane_payload(vb, 0) > vb2_plane_size(vb, 0)) {
+               ret = -EINVAL;
+               goto out;
+       }
+       return 0;
+ out:
+       return ret;
+ }
+ static void mx2_videobuf_queue(struct vb2_buffer *vb)
+ {
+       struct soc_camera_device *icd = soc_camera_from_vb2q(vb->vb2_queue);
+       struct soc_camera_host *ici =
+               to_soc_camera_host(icd->parent);
+       struct mx2_camera_dev *pcdev = ici->priv;
+       struct mx2_buffer *buf = container_of(vb, struct mx2_buffer, vb);
+       unsigned long flags;
+       dev_dbg(icd->parent, "%s (vb=0x%p) 0x%p %lu\n", __func__,
+               vb, vb2_plane_vaddr(vb, 0), vb2_get_plane_payload(vb, 0));
+       spin_lock_irqsave(&pcdev->lock, flags);
+       buf->state = MX2_STATE_QUEUED;
+       list_add_tail(&buf->internal.queue, &pcdev->capture);
+       if (cpu_is_mx25()) {
+               u32 csicr3, dma_inten = 0;
+               if (pcdev->fb1_active == NULL) {
+                       writel(vb2_dma_contig_plane_dma_addr(vb, 0),
+                                       pcdev->base_csi + CSIDMASA_FB1);
+                       pcdev->fb1_active = buf;
+                       dma_inten = CSICR1_FB1_DMA_INTEN;
+               } else if (pcdev->fb2_active == NULL) {
+                       writel(vb2_dma_contig_plane_dma_addr(vb, 0),
+                                       pcdev->base_csi + CSIDMASA_FB2);
+                       pcdev->fb2_active = buf;
+                       dma_inten = CSICR1_FB2_DMA_INTEN;
+               }
+               if (dma_inten) {
+                       list_del(&buf->internal.queue);
+                       buf->state = MX2_STATE_ACTIVE;
+                       csicr3 = readl(pcdev->base_csi + CSICR3);
+                       /* Reflash DMA */
+                       writel(csicr3 | CSICR3_DMA_REFLASH_RFF,
+                                       pcdev->base_csi + CSICR3);
+                       /* clear & enable interrupts */
+                       writel(dma_inten, pcdev->base_csi + CSISR);
+                       pcdev->csicr1 |= dma_inten;
+                       writel(pcdev->csicr1, pcdev->base_csi + CSICR1);
+                       /* enable DMA */
+                       csicr3 |= CSICR3_DMA_REQ_EN_RFF | CSICR3_RXFF_LEVEL(1);
+                       writel(csicr3, pcdev->base_csi + CSICR3);
+               }
+       }
+       spin_unlock_irqrestore(&pcdev->lock, flags);
+ }
+ static void mx2_videobuf_release(struct vb2_buffer *vb)
+ {
+       struct soc_camera_device *icd = soc_camera_from_vb2q(vb->vb2_queue);
+       struct soc_camera_host *ici = to_soc_camera_host(icd->parent);
+       struct mx2_camera_dev *pcdev = ici->priv;
+       struct mx2_buffer *buf = container_of(vb, struct mx2_buffer, vb);
+       unsigned long flags;
+ #ifdef DEBUG
+       dev_dbg(icd->parent, "%s (vb=0x%p) 0x%p %lu\n", __func__,
+               vb, vb2_plane_vaddr(vb, 0), vb2_get_plane_payload(vb, 0));
+       switch (buf->state) {
+       case MX2_STATE_ACTIVE:
+               dev_info(icd->parent, "%s (active)\n", __func__);
+               break;
+       case MX2_STATE_QUEUED:
+               dev_info(icd->parent, "%s (queued)\n", __func__);
+               break;
+       default:
+               dev_info(icd->parent, "%s (unknown) %d\n", __func__,
+                               buf->state);
+               break;
+       }
+ #endif
+       /*
+        * Terminate only queued but inactive buffers. Active buffers are
+        * released when they become inactive after videobuf_waiton().
+        *
+        * FIXME: implement forced termination of active buffers for mx27 and
+        * mx27 eMMA, so that the user won't get stuck in an uninterruptible
+        * state. This requires a specific handling for each of the these DMA
+        * types.
+        */
+       spin_lock_irqsave(&pcdev->lock, flags);
+       if (cpu_is_mx25() && buf->state == MX2_STATE_ACTIVE) {
+               if (pcdev->fb1_active == buf) {
+                       pcdev->csicr1 &= ~CSICR1_FB1_DMA_INTEN;
+                       writel(0, pcdev->base_csi + CSIDMASA_FB1);
+                       pcdev->fb1_active = NULL;
+               } else if (pcdev->fb2_active == buf) {
+                       pcdev->csicr1 &= ~CSICR1_FB2_DMA_INTEN;
+                       writel(0, pcdev->base_csi + CSIDMASA_FB2);
+                       pcdev->fb2_active = NULL;
+               }
+               writel(pcdev->csicr1, pcdev->base_csi + CSICR1);
+       }
+       spin_unlock_irqrestore(&pcdev->lock, flags);
+ }
+ static void mx27_camera_emma_buf_init(struct soc_camera_device *icd,
+               int bytesperline)
+ {
+       struct soc_camera_host *ici =
+               to_soc_camera_host(icd->parent);
+       struct mx2_camera_dev *pcdev = ici->priv;
+       struct mx2_fmt_cfg *prp = pcdev->emma_prp;
+       writel((pcdev->s_width << 16) | pcdev->s_height,
+              pcdev->base_emma + PRP_SRC_FRAME_SIZE);
+       writel(prp->cfg.src_pixel,
+              pcdev->base_emma + PRP_SRC_PIXEL_FORMAT_CNTL);
+       if (prp->cfg.channel == 1) {
+               writel((icd->user_width << 16) | icd->user_height,
+                       pcdev->base_emma + PRP_CH1_OUT_IMAGE_SIZE);
+               writel(bytesperline,
+                       pcdev->base_emma + PRP_DEST_CH1_LINE_STRIDE);
+               writel(prp->cfg.ch1_pixel,
+                       pcdev->base_emma + PRP_CH1_PIXEL_FORMAT_CNTL);
+       } else { /* channel 2 */
+               writel((icd->user_width << 16) | icd->user_height,
+                       pcdev->base_emma + PRP_CH2_OUT_IMAGE_SIZE);
+       }
+       /* Enable interrupts */
+       writel(prp->cfg.irq_flags, pcdev->base_emma + PRP_INTR_CNTL);
+ }
+ static void mx2_prp_resize_commit(struct mx2_camera_dev *pcdev)
+ {
+       int dir;
+       for (dir = RESIZE_DIR_H; dir <= RESIZE_DIR_V; dir++) {
+               unsigned char *s = pcdev->resizing[dir].s;
+               int len = pcdev->resizing[dir].len;
+               unsigned int coeff[2] = {0, 0};
+               unsigned int valid  = 0;
+               int i;
+               if (len == 0)
+                       continue;
+               for (i = RESIZE_NUM_MAX - 1; i >= 0; i--) {
+                       int j;
+                       j = i > 9 ? 1 : 0;
+                       coeff[j] = (coeff[j] << BC_COEF) |
+                                       (s[i] & (SZ_COEF - 1));
+                       if (i == 5 || i == 15)
+                               coeff[j] <<= 1;
+                       valid = (valid << 1) | (s[i] >> BC_COEF);
+               }
+               valid |= PRP_RZ_VALID_TBL_LEN(len);
+               if (pcdev->resizing[dir].algo == RESIZE_ALGO_BILINEAR)
+                       valid |= PRP_RZ_VALID_BILINEAR;
+               if (pcdev->emma_prp->cfg.channel == 1) {
+                       if (dir == RESIZE_DIR_H) {
+                               writel(coeff[0], pcdev->base_emma +
+                                                       PRP_CH1_RZ_HORI_COEF1);
+                               writel(coeff[1], pcdev->base_emma +
+                                                       PRP_CH1_RZ_HORI_COEF2);
+                               writel(valid, pcdev->base_emma +
+                                                       PRP_CH1_RZ_HORI_VALID);
+                       } else {
+                               writel(coeff[0], pcdev->base_emma +
+                                                       PRP_CH1_RZ_VERT_COEF1);
+                               writel(coeff[1], pcdev->base_emma +
+                                                       PRP_CH1_RZ_VERT_COEF2);
+                               writel(valid, pcdev->base_emma +
+                                                       PRP_CH1_RZ_VERT_VALID);
+                       }
+               } else {
+                       if (dir == RESIZE_DIR_H) {
+                               writel(coeff[0], pcdev->base_emma +
+                                                       PRP_CH2_RZ_HORI_COEF1);
+                               writel(coeff[1], pcdev->base_emma +
+                                                       PRP_CH2_RZ_HORI_COEF2);
+                               writel(valid, pcdev->base_emma +
+                                                       PRP_CH2_RZ_HORI_VALID);
+                       } else {
+                               writel(coeff[0], pcdev->base_emma +
+                                                       PRP_CH2_RZ_VERT_COEF1);
+                               writel(coeff[1], pcdev->base_emma +
+                                                       PRP_CH2_RZ_VERT_COEF2);
+                               writel(valid, pcdev->base_emma +
+                                                       PRP_CH2_RZ_VERT_VALID);
+                       }
+               }
+       }
+ }
+ static int mx2_start_streaming(struct vb2_queue *q, unsigned int count)
+ {
+       struct soc_camera_device *icd = soc_camera_from_vb2q(q);
+       struct soc_camera_host *ici =
+               to_soc_camera_host(icd->parent);
+       struct mx2_camera_dev *pcdev = ici->priv;
+       struct mx2_fmt_cfg *prp = pcdev->emma_prp;
+       struct vb2_buffer *vb;
+       struct mx2_buffer *buf;
+       unsigned long phys;
+       int bytesperline;
+       if (cpu_is_mx27()) {
+               unsigned long flags;
+               if (count < 2)
+                       return -EINVAL;
+               spin_lock_irqsave(&pcdev->lock, flags);
+               buf = list_first_entry(&pcdev->capture, struct mx2_buffer,
+                                      internal.queue);
+               buf->internal.bufnum = 0;
+               vb = &buf->vb;
+               buf->state = MX2_STATE_ACTIVE;
+               phys = vb2_dma_contig_plane_dma_addr(vb, 0);
+               mx27_update_emma_buf(pcdev, phys, buf->internal.bufnum);
+               list_move_tail(pcdev->capture.next, &pcdev->active_bufs);
+               buf = list_first_entry(&pcdev->capture, struct mx2_buffer,
+                                      internal.queue);
+               buf->internal.bufnum = 1;
+               vb = &buf->vb;
+               buf->state = MX2_STATE_ACTIVE;
+               phys = vb2_dma_contig_plane_dma_addr(vb, 0);
+               mx27_update_emma_buf(pcdev, phys, buf->internal.bufnum);
+               list_move_tail(pcdev->capture.next, &pcdev->active_bufs);
+               bytesperline = soc_mbus_bytes_per_line(icd->user_width,
+                               icd->current_fmt->host_fmt);
+               if (bytesperline < 0)
+                       return bytesperline;
+               /*
+                * I didn't manage to properly enable/disable the prp
+                * on a per frame basis during running transfers,
+                * thus we allocate a buffer here and use it to
+                * discard frames when no buffer is available.
+                * Feel free to work on this ;)
+                */
+               pcdev->discard_size = icd->user_height * bytesperline;
+               pcdev->discard_buffer = dma_alloc_coherent(ici->v4l2_dev.dev,
+                               pcdev->discard_size, &pcdev->discard_buffer_dma,
+                               GFP_KERNEL);
+               if (!pcdev->discard_buffer)
+                       return -ENOMEM;
+               pcdev->buf_discard[0].discard = true;
+               list_add_tail(&pcdev->buf_discard[0].queue,
+                                     &pcdev->discard);
+               pcdev->buf_discard[1].discard = true;
+               list_add_tail(&pcdev->buf_discard[1].queue,
+                                     &pcdev->discard);
+               mx2_prp_resize_commit(pcdev);
+               mx27_camera_emma_buf_init(icd, bytesperline);
+               if (prp->cfg.channel == 1) {
+                       writel(PRP_CNTL_CH1EN |
+                               PRP_CNTL_CSIEN |
+                               prp->cfg.in_fmt |
+                               prp->cfg.out_fmt |
+                               PRP_CNTL_CH1_LEN |
+                               PRP_CNTL_CH1BYP |
+                               PRP_CNTL_CH1_TSKIP(0) |
+                               PRP_CNTL_IN_TSKIP(0),
+                               pcdev->base_emma + PRP_CNTL);
+               } else {
+                       writel(PRP_CNTL_CH2EN |
+                               PRP_CNTL_CSIEN |
+                               prp->cfg.in_fmt |
+                               prp->cfg.out_fmt |
+                               PRP_CNTL_CH2_LEN |
+                               PRP_CNTL_CH2_TSKIP(0) |
+                               PRP_CNTL_IN_TSKIP(0),
+                               pcdev->base_emma + PRP_CNTL);
+               }
+               spin_unlock_irqrestore(&pcdev->lock, flags);
+       }
+       return 0;
+ }
+ static int mx2_stop_streaming(struct vb2_queue *q)
+ {
+       struct soc_camera_device *icd = soc_camera_from_vb2q(q);
+       struct soc_camera_host *ici =
+               to_soc_camera_host(icd->parent);
+       struct mx2_camera_dev *pcdev = ici->priv;
+       struct mx2_fmt_cfg *prp = pcdev->emma_prp;
+       unsigned long flags;
+       void *b;
+       u32 cntl;
+       if (cpu_is_mx27()) {
+               spin_lock_irqsave(&pcdev->lock, flags);
+               cntl = readl(pcdev->base_emma + PRP_CNTL);
+               if (prp->cfg.channel == 1) {
+                       writel(cntl & ~PRP_CNTL_CH1EN,
+                              pcdev->base_emma + PRP_CNTL);
+               } else {
+                       writel(cntl & ~PRP_CNTL_CH2EN,
+                              pcdev->base_emma + PRP_CNTL);
+               }
+               INIT_LIST_HEAD(&pcdev->capture);
+               INIT_LIST_HEAD(&pcdev->active_bufs);
+               INIT_LIST_HEAD(&pcdev->discard);
+               b = pcdev->discard_buffer;
+               pcdev->discard_buffer = NULL;
+               spin_unlock_irqrestore(&pcdev->lock, flags);
+               dma_free_coherent(ici->v4l2_dev.dev,
+                       pcdev->discard_size, b, pcdev->discard_buffer_dma);
+       }
+       return 0;
+ }
+ static struct vb2_ops mx2_videobuf_ops = {
+       .queue_setup     = mx2_videobuf_setup,
+       .buf_prepare     = mx2_videobuf_prepare,
+       .buf_queue       = mx2_videobuf_queue,
+       .buf_cleanup     = mx2_videobuf_release,
+       .start_streaming = mx2_start_streaming,
+       .stop_streaming  = mx2_stop_streaming,
+ };
+ static int mx2_camera_init_videobuf(struct vb2_queue *q,
+                             struct soc_camera_device *icd)
+ {
+       q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+       q->io_modes = VB2_MMAP | VB2_USERPTR;
+       q->drv_priv = icd;
+       q->ops = &mx2_videobuf_ops;
+       q->mem_ops = &vb2_dma_contig_memops;
+       q->buf_struct_size = sizeof(struct mx2_buffer);
+       return vb2_queue_init(q);
+ }
+ #define MX2_BUS_FLAGS (V4L2_MBUS_MASTER | \
+                       V4L2_MBUS_VSYNC_ACTIVE_HIGH | \
+                       V4L2_MBUS_VSYNC_ACTIVE_LOW | \
+                       V4L2_MBUS_HSYNC_ACTIVE_HIGH | \
+                       V4L2_MBUS_HSYNC_ACTIVE_LOW | \
+                       V4L2_MBUS_PCLK_SAMPLE_RISING | \
+                       V4L2_MBUS_PCLK_SAMPLE_FALLING | \
+                       V4L2_MBUS_DATA_ACTIVE_HIGH | \
+                       V4L2_MBUS_DATA_ACTIVE_LOW)
+ static int mx27_camera_emma_prp_reset(struct mx2_camera_dev *pcdev)
+ {
+       u32 cntl;
+       int count = 0;
+       cntl = readl(pcdev->base_emma + PRP_CNTL);
+       writel(PRP_CNTL_SWRST, pcdev->base_emma + PRP_CNTL);
+       while (count++ < 100) {
+               if (!(readl(pcdev->base_emma + PRP_CNTL) & PRP_CNTL_SWRST))
+                       return 0;
+               barrier();
+               udelay(1);
+       }
+       return -ETIMEDOUT;
+ }
+ static int mx2_camera_set_bus_param(struct soc_camera_device *icd)
+ {
+       struct v4l2_subdev *sd = soc_camera_to_subdev(icd);
+       struct soc_camera_host *ici = to_soc_camera_host(icd->parent);
+       struct mx2_camera_dev *pcdev = ici->priv;
+       struct v4l2_mbus_config cfg = {.type = V4L2_MBUS_PARALLEL,};
+       unsigned long common_flags;
+       int ret;
+       int bytesperline;
+       u32 csicr1 = pcdev->csicr1;
+       ret = v4l2_subdev_call(sd, video, g_mbus_config, &cfg);
+       if (!ret) {
+               common_flags = soc_mbus_config_compatible(&cfg, MX2_BUS_FLAGS);
+               if (!common_flags) {
+                       dev_warn(icd->parent,
+                                "Flags incompatible: camera 0x%x, host 0x%x\n",
+                                cfg.flags, MX2_BUS_FLAGS);
+                       return -EINVAL;
+               }
+       } else if (ret != -ENOIOCTLCMD) {
+               return ret;
+       } else {
+               common_flags = MX2_BUS_FLAGS;
+       }
+       if ((common_flags & V4L2_MBUS_HSYNC_ACTIVE_HIGH) &&
+           (common_flags & V4L2_MBUS_HSYNC_ACTIVE_LOW)) {
+               if (pcdev->platform_flags & MX2_CAMERA_HSYNC_HIGH)
+                       common_flags &= ~V4L2_MBUS_HSYNC_ACTIVE_LOW;
+               else
+                       common_flags &= ~V4L2_MBUS_HSYNC_ACTIVE_HIGH;
+       }
+       if ((common_flags & V4L2_MBUS_PCLK_SAMPLE_RISING) &&
+           (common_flags & V4L2_MBUS_PCLK_SAMPLE_FALLING)) {
+               if (pcdev->platform_flags & MX2_CAMERA_PCLK_SAMPLE_RISING)
+                       common_flags &= ~V4L2_MBUS_PCLK_SAMPLE_FALLING;
+               else
+                       common_flags &= ~V4L2_MBUS_PCLK_SAMPLE_RISING;
+       }
+       cfg.flags = common_flags;
+       ret = v4l2_subdev_call(sd, video, s_mbus_config, &cfg);
+       if (ret < 0 && ret != -ENOIOCTLCMD) {
+               dev_dbg(icd->parent, "camera s_mbus_config(0x%lx) returned %d\n",
+                       common_flags, ret);
+               return ret;
+       }
+       csicr1 = (csicr1 & ~CSICR1_FMT_MASK) | pcdev->emma_prp->cfg.csicr1;
+       if (common_flags & V4L2_MBUS_PCLK_SAMPLE_RISING)
+               csicr1 |= CSICR1_REDGE;
+       if (common_flags & V4L2_MBUS_VSYNC_ACTIVE_HIGH)
+               csicr1 |= CSICR1_SOF_POL;
+       if (common_flags & V4L2_MBUS_HSYNC_ACTIVE_HIGH)
+               csicr1 |= CSICR1_HSYNC_POL;
+       if (pcdev->platform_flags & MX2_CAMERA_EXT_VSYNC)
+               csicr1 |= CSICR1_EXT_VSYNC;
+       if (pcdev->platform_flags & MX2_CAMERA_CCIR)
+               csicr1 |= CSICR1_CCIR_EN;
+       if (pcdev->platform_flags & MX2_CAMERA_CCIR_INTERLACE)
+               csicr1 |= CSICR1_CCIR_MODE;
+       if (pcdev->platform_flags & MX2_CAMERA_GATED_CLOCK)
+               csicr1 |= CSICR1_GCLK_MODE;
+       if (pcdev->platform_flags & MX2_CAMERA_INV_DATA)
+               csicr1 |= CSICR1_INV_DATA;
+       pcdev->csicr1 = csicr1;
+       bytesperline = soc_mbus_bytes_per_line(icd->user_width,
+                       icd->current_fmt->host_fmt);
+       if (bytesperline < 0)
+               return bytesperline;
+       if (cpu_is_mx27()) {
+               ret = mx27_camera_emma_prp_reset(pcdev);
+               if (ret)
+                       return ret;
+       } else if (cpu_is_mx25()) {
+               writel((bytesperline * icd->user_height) >> 2,
+                               pcdev->base_csi + CSIRXCNT);
+               writel((bytesperline << 16) | icd->user_height,
+                               pcdev->base_csi + CSIIMAG_PARA);
+       }
+       writel(pcdev->csicr1, pcdev->base_csi + CSICR1);
+       return 0;
+ }
+ static int mx2_camera_set_crop(struct soc_camera_device *icd,
+                               struct v4l2_crop *a)
+ {
+       struct v4l2_rect *rect = &a->c;
+       struct v4l2_subdev *sd = soc_camera_to_subdev(icd);
+       struct v4l2_mbus_framefmt mf;
+       int ret;
+       soc_camera_limit_side(&rect->left, &rect->width, 0, 2, 4096);
+       soc_camera_limit_side(&rect->top, &rect->height, 0, 2, 4096);
+       ret = v4l2_subdev_call(sd, video, s_crop, a);
+       if (ret < 0)
+               return ret;
+       /* The capture device might have changed its output  */
+       ret = v4l2_subdev_call(sd, video, g_mbus_fmt, &mf);
+       if (ret < 0)
+               return ret;
+       dev_dbg(icd->parent, "Sensor cropped %dx%d\n",
+               mf.width, mf.height);
+       icd->user_width         = mf.width;
+       icd->user_height        = mf.height;
+       return ret;
+ }
+ static int mx2_camera_get_formats(struct soc_camera_device *icd,
+                                 unsigned int idx,
+                                 struct soc_camera_format_xlate *xlate)
+ {
+       struct v4l2_subdev *sd = soc_camera_to_subdev(icd);
+       const struct soc_mbus_pixelfmt *fmt;
+       struct device *dev = icd->parent;
+       enum v4l2_mbus_pixelcode code;
+       int ret, formats = 0;
+       ret = v4l2_subdev_call(sd, video, enum_mbus_fmt, idx, &code);
+       if (ret < 0)
+               /* no more formats */
+               return 0;
+       fmt = soc_mbus_get_fmtdesc(code);
+       if (!fmt) {
+               dev_err(dev, "Invalid format code #%u: %d\n", idx, code);
+               return 0;
+       }
+       if (code == V4L2_MBUS_FMT_YUYV8_2X8 ||
+           code == V4L2_MBUS_FMT_UYVY8_2X8) {
+               formats++;
+               if (xlate) {
+                       /*
+                        * CH2 can output YUV420 which is a standard format in
+                        * soc_mediabus.c
+                        */
+                       xlate->host_fmt =
+                               soc_mbus_get_fmtdesc(V4L2_MBUS_FMT_YUYV8_1_5X8);
+                       xlate->code     = code;
+                       dev_dbg(dev, "Providing host format %s for sensor code %d\n",
+                              xlate->host_fmt->name, code);
+                       xlate++;
+               }
+       }
+       if (code == V4L2_MBUS_FMT_UYVY8_2X8) {
+               formats++;
+               if (xlate) {
+                       xlate->host_fmt =
+                               soc_mbus_get_fmtdesc(V4L2_MBUS_FMT_YUYV8_2X8);
+                       xlate->code     = code;
+                       dev_dbg(dev, "Providing host format %s for sensor code %d\n",
+                               xlate->host_fmt->name, code);
+                       xlate++;
+               }
+       }
+       /* Generic pass-trough */
+       formats++;
+       if (xlate) {
+               xlate->host_fmt = fmt;
+               xlate->code     = code;
+               xlate++;
+       }
+       return formats;
+ }
+ static int mx2_emmaprp_resize(struct mx2_camera_dev *pcdev,
+                             struct v4l2_mbus_framefmt *mf_in,
+                             struct v4l2_pix_format *pix_out, bool apply)
+ {
+       int num, den;
+       unsigned long m;
+       int i, dir;
+       for (dir = RESIZE_DIR_H; dir <= RESIZE_DIR_V; dir++) {
+               struct emma_prp_resize tmprsz;
+               unsigned char *s = tmprsz.s;
+               int len = 0;
+               int in, out;
+               if (dir == RESIZE_DIR_H) {
+                       in = mf_in->width;
+                       out = pix_out->width;
+               } else {
+                       in = mf_in->height;
+                       out = pix_out->height;
+               }
+               if (in < out)
+                       return -EINVAL;
+               else if (in == out)
+                       continue;
+               /* Calculate ratio */
+               m = gcd(in, out);
+               num = in / m;
+               den = out / m;
+               if (num > RESIZE_NUM_MAX)
+                       return -EINVAL;
+               if ((num >= 2 * den) && (den == 1) &&
+                   (num < 9) && (!(num & 0x01))) {
+                       int sum = 0;
+                       int j;
+                       /* Average scaling for >= 2:1 ratios */
+                       /* Support can be added for num >=9 and odd values */
+                       tmprsz.algo = RESIZE_ALGO_AVERAGING;
+                       len = num;
+                       for (i = 0; i < (len / 2); i++)
+                               s[i] = 8;
+                       do {
+                               for (i = 0; i < (len / 2); i++) {
+                                       s[i] = s[i] >> 1;
+                                       sum = 0;
+                                       for (j = 0; j < (len / 2); j++)
+                                               sum += s[j];
+                                       if (sum == 4)
+                                               break;
+                               }
+                       } while (sum != 4);
+                       for (i = (len / 2); i < len; i++)
+                               s[i] = s[len - i - 1];
+                       s[len - 1] |= SZ_COEF;
+               } else {
+                       /* bilinear scaling for < 2:1 ratios */
+                       int v; /* overflow counter */
+                       int coeff, nxt; /* table output */
+                       int in_pos_inc = 2 * den;
+                       int out_pos = num;
+                       int out_pos_inc = 2 * num;
+                       int init_carry = num - den;
+                       int carry = init_carry;
+                       tmprsz.algo = RESIZE_ALGO_BILINEAR;
+                       v = den + in_pos_inc;
+                       do {
+                               coeff = v - out_pos;
+                               out_pos += out_pos_inc;
+                               carry += out_pos_inc;
+                               for (nxt = 0; v < out_pos; nxt++) {
+                                       v += in_pos_inc;
+                                       carry -= in_pos_inc;
+                               }
+                               if (len > RESIZE_NUM_MAX)
+                                       return -EINVAL;
+                               coeff = ((coeff << BC_COEF) +
+                                       (in_pos_inc >> 1)) / in_pos_inc;
+                               if (coeff >= (SZ_COEF - 1))
+                                       coeff--;
+                               coeff |= SZ_COEF;
+                               s[len] = (unsigned char)coeff;
+                               len++;
+                               for (i = 1; i < nxt; i++) {
+                                       if (len >= RESIZE_NUM_MAX)
+                                               return -EINVAL;
+                                       s[len] = 0;
+                                       len++;
+                               }
+                       } while (carry != init_carry);
+               }
+               tmprsz.len = len;
+               if (dir == RESIZE_DIR_H)
+                       mf_in->width = pix_out->width;
+               else
+                       mf_in->height = pix_out->height;
+               if (apply)
+                       memcpy(&pcdev->resizing[dir], &tmprsz, sizeof(tmprsz));
+       }
+       return 0;
+ }
+ static int mx2_camera_set_fmt(struct soc_camera_device *icd,
+                              struct v4l2_format *f)
+ {
+       struct soc_camera_host *ici = to_soc_camera_host(icd->parent);
+       struct mx2_camera_dev *pcdev = ici->priv;
+       struct v4l2_subdev *sd = soc_camera_to_subdev(icd);
+       const struct soc_camera_format_xlate *xlate;
+       struct v4l2_pix_format *pix = &f->fmt.pix;
+       struct v4l2_mbus_framefmt mf;
+       int ret;
+       dev_dbg(icd->parent, "%s: requested params: width = %d, height = %d\n",
+               __func__, pix->width, pix->height);
+       xlate = soc_camera_xlate_by_fourcc(icd, pix->pixelformat);
+       if (!xlate) {
+               dev_warn(icd->parent, "Format %x not found\n",
+                               pix->pixelformat);
+               return -EINVAL;
+       }
+       mf.width        = pix->width;
+       mf.height       = pix->height;
+       mf.field        = pix->field;
+       mf.colorspace   = pix->colorspace;
+       mf.code         = xlate->code;
+       ret = v4l2_subdev_call(sd, video, s_mbus_fmt, &mf);
+       if (ret < 0 && ret != -ENOIOCTLCMD)
+               return ret;
+       /* Store width and height returned by the sensor for resizing */
+       pcdev->s_width = mf.width;
+       pcdev->s_height = mf.height;
+       dev_dbg(icd->parent, "%s: sensor params: width = %d, height = %d\n",
+               __func__, pcdev->s_width, pcdev->s_height);
+       pcdev->emma_prp = mx27_emma_prp_get_format(xlate->code,
+                                                  xlate->host_fmt->fourcc);
+       memset(pcdev->resizing, 0, sizeof(pcdev->resizing));
+       if ((mf.width != pix->width || mf.height != pix->height) &&
+               pcdev->emma_prp->cfg.in_fmt == PRP_CNTL_DATA_IN_YUV422) {
+               if (mx2_emmaprp_resize(pcdev, &mf, pix, true) < 0)
+                       dev_dbg(icd->parent, "%s: can't resize\n", __func__);
+       }
+       if (mf.code != xlate->code)
+               return -EINVAL;
+       pix->width              = mf.width;
+       pix->height             = mf.height;
+       pix->field              = mf.field;
+       pix->colorspace         = mf.colorspace;
+       icd->current_fmt        = xlate;
+       dev_dbg(icd->parent, "%s: returned params: width = %d, height = %d\n",
+               __func__, pix->width, pix->height);
+       return 0;
+ }
+ static int mx2_camera_try_fmt(struct soc_camera_device *icd,
+                                 struct v4l2_format *f)
+ {
+       struct v4l2_subdev *sd = soc_camera_to_subdev(icd);
+       const struct soc_camera_format_xlate *xlate;
+       struct v4l2_pix_format *pix = &f->fmt.pix;
+       struct v4l2_mbus_framefmt mf;
+       __u32 pixfmt = pix->pixelformat;
+       struct soc_camera_host *ici = to_soc_camera_host(icd->parent);
+       struct mx2_camera_dev *pcdev = ici->priv;
+       unsigned int width_limit;
+       int ret;
+       dev_dbg(icd->parent, "%s: requested params: width = %d, height = %d\n",
+               __func__, pix->width, pix->height);
+       xlate = soc_camera_xlate_by_fourcc(icd, pixfmt);
+       if (pixfmt && !xlate) {
+               dev_warn(icd->parent, "Format %x not found\n", pixfmt);
+               return -EINVAL;
+       }
+       /* FIXME: implement MX27 limits */
+       /* limit to MX25 hardware capabilities */
+       if (cpu_is_mx25()) {
+               if (xlate->host_fmt->bits_per_sample <= 8)
+                       width_limit = 0xffff * 4;
+               else
+                       width_limit = 0xffff * 2;
+               /* CSIIMAG_PARA limit */
+               if (pix->width > width_limit)
+                       pix->width = width_limit;
+               if (pix->height > 0xffff)
+                       pix->height = 0xffff;
+               pix->bytesperline = soc_mbus_bytes_per_line(pix->width,
+                               xlate->host_fmt);
+               if (pix->bytesperline < 0)
+                       return pix->bytesperline;
+               pix->sizeimage = soc_mbus_image_size(xlate->host_fmt,
+                                               pix->bytesperline, pix->height);
+               /* Check against the CSIRXCNT limit */
+               if (pix->sizeimage > 4 * 0x3ffff) {
+                       /* Adjust geometry, preserve aspect ratio */
+                       unsigned int new_height = int_sqrt(div_u64(0x3ffffULL *
+                                       4 * pix->height, pix->bytesperline));
+                       pix->width = new_height * pix->width / pix->height;
+                       pix->height = new_height;
+                       pix->bytesperline = soc_mbus_bytes_per_line(pix->width,
+                                                       xlate->host_fmt);
+                       BUG_ON(pix->bytesperline < 0);
+                       pix->sizeimage = soc_mbus_image_size(xlate->host_fmt,
+                                               pix->bytesperline, pix->height);
+               }
+       }
+       /* limit to sensor capabilities */
+       mf.width        = pix->width;
+       mf.height       = pix->height;
+       mf.field        = pix->field;
+       mf.colorspace   = pix->colorspace;
+       mf.code         = xlate->code;
+       ret = v4l2_subdev_call(sd, video, try_mbus_fmt, &mf);
+       if (ret < 0)
+               return ret;
+       dev_dbg(icd->parent, "%s: sensor params: width = %d, height = %d\n",
+               __func__, pcdev->s_width, pcdev->s_height);
+       /* If the sensor does not support image size try PrP resizing */
+       pcdev->emma_prp = mx27_emma_prp_get_format(xlate->code,
+                                                  xlate->host_fmt->fourcc);
+       memset(pcdev->resizing, 0, sizeof(pcdev->resizing));
+       if ((mf.width != pix->width || mf.height != pix->height) &&
+               pcdev->emma_prp->cfg.in_fmt == PRP_CNTL_DATA_IN_YUV422) {
+               if (mx2_emmaprp_resize(pcdev, &mf, pix, false) < 0)
+                       dev_dbg(icd->parent, "%s: can't resize\n", __func__);
+       }
+       if (mf.field == V4L2_FIELD_ANY)
+               mf.field = V4L2_FIELD_NONE;
+       /*
+        * Driver supports interlaced images provided they have
+        * both fields so that they can be processed as if they
+        * were progressive.
+        */
+       if (mf.field != V4L2_FIELD_NONE && !V4L2_FIELD_HAS_BOTH(mf.field)) {
+               dev_err(icd->parent, "Field type %d unsupported.\n",
+                               mf.field);
+               return -EINVAL;
+       }
+       pix->width      = mf.width;
+       pix->height     = mf.height;
+       pix->field      = mf.field;
+       pix->colorspace = mf.colorspace;
+       dev_dbg(icd->parent, "%s: returned params: width = %d, height = %d\n",
+               __func__, pix->width, pix->height);
+       return 0;
+ }
+ static int mx2_camera_querycap(struct soc_camera_host *ici,
+                              struct v4l2_capability *cap)
+ {
+       /* cap->name is set by the friendly caller:-> */
+       strlcpy(cap->card, MX2_CAM_DRIVER_DESCRIPTION, sizeof(cap->card));
+       cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;
+       return 0;
+ }
+ static unsigned int mx2_camera_poll(struct file *file, poll_table *pt)
+ {
+       struct soc_camera_device *icd = file->private_data;
+       return vb2_poll(&icd->vb2_vidq, file, pt);
+ }
+ static struct soc_camera_host_ops mx2_soc_camera_host_ops = {
+       .owner          = THIS_MODULE,
+       .add            = mx2_camera_add_device,
+       .remove         = mx2_camera_remove_device,
+       .set_fmt        = mx2_camera_set_fmt,
+       .set_crop       = mx2_camera_set_crop,
+       .get_formats    = mx2_camera_get_formats,
+       .try_fmt        = mx2_camera_try_fmt,
+       .init_videobuf2 = mx2_camera_init_videobuf,
+       .poll           = mx2_camera_poll,
+       .querycap       = mx2_camera_querycap,
+       .set_bus_param  = mx2_camera_set_bus_param,
+ };
+ static void mx27_camera_frame_done_emma(struct mx2_camera_dev *pcdev,
+               int bufnum, bool err)
+ {
+ #ifdef DEBUG
+       struct mx2_fmt_cfg *prp = pcdev->emma_prp;
+ #endif
+       struct mx2_buf_internal *ibuf;
+       struct mx2_buffer *buf;
+       struct vb2_buffer *vb;
+       unsigned long phys;
+       ibuf = list_first_entry(&pcdev->active_bufs, struct mx2_buf_internal,
+                              queue);
+       BUG_ON(ibuf->bufnum != bufnum);
+       if (ibuf->discard) {
+               /*
+                * Discard buffer must not be returned to user space.
+                * Just return it to the discard queue.
+                */
+               list_move_tail(pcdev->active_bufs.next, &pcdev->discard);
+       } else {
+               buf = mx2_ibuf_to_buf(ibuf);
+               vb = &buf->vb;
+ #ifdef DEBUG
+               phys = vb2_dma_contig_plane_dma_addr(vb, 0);
+               if (prp->cfg.channel == 1) {
+                       if (readl(pcdev->base_emma + PRP_DEST_RGB1_PTR +
+                               4 * bufnum) != phys) {
+                               dev_err(pcdev->dev, "%lx != %x\n", phys,
+                                       readl(pcdev->base_emma +
+                                       PRP_DEST_RGB1_PTR + 4 * bufnum));
+                       }
+               } else {
+                       if (readl(pcdev->base_emma + PRP_DEST_Y_PTR -
+                               0x14 * bufnum) != phys) {
+                               dev_err(pcdev->dev, "%lx != %x\n", phys,
+                                       readl(pcdev->base_emma +
+                                       PRP_DEST_Y_PTR - 0x14 * bufnum));
+                       }
+               }
+ #endif
+               dev_dbg(pcdev->dev, "%s (vb=0x%p) 0x%p %lu\n", __func__, vb,
+                               vb2_plane_vaddr(vb, 0),
+                               vb2_get_plane_payload(vb, 0));
+               list_del_init(&buf->internal.queue);
+               do_gettimeofday(&vb->v4l2_buf.timestamp);
+               vb->v4l2_buf.sequence = pcdev->frame_count;
+               if (err)
+                       vb2_buffer_done(vb, VB2_BUF_STATE_ERROR);
+               else
+                       vb2_buffer_done(vb, VB2_BUF_STATE_DONE);
+       }
+       pcdev->frame_count++;
+       if (list_empty(&pcdev->capture)) {
+               if (list_empty(&pcdev->discard)) {
+                       dev_warn(pcdev->dev, "%s: trying to access empty discard list\n",
+                                __func__);
+                       return;
+               }
+               ibuf = list_first_entry(&pcdev->discard,
+                                       struct mx2_buf_internal, queue);
+               ibuf->bufnum = bufnum;
+               list_move_tail(pcdev->discard.next, &pcdev->active_bufs);
+               mx27_update_emma_buf(pcdev, pcdev->discard_buffer_dma, bufnum);
+               return;
+       }
+       buf = list_first_entry(&pcdev->capture, struct mx2_buffer,
+                              internal.queue);
+       buf->internal.bufnum = bufnum;
+       list_move_tail(pcdev->capture.next, &pcdev->active_bufs);
+       vb = &buf->vb;
+       buf->state = MX2_STATE_ACTIVE;
+       phys = vb2_dma_contig_plane_dma_addr(vb, 0);
+       mx27_update_emma_buf(pcdev, phys, bufnum);
+ }
+ static irqreturn_t mx27_camera_emma_irq(int irq_emma, void *data)
+ {
+       struct mx2_camera_dev *pcdev = data;
+       unsigned int status = readl(pcdev->base_emma + PRP_INTRSTATUS);
+       struct mx2_buf_internal *ibuf;
+       spin_lock(&pcdev->lock);
+       if (list_empty(&pcdev->active_bufs)) {
+               dev_warn(pcdev->dev, "%s: called while active list is empty\n",
+                       __func__);
+               if (!status) {
+                       spin_unlock(&pcdev->lock);
+                       return IRQ_NONE;
+               }
+       }
+       if (status & (1 << 7)) { /* overflow */
+               u32 cntl = readl(pcdev->base_emma + PRP_CNTL);
+               writel(cntl & ~(PRP_CNTL_CH1EN | PRP_CNTL_CH2EN),
+                      pcdev->base_emma + PRP_CNTL);
+               writel(cntl, pcdev->base_emma + PRP_CNTL);
+               ibuf = list_first_entry(&pcdev->active_bufs,
+                                       struct mx2_buf_internal, queue);
+               mx27_camera_frame_done_emma(pcdev,
+                                       ibuf->bufnum, true);
+               status &= ~(1 << 7);
+       } else if (((status & (3 << 5)) == (3 << 5)) ||
+               ((status & (3 << 3)) == (3 << 3))) {
+               /*
+                * Both buffers have triggered, process the one we're expecting
+                * to first
+                */
+               ibuf = list_first_entry(&pcdev->active_bufs,
+                                       struct mx2_buf_internal, queue);
+               mx27_camera_frame_done_emma(pcdev, ibuf->bufnum, false);
+               status &= ~(1 << (6 - ibuf->bufnum)); /* mark processed */
+       } else if ((status & (1 << 6)) || (status & (1 << 4))) {
+               mx27_camera_frame_done_emma(pcdev, 0, false);
+       } else if ((status & (1 << 5)) || (status & (1 << 3))) {
+               mx27_camera_frame_done_emma(pcdev, 1, false);
+       }
+       spin_unlock(&pcdev->lock);
+       writel(status, pcdev->base_emma + PRP_INTRSTATUS);
+       return IRQ_HANDLED;
+ }
+ static int __devinit mx27_camera_emma_init(struct platform_device *pdev)
+ {
+       struct mx2_camera_dev *pcdev = platform_get_drvdata(pdev);
+       struct resource *res_emma;
+       int irq_emma;
+       int err = 0;
+       res_emma = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+       irq_emma = platform_get_irq(pdev, 1);
+       if (!res_emma || !irq_emma) {
+               dev_err(pcdev->dev, "no EMMA resources\n");
+               goto out;
+       }
+       pcdev->base_emma = devm_request_and_ioremap(pcdev->dev, res_emma);
+       if (!pcdev->base_emma) {
+               err = -EADDRNOTAVAIL;
+               goto out;
+       }
+       err = devm_request_irq(pcdev->dev, irq_emma, mx27_camera_emma_irq, 0,
+                              MX2_CAM_DRV_NAME, pcdev);
+       if (err) {
+               dev_err(pcdev->dev, "Camera EMMA interrupt register failed \n");
+               goto out;
+       }
+       pcdev->clk_emma_ipg = devm_clk_get(pcdev->dev, "emma-ipg");
+       if (IS_ERR(pcdev->clk_emma_ipg)) {
+               err = PTR_ERR(pcdev->clk_emma_ipg);
+               goto out;
+       }
+       clk_prepare_enable(pcdev->clk_emma_ipg);
+       pcdev->clk_emma_ahb = devm_clk_get(pcdev->dev, "emma-ahb");
+       if (IS_ERR(pcdev->clk_emma_ahb)) {
+               err = PTR_ERR(pcdev->clk_emma_ahb);
+               goto exit_clk_emma_ipg;
+       }
+       clk_prepare_enable(pcdev->clk_emma_ahb);
+       err = mx27_camera_emma_prp_reset(pcdev);
+       if (err)
+               goto exit_clk_emma_ahb;
+       return err;
+ exit_clk_emma_ahb:
+       clk_disable_unprepare(pcdev->clk_emma_ahb);
+ exit_clk_emma_ipg:
+       clk_disable_unprepare(pcdev->clk_emma_ipg);
+ out:
+       return err;
+ }
+ static int __devinit mx2_camera_probe(struct platform_device *pdev)
+ {
+       struct mx2_camera_dev *pcdev;
+       struct resource *res_csi;
+       int irq_csi;
+       int err = 0;
+       dev_dbg(&pdev->dev, "initialising\n");
+       res_csi = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       irq_csi = platform_get_irq(pdev, 0);
+       if (res_csi == NULL || irq_csi < 0) {
+               dev_err(&pdev->dev, "Missing platform resources data\n");
+               err = -ENODEV;
+               goto exit;
+       }
+       pcdev = devm_kzalloc(&pdev->dev, sizeof(*pcdev), GFP_KERNEL);
+       if (!pcdev) {
+               dev_err(&pdev->dev, "Could not allocate pcdev\n");
+               err = -ENOMEM;
+               goto exit;
+       }
+       pcdev->clk_csi = devm_clk_get(&pdev->dev, "ahb");
+       if (IS_ERR(pcdev->clk_csi)) {
+               dev_err(&pdev->dev, "Could not get csi clock\n");
+               err = PTR_ERR(pcdev->clk_csi);
+               goto exit;
+       }
+       pcdev->pdata = pdev->dev.platform_data;
+       if (pcdev->pdata) {
+               long rate;
+               pcdev->platform_flags = pcdev->pdata->flags;
+               rate = clk_round_rate(pcdev->clk_csi, pcdev->pdata->clk * 2);
+               if (rate <= 0) {
+                       err = -ENODEV;
+                       goto exit;
+               }
+               err = clk_set_rate(pcdev->clk_csi, rate);
+               if (err < 0)
+                       goto exit;
+       }
+       INIT_LIST_HEAD(&pcdev->capture);
+       INIT_LIST_HEAD(&pcdev->active_bufs);
+       INIT_LIST_HEAD(&pcdev->discard);
+       spin_lock_init(&pcdev->lock);
+       pcdev->base_csi = devm_request_and_ioremap(&pdev->dev, res_csi);
+       if (!pcdev->base_csi) {
+               err = -EADDRNOTAVAIL;
+               goto exit;
+       }
+       pcdev->dev = &pdev->dev;
+       platform_set_drvdata(pdev, pcdev);
+       if (cpu_is_mx25()) {
+               err = devm_request_irq(&pdev->dev, irq_csi, mx25_camera_irq, 0,
+                                      MX2_CAM_DRV_NAME, pcdev);
+               if (err) {
+                       dev_err(pcdev->dev, "Camera interrupt register failed \n");
+                       goto exit;
+               }
+       }
+       if (cpu_is_mx27()) {
+               err = mx27_camera_emma_init(pdev);
+               if (err)
+                       goto exit;
+       }
+       /*
+        * We're done with drvdata here.  Clear the pointer so that
+        * v4l2 core can start using drvdata on its purpose.
+        */
+       platform_set_drvdata(pdev, NULL);
+       pcdev->soc_host.drv_name        = MX2_CAM_DRV_NAME,
+       pcdev->soc_host.ops             = &mx2_soc_camera_host_ops,
+       pcdev->soc_host.priv            = pcdev;
+       pcdev->soc_host.v4l2_dev.dev    = &pdev->dev;
+       pcdev->soc_host.nr              = pdev->id;
+       if (cpu_is_mx25())
+               pcdev->soc_host.capabilities = SOCAM_HOST_CAP_STRIDE;
+       pcdev->alloc_ctx = vb2_dma_contig_init_ctx(&pdev->dev);
+       if (IS_ERR(pcdev->alloc_ctx)) {
+               err = PTR_ERR(pcdev->alloc_ctx);
+               goto eallocctx;
+       }
+       err = soc_camera_host_register(&pcdev->soc_host);
+       if (err)
+               goto exit_free_emma;
+       dev_info(&pdev->dev, "MX2 Camera (CSI) driver probed, clock frequency: %ld\n",
+                       clk_get_rate(pcdev->clk_csi));
+       return 0;
+ exit_free_emma:
+       vb2_dma_contig_cleanup_ctx(pcdev->alloc_ctx);
+ eallocctx:
+       if (cpu_is_mx27()) {
+               clk_disable_unprepare(pcdev->clk_emma_ipg);
+               clk_disable_unprepare(pcdev->clk_emma_ahb);
+       }
+ exit:
+       return err;
+ }
+ static int __devexit mx2_camera_remove(struct platform_device *pdev)
+ {
+       struct soc_camera_host *soc_host = to_soc_camera_host(&pdev->dev);
+       struct mx2_camera_dev *pcdev = container_of(soc_host,
+                       struct mx2_camera_dev, soc_host);
+       soc_camera_host_unregister(&pcdev->soc_host);
+       vb2_dma_contig_cleanup_ctx(pcdev->alloc_ctx);
+       if (cpu_is_mx27()) {
+               clk_disable_unprepare(pcdev->clk_emma_ipg);
+               clk_disable_unprepare(pcdev->clk_emma_ahb);
+       }
+       dev_info(&pdev->dev, "MX2 Camera driver unloaded\n");
+       return 0;
+ }
+ static struct platform_driver mx2_camera_driver = {
+       .driver         = {
+               .name   = MX2_CAM_DRV_NAME,
+       },
+       .remove         = __devexit_p(mx2_camera_remove),
+ };
+ static int __init mx2_camera_init(void)
+ {
+       return platform_driver_probe(&mx2_camera_driver, &mx2_camera_probe);
+ }
+ static void __exit mx2_camera_exit(void)
+ {
+       return platform_driver_unregister(&mx2_camera_driver);
+ }
+ module_init(mx2_camera_init);
+ module_exit(mx2_camera_exit);
+ MODULE_DESCRIPTION("i.MX27/i.MX25 SoC Camera Host driver");
+ MODULE_AUTHOR("Sascha Hauer <sha@pengutronix.de>");
+ MODULE_LICENSE("GPL");
+ MODULE_VERSION(MX2_CAM_VERSION);
index 0000000000000000000000000000000000000000,16975c6e69057f7799baf568a589d700d623a7a7..3557ac97e4303f4743330850a5144f43cb06cbd0
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,1290 +1,1290 @@@
 -#include <mach/mx3_camera.h>
 -#include <mach/dma.h>
+ /*
+  * V4L2 Driver for i.MX3x camera host
+  *
+  * Copyright (C) 2008
+  * Guennadi Liakhovetski, DENX Software Engineering, <lg@denx.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.
+  */
+ #include <linux/init.h>
+ #include <linux/module.h>
+ #include <linux/videodev2.h>
+ #include <linux/platform_device.h>
+ #include <linux/clk.h>
+ #include <linux/vmalloc.h>
+ #include <linux/interrupt.h>
+ #include <linux/sched.h>
+ #include <media/v4l2-common.h>
+ #include <media/v4l2-dev.h>
+ #include <media/videobuf2-dma-contig.h>
+ #include <media/soc_camera.h>
+ #include <media/soc_mediabus.h>
+ #include <mach/ipu.h>
++#include <linux/platform_data/camera-mx3.h>
++#include <linux/platform_data/dma-imx.h>
+ #define MX3_CAM_DRV_NAME "mx3-camera"
+ /* CMOS Sensor Interface Registers */
+ #define CSI_REG_START         0x60
+ #define CSI_SENS_CONF         (0x60 - CSI_REG_START)
+ #define CSI_SENS_FRM_SIZE     (0x64 - CSI_REG_START)
+ #define CSI_ACT_FRM_SIZE      (0x68 - CSI_REG_START)
+ #define CSI_OUT_FRM_CTRL      (0x6C - CSI_REG_START)
+ #define CSI_TST_CTRL          (0x70 - CSI_REG_START)
+ #define CSI_CCIR_CODE_1               (0x74 - CSI_REG_START)
+ #define CSI_CCIR_CODE_2               (0x78 - CSI_REG_START)
+ #define CSI_CCIR_CODE_3               (0x7C - CSI_REG_START)
+ #define CSI_FLASH_STROBE_1    (0x80 - CSI_REG_START)
+ #define CSI_FLASH_STROBE_2    (0x84 - CSI_REG_START)
+ #define CSI_SENS_CONF_VSYNC_POL_SHIFT         0
+ #define CSI_SENS_CONF_HSYNC_POL_SHIFT         1
+ #define CSI_SENS_CONF_DATA_POL_SHIFT          2
+ #define CSI_SENS_CONF_PIX_CLK_POL_SHIFT               3
+ #define CSI_SENS_CONF_SENS_PRTCL_SHIFT                4
+ #define CSI_SENS_CONF_SENS_CLKSRC_SHIFT               7
+ #define CSI_SENS_CONF_DATA_FMT_SHIFT          8
+ #define CSI_SENS_CONF_DATA_WIDTH_SHIFT                10
+ #define CSI_SENS_CONF_EXT_VSYNC_SHIFT         15
+ #define CSI_SENS_CONF_DIVRATIO_SHIFT          16
+ #define CSI_SENS_CONF_DATA_FMT_RGB_YUV444     (0UL << CSI_SENS_CONF_DATA_FMT_SHIFT)
+ #define CSI_SENS_CONF_DATA_FMT_YUV422         (2UL << CSI_SENS_CONF_DATA_FMT_SHIFT)
+ #define CSI_SENS_CONF_DATA_FMT_BAYER          (3UL << CSI_SENS_CONF_DATA_FMT_SHIFT)
+ #define MAX_VIDEO_MEM 16
+ struct mx3_camera_buffer {
+       /* common v4l buffer stuff -- must be first */
+       struct vb2_buffer                       vb;
+       struct list_head                        queue;
+       /* One descriptot per scatterlist (per frame) */
+       struct dma_async_tx_descriptor          *txd;
+       /* We have to "build" a scatterlist ourselves - one element per frame */
+       struct scatterlist                      sg;
+ };
+ /**
+  * struct mx3_camera_dev - i.MX3x camera (CSI) object
+  * @dev:              camera device, to which the coherent buffer is attached
+  * @icd:              currently attached camera sensor
+  * @clk:              pointer to clock
+  * @base:             remapped register base address
+  * @pdata:            platform data
+  * @platform_flags:   platform flags
+  * @mclk:             master clock frequency in Hz
+  * @capture:          list of capture videobuffers
+  * @lock:             protects video buffer lists
+  * @active:           active video buffer
+  * @idmac_channel:    array of pointers to IPU DMAC DMA channels
+  * @soc_host:         embedded soc_host object
+  */
+ struct mx3_camera_dev {
+       /*
+        * i.MX3x is only supposed to handle one camera on its Camera Sensor
+        * Interface. If anyone ever builds hardware to enable more than one
+        * camera _simultaneously_, they will have to modify this driver too
+        */
+       struct soc_camera_device *icd;
+       struct clk              *clk;
+       void __iomem            *base;
+       struct mx3_camera_pdata *pdata;
+       unsigned long           platform_flags;
+       unsigned long           mclk;
+       u16                     width_flags;    /* max 15 bits */
+       struct list_head        capture;
+       spinlock_t              lock;           /* Protects video buffer lists */
+       struct mx3_camera_buffer *active;
+       size_t                  buf_total;
+       struct vb2_alloc_ctx    *alloc_ctx;
+       enum v4l2_field         field;
+       int                     sequence;
+       /* IDMAC / dmaengine interface */
+       struct idmac_channel    *idmac_channel[1];      /* We need one channel */
+       struct soc_camera_host  soc_host;
+ };
+ struct dma_chan_request {
+       struct mx3_camera_dev   *mx3_cam;
+       enum ipu_channel        id;
+ };
+ static u32 csi_reg_read(struct mx3_camera_dev *mx3, off_t reg)
+ {
+       return __raw_readl(mx3->base + reg);
+ }
+ static void csi_reg_write(struct mx3_camera_dev *mx3, u32 value, off_t reg)
+ {
+       __raw_writel(value, mx3->base + reg);
+ }
+ static struct mx3_camera_buffer *to_mx3_vb(struct vb2_buffer *vb)
+ {
+       return container_of(vb, struct mx3_camera_buffer, vb);
+ }
+ /* Called from the IPU IDMAC ISR */
+ static void mx3_cam_dma_done(void *arg)
+ {
+       struct idmac_tx_desc *desc = to_tx_desc(arg);
+       struct dma_chan *chan = desc->txd.chan;
+       struct idmac_channel *ichannel = to_idmac_chan(chan);
+       struct mx3_camera_dev *mx3_cam = ichannel->client;
+       dev_dbg(chan->device->dev, "callback cookie %d, active DMA 0x%08x\n",
+               desc->txd.cookie, mx3_cam->active ? sg_dma_address(&mx3_cam->active->sg) : 0);
+       spin_lock(&mx3_cam->lock);
+       if (mx3_cam->active) {
+               struct vb2_buffer *vb = &mx3_cam->active->vb;
+               struct mx3_camera_buffer *buf = to_mx3_vb(vb);
+               list_del_init(&buf->queue);
+               do_gettimeofday(&vb->v4l2_buf.timestamp);
+               vb->v4l2_buf.field = mx3_cam->field;
+               vb->v4l2_buf.sequence = mx3_cam->sequence++;
+               vb2_buffer_done(vb, VB2_BUF_STATE_DONE);
+       }
+       if (list_empty(&mx3_cam->capture)) {
+               mx3_cam->active = NULL;
+               spin_unlock(&mx3_cam->lock);
+               /*
+                * stop capture - without further buffers IPU_CHA_BUF0_RDY will
+                * not get updated
+                */
+               return;
+       }
+       mx3_cam->active = list_entry(mx3_cam->capture.next,
+                                    struct mx3_camera_buffer, queue);
+       spin_unlock(&mx3_cam->lock);
+ }
+ /*
+  * Videobuf operations
+  */
+ /*
+  * Calculate the __buffer__ (not data) size and number of buffers.
+  */
+ static int mx3_videobuf_setup(struct vb2_queue *vq,
+                       const struct v4l2_format *fmt,
+                       unsigned int *count, unsigned int *num_planes,
+                       unsigned int sizes[], void *alloc_ctxs[])
+ {
+       struct soc_camera_device *icd = soc_camera_from_vb2q(vq);
+       struct soc_camera_host *ici = to_soc_camera_host(icd->parent);
+       struct mx3_camera_dev *mx3_cam = ici->priv;
+       if (!mx3_cam->idmac_channel[0])
+               return -EINVAL;
+       if (fmt) {
+               const struct soc_camera_format_xlate *xlate = soc_camera_xlate_by_fourcc(icd,
+                                                               fmt->fmt.pix.pixelformat);
+               unsigned int bytes_per_line;
+               int ret;
+               if (!xlate)
+                       return -EINVAL;
+               ret = soc_mbus_bytes_per_line(fmt->fmt.pix.width,
+                                             xlate->host_fmt);
+               if (ret < 0)
+                       return ret;
+               bytes_per_line = max_t(u32, fmt->fmt.pix.bytesperline, ret);
+               ret = soc_mbus_image_size(xlate->host_fmt, bytes_per_line,
+                                         fmt->fmt.pix.height);
+               if (ret < 0)
+                       return ret;
+               sizes[0] = max_t(u32, fmt->fmt.pix.sizeimage, ret);
+       } else {
+               /* Called from VIDIOC_REQBUFS or in compatibility mode */
+               sizes[0] = icd->sizeimage;
+       }
+       alloc_ctxs[0] = mx3_cam->alloc_ctx;
+       if (!vq->num_buffers)
+               mx3_cam->sequence = 0;
+       if (!*count)
+               *count = 2;
+       /* If *num_planes != 0, we have already verified *count. */
+       if (!*num_planes &&
+           sizes[0] * *count + mx3_cam->buf_total > MAX_VIDEO_MEM * 1024 * 1024)
+               *count = (MAX_VIDEO_MEM * 1024 * 1024 - mx3_cam->buf_total) /
+                       sizes[0];
+       *num_planes = 1;
+       return 0;
+ }
+ static enum pixel_fmt fourcc_to_ipu_pix(__u32 fourcc)
+ {
+       /* Add more formats as need arises and test possibilities appear... */
+       switch (fourcc) {
+       case V4L2_PIX_FMT_RGB24:
+               return IPU_PIX_FMT_RGB24;
+       case V4L2_PIX_FMT_UYVY:
+       case V4L2_PIX_FMT_RGB565:
+       default:
+               return IPU_PIX_FMT_GENERIC;
+       }
+ }
+ static void mx3_videobuf_queue(struct vb2_buffer *vb)
+ {
+       struct soc_camera_device *icd = soc_camera_from_vb2q(vb->vb2_queue);
+       struct soc_camera_host *ici = to_soc_camera_host(icd->parent);
+       struct mx3_camera_dev *mx3_cam = ici->priv;
+       struct mx3_camera_buffer *buf = to_mx3_vb(vb);
+       struct scatterlist *sg = &buf->sg;
+       struct dma_async_tx_descriptor *txd;
+       struct idmac_channel *ichan = mx3_cam->idmac_channel[0];
+       struct idmac_video_param *video = &ichan->params.video;
+       const struct soc_mbus_pixelfmt *host_fmt = icd->current_fmt->host_fmt;
+       unsigned long flags;
+       dma_cookie_t cookie;
+       size_t new_size;
+       new_size = icd->sizeimage;
+       if (vb2_plane_size(vb, 0) < new_size) {
+               dev_err(icd->parent, "Buffer #%d too small (%lu < %zu)\n",
+                       vb->v4l2_buf.index, vb2_plane_size(vb, 0), new_size);
+               goto error;
+       }
+       if (!buf->txd) {
+               sg_dma_address(sg)      = vb2_dma_contig_plane_dma_addr(vb, 0);
+               sg_dma_len(sg)          = new_size;
+               txd = dmaengine_prep_slave_sg(
+                       &ichan->dma_chan, sg, 1, DMA_DEV_TO_MEM,
+                       DMA_PREP_INTERRUPT);
+               if (!txd)
+                       goto error;
+               txd->callback_param     = txd;
+               txd->callback           = mx3_cam_dma_done;
+               buf->txd                = txd;
+       } else {
+               txd = buf->txd;
+       }
+       vb2_set_plane_payload(vb, 0, new_size);
+       /* This is the configuration of one sg-element */
+       video->out_pixel_fmt = fourcc_to_ipu_pix(host_fmt->fourcc);
+       if (video->out_pixel_fmt == IPU_PIX_FMT_GENERIC) {
+               /*
+                * If the IPU DMA channel is configured to transfer generic
+                * 8-bit data, we have to set up the geometry parameters
+                * correctly, according to the current pixel format. The DMA
+                * horizontal parameters in this case are expressed in bytes,
+                * not in pixels.
+                */
+               video->out_width        = icd->bytesperline;
+               video->out_height       = icd->user_height;
+               video->out_stride       = icd->bytesperline;
+       } else {
+               /*
+                * For IPU known formats the pixel unit will be managed
+                * successfully by the IPU code
+                */
+               video->out_width        = icd->user_width;
+               video->out_height       = icd->user_height;
+               video->out_stride       = icd->user_width;
+       }
+ #ifdef DEBUG
+       /* helps to see what DMA actually has written */
+       if (vb2_plane_vaddr(vb, 0))
+               memset(vb2_plane_vaddr(vb, 0), 0xaa, vb2_get_plane_payload(vb, 0));
+ #endif
+       spin_lock_irqsave(&mx3_cam->lock, flags);
+       list_add_tail(&buf->queue, &mx3_cam->capture);
+       if (!mx3_cam->active)
+               mx3_cam->active = buf;
+       spin_unlock_irq(&mx3_cam->lock);
+       cookie = txd->tx_submit(txd);
+       dev_dbg(icd->parent, "Submitted cookie %d DMA 0x%08x\n",
+               cookie, sg_dma_address(&buf->sg));
+       if (cookie >= 0)
+               return;
+       spin_lock_irq(&mx3_cam->lock);
+       /* Submit error */
+       list_del_init(&buf->queue);
+       if (mx3_cam->active == buf)
+               mx3_cam->active = NULL;
+       spin_unlock_irqrestore(&mx3_cam->lock, flags);
+ error:
+       vb2_buffer_done(vb, VB2_BUF_STATE_ERROR);
+ }
+ static void mx3_videobuf_release(struct vb2_buffer *vb)
+ {
+       struct soc_camera_device *icd = soc_camera_from_vb2q(vb->vb2_queue);
+       struct soc_camera_host *ici = to_soc_camera_host(icd->parent);
+       struct mx3_camera_dev *mx3_cam = ici->priv;
+       struct mx3_camera_buffer *buf = to_mx3_vb(vb);
+       struct dma_async_tx_descriptor *txd = buf->txd;
+       unsigned long flags;
+       dev_dbg(icd->parent,
+               "Release%s DMA 0x%08x, queue %sempty\n",
+               mx3_cam->active == buf ? " active" : "", sg_dma_address(&buf->sg),
+               list_empty(&buf->queue) ? "" : "not ");
+       spin_lock_irqsave(&mx3_cam->lock, flags);
+       if (mx3_cam->active == buf)
+               mx3_cam->active = NULL;
+       /* Doesn't hurt also if the list is empty */
+       list_del_init(&buf->queue);
+       if (txd) {
+               buf->txd = NULL;
+               if (mx3_cam->idmac_channel[0])
+                       async_tx_ack(txd);
+       }
+       spin_unlock_irqrestore(&mx3_cam->lock, flags);
+       mx3_cam->buf_total -= vb2_plane_size(vb, 0);
+ }
+ static int mx3_videobuf_init(struct vb2_buffer *vb)
+ {
+       struct soc_camera_device *icd = soc_camera_from_vb2q(vb->vb2_queue);
+       struct soc_camera_host *ici = to_soc_camera_host(icd->parent);
+       struct mx3_camera_dev *mx3_cam = ici->priv;
+       struct mx3_camera_buffer *buf = to_mx3_vb(vb);
+       if (!buf->txd) {
+               /* This is for locking debugging only */
+               INIT_LIST_HEAD(&buf->queue);
+               sg_init_table(&buf->sg, 1);
+               mx3_cam->buf_total += vb2_plane_size(vb, 0);
+       }
+       return 0;
+ }
+ static int mx3_stop_streaming(struct vb2_queue *q)
+ {
+       struct soc_camera_device *icd = soc_camera_from_vb2q(q);
+       struct soc_camera_host *ici = to_soc_camera_host(icd->parent);
+       struct mx3_camera_dev *mx3_cam = ici->priv;
+       struct idmac_channel *ichan = mx3_cam->idmac_channel[0];
+       struct mx3_camera_buffer *buf, *tmp;
+       unsigned long flags;
+       if (ichan) {
+               struct dma_chan *chan = &ichan->dma_chan;
+               chan->device->device_control(chan, DMA_PAUSE, 0);
+       }
+       spin_lock_irqsave(&mx3_cam->lock, flags);
+       mx3_cam->active = NULL;
+       list_for_each_entry_safe(buf, tmp, &mx3_cam->capture, queue) {
+               list_del_init(&buf->queue);
+               vb2_buffer_done(&buf->vb, VB2_BUF_STATE_ERROR);
+       }
+       spin_unlock_irqrestore(&mx3_cam->lock, flags);
+       return 0;
+ }
+ static struct vb2_ops mx3_videobuf_ops = {
+       .queue_setup    = mx3_videobuf_setup,
+       .buf_queue      = mx3_videobuf_queue,
+       .buf_cleanup    = mx3_videobuf_release,
+       .buf_init       = mx3_videobuf_init,
+       .wait_prepare   = soc_camera_unlock,
+       .wait_finish    = soc_camera_lock,
+       .stop_streaming = mx3_stop_streaming,
+ };
+ static int mx3_camera_init_videobuf(struct vb2_queue *q,
+                                    struct soc_camera_device *icd)
+ {
+       q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+       q->io_modes = VB2_MMAP | VB2_USERPTR;
+       q->drv_priv = icd;
+       q->ops = &mx3_videobuf_ops;
+       q->mem_ops = &vb2_dma_contig_memops;
+       q->buf_struct_size = sizeof(struct mx3_camera_buffer);
+       return vb2_queue_init(q);
+ }
+ /* First part of ipu_csi_init_interface() */
+ static void mx3_camera_activate(struct mx3_camera_dev *mx3_cam,
+                               struct soc_camera_device *icd)
+ {
+       u32 conf;
+       long rate;
+       /* Set default size: ipu_csi_set_window_size() */
+       csi_reg_write(mx3_cam, (640 - 1) | ((480 - 1) << 16), CSI_ACT_FRM_SIZE);
+       /* ...and position to 0:0: ipu_csi_set_window_pos() */
+       conf = csi_reg_read(mx3_cam, CSI_OUT_FRM_CTRL) & 0xffff0000;
+       csi_reg_write(mx3_cam, conf, CSI_OUT_FRM_CTRL);
+       /* We use only gated clock synchronisation mode so far */
+       conf = 0 << CSI_SENS_CONF_SENS_PRTCL_SHIFT;
+       /* Set generic data, platform-biggest bus-width */
+       conf |= CSI_SENS_CONF_DATA_FMT_BAYER;
+       if (mx3_cam->platform_flags & MX3_CAMERA_DATAWIDTH_15)
+               conf |= 3 << CSI_SENS_CONF_DATA_WIDTH_SHIFT;
+       else if (mx3_cam->platform_flags & MX3_CAMERA_DATAWIDTH_10)
+               conf |= 2 << CSI_SENS_CONF_DATA_WIDTH_SHIFT;
+       else if (mx3_cam->platform_flags & MX3_CAMERA_DATAWIDTH_8)
+               conf |= 1 << CSI_SENS_CONF_DATA_WIDTH_SHIFT;
+       else/* if (mx3_cam->platform_flags & MX3_CAMERA_DATAWIDTH_4)*/
+               conf |= 0 << CSI_SENS_CONF_DATA_WIDTH_SHIFT;
+       if (mx3_cam->platform_flags & MX3_CAMERA_CLK_SRC)
+               conf |= 1 << CSI_SENS_CONF_SENS_CLKSRC_SHIFT;
+       if (mx3_cam->platform_flags & MX3_CAMERA_EXT_VSYNC)
+               conf |= 1 << CSI_SENS_CONF_EXT_VSYNC_SHIFT;
+       if (mx3_cam->platform_flags & MX3_CAMERA_DP)
+               conf |= 1 << CSI_SENS_CONF_DATA_POL_SHIFT;
+       if (mx3_cam->platform_flags & MX3_CAMERA_PCP)
+               conf |= 1 << CSI_SENS_CONF_PIX_CLK_POL_SHIFT;
+       if (mx3_cam->platform_flags & MX3_CAMERA_HSP)
+               conf |= 1 << CSI_SENS_CONF_HSYNC_POL_SHIFT;
+       if (mx3_cam->platform_flags & MX3_CAMERA_VSP)
+               conf |= 1 << CSI_SENS_CONF_VSYNC_POL_SHIFT;
+       /* ipu_csi_init_interface() */
+       csi_reg_write(mx3_cam, conf, CSI_SENS_CONF);
+       clk_prepare_enable(mx3_cam->clk);
+       rate = clk_round_rate(mx3_cam->clk, mx3_cam->mclk);
+       dev_dbg(icd->parent, "Set SENS_CONF to %x, rate %ld\n", conf, rate);
+       if (rate)
+               clk_set_rate(mx3_cam->clk, rate);
+ }
+ /* Called with .video_lock held */
+ static int mx3_camera_add_device(struct soc_camera_device *icd)
+ {
+       struct soc_camera_host *ici = to_soc_camera_host(icd->parent);
+       struct mx3_camera_dev *mx3_cam = ici->priv;
+       if (mx3_cam->icd)
+               return -EBUSY;
+       mx3_camera_activate(mx3_cam, icd);
+       mx3_cam->buf_total = 0;
+       mx3_cam->icd = icd;
+       dev_info(icd->parent, "MX3 Camera driver attached to camera %d\n",
+                icd->devnum);
+       return 0;
+ }
+ /* Called with .video_lock held */
+ static void mx3_camera_remove_device(struct soc_camera_device *icd)
+ {
+       struct soc_camera_host *ici = to_soc_camera_host(icd->parent);
+       struct mx3_camera_dev *mx3_cam = ici->priv;
+       struct idmac_channel **ichan = &mx3_cam->idmac_channel[0];
+       BUG_ON(icd != mx3_cam->icd);
+       if (*ichan) {
+               dma_release_channel(&(*ichan)->dma_chan);
+               *ichan = NULL;
+       }
+       clk_disable_unprepare(mx3_cam->clk);
+       mx3_cam->icd = NULL;
+       dev_info(icd->parent, "MX3 Camera driver detached from camera %d\n",
+                icd->devnum);
+ }
+ static int test_platform_param(struct mx3_camera_dev *mx3_cam,
+                              unsigned char buswidth, unsigned long *flags)
+ {
+       /*
+        * If requested data width is supported by the platform, use it or any
+        * possible lower value - i.MX31 is smart enough to shift bits
+        */
+       if (buswidth > fls(mx3_cam->width_flags))
+               return -EINVAL;
+       /*
+        * Platform specified synchronization and pixel clock polarities are
+        * only a recommendation and are only used during probing. MX3x
+        * camera interface only works in master mode, i.e., uses HSYNC and
+        * VSYNC signals from the sensor
+        */
+       *flags = V4L2_MBUS_MASTER |
+               V4L2_MBUS_HSYNC_ACTIVE_HIGH |
+               V4L2_MBUS_HSYNC_ACTIVE_LOW |
+               V4L2_MBUS_VSYNC_ACTIVE_HIGH |
+               V4L2_MBUS_VSYNC_ACTIVE_LOW |
+               V4L2_MBUS_PCLK_SAMPLE_RISING |
+               V4L2_MBUS_PCLK_SAMPLE_FALLING |
+               V4L2_MBUS_DATA_ACTIVE_HIGH |
+               V4L2_MBUS_DATA_ACTIVE_LOW;
+       return 0;
+ }
+ static int mx3_camera_try_bus_param(struct soc_camera_device *icd,
+                                   const unsigned int depth)
+ {
+       struct v4l2_subdev *sd = soc_camera_to_subdev(icd);
+       struct soc_camera_host *ici = to_soc_camera_host(icd->parent);
+       struct mx3_camera_dev *mx3_cam = ici->priv;
+       struct v4l2_mbus_config cfg = {.type = V4L2_MBUS_PARALLEL,};
+       unsigned long bus_flags, common_flags;
+       int ret = test_platform_param(mx3_cam, depth, &bus_flags);
+       dev_dbg(icd->parent, "request bus width %d bit: %d\n", depth, ret);
+       if (ret < 0)
+               return ret;
+       ret = v4l2_subdev_call(sd, video, g_mbus_config, &cfg);
+       if (!ret) {
+               common_flags = soc_mbus_config_compatible(&cfg,
+                                                         bus_flags);
+               if (!common_flags) {
+                       dev_warn(icd->parent,
+                                "Flags incompatible: camera 0x%x, host 0x%lx\n",
+                                cfg.flags, bus_flags);
+                       return -EINVAL;
+               }
+       } else if (ret != -ENOIOCTLCMD) {
+               return ret;
+       }
+       return 0;
+ }
+ static bool chan_filter(struct dma_chan *chan, void *arg)
+ {
+       struct dma_chan_request *rq = arg;
+       struct mx3_camera_pdata *pdata;
+       if (!imx_dma_is_ipu(chan))
+               return false;
+       if (!rq)
+               return false;
+       pdata = rq->mx3_cam->soc_host.v4l2_dev.dev->platform_data;
+       return rq->id == chan->chan_id &&
+               pdata->dma_dev == chan->device->dev;
+ }
+ static const struct soc_mbus_pixelfmt mx3_camera_formats[] = {
+       {
+               .fourcc                 = V4L2_PIX_FMT_SBGGR8,
+               .name                   = "Bayer BGGR (sRGB) 8 bit",
+               .bits_per_sample        = 8,
+               .packing                = SOC_MBUS_PACKING_NONE,
+               .order                  = SOC_MBUS_ORDER_LE,
+               .layout                 = SOC_MBUS_LAYOUT_PACKED,
+       }, {
+               .fourcc                 = V4L2_PIX_FMT_GREY,
+               .name                   = "Monochrome 8 bit",
+               .bits_per_sample        = 8,
+               .packing                = SOC_MBUS_PACKING_NONE,
+               .order                  = SOC_MBUS_ORDER_LE,
+               .layout                 = SOC_MBUS_LAYOUT_PACKED,
+       },
+ };
+ /* This will be corrected as we get more formats */
+ static bool mx3_camera_packing_supported(const struct soc_mbus_pixelfmt *fmt)
+ {
+       return  fmt->packing == SOC_MBUS_PACKING_NONE ||
+               (fmt->bits_per_sample == 8 &&
+                fmt->packing == SOC_MBUS_PACKING_2X8_PADHI) ||
+               (fmt->bits_per_sample > 8 &&
+                fmt->packing == SOC_MBUS_PACKING_EXTEND16);
+ }
+ static int mx3_camera_get_formats(struct soc_camera_device *icd, unsigned int idx,
+                                 struct soc_camera_format_xlate *xlate)
+ {
+       struct v4l2_subdev *sd = soc_camera_to_subdev(icd);
+       struct device *dev = icd->parent;
+       int formats = 0, ret;
+       enum v4l2_mbus_pixelcode code;
+       const struct soc_mbus_pixelfmt *fmt;
+       ret = v4l2_subdev_call(sd, video, enum_mbus_fmt, idx, &code);
+       if (ret < 0)
+               /* No more formats */
+               return 0;
+       fmt = soc_mbus_get_fmtdesc(code);
+       if (!fmt) {
+               dev_warn(icd->parent,
+                        "Unsupported format code #%u: %d\n", idx, code);
+               return 0;
+       }
+       /* This also checks support for the requested bits-per-sample */
+       ret = mx3_camera_try_bus_param(icd, fmt->bits_per_sample);
+       if (ret < 0)
+               return 0;
+       switch (code) {
+       case V4L2_MBUS_FMT_SBGGR10_1X10:
+               formats++;
+               if (xlate) {
+                       xlate->host_fmt = &mx3_camera_formats[0];
+                       xlate->code     = code;
+                       xlate++;
+                       dev_dbg(dev, "Providing format %s using code %d\n",
+                               mx3_camera_formats[0].name, code);
+               }
+               break;
+       case V4L2_MBUS_FMT_Y10_1X10:
+               formats++;
+               if (xlate) {
+                       xlate->host_fmt = &mx3_camera_formats[1];
+                       xlate->code     = code;
+                       xlate++;
+                       dev_dbg(dev, "Providing format %s using code %d\n",
+                               mx3_camera_formats[1].name, code);
+               }
+               break;
+       default:
+               if (!mx3_camera_packing_supported(fmt))
+                       return 0;
+       }
+       /* Generic pass-through */
+       formats++;
+       if (xlate) {
+               xlate->host_fmt = fmt;
+               xlate->code     = code;
+               dev_dbg(dev, "Providing format %c%c%c%c in pass-through mode\n",
+                       (fmt->fourcc >> (0*8)) & 0xFF,
+                       (fmt->fourcc >> (1*8)) & 0xFF,
+                       (fmt->fourcc >> (2*8)) & 0xFF,
+                       (fmt->fourcc >> (3*8)) & 0xFF);
+               xlate++;
+       }
+       return formats;
+ }
+ static void configure_geometry(struct mx3_camera_dev *mx3_cam,
+                              unsigned int width, unsigned int height,
+                              const struct soc_mbus_pixelfmt *fmt)
+ {
+       u32 ctrl, width_field, height_field;
+       if (fourcc_to_ipu_pix(fmt->fourcc) == IPU_PIX_FMT_GENERIC) {
+               /*
+                * As the CSI will be configured to output BAYER, here
+                * the width parameter count the number of samples to
+                * capture to complete the whole image width.
+                */
+               unsigned int num, den;
+               int ret = soc_mbus_samples_per_pixel(fmt, &num, &den);
+               BUG_ON(ret < 0);
+               width = width * num / den;
+       }
+       /* Setup frame size - this cannot be changed on-the-fly... */
+       width_field = width - 1;
+       height_field = height - 1;
+       csi_reg_write(mx3_cam, width_field | (height_field << 16), CSI_SENS_FRM_SIZE);
+       csi_reg_write(mx3_cam, width_field << 16, CSI_FLASH_STROBE_1);
+       csi_reg_write(mx3_cam, (height_field << 16) | 0x22, CSI_FLASH_STROBE_2);
+       csi_reg_write(mx3_cam, width_field | (height_field << 16), CSI_ACT_FRM_SIZE);
+       /* ...and position */
+       ctrl = csi_reg_read(mx3_cam, CSI_OUT_FRM_CTRL) & 0xffff0000;
+       /* Sensor does the cropping */
+       csi_reg_write(mx3_cam, ctrl | 0 | (0 << 8), CSI_OUT_FRM_CTRL);
+ }
+ static int acquire_dma_channel(struct mx3_camera_dev *mx3_cam)
+ {
+       dma_cap_mask_t mask;
+       struct dma_chan *chan;
+       struct idmac_channel **ichan = &mx3_cam->idmac_channel[0];
+       /* We have to use IDMAC_IC_7 for Bayer / generic data */
+       struct dma_chan_request rq = {.mx3_cam = mx3_cam,
+                                     .id = IDMAC_IC_7};
+       dma_cap_zero(mask);
+       dma_cap_set(DMA_SLAVE, mask);
+       dma_cap_set(DMA_PRIVATE, mask);
+       chan = dma_request_channel(mask, chan_filter, &rq);
+       if (!chan)
+               return -EBUSY;
+       *ichan = to_idmac_chan(chan);
+       (*ichan)->client = mx3_cam;
+       return 0;
+ }
+ /*
+  * FIXME: learn to use stride != width, then we can keep stride properly aligned
+  * and support arbitrary (even) widths.
+  */
+ static inline void stride_align(__u32 *width)
+ {
+       if (ALIGN(*width, 8) < 4096)
+               *width = ALIGN(*width, 8);
+       else
+               *width = *width &  ~7;
+ }
+ /*
+  * As long as we don't implement host-side cropping and scaling, we can use
+  * default g_crop and cropcap from soc_camera.c
+  */
+ static int mx3_camera_set_crop(struct soc_camera_device *icd,
+                              struct v4l2_crop *a)
+ {
+       struct v4l2_rect *rect = &a->c;
+       struct soc_camera_host *ici = to_soc_camera_host(icd->parent);
+       struct mx3_camera_dev *mx3_cam = ici->priv;
+       struct v4l2_subdev *sd = soc_camera_to_subdev(icd);
+       struct v4l2_mbus_framefmt mf;
+       int ret;
+       soc_camera_limit_side(&rect->left, &rect->width, 0, 2, 4096);
+       soc_camera_limit_side(&rect->top, &rect->height, 0, 2, 4096);
+       ret = v4l2_subdev_call(sd, video, s_crop, a);
+       if (ret < 0)
+               return ret;
+       /* The capture device might have changed its output sizes */
+       ret = v4l2_subdev_call(sd, video, g_mbus_fmt, &mf);
+       if (ret < 0)
+               return ret;
+       if (mf.code != icd->current_fmt->code)
+               return -EINVAL;
+       if (mf.width & 7) {
+               /* Ouch! We can only handle 8-byte aligned width... */
+               stride_align(&mf.width);
+               ret = v4l2_subdev_call(sd, video, s_mbus_fmt, &mf);
+               if (ret < 0)
+                       return ret;
+       }
+       if (mf.width != icd->user_width || mf.height != icd->user_height)
+               configure_geometry(mx3_cam, mf.width, mf.height,
+                                  icd->current_fmt->host_fmt);
+       dev_dbg(icd->parent, "Sensor cropped %dx%d\n",
+               mf.width, mf.height);
+       icd->user_width         = mf.width;
+       icd->user_height        = mf.height;
+       return ret;
+ }
+ static int mx3_camera_set_fmt(struct soc_camera_device *icd,
+                             struct v4l2_format *f)
+ {
+       struct soc_camera_host *ici = to_soc_camera_host(icd->parent);
+       struct mx3_camera_dev *mx3_cam = ici->priv;
+       struct v4l2_subdev *sd = soc_camera_to_subdev(icd);
+       const struct soc_camera_format_xlate *xlate;
+       struct v4l2_pix_format *pix = &f->fmt.pix;
+       struct v4l2_mbus_framefmt mf;
+       int ret;
+       xlate = soc_camera_xlate_by_fourcc(icd, pix->pixelformat);
+       if (!xlate) {
+               dev_warn(icd->parent, "Format %x not found\n",
+                        pix->pixelformat);
+               return -EINVAL;
+       }
+       stride_align(&pix->width);
+       dev_dbg(icd->parent, "Set format %dx%d\n", pix->width, pix->height);
+       /*
+        * Might have to perform a complete interface initialisation like in
+        * ipu_csi_init_interface() in mxc_v4l2_s_param(). Also consider
+        * mxc_v4l2_s_fmt()
+        */
+       configure_geometry(mx3_cam, pix->width, pix->height, xlate->host_fmt);
+       mf.width        = pix->width;
+       mf.height       = pix->height;
+       mf.field        = pix->field;
+       mf.colorspace   = pix->colorspace;
+       mf.code         = xlate->code;
+       ret = v4l2_subdev_call(sd, video, s_mbus_fmt, &mf);
+       if (ret < 0)
+               return ret;
+       if (mf.code != xlate->code)
+               return -EINVAL;
+       if (!mx3_cam->idmac_channel[0]) {
+               ret = acquire_dma_channel(mx3_cam);
+               if (ret < 0)
+                       return ret;
+       }
+       pix->width              = mf.width;
+       pix->height             = mf.height;
+       pix->field              = mf.field;
+       mx3_cam->field          = mf.field;
+       pix->colorspace         = mf.colorspace;
+       icd->current_fmt        = xlate;
+       dev_dbg(icd->parent, "Sensor set %dx%d\n", pix->width, pix->height);
+       return ret;
+ }
+ static int mx3_camera_try_fmt(struct soc_camera_device *icd,
+                             struct v4l2_format *f)
+ {
+       struct v4l2_subdev *sd = soc_camera_to_subdev(icd);
+       const struct soc_camera_format_xlate *xlate;
+       struct v4l2_pix_format *pix = &f->fmt.pix;
+       struct v4l2_mbus_framefmt mf;
+       __u32 pixfmt = pix->pixelformat;
+       int ret;
+       xlate = soc_camera_xlate_by_fourcc(icd, pixfmt);
+       if (pixfmt && !xlate) {
+               dev_warn(icd->parent, "Format %x not found\n", pixfmt);
+               return -EINVAL;
+       }
+       /* limit to MX3 hardware capabilities */
+       if (pix->height > 4096)
+               pix->height = 4096;
+       if (pix->width > 4096)
+               pix->width = 4096;
+       /* limit to sensor capabilities */
+       mf.width        = pix->width;
+       mf.height       = pix->height;
+       mf.field        = pix->field;
+       mf.colorspace   = pix->colorspace;
+       mf.code         = xlate->code;
+       ret = v4l2_subdev_call(sd, video, try_mbus_fmt, &mf);
+       if (ret < 0)
+               return ret;
+       pix->width      = mf.width;
+       pix->height     = mf.height;
+       pix->colorspace = mf.colorspace;
+       switch (mf.field) {
+       case V4L2_FIELD_ANY:
+               pix->field = V4L2_FIELD_NONE;
+               break;
+       case V4L2_FIELD_NONE:
+               break;
+       default:
+               dev_err(icd->parent, "Field type %d unsupported.\n",
+                       mf.field);
+               ret = -EINVAL;
+       }
+       return ret;
+ }
+ static int mx3_camera_reqbufs(struct soc_camera_device *icd,
+                             struct v4l2_requestbuffers *p)
+ {
+       return 0;
+ }
+ static unsigned int mx3_camera_poll(struct file *file, poll_table *pt)
+ {
+       struct soc_camera_device *icd = file->private_data;
+       return vb2_poll(&icd->vb2_vidq, file, pt);
+ }
+ static int mx3_camera_querycap(struct soc_camera_host *ici,
+                              struct v4l2_capability *cap)
+ {
+       /* cap->name is set by the firendly caller:-> */
+       strlcpy(cap->card, "i.MX3x Camera", sizeof(cap->card));
+       cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;
+       return 0;
+ }
+ static int mx3_camera_set_bus_param(struct soc_camera_device *icd)
+ {
+       struct v4l2_subdev *sd = soc_camera_to_subdev(icd);
+       struct soc_camera_host *ici = to_soc_camera_host(icd->parent);
+       struct mx3_camera_dev *mx3_cam = ici->priv;
+       struct v4l2_mbus_config cfg = {.type = V4L2_MBUS_PARALLEL,};
+       u32 pixfmt = icd->current_fmt->host_fmt->fourcc;
+       unsigned long bus_flags, common_flags;
+       u32 dw, sens_conf;
+       const struct soc_mbus_pixelfmt *fmt;
+       int buswidth;
+       int ret;
+       const struct soc_camera_format_xlate *xlate;
+       struct device *dev = icd->parent;
+       fmt = soc_mbus_get_fmtdesc(icd->current_fmt->code);
+       if (!fmt)
+               return -EINVAL;
+       xlate = soc_camera_xlate_by_fourcc(icd, pixfmt);
+       if (!xlate) {
+               dev_warn(dev, "Format %x not found\n", pixfmt);
+               return -EINVAL;
+       }
+       buswidth = fmt->bits_per_sample;
+       ret = test_platform_param(mx3_cam, buswidth, &bus_flags);
+       dev_dbg(dev, "requested bus width %d bit: %d\n", buswidth, ret);
+       if (ret < 0)
+               return ret;
+       ret = v4l2_subdev_call(sd, video, g_mbus_config, &cfg);
+       if (!ret) {
+               common_flags = soc_mbus_config_compatible(&cfg,
+                                                         bus_flags);
+               if (!common_flags) {
+                       dev_warn(icd->parent,
+                                "Flags incompatible: camera 0x%x, host 0x%lx\n",
+                                cfg.flags, bus_flags);
+                       return -EINVAL;
+               }
+       } else if (ret != -ENOIOCTLCMD) {
+               return ret;
+       } else {
+               common_flags = bus_flags;
+       }
+       dev_dbg(dev, "Flags cam: 0x%x host: 0x%lx common: 0x%lx\n",
+               cfg.flags, bus_flags, common_flags);
+       /* Make choices, based on platform preferences */
+       if ((common_flags & V4L2_MBUS_HSYNC_ACTIVE_HIGH) &&
+           (common_flags & V4L2_MBUS_HSYNC_ACTIVE_LOW)) {
+               if (mx3_cam->platform_flags & MX3_CAMERA_HSP)
+                       common_flags &= ~V4L2_MBUS_HSYNC_ACTIVE_HIGH;
+               else
+                       common_flags &= ~V4L2_MBUS_HSYNC_ACTIVE_LOW;
+       }
+       if ((common_flags & V4L2_MBUS_VSYNC_ACTIVE_HIGH) &&
+           (common_flags & V4L2_MBUS_VSYNC_ACTIVE_LOW)) {
+               if (mx3_cam->platform_flags & MX3_CAMERA_VSP)
+                       common_flags &= ~V4L2_MBUS_VSYNC_ACTIVE_HIGH;
+               else
+                       common_flags &= ~V4L2_MBUS_VSYNC_ACTIVE_LOW;
+       }
+       if ((common_flags & V4L2_MBUS_DATA_ACTIVE_HIGH) &&
+           (common_flags & V4L2_MBUS_DATA_ACTIVE_LOW)) {
+               if (mx3_cam->platform_flags & MX3_CAMERA_DP)
+                       common_flags &= ~V4L2_MBUS_DATA_ACTIVE_HIGH;
+               else
+                       common_flags &= ~V4L2_MBUS_DATA_ACTIVE_LOW;
+       }
+       if ((common_flags & V4L2_MBUS_PCLK_SAMPLE_RISING) &&
+           (common_flags & V4L2_MBUS_PCLK_SAMPLE_FALLING)) {
+               if (mx3_cam->platform_flags & MX3_CAMERA_PCP)
+                       common_flags &= ~V4L2_MBUS_PCLK_SAMPLE_RISING;
+               else
+                       common_flags &= ~V4L2_MBUS_PCLK_SAMPLE_FALLING;
+       }
+       cfg.flags = common_flags;
+       ret = v4l2_subdev_call(sd, video, s_mbus_config, &cfg);
+       if (ret < 0 && ret != -ENOIOCTLCMD) {
+               dev_dbg(dev, "camera s_mbus_config(0x%lx) returned %d\n",
+                       common_flags, ret);
+               return ret;
+       }
+       /*
+        * So far only gated clock mode is supported. Add a line
+        *      (3 << CSI_SENS_CONF_SENS_PRTCL_SHIFT) |
+        * below and select the required mode when supporting other
+        * synchronisation protocols.
+        */
+       sens_conf = csi_reg_read(mx3_cam, CSI_SENS_CONF) &
+               ~((1 << CSI_SENS_CONF_VSYNC_POL_SHIFT) |
+                 (1 << CSI_SENS_CONF_HSYNC_POL_SHIFT) |
+                 (1 << CSI_SENS_CONF_DATA_POL_SHIFT) |
+                 (1 << CSI_SENS_CONF_PIX_CLK_POL_SHIFT) |
+                 (3 << CSI_SENS_CONF_DATA_FMT_SHIFT) |
+                 (3 << CSI_SENS_CONF_DATA_WIDTH_SHIFT));
+       /* TODO: Support RGB and YUV formats */
+       /* This has been set in mx3_camera_activate(), but we clear it above */
+       sens_conf |= CSI_SENS_CONF_DATA_FMT_BAYER;
+       if (common_flags & V4L2_MBUS_PCLK_SAMPLE_FALLING)
+               sens_conf |= 1 << CSI_SENS_CONF_PIX_CLK_POL_SHIFT;
+       if (common_flags & V4L2_MBUS_HSYNC_ACTIVE_LOW)
+               sens_conf |= 1 << CSI_SENS_CONF_HSYNC_POL_SHIFT;
+       if (common_flags & V4L2_MBUS_VSYNC_ACTIVE_LOW)
+               sens_conf |= 1 << CSI_SENS_CONF_VSYNC_POL_SHIFT;
+       if (common_flags & V4L2_MBUS_DATA_ACTIVE_LOW)
+               sens_conf |= 1 << CSI_SENS_CONF_DATA_POL_SHIFT;
+       /* Just do what we're asked to do */
+       switch (xlate->host_fmt->bits_per_sample) {
+       case 4:
+               dw = 0 << CSI_SENS_CONF_DATA_WIDTH_SHIFT;
+               break;
+       case 8:
+               dw = 1 << CSI_SENS_CONF_DATA_WIDTH_SHIFT;
+               break;
+       case 10:
+               dw = 2 << CSI_SENS_CONF_DATA_WIDTH_SHIFT;
+               break;
+       default:
+               /*
+                * Actually it can only be 15 now, default is just to silence
+                * compiler warnings
+                */
+       case 15:
+               dw = 3 << CSI_SENS_CONF_DATA_WIDTH_SHIFT;
+       }
+       csi_reg_write(mx3_cam, sens_conf | dw, CSI_SENS_CONF);
+       dev_dbg(dev, "Set SENS_CONF to %x\n", sens_conf | dw);
+       return 0;
+ }
+ static struct soc_camera_host_ops mx3_soc_camera_host_ops = {
+       .owner          = THIS_MODULE,
+       .add            = mx3_camera_add_device,
+       .remove         = mx3_camera_remove_device,
+       .set_crop       = mx3_camera_set_crop,
+       .set_fmt        = mx3_camera_set_fmt,
+       .try_fmt        = mx3_camera_try_fmt,
+       .get_formats    = mx3_camera_get_formats,
+       .init_videobuf2 = mx3_camera_init_videobuf,
+       .reqbufs        = mx3_camera_reqbufs,
+       .poll           = mx3_camera_poll,
+       .querycap       = mx3_camera_querycap,
+       .set_bus_param  = mx3_camera_set_bus_param,
+ };
+ static int __devinit mx3_camera_probe(struct platform_device *pdev)
+ {
+       struct mx3_camera_dev *mx3_cam;
+       struct resource *res;
+       void __iomem *base;
+       int err = 0;
+       struct soc_camera_host *soc_host;
+       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       if (!res) {
+               err = -ENODEV;
+               goto egetres;
+       }
+       mx3_cam = vzalloc(sizeof(*mx3_cam));
+       if (!mx3_cam) {
+               dev_err(&pdev->dev, "Could not allocate mx3 camera object\n");
+               err = -ENOMEM;
+               goto ealloc;
+       }
+       mx3_cam->clk = clk_get(&pdev->dev, NULL);
+       if (IS_ERR(mx3_cam->clk)) {
+               err = PTR_ERR(mx3_cam->clk);
+               goto eclkget;
+       }
+       mx3_cam->pdata = pdev->dev.platform_data;
+       mx3_cam->platform_flags = mx3_cam->pdata->flags;
+       if (!(mx3_cam->platform_flags & MX3_CAMERA_DATAWIDTH_MASK)) {
+               /*
+                * Platform hasn't set available data widths. This is bad.
+                * Warn and use a default.
+                */
+               dev_warn(&pdev->dev, "WARNING! Platform hasn't set available "
+                        "data widths, using default 8 bit\n");
+               mx3_cam->platform_flags |= MX3_CAMERA_DATAWIDTH_8;
+       }
+       if (mx3_cam->platform_flags & MX3_CAMERA_DATAWIDTH_4)
+               mx3_cam->width_flags = 1 << 3;
+       if (mx3_cam->platform_flags & MX3_CAMERA_DATAWIDTH_8)
+               mx3_cam->width_flags |= 1 << 7;
+       if (mx3_cam->platform_flags & MX3_CAMERA_DATAWIDTH_10)
+               mx3_cam->width_flags |= 1 << 9;
+       if (mx3_cam->platform_flags & MX3_CAMERA_DATAWIDTH_15)
+               mx3_cam->width_flags |= 1 << 14;
+       mx3_cam->mclk = mx3_cam->pdata->mclk_10khz * 10000;
+       if (!mx3_cam->mclk) {
+               dev_warn(&pdev->dev,
+                        "mclk_10khz == 0! Please, fix your platform data. "
+                        "Using default 20MHz\n");
+               mx3_cam->mclk = 20000000;
+       }
+       /* list of video-buffers */
+       INIT_LIST_HEAD(&mx3_cam->capture);
+       spin_lock_init(&mx3_cam->lock);
+       base = ioremap(res->start, resource_size(res));
+       if (!base) {
+               pr_err("Couldn't map %x@%x\n", resource_size(res), res->start);
+               err = -ENOMEM;
+               goto eioremap;
+       }
+       mx3_cam->base   = base;
+       soc_host                = &mx3_cam->soc_host;
+       soc_host->drv_name      = MX3_CAM_DRV_NAME;
+       soc_host->ops           = &mx3_soc_camera_host_ops;
+       soc_host->priv          = mx3_cam;
+       soc_host->v4l2_dev.dev  = &pdev->dev;
+       soc_host->nr            = pdev->id;
+       mx3_cam->alloc_ctx = vb2_dma_contig_init_ctx(&pdev->dev);
+       if (IS_ERR(mx3_cam->alloc_ctx)) {
+               err = PTR_ERR(mx3_cam->alloc_ctx);
+               goto eallocctx;
+       }
+       err = soc_camera_host_register(soc_host);
+       if (err)
+               goto ecamhostreg;
+       /* IDMAC interface */
+       dmaengine_get();
+       return 0;
+ ecamhostreg:
+       vb2_dma_contig_cleanup_ctx(mx3_cam->alloc_ctx);
+ eallocctx:
+       iounmap(base);
+ eioremap:
+       clk_put(mx3_cam->clk);
+ eclkget:
+       vfree(mx3_cam);
+ ealloc:
+ egetres:
+       return err;
+ }
+ static int __devexit mx3_camera_remove(struct platform_device *pdev)
+ {
+       struct soc_camera_host *soc_host = to_soc_camera_host(&pdev->dev);
+       struct mx3_camera_dev *mx3_cam = container_of(soc_host,
+                                       struct mx3_camera_dev, soc_host);
+       clk_put(mx3_cam->clk);
+       soc_camera_host_unregister(soc_host);
+       iounmap(mx3_cam->base);
+       /*
+        * The channel has either not been allocated,
+        * or should have been released
+        */
+       if (WARN_ON(mx3_cam->idmac_channel[0]))
+               dma_release_channel(&mx3_cam->idmac_channel[0]->dma_chan);
+       vb2_dma_contig_cleanup_ctx(mx3_cam->alloc_ctx);
+       vfree(mx3_cam);
+       dmaengine_put();
+       return 0;
+ }
+ static struct platform_driver mx3_camera_driver = {
+       .driver         = {
+               .name   = MX3_CAM_DRV_NAME,
+       },
+       .probe          = mx3_camera_probe,
+       .remove         = __devexit_p(mx3_camera_remove),
+ };
+ module_platform_driver(mx3_camera_driver);
+ MODULE_DESCRIPTION("i.MX3x SoC Camera Host driver");
+ MODULE_AUTHOR("Guennadi Liakhovetski <lg@denx.de>");
+ MODULE_LICENSE("GPL v2");
+ MODULE_VERSION("0.2.3");
+ MODULE_ALIAS("platform:" MX3_CAM_DRV_NAME);
index 0000000000000000000000000000000000000000,9c21e01f2c24c9145cc3d7287c9f2db394eb7c8f..1e3776d08dacb6f6159a417ecbe5d6bf9c935f3f
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,1852 +1,1852 @@@
 -#include <mach/camera.h>
+ /*
+  * V4L2 Driver for PXA camera host
+  *
+  * Copyright (C) 2006, Sascha Hauer, Pengutronix
+  * Copyright (C) 2008, Guennadi Liakhovetski <kernel@pengutronix.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.
+  */
+ #include <linux/init.h>
+ #include <linux/module.h>
+ #include <linux/io.h>
+ #include <linux/delay.h>
+ #include <linux/dma-mapping.h>
+ #include <linux/errno.h>
+ #include <linux/fs.h>
+ #include <linux/interrupt.h>
+ #include <linux/kernel.h>
+ #include <linux/mm.h>
+ #include <linux/moduleparam.h>
+ #include <linux/time.h>
+ #include <linux/device.h>
+ #include <linux/platform_device.h>
+ #include <linux/clk.h>
+ #include <linux/sched.h>
+ #include <linux/slab.h>
+ #include <media/v4l2-common.h>
+ #include <media/v4l2-dev.h>
+ #include <media/videobuf-dma-sg.h>
+ #include <media/soc_camera.h>
+ #include <media/soc_mediabus.h>
+ #include <linux/videodev2.h>
+ #include <mach/dma.h>
++#include <linux/platform_data/camera-pxa.h>
+ #define PXA_CAM_VERSION "0.0.6"
+ #define PXA_CAM_DRV_NAME "pxa27x-camera"
+ /* Camera Interface */
+ #define CICR0         0x0000
+ #define CICR1         0x0004
+ #define CICR2         0x0008
+ #define CICR3         0x000C
+ #define CICR4         0x0010
+ #define CISR          0x0014
+ #define CIFR          0x0018
+ #define CITOR         0x001C
+ #define CIBR0         0x0028
+ #define CIBR1         0x0030
+ #define CIBR2         0x0038
+ #define CICR0_DMAEN   (1 << 31)       /* DMA request enable */
+ #define CICR0_PAR_EN  (1 << 30)       /* Parity enable */
+ #define CICR0_SL_CAP_EN       (1 << 29)       /* Capture enable for slave mode */
+ #define CICR0_ENB     (1 << 28)       /* Camera interface enable */
+ #define CICR0_DIS     (1 << 27)       /* Camera interface disable */
+ #define CICR0_SIM     (0x7 << 24)     /* Sensor interface mode mask */
+ #define CICR0_TOM     (1 << 9)        /* Time-out mask */
+ #define CICR0_RDAVM   (1 << 8)        /* Receive-data-available mask */
+ #define CICR0_FEM     (1 << 7)        /* FIFO-empty mask */
+ #define CICR0_EOLM    (1 << 6)        /* End-of-line mask */
+ #define CICR0_PERRM   (1 << 5)        /* Parity-error mask */
+ #define CICR0_QDM     (1 << 4)        /* Quick-disable mask */
+ #define CICR0_CDM     (1 << 3)        /* Disable-done mask */
+ #define CICR0_SOFM    (1 << 2)        /* Start-of-frame mask */
+ #define CICR0_EOFM    (1 << 1)        /* End-of-frame mask */
+ #define CICR0_FOM     (1 << 0)        /* FIFO-overrun mask */
+ #define CICR1_TBIT    (1 << 31)       /* Transparency bit */
+ #define CICR1_RGBT_CONV       (0x3 << 29)     /* RGBT conversion mask */
+ #define CICR1_PPL     (0x7ff << 15)   /* Pixels per line mask */
+ #define CICR1_RGB_CONV        (0x7 << 12)     /* RGB conversion mask */
+ #define CICR1_RGB_F   (1 << 11)       /* RGB format */
+ #define CICR1_YCBCR_F (1 << 10)       /* YCbCr format */
+ #define CICR1_RGB_BPP (0x7 << 7)      /* RGB bis per pixel mask */
+ #define CICR1_RAW_BPP (0x3 << 5)      /* Raw bis per pixel mask */
+ #define CICR1_COLOR_SP        (0x3 << 3)      /* Color space mask */
+ #define CICR1_DW      (0x7 << 0)      /* Data width mask */
+ #define CICR2_BLW     (0xff << 24)    /* Beginning-of-line pixel clock
+                                          wait count mask */
+ #define CICR2_ELW     (0xff << 16)    /* End-of-line pixel clock
+                                          wait count mask */
+ #define CICR2_HSW     (0x3f << 10)    /* Horizontal sync pulse width mask */
+ #define CICR2_BFPW    (0x3f << 3)     /* Beginning-of-frame pixel clock
+                                          wait count mask */
+ #define CICR2_FSW     (0x7 << 0)      /* Frame stabilization
+                                          wait count mask */
+ #define CICR3_BFW     (0xff << 24)    /* Beginning-of-frame line clock
+                                          wait count mask */
+ #define CICR3_EFW     (0xff << 16)    /* End-of-frame line clock
+                                          wait count mask */
+ #define CICR3_VSW     (0x3f << 10)    /* Vertical sync pulse width mask */
+ #define CICR3_BFPW    (0x3f << 3)     /* Beginning-of-frame pixel clock
+                                          wait count mask */
+ #define CICR3_LPF     (0x7ff << 0)    /* Lines per frame mask */
+ #define CICR4_MCLK_DLY        (0x3 << 24)     /* MCLK Data Capture Delay mask */
+ #define CICR4_PCLK_EN (1 << 23)       /* Pixel clock enable */
+ #define CICR4_PCP     (1 << 22)       /* Pixel clock polarity */
+ #define CICR4_HSP     (1 << 21)       /* Horizontal sync polarity */
+ #define CICR4_VSP     (1 << 20)       /* Vertical sync polarity */
+ #define CICR4_MCLK_EN (1 << 19)       /* MCLK enable */
+ #define CICR4_FR_RATE (0x7 << 8)      /* Frame rate mask */
+ #define CICR4_DIV     (0xff << 0)     /* Clock divisor mask */
+ #define CISR_FTO      (1 << 15)       /* FIFO time-out */
+ #define CISR_RDAV_2   (1 << 14)       /* Channel 2 receive data available */
+ #define CISR_RDAV_1   (1 << 13)       /* Channel 1 receive data available */
+ #define CISR_RDAV_0   (1 << 12)       /* Channel 0 receive data available */
+ #define CISR_FEMPTY_2 (1 << 11)       /* Channel 2 FIFO empty */
+ #define CISR_FEMPTY_1 (1 << 10)       /* Channel 1 FIFO empty */
+ #define CISR_FEMPTY_0 (1 << 9)        /* Channel 0 FIFO empty */
+ #define CISR_EOL      (1 << 8)        /* End of line */
+ #define CISR_PAR_ERR  (1 << 7)        /* Parity error */
+ #define CISR_CQD      (1 << 6)        /* Camera interface quick disable */
+ #define CISR_CDD      (1 << 5)        /* Camera interface disable done */
+ #define CISR_SOF      (1 << 4)        /* Start of frame */
+ #define CISR_EOF      (1 << 3)        /* End of frame */
+ #define CISR_IFO_2    (1 << 2)        /* FIFO overrun for Channel 2 */
+ #define CISR_IFO_1    (1 << 1)        /* FIFO overrun for Channel 1 */
+ #define CISR_IFO_0    (1 << 0)        /* FIFO overrun for Channel 0 */
+ #define CIFR_FLVL2    (0x7f << 23)    /* FIFO 2 level mask */
+ #define CIFR_FLVL1    (0x7f << 16)    /* FIFO 1 level mask */
+ #define CIFR_FLVL0    (0xff << 8)     /* FIFO 0 level mask */
+ #define CIFR_THL_0    (0x3 << 4)      /* Threshold Level for Channel 0 FIFO */
+ #define CIFR_RESET_F  (1 << 3)        /* Reset input FIFOs */
+ #define CIFR_FEN2     (1 << 2)        /* FIFO enable for channel 2 */
+ #define CIFR_FEN1     (1 << 1)        /* FIFO enable for channel 1 */
+ #define CIFR_FEN0     (1 << 0)        /* FIFO enable for channel 0 */
+ #define CICR0_SIM_MP  (0 << 24)
+ #define CICR0_SIM_SP  (1 << 24)
+ #define CICR0_SIM_MS  (2 << 24)
+ #define CICR0_SIM_EP  (3 << 24)
+ #define CICR0_SIM_ES  (4 << 24)
+ #define CICR1_DW_VAL(x)   ((x) & CICR1_DW)        /* Data bus width */
+ #define CICR1_PPL_VAL(x)  (((x) << 15) & CICR1_PPL) /* Pixels per line */
+ #define CICR1_COLOR_SP_VAL(x) (((x) << 3) & CICR1_COLOR_SP)   /* color space */
+ #define CICR1_RGB_BPP_VAL(x)  (((x) << 7) & CICR1_RGB_BPP)    /* bpp for rgb */
+ #define CICR1_RGBT_CONV_VAL(x)        (((x) << 29) & CICR1_RGBT_CONV) /* rgbt conv */
+ #define CICR2_BLW_VAL(x)  (((x) << 24) & CICR2_BLW) /* Beginning-of-line pixel clock wait count */
+ #define CICR2_ELW_VAL(x)  (((x) << 16) & CICR2_ELW) /* End-of-line pixel clock wait count */
+ #define CICR2_HSW_VAL(x)  (((x) << 10) & CICR2_HSW) /* Horizontal sync pulse width */
+ #define CICR2_BFPW_VAL(x) (((x) << 3) & CICR2_BFPW) /* Beginning-of-frame pixel clock wait count */
+ #define CICR2_FSW_VAL(x)  (((x) << 0) & CICR2_FSW)  /* Frame stabilization wait count */
+ #define CICR3_BFW_VAL(x)  (((x) << 24) & CICR3_BFW) /* Beginning-of-frame line clock wait count  */
+ #define CICR3_EFW_VAL(x)  (((x) << 16) & CICR3_EFW) /* End-of-frame line clock wait count */
+ #define CICR3_VSW_VAL(x)  (((x) << 11) & CICR3_VSW) /* Vertical sync pulse width */
+ #define CICR3_LPF_VAL(x)  (((x) << 0) & CICR3_LPF)  /* Lines per frame */
+ #define CICR0_IRQ_MASK (CICR0_TOM | CICR0_RDAVM | CICR0_FEM | CICR0_EOLM | \
+                       CICR0_PERRM | CICR0_QDM | CICR0_CDM | CICR0_SOFM | \
+                       CICR0_EOFM | CICR0_FOM)
+ /*
+  * Structures
+  */
+ enum pxa_camera_active_dma {
+       DMA_Y = 0x1,
+       DMA_U = 0x2,
+       DMA_V = 0x4,
+ };
+ /* descriptor needed for the PXA DMA engine */
+ struct pxa_cam_dma {
+       dma_addr_t              sg_dma;
+       struct pxa_dma_desc     *sg_cpu;
+       size_t                  sg_size;
+       int                     sglen;
+ };
+ /* buffer for one video frame */
+ struct pxa_buffer {
+       /* common v4l buffer stuff -- must be first */
+       struct videobuf_buffer          vb;
+       enum v4l2_mbus_pixelcode        code;
+       /* our descriptor lists for Y, U and V channels */
+       struct pxa_cam_dma              dmas[3];
+       int                             inwork;
+       enum pxa_camera_active_dma      active_dma;
+ };
+ struct pxa_camera_dev {
+       struct soc_camera_host  soc_host;
+       /*
+        * PXA27x is only supposed to handle one camera on its Quick Capture
+        * interface. If anyone ever builds hardware to enable more than
+        * one camera, they will have to modify this driver too
+        */
+       struct soc_camera_device *icd;
+       struct clk              *clk;
+       unsigned int            irq;
+       void __iomem            *base;
+       int                     channels;
+       unsigned int            dma_chans[3];
+       struct pxacamera_platform_data *pdata;
+       struct resource         *res;
+       unsigned long           platform_flags;
+       unsigned long           ciclk;
+       unsigned long           mclk;
+       u32                     mclk_divisor;
+       u16                     width_flags;    /* max 10 bits */
+       struct list_head        capture;
+       spinlock_t              lock;
+       struct pxa_buffer       *active;
+       struct pxa_dma_desc     *sg_tail[3];
+       u32                     save_cicr[5];
+ };
+ struct pxa_cam {
+       unsigned long flags;
+ };
+ static const char *pxa_cam_driver_description = "PXA_Camera";
+ static unsigned int vid_limit = 16;   /* Video memory limit, in Mb */
+ /*
+  *  Videobuf operations
+  */
+ static int pxa_videobuf_setup(struct videobuf_queue *vq, unsigned int *count,
+                             unsigned int *size)
+ {
+       struct soc_camera_device *icd = vq->priv_data;
+       dev_dbg(icd->parent, "count=%d, size=%d\n", *count, *size);
+       *size = icd->sizeimage;
+       if (0 == *count)
+               *count = 32;
+       if (*size * *count > vid_limit * 1024 * 1024)
+               *count = (vid_limit * 1024 * 1024) / *size;
+       return 0;
+ }
+ static void free_buffer(struct videobuf_queue *vq, struct pxa_buffer *buf)
+ {
+       struct soc_camera_device *icd = vq->priv_data;
+       struct soc_camera_host *ici = to_soc_camera_host(icd->parent);
+       struct videobuf_dmabuf *dma = videobuf_to_dma(&buf->vb);
+       int i;
+       BUG_ON(in_interrupt());
+       dev_dbg(icd->parent, "%s (vb=0x%p) 0x%08lx %d\n", __func__,
+               &buf->vb, buf->vb.baddr, buf->vb.bsize);
+       /*
+        * This waits until this buffer is out of danger, i.e., until it is no
+        * longer in STATE_QUEUED or STATE_ACTIVE
+        */
+       videobuf_waiton(vq, &buf->vb, 0, 0);
+       videobuf_dma_unmap(vq->dev, dma);
+       videobuf_dma_free(dma);
+       for (i = 0; i < ARRAY_SIZE(buf->dmas); i++) {
+               if (buf->dmas[i].sg_cpu)
+                       dma_free_coherent(ici->v4l2_dev.dev,
+                                         buf->dmas[i].sg_size,
+                                         buf->dmas[i].sg_cpu,
+                                         buf->dmas[i].sg_dma);
+               buf->dmas[i].sg_cpu = NULL;
+       }
+       buf->vb.state = VIDEOBUF_NEEDS_INIT;
+ }
+ static int calculate_dma_sglen(struct scatterlist *sglist, int sglen,
+                              int sg_first_ofs, int size)
+ {
+       int i, offset, dma_len, xfer_len;
+       struct scatterlist *sg;
+       offset = sg_first_ofs;
+       for_each_sg(sglist, sg, sglen, i) {
+               dma_len = sg_dma_len(sg);
+               /* PXA27x Developer's Manual 27.4.4.1: round up to 8 bytes */
+               xfer_len = roundup(min(dma_len - offset, size), 8);
+               size = max(0, size - xfer_len);
+               offset = 0;
+               if (size == 0)
+                       break;
+       }
+       BUG_ON(size != 0);
+       return i + 1;
+ }
+ /**
+  * pxa_init_dma_channel - init dma descriptors
+  * @pcdev: pxa camera device
+  * @buf: pxa buffer to find pxa dma channel
+  * @dma: dma video buffer
+  * @channel: dma channel (0 => 'Y', 1 => 'U', 2 => 'V')
+  * @cibr: camera Receive Buffer Register
+  * @size: bytes to transfer
+  * @sg_first: first element of sg_list
+  * @sg_first_ofs: offset in first element of sg_list
+  *
+  * Prepares the pxa dma descriptors to transfer one camera channel.
+  * Beware sg_first and sg_first_ofs are both input and output parameters.
+  *
+  * Returns 0 or -ENOMEM if no coherent memory is available
+  */
+ static int pxa_init_dma_channel(struct pxa_camera_dev *pcdev,
+                               struct pxa_buffer *buf,
+                               struct videobuf_dmabuf *dma, int channel,
+                               int cibr, int size,
+                               struct scatterlist **sg_first, int *sg_first_ofs)
+ {
+       struct pxa_cam_dma *pxa_dma = &buf->dmas[channel];
+       struct device *dev = pcdev->soc_host.v4l2_dev.dev;
+       struct scatterlist *sg;
+       int i, offset, sglen;
+       int dma_len = 0, xfer_len = 0;
+       if (pxa_dma->sg_cpu)
+               dma_free_coherent(dev, pxa_dma->sg_size,
+                                 pxa_dma->sg_cpu, pxa_dma->sg_dma);
+       sglen = calculate_dma_sglen(*sg_first, dma->sglen,
+                                   *sg_first_ofs, size);
+       pxa_dma->sg_size = (sglen + 1) * sizeof(struct pxa_dma_desc);
+       pxa_dma->sg_cpu = dma_alloc_coherent(dev, pxa_dma->sg_size,
+                                            &pxa_dma->sg_dma, GFP_KERNEL);
+       if (!pxa_dma->sg_cpu)
+               return -ENOMEM;
+       pxa_dma->sglen = sglen;
+       offset = *sg_first_ofs;
+       dev_dbg(dev, "DMA: sg_first=%p, sglen=%d, ofs=%d, dma.desc=%x\n",
+               *sg_first, sglen, *sg_first_ofs, pxa_dma->sg_dma);
+       for_each_sg(*sg_first, sg, sglen, i) {
+               dma_len = sg_dma_len(sg);
+               /* PXA27x Developer's Manual 27.4.4.1: round up to 8 bytes */
+               xfer_len = roundup(min(dma_len - offset, size), 8);
+               size = max(0, size - xfer_len);
+               pxa_dma->sg_cpu[i].dsadr = pcdev->res->start + cibr;
+               pxa_dma->sg_cpu[i].dtadr = sg_dma_address(sg) + offset;
+               pxa_dma->sg_cpu[i].dcmd =
+                       DCMD_FLOWSRC | DCMD_BURST8 | DCMD_INCTRGADDR | xfer_len;
+ #ifdef DEBUG
+               if (!i)
+                       pxa_dma->sg_cpu[i].dcmd |= DCMD_STARTIRQEN;
+ #endif
+               pxa_dma->sg_cpu[i].ddadr =
+                       pxa_dma->sg_dma + (i + 1) * sizeof(struct pxa_dma_desc);
+               dev_vdbg(dev, "DMA: desc.%08x->@phys=0x%08x, len=%d\n",
+                        pxa_dma->sg_dma + i * sizeof(struct pxa_dma_desc),
+                        sg_dma_address(sg) + offset, xfer_len);
+               offset = 0;
+               if (size == 0)
+                       break;
+       }
+       pxa_dma->sg_cpu[sglen].ddadr = DDADR_STOP;
+       pxa_dma->sg_cpu[sglen].dcmd  = DCMD_FLOWSRC | DCMD_BURST8 | DCMD_ENDIRQEN;
+       /*
+        * Handle 1 special case :
+        *  - in 3 planes (YUV422P format), we might finish with xfer_len equal
+        *    to dma_len (end on PAGE boundary). In this case, the sg element
+        *    for next plane should be the next after the last used to store the
+        *    last scatter gather RAM page
+        */
+       if (xfer_len >= dma_len) {
+               *sg_first_ofs = xfer_len - dma_len;
+               *sg_first = sg_next(sg);
+       } else {
+               *sg_first_ofs = xfer_len;
+               *sg_first = sg;
+       }
+       return 0;
+ }
+ static void pxa_videobuf_set_actdma(struct pxa_camera_dev *pcdev,
+                                   struct pxa_buffer *buf)
+ {
+       buf->active_dma = DMA_Y;
+       if (pcdev->channels == 3)
+               buf->active_dma |= DMA_U | DMA_V;
+ }
+ /*
+  * Please check the DMA prepared buffer structure in :
+  *   Documentation/video4linux/pxa_camera.txt
+  * Please check also in pxa_camera_check_link_miss() to understand why DMA chain
+  * modification while DMA chain is running will work anyway.
+  */
+ static int pxa_videobuf_prepare(struct videobuf_queue *vq,
+               struct videobuf_buffer *vb, enum v4l2_field field)
+ {
+       struct soc_camera_device *icd = vq->priv_data;
+       struct soc_camera_host *ici = to_soc_camera_host(icd->parent);
+       struct pxa_camera_dev *pcdev = ici->priv;
+       struct device *dev = pcdev->soc_host.v4l2_dev.dev;
+       struct pxa_buffer *buf = container_of(vb, struct pxa_buffer, vb);
+       int ret;
+       int size_y, size_u = 0, size_v = 0;
+       dev_dbg(dev, "%s (vb=0x%p) 0x%08lx %d\n", __func__,
+               vb, vb->baddr, vb->bsize);
+       /* Added list head initialization on alloc */
+       WARN_ON(!list_empty(&vb->queue));
+ #ifdef DEBUG
+       /*
+        * This can be useful if you want to see if we actually fill
+        * the buffer with something
+        */
+       memset((void *)vb->baddr, 0xaa, vb->bsize);
+ #endif
+       BUG_ON(NULL == icd->current_fmt);
+       /*
+        * I think, in buf_prepare you only have to protect global data,
+        * the actual buffer is yours
+        */
+       buf->inwork = 1;
+       if (buf->code   != icd->current_fmt->code ||
+           vb->width   != icd->user_width ||
+           vb->height  != icd->user_height ||
+           vb->field   != field) {
+               buf->code       = icd->current_fmt->code;
+               vb->width       = icd->user_width;
+               vb->height      = icd->user_height;
+               vb->field       = field;
+               vb->state       = VIDEOBUF_NEEDS_INIT;
+       }
+       vb->size = icd->sizeimage;
+       if (0 != vb->baddr && vb->bsize < vb->size) {
+               ret = -EINVAL;
+               goto out;
+       }
+       if (vb->state == VIDEOBUF_NEEDS_INIT) {
+               int size = vb->size;
+               int next_ofs = 0;
+               struct videobuf_dmabuf *dma = videobuf_to_dma(vb);
+               struct scatterlist *sg;
+               ret = videobuf_iolock(vq, vb, NULL);
+               if (ret)
+                       goto fail;
+               if (pcdev->channels == 3) {
+                       size_y = size / 2;
+                       size_u = size_v = size / 4;
+               } else {
+                       size_y = size;
+               }
+               sg = dma->sglist;
+               /* init DMA for Y channel */
+               ret = pxa_init_dma_channel(pcdev, buf, dma, 0, CIBR0, size_y,
+                                          &sg, &next_ofs);
+               if (ret) {
+                       dev_err(dev, "DMA initialization for Y/RGB failed\n");
+                       goto fail;
+               }
+               /* init DMA for U channel */
+               if (size_u)
+                       ret = pxa_init_dma_channel(pcdev, buf, dma, 1, CIBR1,
+                                                  size_u, &sg, &next_ofs);
+               if (ret) {
+                       dev_err(dev, "DMA initialization for U failed\n");
+                       goto fail_u;
+               }
+               /* init DMA for V channel */
+               if (size_v)
+                       ret = pxa_init_dma_channel(pcdev, buf, dma, 2, CIBR2,
+                                                  size_v, &sg, &next_ofs);
+               if (ret) {
+                       dev_err(dev, "DMA initialization for V failed\n");
+                       goto fail_v;
+               }
+               vb->state = VIDEOBUF_PREPARED;
+       }
+       buf->inwork = 0;
+       pxa_videobuf_set_actdma(pcdev, buf);
+       return 0;
+ fail_v:
+       dma_free_coherent(dev, buf->dmas[1].sg_size,
+                         buf->dmas[1].sg_cpu, buf->dmas[1].sg_dma);
+ fail_u:
+       dma_free_coherent(dev, buf->dmas[0].sg_size,
+                         buf->dmas[0].sg_cpu, buf->dmas[0].sg_dma);
+ fail:
+       free_buffer(vq, buf);
+ out:
+       buf->inwork = 0;
+       return ret;
+ }
+ /**
+  * pxa_dma_start_channels - start DMA channel for active buffer
+  * @pcdev: pxa camera device
+  *
+  * Initialize DMA channels to the beginning of the active video buffer, and
+  * start these channels.
+  */
+ static void pxa_dma_start_channels(struct pxa_camera_dev *pcdev)
+ {
+       int i;
+       struct pxa_buffer *active;
+       active = pcdev->active;
+       for (i = 0; i < pcdev->channels; i++) {
+               dev_dbg(pcdev->soc_host.v4l2_dev.dev,
+                       "%s (channel=%d) ddadr=%08x\n", __func__,
+                       i, active->dmas[i].sg_dma);
+               DDADR(pcdev->dma_chans[i]) = active->dmas[i].sg_dma;
+               DCSR(pcdev->dma_chans[i]) = DCSR_RUN;
+       }
+ }
+ static void pxa_dma_stop_channels(struct pxa_camera_dev *pcdev)
+ {
+       int i;
+       for (i = 0; i < pcdev->channels; i++) {
+               dev_dbg(pcdev->soc_host.v4l2_dev.dev,
+                       "%s (channel=%d)\n", __func__, i);
+               DCSR(pcdev->dma_chans[i]) = 0;
+       }
+ }
+ static void pxa_dma_add_tail_buf(struct pxa_camera_dev *pcdev,
+                                struct pxa_buffer *buf)
+ {
+       int i;
+       struct pxa_dma_desc *buf_last_desc;
+       for (i = 0; i < pcdev->channels; i++) {
+               buf_last_desc = buf->dmas[i].sg_cpu + buf->dmas[i].sglen;
+               buf_last_desc->ddadr = DDADR_STOP;
+               if (pcdev->sg_tail[i])
+                       /* Link the new buffer to the old tail */
+                       pcdev->sg_tail[i]->ddadr = buf->dmas[i].sg_dma;
+               /* Update the channel tail */
+               pcdev->sg_tail[i] = buf_last_desc;
+       }
+ }
+ /**
+  * pxa_camera_start_capture - start video capturing
+  * @pcdev: camera device
+  *
+  * Launch capturing. DMA channels should not be active yet. They should get
+  * activated at the end of frame interrupt, to capture only whole frames, and
+  * never begin the capture of a partial frame.
+  */
+ static void pxa_camera_start_capture(struct pxa_camera_dev *pcdev)
+ {
+       unsigned long cicr0;
+       dev_dbg(pcdev->soc_host.v4l2_dev.dev, "%s\n", __func__);
+       /* Enable End-Of-Frame Interrupt */
+       cicr0 = __raw_readl(pcdev->base + CICR0) | CICR0_ENB;
+       cicr0 &= ~CICR0_EOFM;
+       __raw_writel(cicr0, pcdev->base + CICR0);
+ }
+ static void pxa_camera_stop_capture(struct pxa_camera_dev *pcdev)
+ {
+       unsigned long cicr0;
+       pxa_dma_stop_channels(pcdev);
+       cicr0 = __raw_readl(pcdev->base + CICR0) & ~CICR0_ENB;
+       __raw_writel(cicr0, pcdev->base + CICR0);
+       pcdev->active = NULL;
+       dev_dbg(pcdev->soc_host.v4l2_dev.dev, "%s\n", __func__);
+ }
+ /* Called under spinlock_irqsave(&pcdev->lock, ...) */
+ static void pxa_videobuf_queue(struct videobuf_queue *vq,
+                              struct videobuf_buffer *vb)
+ {
+       struct soc_camera_device *icd = vq->priv_data;
+       struct soc_camera_host *ici = to_soc_camera_host(icd->parent);
+       struct pxa_camera_dev *pcdev = ici->priv;
+       struct pxa_buffer *buf = container_of(vb, struct pxa_buffer, vb);
+       dev_dbg(icd->parent, "%s (vb=0x%p) 0x%08lx %d active=%p\n",
+               __func__, vb, vb->baddr, vb->bsize, pcdev->active);
+       list_add_tail(&vb->queue, &pcdev->capture);
+       vb->state = VIDEOBUF_ACTIVE;
+       pxa_dma_add_tail_buf(pcdev, buf);
+       if (!pcdev->active)
+               pxa_camera_start_capture(pcdev);
+ }
+ static void pxa_videobuf_release(struct videobuf_queue *vq,
+                                struct videobuf_buffer *vb)
+ {
+       struct pxa_buffer *buf = container_of(vb, struct pxa_buffer, vb);
+ #ifdef DEBUG
+       struct soc_camera_device *icd = vq->priv_data;
+       struct device *dev = icd->parent;
+       dev_dbg(dev, "%s (vb=0x%p) 0x%08lx %d\n", __func__,
+               vb, vb->baddr, vb->bsize);
+       switch (vb->state) {
+       case VIDEOBUF_ACTIVE:
+               dev_dbg(dev, "%s (active)\n", __func__);
+               break;
+       case VIDEOBUF_QUEUED:
+               dev_dbg(dev, "%s (queued)\n", __func__);
+               break;
+       case VIDEOBUF_PREPARED:
+               dev_dbg(dev, "%s (prepared)\n", __func__);
+               break;
+       default:
+               dev_dbg(dev, "%s (unknown)\n", __func__);
+               break;
+       }
+ #endif
+       free_buffer(vq, buf);
+ }
+ static void pxa_camera_wakeup(struct pxa_camera_dev *pcdev,
+                             struct videobuf_buffer *vb,
+                             struct pxa_buffer *buf)
+ {
+       int i;
+       /* _init is used to debug races, see comment in pxa_camera_reqbufs() */
+       list_del_init(&vb->queue);
+       vb->state = VIDEOBUF_DONE;
+       do_gettimeofday(&vb->ts);
+       vb->field_count++;
+       wake_up(&vb->done);
+       dev_dbg(pcdev->soc_host.v4l2_dev.dev, "%s dequeud buffer (vb=0x%p)\n",
+               __func__, vb);
+       if (list_empty(&pcdev->capture)) {
+               pxa_camera_stop_capture(pcdev);
+               for (i = 0; i < pcdev->channels; i++)
+                       pcdev->sg_tail[i] = NULL;
+               return;
+       }
+       pcdev->active = list_entry(pcdev->capture.next,
+                                  struct pxa_buffer, vb.queue);
+ }
+ /**
+  * pxa_camera_check_link_miss - check missed DMA linking
+  * @pcdev: camera device
+  *
+  * The DMA chaining is done with DMA running. This means a tiny temporal window
+  * remains, where a buffer is queued on the chain, while the chain is already
+  * stopped. This means the tailed buffer would never be transferred by DMA.
+  * This function restarts the capture for this corner case, where :
+  *  - DADR() == DADDR_STOP
+  *  - a videobuffer is queued on the pcdev->capture list
+  *
+  * Please check the "DMA hot chaining timeslice issue" in
+  *   Documentation/video4linux/pxa_camera.txt
+  *
+  * Context: should only be called within the dma irq handler
+  */
+ static void pxa_camera_check_link_miss(struct pxa_camera_dev *pcdev)
+ {
+       int i, is_dma_stopped = 1;
+       for (i = 0; i < pcdev->channels; i++)
+               if (DDADR(pcdev->dma_chans[i]) != DDADR_STOP)
+                       is_dma_stopped = 0;
+       dev_dbg(pcdev->soc_host.v4l2_dev.dev,
+               "%s : top queued buffer=%p, dma_stopped=%d\n",
+               __func__, pcdev->active, is_dma_stopped);
+       if (pcdev->active && is_dma_stopped)
+               pxa_camera_start_capture(pcdev);
+ }
+ static void pxa_camera_dma_irq(int channel, struct pxa_camera_dev *pcdev,
+                              enum pxa_camera_active_dma act_dma)
+ {
+       struct device *dev = pcdev->soc_host.v4l2_dev.dev;
+       struct pxa_buffer *buf;
+       unsigned long flags;
+       u32 status, camera_status, overrun;
+       struct videobuf_buffer *vb;
+       spin_lock_irqsave(&pcdev->lock, flags);
+       status = DCSR(channel);
+       DCSR(channel) = status;
+       camera_status = __raw_readl(pcdev->base + CISR);
+       overrun = CISR_IFO_0;
+       if (pcdev->channels == 3)
+               overrun |= CISR_IFO_1 | CISR_IFO_2;
+       if (status & DCSR_BUSERR) {
+               dev_err(dev, "DMA Bus Error IRQ!\n");
+               goto out;
+       }
+       if (!(status & (DCSR_ENDINTR | DCSR_STARTINTR))) {
+               dev_err(dev, "Unknown DMA IRQ source, status: 0x%08x\n",
+                       status);
+               goto out;
+       }
+       /*
+        * pcdev->active should not be NULL in DMA irq handler.
+        *
+        * But there is one corner case : if capture was stopped due to an
+        * overrun of channel 1, and at that same channel 2 was completed.
+        *
+        * When handling the overrun in DMA irq for channel 1, we'll stop the
+        * capture and restart it (and thus set pcdev->active to NULL). But the
+        * DMA irq handler will already be pending for channel 2. So on entering
+        * the DMA irq handler for channel 2 there will be no active buffer, yet
+        * that is normal.
+        */
+       if (!pcdev->active)
+               goto out;
+       vb = &pcdev->active->vb;
+       buf = container_of(vb, struct pxa_buffer, vb);
+       WARN_ON(buf->inwork || list_empty(&vb->queue));
+       dev_dbg(dev, "%s channel=%d %s%s(vb=0x%p) dma.desc=%x\n",
+               __func__, channel, status & DCSR_STARTINTR ? "SOF " : "",
+               status & DCSR_ENDINTR ? "EOF " : "", vb, DDADR(channel));
+       if (status & DCSR_ENDINTR) {
+               /*
+                * It's normal if the last frame creates an overrun, as there
+                * are no more DMA descriptors to fetch from QCI fifos
+                */
+               if (camera_status & overrun &&
+                   !list_is_last(pcdev->capture.next, &pcdev->capture)) {
+                       dev_dbg(dev, "FIFO overrun! CISR: %x\n",
+                               camera_status);
+                       pxa_camera_stop_capture(pcdev);
+                       pxa_camera_start_capture(pcdev);
+                       goto out;
+               }
+               buf->active_dma &= ~act_dma;
+               if (!buf->active_dma) {
+                       pxa_camera_wakeup(pcdev, vb, buf);
+                       pxa_camera_check_link_miss(pcdev);
+               }
+       }
+ out:
+       spin_unlock_irqrestore(&pcdev->lock, flags);
+ }
+ static void pxa_camera_dma_irq_y(int channel, void *data)
+ {
+       struct pxa_camera_dev *pcdev = data;
+       pxa_camera_dma_irq(channel, pcdev, DMA_Y);
+ }
+ static void pxa_camera_dma_irq_u(int channel, void *data)
+ {
+       struct pxa_camera_dev *pcdev = data;
+       pxa_camera_dma_irq(channel, pcdev, DMA_U);
+ }
+ static void pxa_camera_dma_irq_v(int channel, void *data)
+ {
+       struct pxa_camera_dev *pcdev = data;
+       pxa_camera_dma_irq(channel, pcdev, DMA_V);
+ }
+ static struct videobuf_queue_ops pxa_videobuf_ops = {
+       .buf_setup      = pxa_videobuf_setup,
+       .buf_prepare    = pxa_videobuf_prepare,
+       .buf_queue      = pxa_videobuf_queue,
+       .buf_release    = pxa_videobuf_release,
+ };
+ static void pxa_camera_init_videobuf(struct videobuf_queue *q,
+                             struct soc_camera_device *icd)
+ {
+       struct soc_camera_host *ici = to_soc_camera_host(icd->parent);
+       struct pxa_camera_dev *pcdev = ici->priv;
+       /*
+        * We must pass NULL as dev pointer, then all pci_* dma operations
+        * transform to normal dma_* ones.
+        */
+       videobuf_queue_sg_init(q, &pxa_videobuf_ops, NULL, &pcdev->lock,
+                               V4L2_BUF_TYPE_VIDEO_CAPTURE, V4L2_FIELD_NONE,
+                               sizeof(struct pxa_buffer), icd, &icd->video_lock);
+ }
+ static u32 mclk_get_divisor(struct platform_device *pdev,
+                           struct pxa_camera_dev *pcdev)
+ {
+       unsigned long mclk = pcdev->mclk;
+       struct device *dev = &pdev->dev;
+       u32 div;
+       unsigned long lcdclk;
+       lcdclk = clk_get_rate(pcdev->clk);
+       pcdev->ciclk = lcdclk;
+       /* mclk <= ciclk / 4 (27.4.2) */
+       if (mclk > lcdclk / 4) {
+               mclk = lcdclk / 4;
+               dev_warn(dev, "Limiting master clock to %lu\n", mclk);
+       }
+       /* We verify mclk != 0, so if anyone breaks it, here comes their Oops */
+       div = (lcdclk + 2 * mclk - 1) / (2 * mclk) - 1;
+       /* If we're not supplying MCLK, leave it at 0 */
+       if (pcdev->platform_flags & PXA_CAMERA_MCLK_EN)
+               pcdev->mclk = lcdclk / (2 * (div + 1));
+       dev_dbg(dev, "LCD clock %luHz, target freq %luHz, divisor %u\n",
+               lcdclk, mclk, div);
+       return div;
+ }
+ static void recalculate_fifo_timeout(struct pxa_camera_dev *pcdev,
+                                    unsigned long pclk)
+ {
+       /* We want a timeout > 1 pixel time, not ">=" */
+       u32 ciclk_per_pixel = pcdev->ciclk / pclk + 1;
+       __raw_writel(ciclk_per_pixel, pcdev->base + CITOR);
+ }
+ static void pxa_camera_activate(struct pxa_camera_dev *pcdev)
+ {
+       u32 cicr4 = 0;
+       /* disable all interrupts */
+       __raw_writel(0x3ff, pcdev->base + CICR0);
+       if (pcdev->platform_flags & PXA_CAMERA_PCLK_EN)
+               cicr4 |= CICR4_PCLK_EN;
+       if (pcdev->platform_flags & PXA_CAMERA_MCLK_EN)
+               cicr4 |= CICR4_MCLK_EN;
+       if (pcdev->platform_flags & PXA_CAMERA_PCP)
+               cicr4 |= CICR4_PCP;
+       if (pcdev->platform_flags & PXA_CAMERA_HSP)
+               cicr4 |= CICR4_HSP;
+       if (pcdev->platform_flags & PXA_CAMERA_VSP)
+               cicr4 |= CICR4_VSP;
+       __raw_writel(pcdev->mclk_divisor | cicr4, pcdev->base + CICR4);
+       if (pcdev->platform_flags & PXA_CAMERA_MCLK_EN)
+               /* Initialise the timeout under the assumption pclk = mclk */
+               recalculate_fifo_timeout(pcdev, pcdev->mclk);
+       else
+               /* "Safe default" - 13MHz */
+               recalculate_fifo_timeout(pcdev, 13000000);
+       clk_prepare_enable(pcdev->clk);
+ }
+ static void pxa_camera_deactivate(struct pxa_camera_dev *pcdev)
+ {
+       clk_disable_unprepare(pcdev->clk);
+ }
+ static irqreturn_t pxa_camera_irq(int irq, void *data)
+ {
+       struct pxa_camera_dev *pcdev = data;
+       unsigned long status, cifr, cicr0;
+       struct pxa_buffer *buf;
+       struct videobuf_buffer *vb;
+       status = __raw_readl(pcdev->base + CISR);
+       dev_dbg(pcdev->soc_host.v4l2_dev.dev,
+               "Camera interrupt status 0x%lx\n", status);
+       if (!status)
+               return IRQ_NONE;
+       __raw_writel(status, pcdev->base + CISR);
+       if (status & CISR_EOF) {
+               /* Reset the FIFOs */
+               cifr = __raw_readl(pcdev->base + CIFR) | CIFR_RESET_F;
+               __raw_writel(cifr, pcdev->base + CIFR);
+               pcdev->active = list_first_entry(&pcdev->capture,
+                                          struct pxa_buffer, vb.queue);
+               vb = &pcdev->active->vb;
+               buf = container_of(vb, struct pxa_buffer, vb);
+               pxa_videobuf_set_actdma(pcdev, buf);
+               pxa_dma_start_channels(pcdev);
+               cicr0 = __raw_readl(pcdev->base + CICR0) | CICR0_EOFM;
+               __raw_writel(cicr0, pcdev->base + CICR0);
+       }
+       return IRQ_HANDLED;
+ }
+ /*
+  * The following two functions absolutely depend on the fact, that
+  * there can be only one camera on PXA quick capture interface
+  * Called with .video_lock held
+  */
+ static int pxa_camera_add_device(struct soc_camera_device *icd)
+ {
+       struct soc_camera_host *ici = to_soc_camera_host(icd->parent);
+       struct pxa_camera_dev *pcdev = ici->priv;
+       if (pcdev->icd)
+               return -EBUSY;
+       pxa_camera_activate(pcdev);
+       pcdev->icd = icd;
+       dev_info(icd->parent, "PXA Camera driver attached to camera %d\n",
+                icd->devnum);
+       return 0;
+ }
+ /* Called with .video_lock held */
+ static void pxa_camera_remove_device(struct soc_camera_device *icd)
+ {
+       struct soc_camera_host *ici = to_soc_camera_host(icd->parent);
+       struct pxa_camera_dev *pcdev = ici->priv;
+       BUG_ON(icd != pcdev->icd);
+       dev_info(icd->parent, "PXA Camera driver detached from camera %d\n",
+                icd->devnum);
+       /* disable capture, disable interrupts */
+       __raw_writel(0x3ff, pcdev->base + CICR0);
+       /* Stop DMA engine */
+       DCSR(pcdev->dma_chans[0]) = 0;
+       DCSR(pcdev->dma_chans[1]) = 0;
+       DCSR(pcdev->dma_chans[2]) = 0;
+       pxa_camera_deactivate(pcdev);
+       pcdev->icd = NULL;
+ }
+ static int test_platform_param(struct pxa_camera_dev *pcdev,
+                              unsigned char buswidth, unsigned long *flags)
+ {
+       /*
+        * Platform specified synchronization and pixel clock polarities are
+        * only a recommendation and are only used during probing. The PXA270
+        * quick capture interface supports both.
+        */
+       *flags = (pcdev->platform_flags & PXA_CAMERA_MASTER ?
+                 V4L2_MBUS_MASTER : V4L2_MBUS_SLAVE) |
+               V4L2_MBUS_HSYNC_ACTIVE_HIGH |
+               V4L2_MBUS_HSYNC_ACTIVE_LOW |
+               V4L2_MBUS_VSYNC_ACTIVE_HIGH |
+               V4L2_MBUS_VSYNC_ACTIVE_LOW |
+               V4L2_MBUS_DATA_ACTIVE_HIGH |
+               V4L2_MBUS_PCLK_SAMPLE_RISING |
+               V4L2_MBUS_PCLK_SAMPLE_FALLING;
+       /* If requested data width is supported by the platform, use it */
+       if ((1 << (buswidth - 1)) & pcdev->width_flags)
+               return 0;
+       return -EINVAL;
+ }
+ static void pxa_camera_setup_cicr(struct soc_camera_device *icd,
+                                 unsigned long flags, __u32 pixfmt)
+ {
+       struct soc_camera_host *ici = to_soc_camera_host(icd->parent);
+       struct pxa_camera_dev *pcdev = ici->priv;
+       struct v4l2_subdev *sd = soc_camera_to_subdev(icd);
+       unsigned long dw, bpp;
+       u32 cicr0, cicr1, cicr2, cicr3, cicr4 = 0, y_skip_top;
+       int ret = v4l2_subdev_call(sd, sensor, g_skip_top_lines, &y_skip_top);
+       if (ret < 0)
+               y_skip_top = 0;
+       /*
+        * Datawidth is now guaranteed to be equal to one of the three values.
+        * We fix bit-per-pixel equal to data-width...
+        */
+       switch (icd->current_fmt->host_fmt->bits_per_sample) {
+       case 10:
+               dw = 4;
+               bpp = 0x40;
+               break;
+       case 9:
+               dw = 3;
+               bpp = 0x20;
+               break;
+       default:
+               /*
+                * Actually it can only be 8 now,
+                * default is just to silence compiler warnings
+                */
+       case 8:
+               dw = 2;
+               bpp = 0;
+       }
+       if (pcdev->platform_flags & PXA_CAMERA_PCLK_EN)
+               cicr4 |= CICR4_PCLK_EN;
+       if (pcdev->platform_flags & PXA_CAMERA_MCLK_EN)
+               cicr4 |= CICR4_MCLK_EN;
+       if (flags & V4L2_MBUS_PCLK_SAMPLE_FALLING)
+               cicr4 |= CICR4_PCP;
+       if (flags & V4L2_MBUS_HSYNC_ACTIVE_LOW)
+               cicr4 |= CICR4_HSP;
+       if (flags & V4L2_MBUS_VSYNC_ACTIVE_LOW)
+               cicr4 |= CICR4_VSP;
+       cicr0 = __raw_readl(pcdev->base + CICR0);
+       if (cicr0 & CICR0_ENB)
+               __raw_writel(cicr0 & ~CICR0_ENB, pcdev->base + CICR0);
+       cicr1 = CICR1_PPL_VAL(icd->user_width - 1) | bpp | dw;
+       switch (pixfmt) {
+       case V4L2_PIX_FMT_YUV422P:
+               pcdev->channels = 3;
+               cicr1 |= CICR1_YCBCR_F;
+               /*
+                * Normally, pxa bus wants as input UYVY format. We allow all
+                * reorderings of the YUV422 format, as no processing is done,
+                * and the YUV stream is just passed through without any
+                * transformation. Note that UYVY is the only format that
+                * should be used if pxa framebuffer Overlay2 is used.
+                */
+       case V4L2_PIX_FMT_UYVY:
+       case V4L2_PIX_FMT_VYUY:
+       case V4L2_PIX_FMT_YUYV:
+       case V4L2_PIX_FMT_YVYU:
+               cicr1 |= CICR1_COLOR_SP_VAL(2);
+               break;
+       case V4L2_PIX_FMT_RGB555:
+               cicr1 |= CICR1_RGB_BPP_VAL(1) | CICR1_RGBT_CONV_VAL(2) |
+                       CICR1_TBIT | CICR1_COLOR_SP_VAL(1);
+               break;
+       case V4L2_PIX_FMT_RGB565:
+               cicr1 |= CICR1_COLOR_SP_VAL(1) | CICR1_RGB_BPP_VAL(2);
+               break;
+       }
+       cicr2 = 0;
+       cicr3 = CICR3_LPF_VAL(icd->user_height - 1) |
+               CICR3_BFW_VAL(min((u32)255, y_skip_top));
+       cicr4 |= pcdev->mclk_divisor;
+       __raw_writel(cicr1, pcdev->base + CICR1);
+       __raw_writel(cicr2, pcdev->base + CICR2);
+       __raw_writel(cicr3, pcdev->base + CICR3);
+       __raw_writel(cicr4, pcdev->base + CICR4);
+       /* CIF interrupts are not used, only DMA */
+       cicr0 = (cicr0 & CICR0_ENB) | (pcdev->platform_flags & PXA_CAMERA_MASTER ?
+               CICR0_SIM_MP : (CICR0_SL_CAP_EN | CICR0_SIM_SP));
+       cicr0 |= CICR0_DMAEN | CICR0_IRQ_MASK;
+       __raw_writel(cicr0, pcdev->base + CICR0);
+ }
+ static int pxa_camera_set_bus_param(struct soc_camera_device *icd)
+ {
+       struct v4l2_subdev *sd = soc_camera_to_subdev(icd);
+       struct soc_camera_host *ici = to_soc_camera_host(icd->parent);
+       struct pxa_camera_dev *pcdev = ici->priv;
+       struct v4l2_mbus_config cfg = {.type = V4L2_MBUS_PARALLEL,};
+       u32 pixfmt = icd->current_fmt->host_fmt->fourcc;
+       unsigned long bus_flags, common_flags;
+       int ret;
+       struct pxa_cam *cam = icd->host_priv;
+       ret = test_platform_param(pcdev, icd->current_fmt->host_fmt->bits_per_sample,
+                                 &bus_flags);
+       if (ret < 0)
+               return ret;
+       ret = v4l2_subdev_call(sd, video, g_mbus_config, &cfg);
+       if (!ret) {
+               common_flags = soc_mbus_config_compatible(&cfg,
+                                                         bus_flags);
+               if (!common_flags) {
+                       dev_warn(icd->parent,
+                                "Flags incompatible: camera 0x%x, host 0x%lx\n",
+                                cfg.flags, bus_flags);
+                       return -EINVAL;
+               }
+       } else if (ret != -ENOIOCTLCMD) {
+               return ret;
+       } else {
+               common_flags = bus_flags;
+       }
+       pcdev->channels = 1;
+       /* Make choises, based on platform preferences */
+       if ((common_flags & V4L2_MBUS_HSYNC_ACTIVE_HIGH) &&
+           (common_flags & V4L2_MBUS_HSYNC_ACTIVE_LOW)) {
+               if (pcdev->platform_flags & PXA_CAMERA_HSP)
+                       common_flags &= ~V4L2_MBUS_HSYNC_ACTIVE_HIGH;
+               else
+                       common_flags &= ~V4L2_MBUS_HSYNC_ACTIVE_LOW;
+       }
+       if ((common_flags & V4L2_MBUS_VSYNC_ACTIVE_HIGH) &&
+           (common_flags & V4L2_MBUS_VSYNC_ACTIVE_LOW)) {
+               if (pcdev->platform_flags & PXA_CAMERA_VSP)
+                       common_flags &= ~V4L2_MBUS_VSYNC_ACTIVE_HIGH;
+               else
+                       common_flags &= ~V4L2_MBUS_VSYNC_ACTIVE_LOW;
+       }
+       if ((common_flags & V4L2_MBUS_PCLK_SAMPLE_RISING) &&
+           (common_flags & V4L2_MBUS_PCLK_SAMPLE_FALLING)) {
+               if (pcdev->platform_flags & PXA_CAMERA_PCP)
+                       common_flags &= ~V4L2_MBUS_PCLK_SAMPLE_RISING;
+               else
+                       common_flags &= ~V4L2_MBUS_PCLK_SAMPLE_FALLING;
+       }
+       cfg.flags = common_flags;
+       ret = v4l2_subdev_call(sd, video, s_mbus_config, &cfg);
+       if (ret < 0 && ret != -ENOIOCTLCMD) {
+               dev_dbg(icd->parent, "camera s_mbus_config(0x%lx) returned %d\n",
+                       common_flags, ret);
+               return ret;
+       }
+       cam->flags = common_flags;
+       pxa_camera_setup_cicr(icd, common_flags, pixfmt);
+       return 0;
+ }
+ static int pxa_camera_try_bus_param(struct soc_camera_device *icd,
+                                   unsigned char buswidth)
+ {
+       struct v4l2_subdev *sd = soc_camera_to_subdev(icd);
+       struct soc_camera_host *ici = to_soc_camera_host(icd->parent);
+       struct pxa_camera_dev *pcdev = ici->priv;
+       struct v4l2_mbus_config cfg = {.type = V4L2_MBUS_PARALLEL,};
+       unsigned long bus_flags, common_flags;
+       int ret = test_platform_param(pcdev, buswidth, &bus_flags);
+       if (ret < 0)
+               return ret;
+       ret = v4l2_subdev_call(sd, video, g_mbus_config, &cfg);
+       if (!ret) {
+               common_flags = soc_mbus_config_compatible(&cfg,
+                                                         bus_flags);
+               if (!common_flags) {
+                       dev_warn(icd->parent,
+                                "Flags incompatible: camera 0x%x, host 0x%lx\n",
+                                cfg.flags, bus_flags);
+                       return -EINVAL;
+               }
+       } else if (ret == -ENOIOCTLCMD) {
+               ret = 0;
+       }
+       return ret;
+ }
+ static const struct soc_mbus_pixelfmt pxa_camera_formats[] = {
+       {
+               .fourcc                 = V4L2_PIX_FMT_YUV422P,
+               .name                   = "Planar YUV422 16 bit",
+               .bits_per_sample        = 8,
+               .packing                = SOC_MBUS_PACKING_2X8_PADHI,
+               .order                  = SOC_MBUS_ORDER_LE,
+               .layout                 = SOC_MBUS_LAYOUT_PLANAR_2Y_U_V,
+       },
+ };
+ /* This will be corrected as we get more formats */
+ static bool pxa_camera_packing_supported(const struct soc_mbus_pixelfmt *fmt)
+ {
+       return  fmt->packing == SOC_MBUS_PACKING_NONE ||
+               (fmt->bits_per_sample == 8 &&
+                fmt->packing == SOC_MBUS_PACKING_2X8_PADHI) ||
+               (fmt->bits_per_sample > 8 &&
+                fmt->packing == SOC_MBUS_PACKING_EXTEND16);
+ }
+ static int pxa_camera_get_formats(struct soc_camera_device *icd, unsigned int idx,
+                                 struct soc_camera_format_xlate *xlate)
+ {
+       struct v4l2_subdev *sd = soc_camera_to_subdev(icd);
+       struct device *dev = icd->parent;
+       int formats = 0, ret;
+       struct pxa_cam *cam;
+       enum v4l2_mbus_pixelcode code;
+       const struct soc_mbus_pixelfmt *fmt;
+       ret = v4l2_subdev_call(sd, video, enum_mbus_fmt, idx, &code);
+       if (ret < 0)
+               /* No more formats */
+               return 0;
+       fmt = soc_mbus_get_fmtdesc(code);
+       if (!fmt) {
+               dev_err(dev, "Invalid format code #%u: %d\n", idx, code);
+               return 0;
+       }
+       /* This also checks support for the requested bits-per-sample */
+       ret = pxa_camera_try_bus_param(icd, fmt->bits_per_sample);
+       if (ret < 0)
+               return 0;
+       if (!icd->host_priv) {
+               cam = kzalloc(sizeof(*cam), GFP_KERNEL);
+               if (!cam)
+                       return -ENOMEM;
+               icd->host_priv = cam;
+       } else {
+               cam = icd->host_priv;
+       }
+       switch (code) {
+       case V4L2_MBUS_FMT_UYVY8_2X8:
+               formats++;
+               if (xlate) {
+                       xlate->host_fmt = &pxa_camera_formats[0];
+                       xlate->code     = code;
+                       xlate++;
+                       dev_dbg(dev, "Providing format %s using code %d\n",
+                               pxa_camera_formats[0].name, code);
+               }
+       case V4L2_MBUS_FMT_VYUY8_2X8:
+       case V4L2_MBUS_FMT_YUYV8_2X8:
+       case V4L2_MBUS_FMT_YVYU8_2X8:
+       case V4L2_MBUS_FMT_RGB565_2X8_LE:
+       case V4L2_MBUS_FMT_RGB555_2X8_PADHI_LE:
+               if (xlate)
+                       dev_dbg(dev, "Providing format %s packed\n",
+                               fmt->name);
+               break;
+       default:
+               if (!pxa_camera_packing_supported(fmt))
+                       return 0;
+               if (xlate)
+                       dev_dbg(dev,
+                               "Providing format %s in pass-through mode\n",
+                               fmt->name);
+       }
+       /* Generic pass-through */
+       formats++;
+       if (xlate) {
+               xlate->host_fmt = fmt;
+               xlate->code     = code;
+               xlate++;
+       }
+       return formats;
+ }
+ static void pxa_camera_put_formats(struct soc_camera_device *icd)
+ {
+       kfree(icd->host_priv);
+       icd->host_priv = NULL;
+ }
+ static int pxa_camera_check_frame(u32 width, u32 height)
+ {
+       /* limit to pxa hardware capabilities */
+       return height < 32 || height > 2048 || width < 48 || width > 2048 ||
+               (width & 0x01);
+ }
+ static int pxa_camera_set_crop(struct soc_camera_device *icd,
+                              struct v4l2_crop *a)
+ {
+       struct v4l2_rect *rect = &a->c;
+       struct device *dev = icd->parent;
+       struct soc_camera_host *ici = to_soc_camera_host(dev);
+       struct pxa_camera_dev *pcdev = ici->priv;
+       struct v4l2_subdev *sd = soc_camera_to_subdev(icd);
+       struct soc_camera_sense sense = {
+               .master_clock = pcdev->mclk,
+               .pixel_clock_max = pcdev->ciclk / 4,
+       };
+       struct v4l2_mbus_framefmt mf;
+       struct pxa_cam *cam = icd->host_priv;
+       u32 fourcc = icd->current_fmt->host_fmt->fourcc;
+       int ret;
+       /* If PCLK is used to latch data from the sensor, check sense */
+       if (pcdev->platform_flags & PXA_CAMERA_PCLK_EN)
+               icd->sense = &sense;
+       ret = v4l2_subdev_call(sd, video, s_crop, a);
+       icd->sense = NULL;
+       if (ret < 0) {
+               dev_warn(dev, "Failed to crop to %ux%u@%u:%u\n",
+                        rect->width, rect->height, rect->left, rect->top);
+               return ret;
+       }
+       ret = v4l2_subdev_call(sd, video, g_mbus_fmt, &mf);
+       if (ret < 0)
+               return ret;
+       if (pxa_camera_check_frame(mf.width, mf.height)) {
+               /*
+                * Camera cropping produced a frame beyond our capabilities.
+                * FIXME: just extract a subframe, that we can process.
+                */
+               v4l_bound_align_image(&mf.width, 48, 2048, 1,
+                       &mf.height, 32, 2048, 0,
+                       fourcc == V4L2_PIX_FMT_YUV422P ? 4 : 0);
+               ret = v4l2_subdev_call(sd, video, s_mbus_fmt, &mf);
+               if (ret < 0)
+                       return ret;
+               if (pxa_camera_check_frame(mf.width, mf.height)) {
+                       dev_warn(icd->parent,
+                                "Inconsistent state. Use S_FMT to repair\n");
+                       return -EINVAL;
+               }
+       }
+       if (sense.flags & SOCAM_SENSE_PCLK_CHANGED) {
+               if (sense.pixel_clock > sense.pixel_clock_max) {
+                       dev_err(dev,
+                               "pixel clock %lu set by the camera too high!",
+                               sense.pixel_clock);
+                       return -EIO;
+               }
+               recalculate_fifo_timeout(pcdev, sense.pixel_clock);
+       }
+       icd->user_width         = mf.width;
+       icd->user_height        = mf.height;
+       pxa_camera_setup_cicr(icd, cam->flags, fourcc);
+       return ret;
+ }
+ static int pxa_camera_set_fmt(struct soc_camera_device *icd,
+                             struct v4l2_format *f)
+ {
+       struct device *dev = icd->parent;
+       struct soc_camera_host *ici = to_soc_camera_host(dev);
+       struct pxa_camera_dev *pcdev = ici->priv;
+       struct v4l2_subdev *sd = soc_camera_to_subdev(icd);
+       const struct soc_camera_format_xlate *xlate = NULL;
+       struct soc_camera_sense sense = {
+               .master_clock = pcdev->mclk,
+               .pixel_clock_max = pcdev->ciclk / 4,
+       };
+       struct v4l2_pix_format *pix = &f->fmt.pix;
+       struct v4l2_mbus_framefmt mf;
+       int ret;
+       xlate = soc_camera_xlate_by_fourcc(icd, pix->pixelformat);
+       if (!xlate) {
+               dev_warn(dev, "Format %x not found\n", pix->pixelformat);
+               return -EINVAL;
+       }
+       /* If PCLK is used to latch data from the sensor, check sense */
+       if (pcdev->platform_flags & PXA_CAMERA_PCLK_EN)
+               /* The caller holds a mutex. */
+               icd->sense = &sense;
+       mf.width        = pix->width;
+       mf.height       = pix->height;
+       mf.field        = pix->field;
+       mf.colorspace   = pix->colorspace;
+       mf.code         = xlate->code;
+       ret = v4l2_subdev_call(sd, video, s_mbus_fmt, &mf);
+       if (mf.code != xlate->code)
+               return -EINVAL;
+       icd->sense = NULL;
+       if (ret < 0) {
+               dev_warn(dev, "Failed to configure for format %x\n",
+                        pix->pixelformat);
+       } else if (pxa_camera_check_frame(mf.width, mf.height)) {
+               dev_warn(dev,
+                        "Camera driver produced an unsupported frame %dx%d\n",
+                        mf.width, mf.height);
+               ret = -EINVAL;
+       } else if (sense.flags & SOCAM_SENSE_PCLK_CHANGED) {
+               if (sense.pixel_clock > sense.pixel_clock_max) {
+                       dev_err(dev,
+                               "pixel clock %lu set by the camera too high!",
+                               sense.pixel_clock);
+                       return -EIO;
+               }
+               recalculate_fifo_timeout(pcdev, sense.pixel_clock);
+       }
+       if (ret < 0)
+               return ret;
+       pix->width              = mf.width;
+       pix->height             = mf.height;
+       pix->field              = mf.field;
+       pix->colorspace         = mf.colorspace;
+       icd->current_fmt        = xlate;
+       return ret;
+ }
+ static int pxa_camera_try_fmt(struct soc_camera_device *icd,
+                             struct v4l2_format *f)
+ {
+       struct v4l2_subdev *sd = soc_camera_to_subdev(icd);
+       const struct soc_camera_format_xlate *xlate;
+       struct v4l2_pix_format *pix = &f->fmt.pix;
+       struct v4l2_mbus_framefmt mf;
+       __u32 pixfmt = pix->pixelformat;
+       int ret;
+       xlate = soc_camera_xlate_by_fourcc(icd, pixfmt);
+       if (!xlate) {
+               dev_warn(icd->parent, "Format %x not found\n", pixfmt);
+               return -EINVAL;
+       }
+       /*
+        * Limit to pxa hardware capabilities.  YUV422P planar format requires
+        * images size to be a multiple of 16 bytes.  If not, zeros will be
+        * inserted between Y and U planes, and U and V planes, which violates
+        * the YUV422P standard.
+        */
+       v4l_bound_align_image(&pix->width, 48, 2048, 1,
+                             &pix->height, 32, 2048, 0,
+                             pixfmt == V4L2_PIX_FMT_YUV422P ? 4 : 0);
+       /* limit to sensor capabilities */
+       mf.width        = pix->width;
+       mf.height       = pix->height;
+       /* Only progressive video supported so far */
+       mf.field        = V4L2_FIELD_NONE;
+       mf.colorspace   = pix->colorspace;
+       mf.code         = xlate->code;
+       ret = v4l2_subdev_call(sd, video, try_mbus_fmt, &mf);
+       if (ret < 0)
+               return ret;
+       pix->width      = mf.width;
+       pix->height     = mf.height;
+       pix->colorspace = mf.colorspace;
+       switch (mf.field) {
+       case V4L2_FIELD_ANY:
+       case V4L2_FIELD_NONE:
+               pix->field      = V4L2_FIELD_NONE;
+               break;
+       default:
+               /* TODO: support interlaced at least in pass-through mode */
+               dev_err(icd->parent, "Field type %d unsupported.\n",
+                       mf.field);
+               return -EINVAL;
+       }
+       return ret;
+ }
+ static int pxa_camera_reqbufs(struct soc_camera_device *icd,
+                             struct v4l2_requestbuffers *p)
+ {
+       int i;
+       /*
+        * This is for locking debugging only. I removed spinlocks and now I
+        * check whether .prepare is ever called on a linked buffer, or whether
+        * a dma IRQ can occur for an in-work or unlinked buffer. Until now
+        * it hadn't triggered
+        */
+       for (i = 0; i < p->count; i++) {
+               struct pxa_buffer *buf = container_of(icd->vb_vidq.bufs[i],
+                                                     struct pxa_buffer, vb);
+               buf->inwork = 0;
+               INIT_LIST_HEAD(&buf->vb.queue);
+       }
+       return 0;
+ }
+ static unsigned int pxa_camera_poll(struct file *file, poll_table *pt)
+ {
+       struct soc_camera_device *icd = file->private_data;
+       struct pxa_buffer *buf;
+       buf = list_entry(icd->vb_vidq.stream.next, struct pxa_buffer,
+                        vb.stream);
+       poll_wait(file, &buf->vb.done, pt);
+       if (buf->vb.state == VIDEOBUF_DONE ||
+           buf->vb.state == VIDEOBUF_ERROR)
+               return POLLIN|POLLRDNORM;
+       return 0;
+ }
+ static int pxa_camera_querycap(struct soc_camera_host *ici,
+                              struct v4l2_capability *cap)
+ {
+       /* cap->name is set by the firendly caller:-> */
+       strlcpy(cap->card, pxa_cam_driver_description, sizeof(cap->card));
+       cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;
+       return 0;
+ }
+ static int pxa_camera_suspend(struct device *dev)
+ {
+       struct soc_camera_host *ici = to_soc_camera_host(dev);
+       struct pxa_camera_dev *pcdev = ici->priv;
+       int i = 0, ret = 0;
+       pcdev->save_cicr[i++] = __raw_readl(pcdev->base + CICR0);
+       pcdev->save_cicr[i++] = __raw_readl(pcdev->base + CICR1);
+       pcdev->save_cicr[i++] = __raw_readl(pcdev->base + CICR2);
+       pcdev->save_cicr[i++] = __raw_readl(pcdev->base + CICR3);
+       pcdev->save_cicr[i++] = __raw_readl(pcdev->base + CICR4);
+       if (pcdev->icd) {
+               struct v4l2_subdev *sd = soc_camera_to_subdev(pcdev->icd);
+               ret = v4l2_subdev_call(sd, core, s_power, 0);
+               if (ret == -ENOIOCTLCMD)
+                       ret = 0;
+       }
+       return ret;
+ }
+ static int pxa_camera_resume(struct device *dev)
+ {
+       struct soc_camera_host *ici = to_soc_camera_host(dev);
+       struct pxa_camera_dev *pcdev = ici->priv;
+       int i = 0, ret = 0;
+       DRCMR(68) = pcdev->dma_chans[0] | DRCMR_MAPVLD;
+       DRCMR(69) = pcdev->dma_chans[1] | DRCMR_MAPVLD;
+       DRCMR(70) = pcdev->dma_chans[2] | DRCMR_MAPVLD;
+       __raw_writel(pcdev->save_cicr[i++] & ~CICR0_ENB, pcdev->base + CICR0);
+       __raw_writel(pcdev->save_cicr[i++], pcdev->base + CICR1);
+       __raw_writel(pcdev->save_cicr[i++], pcdev->base + CICR2);
+       __raw_writel(pcdev->save_cicr[i++], pcdev->base + CICR3);
+       __raw_writel(pcdev->save_cicr[i++], pcdev->base + CICR4);
+       if (pcdev->icd) {
+               struct v4l2_subdev *sd = soc_camera_to_subdev(pcdev->icd);
+               ret = v4l2_subdev_call(sd, core, s_power, 1);
+               if (ret == -ENOIOCTLCMD)
+                       ret = 0;
+       }
+       /* Restart frame capture if active buffer exists */
+       if (!ret && pcdev->active)
+               pxa_camera_start_capture(pcdev);
+       return ret;
+ }
+ static struct soc_camera_host_ops pxa_soc_camera_host_ops = {
+       .owner          = THIS_MODULE,
+       .add            = pxa_camera_add_device,
+       .remove         = pxa_camera_remove_device,
+       .set_crop       = pxa_camera_set_crop,
+       .get_formats    = pxa_camera_get_formats,
+       .put_formats    = pxa_camera_put_formats,
+       .set_fmt        = pxa_camera_set_fmt,
+       .try_fmt        = pxa_camera_try_fmt,
+       .init_videobuf  = pxa_camera_init_videobuf,
+       .reqbufs        = pxa_camera_reqbufs,
+       .poll           = pxa_camera_poll,
+       .querycap       = pxa_camera_querycap,
+       .set_bus_param  = pxa_camera_set_bus_param,
+ };
+ static int __devinit pxa_camera_probe(struct platform_device *pdev)
+ {
+       struct pxa_camera_dev *pcdev;
+       struct resource *res;
+       void __iomem *base;
+       int irq;
+       int err = 0;
+       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       irq = platform_get_irq(pdev, 0);
+       if (!res || irq < 0) {
+               err = -ENODEV;
+               goto exit;
+       }
+       pcdev = kzalloc(sizeof(*pcdev), GFP_KERNEL);
+       if (!pcdev) {
+               dev_err(&pdev->dev, "Could not allocate pcdev\n");
+               err = -ENOMEM;
+               goto exit;
+       }
+       pcdev->clk = clk_get(&pdev->dev, NULL);
+       if (IS_ERR(pcdev->clk)) {
+               err = PTR_ERR(pcdev->clk);
+               goto exit_kfree;
+       }
+       pcdev->res = res;
+       pcdev->pdata = pdev->dev.platform_data;
+       pcdev->platform_flags = pcdev->pdata->flags;
+       if (!(pcdev->platform_flags & (PXA_CAMERA_DATAWIDTH_8 |
+                       PXA_CAMERA_DATAWIDTH_9 | PXA_CAMERA_DATAWIDTH_10))) {
+               /*
+                * Platform hasn't set available data widths. This is bad.
+                * Warn and use a default.
+                */
+               dev_warn(&pdev->dev, "WARNING! Platform hasn't set available "
+                        "data widths, using default 10 bit\n");
+               pcdev->platform_flags |= PXA_CAMERA_DATAWIDTH_10;
+       }
+       if (pcdev->platform_flags & PXA_CAMERA_DATAWIDTH_8)
+               pcdev->width_flags = 1 << 7;
+       if (pcdev->platform_flags & PXA_CAMERA_DATAWIDTH_9)
+               pcdev->width_flags |= 1 << 8;
+       if (pcdev->platform_flags & PXA_CAMERA_DATAWIDTH_10)
+               pcdev->width_flags |= 1 << 9;
+       pcdev->mclk = pcdev->pdata->mclk_10khz * 10000;
+       if (!pcdev->mclk) {
+               dev_warn(&pdev->dev,
+                        "mclk == 0! Please, fix your platform data. "
+                        "Using default 20MHz\n");
+               pcdev->mclk = 20000000;
+       }
+       pcdev->mclk_divisor = mclk_get_divisor(pdev, pcdev);
+       INIT_LIST_HEAD(&pcdev->capture);
+       spin_lock_init(&pcdev->lock);
+       /*
+        * Request the regions.
+        */
+       if (!request_mem_region(res->start, resource_size(res),
+                               PXA_CAM_DRV_NAME)) {
+               err = -EBUSY;
+               goto exit_clk;
+       }
+       base = ioremap(res->start, resource_size(res));
+       if (!base) {
+               err = -ENOMEM;
+               goto exit_release;
+       }
+       pcdev->irq = irq;
+       pcdev->base = base;
+       /* request dma */
+       err = pxa_request_dma("CI_Y", DMA_PRIO_HIGH,
+                             pxa_camera_dma_irq_y, pcdev);
+       if (err < 0) {
+               dev_err(&pdev->dev, "Can't request DMA for Y\n");
+               goto exit_iounmap;
+       }
+       pcdev->dma_chans[0] = err;
+       dev_dbg(&pdev->dev, "got DMA channel %d\n", pcdev->dma_chans[0]);
+       err = pxa_request_dma("CI_U", DMA_PRIO_HIGH,
+                             pxa_camera_dma_irq_u, pcdev);
+       if (err < 0) {
+               dev_err(&pdev->dev, "Can't request DMA for U\n");
+               goto exit_free_dma_y;
+       }
+       pcdev->dma_chans[1] = err;
+       dev_dbg(&pdev->dev, "got DMA channel (U) %d\n", pcdev->dma_chans[1]);
+       err = pxa_request_dma("CI_V", DMA_PRIO_HIGH,
+                             pxa_camera_dma_irq_v, pcdev);
+       if (err < 0) {
+               dev_err(&pdev->dev, "Can't request DMA for V\n");
+               goto exit_free_dma_u;
+       }
+       pcdev->dma_chans[2] = err;
+       dev_dbg(&pdev->dev, "got DMA channel (V) %d\n", pcdev->dma_chans[2]);
+       DRCMR(68) = pcdev->dma_chans[0] | DRCMR_MAPVLD;
+       DRCMR(69) = pcdev->dma_chans[1] | DRCMR_MAPVLD;
+       DRCMR(70) = pcdev->dma_chans[2] | DRCMR_MAPVLD;
+       /* request irq */
+       err = request_irq(pcdev->irq, pxa_camera_irq, 0, PXA_CAM_DRV_NAME,
+                         pcdev);
+       if (err) {
+               dev_err(&pdev->dev, "Camera interrupt register failed \n");
+               goto exit_free_dma;
+       }
+       pcdev->soc_host.drv_name        = PXA_CAM_DRV_NAME;
+       pcdev->soc_host.ops             = &pxa_soc_camera_host_ops;
+       pcdev->soc_host.priv            = pcdev;
+       pcdev->soc_host.v4l2_dev.dev    = &pdev->dev;
+       pcdev->soc_host.nr              = pdev->id;
+       err = soc_camera_host_register(&pcdev->soc_host);
+       if (err)
+               goto exit_free_irq;
+       return 0;
+ exit_free_irq:
+       free_irq(pcdev->irq, pcdev);
+ exit_free_dma:
+       pxa_free_dma(pcdev->dma_chans[2]);
+ exit_free_dma_u:
+       pxa_free_dma(pcdev->dma_chans[1]);
+ exit_free_dma_y:
+       pxa_free_dma(pcdev->dma_chans[0]);
+ exit_iounmap:
+       iounmap(base);
+ exit_release:
+       release_mem_region(res->start, resource_size(res));
+ exit_clk:
+       clk_put(pcdev->clk);
+ exit_kfree:
+       kfree(pcdev);
+ exit:
+       return err;
+ }
+ static int __devexit pxa_camera_remove(struct platform_device *pdev)
+ {
+       struct soc_camera_host *soc_host = to_soc_camera_host(&pdev->dev);
+       struct pxa_camera_dev *pcdev = container_of(soc_host,
+                                       struct pxa_camera_dev, soc_host);
+       struct resource *res;
+       clk_put(pcdev->clk);
+       pxa_free_dma(pcdev->dma_chans[0]);
+       pxa_free_dma(pcdev->dma_chans[1]);
+       pxa_free_dma(pcdev->dma_chans[2]);
+       free_irq(pcdev->irq, pcdev);
+       soc_camera_host_unregister(soc_host);
+       iounmap(pcdev->base);
+       res = pcdev->res;
+       release_mem_region(res->start, resource_size(res));
+       kfree(pcdev);
+       dev_info(&pdev->dev, "PXA Camera driver unloaded\n");
+       return 0;
+ }
+ static struct dev_pm_ops pxa_camera_pm = {
+       .suspend        = pxa_camera_suspend,
+       .resume         = pxa_camera_resume,
+ };
+ static struct platform_driver pxa_camera_driver = {
+       .driver         = {
+               .name   = PXA_CAM_DRV_NAME,
+               .pm     = &pxa_camera_pm,
+       },
+       .probe          = pxa_camera_probe,
+       .remove         = __devexit_p(pxa_camera_remove),
+ };
+ module_platform_driver(pxa_camera_driver);
+ MODULE_DESCRIPTION("PXA27x SoC Camera Host driver");
+ MODULE_AUTHOR("Guennadi Liakhovetski <kernel@pengutronix.de>");
+ MODULE_LICENSE("GPL");
+ MODULE_VERSION(PXA_CAM_VERSION);
+ MODULE_ALIAS("platform:" PXA_CAM_DRV_NAME);
index 0000000000000000000000000000000000000000,02d4d36735d39e6d68a1f8b82d3f3896f4a8e468..b84ebc54d91bce2279e1f1f7b8a8b09d4d0ff12d
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,1370 +1,1370 @@@
 -      flush_work_sync(&dev->request_module_wk);
+ /*
+    cx231xx-cards.c - driver for Conexant Cx23100/101/102
+                               USB video capture devices
+    Copyright (C) 2008 <srinivasa.deevi at conexant dot com>
+                               Based on em28xx driver
+    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/init.h>
+ #include <linux/module.h>
+ #include <linux/slab.h>
+ #include <linux/delay.h>
+ #include <linux/i2c.h>
+ #include <linux/usb.h>
+ #include <media/tuner.h>
+ #include <media/tveeprom.h>
+ #include <media/v4l2-common.h>
+ #include <media/v4l2-chip-ident.h>
+ #include <media/cx25840.h>
+ #include "dvb-usb-ids.h"
+ #include "xc5000.h"
+ #include "tda18271.h"
+ #include "cx231xx.h"
+ static int tuner = -1;
+ module_param(tuner, int, 0444);
+ MODULE_PARM_DESC(tuner, "tuner type");
+ static int transfer_mode = 1;
+ module_param(transfer_mode, int, 0444);
+ MODULE_PARM_DESC(transfer_mode, "transfer mode (1-ISO or 0-BULK)");
+ static unsigned int disable_ir;
+ module_param(disable_ir, int, 0444);
+ MODULE_PARM_DESC(disable_ir, "disable infrared remote support");
+ /* Bitmask marking allocated devices from 0 to CX231XX_MAXBOARDS */
+ static unsigned long cx231xx_devused;
+ /*
+  *  Reset sequences for analog/digital modes
+  */
+ static struct cx231xx_reg_seq RDE250_XCV_TUNER[] = {
+       {0x03, 0x01, 10},
+       {0x03, 0x00, 30},
+       {0x03, 0x01, 10},
+       {-1, -1, -1},
+ };
+ /*
+  *  Board definitions
+  */
+ struct cx231xx_board cx231xx_boards[] = {
+       [CX231XX_BOARD_UNKNOWN] = {
+               .name = "Unknown CX231xx video grabber",
+               .tuner_type = TUNER_ABSENT,
+               .input = {{
+                               .type = CX231XX_VMUX_TELEVISION,
+                               .vmux = CX231XX_VIN_3_1,
+                               .amux = CX231XX_AMUX_VIDEO,
+                               .gpio = NULL,
+                       }, {
+                               .type = CX231XX_VMUX_COMPOSITE1,
+                               .vmux = CX231XX_VIN_2_1,
+                               .amux = CX231XX_AMUX_LINE_IN,
+                               .gpio = NULL,
+                       }, {
+                               .type = CX231XX_VMUX_SVIDEO,
+                               .vmux = CX231XX_VIN_1_1 |
+                                       (CX231XX_VIN_1_2 << 8) |
+                                       CX25840_SVIDEO_ON,
+                               .amux = CX231XX_AMUX_LINE_IN,
+                               .gpio = NULL,
+                       }
+               },
+       },
+       [CX231XX_BOARD_CNXT_CARRAERA] = {
+               .name = "Conexant Hybrid TV - CARRAERA",
+               .tuner_type = TUNER_XC5000,
+               .tuner_addr = 0x61,
+               .tuner_gpio = RDE250_XCV_TUNER,
+               .tuner_sif_gpio = 0x05,
+               .tuner_scl_gpio = 0x1a,
+               .tuner_sda_gpio = 0x1b,
+               .decoder = CX231XX_AVDECODER,
+               .output_mode = OUT_MODE_VIP11,
+               .demod_xfer_mode = 0,
+               .ctl_pin_status_mask = 0xFFFFFFC4,
+               .agc_analog_digital_select_gpio = 0x0c,
+               .gpio_pin_status_mask = 0x4001000,
+               .tuner_i2c_master = 1,
+               .demod_i2c_master = 2,
+               .has_dvb = 1,
+               .demod_addr = 0x02,
+               .norm = V4L2_STD_PAL,
+               .input = {{
+                               .type = CX231XX_VMUX_TELEVISION,
+                               .vmux = CX231XX_VIN_3_1,
+                               .amux = CX231XX_AMUX_VIDEO,
+                               .gpio = NULL,
+                       }, {
+                               .type = CX231XX_VMUX_COMPOSITE1,
+                               .vmux = CX231XX_VIN_2_1,
+                               .amux = CX231XX_AMUX_LINE_IN,
+                               .gpio = NULL,
+                       }, {
+                               .type = CX231XX_VMUX_SVIDEO,
+                               .vmux = CX231XX_VIN_1_1 |
+                                       (CX231XX_VIN_1_2 << 8) |
+                                       CX25840_SVIDEO_ON,
+                               .amux = CX231XX_AMUX_LINE_IN,
+                               .gpio = NULL,
+                       }
+               },
+       },
+       [CX231XX_BOARD_CNXT_SHELBY] = {
+               .name = "Conexant Hybrid TV - SHELBY",
+               .tuner_type = TUNER_XC5000,
+               .tuner_addr = 0x61,
+               .tuner_gpio = RDE250_XCV_TUNER,
+               .tuner_sif_gpio = 0x05,
+               .tuner_scl_gpio = 0x1a,
+               .tuner_sda_gpio = 0x1b,
+               .decoder = CX231XX_AVDECODER,
+               .output_mode = OUT_MODE_VIP11,
+               .demod_xfer_mode = 0,
+               .ctl_pin_status_mask = 0xFFFFFFC4,
+               .agc_analog_digital_select_gpio = 0x0c,
+               .gpio_pin_status_mask = 0x4001000,
+               .tuner_i2c_master = 1,
+               .demod_i2c_master = 2,
+               .has_dvb = 1,
+               .demod_addr = 0x32,
+               .norm = V4L2_STD_NTSC,
+               .input = {{
+                               .type = CX231XX_VMUX_TELEVISION,
+                               .vmux = CX231XX_VIN_3_1,
+                               .amux = CX231XX_AMUX_VIDEO,
+                               .gpio = NULL,
+                       }, {
+                               .type = CX231XX_VMUX_COMPOSITE1,
+                               .vmux = CX231XX_VIN_2_1,
+                               .amux = CX231XX_AMUX_LINE_IN,
+                               .gpio = NULL,
+                       }, {
+                               .type = CX231XX_VMUX_SVIDEO,
+                               .vmux = CX231XX_VIN_1_1 |
+                                       (CX231XX_VIN_1_2 << 8) |
+                                       CX25840_SVIDEO_ON,
+                               .amux = CX231XX_AMUX_LINE_IN,
+                               .gpio = NULL,
+                       }
+               },
+       },
+       [CX231XX_BOARD_CNXT_RDE_253S] = {
+               .name = "Conexant Hybrid TV - RDE253S",
+               .tuner_type = TUNER_NXP_TDA18271,
+               .tuner_addr = 0x60,
+               .tuner_gpio = RDE250_XCV_TUNER,
+               .tuner_sif_gpio = 0x05,
+               .tuner_scl_gpio = 0x1a,
+               .tuner_sda_gpio = 0x1b,
+               .decoder = CX231XX_AVDECODER,
+               .output_mode = OUT_MODE_VIP11,
+               .demod_xfer_mode = 0,
+               .ctl_pin_status_mask = 0xFFFFFFC4,
+               .agc_analog_digital_select_gpio = 0x1c,
+               .gpio_pin_status_mask = 0x4001000,
+               .tuner_i2c_master = 1,
+               .demod_i2c_master = 2,
+               .has_dvb = 1,
+               .demod_addr = 0x02,
+               .norm = V4L2_STD_PAL,
+               .input = {{
+                               .type = CX231XX_VMUX_TELEVISION,
+                               .vmux = CX231XX_VIN_3_1,
+                               .amux = CX231XX_AMUX_VIDEO,
+                               .gpio = NULL,
+                       }, {
+                               .type = CX231XX_VMUX_COMPOSITE1,
+                               .vmux = CX231XX_VIN_2_1,
+                               .amux = CX231XX_AMUX_LINE_IN,
+                               .gpio = NULL,
+                       }, {
+                               .type = CX231XX_VMUX_SVIDEO,
+                               .vmux = CX231XX_VIN_1_1 |
+                                       (CX231XX_VIN_1_2 << 8) |
+                                       CX25840_SVIDEO_ON,
+                               .amux = CX231XX_AMUX_LINE_IN,
+                               .gpio = NULL,
+                       }
+               },
+       },
+       [CX231XX_BOARD_CNXT_RDU_253S] = {
+               .name = "Conexant Hybrid TV - RDU253S",
+               .tuner_type = TUNER_NXP_TDA18271,
+               .tuner_addr = 0x60,
+               .tuner_gpio = RDE250_XCV_TUNER,
+               .tuner_sif_gpio = 0x05,
+               .tuner_scl_gpio = 0x1a,
+               .tuner_sda_gpio = 0x1b,
+               .decoder = CX231XX_AVDECODER,
+               .output_mode = OUT_MODE_VIP11,
+               .demod_xfer_mode = 0,
+               .ctl_pin_status_mask = 0xFFFFFFC4,
+               .agc_analog_digital_select_gpio = 0x1c,
+               .gpio_pin_status_mask = 0x4001000,
+               .tuner_i2c_master = 1,
+               .demod_i2c_master = 2,
+               .has_dvb = 1,
+               .demod_addr = 0x02,
+               .norm = V4L2_STD_PAL,
+               .input = {{
+                               .type = CX231XX_VMUX_TELEVISION,
+                               .vmux = CX231XX_VIN_3_1,
+                               .amux = CX231XX_AMUX_VIDEO,
+                               .gpio = NULL,
+                       }, {
+                               .type = CX231XX_VMUX_COMPOSITE1,
+                               .vmux = CX231XX_VIN_2_1,
+                               .amux = CX231XX_AMUX_LINE_IN,
+                               .gpio = NULL,
+                       }, {
+                               .type = CX231XX_VMUX_SVIDEO,
+                               .vmux = CX231XX_VIN_1_1 |
+                                       (CX231XX_VIN_1_2 << 8) |
+                                       CX25840_SVIDEO_ON,
+                               .amux = CX231XX_AMUX_LINE_IN,
+                               .gpio = NULL,
+                       }
+               },
+       },
+       [CX231XX_BOARD_CNXT_VIDEO_GRABBER] = {
+               .name = "Conexant VIDEO GRABBER",
+               .tuner_type = TUNER_ABSENT,
+               .decoder = CX231XX_AVDECODER,
+               .output_mode = OUT_MODE_VIP11,
+               .ctl_pin_status_mask = 0xFFFFFFC4,
+               .agc_analog_digital_select_gpio = 0x1c,
+               .gpio_pin_status_mask = 0x4001000,
+               .norm = V4L2_STD_PAL,
+               .no_alt_vanc = 1,
+               .external_av = 1,
+               .has_417 = 1,
+               .input = {{
+                               .type = CX231XX_VMUX_COMPOSITE1,
+                               .vmux = CX231XX_VIN_2_1,
+                               .amux = CX231XX_AMUX_LINE_IN,
+                               .gpio = NULL,
+                       }, {
+                               .type = CX231XX_VMUX_SVIDEO,
+                               .vmux = CX231XX_VIN_1_1 |
+                                       (CX231XX_VIN_1_2 << 8) |
+                                       CX25840_SVIDEO_ON,
+                               .amux = CX231XX_AMUX_LINE_IN,
+                               .gpio = NULL,
+                       }
+               },
+       },
+       [CX231XX_BOARD_CNXT_RDE_250] = {
+               .name = "Conexant Hybrid TV - rde 250",
+               .tuner_type = TUNER_XC5000,
+               .tuner_addr = 0x61,
+               .tuner_gpio = RDE250_XCV_TUNER,
+               .tuner_sif_gpio = 0x05,
+               .tuner_scl_gpio = 0x1a,
+               .tuner_sda_gpio = 0x1b,
+               .decoder = CX231XX_AVDECODER,
+               .output_mode = OUT_MODE_VIP11,
+               .demod_xfer_mode = 0,
+               .ctl_pin_status_mask = 0xFFFFFFC4,
+               .agc_analog_digital_select_gpio = 0x0c,
+               .gpio_pin_status_mask = 0x4001000,
+               .tuner_i2c_master = 1,
+               .demod_i2c_master = 2,
+               .has_dvb = 1,
+               .demod_addr = 0x02,
+               .norm = V4L2_STD_PAL,
+               .input = {{
+                               .type = CX231XX_VMUX_TELEVISION,
+                               .vmux = CX231XX_VIN_2_1,
+                               .amux = CX231XX_AMUX_VIDEO,
+                               .gpio = NULL,
+                       }
+               },
+       },
+       [CX231XX_BOARD_CNXT_RDU_250] = {
+               .name = "Conexant Hybrid TV - RDU 250",
+               .tuner_type = TUNER_XC5000,
+               .tuner_addr = 0x61,
+               .tuner_gpio = RDE250_XCV_TUNER,
+               .tuner_sif_gpio = 0x05,
+               .tuner_scl_gpio = 0x1a,
+               .tuner_sda_gpio = 0x1b,
+               .decoder = CX231XX_AVDECODER,
+               .output_mode = OUT_MODE_VIP11,
+               .demod_xfer_mode = 0,
+               .ctl_pin_status_mask = 0xFFFFFFC4,
+               .agc_analog_digital_select_gpio = 0x0c,
+               .gpio_pin_status_mask = 0x4001000,
+               .tuner_i2c_master = 1,
+               .demod_i2c_master = 2,
+               .has_dvb = 1,
+               .demod_addr = 0x32,
+               .norm = V4L2_STD_NTSC,
+               .input = {{
+                               .type = CX231XX_VMUX_TELEVISION,
+                               .vmux = CX231XX_VIN_2_1,
+                               .amux = CX231XX_AMUX_VIDEO,
+                               .gpio = NULL,
+                       }
+               },
+       },
+       [CX231XX_BOARD_HAUPPAUGE_EXETER] = {
+               .name = "Hauppauge EXETER",
+               .tuner_type = TUNER_NXP_TDA18271,
+               .tuner_addr = 0x60,
+               .tuner_gpio = RDE250_XCV_TUNER,
+               .tuner_sif_gpio = 0x05,
+               .tuner_scl_gpio = 0x1a,
+               .tuner_sda_gpio = 0x1b,
+               .decoder = CX231XX_AVDECODER,
+               .output_mode = OUT_MODE_VIP11,
+               .demod_xfer_mode = 0,
+               .ctl_pin_status_mask = 0xFFFFFFC4,
+               .agc_analog_digital_select_gpio = 0x0c,
+               .gpio_pin_status_mask = 0x4001000,
+               .tuner_i2c_master = 1,
+               .demod_i2c_master = 2,
+               .has_dvb = 1,
+               .demod_addr = 0x0e,
+               .norm = V4L2_STD_NTSC,
+               .input = {{
+                       .type = CX231XX_VMUX_TELEVISION,
+                       .vmux = CX231XX_VIN_3_1,
+                       .amux = CX231XX_AMUX_VIDEO,
+                       .gpio = NULL,
+               }, {
+                       .type = CX231XX_VMUX_COMPOSITE1,
+                       .vmux = CX231XX_VIN_2_1,
+                       .amux = CX231XX_AMUX_LINE_IN,
+                       .gpio = NULL,
+               }, {
+                       .type = CX231XX_VMUX_SVIDEO,
+                       .vmux = CX231XX_VIN_1_1 |
+                               (CX231XX_VIN_1_2 << 8) |
+                               CX25840_SVIDEO_ON,
+                       .amux = CX231XX_AMUX_LINE_IN,
+                       .gpio = NULL,
+               } },
+       },
+       [CX231XX_BOARD_HAUPPAUGE_USBLIVE2] = {
+               .name = "Hauppauge USB Live 2",
+               .tuner_type = TUNER_ABSENT,
+               .decoder = CX231XX_AVDECODER,
+               .output_mode = OUT_MODE_VIP11,
+               .demod_xfer_mode = 0,
+               .ctl_pin_status_mask = 0xFFFFFFC4,
+               .agc_analog_digital_select_gpio = 0x0c,
+               .gpio_pin_status_mask = 0x4001000,
+               .norm = V4L2_STD_NTSC,
+               .no_alt_vanc = 1,
+               .external_av = 1,
+               .dont_use_port_3 = 1,
+               .input = {{
+                       .type = CX231XX_VMUX_COMPOSITE1,
+                       .vmux = CX231XX_VIN_2_1,
+                       .amux = CX231XX_AMUX_LINE_IN,
+                       .gpio = NULL,
+               }, {
+                       .type = CX231XX_VMUX_SVIDEO,
+                       .vmux = CX231XX_VIN_1_1 |
+                               (CX231XX_VIN_1_2 << 8) |
+                               CX25840_SVIDEO_ON,
+                       .amux = CX231XX_AMUX_LINE_IN,
+                       .gpio = NULL,
+               } },
+       },
+       [CX231XX_BOARD_KWORLD_UB430_USB_HYBRID] = {
+               .name = "Kworld UB430 USB Hybrid",
+               .tuner_type = TUNER_NXP_TDA18271,
+               .tuner_addr = 0x60,
+               .decoder = CX231XX_AVDECODER,
+               .output_mode = OUT_MODE_VIP11,
+               .demod_xfer_mode = 0,
+               .ctl_pin_status_mask = 0xFFFFFFC4,
+               .agc_analog_digital_select_gpio = 0x11, /* According with PV cxPolaris.inf file */
+               .tuner_sif_gpio = -1,
+               .tuner_scl_gpio = -1,
+               .tuner_sda_gpio = -1,
+               .gpio_pin_status_mask = 0x4001000,
+               .tuner_i2c_master = 2,
+               .demod_i2c_master = 1,
+               .ir_i2c_master = 2,
+               .has_dvb = 1,
+               .demod_addr = 0x10,
+               .norm = V4L2_STD_PAL_M,
+               .input = {{
+                       .type = CX231XX_VMUX_TELEVISION,
+                       .vmux = CX231XX_VIN_3_1,
+                       .amux = CX231XX_AMUX_VIDEO,
+                       .gpio = NULL,
+               }, {
+                       .type = CX231XX_VMUX_COMPOSITE1,
+                       .vmux = CX231XX_VIN_2_1,
+                       .amux = CX231XX_AMUX_LINE_IN,
+                       .gpio = NULL,
+               }, {
+                       .type = CX231XX_VMUX_SVIDEO,
+                       .vmux = CX231XX_VIN_1_1 |
+                               (CX231XX_VIN_1_2 << 8) |
+                               CX25840_SVIDEO_ON,
+                       .amux = CX231XX_AMUX_LINE_IN,
+                       .gpio = NULL,
+               } },
+       },
+       [CX231XX_BOARD_PV_PLAYTV_USB_HYBRID] = {
+               .name = "Pixelview PlayTV USB Hybrid",
+               .tuner_type = TUNER_NXP_TDA18271,
+               .tuner_addr = 0x60,
+               .decoder = CX231XX_AVDECODER,
+               .output_mode = OUT_MODE_VIP11,
+               .demod_xfer_mode = 0,
+               .ctl_pin_status_mask = 0xFFFFFFC4,
+               .agc_analog_digital_select_gpio = 0x00, /* According with PV cxPolaris.inf file */
+               .tuner_sif_gpio = -1,
+               .tuner_scl_gpio = -1,
+               .tuner_sda_gpio = -1,
+               .gpio_pin_status_mask = 0x4001000,
+               .tuner_i2c_master = 2,
+               .demod_i2c_master = 1,
+               .ir_i2c_master = 2,
+               .rc_map_name = RC_MAP_PIXELVIEW_002T,
+               .has_dvb = 1,
+               .demod_addr = 0x10,
+               .norm = V4L2_STD_PAL_M,
+               .input = {{
+                       .type = CX231XX_VMUX_TELEVISION,
+                       .vmux = CX231XX_VIN_3_1,
+                       .amux = CX231XX_AMUX_VIDEO,
+                       .gpio = NULL,
+               }, {
+                       .type = CX231XX_VMUX_COMPOSITE1,
+                       .vmux = CX231XX_VIN_2_1,
+                       .amux = CX231XX_AMUX_LINE_IN,
+                       .gpio = NULL,
+               }, {
+                       .type = CX231XX_VMUX_SVIDEO,
+                       .vmux = CX231XX_VIN_1_1 |
+                               (CX231XX_VIN_1_2 << 8) |
+                               CX25840_SVIDEO_ON,
+                       .amux = CX231XX_AMUX_LINE_IN,
+                       .gpio = NULL,
+               } },
+       },
+       [CX231XX_BOARD_PV_XCAPTURE_USB] = {
+               .name = "Pixelview Xcapture USB",
+               .tuner_type = TUNER_ABSENT,
+               .decoder = CX231XX_AVDECODER,
+               .output_mode = OUT_MODE_VIP11,
+               .demod_xfer_mode = 0,
+               .ctl_pin_status_mask = 0xFFFFFFC4,
+               .agc_analog_digital_select_gpio = 0x0c,
+               .gpio_pin_status_mask = 0x4001000,
+               .norm = V4L2_STD_NTSC,
+               .no_alt_vanc = 1,
+               .external_av = 1,
+               .dont_use_port_3 = 1,
+               .input = {{
+                               .type = CX231XX_VMUX_COMPOSITE1,
+                               .vmux = CX231XX_VIN_2_1,
+                               .amux = CX231XX_AMUX_LINE_IN,
+                               .gpio = NULL,
+                       }, {
+                               .type = CX231XX_VMUX_SVIDEO,
+                               .vmux = CX231XX_VIN_1_1 |
+                                       (CX231XX_VIN_1_2 << 8) |
+                                       CX25840_SVIDEO_ON,
+                               .amux = CX231XX_AMUX_LINE_IN,
+                               .gpio = NULL,
+                       }
+               },
+       },
+       [CX231XX_BOARD_ICONBIT_U100] = {
+               .name = "Iconbit Analog Stick U100 FM",
+               .tuner_type = TUNER_ABSENT,
+               .decoder = CX231XX_AVDECODER,
+               .output_mode = OUT_MODE_VIP11,
+               .demod_xfer_mode = 0,
+               .ctl_pin_status_mask = 0xFFFFFFC4,
+               .agc_analog_digital_select_gpio = 0x1C,
+               .gpio_pin_status_mask = 0x4001000,
+               .input = {{
+                       .type = CX231XX_VMUX_COMPOSITE1,
+                       .vmux = CX231XX_VIN_2_1,
+                       .amux = CX231XX_AMUX_LINE_IN,
+                       .gpio = NULL,
+               }, {
+                       .type = CX231XX_VMUX_SVIDEO,
+                       .vmux = CX231XX_VIN_1_1 |
+                               (CX231XX_VIN_1_2 << 8) |
+                               CX25840_SVIDEO_ON,
+                       .amux = CX231XX_AMUX_LINE_IN,
+                       .gpio = NULL,
+               } },
+       },
+       [CX231XX_BOARD_HAUPPAUGE_USB2_FM_PAL] = {
+               .name = "Hauppauge WinTV USB2 FM (PAL)",
+               .tuner_type = TUNER_NXP_TDA18271,
+               .tuner_addr = 0x60,
+               .tuner_gpio = RDE250_XCV_TUNER,
+               .tuner_sif_gpio = 0x05,
+               .tuner_scl_gpio = 0x1a,
+               .tuner_sda_gpio = 0x1b,
+               .decoder = CX231XX_AVDECODER,
+               .output_mode = OUT_MODE_VIP11,
+               .ctl_pin_status_mask = 0xFFFFFFC4,
+               .agc_analog_digital_select_gpio = 0x0c,
+               .gpio_pin_status_mask = 0x4001000,
+               .tuner_i2c_master = 1,
+               .norm = V4L2_STD_PAL,
+               .input = {{
+                       .type = CX231XX_VMUX_TELEVISION,
+                       .vmux = CX231XX_VIN_3_1,
+                       .amux = CX231XX_AMUX_VIDEO,
+                       .gpio = NULL,
+               }, {
+                       .type = CX231XX_VMUX_COMPOSITE1,
+                       .vmux = CX231XX_VIN_2_1,
+                       .amux = CX231XX_AMUX_LINE_IN,
+                       .gpio = NULL,
+               }, {
+                       .type = CX231XX_VMUX_SVIDEO,
+                       .vmux = CX231XX_VIN_1_1 |
+                               (CX231XX_VIN_1_2 << 8) |
+                               CX25840_SVIDEO_ON,
+                       .amux = CX231XX_AMUX_LINE_IN,
+                       .gpio = NULL,
+               } },
+       },
+       [CX231XX_BOARD_HAUPPAUGE_USB2_FM_NTSC] = {
+               .name = "Hauppauge WinTV USB2 FM (NTSC)",
+               .tuner_type = TUNER_NXP_TDA18271,
+               .tuner_addr = 0x60,
+               .tuner_gpio = RDE250_XCV_TUNER,
+               .tuner_sif_gpio = 0x05,
+               .tuner_scl_gpio = 0x1a,
+               .tuner_sda_gpio = 0x1b,
+               .decoder = CX231XX_AVDECODER,
+               .output_mode = OUT_MODE_VIP11,
+               .ctl_pin_status_mask = 0xFFFFFFC4,
+               .agc_analog_digital_select_gpio = 0x0c,
+               .gpio_pin_status_mask = 0x4001000,
+               .tuner_i2c_master = 1,
+               .norm = V4L2_STD_NTSC,
+               .input = {{
+                       .type = CX231XX_VMUX_TELEVISION,
+                       .vmux = CX231XX_VIN_3_1,
+                       .amux = CX231XX_AMUX_VIDEO,
+                       .gpio = NULL,
+               }, {
+                       .type = CX231XX_VMUX_COMPOSITE1,
+                       .vmux = CX231XX_VIN_2_1,
+                       .amux = CX231XX_AMUX_LINE_IN,
+                       .gpio = NULL,
+               }, {
+                       .type = CX231XX_VMUX_SVIDEO,
+                       .vmux = CX231XX_VIN_1_1 |
+                               (CX231XX_VIN_1_2 << 8) |
+                               CX25840_SVIDEO_ON,
+                       .amux = CX231XX_AMUX_LINE_IN,
+                       .gpio = NULL,
+               } },
+       },
+ };
+ const unsigned int cx231xx_bcount = ARRAY_SIZE(cx231xx_boards);
+ /* table of devices that work with this driver */
+ struct usb_device_id cx231xx_id_table[] = {
+       {USB_DEVICE(0x0572, 0x5A3C),
+        .driver_info = CX231XX_BOARD_UNKNOWN},
+       {USB_DEVICE(0x0572, 0x58A2),
+        .driver_info = CX231XX_BOARD_CNXT_CARRAERA},
+       {USB_DEVICE(0x0572, 0x58A1),
+        .driver_info = CX231XX_BOARD_CNXT_SHELBY},
+       {USB_DEVICE(0x0572, 0x58A4),
+        .driver_info = CX231XX_BOARD_CNXT_RDE_253S},
+       {USB_DEVICE(0x0572, 0x58A5),
+        .driver_info = CX231XX_BOARD_CNXT_RDU_253S},
+       {USB_DEVICE(0x0572, 0x58A6),
+        .driver_info = CX231XX_BOARD_CNXT_VIDEO_GRABBER},
+       {USB_DEVICE(0x0572, 0x589E),
+        .driver_info = CX231XX_BOARD_CNXT_RDE_250},
+       {USB_DEVICE(0x0572, 0x58A0),
+        .driver_info = CX231XX_BOARD_CNXT_RDU_250},
+       {USB_DEVICE(0x2040, 0xb110),
+        .driver_info = CX231XX_BOARD_HAUPPAUGE_USB2_FM_PAL},
+       {USB_DEVICE(0x2040, 0xb111),
+        .driver_info = CX231XX_BOARD_HAUPPAUGE_USB2_FM_NTSC},
+       {USB_DEVICE(0x2040, 0xb120),
+        .driver_info = CX231XX_BOARD_HAUPPAUGE_EXETER},
+       {USB_DEVICE(0x2040, 0xb140),
+        .driver_info = CX231XX_BOARD_HAUPPAUGE_EXETER},
+       {USB_DEVICE(0x2040, 0xc200),
+        .driver_info = CX231XX_BOARD_HAUPPAUGE_USBLIVE2},
+       {USB_DEVICE_VER(USB_VID_PIXELVIEW, USB_PID_PIXELVIEW_SBTVD, 0x4000, 0x4001),
+        .driver_info = CX231XX_BOARD_PV_PLAYTV_USB_HYBRID},
+       {USB_DEVICE(USB_VID_PIXELVIEW, 0x5014),
+        .driver_info = CX231XX_BOARD_PV_XCAPTURE_USB},
+       {USB_DEVICE(0x1b80, 0xe424),
+        .driver_info = CX231XX_BOARD_KWORLD_UB430_USB_HYBRID},
+       {USB_DEVICE(0x1f4d, 0x0237),
+        .driver_info = CX231XX_BOARD_ICONBIT_U100},
+       {},
+ };
+ MODULE_DEVICE_TABLE(usb, cx231xx_id_table);
+ /* cx231xx_tuner_callback
+  * will be used to reset XC5000 tuner using GPIO pin
+  */
+ int cx231xx_tuner_callback(void *ptr, int component, int command, int arg)
+ {
+       int rc = 0;
+       struct cx231xx *dev = ptr;
+       if (dev->tuner_type == TUNER_XC5000) {
+               if (command == XC5000_TUNER_RESET) {
+                       cx231xx_info
+                               ("Tuner CB: RESET: cmd %d : tuner type %d \n",
+                                command, dev->tuner_type);
+                       cx231xx_set_gpio_value(dev, dev->board.tuner_gpio->bit,
+                                              1);
+                       msleep(10);
+                       cx231xx_set_gpio_value(dev, dev->board.tuner_gpio->bit,
+                                              0);
+                       msleep(330);
+                       cx231xx_set_gpio_value(dev, dev->board.tuner_gpio->bit,
+                                              1);
+                       msleep(10);
+               }
+       } else if (dev->tuner_type == TUNER_NXP_TDA18271) {
+               switch (command) {
+               case TDA18271_CALLBACK_CMD_AGC_ENABLE:
+                       if (dev->model == CX231XX_BOARD_PV_PLAYTV_USB_HYBRID)
+                               rc = cx231xx_set_agc_analog_digital_mux_select(dev, arg);
+                       break;
+               default:
+                       rc = -EINVAL;
+                       break;
+               }
+       }
+       return rc;
+ }
+ EXPORT_SYMBOL_GPL(cx231xx_tuner_callback);
+ void cx231xx_reset_out(struct cx231xx *dev)
+ {
+       cx231xx_set_gpio_value(dev, CX23417_RESET, 1);
+       msleep(200);
+       cx231xx_set_gpio_value(dev, CX23417_RESET, 0);
+       msleep(200);
+       cx231xx_set_gpio_value(dev, CX23417_RESET, 1);
+ }
+ void cx231xx_enable_OSC(struct cx231xx *dev)
+ {
+       cx231xx_set_gpio_value(dev, CX23417_OSC_EN, 1);
+ }
+ void cx231xx_sleep_s5h1432(struct cx231xx *dev)
+ {
+       cx231xx_set_gpio_value(dev, SLEEP_S5H1432, 0);
+ }
+ static inline void cx231xx_set_model(struct cx231xx *dev)
+ {
+       memcpy(&dev->board, &cx231xx_boards[dev->model], sizeof(dev->board));
+ }
+ /* Since cx231xx_pre_card_setup() requires a proper dev->model,
+  * this won't work for boards with generic PCI IDs
+  */
+ void cx231xx_pre_card_setup(struct cx231xx *dev)
+ {
+       cx231xx_set_model(dev);
+       cx231xx_info("Identified as %s (card=%d)\n",
+                    dev->board.name, dev->model);
+       /* set the direction for GPIO pins */
+       if (dev->board.tuner_gpio) {
+               cx231xx_set_gpio_direction(dev, dev->board.tuner_gpio->bit, 1);
+               cx231xx_set_gpio_value(dev, dev->board.tuner_gpio->bit, 1);
+       }
+       if (dev->board.tuner_sif_gpio >= 0)
+               cx231xx_set_gpio_direction(dev, dev->board.tuner_sif_gpio, 1);
+       /* request some modules if any required */
+       /* set the mode to Analog mode initially */
+       cx231xx_set_mode(dev, CX231XX_ANALOG_MODE);
+       /* Unlock device */
+       /* cx231xx_set_mode(dev, CX231XX_SUSPEND); */
+ }
+ static void cx231xx_config_tuner(struct cx231xx *dev)
+ {
+       struct tuner_setup tun_setup;
+       struct v4l2_frequency f;
+       if (dev->tuner_type == TUNER_ABSENT)
+               return;
+       tun_setup.mode_mask = T_ANALOG_TV | T_RADIO;
+       tun_setup.type = dev->tuner_type;
+       tun_setup.addr = dev->tuner_addr;
+       tun_setup.tuner_callback = cx231xx_tuner_callback;
+       tuner_call(dev, tuner, s_type_addr, &tun_setup);
+ #if 0
+       if (tun_setup.type == TUNER_XC5000) {
+               static struct xc2028_ctrl ctrl = {
+                       .fname = XC5000_DEFAULT_FIRMWARE,
+                       .max_len = 64,
+                       .demod = 0;
+               };
+               struct v4l2_priv_tun_config cfg = {
+                       .tuner = dev->tuner_type,
+                       .priv = &ctrl,
+               };
+               tuner_call(dev, tuner, s_config, &cfg);
+       }
+ #endif
+       /* configure tuner */
+       f.tuner = 0;
+       f.type = V4L2_TUNER_ANALOG_TV;
+       f.frequency = 9076;     /* just a magic number */
+       dev->ctl_freq = f.frequency;
+       call_all(dev, tuner, s_frequency, &f);
+ }
+ void cx231xx_card_setup(struct cx231xx *dev)
+ {
+       cx231xx_set_model(dev);
+       dev->tuner_type = cx231xx_boards[dev->model].tuner_type;
+       if (cx231xx_boards[dev->model].tuner_addr)
+               dev->tuner_addr = cx231xx_boards[dev->model].tuner_addr;
+       /* request some modules */
+       if (dev->board.decoder == CX231XX_AVDECODER) {
+               dev->sd_cx25840 = v4l2_i2c_new_subdev(&dev->v4l2_dev,
+                                       &dev->i2c_bus[0].i2c_adap,
+                                       "cx25840", 0x88 >> 1, NULL);
+               if (dev->sd_cx25840 == NULL)
+                       cx231xx_info("cx25840 subdev registration failure\n");
+               cx25840_call(dev, core, load_fw);
+       }
+       /* Initialize the tuner */
+       if (dev->board.tuner_type != TUNER_ABSENT) {
+               dev->sd_tuner = v4l2_i2c_new_subdev(&dev->v4l2_dev,
+                                                   &dev->i2c_bus[dev->board.tuner_i2c_master].i2c_adap,
+                                                   "tuner",
+                                                   dev->tuner_addr, NULL);
+               if (dev->sd_tuner == NULL)
+                       cx231xx_info("tuner subdev registration failure\n");
+               else
+                       cx231xx_config_tuner(dev);
+       }
+ }
+ /*
+  * cx231xx_config()
+  * inits registers with sane defaults
+  */
+ int cx231xx_config(struct cx231xx *dev)
+ {
+       /* TBD need to add cx231xx specific code */
+       dev->mute = 1;          /* maybe not the right place... */
+       dev->volume = 0x1f;
+       return 0;
+ }
+ /*
+  * cx231xx_config_i2c()
+  * configure i2c attached devices
+  */
+ void cx231xx_config_i2c(struct cx231xx *dev)
+ {
+       /* u32 input = INPUT(dev->video_input)->vmux; */
+       call_all(dev, video, s_stream, 1);
+ }
+ /*
+  * cx231xx_realease_resources()
+  * unregisters the v4l2,i2c and usb devices
+  * called when the device gets disconected or at module unload
+ */
+ void cx231xx_release_resources(struct cx231xx *dev)
+ {
+       cx231xx_release_analog_resources(dev);
+       cx231xx_remove_from_devlist(dev);
+       cx231xx_ir_exit(dev);
+       /* Release I2C buses */
+       cx231xx_dev_uninit(dev);
+       /* delete v4l2 device */
+       v4l2_device_unregister(&dev->v4l2_dev);
+       usb_put_dev(dev->udev);
+       /* Mark device as unused */
+       clear_bit(dev->devno, &cx231xx_devused);
+       kfree(dev->video_mode.alt_max_pkt_size);
+       kfree(dev->vbi_mode.alt_max_pkt_size);
+       kfree(dev->sliced_cc_mode.alt_max_pkt_size);
+       kfree(dev->ts1_mode.alt_max_pkt_size);
+       kfree(dev);
+ }
+ /*
+  * cx231xx_init_dev()
+  * allocates and inits the device structs, registers i2c bus and v4l device
+  */
+ static int cx231xx_init_dev(struct cx231xx *dev, struct usb_device *udev,
+                           int minor)
+ {
+       int retval = -ENOMEM;
+       int errCode;
+       unsigned int maxh, maxw;
+       dev->udev = udev;
+       mutex_init(&dev->lock);
+       mutex_init(&dev->ctrl_urb_lock);
+       mutex_init(&dev->gpio_i2c_lock);
+       mutex_init(&dev->i2c_lock);
+       spin_lock_init(&dev->video_mode.slock);
+       spin_lock_init(&dev->vbi_mode.slock);
+       spin_lock_init(&dev->sliced_cc_mode.slock);
+       init_waitqueue_head(&dev->open);
+       init_waitqueue_head(&dev->wait_frame);
+       init_waitqueue_head(&dev->wait_stream);
+       dev->cx231xx_read_ctrl_reg = cx231xx_read_ctrl_reg;
+       dev->cx231xx_write_ctrl_reg = cx231xx_write_ctrl_reg;
+       dev->cx231xx_send_usb_command = cx231xx_send_usb_command;
+       dev->cx231xx_gpio_i2c_read = cx231xx_gpio_i2c_read;
+       dev->cx231xx_gpio_i2c_write = cx231xx_gpio_i2c_write;
+       /* Query cx231xx to find what pcb config it is related to */
+       initialize_cx231xx(dev);
+       /*To workaround error number=-71 on EP0 for VideoGrabber,
+                need set alt here.*/
+       if (dev->model == CX231XX_BOARD_CNXT_VIDEO_GRABBER ||
+           dev->model == CX231XX_BOARD_HAUPPAUGE_USBLIVE2) {
+               cx231xx_set_alt_setting(dev, INDEX_VIDEO, 3);
+               cx231xx_set_alt_setting(dev, INDEX_VANC, 1);
+       }
+       /* Cx231xx pre card setup */
+       cx231xx_pre_card_setup(dev);
+       errCode = cx231xx_config(dev);
+       if (errCode) {
+               cx231xx_errdev("error configuring device\n");
+               return -ENOMEM;
+       }
+       /* set default norm */
+       dev->norm = dev->board.norm;
+       /* register i2c bus */
+       errCode = cx231xx_dev_init(dev);
+       if (errCode < 0) {
+               cx231xx_dev_uninit(dev);
+               cx231xx_errdev("%s: cx231xx_i2c_register - errCode [%d]!\n",
+                              __func__, errCode);
+               return errCode;
+       }
+       /* Do board specific init */
+       cx231xx_card_setup(dev);
+       /* configure the device */
+       cx231xx_config_i2c(dev);
+       maxw = norm_maxw(dev);
+       maxh = norm_maxh(dev);
+       /* set default image size */
+       dev->width = maxw;
+       dev->height = maxh;
+       dev->interlaced = 0;
+       dev->video_input = 0;
+       errCode = cx231xx_config(dev);
+       if (errCode < 0) {
+               cx231xx_errdev("%s: cx231xx_config - errCode [%d]!\n",
+                              __func__, errCode);
+               return errCode;
+       }
+       /* init video dma queues */
+       INIT_LIST_HEAD(&dev->video_mode.vidq.active);
+       INIT_LIST_HEAD(&dev->video_mode.vidq.queued);
+       /* init vbi dma queues */
+       INIT_LIST_HEAD(&dev->vbi_mode.vidq.active);
+       INIT_LIST_HEAD(&dev->vbi_mode.vidq.queued);
+       /* Reset other chips required if they are tied up with GPIO pins */
+       cx231xx_add_into_devlist(dev);
+       if (dev->board.has_417) {
+               printk(KERN_INFO "attach 417 %d\n", dev->model);
+               if (cx231xx_417_register(dev) < 0) {
+                       printk(KERN_ERR
+                               "%s() Failed to register 417 on VID_B\n",
+                              __func__);
+               }
+       }
+       retval = cx231xx_register_analog_devices(dev);
+       if (retval < 0) {
+               cx231xx_release_resources(dev);
+               return retval;
+       }
+       cx231xx_ir_init(dev);
+       cx231xx_init_extension(dev);
+       return 0;
+ }
+ #if defined(CONFIG_MODULES) && defined(MODULE)
+ static void request_module_async(struct work_struct *work)
+ {
+       struct cx231xx *dev = container_of(work,
+                                          struct cx231xx, request_module_wk);
+       if (dev->has_alsa_audio)
+               request_module("cx231xx-alsa");
+       if (dev->board.has_dvb)
+               request_module("cx231xx-dvb");
+ }
+ static void request_modules(struct cx231xx *dev)
+ {
+       INIT_WORK(&dev->request_module_wk, request_module_async);
+       schedule_work(&dev->request_module_wk);
+ }
+ static void flush_request_modules(struct cx231xx *dev)
+ {
++      flush_work(&dev->request_module_wk);
+ }
+ #else
+ #define request_modules(dev)
+ #define flush_request_modules(dev)
+ #endif /* CONFIG_MODULES */
+ /*
+  * cx231xx_usb_probe()
+  * checks for supported devices
+  */
+ static int cx231xx_usb_probe(struct usb_interface *interface,
+                            const struct usb_device_id *id)
+ {
+       struct usb_device *udev;
+       struct usb_interface *uif;
+       struct cx231xx *dev = NULL;
+       int retval = -ENODEV;
+       int nr = 0, ifnum;
+       int i, isoc_pipe = 0;
+       char *speed;
+       struct usb_interface_assoc_descriptor *assoc_desc;
+       udev = usb_get_dev(interface_to_usbdev(interface));
+       ifnum = interface->altsetting[0].desc.bInterfaceNumber;
+       /*
+        * Interface number 0 - IR interface (handled by mceusb driver)
+        * Interface number 1 - AV interface (handled by this driver)
+        */
+       if (ifnum != 1)
+               return -ENODEV;
+       /* Check to see next free device and mark as used */
+       do {
+               nr = find_first_zero_bit(&cx231xx_devused, CX231XX_MAXBOARDS);
+               if (nr >= CX231XX_MAXBOARDS) {
+                       /* No free device slots */
+                       cx231xx_err(DRIVER_NAME ": Supports only %i devices.\n",
+                                       CX231XX_MAXBOARDS);
+                       return -ENOMEM;
+               }
+       } while (test_and_set_bit(nr, &cx231xx_devused));
+       /* allocate memory for our device state and initialize it */
+       dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+       if (dev == NULL) {
+               cx231xx_err(DRIVER_NAME ": out of memory!\n");
+               clear_bit(nr, &cx231xx_devused);
+               return -ENOMEM;
+       }
+       snprintf(dev->name, 29, "cx231xx #%d", nr);
+       dev->devno = nr;
+       dev->model = id->driver_info;
+       dev->video_mode.alt = -1;
+       dev->interface_count++;
+       /* reset gpio dir and value */
+       dev->gpio_dir = 0;
+       dev->gpio_val = 0;
+       dev->xc_fw_load_done = 0;
+       dev->has_alsa_audio = 1;
+       dev->power_mode = -1;
+       atomic_set(&dev->devlist_count, 0);
+       /* 0 - vbi ; 1 -sliced cc mode */
+       dev->vbi_or_sliced_cc_mode = 0;
+       /* get maximum no.of IAD interfaces */
+       assoc_desc = udev->actconfig->intf_assoc[0];
+       dev->max_iad_interface_count = assoc_desc->bInterfaceCount;
+       /* init CIR module TBD */
+       /*mode_tv: digital=1 or analog=0*/
+       dev->mode_tv = 0;
+       dev->USE_ISO = transfer_mode;
+       switch (udev->speed) {
+       case USB_SPEED_LOW:
+               speed = "1.5";
+               break;
+       case USB_SPEED_UNKNOWN:
+       case USB_SPEED_FULL:
+               speed = "12";
+               break;
+       case USB_SPEED_HIGH:
+               speed = "480";
+               break;
+       default:
+               speed = "unknown";
+       }
+       cx231xx_info("New device %s %s @ %s Mbps "
+            "(%04x:%04x) with %d interfaces\n",
+            udev->manufacturer ? udev->manufacturer : "",
+            udev->product ? udev->product : "",
+            speed,
+            le16_to_cpu(udev->descriptor.idVendor),
+            le16_to_cpu(udev->descriptor.idProduct),
+            dev->max_iad_interface_count);
+       /* increment interface count */
+       dev->interface_count++;
+       /* get device number */
+       nr = dev->devno;
+       assoc_desc = udev->actconfig->intf_assoc[0];
+       if (assoc_desc->bFirstInterface != ifnum) {
+               cx231xx_err(DRIVER_NAME ": Not found "
+                           "matching IAD interface\n");
+               clear_bit(dev->devno, &cx231xx_devused);
+               kfree(dev);
+               dev = NULL;
+               return -ENODEV;
+       }
+       cx231xx_info("registering interface %d\n", ifnum);
+       /* save our data pointer in this interface device */
+       usb_set_intfdata(interface, dev);
+       /*
+        * AV device initialization - only done at the last interface
+        */
+       /* Create v4l2 device */
+       retval = v4l2_device_register(&interface->dev, &dev->v4l2_dev);
+       if (retval) {
+               cx231xx_errdev("v4l2_device_register failed\n");
+               clear_bit(dev->devno, &cx231xx_devused);
+               kfree(dev);
+               dev = NULL;
+               return -EIO;
+       }
+       /* allocate device struct */
+       retval = cx231xx_init_dev(dev, udev, nr);
+       if (retval) {
+               clear_bit(dev->devno, &cx231xx_devused);
+               v4l2_device_unregister(&dev->v4l2_dev);
+               kfree(dev);
+               dev = NULL;
+               usb_set_intfdata(interface, NULL);
+               return retval;
+       }
+       /* compute alternate max packet sizes for video */
+       uif = udev->actconfig->interface[dev->current_pcb_config.
+                      hs_config_info[0].interface_info.video_index + 1];
+       dev->video_mode.end_point_addr = le16_to_cpu(uif->altsetting[0].
+                       endpoint[isoc_pipe].desc.bEndpointAddress);
+       dev->video_mode.num_alt = uif->num_altsetting;
+       cx231xx_info("EndPoint Addr 0x%x, Alternate settings: %i\n",
+                    dev->video_mode.end_point_addr,
+                    dev->video_mode.num_alt);
+       dev->video_mode.alt_max_pkt_size =
+               kmalloc(32 * dev->video_mode.num_alt, GFP_KERNEL);
+       if (dev->video_mode.alt_max_pkt_size == NULL) {
+               cx231xx_errdev("out of memory!\n");
+               clear_bit(dev->devno, &cx231xx_devused);
+               v4l2_device_unregister(&dev->v4l2_dev);
+               kfree(dev);
+               dev = NULL;
+               return -ENOMEM;
+       }
+       for (i = 0; i < dev->video_mode.num_alt; i++) {
+               u16 tmp = le16_to_cpu(uif->altsetting[i].endpoint[isoc_pipe].
+                               desc.wMaxPacketSize);
+               dev->video_mode.alt_max_pkt_size[i] =
+                   (tmp & 0x07ff) * (((tmp & 0x1800) >> 11) + 1);
+               cx231xx_info("Alternate setting %i, max size= %i\n", i,
+                            dev->video_mode.alt_max_pkt_size[i]);
+       }
+       /* compute alternate max packet sizes for vbi */
+       uif = udev->actconfig->interface[dev->current_pcb_config.
+                                      hs_config_info[0].interface_info.
+                                      vanc_index + 1];
+       dev->vbi_mode.end_point_addr =
+           le16_to_cpu(uif->altsetting[0].endpoint[isoc_pipe].desc.
+                       bEndpointAddress);
+       dev->vbi_mode.num_alt = uif->num_altsetting;
+       cx231xx_info("EndPoint Addr 0x%x, Alternate settings: %i\n",
+                    dev->vbi_mode.end_point_addr,
+                    dev->vbi_mode.num_alt);
+       dev->vbi_mode.alt_max_pkt_size =
+           kmalloc(32 * dev->vbi_mode.num_alt, GFP_KERNEL);
+       if (dev->vbi_mode.alt_max_pkt_size == NULL) {
+               cx231xx_errdev("out of memory!\n");
+               clear_bit(dev->devno, &cx231xx_devused);
+               v4l2_device_unregister(&dev->v4l2_dev);
+               kfree(dev);
+               dev = NULL;
+               return -ENOMEM;
+       }
+       for (i = 0; i < dev->vbi_mode.num_alt; i++) {
+               u16 tmp =
+                   le16_to_cpu(uif->altsetting[i].endpoint[isoc_pipe].
+                               desc.wMaxPacketSize);
+               dev->vbi_mode.alt_max_pkt_size[i] =
+                   (tmp & 0x07ff) * (((tmp & 0x1800) >> 11) + 1);
+               cx231xx_info("Alternate setting %i, max size= %i\n", i,
+                            dev->vbi_mode.alt_max_pkt_size[i]);
+       }
+       /* compute alternate max packet sizes for sliced CC */
+       uif = udev->actconfig->interface[dev->current_pcb_config.
+                                      hs_config_info[0].interface_info.
+                                      hanc_index + 1];
+       dev->sliced_cc_mode.end_point_addr =
+           le16_to_cpu(uif->altsetting[0].endpoint[isoc_pipe].desc.
+                       bEndpointAddress);
+       dev->sliced_cc_mode.num_alt = uif->num_altsetting;
+       cx231xx_info("EndPoint Addr 0x%x, Alternate settings: %i\n",
+                    dev->sliced_cc_mode.end_point_addr,
+                    dev->sliced_cc_mode.num_alt);
+       dev->sliced_cc_mode.alt_max_pkt_size =
+               kmalloc(32 * dev->sliced_cc_mode.num_alt, GFP_KERNEL);
+       if (dev->sliced_cc_mode.alt_max_pkt_size == NULL) {
+               cx231xx_errdev("out of memory!\n");
+               clear_bit(dev->devno, &cx231xx_devused);
+               v4l2_device_unregister(&dev->v4l2_dev);
+               kfree(dev);
+               dev = NULL;
+               return -ENOMEM;
+       }
+       for (i = 0; i < dev->sliced_cc_mode.num_alt; i++) {
+               u16 tmp = le16_to_cpu(uif->altsetting[i].endpoint[isoc_pipe].
+                               desc.wMaxPacketSize);
+               dev->sliced_cc_mode.alt_max_pkt_size[i] =
+                   (tmp & 0x07ff) * (((tmp & 0x1800) >> 11) + 1);
+               cx231xx_info("Alternate setting %i, max size= %i\n", i,
+                            dev->sliced_cc_mode.alt_max_pkt_size[i]);
+       }
+       if (dev->current_pcb_config.ts1_source != 0xff) {
+               /* compute alternate max packet sizes for TS1 */
+               uif = udev->actconfig->interface[dev->current_pcb_config.
+                                              hs_config_info[0].
+                                              interface_info.
+                                              ts1_index + 1];
+               dev->ts1_mode.end_point_addr =
+                   le16_to_cpu(uif->altsetting[0].endpoint[isoc_pipe].
+                               desc.bEndpointAddress);
+               dev->ts1_mode.num_alt = uif->num_altsetting;
+               cx231xx_info("EndPoint Addr 0x%x, Alternate settings: %i\n",
+                            dev->ts1_mode.end_point_addr,
+                            dev->ts1_mode.num_alt);
+               dev->ts1_mode.alt_max_pkt_size =
+                       kmalloc(32 * dev->ts1_mode.num_alt, GFP_KERNEL);
+               if (dev->ts1_mode.alt_max_pkt_size == NULL) {
+                       cx231xx_errdev("out of memory!\n");
+                       clear_bit(dev->devno, &cx231xx_devused);
+                       v4l2_device_unregister(&dev->v4l2_dev);
+                       kfree(dev);
+                       dev = NULL;
+                       return -ENOMEM;
+               }
+               for (i = 0; i < dev->ts1_mode.num_alt; i++) {
+                       u16 tmp = le16_to_cpu(uif->altsetting[i].
+                                               endpoint[isoc_pipe].desc.
+                                               wMaxPacketSize);
+                       dev->ts1_mode.alt_max_pkt_size[i] =
+                           (tmp & 0x07ff) * (((tmp & 0x1800) >> 11) + 1);
+                       cx231xx_info("Alternate setting %i, max size= %i\n", i,
+                                    dev->ts1_mode.alt_max_pkt_size[i]);
+               }
+       }
+       if (dev->model == CX231XX_BOARD_CNXT_VIDEO_GRABBER) {
+               cx231xx_enable_OSC(dev);
+               cx231xx_reset_out(dev);
+               cx231xx_set_alt_setting(dev, INDEX_VIDEO, 3);
+       }
+       if (dev->model == CX231XX_BOARD_CNXT_RDE_253S)
+               cx231xx_sleep_s5h1432(dev);
+       /* load other modules required */
+       request_modules(dev);
+       return 0;
+ }
+ /*
+  * cx231xx_usb_disconnect()
+  * called when the device gets diconencted
+  * video device will be unregistered on v4l2_close in case it is still open
+  */
+ static void cx231xx_usb_disconnect(struct usb_interface *interface)
+ {
+       struct cx231xx *dev;
+       dev = usb_get_intfdata(interface);
+       usb_set_intfdata(interface, NULL);
+       if (!dev)
+               return;
+       if (!dev->udev)
+               return;
+       dev->state |= DEV_DISCONNECTED;
+       flush_request_modules(dev);
+       /* wait until all current v4l2 io is finished then deallocate
+          resources */
+       mutex_lock(&dev->lock);
+       wake_up_interruptible_all(&dev->open);
+       if (dev->users) {
+               cx231xx_warn
+                   ("device %s is open! Deregistration and memory "
+                    "deallocation are deferred on close.\n",
+                    video_device_node_name(dev->vdev));
+               /* Even having users, it is safe to remove the RC i2c driver */
+               cx231xx_ir_exit(dev);
+               if (dev->USE_ISO)
+                       cx231xx_uninit_isoc(dev);
+               else
+                       cx231xx_uninit_bulk(dev);
+               wake_up_interruptible(&dev->wait_frame);
+               wake_up_interruptible(&dev->wait_stream);
+       } else {
+       }
+       cx231xx_close_extension(dev);
+       mutex_unlock(&dev->lock);
+       if (!dev->users)
+               cx231xx_release_resources(dev);
+ }
+ static struct usb_driver cx231xx_usb_driver = {
+       .name = "cx231xx",
+       .probe = cx231xx_usb_probe,
+       .disconnect = cx231xx_usb_disconnect,
+       .id_table = cx231xx_id_table,
+ };
+ module_usb_driver(cx231xx_usb_driver);
index 0000000000000000000000000000000000000000,ab98d0845861bbbe4fdb3027fe7ccd2391b2ac34..f7297ae76b4888f8a8aafc3b52e09f4b0066fd3d
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,3415 +1,3415 @@@
 -      flush_work_sync(&dev->request_module_wk);
+ /*
+    em28xx-cards.c - driver for Empia EM2800/EM2820/2840 USB
+                   video capture devices
+    Copyright (C) 2005 Ludovico Cavedon <cavedon@sssup.it>
+                     Markus Rechberger <mrechberger@gmail.com>
+                     Mauro Carvalho Chehab <mchehab@infradead.org>
+                     Sascha Sommer <saschasommer@freenet.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., 675 Mass Ave, Cambridge, MA 02139, USA.
+  */
+ #include <linux/init.h>
+ #include <linux/module.h>
+ #include <linux/slab.h>
+ #include <linux/delay.h>
+ #include <linux/i2c.h>
+ #include <linux/usb.h>
+ #include <media/tuner.h>
+ #include <media/msp3400.h>
+ #include <media/saa7115.h>
+ #include <media/tvp5150.h>
+ #include <media/tvaudio.h>
+ #include <media/mt9v011.h>
+ #include <media/i2c-addr.h>
+ #include <media/tveeprom.h>
+ #include <media/v4l2-common.h>
+ #include <media/v4l2-chip-ident.h>
+ #include "em28xx.h"
+ #define DRIVER_NAME         "em28xx"
+ static int tuner = -1;
+ module_param(tuner, int, 0444);
+ MODULE_PARM_DESC(tuner, "tuner type");
+ static unsigned int disable_ir;
+ module_param(disable_ir, int, 0444);
+ MODULE_PARM_DESC(disable_ir, "disable infrared remote support");
+ static unsigned int disable_usb_speed_check;
+ module_param(disable_usb_speed_check, int, 0444);
+ MODULE_PARM_DESC(disable_usb_speed_check,
+                "override min bandwidth requirement of 480M bps");
+ static unsigned int card[]     = {[0 ... (EM28XX_MAXBOARDS - 1)] = UNSET };
+ module_param_array(card,  int, NULL, 0444);
+ MODULE_PARM_DESC(card,     "card type");
+ /* Bitmask marking allocated devices from 0 to EM28XX_MAXBOARDS - 1 */
+ static unsigned long em28xx_devused;
+ struct em28xx_hash_table {
+       unsigned long hash;
+       unsigned int  model;
+       unsigned int  tuner;
+ };
+ static void em28xx_pre_card_setup(struct em28xx *dev);
+ /*
+  *  Reset sequences for analog/digital modes
+  */
+ /* Reset for the most [analog] boards */
+ static struct em28xx_reg_seq default_analog[] = {
+       {EM28XX_R08_GPIO,       0x6d,   ~EM_GPIO_4,     10},
+       {       -1,             -1,     -1,             -1},
+ };
+ /* Reset for the most [digital] boards */
+ static struct em28xx_reg_seq default_digital[] = {
+       {EM28XX_R08_GPIO,       0x6e,   ~EM_GPIO_4,     10},
+       {       -1,             -1,     -1,             -1},
+ };
+ /* Board Hauppauge WinTV HVR 900 analog */
+ static struct em28xx_reg_seq hauppauge_wintv_hvr_900_analog[] = {
+       {EM28XX_R08_GPIO,       0x2d,   ~EM_GPIO_4,     10},
+       {0x05,                  0xff,   0x10,           10},
+       {  -1,                  -1,     -1,             -1},
+ };
+ /* Board Hauppauge WinTV HVR 900 digital */
+ static struct em28xx_reg_seq hauppauge_wintv_hvr_900_digital[] = {
+       {EM28XX_R08_GPIO,       0x2e,   ~EM_GPIO_4,     10},
+       {EM2880_R04_GPO,        0x04,   0x0f,           10},
+       {EM2880_R04_GPO,        0x0c,   0x0f,           10},
+       { -1,                   -1,     -1,             -1},
+ };
+ /* Board Hauppauge WinTV HVR 900 (R2) digital */
+ static struct em28xx_reg_seq hauppauge_wintv_hvr_900R2_digital[] = {
+       {EM28XX_R08_GPIO,       0x2e,   ~EM_GPIO_4,     10},
+       {EM2880_R04_GPO,        0x0c,   0x0f,           10},
+       { -1,                   -1,     -1,             -1},
+ };
+ /* Boards - EM2880 MSI DIGIVOX AD and EM2880_BOARD_MSI_DIGIVOX_AD_II */
+ static struct em28xx_reg_seq em2880_msi_digivox_ad_analog[] = {
+       {EM28XX_R08_GPIO,       0x69,   ~EM_GPIO_4,      10},
+       {       -1,             -1,     -1,              -1},
+ };
+ /* Boards - EM2880 MSI DIGIVOX AD and EM2880_BOARD_MSI_DIGIVOX_AD_II */
+ /* Board  - EM2870 Kworld 355u
+    Analog - No input analog */
+ /* Board - EM2882 Kworld 315U digital */
+ static struct em28xx_reg_seq em2882_kworld_315u_digital[] = {
+       {EM28XX_R08_GPIO,       0xff,   0xff,           10},
+       {EM28XX_R08_GPIO,       0xfe,   0xff,           10},
+       {EM2880_R04_GPO,        0x04,   0xff,           10},
+       {EM2880_R04_GPO,        0x0c,   0xff,           10},
+       {EM28XX_R08_GPIO,       0x7e,   0xff,           10},
+       {  -1,                  -1,     -1,             -1},
+ };
+ static struct em28xx_reg_seq em2882_kworld_315u_tuner_gpio[] = {
+       {EM2880_R04_GPO,        0x08,   0xff,           10},
+       {EM2880_R04_GPO,        0x0c,   0xff,           10},
+       {EM2880_R04_GPO,        0x08,   0xff,           10},
+       {EM2880_R04_GPO,        0x0c,   0xff,           10},
+       {  -1,                  -1,     -1,             -1},
+ };
+ static struct em28xx_reg_seq kworld_330u_analog[] = {
+       {EM28XX_R08_GPIO,       0x6d,   ~EM_GPIO_4,     10},
+       {EM2880_R04_GPO,        0x00,   0xff,           10},
+       { -1,                   -1,     -1,             -1},
+ };
+ static struct em28xx_reg_seq kworld_330u_digital[] = {
+       {EM28XX_R08_GPIO,       0x6e,   ~EM_GPIO_4,     10},
+       {EM2880_R04_GPO,        0x08,   0xff,           10},
+       { -1,                   -1,     -1,             -1},
+ };
+ /* Evga inDtube
+    GPIO0 - Enable digital power (s5h1409) - low to enable
+    GPIO1 - Enable analog power (tvp5150/emp202) - low to enable
+    GPIO4 - xc3028 reset
+    GOP3  - s5h1409 reset
+  */
+ static struct em28xx_reg_seq evga_indtube_analog[] = {
+       {EM28XX_R08_GPIO,       0x79,   0xff,           60},
+       {       -1,             -1,     -1,             -1},
+ };
+ static struct em28xx_reg_seq evga_indtube_digital[] = {
+       {EM28XX_R08_GPIO,       0x7a,   0xff,            1},
+       {EM2880_R04_GPO,        0x04,   0xff,           10},
+       {EM2880_R04_GPO,        0x0c,   0xff,            1},
+       { -1,                   -1,     -1,             -1},
+ };
+ /*
+  * KWorld PlusTV 340U and UB435-Q (ATSC) GPIOs map:
+  * EM_GPIO_0 - currently unknown
+  * EM_GPIO_1 - LED disable/enable (1 = off, 0 = on)
+  * EM_GPIO_2 - currently unknown
+  * EM_GPIO_3 - currently unknown
+  * EM_GPIO_4 - TDA18271HD/C1 tuner (1 = active, 0 = in reset)
+  * EM_GPIO_5 - LGDT3304 ATSC/QAM demod (1 = active, 0 = in reset)
+  * EM_GPIO_6 - currently unknown
+  * EM_GPIO_7 - currently unknown
+  */
+ static struct em28xx_reg_seq kworld_a340_digital[] = {
+       {EM28XX_R08_GPIO,       0x6d,           ~EM_GPIO_4,     10},
+       { -1,                   -1,             -1,             -1},
+ };
+ /* Pinnacle Hybrid Pro eb1a:2881 */
+ static struct em28xx_reg_seq pinnacle_hybrid_pro_analog[] = {
+       {EM28XX_R08_GPIO,       0xfd,   ~EM_GPIO_4,     10},
+       {       -1,             -1,     -1,             -1},
+ };
+ static struct em28xx_reg_seq pinnacle_hybrid_pro_digital[] = {
+       {EM28XX_R08_GPIO,       0x6e,   ~EM_GPIO_4,     10},
+       {EM2880_R04_GPO,        0x04,   0xff,          100},/* zl10353 reset */
+       {EM2880_R04_GPO,        0x0c,   0xff,            1},
+       {       -1,             -1,     -1,             -1},
+ };
+ static struct em28xx_reg_seq terratec_cinergy_USB_XS_FR_analog[] = {
+       {EM28XX_R08_GPIO,       0x6d,   ~EM_GPIO_4,     10},
+       {EM2880_R04_GPO,        0x00,   0xff,           10},
+       { -1,                   -1,     -1,             -1},
+ };
+ static struct em28xx_reg_seq terratec_cinergy_USB_XS_FR_digital[] = {
+       {EM28XX_R08_GPIO,       0x6e,   ~EM_GPIO_4,     10},
+       {EM2880_R04_GPO,        0x08,   0xff,           10},
+       { -1,                   -1,     -1,             -1},
+ };
+ /* eb1a:2868 Reddo DVB-C USB TV Box
+    GPIO4 - CU1216L NIM
+    Other GPIOs seems to be don't care. */
+ static struct em28xx_reg_seq reddo_dvb_c_usb_box[] = {
+       {EM28XX_R08_GPIO,       0xfe,   0xff,           10},
+       {EM28XX_R08_GPIO,       0xde,   0xff,           10},
+       {EM28XX_R08_GPIO,       0xfe,   0xff,           10},
+       {EM28XX_R08_GPIO,       0xff,   0xff,           10},
+       {EM28XX_R08_GPIO,       0x7f,   0xff,           10},
+       {EM28XX_R08_GPIO,       0x6f,   0xff,           10},
+       {EM28XX_R08_GPIO,       0xff,   0xff,           10},
+       {-1,                    -1,     -1,             -1},
+ };
+ /* Callback for the most boards */
+ static struct em28xx_reg_seq default_tuner_gpio[] = {
+       {EM28XX_R08_GPIO,       EM_GPIO_4,      EM_GPIO_4,      10},
+       {EM28XX_R08_GPIO,       0,              EM_GPIO_4,      10},
+       {EM28XX_R08_GPIO,       EM_GPIO_4,      EM_GPIO_4,      10},
+       {  -1,                  -1,             -1,             -1},
+ };
+ /* Mute/unmute */
+ static struct em28xx_reg_seq compro_unmute_tv_gpio[] = {
+       {EM28XX_R08_GPIO,       5,              7,              10},
+       {  -1,                  -1,             -1,             -1},
+ };
+ static struct em28xx_reg_seq compro_unmute_svid_gpio[] = {
+       {EM28XX_R08_GPIO,       4,              7,              10},
+       {  -1,                  -1,             -1,             -1},
+ };
+ static struct em28xx_reg_seq compro_mute_gpio[] = {
+       {EM28XX_R08_GPIO,       6,              7,              10},
+       {  -1,                  -1,             -1,             -1},
+ };
+ /* Terratec AV350 */
+ static struct em28xx_reg_seq terratec_av350_mute_gpio[] = {
+       {EM28XX_R08_GPIO,       0xff,   0x7f,           10},
+       {       -1,             -1,     -1,             -1},
+ };
+ static struct em28xx_reg_seq terratec_av350_unmute_gpio[] = {
+       {EM28XX_R08_GPIO,       0xff,   0xff,           10},
+       {       -1,             -1,     -1,             -1},
+ };
+ static struct em28xx_reg_seq silvercrest_reg_seq[] = {
+       {EM28XX_R08_GPIO,       0xff,   0xff,           10},
+       {EM28XX_R08_GPIO,       0x01,   0xf7,           10},
+       {       -1,             -1,     -1,             -1},
+ };
+ static struct em28xx_reg_seq vc211a_enable[] = {
+       {EM28XX_R08_GPIO,       0xff,   0x07,           10},
+       {EM28XX_R08_GPIO,       0xff,   0x0f,           10},
+       {EM28XX_R08_GPIO,       0xff,   0x0b,           10},
+       {       -1,             -1,     -1,             -1},
+ };
+ static struct em28xx_reg_seq dikom_dk300_digital[] = {
+       {EM28XX_R08_GPIO,       0x6e,   ~EM_GPIO_4,     10},
+       {EM2880_R04_GPO,        0x08,   0xff,           10},
+       { -1,                   -1,     -1,             -1},
+ };
+ /* Reset for the most [digital] boards */
+ static struct em28xx_reg_seq leadership_digital[] = {
+       {EM2874_R80_GPIO,       0x70,   0xff,   10},
+       {       -1,             -1,     -1,     -1},
+ };
+ static struct em28xx_reg_seq leadership_reset[] = {
+       {EM2874_R80_GPIO,       0xf0,   0xff,   10},
+       {EM2874_R80_GPIO,       0xb0,   0xff,   10},
+       {EM2874_R80_GPIO,       0xf0,   0xff,   10},
+       {       -1,             -1,     -1,     -1},
+ };
+ /* 2013:024f PCTV nanoStick T2 290e
+  * GPIO_6 - demod reset
+  * GPIO_7 - LED
+  */
+ static struct em28xx_reg_seq pctv_290e[] = {
+       {EM2874_R80_GPIO,       0x00,   0xff,           80},
+       {EM2874_R80_GPIO,       0x40,   0xff,           80}, /* GPIO_6 = 1 */
+       {EM2874_R80_GPIO,       0xc0,   0xff,           80}, /* GPIO_7 = 1 */
+       {-1,                    -1,     -1,             -1},
+ };
+ #if 0
+ static struct em28xx_reg_seq terratec_h5_gpio[] = {
+       {EM28XX_R08_GPIO,       0xff,   0xff,   10},
+       {EM2874_R80_GPIO,       0xf6,   0xff,   100},
+       {EM2874_R80_GPIO,       0xf2,   0xff,   50},
+       {EM2874_R80_GPIO,       0xf6,   0xff,   50},
+       { -1,                   -1,     -1,     -1},
+ };
+ static struct em28xx_reg_seq terratec_h5_digital[] = {
+       {EM2874_R80_GPIO,       0xf6,   0xff,   10},
+       {EM2874_R80_GPIO,       0xe6,   0xff,   100},
+       {EM2874_R80_GPIO,       0xa6,   0xff,   10},
+       { -1,                   -1,     -1,     -1},
+ };
+ #endif
+ /* 2013:024f PCTV DVB-S2 Stick 460e
+  * GPIO_0 - POWER_ON
+  * GPIO_1 - BOOST
+  * GPIO_2 - VUV_LNB (red LED)
+  * GPIO_3 - EXT_12V
+  * GPIO_4 - INT_DEM (DEMOD GPIO_0)
+  * GPIO_5 - INT_LNB
+  * GPIO_6 - RESET_DEM
+  * GPIO_7 - LED (green LED)
+  */
+ static struct em28xx_reg_seq pctv_460e[] = {
+       {EM2874_R80_GPIO, 0x01, 0xff,  50},
+       {0x0d,            0xff, 0xff,  50},
+       {EM2874_R80_GPIO, 0x41, 0xff,  50}, /* GPIO_6=1 */
+       {0x0d,            0x42, 0xff,  50},
+       {EM2874_R80_GPIO, 0x61, 0xff,  50}, /* GPIO_5=1 */
+       {             -1,   -1,   -1,  -1},
+ };
+ #if 0
+ static struct em28xx_reg_seq hauppauge_930c_gpio[] = {
+       {EM2874_R80_GPIO,       0x6f,   0xff,   10},
+       {EM2874_R80_GPIO,       0x4f,   0xff,   10}, /* xc5000 reset */
+       {EM2874_R80_GPIO,       0x6f,   0xff,   10},
+       {EM2874_R80_GPIO,       0x4f,   0xff,   10},
+       { -1,                   -1,     -1,     -1},
+ };
+ static struct em28xx_reg_seq hauppauge_930c_digital[] = {
+       {EM2874_R80_GPIO,       0xf6,   0xff,   10},
+       {EM2874_R80_GPIO,       0xe6,   0xff,   100},
+       {EM2874_R80_GPIO,       0xa6,   0xff,   10},
+       { -1,                   -1,     -1,     -1},
+ };
+ #endif
+ /* 1b80:e425 MaxMedia UB425-TC
+  * GPIO_6 - demod reset, 0=active
+  * GPIO_7 - LED, 0=active
+  */
+ static struct em28xx_reg_seq maxmedia_ub425_tc[] = {
+       {EM2874_R80_GPIO,  0x83,  0xff,  100},
+       {EM2874_R80_GPIO,  0xc3,  0xff,  100}, /* GPIO_6 = 1 */
+       {EM2874_R80_GPIO,  0x43,  0xff,  000}, /* GPIO_7 = 0 */
+       {-1,                 -1,    -1,   -1},
+ };
+ /* 2304:0242 PCTV QuatroStick (510e)
+  * GPIO_2: decoder reset, 0=active
+  * GPIO_4: decoder suspend, 0=active
+  * GPIO_6: demod reset, 0=active
+  * GPIO_7: LED, 1=active
+  */
+ static struct em28xx_reg_seq pctv_510e[] = {
+       {EM2874_R80_GPIO, 0x10, 0xff, 100},
+       {EM2874_R80_GPIO, 0x14, 0xff, 100}, /* GPIO_2 = 1 */
+       {EM2874_R80_GPIO, 0x54, 0xff, 050}, /* GPIO_6 = 1 */
+       {             -1,   -1,   -1,  -1},
+ };
+ /* 2013:0251 PCTV QuatroStick nano (520e)
+  * GPIO_2: decoder reset, 0=active
+  * GPIO_4: decoder suspend, 0=active
+  * GPIO_6: demod reset, 0=active
+  * GPIO_7: LED, 1=active
+  */
+ static struct em28xx_reg_seq pctv_520e[] = {
+       {EM2874_R80_GPIO, 0x10, 0xff, 100},
+       {EM2874_R80_GPIO, 0x14, 0xff, 100}, /* GPIO_2 = 1 */
+       {EM2874_R80_GPIO, 0x54, 0xff, 050}, /* GPIO_6 = 1 */
+       {EM2874_R80_GPIO, 0xd4, 0xff, 000}, /* GPIO_7 = 1 */
+       {             -1,   -1,   -1,  -1},
+ };
+ /*
+  *  Board definitions
+  */
+ struct em28xx_board em28xx_boards[] = {
+       [EM2750_BOARD_UNKNOWN] = {
+               .name          = "EM2710/EM2750/EM2751 webcam grabber",
+               .xclk          = EM28XX_XCLK_FREQUENCY_20MHZ,
+               .tuner_type    = TUNER_ABSENT,
+               .is_webcam     = 1,
+               .input         = { {
+                       .type     = EM28XX_VMUX_COMPOSITE1,
+                       .vmux     = 0,
+                       .amux     = EM28XX_AMUX_VIDEO,
+                       .gpio     = silvercrest_reg_seq,
+               } },
+       },
+       [EM2800_BOARD_UNKNOWN] = {
+               .name         = "Unknown EM2800 video grabber",
+               .is_em2800    = 1,
+               .tda9887_conf = TDA9887_PRESENT,
+               .decoder      = EM28XX_SAA711X,
+               .tuner_type   = TUNER_ABSENT,
+               .input        = { {
+                       .type     = EM28XX_VMUX_COMPOSITE1,
+                       .vmux     = SAA7115_COMPOSITE0,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+               }, {
+                       .type     = EM28XX_VMUX_SVIDEO,
+                       .vmux     = SAA7115_SVIDEO3,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+               } },
+       },
+       [EM2820_BOARD_UNKNOWN] = {
+               .name          = "Unknown EM2750/28xx video grabber",
+               .tuner_type    = TUNER_ABSENT,
+               .is_webcam     = 1,     /* To enable sensor probe */
+       },
+       [EM2750_BOARD_DLCW_130] = {
+               /* Beijing Huaqi Information Digital Technology Co., Ltd */
+               .name          = "Huaqi DLCW-130",
+               .valid         = EM28XX_BOARD_NOT_VALIDATED,
+               .xclk          = EM28XX_XCLK_FREQUENCY_48MHZ,
+               .tuner_type    = TUNER_ABSENT,
+               .is_webcam     = 1,
+               .input         = { {
+                       .type     = EM28XX_VMUX_COMPOSITE1,
+                       .vmux     = 0,
+                       .amux     = EM28XX_AMUX_VIDEO,
+               } },
+       },
+       [EM2820_BOARD_KWORLD_PVRTV2800RF] = {
+               .name         = "Kworld PVR TV 2800 RF",
+               .tuner_type   = TUNER_TEMIC_PAL,
+               .tda9887_conf = TDA9887_PRESENT,
+               .decoder      = EM28XX_SAA711X,
+               .input        = { {
+                       .type     = EM28XX_VMUX_COMPOSITE1,
+                       .vmux     = SAA7115_COMPOSITE0,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+               }, {
+                       .type     = EM28XX_VMUX_SVIDEO,
+                       .vmux     = SAA7115_SVIDEO3,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+               } },
+       },
+       [EM2820_BOARD_GADMEI_TVR200] = {
+               .name         = "Gadmei TVR200",
+               .tuner_type   = TUNER_LG_PAL_NEW_TAPC,
+               .tda9887_conf = TDA9887_PRESENT,
+               .decoder      = EM28XX_SAA711X,
+               .input        = { {
+                       .type     = EM28XX_VMUX_TELEVISION,
+                       .vmux     = SAA7115_COMPOSITE2,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+               }, {
+                       .type     = EM28XX_VMUX_COMPOSITE1,
+                       .vmux     = SAA7115_COMPOSITE0,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+               }, {
+                       .type     = EM28XX_VMUX_SVIDEO,
+                       .vmux     = SAA7115_SVIDEO3,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+               } },
+       },
+       [EM2820_BOARD_TERRATEC_CINERGY_250] = {
+               .name         = "Terratec Cinergy 250 USB",
+               .tuner_type   = TUNER_LG_PAL_NEW_TAPC,
+               .has_ir_i2c   = 1,
+               .tda9887_conf = TDA9887_PRESENT,
+               .decoder      = EM28XX_SAA711X,
+               .input        = { {
+                       .type     = EM28XX_VMUX_TELEVISION,
+                       .vmux     = SAA7115_COMPOSITE2,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+               }, {
+                       .type     = EM28XX_VMUX_COMPOSITE1,
+                       .vmux     = SAA7115_COMPOSITE0,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+               }, {
+                       .type     = EM28XX_VMUX_SVIDEO,
+                       .vmux     = SAA7115_SVIDEO3,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+               } },
+       },
+       [EM2820_BOARD_PINNACLE_USB_2] = {
+               .name         = "Pinnacle PCTV USB 2",
+               .tuner_type   = TUNER_LG_PAL_NEW_TAPC,
+               .has_ir_i2c   = 1,
+               .tda9887_conf = TDA9887_PRESENT,
+               .decoder      = EM28XX_SAA711X,
+               .input        = { {
+                       .type     = EM28XX_VMUX_TELEVISION,
+                       .vmux     = SAA7115_COMPOSITE2,
+                       .amux     = EM28XX_AMUX_VIDEO,
+               }, {
+                       .type     = EM28XX_VMUX_COMPOSITE1,
+                       .vmux     = SAA7115_COMPOSITE0,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+               }, {
+                       .type     = EM28XX_VMUX_SVIDEO,
+                       .vmux     = SAA7115_SVIDEO3,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+               } },
+       },
+       [EM2820_BOARD_HAUPPAUGE_WINTV_USB_2] = {
+               .name         = "Hauppauge WinTV USB 2",
+               .tuner_type   = TUNER_PHILIPS_FM1236_MK3,
+               .tda9887_conf = TDA9887_PRESENT |
+                               TDA9887_PORT1_ACTIVE |
+                               TDA9887_PORT2_ACTIVE,
+               .decoder      = EM28XX_TVP5150,
+               .has_msp34xx  = 1,
+               .has_ir_i2c   = 1,
+               .input        = { {
+                       .type     = EM28XX_VMUX_TELEVISION,
+                       .vmux     = TVP5150_COMPOSITE0,
+                       .amux     = MSP_INPUT_DEFAULT,
+               }, {
+                       .type     = EM28XX_VMUX_SVIDEO,
+                       .vmux     = TVP5150_SVIDEO,
+                       .amux     = MSP_INPUT(MSP_IN_SCART1, MSP_IN_TUNER1,
+                                       MSP_DSP_IN_SCART, MSP_DSP_IN_SCART),
+               } },
+       },
+       [EM2820_BOARD_DLINK_USB_TV] = {
+               .name         = "D-Link DUB-T210 TV Tuner",
+               .valid        = EM28XX_BOARD_NOT_VALIDATED,
+               .tuner_type   = TUNER_LG_PAL_NEW_TAPC,
+               .tda9887_conf = TDA9887_PRESENT,
+               .decoder      = EM28XX_SAA711X,
+               .input        = { {
+                       .type     = EM28XX_VMUX_TELEVISION,
+                       .vmux     = SAA7115_COMPOSITE2,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+               }, {
+                       .type     = EM28XX_VMUX_COMPOSITE1,
+                       .vmux     = SAA7115_COMPOSITE0,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+               }, {
+                       .type     = EM28XX_VMUX_SVIDEO,
+                       .vmux     = SAA7115_SVIDEO3,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+               } },
+       },
+       [EM2820_BOARD_HERCULES_SMART_TV_USB2] = {
+               .name         = "Hercules Smart TV USB 2.0",
+               .valid        = EM28XX_BOARD_NOT_VALIDATED,
+               .tuner_type   = TUNER_LG_PAL_NEW_TAPC,
+               .tda9887_conf = TDA9887_PRESENT,
+               .decoder      = EM28XX_SAA711X,
+               .input        = { {
+                       .type     = EM28XX_VMUX_TELEVISION,
+                       .vmux     = SAA7115_COMPOSITE2,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+               }, {
+                       .type     = EM28XX_VMUX_COMPOSITE1,
+                       .vmux     = SAA7115_COMPOSITE0,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+               }, {
+                       .type     = EM28XX_VMUX_SVIDEO,
+                       .vmux     = SAA7115_SVIDEO3,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+               } },
+       },
+       [EM2820_BOARD_PINNACLE_USB_2_FM1216ME] = {
+               .name         = "Pinnacle PCTV USB 2 (Philips FM1216ME)",
+               .valid        = EM28XX_BOARD_NOT_VALIDATED,
+               .tuner_type   = TUNER_PHILIPS_FM1216ME_MK3,
+               .tda9887_conf = TDA9887_PRESENT,
+               .decoder      = EM28XX_SAA711X,
+               .input        = { {
+                       .type     = EM28XX_VMUX_TELEVISION,
+                       .vmux     = SAA7115_COMPOSITE2,
+                       .amux     = EM28XX_AMUX_VIDEO,
+               }, {
+                       .type     = EM28XX_VMUX_COMPOSITE1,
+                       .vmux     = SAA7115_COMPOSITE0,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+               }, {
+                       .type     = EM28XX_VMUX_SVIDEO,
+                       .vmux     = SAA7115_SVIDEO3,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+               } },
+       },
+       [EM2820_BOARD_GADMEI_UTV310] = {
+               .name         = "Gadmei UTV310",
+               .valid        = EM28XX_BOARD_NOT_VALIDATED,
+               .tuner_type   = TUNER_TNF_5335MF,
+               .tda9887_conf = TDA9887_PRESENT,
+               .decoder      = EM28XX_SAA711X,
+               .input        = { {
+                       .type     = EM28XX_VMUX_TELEVISION,
+                       .vmux     = SAA7115_COMPOSITE1,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+               }, {
+                       .type     = EM28XX_VMUX_COMPOSITE1,
+                       .vmux     = SAA7115_COMPOSITE0,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+               }, {
+                       .type     = EM28XX_VMUX_SVIDEO,
+                       .vmux     = SAA7115_SVIDEO3,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+               } },
+       },
+       [EM2820_BOARD_LEADTEK_WINFAST_USBII_DELUXE] = {
+               .name         = "Leadtek Winfast USB II Deluxe",
+               .valid        = EM28XX_BOARD_NOT_VALIDATED,
+               .tuner_type   = TUNER_PHILIPS_FM1216ME_MK3,
+               .has_ir_i2c   = 1,
+               .tvaudio_addr = 0x58,
+               .tda9887_conf = TDA9887_PRESENT |
+                               TDA9887_PORT2_ACTIVE |
+                               TDA9887_QSS,
+               .decoder      = EM28XX_SAA711X,
+               .adecoder     = EM28XX_TVAUDIO,
+               .input        = { {
+                       .type     = EM28XX_VMUX_TELEVISION,
+                       .vmux     = SAA7115_COMPOSITE4,
+                       .amux     = EM28XX_AMUX_AUX,
+               }, {
+                       .type     = EM28XX_VMUX_COMPOSITE1,
+                       .vmux     = SAA7115_COMPOSITE5,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+               }, {
+                       .type     = EM28XX_VMUX_SVIDEO,
+                       .vmux     = SAA7115_SVIDEO3,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+               } },
+                       .radio    = {
+                       .type     = EM28XX_RADIO,
+                       .amux     = EM28XX_AMUX_AUX,
+                       }
+       },
+       [EM2820_BOARD_VIDEOLOGY_20K14XUSB] = {
+               .name         = "Videology 20K14XUSB USB2.0",
+               .valid        = EM28XX_BOARD_NOT_VALIDATED,
+               .tuner_type   = TUNER_ABSENT,
+               .is_webcam    = 1,
+               .input        = { {
+                       .type     = EM28XX_VMUX_COMPOSITE1,
+                       .vmux     = 0,
+                       .amux     = EM28XX_AMUX_VIDEO,
+               } },
+       },
+       [EM2820_BOARD_SILVERCREST_WEBCAM] = {
+               .name         = "Silvercrest Webcam 1.3mpix",
+               .tuner_type   = TUNER_ABSENT,
+               .is_webcam    = 1,
+               .input        = { {
+                       .type     = EM28XX_VMUX_COMPOSITE1,
+                       .vmux     = 0,
+                       .amux     = EM28XX_AMUX_VIDEO,
+                       .gpio     = silvercrest_reg_seq,
+               } },
+       },
+       [EM2821_BOARD_SUPERCOMP_USB_2] = {
+               .name         = "Supercomp USB 2.0 TV",
+               .valid        = EM28XX_BOARD_NOT_VALIDATED,
+               .tuner_type   = TUNER_PHILIPS_FM1236_MK3,
+               .tda9887_conf = TDA9887_PRESENT |
+                               TDA9887_PORT1_ACTIVE |
+                               TDA9887_PORT2_ACTIVE,
+               .decoder      = EM28XX_SAA711X,
+               .input        = { {
+                       .type     = EM28XX_VMUX_TELEVISION,
+                       .vmux     = SAA7115_COMPOSITE2,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+               }, {
+                       .type     = EM28XX_VMUX_COMPOSITE1,
+                       .vmux     = SAA7115_COMPOSITE0,
+                       .amux     = EM28XX_AMUX_VIDEO,
+               }, {
+                       .type     = EM28XX_VMUX_SVIDEO,
+                       .vmux     = SAA7115_SVIDEO3,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+               } },
+       },
+       [EM2821_BOARD_USBGEAR_VD204] = {
+               .name         = "Usbgear VD204v9",
+               .valid        = EM28XX_BOARD_NOT_VALIDATED,
+               .tuner_type   = TUNER_ABSENT,   /* Capture only device */
+               .decoder      = EM28XX_SAA711X,
+               .input        = { {
+                       .type  = EM28XX_VMUX_COMPOSITE1,
+                       .vmux  = SAA7115_COMPOSITE0,
+                       .amux  = EM28XX_AMUX_LINE_IN,
+               }, {
+                       .type  = EM28XX_VMUX_SVIDEO,
+                       .vmux  = SAA7115_SVIDEO3,
+                       .amux  = EM28XX_AMUX_LINE_IN,
+               } },
+       },
+       [EM2860_BOARD_NETGMBH_CAM] = {
+               /* Beijing Huaqi Information Digital Technology Co., Ltd */
+               .name         = "NetGMBH Cam",
+               .valid        = EM28XX_BOARD_NOT_VALIDATED,
+               .tuner_type   = TUNER_ABSENT,
+               .is_webcam    = 1,
+               .input        = { {
+                       .type     = EM28XX_VMUX_COMPOSITE1,
+                       .vmux     = 0,
+                       .amux     = EM28XX_AMUX_VIDEO,
+               } },
+       },
+       [EM2860_BOARD_TYPHOON_DVD_MAKER] = {
+               .name         = "Typhoon DVD Maker",
+               .decoder      = EM28XX_SAA711X,
+               .tuner_type   = TUNER_ABSENT,   /* Capture only device */
+               .input        = { {
+                       .type  = EM28XX_VMUX_COMPOSITE1,
+                       .vmux  = SAA7115_COMPOSITE0,
+                       .amux  = EM28XX_AMUX_LINE_IN,
+               }, {
+                       .type  = EM28XX_VMUX_SVIDEO,
+                       .vmux  = SAA7115_SVIDEO3,
+                       .amux  = EM28XX_AMUX_LINE_IN,
+               } },
+       },
+       [EM2860_BOARD_GADMEI_UTV330] = {
+               .name         = "Gadmei UTV330",
+               .valid        = EM28XX_BOARD_NOT_VALIDATED,
+               .tuner_type   = TUNER_TNF_5335MF,
+               .tda9887_conf = TDA9887_PRESENT,
+               .decoder      = EM28XX_SAA711X,
+               .input        = { {
+                       .type     = EM28XX_VMUX_TELEVISION,
+                       .vmux     = SAA7115_COMPOSITE2,
+                       .amux     = EM28XX_AMUX_VIDEO,
+               }, {
+                       .type     = EM28XX_VMUX_COMPOSITE1,
+                       .vmux     = SAA7115_COMPOSITE0,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+               }, {
+                       .type     = EM28XX_VMUX_SVIDEO,
+                       .vmux     = SAA7115_SVIDEO3,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+               } },
+       },
+       [EM2861_BOARD_GADMEI_UTV330PLUS] = {
+               .name         = "Gadmei UTV330+",
+               .tuner_type   = TUNER_TNF_5335MF,
+               .tda9887_conf = TDA9887_PRESENT,
+               .ir_codes     = RC_MAP_GADMEI_RM008Z,
+               .decoder      = EM28XX_SAA711X,
+               .xclk         = EM28XX_XCLK_FREQUENCY_12MHZ,
+               .input        = { {
+                       .type     = EM28XX_VMUX_TELEVISION,
+                       .vmux     = SAA7115_COMPOSITE2,
+                       .amux     = EM28XX_AMUX_VIDEO,
+               }, {
+                       .type     = EM28XX_VMUX_COMPOSITE1,
+                       .vmux     = SAA7115_COMPOSITE0,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+               }, {
+                       .type     = EM28XX_VMUX_SVIDEO,
+                       .vmux     = SAA7115_SVIDEO3,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+               } },
+       },
+       [EM2860_BOARD_TERRATEC_HYBRID_XS] = {
+               .name         = "Terratec Cinergy A Hybrid XS",
+               .valid        = EM28XX_BOARD_NOT_VALIDATED,
+               .tuner_type   = TUNER_XC2028,
+               .tuner_gpio   = default_tuner_gpio,
+               .decoder      = EM28XX_TVP5150,
+               .input        = { {
+                       .type     = EM28XX_VMUX_TELEVISION,
+                       .vmux     = TVP5150_COMPOSITE0,
+                       .amux     = EM28XX_AMUX_VIDEO,
+                       .gpio     = hauppauge_wintv_hvr_900_analog,
+               }, {
+                       .type     = EM28XX_VMUX_COMPOSITE1,
+                       .vmux     = TVP5150_COMPOSITE1,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+                       .gpio     = hauppauge_wintv_hvr_900_analog,
+               }, {
+                       .type     = EM28XX_VMUX_SVIDEO,
+                       .vmux     = TVP5150_SVIDEO,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+                       .gpio     = hauppauge_wintv_hvr_900_analog,
+               } },
+       },
+       [EM2861_BOARD_KWORLD_PVRTV_300U] = {
+               .name         = "KWorld PVRTV 300U",
+               .valid        = EM28XX_BOARD_NOT_VALIDATED,
+               .tuner_type   = TUNER_XC2028,
+               .tuner_gpio   = default_tuner_gpio,
+               .decoder      = EM28XX_TVP5150,
+               .input        = { {
+                       .type     = EM28XX_VMUX_TELEVISION,
+                       .vmux     = TVP5150_COMPOSITE0,
+                       .amux     = EM28XX_AMUX_VIDEO,
+               }, {
+                       .type     = EM28XX_VMUX_COMPOSITE1,
+                       .vmux     = TVP5150_COMPOSITE1,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+               }, {
+                       .type     = EM28XX_VMUX_SVIDEO,
+                       .vmux     = TVP5150_SVIDEO,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+               } },
+       },
+       [EM2861_BOARD_YAKUMO_MOVIE_MIXER] = {
+               .name          = "Yakumo MovieMixer",
+               .tuner_type    = TUNER_ABSENT,  /* Capture only device */
+               .decoder       = EM28XX_TVP5150,
+               .input         = { {
+                       .type     = EM28XX_VMUX_TELEVISION,
+                       .vmux     = TVP5150_COMPOSITE0,
+                       .amux     = EM28XX_AMUX_VIDEO,
+               }, {
+                       .type     = EM28XX_VMUX_COMPOSITE1,
+                       .vmux     = TVP5150_COMPOSITE1,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+               }, {
+                       .type     = EM28XX_VMUX_SVIDEO,
+                       .vmux     = TVP5150_SVIDEO,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+               } },
+       },
+       [EM2860_BOARD_TVP5150_REFERENCE_DESIGN] = {
+               .name          = "EM2860/TVP5150 Reference Design",
+               .tuner_type    = TUNER_ABSENT,  /* Capture only device */
+               .decoder       = EM28XX_TVP5150,
+               .input         = { {
+                       .type     = EM28XX_VMUX_COMPOSITE1,
+                       .vmux     = TVP5150_COMPOSITE1,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+               }, {
+                       .type     = EM28XX_VMUX_SVIDEO,
+                       .vmux     = TVP5150_SVIDEO,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+               } },
+       },
+       [EM2861_BOARD_PLEXTOR_PX_TV100U] = {
+               .name         = "Plextor ConvertX PX-TV100U",
+               .tuner_type   = TUNER_TNF_5335MF,
+               .xclk         = EM28XX_XCLK_I2S_MSB_TIMING |
+                               EM28XX_XCLK_FREQUENCY_12MHZ,
+               .tda9887_conf = TDA9887_PRESENT,
+               .decoder      = EM28XX_TVP5150,
+               .has_msp34xx  = 1,
+               .input        = { {
+                       .type     = EM28XX_VMUX_TELEVISION,
+                       .vmux     = TVP5150_COMPOSITE0,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+                       .gpio     = pinnacle_hybrid_pro_analog,
+               }, {
+                       .type     = EM28XX_VMUX_COMPOSITE1,
+                       .vmux     = TVP5150_COMPOSITE1,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+                       .gpio     = pinnacle_hybrid_pro_analog,
+               }, {
+                       .type     = EM28XX_VMUX_SVIDEO,
+                       .vmux     = TVP5150_SVIDEO,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+                       .gpio     = pinnacle_hybrid_pro_analog,
+               } },
+       },
+       /* Those boards with em2870 are DVB Only*/
+       [EM2870_BOARD_TERRATEC_XS] = {
+               .name         = "Terratec Cinergy T XS",
+               .valid        = EM28XX_BOARD_NOT_VALIDATED,
+               .tuner_type   = TUNER_XC2028,
+               .tuner_gpio   = default_tuner_gpio,
+       },
+       [EM2870_BOARD_TERRATEC_XS_MT2060] = {
+               .name         = "Terratec Cinergy T XS (MT2060)",
+               .valid        = EM28XX_BOARD_NOT_VALIDATED,
+               .tuner_type   = TUNER_ABSENT, /* MT2060 */
+       },
+       [EM2870_BOARD_KWORLD_350U] = {
+               .name         = "Kworld 350 U DVB-T",
+               .valid        = EM28XX_BOARD_NOT_VALIDATED,
+               .tuner_type   = TUNER_XC2028,
+               .tuner_gpio   = default_tuner_gpio,
+       },
+       [EM2870_BOARD_KWORLD_355U] = {
+               .name         = "Kworld 355 U DVB-T",
+               .valid        = EM28XX_BOARD_NOT_VALIDATED,
+               .tuner_type   = TUNER_ABSENT,
+               .tuner_gpio   = default_tuner_gpio,
+               .has_dvb      = 1,
+               .dvb_gpio     = default_digital,
+       },
+       [EM2870_BOARD_PINNACLE_PCTV_DVB] = {
+               .name         = "Pinnacle PCTV DVB-T",
+               .valid        = EM28XX_BOARD_NOT_VALIDATED,
+               .tuner_type   = TUNER_ABSENT, /* MT2060 */
+               /* djh - I have serious doubts this is right... */
+               .xclk         = EM28XX_XCLK_IR_RC5_MODE |
+                               EM28XX_XCLK_FREQUENCY_10MHZ,
+       },
+       [EM2870_BOARD_COMPRO_VIDEOMATE] = {
+               .name         = "Compro, VideoMate U3",
+               .valid        = EM28XX_BOARD_NOT_VALIDATED,
+               .tuner_type   = TUNER_ABSENT, /* MT2060 */
+       },
+       [EM2880_BOARD_TERRATEC_HYBRID_XS_FR] = {
+               .name         = "Terratec Hybrid XS Secam",
+               .has_msp34xx  = 1,
+               .tuner_type   = TUNER_XC2028,
+               .tuner_gpio   = default_tuner_gpio,
+               .decoder      = EM28XX_TVP5150,
+               .has_dvb      = 1,
+               .dvb_gpio     = terratec_cinergy_USB_XS_FR_digital,
+               .input        = { {
+                       .type     = EM28XX_VMUX_TELEVISION,
+                       .vmux     = TVP5150_COMPOSITE0,
+                       .amux     = EM28XX_AMUX_VIDEO,
+                       .gpio     = terratec_cinergy_USB_XS_FR_analog,
+               }, {
+                       .type     = EM28XX_VMUX_COMPOSITE1,
+                       .vmux     = TVP5150_COMPOSITE1,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+                       .gpio     = terratec_cinergy_USB_XS_FR_analog,
+               }, {
+                       .type     = EM28XX_VMUX_SVIDEO,
+                       .vmux     = TVP5150_SVIDEO,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+                       .gpio     = terratec_cinergy_USB_XS_FR_analog,
+               } },
+       },
+       [EM2884_BOARD_TERRATEC_H5] = {
+               .name         = "Terratec Cinergy H5",
+               .has_dvb      = 1,
+ #if 0
+               .tuner_type   = TUNER_PHILIPS_TDA8290,
+               .tuner_addr   = 0x41,
+               .dvb_gpio     = terratec_h5_digital, /* FIXME: probably wrong */
+               .tuner_gpio   = terratec_h5_gpio,
+ #else
+               .tuner_type   = TUNER_ABSENT,
+ #endif
+               .i2c_speed    = EM2874_I2C_SECONDARY_BUS_SELECT |
+                               EM28XX_I2C_CLK_WAIT_ENABLE |
+                               EM28XX_I2C_FREQ_400_KHZ,
+       },
+       [EM2884_BOARD_HAUPPAUGE_WINTV_HVR_930C] = {
+               .name         = "Hauppauge WinTV HVR 930C",
+               .has_dvb      = 1,
+ #if 0 /* FIXME: Add analog support */
+               .tuner_type   = TUNER_XC5000,
+               .tuner_addr   = 0x41,
+               .dvb_gpio     = hauppauge_930c_digital,
+               .tuner_gpio   = hauppauge_930c_gpio,
+ #else
+               .tuner_type   = TUNER_ABSENT,
+ #endif
+               .ir_codes     = RC_MAP_HAUPPAUGE,
+               .i2c_speed    = EM2874_I2C_SECONDARY_BUS_SELECT |
+                               EM28XX_I2C_CLK_WAIT_ENABLE |
+                               EM28XX_I2C_FREQ_400_KHZ,
+       },
+       [EM2884_BOARD_CINERGY_HTC_STICK] = {
+               .name         = "Terratec Cinergy HTC Stick",
+               .has_dvb      = 1,
+               .ir_codes     = RC_MAP_NEC_TERRATEC_CINERGY_XS,
+               .tuner_type   = TUNER_ABSENT,
+               .i2c_speed    = EM2874_I2C_SECONDARY_BUS_SELECT |
+                               EM28XX_I2C_CLK_WAIT_ENABLE |
+                               EM28XX_I2C_FREQ_400_KHZ,
+       },
+       [EM2880_BOARD_HAUPPAUGE_WINTV_HVR_900] = {
+               .name         = "Hauppauge WinTV HVR 900",
+               .tda9887_conf = TDA9887_PRESENT,
+               .tuner_type   = TUNER_XC2028,
+               .tuner_gpio   = default_tuner_gpio,
+               .mts_firmware = 1,
+               .has_dvb      = 1,
+               .dvb_gpio     = hauppauge_wintv_hvr_900_digital,
+               .ir_codes     = RC_MAP_HAUPPAUGE,
+               .decoder      = EM28XX_TVP5150,
+               .input        = { {
+                       .type     = EM28XX_VMUX_TELEVISION,
+                       .vmux     = TVP5150_COMPOSITE0,
+                       .amux     = EM28XX_AMUX_VIDEO,
+                       .gpio     = hauppauge_wintv_hvr_900_analog,
+               }, {
+                       .type     = EM28XX_VMUX_COMPOSITE1,
+                       .vmux     = TVP5150_COMPOSITE1,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+                       .gpio     = hauppauge_wintv_hvr_900_analog,
+               }, {
+                       .type     = EM28XX_VMUX_SVIDEO,
+                       .vmux     = TVP5150_SVIDEO,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+                       .gpio     = hauppauge_wintv_hvr_900_analog,
+               } },
+       },
+       [EM2880_BOARD_HAUPPAUGE_WINTV_HVR_900_R2] = {
+               .name         = "Hauppauge WinTV HVR 900 (R2)",
+               .tda9887_conf = TDA9887_PRESENT,
+               .tuner_type   = TUNER_XC2028,
+               .tuner_gpio   = default_tuner_gpio,
+               .mts_firmware = 1,
+               .has_dvb      = 1,
+               .dvb_gpio     = hauppauge_wintv_hvr_900R2_digital,
+               .ir_codes     = RC_MAP_HAUPPAUGE,
+               .decoder      = EM28XX_TVP5150,
+               .input        = { {
+                       .type     = EM28XX_VMUX_TELEVISION,
+                       .vmux     = TVP5150_COMPOSITE0,
+                       .amux     = EM28XX_AMUX_VIDEO,
+                       .gpio     = hauppauge_wintv_hvr_900_analog,
+               }, {
+                       .type     = EM28XX_VMUX_COMPOSITE1,
+                       .vmux     = TVP5150_COMPOSITE1,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+                       .gpio     = hauppauge_wintv_hvr_900_analog,
+               }, {
+                       .type     = EM28XX_VMUX_SVIDEO,
+                       .vmux     = TVP5150_SVIDEO,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+                       .gpio     = hauppauge_wintv_hvr_900_analog,
+               } },
+       },
+       [EM2883_BOARD_HAUPPAUGE_WINTV_HVR_850] = {
+               .name           = "Hauppauge WinTV HVR 850",
+               .tuner_type     = TUNER_XC2028,
+               .tuner_gpio     = default_tuner_gpio,
+               .mts_firmware   = 1,
+               .has_dvb        = 1,
+               .dvb_gpio       = hauppauge_wintv_hvr_900_digital,
+               .ir_codes       = RC_MAP_HAUPPAUGE,
+               .decoder        = EM28XX_TVP5150,
+               .input          = { {
+                       .type     = EM28XX_VMUX_TELEVISION,
+                       .vmux     = TVP5150_COMPOSITE0,
+                       .amux     = EM28XX_AMUX_VIDEO,
+                       .gpio     = hauppauge_wintv_hvr_900_analog,
+               }, {
+                       .type     = EM28XX_VMUX_COMPOSITE1,
+                       .vmux     = TVP5150_COMPOSITE1,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+                       .gpio     = hauppauge_wintv_hvr_900_analog,
+               }, {
+                       .type     = EM28XX_VMUX_SVIDEO,
+                       .vmux     = TVP5150_SVIDEO,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+                       .gpio     = hauppauge_wintv_hvr_900_analog,
+               } },
+       },
+       [EM2883_BOARD_HAUPPAUGE_WINTV_HVR_950] = {
+               .name           = "Hauppauge WinTV HVR 950",
+               .tuner_type     = TUNER_XC2028,
+               .tuner_gpio     = default_tuner_gpio,
+               .mts_firmware   = 1,
+               .has_dvb        = 1,
+               .dvb_gpio       = hauppauge_wintv_hvr_900_digital,
+               .ir_codes       = RC_MAP_HAUPPAUGE,
+               .decoder        = EM28XX_TVP5150,
+               .input          = { {
+                       .type     = EM28XX_VMUX_TELEVISION,
+                       .vmux     = TVP5150_COMPOSITE0,
+                       .amux     = EM28XX_AMUX_VIDEO,
+                       .gpio     = hauppauge_wintv_hvr_900_analog,
+               }, {
+                       .type     = EM28XX_VMUX_COMPOSITE1,
+                       .vmux     = TVP5150_COMPOSITE1,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+                       .gpio     = hauppauge_wintv_hvr_900_analog,
+               }, {
+                       .type     = EM28XX_VMUX_SVIDEO,
+                       .vmux     = TVP5150_SVIDEO,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+                       .gpio     = hauppauge_wintv_hvr_900_analog,
+               } },
+       },
+       [EM2880_BOARD_PINNACLE_PCTV_HD_PRO] = {
+               .name           = "Pinnacle PCTV HD Pro Stick",
+               .tuner_type     = TUNER_XC2028,
+               .tuner_gpio   = default_tuner_gpio,
+               .mts_firmware   = 1,
+               .has_dvb        = 1,
+               .dvb_gpio       = hauppauge_wintv_hvr_900_digital,
+               .ir_codes       = RC_MAP_PINNACLE_PCTV_HD,
+               .decoder        = EM28XX_TVP5150,
+               .input          = { {
+                       .type     = EM28XX_VMUX_TELEVISION,
+                       .vmux     = TVP5150_COMPOSITE0,
+                       .amux     = EM28XX_AMUX_VIDEO,
+                       .gpio     = hauppauge_wintv_hvr_900_analog,
+               }, {
+                       .type     = EM28XX_VMUX_COMPOSITE1,
+                       .vmux     = TVP5150_COMPOSITE1,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+                       .gpio     = hauppauge_wintv_hvr_900_analog,
+               }, {
+                       .type     = EM28XX_VMUX_SVIDEO,
+                       .vmux     = TVP5150_SVIDEO,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+                       .gpio     = hauppauge_wintv_hvr_900_analog,
+               } },
+       },
+       [EM2880_BOARD_AMD_ATI_TV_WONDER_HD_600] = {
+               .name           = "AMD ATI TV Wonder HD 600",
+               .tuner_type     = TUNER_XC2028,
+               .tuner_gpio     = default_tuner_gpio,
+               .mts_firmware   = 1,
+               .has_dvb        = 1,
+               .dvb_gpio       = hauppauge_wintv_hvr_900_digital,
+               .ir_codes       = RC_MAP_ATI_TV_WONDER_HD_600,
+               .decoder        = EM28XX_TVP5150,
+               .input          = { {
+                       .type     = EM28XX_VMUX_TELEVISION,
+                       .vmux     = TVP5150_COMPOSITE0,
+                       .amux     = EM28XX_AMUX_VIDEO,
+                       .gpio     = hauppauge_wintv_hvr_900_analog,
+               }, {
+                       .type     = EM28XX_VMUX_COMPOSITE1,
+                       .vmux     = TVP5150_COMPOSITE1,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+                       .gpio     = hauppauge_wintv_hvr_900_analog,
+               }, {
+                       .type     = EM28XX_VMUX_SVIDEO,
+                       .vmux     = TVP5150_SVIDEO,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+                       .gpio     = hauppauge_wintv_hvr_900_analog,
+               } },
+       },
+       [EM2880_BOARD_TERRATEC_HYBRID_XS] = {
+               .name           = "Terratec Hybrid XS",
+               .tuner_type     = TUNER_XC2028,
+               .tuner_gpio     = default_tuner_gpio,
+               .decoder        = EM28XX_TVP5150,
+               .has_dvb        = 1,
+               .dvb_gpio       = default_digital,
+               .ir_codes       = RC_MAP_TERRATEC_CINERGY_XS,
+               .xclk           = EM28XX_XCLK_FREQUENCY_12MHZ, /* NEC IR */
+               .input          = { {
+                       .type     = EM28XX_VMUX_TELEVISION,
+                       .vmux     = TVP5150_COMPOSITE0,
+                       .amux     = EM28XX_AMUX_VIDEO,
+                       .gpio     = default_analog,
+               }, {
+                       .type     = EM28XX_VMUX_COMPOSITE1,
+                       .vmux     = TVP5150_COMPOSITE1,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+                       .gpio     = default_analog,
+               }, {
+                       .type     = EM28XX_VMUX_SVIDEO,
+                       .vmux     = TVP5150_SVIDEO,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+                       .gpio     = default_analog,
+               } },
+       },
+       /* maybe there's a reason behind it why Terratec sells the Hybrid XS
+          as Prodigy XS with a different PID, let's keep it separated for now
+          maybe we'll need it lateron */
+       [EM2880_BOARD_TERRATEC_PRODIGY_XS] = {
+               .name         = "Terratec Prodigy XS",
+               .tuner_type   = TUNER_XC2028,
+               .tuner_gpio   = default_tuner_gpio,
+               .decoder      = EM28XX_TVP5150,
+               .input        = { {
+                       .type     = EM28XX_VMUX_TELEVISION,
+                       .vmux     = TVP5150_COMPOSITE0,
+                       .amux     = EM28XX_AMUX_VIDEO,
+                       .gpio     = hauppauge_wintv_hvr_900_analog,
+               }, {
+                       .type     = EM28XX_VMUX_COMPOSITE1,
+                       .vmux     = TVP5150_COMPOSITE1,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+                       .gpio     = hauppauge_wintv_hvr_900_analog,
+               }, {
+                       .type     = EM28XX_VMUX_SVIDEO,
+                       .vmux     = TVP5150_SVIDEO,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+                       .gpio     = hauppauge_wintv_hvr_900_analog,
+               } },
+       },
+       [EM2820_BOARD_MSI_VOX_USB_2] = {
+               .name              = "MSI VOX USB 2.0",
+               .tuner_type        = TUNER_LG_PAL_NEW_TAPC,
+               .tda9887_conf      = TDA9887_PRESENT      |
+                                    TDA9887_PORT1_ACTIVE |
+                                    TDA9887_PORT2_ACTIVE,
+               .max_range_640_480 = 1,
+               .decoder           = EM28XX_SAA711X,
+               .input             = { {
+                       .type      = EM28XX_VMUX_TELEVISION,
+                       .vmux      = SAA7115_COMPOSITE4,
+                       .amux      = EM28XX_AMUX_VIDEO,
+               }, {
+                       .type      = EM28XX_VMUX_COMPOSITE1,
+                       .vmux      = SAA7115_COMPOSITE0,
+                       .amux      = EM28XX_AMUX_LINE_IN,
+               }, {
+                       .type      = EM28XX_VMUX_SVIDEO,
+                       .vmux      = SAA7115_SVIDEO3,
+                       .amux      = EM28XX_AMUX_LINE_IN,
+               } },
+       },
+       [EM2800_BOARD_TERRATEC_CINERGY_200] = {
+               .name         = "Terratec Cinergy 200 USB",
+               .is_em2800    = 1,
+               .has_ir_i2c   = 1,
+               .tuner_type   = TUNER_LG_TALN,
+               .tda9887_conf = TDA9887_PRESENT,
+               .decoder      = EM28XX_SAA711X,
+               .input        = { {
+                       .type     = EM28XX_VMUX_TELEVISION,
+                       .vmux     = SAA7115_COMPOSITE2,
+                       .amux     = EM28XX_AMUX_VIDEO,
+               }, {
+                       .type     = EM28XX_VMUX_COMPOSITE1,
+                       .vmux     = SAA7115_COMPOSITE0,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+               }, {
+                       .type     = EM28XX_VMUX_SVIDEO,
+                       .vmux     = SAA7115_SVIDEO3,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+               } },
+       },
+       [EM2800_BOARD_GRABBEEX_USB2800] = {
+               .name       = "eMPIA Technology, Inc. GrabBeeX+ Video Encoder",
+               .is_em2800  = 1,
+               .decoder    = EM28XX_SAA711X,
+               .tuner_type = TUNER_ABSENT, /* capture only board */
+               .input      = { {
+                       .type     = EM28XX_VMUX_COMPOSITE1,
+                       .vmux     = SAA7115_COMPOSITE0,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+               }, {
+                       .type     = EM28XX_VMUX_SVIDEO,
+                       .vmux     = SAA7115_SVIDEO3,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+               } },
+       },
+       [EM2800_BOARD_VC211A] = {
+               .name         = "Actionmaster/LinXcel/Digitus VC211A",
+               .is_em2800    = 1,
+               .tuner_type   = TUNER_ABSENT,   /* Capture-only board */
+               .decoder      = EM28XX_SAA711X,
+               .input        = { {
+                       .type     = EM28XX_VMUX_COMPOSITE1,
+                       .vmux     = SAA7115_COMPOSITE0,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+                       .gpio     = vc211a_enable,
+               }, {
+                       .type     = EM28XX_VMUX_SVIDEO,
+                       .vmux     = SAA7115_SVIDEO3,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+                       .gpio     = vc211a_enable,
+               } },
+       },
+       [EM2800_BOARD_LEADTEK_WINFAST_USBII] = {
+               .name         = "Leadtek Winfast USB II",
+               .is_em2800    = 1,
+               .tuner_type   = TUNER_LG_PAL_NEW_TAPC,
+               .tda9887_conf = TDA9887_PRESENT,
+               .decoder      = EM28XX_SAA711X,
+               .input        = { {
+                       .type     = EM28XX_VMUX_TELEVISION,
+                       .vmux     = SAA7115_COMPOSITE2,
+                       .amux     = EM28XX_AMUX_VIDEO,
+               }, {
+                       .type     = EM28XX_VMUX_COMPOSITE1,
+                       .vmux     = SAA7115_COMPOSITE0,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+               }, {
+                       .type     = EM28XX_VMUX_SVIDEO,
+                       .vmux     = SAA7115_SVIDEO3,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+               } },
+       },
+       [EM2800_BOARD_KWORLD_USB2800] = {
+               .name         = "Kworld USB2800",
+               .is_em2800    = 1,
+               .tuner_type   = TUNER_PHILIPS_FCV1236D,
+               .tda9887_conf = TDA9887_PRESENT,
+               .decoder      = EM28XX_SAA711X,
+               .input        = { {
+                       .type     = EM28XX_VMUX_TELEVISION,
+                       .vmux     = SAA7115_COMPOSITE2,
+                       .amux     = EM28XX_AMUX_VIDEO,
+               }, {
+                       .type     = EM28XX_VMUX_COMPOSITE1,
+                       .vmux     = SAA7115_COMPOSITE0,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+               }, {
+                       .type     = EM28XX_VMUX_SVIDEO,
+                       .vmux     = SAA7115_SVIDEO3,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+               } },
+       },
+       [EM2820_BOARD_PINNACLE_DVC_90] = {
+               .name         = "Pinnacle Dazzle DVC 90/100/101/107 / Kaiser Baas Video to DVD maker "
+                              "/ Kworld DVD Maker 2 / Plextor ConvertX PX-AV100U",
+               .tuner_type   = TUNER_ABSENT, /* capture only board */
+               .decoder      = EM28XX_SAA711X,
+               .input        = { {
+                       .type     = EM28XX_VMUX_COMPOSITE1,
+                       .vmux     = SAA7115_COMPOSITE0,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+               }, {
+                       .type     = EM28XX_VMUX_SVIDEO,
+                       .vmux     = SAA7115_SVIDEO3,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+               } },
+       },
+       [EM2800_BOARD_VGEAR_POCKETTV] = {
+               .name         = "V-Gear PocketTV",
+               .is_em2800    = 1,
+               .tuner_type   = TUNER_LG_PAL_NEW_TAPC,
+               .tda9887_conf = TDA9887_PRESENT,
+               .decoder      = EM28XX_SAA711X,
+               .input        = { {
+                       .type     = EM28XX_VMUX_TELEVISION,
+                       .vmux     = SAA7115_COMPOSITE2,
+                       .amux     = EM28XX_AMUX_VIDEO,
+               }, {
+                       .type     = EM28XX_VMUX_COMPOSITE1,
+                       .vmux     = SAA7115_COMPOSITE0,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+               }, {
+                       .type     = EM28XX_VMUX_SVIDEO,
+                       .vmux     = SAA7115_SVIDEO3,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+               } },
+       },
+       [EM2820_BOARD_PROLINK_PLAYTV_BOX4_USB2] = {
+               .name         = "Pixelview PlayTV Box 4 USB 2.0",
+               .tda9887_conf = TDA9887_PRESENT,
+               .tuner_type   = TUNER_YMEC_TVF_5533MF,
+               .decoder      = EM28XX_SAA711X,
+               .input        = { {
+                       .type     = EM28XX_VMUX_TELEVISION,
+                       .vmux     = SAA7115_COMPOSITE2,
+                       .amux     = EM28XX_AMUX_VIDEO,
+                       .aout     = EM28XX_AOUT_MONO |  /* I2S */
+                                   EM28XX_AOUT_MASTER, /* Line out pin */
+               }, {
+                       .type     = EM28XX_VMUX_COMPOSITE1,
+                       .vmux     = SAA7115_COMPOSITE0,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+               }, {
+                       .type     = EM28XX_VMUX_SVIDEO,
+                       .vmux     = SAA7115_SVIDEO3,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+               } },
+       },
+       [EM2820_BOARD_PROLINK_PLAYTV_USB2] = {
+               .name         = "SIIG AVTuner-PVR / Pixelview Prolink PlayTV USB 2.0",
+               .has_snapshot_button = 1,
+               .tda9887_conf = TDA9887_PRESENT,
+               .tuner_type   = TUNER_YMEC_TVF_5533MF,
+               .decoder      = EM28XX_SAA711X,
+               .input        = { {
+                       .type     = EM28XX_VMUX_TELEVISION,
+                       .vmux     = SAA7115_COMPOSITE2,
+                       .amux     = EM28XX_AMUX_VIDEO,
+                       .aout     = EM28XX_AOUT_MONO |  /* I2S */
+                                   EM28XX_AOUT_MASTER, /* Line out pin */
+               }, {
+                       .type     = EM28XX_VMUX_COMPOSITE1,
+                       .vmux     = SAA7115_COMPOSITE0,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+               }, {
+                       .type     = EM28XX_VMUX_SVIDEO,
+                       .vmux     = SAA7115_SVIDEO3,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+               } },
+       },
+       [EM2860_BOARD_SAA711X_REFERENCE_DESIGN] = {
+               .name                = "EM2860/SAA711X Reference Design",
+               .has_snapshot_button = 1,
+               .tuner_type          = TUNER_ABSENT,
+               .decoder             = EM28XX_SAA711X,
+               .input               = { {
+                       .type     = EM28XX_VMUX_SVIDEO,
+                       .vmux     = SAA7115_SVIDEO3,
+               }, {
+                       .type     = EM28XX_VMUX_COMPOSITE1,
+                       .vmux     = SAA7115_COMPOSITE0,
+               } },
+       },
+       [EM2874_BOARD_LEADERSHIP_ISDBT] = {
+               .i2c_speed      = EM2874_I2C_SECONDARY_BUS_SELECT |
+                                 EM28XX_I2C_CLK_WAIT_ENABLE |
+                                 EM28XX_I2C_FREQ_100_KHZ,
+               .xclk           = EM28XX_XCLK_FREQUENCY_10MHZ,
+               .name           = "EM2874 Leadership ISDBT",
+               .tuner_type     = TUNER_ABSENT,
+               .tuner_gpio     = leadership_reset,
+               .dvb_gpio       = leadership_digital,
+               .has_dvb        = 1,
+       },
+       [EM2880_BOARD_MSI_DIGIVOX_AD] = {
+               .name         = "MSI DigiVox A/D",
+               .valid        = EM28XX_BOARD_NOT_VALIDATED,
+               .tuner_type   = TUNER_XC2028,
+               .tuner_gpio   = default_tuner_gpio,
+               .decoder      = EM28XX_TVP5150,
+               .input        = { {
+                       .type     = EM28XX_VMUX_TELEVISION,
+                       .vmux     = TVP5150_COMPOSITE0,
+                       .amux     = EM28XX_AMUX_VIDEO,
+                       .gpio     = em2880_msi_digivox_ad_analog,
+               }, {
+                       .type     = EM28XX_VMUX_COMPOSITE1,
+                       .vmux     = TVP5150_COMPOSITE1,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+                       .gpio     = em2880_msi_digivox_ad_analog,
+               }, {
+                       .type     = EM28XX_VMUX_SVIDEO,
+                       .vmux     = TVP5150_SVIDEO,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+                       .gpio     = em2880_msi_digivox_ad_analog,
+               } },
+       },
+       [EM2880_BOARD_MSI_DIGIVOX_AD_II] = {
+               .name         = "MSI DigiVox A/D II",
+               .valid        = EM28XX_BOARD_NOT_VALIDATED,
+               .tuner_type   = TUNER_XC2028,
+               .tuner_gpio   = default_tuner_gpio,
+               .decoder      = EM28XX_TVP5150,
+               .input        = { {
+                       .type     = EM28XX_VMUX_TELEVISION,
+                       .vmux     = TVP5150_COMPOSITE0,
+                       .amux     = EM28XX_AMUX_VIDEO,
+                       .gpio     = em2880_msi_digivox_ad_analog,
+               }, {
+                       .type     = EM28XX_VMUX_COMPOSITE1,
+                       .vmux     = TVP5150_COMPOSITE1,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+                       .gpio     = em2880_msi_digivox_ad_analog,
+               }, {
+                       .type     = EM28XX_VMUX_SVIDEO,
+                       .vmux     = TVP5150_SVIDEO,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+                       .gpio     = em2880_msi_digivox_ad_analog,
+               } },
+       },
+       [EM2880_BOARD_KWORLD_DVB_305U] = {
+               .name         = "KWorld DVB-T 305U",
+               .tuner_type   = TUNER_XC2028,
+               .tuner_gpio   = default_tuner_gpio,
+               .decoder      = EM28XX_TVP5150,
+               .input        = { {
+                       .type     = EM28XX_VMUX_TELEVISION,
+                       .vmux     = TVP5150_COMPOSITE0,
+                       .amux     = EM28XX_AMUX_VIDEO,
+               }, {
+                       .type     = EM28XX_VMUX_COMPOSITE1,
+                       .vmux     = TVP5150_COMPOSITE1,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+               }, {
+                       .type     = EM28XX_VMUX_SVIDEO,
+                       .vmux     = TVP5150_SVIDEO,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+               } },
+       },
+       [EM2880_BOARD_KWORLD_DVB_310U] = {
+               .name         = "KWorld DVB-T 310U",
+               .tuner_type   = TUNER_XC2028,
+               .tuner_gpio   = default_tuner_gpio,
+               .has_dvb      = 1,
+               .dvb_gpio     = default_digital,
+               .mts_firmware = 1,
+               .decoder      = EM28XX_TVP5150,
+               .input        = { {
+                       .type     = EM28XX_VMUX_TELEVISION,
+                       .vmux     = TVP5150_COMPOSITE0,
+                       .amux     = EM28XX_AMUX_VIDEO,
+                       .gpio     = default_analog,
+               }, {
+                       .type     = EM28XX_VMUX_COMPOSITE1,
+                       .vmux     = TVP5150_COMPOSITE1,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+                       .gpio     = default_analog,
+               }, {    /* S-video has not been tested yet */
+                       .type     = EM28XX_VMUX_SVIDEO,
+                       .vmux     = TVP5150_SVIDEO,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+                       .gpio     = default_analog,
+               } },
+       },
+       [EM2882_BOARD_KWORLD_ATSC_315U] = {
+               .name           = "KWorld ATSC 315U HDTV TV Box",
+               .valid          = EM28XX_BOARD_NOT_VALIDATED,
+               .tuner_type     = TUNER_THOMSON_DTT761X,
+               .tuner_gpio     = em2882_kworld_315u_tuner_gpio,
+               .tda9887_conf   = TDA9887_PRESENT,
+               .decoder        = EM28XX_SAA711X,
+               .has_dvb        = 1,
+               .dvb_gpio       = em2882_kworld_315u_digital,
+               .ir_codes       = RC_MAP_KWORLD_315U,
+               .xclk           = EM28XX_XCLK_FREQUENCY_12MHZ,
+               .i2c_speed      = EM28XX_I2C_CLK_WAIT_ENABLE,
+               /* Analog mode - still not ready */
+               /*.input        = { {
+                       .type = EM28XX_VMUX_TELEVISION,
+                       .vmux = SAA7115_COMPOSITE2,
+                       .amux = EM28XX_AMUX_VIDEO,
+                       .gpio = em2882_kworld_315u_analog,
+                       .aout = EM28XX_AOUT_PCM_IN | EM28XX_AOUT_PCM_STEREO,
+               }, {
+                       .type = EM28XX_VMUX_COMPOSITE1,
+                       .vmux = SAA7115_COMPOSITE0,
+                       .amux = EM28XX_AMUX_LINE_IN,
+                       .gpio = em2882_kworld_315u_analog1,
+                       .aout = EM28XX_AOUT_PCM_IN | EM28XX_AOUT_PCM_STEREO,
+               }, {
+                       .type = EM28XX_VMUX_SVIDEO,
+                       .vmux = SAA7115_SVIDEO3,
+                       .amux = EM28XX_AMUX_LINE_IN,
+                       .gpio = em2882_kworld_315u_analog1,
+                       .aout = EM28XX_AOUT_PCM_IN | EM28XX_AOUT_PCM_STEREO,
+               } }, */
+       },
+       [EM2880_BOARD_EMPIRE_DUAL_TV] = {
+               .name = "Empire dual TV",
+               .tuner_type = TUNER_XC2028,
+               .tuner_gpio = default_tuner_gpio,
+               .has_dvb = 1,
+               .dvb_gpio = default_digital,
+               .mts_firmware = 1,
+               .decoder = EM28XX_TVP5150,
+               .input = { {
+                       .type = EM28XX_VMUX_TELEVISION,
+                       .vmux = TVP5150_COMPOSITE0,
+                       .amux = EM28XX_AMUX_VIDEO,
+                       .gpio = default_analog,
+               }, {
+                       .type = EM28XX_VMUX_COMPOSITE1,
+                       .vmux = TVP5150_COMPOSITE1,
+                       .amux = EM28XX_AMUX_LINE_IN,
+                       .gpio = default_analog,
+               }, {
+                       .type = EM28XX_VMUX_SVIDEO,
+                       .vmux = TVP5150_SVIDEO,
+                       .amux = EM28XX_AMUX_LINE_IN,
+                       .gpio = default_analog,
+               } },
+       },
+       [EM2881_BOARD_DNT_DA2_HYBRID] = {
+               .name         = "DNT DA2 Hybrid",
+               .valid        = EM28XX_BOARD_NOT_VALIDATED,
+               .tuner_type   = TUNER_XC2028,
+               .tuner_gpio   = default_tuner_gpio,
+               .decoder      = EM28XX_TVP5150,
+               .input        = { {
+                       .type     = EM28XX_VMUX_TELEVISION,
+                       .vmux     = TVP5150_COMPOSITE0,
+                       .amux     = EM28XX_AMUX_VIDEO,
+                       .gpio     = default_analog,
+               }, {
+                       .type     = EM28XX_VMUX_COMPOSITE1,
+                       .vmux     = TVP5150_COMPOSITE1,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+                       .gpio     = default_analog,
+               }, {
+                       .type     = EM28XX_VMUX_SVIDEO,
+                       .vmux     = TVP5150_SVIDEO,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+                       .gpio     = default_analog,
+               } },
+       },
+       [EM2881_BOARD_PINNACLE_HYBRID_PRO] = {
+               .name         = "Pinnacle Hybrid Pro",
+               .tuner_type   = TUNER_XC2028,
+               .tuner_gpio   = default_tuner_gpio,
+               .decoder      = EM28XX_TVP5150,
+               .has_dvb      = 1,
+               .dvb_gpio     = pinnacle_hybrid_pro_digital,
+               .input        = { {
+                       .type     = EM28XX_VMUX_TELEVISION,
+                       .vmux     = TVP5150_COMPOSITE0,
+                       .amux     = EM28XX_AMUX_VIDEO,
+                       .gpio     = pinnacle_hybrid_pro_analog,
+               }, {
+                       .type     = EM28XX_VMUX_COMPOSITE1,
+                       .vmux     = TVP5150_COMPOSITE1,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+                       .gpio     = pinnacle_hybrid_pro_analog,
+               }, {
+                       .type     = EM28XX_VMUX_SVIDEO,
+                       .vmux     = TVP5150_SVIDEO,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+                       .gpio     = pinnacle_hybrid_pro_analog,
+               } },
+       },
+       [EM2882_BOARD_PINNACLE_HYBRID_PRO_330E] = {
+               .name         = "Pinnacle Hybrid Pro (330e)",
+               .tuner_type   = TUNER_XC2028,
+               .tuner_gpio   = default_tuner_gpio,
+               .mts_firmware = 1,
+               .has_dvb      = 1,
+               .dvb_gpio     = hauppauge_wintv_hvr_900R2_digital,
+               .ir_codes     = RC_MAP_PINNACLE_PCTV_HD,
+               .decoder      = EM28XX_TVP5150,
+               .input        = { {
+                       .type     = EM28XX_VMUX_TELEVISION,
+                       .vmux     = TVP5150_COMPOSITE0,
+                       .amux     = EM28XX_AMUX_VIDEO,
+                       .gpio     = hauppauge_wintv_hvr_900_analog,
+               }, {
+                       .type     = EM28XX_VMUX_COMPOSITE1,
+                       .vmux     = TVP5150_COMPOSITE1,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+                       .gpio     = hauppauge_wintv_hvr_900_analog,
+               }, {
+                       .type     = EM28XX_VMUX_SVIDEO,
+                       .vmux     = TVP5150_SVIDEO,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+                       .gpio     = hauppauge_wintv_hvr_900_analog,
+               } },
+       },
+       [EM2882_BOARD_KWORLD_VS_DVBT] = {
+               .name         = "Kworld VS-DVB-T 323UR",
+               .tuner_type   = TUNER_XC2028,
+               .tuner_gpio   = default_tuner_gpio,
+               .decoder      = EM28XX_TVP5150,
+               .mts_firmware = 1,
+               .has_dvb      = 1,
+               .dvb_gpio     = kworld_330u_digital,
+               .xclk         = EM28XX_XCLK_FREQUENCY_12MHZ, /* NEC IR */
+               .ir_codes     = RC_MAP_KWORLD_315U,
+               .input        = { {
+                       .type     = EM28XX_VMUX_TELEVISION,
+                       .vmux     = TVP5150_COMPOSITE0,
+                       .amux     = EM28XX_AMUX_VIDEO,
+               }, {
+                       .type     = EM28XX_VMUX_COMPOSITE1,
+                       .vmux     = TVP5150_COMPOSITE1,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+               }, {
+                       .type     = EM28XX_VMUX_SVIDEO,
+                       .vmux     = TVP5150_SVIDEO,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+               } },
+       },
+       [EM2882_BOARD_TERRATEC_HYBRID_XS] = {
+               .name         = "Terratec Cinnergy Hybrid T USB XS (em2882)",
+               .tuner_type   = TUNER_XC2028,
+               .tuner_gpio   = default_tuner_gpio,
+               .mts_firmware = 1,
+               .decoder      = EM28XX_TVP5150,
+               .has_dvb      = 1,
+               .dvb_gpio     = hauppauge_wintv_hvr_900_digital,
+               .ir_codes     = RC_MAP_TERRATEC_CINERGY_XS,
+               .xclk         = EM28XX_XCLK_FREQUENCY_12MHZ,
+               .input        = { {
+                       .type     = EM28XX_VMUX_TELEVISION,
+                       .vmux     = TVP5150_COMPOSITE0,
+                       .amux     = EM28XX_AMUX_VIDEO,
+                       .gpio     = hauppauge_wintv_hvr_900_analog,
+               }, {
+                       .type     = EM28XX_VMUX_COMPOSITE1,
+                       .vmux     = TVP5150_COMPOSITE1,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+                       .gpio     = hauppauge_wintv_hvr_900_analog,
+               }, {
+                       .type     = EM28XX_VMUX_SVIDEO,
+                       .vmux     = TVP5150_SVIDEO,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+                       .gpio     = hauppauge_wintv_hvr_900_analog,
+               } },
+       },
+       [EM2882_BOARD_DIKOM_DK300] = {
+               .name         = "Dikom DK300",
+               .tuner_type   = TUNER_XC2028,
+               .tuner_gpio   = default_tuner_gpio,
+               .decoder      = EM28XX_TVP5150,
+               .mts_firmware = 1,
+               .has_dvb      = 1,
+               .dvb_gpio     = dikom_dk300_digital,
+               .input        = { {
+                       .type     = EM28XX_VMUX_TELEVISION,
+                       .vmux     = TVP5150_COMPOSITE0,
+                       .amux     = EM28XX_AMUX_VIDEO,
+                       .gpio     = default_analog,
+               } },
+       },
+       [EM2883_BOARD_KWORLD_HYBRID_330U] = {
+               .name         = "Kworld PlusTV HD Hybrid 330",
+               .tuner_type   = TUNER_XC2028,
+               .tuner_gpio   = default_tuner_gpio,
+               .decoder      = EM28XX_TVP5150,
+               .mts_firmware = 1,
+               .has_dvb      = 1,
+               .dvb_gpio     = kworld_330u_digital,
+               .xclk             = EM28XX_XCLK_FREQUENCY_12MHZ,
+               .i2c_speed        = EM28XX_I2C_CLK_WAIT_ENABLE |
+                                   EM28XX_I2C_EEPROM_ON_BOARD |
+                                   EM28XX_I2C_EEPROM_KEY_VALID,
+               .input        = { {
+                       .type     = EM28XX_VMUX_TELEVISION,
+                       .vmux     = TVP5150_COMPOSITE0,
+                       .amux     = EM28XX_AMUX_VIDEO,
+                       .gpio     = kworld_330u_analog,
+                       .aout     = EM28XX_AOUT_PCM_IN | EM28XX_AOUT_PCM_STEREO,
+               }, {
+                       .type     = EM28XX_VMUX_COMPOSITE1,
+                       .vmux     = TVP5150_COMPOSITE1,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+                       .gpio     = kworld_330u_analog,
+                       .aout     = EM28XX_AOUT_PCM_IN | EM28XX_AOUT_PCM_STEREO,
+               }, {
+                       .type     = EM28XX_VMUX_SVIDEO,
+                       .vmux     = TVP5150_SVIDEO,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+                       .gpio     = kworld_330u_analog,
+               } },
+       },
+       [EM2820_BOARD_COMPRO_VIDEOMATE_FORYOU] = {
+               .name         = "Compro VideoMate ForYou/Stereo",
+               .tuner_type   = TUNER_LG_PAL_NEW_TAPC,
+               .tvaudio_addr = 0xb0,
+               .tda9887_conf = TDA9887_PRESENT,
+               .decoder      = EM28XX_TVP5150,
+               .adecoder     = EM28XX_TVAUDIO,
+               .mute_gpio    = compro_mute_gpio,
+               .input        = { {
+                       .type     = EM28XX_VMUX_TELEVISION,
+                       .vmux     = TVP5150_COMPOSITE0,
+                       .amux     = EM28XX_AMUX_VIDEO,
+                       .gpio     = compro_unmute_tv_gpio,
+               }, {
+                       .type     = EM28XX_VMUX_SVIDEO,
+                       .vmux     = TVP5150_SVIDEO,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+                       .gpio     = compro_unmute_svid_gpio,
+               } },
+       },
+       [EM2860_BOARD_KAIOMY_TVNPC_U2] = {
+               .name         = "Kaiomy TVnPC U2",
+               .vchannels    = 3,
+               .tuner_type   = TUNER_XC2028,
+               .tuner_addr   = 0x61,
+               .mts_firmware = 1,
+               .decoder      = EM28XX_TVP5150,
+               .tuner_gpio   = default_tuner_gpio,
+               .ir_codes     = RC_MAP_KAIOMY,
+               .input          = { {
+                       .type     = EM28XX_VMUX_TELEVISION,
+                       .vmux     = TVP5150_COMPOSITE0,
+                       .amux     = EM28XX_AMUX_VIDEO,
+               }, {
+                       .type     = EM28XX_VMUX_COMPOSITE1,
+                       .vmux     = TVP5150_COMPOSITE1,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+               }, {
+                       .type     = EM28XX_VMUX_SVIDEO,
+                       .vmux     = TVP5150_SVIDEO,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+               } },
+               .radio          = {
+                       .type     = EM28XX_RADIO,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+               }
+       },
+       [EM2860_BOARD_EASYCAP] = {
+               .name         = "Easy Cap Capture DC-60",
+               .vchannels    = 2,
+               .tuner_type   = TUNER_ABSENT,
+               .decoder      = EM28XX_SAA711X,
+               .input           = { {
+                       .type     = EM28XX_VMUX_COMPOSITE1,
+                       .vmux     = SAA7115_COMPOSITE0,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+               }, {
+                       .type     = EM28XX_VMUX_SVIDEO,
+                       .vmux     = SAA7115_SVIDEO3,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+               } },
+       },
+       [EM2820_BOARD_IODATA_GVMVP_SZ] = {
+               .name       = "IO-DATA GV-MVP/SZ",
+               .tuner_type   = TUNER_PHILIPS_FM1236_MK3,
+               .tuner_gpio   = default_tuner_gpio,
+               .tda9887_conf = TDA9887_PRESENT,
+               .decoder      = EM28XX_TVP5150,
+               .input        = { {
+                       .type     = EM28XX_VMUX_TELEVISION,
+                       .vmux     = TVP5150_COMPOSITE0,
+                       .amux     = EM28XX_AMUX_VIDEO,
+               }, { /* Composite has not been tested yet */
+                       .type     = EM28XX_VMUX_COMPOSITE1,
+                       .vmux     = TVP5150_COMPOSITE1,
+                       .amux     = EM28XX_AMUX_VIDEO,
+               }, { /* S-video has not been tested yet */
+                       .type     = EM28XX_VMUX_SVIDEO,
+                       .vmux     = TVP5150_SVIDEO,
+                       .amux     = EM28XX_AMUX_VIDEO,
+               } },
+       },
+       [EM2860_BOARD_TERRATEC_GRABBY] = {
+               .name            = "Terratec Grabby",
+               .vchannels       = 2,
+               .tuner_type      = TUNER_ABSENT,
+               .decoder         = EM28XX_SAA711X,
+               .xclk            = EM28XX_XCLK_FREQUENCY_12MHZ,
+               .input           = { {
+                       .type     = EM28XX_VMUX_COMPOSITE1,
+                       .vmux     = SAA7115_COMPOSITE0,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+               }, {
+                       .type     = EM28XX_VMUX_SVIDEO,
+                       .vmux     = SAA7115_SVIDEO3,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+               } },
+       },
+       [EM2860_BOARD_TERRATEC_AV350] = {
+               .name            = "Terratec AV350",
+               .vchannels       = 2,
+               .tuner_type      = TUNER_ABSENT,
+               .decoder         = EM28XX_TVP5150,
+               .xclk            = EM28XX_XCLK_FREQUENCY_12MHZ,
+               .mute_gpio       = terratec_av350_mute_gpio,
+               .input           = { {
+                       .type     = EM28XX_VMUX_COMPOSITE1,
+                       .vmux     = TVP5150_COMPOSITE1,
+                       .amux     = EM28XX_AUDIO_SRC_LINE,
+                       .gpio     = terratec_av350_unmute_gpio,
+               }, {
+                       .type     = EM28XX_VMUX_SVIDEO,
+                       .vmux     = TVP5150_SVIDEO,
+                       .amux     = EM28XX_AUDIO_SRC_LINE,
+                       .gpio     = terratec_av350_unmute_gpio,
+               } },
+       },
+       [EM2860_BOARD_ELGATO_VIDEO_CAPTURE] = {
+               .name         = "Elgato Video Capture",
+               .decoder      = EM28XX_SAA711X,
+               .tuner_type   = TUNER_ABSENT,   /* Capture only device */
+               .input        = { {
+                       .type  = EM28XX_VMUX_COMPOSITE1,
+                       .vmux  = SAA7115_COMPOSITE0,
+                       .amux  = EM28XX_AMUX_LINE_IN,
+               }, {
+                       .type  = EM28XX_VMUX_SVIDEO,
+                       .vmux  = SAA7115_SVIDEO3,
+                       .amux  = EM28XX_AMUX_LINE_IN,
+               } },
+       },
+       [EM2882_BOARD_EVGA_INDTUBE] = {
+               .name         = "Evga inDtube",
+               .tuner_type   = TUNER_XC2028,
+               .tuner_gpio   = default_tuner_gpio,
+               .decoder      = EM28XX_TVP5150,
+               .xclk         = EM28XX_XCLK_FREQUENCY_12MHZ, /* NEC IR */
+               .mts_firmware = 1,
+               .has_dvb      = 1,
+               .dvb_gpio     = evga_indtube_digital,
+               .ir_codes     = RC_MAP_EVGA_INDTUBE,
+               .input        = { {
+                       .type     = EM28XX_VMUX_TELEVISION,
+                       .vmux     = TVP5150_COMPOSITE0,
+                       .amux     = EM28XX_AMUX_VIDEO,
+                       .gpio     = evga_indtube_analog,
+               }, {
+                       .type     = EM28XX_VMUX_COMPOSITE1,
+                       .vmux     = TVP5150_COMPOSITE1,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+                       .gpio     = evga_indtube_analog,
+               }, {
+                       .type     = EM28XX_VMUX_SVIDEO,
+                       .vmux     = TVP5150_SVIDEO,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+                       .gpio     = evga_indtube_analog,
+               } },
+       },
+       /* eb1a:2868 Empia EM2870 + Philips CU1216L NIM (Philips TDA10023 +
+          Infineon TUA6034) */
+       [EM2870_BOARD_REDDO_DVB_C_USB_BOX] = {
+               .name          = "Reddo DVB-C USB TV Box",
+               .tuner_type    = TUNER_ABSENT,
+               .tuner_gpio    = reddo_dvb_c_usb_box,
+               .has_dvb       = 1,
+       },
+       /* 1b80:a340 - Empia EM2870, NXP TDA18271HD and LG DT3304, sold
+        * initially as the KWorld PlusTV 340U, then as the UB435-Q.
+        * Early variants have a TDA18271HD/C1, later ones a TDA18271HD/C2 */
+       [EM2870_BOARD_KWORLD_A340] = {
+               .name       = "KWorld PlusTV 340U or UB435-Q (ATSC)",
+               .tuner_type = TUNER_ABSENT,     /* Digital-only TDA18271HD */
+               .has_dvb    = 1,
+               .dvb_gpio   = kworld_a340_digital,
+               .tuner_gpio = default_tuner_gpio,
+       },
+       /* 2013:024f PCTV nanoStick T2 290e.
+        * Empia EM28174, Sony CXD2820R and NXP TDA18271HD/C2 */
+       [EM28174_BOARD_PCTV_290E] = {
+               .name          = "PCTV nanoStick T2 290e",
+               .i2c_speed      = EM2874_I2C_SECONDARY_BUS_SELECT |
+                       EM28XX_I2C_CLK_WAIT_ENABLE | EM28XX_I2C_FREQ_100_KHZ,
+               .tuner_type    = TUNER_ABSENT,
+               .tuner_gpio    = pctv_290e,
+               .has_dvb       = 1,
+               .ir_codes      = RC_MAP_PINNACLE_PCTV_HD,
+       },
+       /* 2013:024f PCTV DVB-S2 Stick 460e
+        * Empia EM28174, NXP TDA10071, Conexant CX24118A and Allegro A8293 */
+       [EM28174_BOARD_PCTV_460E] = {
+               .i2c_speed     = EM2874_I2C_SECONDARY_BUS_SELECT |
+                       EM28XX_I2C_CLK_WAIT_ENABLE | EM28XX_I2C_FREQ_400_KHZ,
+               .name          = "PCTV DVB-S2 Stick (460e)",
+               .tuner_type    = TUNER_ABSENT,
+               .tuner_gpio    = pctv_460e,
+               .has_dvb       = 1,
+               .ir_codes      = RC_MAP_PINNACLE_PCTV_HD,
+       },
+       /* eb1a:5006 Honestech VIDBOX NW03
+        * Empia EM2860, Philips SAA7113, Empia EMP202, No Tuner */
+       [EM2860_BOARD_HT_VIDBOX_NW03] = {
+               .name                = "Honestech Vidbox NW03",
+               .tuner_type          = TUNER_ABSENT,
+               .decoder             = EM28XX_SAA711X,
+               .input               = { {
+                       .type     = EM28XX_VMUX_COMPOSITE1,
+                       .vmux     = SAA7115_COMPOSITE0,
+                       .amux     = EM28XX_AMUX_LINE_IN,
+               }, {
+                       .type     = EM28XX_VMUX_SVIDEO,
+                       .vmux     = SAA7115_SVIDEO3,  /* S-VIDEO needs confirming */
+                       .amux     = EM28XX_AMUX_LINE_IN,
+               } },
+       },
+       /* 1b80:e425 MaxMedia UB425-TC
+        * Empia EM2874B + Micronas DRX 3913KA2 + NXP TDA18271HDC2 */
+       [EM2874_BOARD_MAXMEDIA_UB425_TC] = {
+               .name          = "MaxMedia UB425-TC",
+               .tuner_type    = TUNER_ABSENT,
+               .tuner_gpio    = maxmedia_ub425_tc,
+               .has_dvb       = 1,
+               .i2c_speed     = EM2874_I2C_SECONDARY_BUS_SELECT |
+                               EM28XX_I2C_CLK_WAIT_ENABLE |
+                               EM28XX_I2C_FREQ_400_KHZ,
+       },
+       /* 2304:0242 PCTV QuatroStick (510e)
+        * Empia EM2884 + Micronas DRX 3926K + NXP TDA18271HDC2 */
+       [EM2884_BOARD_PCTV_510E] = {
+               .name          = "PCTV QuatroStick (510e)",
+               .tuner_type    = TUNER_ABSENT,
+               .tuner_gpio    = pctv_510e,
+               .has_dvb       = 1,
+               .ir_codes      = RC_MAP_PINNACLE_PCTV_HD,
+               .i2c_speed     = EM2874_I2C_SECONDARY_BUS_SELECT |
+                               EM28XX_I2C_CLK_WAIT_ENABLE |
+                               EM28XX_I2C_FREQ_400_KHZ,
+       },
+       /* 2013:0251 PCTV QuatroStick nano (520e)
+        * Empia EM2884 + Micronas DRX 3926K + NXP TDA18271HDC2 */
+       [EM2884_BOARD_PCTV_520E] = {
+               .name          = "PCTV QuatroStick nano (520e)",
+               .tuner_type    = TUNER_ABSENT,
+               .tuner_gpio    = pctv_520e,
+               .has_dvb       = 1,
+               .ir_codes      = RC_MAP_PINNACLE_PCTV_HD,
+               .i2c_speed     = EM2874_I2C_SECONDARY_BUS_SELECT |
+                               EM28XX_I2C_CLK_WAIT_ENABLE |
+                               EM28XX_I2C_FREQ_400_KHZ,
+       },
+ };
+ const unsigned int em28xx_bcount = ARRAY_SIZE(em28xx_boards);
+ /* table of devices that work with this driver */
+ struct usb_device_id em28xx_id_table[] = {
+       { USB_DEVICE(0xeb1a, 0x2750),
+                       .driver_info = EM2750_BOARD_UNKNOWN },
+       { USB_DEVICE(0xeb1a, 0x2751),
+                       .driver_info = EM2750_BOARD_UNKNOWN },
+       { USB_DEVICE(0xeb1a, 0x2800),
+                       .driver_info = EM2800_BOARD_UNKNOWN },
+       { USB_DEVICE(0xeb1a, 0x2710),
+                       .driver_info = EM2820_BOARD_UNKNOWN },
+       { USB_DEVICE(0xeb1a, 0x2820),
+                       .driver_info = EM2820_BOARD_UNKNOWN },
+       { USB_DEVICE(0xeb1a, 0x2821),
+                       .driver_info = EM2820_BOARD_UNKNOWN },
+       { USB_DEVICE(0xeb1a, 0x2860),
+                       .driver_info = EM2820_BOARD_UNKNOWN },
+       { USB_DEVICE(0xeb1a, 0x2861),
+                       .driver_info = EM2820_BOARD_UNKNOWN },
+       { USB_DEVICE(0xeb1a, 0x2862),
+                       .driver_info = EM2820_BOARD_UNKNOWN },
+       { USB_DEVICE(0xeb1a, 0x2863),
+                       .driver_info = EM2820_BOARD_UNKNOWN },
+       { USB_DEVICE(0xeb1a, 0x2870),
+                       .driver_info = EM2820_BOARD_UNKNOWN },
+       { USB_DEVICE(0xeb1a, 0x2881),
+                       .driver_info = EM2820_BOARD_UNKNOWN },
+       { USB_DEVICE(0xeb1a, 0x2883),
+                       .driver_info = EM2820_BOARD_UNKNOWN },
+       { USB_DEVICE(0xeb1a, 0x2868),
+                       .driver_info = EM2820_BOARD_UNKNOWN },
+       { USB_DEVICE(0xeb1a, 0x2875),
+                       .driver_info = EM2820_BOARD_UNKNOWN },
+       { USB_DEVICE(0xeb1a, 0xe300),
+                       .driver_info = EM2861_BOARD_KWORLD_PVRTV_300U },
+       { USB_DEVICE(0xeb1a, 0xe303),
+                       .driver_info = EM2860_BOARD_KAIOMY_TVNPC_U2 },
+       { USB_DEVICE(0xeb1a, 0xe305),
+                       .driver_info = EM2880_BOARD_KWORLD_DVB_305U },
+       { USB_DEVICE(0xeb1a, 0xe310),
+                       .driver_info = EM2880_BOARD_MSI_DIGIVOX_AD },
+       { USB_DEVICE(0xeb1a, 0xa313),
+               .driver_info = EM2882_BOARD_KWORLD_ATSC_315U },
+       { USB_DEVICE(0xeb1a, 0xa316),
+                       .driver_info = EM2883_BOARD_KWORLD_HYBRID_330U },
+       { USB_DEVICE(0xeb1a, 0xe320),
+                       .driver_info = EM2880_BOARD_MSI_DIGIVOX_AD_II },
+       { USB_DEVICE(0xeb1a, 0xe323),
+                       .driver_info = EM2882_BOARD_KWORLD_VS_DVBT },
+       { USB_DEVICE(0xeb1a, 0xe350),
+                       .driver_info = EM2870_BOARD_KWORLD_350U },
+       { USB_DEVICE(0xeb1a, 0xe355),
+                       .driver_info = EM2870_BOARD_KWORLD_355U },
+       { USB_DEVICE(0xeb1a, 0x2801),
+                       .driver_info = EM2800_BOARD_GRABBEEX_USB2800 },
+       { USB_DEVICE(0xeb1a, 0xe357),
+                       .driver_info = EM2870_BOARD_KWORLD_355U },
+       { USB_DEVICE(0xeb1a, 0xe359),
+                       .driver_info = EM2870_BOARD_KWORLD_355U },
+       { USB_DEVICE(0x1b80, 0xe302),
+                       .driver_info = EM2820_BOARD_PINNACLE_DVC_90 }, /* Kaiser Baas Video to DVD maker */
+       { USB_DEVICE(0x1b80, 0xe304),
+                       .driver_info = EM2820_BOARD_PINNACLE_DVC_90 }, /* Kworld DVD Maker 2 */
+       { USB_DEVICE(0x0ccd, 0x0036),
+                       .driver_info = EM2820_BOARD_TERRATEC_CINERGY_250 },
+       { USB_DEVICE(0x0ccd, 0x004c),
+                       .driver_info = EM2880_BOARD_TERRATEC_HYBRID_XS_FR },
+       { USB_DEVICE(0x0ccd, 0x004f),
+                       .driver_info = EM2860_BOARD_TERRATEC_HYBRID_XS },
+       { USB_DEVICE(0x0ccd, 0x005e),
+                       .driver_info = EM2882_BOARD_TERRATEC_HYBRID_XS },
+       { USB_DEVICE(0x0ccd, 0x0042),
+                       .driver_info = EM2882_BOARD_TERRATEC_HYBRID_XS },
+       { USB_DEVICE(0x0ccd, 0x0043),
+                       .driver_info = EM2870_BOARD_TERRATEC_XS },
+       { USB_DEVICE(0x0ccd, 0x008e),   /* Cinergy HTC USB XS Rev. 1 */
+                       .driver_info = EM2884_BOARD_TERRATEC_H5 },
+       { USB_DEVICE(0x0ccd, 0x00ac),   /* Cinergy HTC USB XS Rev. 2 */
+                       .driver_info = EM2884_BOARD_TERRATEC_H5 },
+       { USB_DEVICE(0x0ccd, 0x10a2),   /* H5 Rev. 1 */
+                       .driver_info = EM2884_BOARD_TERRATEC_H5 },
+       { USB_DEVICE(0x0ccd, 0x10ad),   /* H5 Rev. 2 */
+                       .driver_info = EM2884_BOARD_TERRATEC_H5 },
+       { USB_DEVICE(0x0ccd, 0x0084),
+                       .driver_info = EM2860_BOARD_TERRATEC_AV350 },
+       { USB_DEVICE(0x0ccd, 0x0096),
+                       .driver_info = EM2860_BOARD_TERRATEC_GRABBY },
+       { USB_DEVICE(0x0ccd, 0x10AF),
+                       .driver_info = EM2860_BOARD_TERRATEC_GRABBY },
+       { USB_DEVICE(0x0ccd, 0x00b2),
+                       .driver_info = EM2884_BOARD_CINERGY_HTC_STICK },
+       { USB_DEVICE(0x0fd9, 0x0033),
+                       .driver_info = EM2860_BOARD_ELGATO_VIDEO_CAPTURE},
+       { USB_DEVICE(0x185b, 0x2870),
+                       .driver_info = EM2870_BOARD_COMPRO_VIDEOMATE },
+       { USB_DEVICE(0x185b, 0x2041),
+                       .driver_info = EM2820_BOARD_COMPRO_VIDEOMATE_FORYOU },
+       { USB_DEVICE(0x2040, 0x4200),
+                       .driver_info = EM2820_BOARD_HAUPPAUGE_WINTV_USB_2 },
+       { USB_DEVICE(0x2040, 0x4201),
+                       .driver_info = EM2820_BOARD_HAUPPAUGE_WINTV_USB_2 },
+       { USB_DEVICE(0x2040, 0x6500),
+                       .driver_info = EM2880_BOARD_HAUPPAUGE_WINTV_HVR_900 },
+       { USB_DEVICE(0x2040, 0x6502),
+                       .driver_info = EM2880_BOARD_HAUPPAUGE_WINTV_HVR_900_R2 },
+       { USB_DEVICE(0x2040, 0x6513), /* HCW HVR-980 */
+                       .driver_info = EM2883_BOARD_HAUPPAUGE_WINTV_HVR_950 },
+       { USB_DEVICE(0x2040, 0x6517), /* HP  HVR-950 */
+                       .driver_info = EM2883_BOARD_HAUPPAUGE_WINTV_HVR_950 },
+       { USB_DEVICE(0x2040, 0x651b), /* RP  HVR-950 */
+                       .driver_info = EM2883_BOARD_HAUPPAUGE_WINTV_HVR_950 },
+       { USB_DEVICE(0x2040, 0x651f),
+                       .driver_info = EM2883_BOARD_HAUPPAUGE_WINTV_HVR_850 },
+       { USB_DEVICE(0x0438, 0xb002),
+                       .driver_info = EM2880_BOARD_AMD_ATI_TV_WONDER_HD_600 },
+       { USB_DEVICE(0x2001, 0xf112),
+                       .driver_info = EM2820_BOARD_DLINK_USB_TV },
+       { USB_DEVICE(0x2304, 0x0207),
+                       .driver_info = EM2820_BOARD_PINNACLE_DVC_90 },
+       { USB_DEVICE(0x2304, 0x0208),
+                       .driver_info = EM2820_BOARD_PINNACLE_USB_2 },
+       { USB_DEVICE(0x2304, 0x021a),
+                       .driver_info = EM2820_BOARD_PINNACLE_DVC_90 },
+       { USB_DEVICE(0x2304, 0x0226),
+                       .driver_info = EM2882_BOARD_PINNACLE_HYBRID_PRO_330E },
+       { USB_DEVICE(0x2304, 0x0227),
+                       .driver_info = EM2880_BOARD_PINNACLE_PCTV_HD_PRO },
+       { USB_DEVICE(0x0413, 0x6023),
+                       .driver_info = EM2800_BOARD_LEADTEK_WINFAST_USBII },
+       { USB_DEVICE(0x093b, 0xa003),
+                      .driver_info = EM2820_BOARD_PINNACLE_DVC_90 },
+       { USB_DEVICE(0x093b, 0xa005),
+                       .driver_info = EM2861_BOARD_PLEXTOR_PX_TV100U },
+       { USB_DEVICE(0x04bb, 0x0515),
+                       .driver_info = EM2820_BOARD_IODATA_GVMVP_SZ },
+       { USB_DEVICE(0xeb1a, 0x50a6),
+                       .driver_info = EM2860_BOARD_GADMEI_UTV330 },
+       { USB_DEVICE(0x1b80, 0xa340),
+                       .driver_info = EM2870_BOARD_KWORLD_A340 },
+       { USB_DEVICE(0x2013, 0x024f),
+                       .driver_info = EM28174_BOARD_PCTV_290E },
+       { USB_DEVICE(0x2013, 0x024c),
+                       .driver_info = EM28174_BOARD_PCTV_460E },
+       { USB_DEVICE(0x2040, 0x1605),
+                       .driver_info = EM2884_BOARD_HAUPPAUGE_WINTV_HVR_930C },
+       { USB_DEVICE(0xeb1a, 0x5006),
+                       .driver_info = EM2860_BOARD_HT_VIDBOX_NW03 },
+       { USB_DEVICE(0x1b80, 0xe309), /* Sveon STV40 */
+                       .driver_info = EM2860_BOARD_EASYCAP },
+       { USB_DEVICE(0x1b80, 0xe425),
+                       .driver_info = EM2874_BOARD_MAXMEDIA_UB425_TC },
+       { USB_DEVICE(0x2304, 0x0242),
+                       .driver_info = EM2884_BOARD_PCTV_510E },
+       { USB_DEVICE(0x2013, 0x0251),
+                       .driver_info = EM2884_BOARD_PCTV_520E },
+       { },
+ };
+ MODULE_DEVICE_TABLE(usb, em28xx_id_table);
+ /*
+  * EEPROM hash table for devices with generic USB IDs
+  */
+ static struct em28xx_hash_table em28xx_eeprom_hash[] = {
+       /* P/N: SA 60002070465 Tuner: TVF7533-MF */
+       {0x6ce05a8f, EM2820_BOARD_PROLINK_PLAYTV_USB2, TUNER_YMEC_TVF_5533MF},
+       {0x72cc5a8b, EM2820_BOARD_PROLINK_PLAYTV_BOX4_USB2, TUNER_YMEC_TVF_5533MF},
+       {0x966a0441, EM2880_BOARD_KWORLD_DVB_310U, TUNER_XC2028},
+       {0x166a0441, EM2880_BOARD_EMPIRE_DUAL_TV, TUNER_XC2028},
+       {0xcee44a99, EM2882_BOARD_EVGA_INDTUBE, TUNER_XC2028},
+       {0xb8846b20, EM2881_BOARD_PINNACLE_HYBRID_PRO, TUNER_XC2028},
+       {0x63f653bd, EM2870_BOARD_REDDO_DVB_C_USB_BOX, TUNER_ABSENT},
+       {0x4e913442, EM2882_BOARD_DIKOM_DK300, TUNER_XC2028},
+ };
+ /* I2C devicelist hash table for devices with generic USB IDs */
+ static struct em28xx_hash_table em28xx_i2c_hash[] = {
+       {0xb06a32c3, EM2800_BOARD_TERRATEC_CINERGY_200, TUNER_LG_PAL_NEW_TAPC},
+       {0xf51200e3, EM2800_BOARD_VGEAR_POCKETTV, TUNER_LG_PAL_NEW_TAPC},
+       {0x1ba50080, EM2860_BOARD_SAA711X_REFERENCE_DESIGN, TUNER_ABSENT},
+       {0x77800080, EM2860_BOARD_TVP5150_REFERENCE_DESIGN, TUNER_ABSENT},
+       {0xc51200e3, EM2820_BOARD_GADMEI_TVR200, TUNER_LG_PAL_NEW_TAPC},
+       {0x4ba50080, EM2861_BOARD_GADMEI_UTV330PLUS, TUNER_TNF_5335MF},
+       {0x6b800080, EM2874_BOARD_LEADERSHIP_ISDBT, TUNER_ABSENT},
+ };
+ /* I2C possible address to saa7115, tvp5150, msp3400, tvaudio */
+ static unsigned short saa711x_addrs[] = {
+       0x4a >> 1, 0x48 >> 1,   /* SAA7111, SAA7111A and SAA7113 */
+       0x42 >> 1, 0x40 >> 1,   /* SAA7114, SAA7115 and SAA7118 */
+       I2C_CLIENT_END };
+ static unsigned short tvp5150_addrs[] = {
+       0xb8 >> 1,
+       0xba >> 1,
+       I2C_CLIENT_END
+ };
+ static unsigned short msp3400_addrs[] = {
+       0x80 >> 1,
+       0x88 >> 1,
+       I2C_CLIENT_END
+ };
+ int em28xx_tuner_callback(void *ptr, int component, int command, int arg)
+ {
+       int rc = 0;
+       struct em28xx *dev = ptr;
+       if (dev->tuner_type != TUNER_XC2028 && dev->tuner_type != TUNER_XC5000)
+               return 0;
+       if (command != XC2028_TUNER_RESET && command != XC5000_TUNER_RESET)
+               return 0;
+       rc = em28xx_gpio_set(dev, dev->board.tuner_gpio);
+       return rc;
+ }
+ EXPORT_SYMBOL_GPL(em28xx_tuner_callback);
+ static inline void em28xx_set_model(struct em28xx *dev)
+ {
+       memcpy(&dev->board, &em28xx_boards[dev->model], sizeof(dev->board));
+       /* Those are the default values for the majority of boards
+          Use those values if not specified otherwise at boards entry
+        */
+       if (!dev->board.xclk)
+               dev->board.xclk = EM28XX_XCLK_IR_RC5_MODE |
+                                 EM28XX_XCLK_FREQUENCY_12MHZ;
+       if (!dev->board.i2c_speed)
+               dev->board.i2c_speed = EM28XX_I2C_CLK_WAIT_ENABLE |
+                                      EM28XX_I2C_FREQ_100_KHZ;
+ }
+ /* FIXME: Should be replaced by a proper mt9m111 driver */
+ static int em28xx_initialize_mt9m111(struct em28xx *dev)
+ {
+       int i;
+       unsigned char regs[][3] = {
+               { 0x0d, 0x00, 0x01, },  /* reset and use defaults */
+               { 0x0d, 0x00, 0x00, },
+               { 0x0a, 0x00, 0x21, },
+               { 0x21, 0x04, 0x00, },  /* full readout speed, no row/col skipping */
+       };
+       for (i = 0; i < ARRAY_SIZE(regs); i++)
+               i2c_master_send(&dev->i2c_client, &regs[i][0], 3);
+       return 0;
+ }
+ /* FIXME: Should be replaced by a proper mt9m001 driver */
+ static int em28xx_initialize_mt9m001(struct em28xx *dev)
+ {
+       int i;
+       unsigned char regs[][3] = {
+               { 0x0d, 0x00, 0x01, },
+               { 0x0d, 0x00, 0x00, },
+               { 0x04, 0x05, 0x00, },  /* hres = 1280 */
+               { 0x03, 0x04, 0x00, },  /* vres = 1024 */
+               { 0x20, 0x11, 0x00, },
+               { 0x06, 0x00, 0x10, },
+               { 0x2b, 0x00, 0x24, },
+               { 0x2e, 0x00, 0x24, },
+               { 0x35, 0x00, 0x24, },
+               { 0x2d, 0x00, 0x20, },
+               { 0x2c, 0x00, 0x20, },
+               { 0x09, 0x0a, 0xd4, },
+               { 0x35, 0x00, 0x57, },
+       };
+       for (i = 0; i < ARRAY_SIZE(regs); i++)
+               i2c_master_send(&dev->i2c_client, &regs[i][0], 3);
+       return 0;
+ }
+ /* HINT method: webcam I2C chips
+  *
+  * This method works for webcams with Micron sensors
+  */
+ static int em28xx_hint_sensor(struct em28xx *dev)
+ {
+       int rc;
+       char *sensor_name;
+       unsigned char cmd;
+       __be16 version_be;
+       u16 version;
+       /* Micron sensor detection */
+       dev->i2c_client.addr = 0xba >> 1;
+       cmd = 0;
+       i2c_master_send(&dev->i2c_client, &cmd, 1);
+       rc = i2c_master_recv(&dev->i2c_client, (char *)&version_be, 2);
+       if (rc != 2)
+               return -EINVAL;
+       version = be16_to_cpu(version_be);
+       switch (version) {
+       case 0x8232:            /* mt9v011 640x480 1.3 Mpix sensor */
+       case 0x8243:            /* mt9v011 rev B 640x480 1.3 Mpix sensor */
+               dev->model = EM2820_BOARD_SILVERCREST_WEBCAM;
+               em28xx_set_model(dev);
+               sensor_name = "mt9v011";
+               dev->em28xx_sensor = EM28XX_MT9V011;
+               dev->sensor_xres = 640;
+               dev->sensor_yres = 480;
+               /*
+                * FIXME: mt9v011 uses I2S speed as xtal clk - at least with
+                * the Silvercrest cam I have here for testing - for higher
+                * resolutions, a high clock cause horizontal artifacts, so we
+                * need to use a lower xclk frequency.
+                * Yet, it would be possible to adjust xclk depending on the
+                * desired resolution, since this affects directly the
+                * frame rate.
+                */
+               dev->board.xclk = EM28XX_XCLK_FREQUENCY_4_3MHZ;
+               dev->sensor_xtal = 4300000;
+               /* probably means GRGB 16 bit bayer */
+               dev->vinmode = 0x0d;
+               dev->vinctl = 0x00;
+               break;
+       case 0x143a:    /* MT9M111 as found in the ECS G200 */
+               dev->model = EM2750_BOARD_UNKNOWN;
+               em28xx_set_model(dev);
+               sensor_name = "mt9m111";
+               dev->board.xclk = EM28XX_XCLK_FREQUENCY_48MHZ;
+               dev->em28xx_sensor = EM28XX_MT9M111;
+               em28xx_initialize_mt9m111(dev);
+               dev->sensor_xres = 640;
+               dev->sensor_yres = 512;
+               dev->vinmode = 0x0a;
+               dev->vinctl = 0x00;
+               break;
+       case 0x8431:
+               dev->model = EM2750_BOARD_UNKNOWN;
+               em28xx_set_model(dev);
+               sensor_name = "mt9m001";
+               dev->em28xx_sensor = EM28XX_MT9M001;
+               em28xx_initialize_mt9m001(dev);
+               dev->sensor_xres = 1280;
+               dev->sensor_yres = 1024;
+               /* probably means BGGR 16 bit bayer */
+               dev->vinmode = 0x0c;
+               dev->vinctl = 0x00;
+               break;
+       default:
+               printk("Unknown Micron Sensor 0x%04x\n", version);
+               return -EINVAL;
+       }
+       /* Setup webcam defaults */
+       em28xx_pre_card_setup(dev);
+       em28xx_errdev("Sensor is %s, using model %s entry.\n",
+                     sensor_name, em28xx_boards[dev->model].name);
+       return 0;
+ }
+ /* Since em28xx_pre_card_setup() requires a proper dev->model,
+  * this won't work for boards with generic PCI IDs
+  */
+ static void em28xx_pre_card_setup(struct em28xx *dev)
+ {
+       /* Set the initial XCLK and I2C clock values based on the board
+          definition */
+       em28xx_write_reg(dev, EM28XX_R0F_XCLK, dev->board.xclk & 0x7f);
+       if (!dev->board.is_em2800)
+               em28xx_write_reg(dev, EM28XX_R06_I2C_CLK, dev->board.i2c_speed);
+       msleep(50);
+       /* request some modules */
+       switch (dev->model) {
+       case EM2861_BOARD_PLEXTOR_PX_TV100U:
+               /* Sets the msp34xx I2S speed */
+               dev->i2s_speed = 2048000;
+               break;
+       case EM2861_BOARD_KWORLD_PVRTV_300U:
+       case EM2880_BOARD_KWORLD_DVB_305U:
+               em28xx_write_reg(dev, EM28XX_R08_GPIO, 0x6d);
+               msleep(10);
+               em28xx_write_reg(dev, EM28XX_R08_GPIO, 0x7d);
+               msleep(10);
+               break;
+       case EM2870_BOARD_COMPRO_VIDEOMATE:
+               /* TODO: someone can do some cleanup here...
+                        not everything's needed */
+               em28xx_write_reg(dev, EM2880_R04_GPO, 0x00);
+               msleep(10);
+               em28xx_write_reg(dev, EM2880_R04_GPO, 0x01);
+               msleep(10);
+               em28xx_write_reg(dev, EM28XX_R08_GPIO, 0xfd);
+               mdelay(70);
+               em28xx_write_reg(dev, EM28XX_R08_GPIO, 0xfc);
+               mdelay(70);
+               em28xx_write_reg(dev, EM28XX_R08_GPIO, 0xdc);
+               mdelay(70);
+               em28xx_write_reg(dev, EM28XX_R08_GPIO, 0xfc);
+               mdelay(70);
+               break;
+       case EM2870_BOARD_TERRATEC_XS_MT2060:
+               /* this device needs some gpio writes to get the DVB-T
+                  demod work */
+               em28xx_write_reg(dev, EM28XX_R08_GPIO, 0xfe);
+               mdelay(70);
+               em28xx_write_reg(dev, EM28XX_R08_GPIO, 0xde);
+               mdelay(70);
+               em28xx_write_reg(dev, EM28XX_R08_GPIO, 0xfe);
+               mdelay(70);
+               break;
+       case EM2870_BOARD_PINNACLE_PCTV_DVB:
+               /* this device needs some gpio writes to get the
+                  DVB-T demod work */
+               em28xx_write_reg(dev, EM28XX_R08_GPIO, 0xfe);
+               mdelay(70);
+               em28xx_write_reg(dev, EM28XX_R08_GPIO, 0xde);
+               mdelay(70);
+               em28xx_write_reg(dev, EM28XX_R08_GPIO, 0xfe);
+               mdelay(70);
+               break;
+       case EM2820_BOARD_GADMEI_UTV310:
+       case EM2820_BOARD_MSI_VOX_USB_2:
+               /* enables audio for that devices */
+               em28xx_write_reg(dev, EM28XX_R08_GPIO, 0xfd);
+               break;
+       case EM2882_BOARD_KWORLD_ATSC_315U:
+               em28xx_write_reg(dev, EM28XX_R08_GPIO, 0xff);
+               msleep(10);
+               em28xx_write_reg(dev, EM28XX_R08_GPIO, 0xfe);
+               msleep(10);
+               em28xx_write_reg(dev, EM2880_R04_GPO, 0x00);
+               msleep(10);
+               em28xx_write_reg(dev, EM2880_R04_GPO, 0x08);
+               msleep(10);
+               break;
+       case EM2860_BOARD_KAIOMY_TVNPC_U2:
+               em28xx_write_regs(dev, EM28XX_R0F_XCLK, "\x07", 1);
+               em28xx_write_regs(dev, EM28XX_R06_I2C_CLK, "\x40", 1);
+               em28xx_write_regs(dev, 0x0d, "\x42", 1);
+               em28xx_write_regs(dev, 0x08, "\xfd", 1);
+               msleep(10);
+               em28xx_write_regs(dev, 0x08, "\xff", 1);
+               msleep(10);
+               em28xx_write_regs(dev, 0x08, "\x7f", 1);
+               msleep(10);
+               em28xx_write_regs(dev, 0x08, "\x6b", 1);
+               break;
+       case EM2860_BOARD_EASYCAP:
+               em28xx_write_regs(dev, 0x08, "\xf8", 1);
+               break;
+       case EM2820_BOARD_IODATA_GVMVP_SZ:
+               em28xx_write_reg(dev, EM28XX_R08_GPIO, 0xff);
+               msleep(70);
+               em28xx_write_reg(dev, EM28XX_R08_GPIO, 0xf7);
+               msleep(10);
+               em28xx_write_reg(dev, EM28XX_R08_GPIO, 0xfe);
+               msleep(70);
+               em28xx_write_reg(dev, EM28XX_R08_GPIO, 0xfd);
+               msleep(70);
+               break;
+       }
+       em28xx_gpio_set(dev, dev->board.tuner_gpio);
+       em28xx_set_mode(dev, EM28XX_ANALOG_MODE);
+       /* Unlock device */
+       em28xx_set_mode(dev, EM28XX_SUSPEND);
+ }
+ static void em28xx_setup_xc3028(struct em28xx *dev, struct xc2028_ctrl *ctl)
+ {
+       memset(ctl, 0, sizeof(*ctl));
+       ctl->fname   = XC2028_DEFAULT_FIRMWARE;
+       ctl->max_len = 64;
+       ctl->mts = em28xx_boards[dev->model].mts_firmware;
+       switch (dev->model) {
+       case EM2880_BOARD_EMPIRE_DUAL_TV:
+       case EM2880_BOARD_HAUPPAUGE_WINTV_HVR_900:
+       case EM2882_BOARD_TERRATEC_HYBRID_XS:
+               ctl->demod = XC3028_FE_ZARLINK456;
+               break;
+       case EM2880_BOARD_TERRATEC_HYBRID_XS:
+       case EM2880_BOARD_TERRATEC_HYBRID_XS_FR:
+       case EM2881_BOARD_PINNACLE_HYBRID_PRO:
+               ctl->demod = XC3028_FE_ZARLINK456;
+               break;
+       case EM2880_BOARD_HAUPPAUGE_WINTV_HVR_900_R2:
+       case EM2882_BOARD_PINNACLE_HYBRID_PRO_330E:
+               ctl->demod = XC3028_FE_DEFAULT;
+               break;
+       case EM2880_BOARD_AMD_ATI_TV_WONDER_HD_600:
+               ctl->demod = XC3028_FE_DEFAULT;
+               ctl->fname = XC3028L_DEFAULT_FIRMWARE;
+               break;
+       case EM2883_BOARD_HAUPPAUGE_WINTV_HVR_850:
+       case EM2883_BOARD_HAUPPAUGE_WINTV_HVR_950:
+       case EM2880_BOARD_PINNACLE_PCTV_HD_PRO:
+               /* FIXME: Better to specify the needed IF */
+               ctl->demod = XC3028_FE_DEFAULT;
+               break;
+       case EM2883_BOARD_KWORLD_HYBRID_330U:
+       case EM2882_BOARD_DIKOM_DK300:
+       case EM2882_BOARD_KWORLD_VS_DVBT:
+               ctl->demod = XC3028_FE_CHINA;
+               ctl->fname = XC2028_DEFAULT_FIRMWARE;
+               break;
+       case EM2882_BOARD_EVGA_INDTUBE:
+               ctl->demod = XC3028_FE_CHINA;
+               ctl->fname = XC3028L_DEFAULT_FIRMWARE;
+               break;
+       default:
+               ctl->demod = XC3028_FE_OREN538;
+       }
+ }
+ static void em28xx_tuner_setup(struct em28xx *dev)
+ {
+       struct tuner_setup           tun_setup;
+       struct v4l2_frequency        f;
+       if (dev->tuner_type == TUNER_ABSENT)
+               return;
+       memset(&tun_setup, 0, sizeof(tun_setup));
+       tun_setup.mode_mask = T_ANALOG_TV | T_RADIO;
+       tun_setup.tuner_callback = em28xx_tuner_callback;
+       if (dev->board.radio.type) {
+               tun_setup.type = dev->board.radio.type;
+               tun_setup.addr = dev->board.radio_addr;
+               v4l2_device_call_all(&dev->v4l2_dev, 0, tuner, s_type_addr, &tun_setup);
+       }
+       if ((dev->tuner_type != TUNER_ABSENT) && (dev->tuner_type)) {
+               tun_setup.type   = dev->tuner_type;
+               tun_setup.addr   = dev->tuner_addr;
+               v4l2_device_call_all(&dev->v4l2_dev, 0, tuner, s_type_addr, &tun_setup);
+       }
+       if (dev->tda9887_conf) {
+               struct v4l2_priv_tun_config tda9887_cfg;
+               tda9887_cfg.tuner = TUNER_TDA9887;
+               tda9887_cfg.priv = &dev->tda9887_conf;
+               v4l2_device_call_all(&dev->v4l2_dev, 0, tuner, s_config, &tda9887_cfg);
+       }
+       if (dev->tuner_type == TUNER_XC2028) {
+               struct v4l2_priv_tun_config  xc2028_cfg;
+               struct xc2028_ctrl           ctl;
+               memset(&xc2028_cfg, 0, sizeof(xc2028_cfg));
+               memset(&ctl, 0, sizeof(ctl));
+               em28xx_setup_xc3028(dev, &ctl);
+               xc2028_cfg.tuner = TUNER_XC2028;
+               xc2028_cfg.priv  = &ctl;
+               v4l2_device_call_all(&dev->v4l2_dev, 0, tuner, s_config, &xc2028_cfg);
+       }
+       /* configure tuner */
+       f.tuner = 0;
+       f.type = V4L2_TUNER_ANALOG_TV;
+       f.frequency = 9076;     /* just a magic number */
+       dev->ctl_freq = f.frequency;
+       v4l2_device_call_all(&dev->v4l2_dev, 0, tuner, s_frequency, &f);
+ }
+ static int em28xx_hint_board(struct em28xx *dev)
+ {
+       int i;
+       /* HINT method: EEPROM
+        *
+        * This method works only for boards with eeprom.
+        * Uses a hash of all eeprom bytes. The hash should be
+        * unique for a vendor/tuner pair.
+        * There are a high chance that tuners for different
+        * video standards produce different hashes.
+        */
+       for (i = 0; i < ARRAY_SIZE(em28xx_eeprom_hash); i++) {
+               if (dev->hash == em28xx_eeprom_hash[i].hash) {
+                       dev->model = em28xx_eeprom_hash[i].model;
+                       dev->tuner_type = em28xx_eeprom_hash[i].tuner;
+                       em28xx_errdev("Your board has no unique USB ID.\n");
+                       em28xx_errdev("A hint were successfully done, "
+                                     "based on eeprom hash.\n");
+                       em28xx_errdev("This method is not 100%% failproof.\n");
+                       em28xx_errdev("If the board were missdetected, "
+                                     "please email this log to:\n");
+                       em28xx_errdev("\tV4L Mailing List "
+                                     " <linux-media@vger.kernel.org>\n");
+                       em28xx_errdev("Board detected as %s\n",
+                                     em28xx_boards[dev->model].name);
+                       return 0;
+               }
+       }
+       /* HINT method: I2C attached devices
+        *
+        * This method works for all boards.
+        * Uses a hash of i2c scanned devices.
+        * Devices with the same i2c attached chips will
+        * be considered equal.
+        * This method is less precise than the eeprom one.
+        */
+       /* user did not request i2c scanning => do it now */
+       if (!dev->i2c_hash)
+               em28xx_do_i2c_scan(dev);
+       for (i = 0; i < ARRAY_SIZE(em28xx_i2c_hash); i++) {
+               if (dev->i2c_hash == em28xx_i2c_hash[i].hash) {
+                       dev->model = em28xx_i2c_hash[i].model;
+                       dev->tuner_type = em28xx_i2c_hash[i].tuner;
+                       em28xx_errdev("Your board has no unique USB ID.\n");
+                       em28xx_errdev("A hint were successfully done, "
+                                     "based on i2c devicelist hash.\n");
+                       em28xx_errdev("This method is not 100%% failproof.\n");
+                       em28xx_errdev("If the board were missdetected, "
+                                     "please email this log to:\n");
+                       em28xx_errdev("\tV4L Mailing List "
+                                     " <linux-media@vger.kernel.org>\n");
+                       em28xx_errdev("Board detected as %s\n",
+                                     em28xx_boards[dev->model].name);
+                       return 0;
+               }
+       }
+       em28xx_errdev("Your board has no unique USB ID and thus need a "
+                     "hint to be detected.\n");
+       em28xx_errdev("You may try to use card=<n> insmod option to "
+                     "workaround that.\n");
+       em28xx_errdev("Please send an email with this log to:\n");
+       em28xx_errdev("\tV4L Mailing List <linux-media@vger.kernel.org>\n");
+       em28xx_errdev("Board eeprom hash is 0x%08lx\n", dev->hash);
+       em28xx_errdev("Board i2c devicelist hash is 0x%08lx\n", dev->i2c_hash);
+       em28xx_errdev("Here is a list of valid choices for the card=<n>"
+                     " insmod option:\n");
+       for (i = 0; i < em28xx_bcount; i++) {
+               em28xx_errdev("    card=%d -> %s\n",
+                               i, em28xx_boards[i].name);
+       }
+       return -1;
+ }
+ static void em28xx_card_setup(struct em28xx *dev)
+ {
+       /*
+        * If the device can be a webcam, seek for a sensor.
+        * If sensor is not found, then it isn't a webcam.
+        */
+       if (dev->board.is_webcam) {
+               if (em28xx_hint_sensor(dev) < 0)
+                       dev->board.is_webcam = 0;
+               else
+                       dev->progressive = 1;
+       }
+       if (!dev->board.is_webcam) {
+               switch (dev->model) {
+               case EM2820_BOARD_UNKNOWN:
+               case EM2800_BOARD_UNKNOWN:
+               /*
+                * The K-WORLD DVB-T 310U is detected as an MSI Digivox AD.
+                *
+                * This occurs because they share identical USB vendor and
+                * product IDs.
+                *
+                * What we do here is look up the EEPROM hash of the K-WORLD
+                * and if it is found then we decide that we do not have
+                * a DIGIVOX and reset the device to the K-WORLD instead.
+                *
+                * This solution is only valid if they do not share eeprom
+                * hash identities which has not been determined as yet.
+                */
+               if (em28xx_hint_board(dev) < 0)
+                       em28xx_errdev("Board not discovered\n");
+               else {
+                       em28xx_set_model(dev);
+                       em28xx_pre_card_setup(dev);
+               }
+               break;
+               default:
+                       em28xx_set_model(dev);
+               }
+       }
+       em28xx_info("Identified as %s (card=%d)\n",
+                   dev->board.name, dev->model);
+       dev->tuner_type = em28xx_boards[dev->model].tuner_type;
+       if (em28xx_boards[dev->model].tuner_addr)
+               dev->tuner_addr = em28xx_boards[dev->model].tuner_addr;
+       if (em28xx_boards[dev->model].tda9887_conf)
+               dev->tda9887_conf = em28xx_boards[dev->model].tda9887_conf;
+       /* request some modules */
+       switch (dev->model) {
+       case EM2820_BOARD_HAUPPAUGE_WINTV_USB_2:
+       case EM2880_BOARD_HAUPPAUGE_WINTV_HVR_900:
+       case EM2880_BOARD_HAUPPAUGE_WINTV_HVR_900_R2:
+       case EM2883_BOARD_HAUPPAUGE_WINTV_HVR_850:
+       case EM2883_BOARD_HAUPPAUGE_WINTV_HVR_950:
+       {
+               struct tveeprom tv;
+ #if defined(CONFIG_MODULES) && defined(MODULE)
+               request_module("tveeprom");
+ #endif
+               /* Call first TVeeprom */
+               dev->i2c_client.addr = 0xa0 >> 1;
+               tveeprom_hauppauge_analog(&dev->i2c_client, &tv, dev->eedata);
+               dev->tuner_type = tv.tuner_type;
+               if (tv.audio_processor == V4L2_IDENT_MSPX4XX) {
+                       dev->i2s_speed = 2048000;
+                       dev->board.has_msp34xx = 1;
+               }
+               break;
+       }
+       case EM2882_BOARD_KWORLD_ATSC_315U:
+               em28xx_write_reg(dev, 0x0d, 0x42);
+               msleep(10);
+               em28xx_write_reg(dev, EM28XX_R08_GPIO, 0xfd);
+               msleep(10);
+               break;
+       case EM2820_BOARD_KWORLD_PVRTV2800RF:
+               /* GPIO enables sound on KWORLD PVR TV 2800RF */
+               em28xx_write_reg(dev, EM28XX_R08_GPIO, 0xf9);
+               break;
+       case EM2820_BOARD_UNKNOWN:
+       case EM2800_BOARD_UNKNOWN:
+               /*
+                * The K-WORLD DVB-T 310U is detected as an MSI Digivox AD.
+                *
+                * This occurs because they share identical USB vendor and
+                * product IDs.
+                *
+                * What we do here is look up the EEPROM hash of the K-WORLD
+                * and if it is found then we decide that we do not have
+                * a DIGIVOX and reset the device to the K-WORLD instead.
+                *
+                * This solution is only valid if they do not share eeprom
+                * hash identities which has not been determined as yet.
+                */
+       case EM2880_BOARD_MSI_DIGIVOX_AD:
+               if (!em28xx_hint_board(dev))
+                       em28xx_set_model(dev);
+               /* In cases where we had to use a board hint, the call to
+                  em28xx_set_mode() in em28xx_pre_card_setup() was a no-op,
+                  so make the call now so the analog GPIOs are set properly
+                  before probing the i2c bus. */
+               em28xx_gpio_set(dev, dev->board.tuner_gpio);
+               em28xx_set_mode(dev, EM28XX_ANALOG_MODE);
+               break;
+ /*
+                * The Dikom DK300 is detected as an Kworld VS-DVB-T 323UR.
+                *
+                * This occurs because they share identical USB vendor and
+                * product IDs.
+                *
+                * What we do here is look up the EEPROM hash of the Dikom
+                * and if it is found then we decide that we do not have
+                * a Kworld and reset the device to the Dikom instead.
+                *
+                * This solution is only valid if they do not share eeprom
+                * hash identities which has not been determined as yet.
+                */
+       case EM2882_BOARD_KWORLD_VS_DVBT:
+               if (!em28xx_hint_board(dev))
+                       em28xx_set_model(dev);
+               /* In cases where we had to use a board hint, the call to
+                  em28xx_set_mode() in em28xx_pre_card_setup() was a no-op,
+                  so make the call now so the analog GPIOs are set properly
+                  before probing the i2c bus. */
+               em28xx_gpio_set(dev, dev->board.tuner_gpio);
+               em28xx_set_mode(dev, EM28XX_ANALOG_MODE);
+               break;
+       }
+       if (dev->board.valid == EM28XX_BOARD_NOT_VALIDATED) {
+               em28xx_errdev("\n\n");
+               em28xx_errdev("The support for this board weren't "
+                             "valid yet.\n");
+               em28xx_errdev("Please send a report of having this working\n");
+               em28xx_errdev("not to V4L mailing list (and/or to other "
+                               "addresses)\n\n");
+       }
+       /* Allow override tuner type by a module parameter */
+       if (tuner >= 0)
+               dev->tuner_type = tuner;
+       /* request some modules */
+       if (dev->board.has_msp34xx)
+               v4l2_i2c_new_subdev(&dev->v4l2_dev, &dev->i2c_adap,
+                       "msp3400", 0, msp3400_addrs);
+       if (dev->board.decoder == EM28XX_SAA711X)
+               v4l2_i2c_new_subdev(&dev->v4l2_dev, &dev->i2c_adap,
+                       "saa7115_auto", 0, saa711x_addrs);
+       if (dev->board.decoder == EM28XX_TVP5150)
+               v4l2_i2c_new_subdev(&dev->v4l2_dev, &dev->i2c_adap,
+                       "tvp5150", 0, tvp5150_addrs);
+       if (dev->em28xx_sensor == EM28XX_MT9V011) {
+               struct mt9v011_platform_data pdata;
+               struct i2c_board_info mt9v011_info = {
+                       .type = "mt9v011",
+                       .addr = 0xba >> 1,
+                       .platform_data = &pdata,
+               };
+               pdata.xtal = dev->sensor_xtal;
+               v4l2_i2c_new_subdev_board(&dev->v4l2_dev, &dev->i2c_adap,
+                               &mt9v011_info, NULL);
+       }
+       if (dev->board.adecoder == EM28XX_TVAUDIO)
+               v4l2_i2c_new_subdev(&dev->v4l2_dev, &dev->i2c_adap,
+                       "tvaudio", dev->board.tvaudio_addr, NULL);
+       if (dev->board.tuner_type != TUNER_ABSENT) {
+               int has_demod = (dev->tda9887_conf & TDA9887_PRESENT);
+               if (dev->board.radio.type)
+                       v4l2_i2c_new_subdev(&dev->v4l2_dev, &dev->i2c_adap,
+                               "tuner", dev->board.radio_addr, NULL);
+               if (has_demod)
+                       v4l2_i2c_new_subdev(&dev->v4l2_dev,
+                               &dev->i2c_adap, "tuner",
+                               0, v4l2_i2c_tuner_addrs(ADDRS_DEMOD));
+               if (dev->tuner_addr == 0) {
+                       enum v4l2_i2c_tuner_type type =
+                               has_demod ? ADDRS_TV_WITH_DEMOD : ADDRS_TV;
+                       struct v4l2_subdev *sd;
+                       sd = v4l2_i2c_new_subdev(&dev->v4l2_dev,
+                               &dev->i2c_adap, "tuner",
+                               0, v4l2_i2c_tuner_addrs(type));
+                       if (sd)
+                               dev->tuner_addr = v4l2_i2c_subdev_addr(sd);
+               } else {
+                       v4l2_i2c_new_subdev(&dev->v4l2_dev, &dev->i2c_adap,
+                               "tuner", dev->tuner_addr, NULL);
+               }
+       }
+       em28xx_tuner_setup(dev);
+ }
+ static void request_module_async(struct work_struct *work)
+ {
+       struct em28xx *dev = container_of(work,
+                            struct em28xx, request_module_wk);
+       /*
+        * The em28xx extensions can be modules or builtin. If the
+        * modules are already loaded or are built in, those extensions
+        * can be initialised right now. Otherwise, the module init
+        * code will do it.
+        */
+       em28xx_init_extension(dev);
+ #if defined(CONFIG_MODULES) && defined(MODULE)
+       if (dev->has_audio_class)
+               request_module("snd-usb-audio");
+       else if (dev->has_alsa_audio)
+               request_module("em28xx-alsa");
+       if (dev->board.has_dvb)
+               request_module("em28xx-dvb");
+       if (dev->board.ir_codes && !disable_ir)
+               request_module("em28xx-rc");
+ #endif /* CONFIG_MODULES */
+ }
+ static void request_modules(struct em28xx *dev)
+ {
+       INIT_WORK(&dev->request_module_wk, request_module_async);
+       schedule_work(&dev->request_module_wk);
+ }
+ static void flush_request_modules(struct em28xx *dev)
+ {
++      flush_work(&dev->request_module_wk);
+ }
+ /*
+  * em28xx_release_resources()
+  * unregisters the v4l2,i2c and usb devices
+  * called when the device gets disconnected or at module unload
+ */
+ void em28xx_release_resources(struct em28xx *dev)
+ {
+       /*FIXME: I2C IR should be disconnected */
+       em28xx_release_analog_resources(dev);
+       em28xx_i2c_unregister(dev);
+       v4l2_device_unregister(&dev->v4l2_dev);
+       usb_put_dev(dev->udev);
+       /* Mark device as unused */
+       clear_bit(dev->devno, &em28xx_devused);
+ };
+ /*
+  * em28xx_init_dev()
+  * allocates and inits the device structs, registers i2c bus and v4l device
+  */
+ static int em28xx_init_dev(struct em28xx *dev, struct usb_device *udev,
+                          struct usb_interface *interface,
+                          int minor)
+ {
+       int retval;
+       dev->udev = udev;
+       mutex_init(&dev->ctrl_urb_lock);
+       spin_lock_init(&dev->slock);
+       dev->em28xx_write_regs = em28xx_write_regs;
+       dev->em28xx_read_reg = em28xx_read_reg;
+       dev->em28xx_read_reg_req_len = em28xx_read_reg_req_len;
+       dev->em28xx_write_regs_req = em28xx_write_regs_req;
+       dev->em28xx_read_reg_req = em28xx_read_reg_req;
+       dev->board.is_em2800 = em28xx_boards[dev->model].is_em2800;
+       em28xx_set_model(dev);
+       /* Set the default GPO/GPIO for legacy devices */
+       dev->reg_gpo_num = EM2880_R04_GPO;
+       dev->reg_gpio_num = EM28XX_R08_GPIO;
+       dev->wait_after_write = 5;
+       /* Based on the Chip ID, set the device configuration */
+       retval = em28xx_read_reg(dev, EM28XX_R0A_CHIPID);
+       if (retval > 0) {
+               dev->chip_id = retval;
+               switch (dev->chip_id) {
+               case CHIP_ID_EM2800:
+                       em28xx_info("chip ID is em2800\n");
+                       break;
+               case CHIP_ID_EM2710:
+                       em28xx_info("chip ID is em2710\n");
+                       break;
+               case CHIP_ID_EM2750:
+                       em28xx_info("chip ID is em2750\n");
+                       break;
+               case CHIP_ID_EM2820:
+                       em28xx_info("chip ID is em2820 (or em2710)\n");
+                       break;
+               case CHIP_ID_EM2840:
+                       em28xx_info("chip ID is em2840\n");
+                       break;
+               case CHIP_ID_EM2860:
+                       em28xx_info("chip ID is em2860\n");
+                       break;
+               case CHIP_ID_EM2870:
+                       em28xx_info("chip ID is em2870\n");
+                       dev->wait_after_write = 0;
+                       break;
+               case CHIP_ID_EM2874:
+                       em28xx_info("chip ID is em2874\n");
+                       dev->reg_gpio_num = EM2874_R80_GPIO;
+                       dev->wait_after_write = 0;
+                       break;
+               case CHIP_ID_EM28174:
+                       em28xx_info("chip ID is em28174\n");
+                       dev->reg_gpio_num = EM2874_R80_GPIO;
+                       dev->wait_after_write = 0;
+                       break;
+               case CHIP_ID_EM2883:
+                       em28xx_info("chip ID is em2882/em2883\n");
+                       dev->wait_after_write = 0;
+                       break;
+               case CHIP_ID_EM2884:
+                       em28xx_info("chip ID is em2884\n");
+                       dev->reg_gpio_num = EM2874_R80_GPIO;
+                       dev->wait_after_write = 0;
+                       break;
+               default:
+                       em28xx_info("em28xx chip ID = %d\n", dev->chip_id);
+               }
+       }
+       if (dev->is_audio_only) {
+               retval = em28xx_audio_setup(dev);
+               if (retval)
+                       return -ENODEV;
+               em28xx_init_extension(dev);
+               return 0;
+       }
+       /* Prepopulate cached GPO register content */
+       retval = em28xx_read_reg(dev, dev->reg_gpo_num);
+       if (retval >= 0)
+               dev->reg_gpo = retval;
+       em28xx_pre_card_setup(dev);
+       if (!dev->board.is_em2800) {
+               /* Resets I2C speed */
+               retval = em28xx_write_reg(dev, EM28XX_R06_I2C_CLK, dev->board.i2c_speed);
+               if (retval < 0) {
+                       em28xx_errdev("%s: em28xx_write_reg failed!"
+                                     " retval [%d]\n",
+                                     __func__, retval);
+                       return retval;
+               }
+       }
+       retval = v4l2_device_register(&interface->dev, &dev->v4l2_dev);
+       if (retval < 0) {
+               em28xx_errdev("Call to v4l2_device_register() failed!\n");
+               return retval;
+       }
+       /* register i2c bus */
+       retval = em28xx_i2c_register(dev);
+       if (retval < 0) {
+               em28xx_errdev("%s: em28xx_i2c_register - error [%d]!\n",
+                       __func__, retval);
+               goto unregister_dev;
+       }
+       /*
+        * Default format, used for tvp5150 or saa711x output formats
+        */
+       dev->vinmode = 0x10;
+       dev->vinctl  = EM28XX_VINCTRL_INTERLACED |
+                      EM28XX_VINCTRL_CCIR656_ENABLE;
+       /* Do board specific init and eeprom reading */
+       em28xx_card_setup(dev);
+       /* Configure audio */
+       retval = em28xx_audio_setup(dev);
+       if (retval < 0) {
+               em28xx_errdev("%s: Error while setting audio - error [%d]!\n",
+                       __func__, retval);
+               goto fail;
+       }
+       /* wake i2c devices */
+       em28xx_wake_i2c(dev);
+       /* init video dma queues */
+       INIT_LIST_HEAD(&dev->vidq.active);
+       INIT_LIST_HEAD(&dev->vbiq.active);
+       if (dev->board.has_msp34xx) {
+               /* Send a reset to other chips via gpio */
+               retval = em28xx_write_reg(dev, EM28XX_R08_GPIO, 0xf7);
+               if (retval < 0) {
+                       em28xx_errdev("%s: em28xx_write_reg - "
+                                     "msp34xx(1) failed! error [%d]\n",
+                                     __func__, retval);
+                       goto fail;
+               }
+               msleep(3);
+               retval = em28xx_write_reg(dev, EM28XX_R08_GPIO, 0xff);
+               if (retval < 0) {
+                       em28xx_errdev("%s: em28xx_write_reg - "
+                                     "msp34xx(2) failed! error [%d]\n",
+                                     __func__, retval);
+                       goto fail;
+               }
+               msleep(3);
+       }
+       retval = em28xx_register_analog_devices(dev);
+       if (retval < 0) {
+               goto fail;
+       }
+       /* Save some power by putting tuner to sleep */
+       v4l2_device_call_all(&dev->v4l2_dev, 0, core, s_power, 0);
+       return 0;
+ fail:
+       em28xx_i2c_unregister(dev);
+ unregister_dev:
+       v4l2_device_unregister(&dev->v4l2_dev);
+       return retval;
+ }
+ /* high bandwidth multiplier, as encoded in highspeed endpoint descriptors */
+ #define hb_mult(wMaxPacketSize) (1 + (((wMaxPacketSize) >> 11) & 0x03))
+ /*
+  * em28xx_usb_probe()
+  * checks for supported devices
+  */
+ static int em28xx_usb_probe(struct usb_interface *interface,
+                           const struct usb_device_id *id)
+ {
+       struct usb_device *udev;
+       struct em28xx *dev = NULL;
+       int retval;
+       bool has_audio = false, has_video = false, has_dvb = false;
+       int i, nr;
+       const int ifnum = interface->altsetting[0].desc.bInterfaceNumber;
+       char *speed;
+       udev = usb_get_dev(interface_to_usbdev(interface));
+       /* Check to see next free device and mark as used */
+       do {
+               nr = find_first_zero_bit(&em28xx_devused, EM28XX_MAXBOARDS);
+               if (nr >= EM28XX_MAXBOARDS) {
+                       /* No free device slots */
+                       printk(DRIVER_NAME ": Supports only %i em28xx boards.\n",
+                                       EM28XX_MAXBOARDS);
+                       retval = -ENOMEM;
+                       goto err_no_slot;
+               }
+       } while (test_and_set_bit(nr, &em28xx_devused));
+       /* Don't register audio interfaces */
+       if (interface->altsetting[0].desc.bInterfaceClass == USB_CLASS_AUDIO) {
+               em28xx_err(DRIVER_NAME " audio device (%04x:%04x): "
+                       "interface %i, class %i\n",
+                       le16_to_cpu(udev->descriptor.idVendor),
+                       le16_to_cpu(udev->descriptor.idProduct),
+                       ifnum,
+                       interface->altsetting[0].desc.bInterfaceClass);
+               retval = -ENODEV;
+               goto err;
+       }
+       /* allocate memory for our device state and initialize it */
+       dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+       if (dev == NULL) {
+               em28xx_err(DRIVER_NAME ": out of memory!\n");
+               retval = -ENOMEM;
+               goto err;
+       }
+       /* compute alternate max packet sizes */
+       dev->alt_max_pkt_size = kmalloc(sizeof(dev->alt_max_pkt_size[0]) *
+                                       interface->num_altsetting, GFP_KERNEL);
+       if (dev->alt_max_pkt_size == NULL) {
+               em28xx_errdev("out of memory!\n");
+               kfree(dev);
+               retval = -ENOMEM;
+               goto err;
+       }
+       /* Get endpoints */
+       for (i = 0; i < interface->num_altsetting; i++) {
+               int ep;
+               for (ep = 0; ep < interface->altsetting[i].desc.bNumEndpoints; ep++) {
+                       const struct usb_endpoint_descriptor *e;
+                       int sizedescr, size;
+                       e = &interface->altsetting[i].endpoint[ep].desc;
+                       sizedescr = le16_to_cpu(e->wMaxPacketSize);
+                       size = sizedescr & 0x7ff;
+                       if (udev->speed == USB_SPEED_HIGH)
+                               size = size * hb_mult(sizedescr);
+                       if (usb_endpoint_xfer_isoc(e) &&
+                           usb_endpoint_dir_in(e)) {
+                               switch (e->bEndpointAddress) {
+                               case EM28XX_EP_AUDIO:
+                                       has_audio = true;
+                                       break;
+                               case EM28XX_EP_ANALOG:
+                                       has_video = true;
+                                       dev->alt_max_pkt_size[i] = size;
+                                       break;
+                               case EM28XX_EP_DIGITAL:
+                                       has_dvb = true;
+                                       if (size > dev->dvb_max_pkt_size) {
+                                               dev->dvb_max_pkt_size = size;
+                                               dev->dvb_alt = i;
+                                       }
+                                       break;
+                               }
+                       }
+               }
+       }
+       if (!(has_audio || has_video || has_dvb)) {
+               retval = -ENODEV;
+               goto err_free;
+       }
+       switch (udev->speed) {
+       case USB_SPEED_LOW:
+               speed = "1.5";
+               break;
+       case USB_SPEED_UNKNOWN:
+       case USB_SPEED_FULL:
+               speed = "12";
+               break;
+       case USB_SPEED_HIGH:
+               speed = "480";
+               break;
+       default:
+               speed = "unknown";
+       }
+       printk(KERN_INFO DRIVER_NAME
+               ": New device %s %s @ %s Mbps "
+               "(%04x:%04x, interface %d, class %d)\n",
+               udev->manufacturer ? udev->manufacturer : "",
+               udev->product ? udev->product : "",
+               speed,
+               le16_to_cpu(udev->descriptor.idVendor),
+               le16_to_cpu(udev->descriptor.idProduct),
+               ifnum,
+               interface->altsetting->desc.bInterfaceNumber);
+       if (has_audio)
+               printk(KERN_INFO DRIVER_NAME
+                      ": Audio Vendor Class interface %i found\n",
+                      ifnum);
+       if (has_video)
+               printk(KERN_INFO DRIVER_NAME
+                      ": Video interface %i found\n",
+                      ifnum);
+       if (has_dvb)
+               printk(KERN_INFO DRIVER_NAME
+                      ": DVB interface %i found\n",
+                      ifnum);
+       /*
+        * Make sure we have 480 Mbps of bandwidth, otherwise things like
+        * video stream wouldn't likely work, since 12 Mbps is generally
+        * not enough even for most Digital TV streams.
+        */
+       if (udev->speed != USB_SPEED_HIGH && disable_usb_speed_check == 0) {
+               printk(DRIVER_NAME ": Device initialization failed.\n");
+               printk(DRIVER_NAME ": Device must be connected to a high-speed"
+                      " USB 2.0 port.\n");
+               retval = -ENODEV;
+               goto err_free;
+       }
+       snprintf(dev->name, sizeof(dev->name), "em28xx #%d", nr);
+       dev->devno = nr;
+       dev->model = id->driver_info;
+       dev->alt   = -1;
+       dev->is_audio_only = has_audio && !(has_video || has_dvb);
+       dev->has_alsa_audio = has_audio;
+       dev->audio_ifnum = ifnum;
+       /* Checks if audio is provided by some interface */
+       for (i = 0; i < udev->config->desc.bNumInterfaces; i++) {
+               struct usb_interface *uif = udev->config->interface[i];
+               if (uif->altsetting[0].desc.bInterfaceClass == USB_CLASS_AUDIO) {
+                       dev->has_audio_class = 1;
+                       break;
+               }
+       }
+       dev->num_alt = interface->num_altsetting;
+       if ((card[nr] >= 0) && (card[nr] < em28xx_bcount))
+               dev->model = card[nr];
+       /* save our data pointer in this interface device */
+       usb_set_intfdata(interface, dev);
+       /* allocate device struct */
+       mutex_init(&dev->lock);
+       mutex_lock(&dev->lock);
+       retval = em28xx_init_dev(dev, udev, interface, nr);
+       if (retval) {
+               goto unlock_and_free;
+       }
+       if (has_dvb) {
+               /* pre-allocate DVB isoc transfer buffers */
+               retval = em28xx_alloc_isoc(dev, EM28XX_DIGITAL_MODE,
+                                          EM28XX_DVB_MAX_PACKETS,
+                                          EM28XX_DVB_NUM_BUFS,
+                                          dev->dvb_max_pkt_size);
+               if (retval) {
+                       goto unlock_and_free;
+               }
+       }
+       request_modules(dev);
+       /* Should be the last thing to do, to avoid newer udev's to
+          open the device before fully initializing it
+        */
+       mutex_unlock(&dev->lock);
+       return 0;
+ unlock_and_free:
+       mutex_unlock(&dev->lock);
+ err_free:
+       kfree(dev->alt_max_pkt_size);
+       kfree(dev);
+ err:
+       clear_bit(nr, &em28xx_devused);
+ err_no_slot:
+       usb_put_dev(udev);
+       return retval;
+ }
+ /*
+  * em28xx_usb_disconnect()
+  * called when the device gets disconnected
+  * video device will be unregistered on v4l2_close in case it is still open
+  */
+ static void em28xx_usb_disconnect(struct usb_interface *interface)
+ {
+       struct em28xx *dev;
+       dev = usb_get_intfdata(interface);
+       usb_set_intfdata(interface, NULL);
+       if (!dev)
+               return;
+       if (dev->is_audio_only) {
+               mutex_lock(&dev->lock);
+               em28xx_close_extension(dev);
+               mutex_unlock(&dev->lock);
+               return;
+       }
+       em28xx_info("disconnecting %s\n", dev->vdev->name);
+       flush_request_modules(dev);
+       /* wait until all current v4l2 io is finished then deallocate
+          resources */
+       mutex_lock(&dev->lock);
+       v4l2_device_disconnect(&dev->v4l2_dev);
+       if (dev->users) {
+               em28xx_warn
+                   ("device %s is open! Deregistration and memory "
+                    "deallocation are deferred on close.\n",
+                    video_device_node_name(dev->vdev));
+               dev->state |= DEV_MISCONFIGURED;
+               em28xx_uninit_isoc(dev, dev->mode);
+               dev->state |= DEV_DISCONNECTED;
+       } else {
+               dev->state |= DEV_DISCONNECTED;
+               em28xx_release_resources(dev);
+       }
+       /* free DVB isoc buffers */
+       em28xx_uninit_isoc(dev, EM28XX_DIGITAL_MODE);
+       mutex_unlock(&dev->lock);
+       em28xx_close_extension(dev);
+       if (!dev->users) {
+               kfree(dev->alt_max_pkt_size);
+               kfree(dev);
+       }
+ }
+ static struct usb_driver em28xx_usb_driver = {
+       .name = "em28xx",
+       .probe = em28xx_usb_probe,
+       .disconnect = em28xx_usb_disconnect,
+       .id_table = em28xx_id_table,
+ };
+ module_usb_driver(em28xx_usb_driver);
index 0000000000000000000000000000000000000000,034659b1317480a3c389ff9619dd3123c9aeb39d..307d8c5fb7cd771654a1bc08d0c0f94f45c2d103
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,1413 +1,1413 @@@
 -      flush_work_sync(&dev->request_module_wk);
+ /*
+  *  tm6000-cards.c - driver for TM5600/TM6000/TM6010 USB video capture devices
+  *
+  *  Copyright (C) 2006-2007 Mauro Carvalho Chehab <mchehab@infradead.org>
+  *
+  *  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., 675 Mass Ave, Cambridge, MA 02139, USA.
+  */
+ #include <linux/init.h>
+ #include <linux/module.h>
+ #include <linux/pci.h>
+ #include <linux/delay.h>
+ #include <linux/i2c.h>
+ #include <linux/usb.h>
+ #include <linux/slab.h>
+ #include <media/v4l2-common.h>
+ #include <media/tuner.h>
+ #include <media/tvaudio.h>
+ #include <media/i2c-addr.h>
+ #include <media/rc-map.h>
+ #include "tm6000.h"
+ #include "tm6000-regs.h"
+ #include "tuner-xc2028.h"
+ #include "xc5000.h"
+ #define TM6000_BOARD_UNKNOWN                  0
+ #define TM5600_BOARD_GENERIC                  1
+ #define TM6000_BOARD_GENERIC                  2
+ #define TM6010_BOARD_GENERIC                  3
+ #define TM5600_BOARD_10MOONS_UT821            4
+ #define TM5600_BOARD_10MOONS_UT330            5
+ #define TM6000_BOARD_ADSTECH_DUAL_TV          6
+ #define TM6000_BOARD_FREECOM_AND_SIMILAR      7
+ #define TM6000_BOARD_ADSTECH_MINI_DUAL_TV     8
+ #define TM6010_BOARD_HAUPPAUGE_900H           9
+ #define TM6010_BOARD_BEHOLD_WANDER            10
+ #define TM6010_BOARD_BEHOLD_VOYAGER           11
+ #define TM6010_BOARD_TERRATEC_CINERGY_HYBRID_XE       12
+ #define TM6010_BOARD_TWINHAN_TU501            13
+ #define TM6010_BOARD_BEHOLD_WANDER_LITE               14
+ #define TM6010_BOARD_BEHOLD_VOYAGER_LITE      15
+ #define TM5600_BOARD_TERRATEC_GRABSTER                16
+ #define is_generic(model) ((model == TM6000_BOARD_UNKNOWN) || \
+                          (model == TM5600_BOARD_GENERIC) || \
+                          (model == TM6000_BOARD_GENERIC) || \
+                          (model == TM6010_BOARD_GENERIC))
+ #define TM6000_MAXBOARDS        16
+ static unsigned int card[]     = {[0 ... (TM6000_MAXBOARDS - 1)] = UNSET };
+ module_param_array(card,  int, NULL, 0444);
+ static unsigned long tm6000_devused;
+ struct tm6000_board {
+       char            *name;
+       char            eename[16];             /* EEPROM name */
+       unsigned        eename_size;            /* size of EEPROM name */
+       unsigned        eename_pos;             /* Position where it appears at ROM */
+       struct tm6000_capabilities caps;
+       enum            tm6000_devtype type;    /* variant of the chipset */
+       int             tuner_type;     /* type of the tuner */
+       int             tuner_addr;     /* tuner address */
+       int             demod_addr;     /* demodulator address */
+       struct tm6000_gpio gpio;
+       struct tm6000_input     vinput[3];
+       struct tm6000_input     rinput;
+       char            *ir_codes;
+ };
+ static struct tm6000_board tm6000_boards[] = {
+       [TM6000_BOARD_UNKNOWN] = {
+               .name         = "Unknown tm6000 video grabber",
+               .caps = {
+                       .has_tuner      = 1,
+                       .has_eeprom     = 1,
+               },
+               .gpio = {
+                       .tuner_reset    = TM6000_GPIO_1,
+               },
+               .vinput = { {
+                       .type   = TM6000_INPUT_TV,
+                       .vmux   = TM6000_VMUX_VIDEO_B,
+                       .amux   = TM6000_AMUX_ADC1,
+                       }, {
+                       .type   = TM6000_INPUT_COMPOSITE1,
+                       .vmux   = TM6000_VMUX_VIDEO_A,
+                       .amux   = TM6000_AMUX_ADC2,
+                       }, {
+                       .type   = TM6000_INPUT_SVIDEO,
+                       .vmux   = TM6000_VMUX_VIDEO_AB,
+                       .amux   = TM6000_AMUX_ADC2,
+                       },
+               },
+       },
+       [TM5600_BOARD_GENERIC] = {
+               .name         = "Generic tm5600 board",
+               .type         = TM5600,
+               .tuner_type   = TUNER_XC2028,
+               .tuner_addr   = 0xc2 >> 1,
+               .caps = {
+                       .has_tuner      = 1,
+                       .has_eeprom     = 1,
+               },
+               .gpio = {
+                       .tuner_reset    = TM6000_GPIO_1,
+               },
+               .vinput = { {
+                       .type   = TM6000_INPUT_TV,
+                       .vmux   = TM6000_VMUX_VIDEO_B,
+                       .amux   = TM6000_AMUX_ADC1,
+                       }, {
+                       .type   = TM6000_INPUT_COMPOSITE1,
+                       .vmux   = TM6000_VMUX_VIDEO_A,
+                       .amux   = TM6000_AMUX_ADC2,
+                       }, {
+                       .type   = TM6000_INPUT_SVIDEO,
+                       .vmux   = TM6000_VMUX_VIDEO_AB,
+                       .amux   = TM6000_AMUX_ADC2,
+                       },
+               },
+       },
+       [TM6000_BOARD_GENERIC] = {
+               .name         = "Generic tm6000 board",
+               .tuner_type   = TUNER_XC2028,
+               .tuner_addr   = 0xc2 >> 1,
+               .caps = {
+                       .has_tuner      = 1,
+                       .has_eeprom     = 1,
+               },
+               .gpio = {
+                       .tuner_reset    = TM6000_GPIO_1,
+               },
+               .vinput = { {
+                       .type   = TM6000_INPUT_TV,
+                       .vmux   = TM6000_VMUX_VIDEO_B,
+                       .amux   = TM6000_AMUX_ADC1,
+                       }, {
+                       .type   = TM6000_INPUT_COMPOSITE1,
+                       .vmux   = TM6000_VMUX_VIDEO_A,
+                       .amux   = TM6000_AMUX_ADC2,
+                       }, {
+                       .type   = TM6000_INPUT_SVIDEO,
+                       .vmux   = TM6000_VMUX_VIDEO_AB,
+                       .amux   = TM6000_AMUX_ADC2,
+                       },
+               },
+       },
+       [TM6010_BOARD_GENERIC] = {
+               .name         = "Generic tm6010 board",
+               .type         = TM6010,
+               .tuner_type   = TUNER_XC2028,
+               .tuner_addr   = 0xc2 >> 1,
+               .demod_addr   = 0x1e >> 1,
+               .caps = {
+                       .has_tuner      = 1,
+                       .has_dvb        = 1,
+                       .has_zl10353    = 1,
+                       .has_eeprom     = 1,
+                       .has_remote     = 1,
+               },
+               .gpio = {
+                       .tuner_reset    = TM6010_GPIO_2,
+                       .tuner_on       = TM6010_GPIO_3,
+                       .demod_reset    = TM6010_GPIO_1,
+                       .demod_on       = TM6010_GPIO_4,
+                       .power_led      = TM6010_GPIO_7,
+                       .dvb_led        = TM6010_GPIO_5,
+                       .ir             = TM6010_GPIO_0,
+               },
+               .vinput = { {
+                       .type   = TM6000_INPUT_TV,
+                       .vmux   = TM6000_VMUX_VIDEO_B,
+                       .amux   = TM6000_AMUX_SIF1,
+                       }, {
+                       .type   = TM6000_INPUT_COMPOSITE1,
+                       .vmux   = TM6000_VMUX_VIDEO_A,
+                       .amux   = TM6000_AMUX_ADC2,
+                       }, {
+                       .type   = TM6000_INPUT_SVIDEO,
+                       .vmux   = TM6000_VMUX_VIDEO_AB,
+                       .amux   = TM6000_AMUX_ADC2,
+                       },
+               },
+       },
+       [TM5600_BOARD_10MOONS_UT821] = {
+               .name         = "10Moons UT 821",
+               .tuner_type   = TUNER_XC2028,
+               .eename       = { '1', '0', 'M', 'O', 'O', 'N', 'S', '5', '6', '0', '0', 0xff, 0x45, 0x5b},
+               .eename_size  = 14,
+               .eename_pos   = 0x14,
+               .type         = TM5600,
+               .tuner_addr   = 0xc2 >> 1,
+               .caps = {
+                       .has_tuner    = 1,
+                       .has_eeprom   = 1,
+               },
+               .gpio = {
+                       .tuner_reset    = TM6000_GPIO_1,
+               },
+               .vinput = { {
+                       .type   = TM6000_INPUT_TV,
+                       .vmux   = TM6000_VMUX_VIDEO_B,
+                       .amux   = TM6000_AMUX_ADC1,
+                       }, {
+                       .type   = TM6000_INPUT_COMPOSITE1,
+                       .vmux   = TM6000_VMUX_VIDEO_A,
+                       .amux   = TM6000_AMUX_ADC2,
+                       }, {
+                       .type   = TM6000_INPUT_SVIDEO,
+                       .vmux   = TM6000_VMUX_VIDEO_AB,
+                       .amux   = TM6000_AMUX_ADC2,
+                       },
+               },
+       },
+       [TM5600_BOARD_10MOONS_UT330] = {
+               .name         = "10Moons UT 330",
+               .tuner_type   = TUNER_PHILIPS_FQ1216AME_MK4,
+               .tuner_addr   = 0xc8 >> 1,
+               .caps = {
+                       .has_tuner    = 1,
+                       .has_dvb      = 0,
+                       .has_zl10353  = 0,
+                       .has_eeprom   = 1,
+               },
+               .vinput = { {
+                       .type   = TM6000_INPUT_TV,
+                       .vmux   = TM6000_VMUX_VIDEO_B,
+                       .amux   = TM6000_AMUX_ADC1,
+                       }, {
+                       .type   = TM6000_INPUT_COMPOSITE1,
+                       .vmux   = TM6000_VMUX_VIDEO_A,
+                       .amux   = TM6000_AMUX_ADC2,
+                       }, {
+                       .type   = TM6000_INPUT_SVIDEO,
+                       .vmux   = TM6000_VMUX_VIDEO_AB,
+                       .amux   = TM6000_AMUX_ADC2,
+                       },
+               },
+       },
+       [TM6000_BOARD_ADSTECH_DUAL_TV] = {
+               .name         = "ADSTECH Dual TV USB",
+               .tuner_type   = TUNER_XC2028,
+               .tuner_addr   = 0xc8 >> 1,
+               .caps = {
+                       .has_tuner    = 1,
+                       .has_tda9874  = 1,
+                       .has_dvb      = 1,
+                       .has_zl10353  = 1,
+                       .has_eeprom   = 1,
+               },
+               .vinput = { {
+                       .type   = TM6000_INPUT_TV,
+                       .vmux   = TM6000_VMUX_VIDEO_B,
+                       .amux   = TM6000_AMUX_ADC1,
+                       }, {
+                       .type   = TM6000_INPUT_COMPOSITE1,
+                       .vmux   = TM6000_VMUX_VIDEO_A,
+                       .amux   = TM6000_AMUX_ADC2,
+                       }, {
+                       .type   = TM6000_INPUT_SVIDEO,
+                       .vmux   = TM6000_VMUX_VIDEO_AB,
+                       .amux   = TM6000_AMUX_ADC2,
+                       },
+               },
+       },
+       [TM6000_BOARD_FREECOM_AND_SIMILAR] = {
+               .name         = "Freecom Hybrid Stick / Moka DVB-T Receiver Dual",
+               .tuner_type   = TUNER_XC2028, /* has a XC3028 */
+               .tuner_addr   = 0xc2 >> 1,
+               .demod_addr   = 0x1e >> 1,
+               .caps = {
+                       .has_tuner    = 1,
+                       .has_dvb      = 1,
+                       .has_zl10353  = 1,
+                       .has_eeprom   = 0,
+                       .has_remote   = 1,
+               },
+               .gpio = {
+                       .tuner_reset    = TM6000_GPIO_4,
+               },
+               .vinput = { {
+                       .type   = TM6000_INPUT_TV,
+                       .vmux   = TM6000_VMUX_VIDEO_B,
+                       .amux   = TM6000_AMUX_ADC1,
+                       }, {
+                       .type   = TM6000_INPUT_COMPOSITE1,
+                       .vmux   = TM6000_VMUX_VIDEO_A,
+                       .amux   = TM6000_AMUX_ADC2,
+                       }, {
+                       .type   = TM6000_INPUT_SVIDEO,
+                       .vmux   = TM6000_VMUX_VIDEO_AB,
+                       .amux   = TM6000_AMUX_ADC2,
+                       },
+               },
+       },
+       [TM6000_BOARD_ADSTECH_MINI_DUAL_TV] = {
+               .name         = "ADSTECH Mini Dual TV USB",
+               .tuner_type   = TUNER_XC2028, /* has a XC3028 */
+               .tuner_addr   = 0xc8 >> 1,
+               .demod_addr   = 0x1e >> 1,
+               .caps = {
+                       .has_tuner    = 1,
+                       .has_dvb      = 1,
+                       .has_zl10353  = 1,
+                       .has_eeprom   = 0,
+               },
+               .gpio = {
+                       .tuner_reset    = TM6000_GPIO_4,
+               },
+               .vinput = { {
+                       .type   = TM6000_INPUT_TV,
+                       .vmux   = TM6000_VMUX_VIDEO_B,
+                       .amux   = TM6000_AMUX_ADC1,
+                       }, {
+                       .type   = TM6000_INPUT_COMPOSITE1,
+                       .vmux   = TM6000_VMUX_VIDEO_A,
+                       .amux   = TM6000_AMUX_ADC2,
+                       }, {
+                       .type   = TM6000_INPUT_SVIDEO,
+                       .vmux   = TM6000_VMUX_VIDEO_AB,
+                       .amux   = TM6000_AMUX_ADC2,
+                       },
+               },
+       },
+       [TM6010_BOARD_HAUPPAUGE_900H] = {
+               .name         = "Hauppauge WinTV HVR-900H / WinTV USB2-Stick",
+               .eename       = { 'H', 0, 'V', 0, 'R', 0, '9', 0, '0', 0, '0', 0, 'H', 0 },
+               .eename_size  = 14,
+               .eename_pos   = 0x42,
+               .tuner_type   = TUNER_XC2028, /* has a XC3028 */
+               .tuner_addr   = 0xc2 >> 1,
+               .demod_addr   = 0x1e >> 1,
+               .type         = TM6010,
+               .ir_codes = RC_MAP_HAUPPAUGE,
+               .caps = {
+                       .has_tuner    = 1,
+                       .has_dvb      = 1,
+                       .has_zl10353  = 1,
+                       .has_eeprom   = 1,
+                       .has_remote   = 1,
+               },
+               .gpio = {
+                       .tuner_reset    = TM6010_GPIO_2,
+                       .tuner_on       = TM6010_GPIO_3,
+                       .demod_reset    = TM6010_GPIO_1,
+                       .demod_on       = TM6010_GPIO_4,
+                       .power_led      = TM6010_GPIO_7,
+                       .dvb_led        = TM6010_GPIO_5,
+                       .ir             = TM6010_GPIO_0,
+               },
+               .vinput = { {
+                       .type   = TM6000_INPUT_TV,
+                       .vmux   = TM6000_VMUX_VIDEO_B,
+                       .amux   = TM6000_AMUX_SIF1,
+                       }, {
+                       .type   = TM6000_INPUT_COMPOSITE1,
+                       .vmux   = TM6000_VMUX_VIDEO_A,
+                       .amux   = TM6000_AMUX_ADC2,
+                       }, {
+                       .type   = TM6000_INPUT_SVIDEO,
+                       .vmux   = TM6000_VMUX_VIDEO_AB,
+                       .amux   = TM6000_AMUX_ADC2,
+                       },
+               },
+       },
+       [TM6010_BOARD_BEHOLD_WANDER] = {
+               .name         = "Beholder Wander DVB-T/TV/FM USB2.0",
+               .tuner_type   = TUNER_XC5000,
+               .tuner_addr   = 0xc2 >> 1,
+               .demod_addr   = 0x1e >> 1,
+               .type         = TM6010,
+               .caps = {
+                       .has_tuner      = 1,
+                       .has_dvb        = 1,
+                       .has_zl10353    = 1,
+                       .has_eeprom     = 1,
+                       .has_remote     = 1,
+                       .has_radio      = 1,
+               },
+               .gpio = {
+                       .tuner_reset    = TM6010_GPIO_0,
+                       .demod_reset    = TM6010_GPIO_1,
+                       .power_led      = TM6010_GPIO_6,
+               },
+               .vinput = { {
+                       .type   = TM6000_INPUT_TV,
+                       .vmux   = TM6000_VMUX_VIDEO_B,
+                       .amux   = TM6000_AMUX_SIF1,
+                       }, {
+                       .type   = TM6000_INPUT_COMPOSITE1,
+                       .vmux   = TM6000_VMUX_VIDEO_A,
+                       .amux   = TM6000_AMUX_ADC2,
+                       }, {
+                       .type   = TM6000_INPUT_SVIDEO,
+                       .vmux   = TM6000_VMUX_VIDEO_AB,
+                       .amux   = TM6000_AMUX_ADC2,
+                       },
+               },
+               .rinput = {
+                       .type   = TM6000_INPUT_RADIO,
+                       .amux   = TM6000_AMUX_ADC1,
+               },
+       },
+       [TM6010_BOARD_BEHOLD_VOYAGER] = {
+               .name         = "Beholder Voyager TV/FM USB2.0",
+               .tuner_type   = TUNER_XC5000,
+               .tuner_addr   = 0xc2 >> 1,
+               .type         = TM6010,
+               .caps = {
+                       .has_tuner      = 1,
+                       .has_dvb        = 0,
+                       .has_zl10353    = 0,
+                       .has_eeprom     = 1,
+                       .has_remote     = 1,
+                       .has_radio      = 1,
+               },
+               .gpio = {
+                       .tuner_reset    = TM6010_GPIO_0,
+                       .power_led      = TM6010_GPIO_6,
+               },
+               .vinput = { {
+                       .type   = TM6000_INPUT_TV,
+                       .vmux   = TM6000_VMUX_VIDEO_B,
+                       .amux   = TM6000_AMUX_SIF1,
+                       }, {
+                       .type   = TM6000_INPUT_COMPOSITE1,
+                       .vmux   = TM6000_VMUX_VIDEO_A,
+                       .amux   = TM6000_AMUX_ADC2,
+                       }, {
+                       .type   = TM6000_INPUT_SVIDEO,
+                       .vmux   = TM6000_VMUX_VIDEO_AB,
+                       .amux   = TM6000_AMUX_ADC2,
+                       },
+               },
+               .rinput = {
+                       .type   = TM6000_INPUT_RADIO,
+                       .amux   = TM6000_AMUX_ADC1,
+               },
+       },
+       [TM6010_BOARD_TERRATEC_CINERGY_HYBRID_XE] = {
+               .name         = "Terratec Cinergy Hybrid XE / Cinergy Hybrid-Stick",
+               .tuner_type   = TUNER_XC2028, /* has a XC3028 */
+               .tuner_addr   = 0xc2 >> 1,
+               .demod_addr   = 0x1e >> 1,
+               .type         = TM6010,
+               .caps = {
+                       .has_tuner    = 1,
+                       .has_dvb      = 1,
+                       .has_zl10353  = 1,
+                       .has_eeprom   = 1,
+                       .has_remote   = 1,
+                       .has_radio    = 1,
+               },
+               .gpio = {
+                       .tuner_reset    = TM6010_GPIO_2,
+                       .tuner_on       = TM6010_GPIO_3,
+                       .demod_reset    = TM6010_GPIO_1,
+                       .demod_on       = TM6010_GPIO_4,
+                       .power_led      = TM6010_GPIO_7,
+                       .dvb_led        = TM6010_GPIO_5,
+                       .ir             = TM6010_GPIO_0,
+               },
+               .ir_codes = RC_MAP_NEC_TERRATEC_CINERGY_XS,
+               .vinput = { {
+                       .type   = TM6000_INPUT_TV,
+                       .vmux   = TM6000_VMUX_VIDEO_B,
+                       .amux   = TM6000_AMUX_SIF1,
+                       }, {
+                       .type   = TM6000_INPUT_COMPOSITE1,
+                       .vmux   = TM6000_VMUX_VIDEO_A,
+                       .amux   = TM6000_AMUX_ADC2,
+                       }, {
+                       .type   = TM6000_INPUT_SVIDEO,
+                       .vmux   = TM6000_VMUX_VIDEO_AB,
+                       .amux   = TM6000_AMUX_ADC2,
+                       },
+               },
+               .rinput = {
+                       .type = TM6000_INPUT_RADIO,
+                       .amux = TM6000_AMUX_SIF1,
+               },
+       },
+       [TM5600_BOARD_TERRATEC_GRABSTER] = {
+               .name         = "Terratec Grabster AV 150/250 MX",
+               .type         = TM5600,
+               .tuner_type   = TUNER_ABSENT,
+               .vinput = { {
+                       .type   = TM6000_INPUT_TV,
+                       .vmux   = TM6000_VMUX_VIDEO_B,
+                       .amux   = TM6000_AMUX_ADC1,
+                       }, {
+                       .type   = TM6000_INPUT_COMPOSITE1,
+                       .vmux   = TM6000_VMUX_VIDEO_A,
+                       .amux   = TM6000_AMUX_ADC2,
+                       }, {
+                       .type   = TM6000_INPUT_SVIDEO,
+                       .vmux   = TM6000_VMUX_VIDEO_AB,
+                       .amux   = TM6000_AMUX_ADC2,
+                       },
+               },
+       },
+       [TM6010_BOARD_TWINHAN_TU501] = {
+               .name         = "Twinhan TU501(704D1)",
+               .tuner_type   = TUNER_XC2028, /* has a XC3028 */
+               .tuner_addr   = 0xc2 >> 1,
+               .demod_addr   = 0x1e >> 1,
+               .type         = TM6010,
+               .caps = {
+                       .has_tuner    = 1,
+                       .has_dvb      = 1,
+                       .has_zl10353  = 1,
+                       .has_eeprom   = 1,
+                       .has_remote   = 1,
+               },
+               .gpio = {
+                       .tuner_reset    = TM6010_GPIO_2,
+                       .tuner_on       = TM6010_GPIO_3,
+                       .demod_reset    = TM6010_GPIO_1,
+                       .demod_on       = TM6010_GPIO_4,
+                       .power_led      = TM6010_GPIO_7,
+                       .dvb_led        = TM6010_GPIO_5,
+                       .ir             = TM6010_GPIO_0,
+               },
+               .vinput = { {
+                       .type   = TM6000_INPUT_TV,
+                       .vmux   = TM6000_VMUX_VIDEO_B,
+                       .amux   = TM6000_AMUX_SIF1,
+                       }, {
+                       .type   = TM6000_INPUT_COMPOSITE1,
+                       .vmux   = TM6000_VMUX_VIDEO_A,
+                       .amux   = TM6000_AMUX_ADC2,
+                       }, {
+                       .type   = TM6000_INPUT_SVIDEO,
+                       .vmux   = TM6000_VMUX_VIDEO_AB,
+                       .amux   = TM6000_AMUX_ADC2,
+                       },
+               },
+       },
+       [TM6010_BOARD_BEHOLD_WANDER_LITE] = {
+               .name         = "Beholder Wander Lite DVB-T/TV/FM USB2.0",
+               .tuner_type   = TUNER_XC5000,
+               .tuner_addr   = 0xc2 >> 1,
+               .demod_addr   = 0x1e >> 1,
+               .type         = TM6010,
+               .caps = {
+                       .has_tuner      = 1,
+                       .has_dvb        = 1,
+                       .has_zl10353    = 1,
+                       .has_eeprom     = 1,
+                       .has_remote     = 0,
+                       .has_radio      = 1,
+               },
+               .gpio = {
+                       .tuner_reset    = TM6010_GPIO_0,
+                       .demod_reset    = TM6010_GPIO_1,
+                       .power_led      = TM6010_GPIO_6,
+               },
+               .vinput = { {
+                       .type   = TM6000_INPUT_TV,
+                       .vmux   = TM6000_VMUX_VIDEO_B,
+                       .amux   = TM6000_AMUX_SIF1,
+                       },
+               },
+               .rinput = {
+                       .type   = TM6000_INPUT_RADIO,
+                       .amux   = TM6000_AMUX_ADC1,
+               },
+       },
+       [TM6010_BOARD_BEHOLD_VOYAGER_LITE] = {
+               .name         = "Beholder Voyager Lite TV/FM USB2.0",
+               .tuner_type   = TUNER_XC5000,
+               .tuner_addr   = 0xc2 >> 1,
+               .type         = TM6010,
+               .caps = {
+                       .has_tuner      = 1,
+                       .has_dvb        = 0,
+                       .has_zl10353    = 0,
+                       .has_eeprom     = 1,
+                       .has_remote     = 0,
+                       .has_radio      = 1,
+               },
+               .gpio = {
+                       .tuner_reset    = TM6010_GPIO_0,
+                       .power_led      = TM6010_GPIO_6,
+               },
+               .vinput = { {
+                       .type   = TM6000_INPUT_TV,
+                       .vmux   = TM6000_VMUX_VIDEO_B,
+                       .amux   = TM6000_AMUX_SIF1,
+                       },
+               },
+               .rinput = {
+                       .type   = TM6000_INPUT_RADIO,
+                       .amux   = TM6000_AMUX_ADC1,
+               },
+       },
+ };
+ /* table of devices that work with this driver */
+ static struct usb_device_id tm6000_id_table[] = {
+       { USB_DEVICE(0x6000, 0x0001), .driver_info = TM5600_BOARD_GENERIC },
+       { USB_DEVICE(0x6000, 0x0002), .driver_info = TM6010_BOARD_GENERIC },
+       { USB_DEVICE(0x06e1, 0xf332), .driver_info = TM6000_BOARD_ADSTECH_DUAL_TV },
+       { USB_DEVICE(0x14aa, 0x0620), .driver_info = TM6000_BOARD_FREECOM_AND_SIMILAR },
+       { USB_DEVICE(0x06e1, 0xb339), .driver_info = TM6000_BOARD_ADSTECH_MINI_DUAL_TV },
+       { USB_DEVICE(0x2040, 0x6600), .driver_info = TM6010_BOARD_HAUPPAUGE_900H },
+       { USB_DEVICE(0x2040, 0x6601), .driver_info = TM6010_BOARD_HAUPPAUGE_900H },
+       { USB_DEVICE(0x2040, 0x6610), .driver_info = TM6010_BOARD_HAUPPAUGE_900H },
+       { USB_DEVICE(0x2040, 0x6611), .driver_info = TM6010_BOARD_HAUPPAUGE_900H },
+       { USB_DEVICE(0x6000, 0xdec0), .driver_info = TM6010_BOARD_BEHOLD_WANDER },
+       { USB_DEVICE(0x6000, 0xdec1), .driver_info = TM6010_BOARD_BEHOLD_VOYAGER },
+       { USB_DEVICE(0x0ccd, 0x0086), .driver_info = TM6010_BOARD_TERRATEC_CINERGY_HYBRID_XE },
+       { USB_DEVICE(0x0ccd, 0x00A5), .driver_info = TM6010_BOARD_TERRATEC_CINERGY_HYBRID_XE },
+       { USB_DEVICE(0x0ccd, 0x0079), .driver_info = TM5600_BOARD_TERRATEC_GRABSTER },
+       { USB_DEVICE(0x13d3, 0x3240), .driver_info = TM6010_BOARD_TWINHAN_TU501 },
+       { USB_DEVICE(0x13d3, 0x3241), .driver_info = TM6010_BOARD_TWINHAN_TU501 },
+       { USB_DEVICE(0x13d3, 0x3243), .driver_info = TM6010_BOARD_TWINHAN_TU501 },
+       { USB_DEVICE(0x13d3, 0x3264), .driver_info = TM6010_BOARD_TWINHAN_TU501 },
+       { USB_DEVICE(0x6000, 0xdec2), .driver_info = TM6010_BOARD_BEHOLD_WANDER_LITE },
+       { USB_DEVICE(0x6000, 0xdec3), .driver_info = TM6010_BOARD_BEHOLD_VOYAGER_LITE },
+       { }
+ };
+ MODULE_DEVICE_TABLE(usb, tm6000_id_table);
+ /* Control power led for show some activity */
+ void tm6000_flash_led(struct tm6000_core *dev, u8 state)
+ {
+       /* Power LED unconfigured */
+       if (!dev->gpio.power_led)
+               return;
+       /* ON Power LED */
+       if (state) {
+               switch (dev->model) {
+               case TM6010_BOARD_HAUPPAUGE_900H:
+               case TM6010_BOARD_TERRATEC_CINERGY_HYBRID_XE:
+               case TM6010_BOARD_TWINHAN_TU501:
+                       tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN,
+                               dev->gpio.power_led, 0x00);
+                       break;
+               case TM6010_BOARD_BEHOLD_WANDER:
+               case TM6010_BOARD_BEHOLD_VOYAGER:
+               case TM6010_BOARD_BEHOLD_WANDER_LITE:
+               case TM6010_BOARD_BEHOLD_VOYAGER_LITE:
+                       tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN,
+                               dev->gpio.power_led, 0x01);
+                       break;
+               }
+       }
+       /* OFF Power LED */
+       else {
+               switch (dev->model) {
+               case TM6010_BOARD_HAUPPAUGE_900H:
+               case TM6010_BOARD_TERRATEC_CINERGY_HYBRID_XE:
+               case TM6010_BOARD_TWINHAN_TU501:
+                       tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN,
+                               dev->gpio.power_led, 0x01);
+                       break;
+               case TM6010_BOARD_BEHOLD_WANDER:
+               case TM6010_BOARD_BEHOLD_VOYAGER:
+               case TM6010_BOARD_BEHOLD_WANDER_LITE:
+               case TM6010_BOARD_BEHOLD_VOYAGER_LITE:
+                       tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN,
+                               dev->gpio.power_led, 0x00);
+                       break;
+               }
+       }
+ }
+ /* Tuner callback to provide the proper gpio changes needed for xc5000 */
+ int tm6000_xc5000_callback(void *ptr, int component, int command, int arg)
+ {
+       int rc = 0;
+       struct tm6000_core *dev = ptr;
+       if (dev->tuner_type != TUNER_XC5000)
+               return 0;
+       switch (command) {
+       case XC5000_TUNER_RESET:
+               tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN,
+                              dev->gpio.tuner_reset, 0x01);
+               msleep(15);
+               tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN,
+                              dev->gpio.tuner_reset, 0x00);
+               msleep(15);
+               tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN,
+                              dev->gpio.tuner_reset, 0x01);
+               break;
+       }
+       return rc;
+ }
+ EXPORT_SYMBOL_GPL(tm6000_xc5000_callback);
+ /* Tuner callback to provide the proper gpio changes needed for xc2028 */
+ int tm6000_tuner_callback(void *ptr, int component, int command, int arg)
+ {
+       int rc = 0;
+       struct tm6000_core *dev = ptr;
+       if (dev->tuner_type != TUNER_XC2028)
+               return 0;
+       switch (command) {
+       case XC2028_RESET_CLK:
+               tm6000_ir_wait(dev, 0);
+               tm6000_set_reg(dev, REQ_04_EN_DISABLE_MCU_INT,
+                                       0x02, arg);
+               msleep(10);
+               rc = tm6000_i2c_reset(dev, 10);
+               break;
+       case XC2028_TUNER_RESET:
+               /* Reset codes during load firmware */
+               switch (arg) {
+               case 0:
+                       /* newer tuner can faster reset */
+                       switch (dev->model) {
+                       case TM5600_BOARD_10MOONS_UT821:
+                               tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN,
+                                              dev->gpio.tuner_reset, 0x01);
+                               tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN,
+                                              0x300, 0x01);
+                               msleep(10);
+                               tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN,
+                                              dev->gpio.tuner_reset, 0x00);
+                               tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN,
+                                              0x300, 0x00);
+                               msleep(10);
+                               tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN,
+                                              dev->gpio.tuner_reset, 0x01);
+                               tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN,
+                                              0x300, 0x01);
+                               break;
+                       case TM6010_BOARD_HAUPPAUGE_900H:
+                       case TM6010_BOARD_TERRATEC_CINERGY_HYBRID_XE:
+                       case TM6010_BOARD_TWINHAN_TU501:
+                               tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN,
+                                              dev->gpio.tuner_reset, 0x01);
+                               msleep(60);
+                               tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN,
+                                              dev->gpio.tuner_reset, 0x00);
+                               msleep(75);
+                               tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN,
+                                              dev->gpio.tuner_reset, 0x01);
+                               msleep(60);
+                               break;
+                       default:
+                               tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN,
+                                              dev->gpio.tuner_reset, 0x00);
+                               msleep(130);
+                               tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN,
+                                              dev->gpio.tuner_reset, 0x01);
+                               msleep(130);
+                               break;
+                       }
+                       tm6000_ir_wait(dev, 1);
+                       break;
+               case 1:
+                       tm6000_set_reg(dev, REQ_04_EN_DISABLE_MCU_INT,
+                                               0x02, 0x01);
+                       msleep(10);
+                       break;
+               case 2:
+                       rc = tm6000_i2c_reset(dev, 100);
+                       break;
+               }
+               break;
+       case XC2028_I2C_FLUSH:
+               tm6000_set_reg(dev, REQ_50_SET_START, 0, 0);
+               tm6000_set_reg(dev, REQ_51_SET_STOP, 0, 0);
+               break;
+       }
+       return rc;
+ }
+ EXPORT_SYMBOL_GPL(tm6000_tuner_callback);
+ int tm6000_cards_setup(struct tm6000_core *dev)
+ {
+       /*
+        * Board-specific initialization sequence. Handles all GPIO
+        * initialization sequences that are board-specific.
+        * Up to now, all found devices use GPIO1 and GPIO4 at the same way.
+        * Probably, they're all based on some reference device. Due to that,
+        * there's a common routine at the end to handle those GPIO's. Devices
+        * that use different pinups or init sequences can just return at
+        * the board-specific session.
+        */
+       switch (dev->model) {
+       case TM6010_BOARD_HAUPPAUGE_900H:
+       case TM6010_BOARD_TERRATEC_CINERGY_HYBRID_XE:
+       case TM6010_BOARD_TWINHAN_TU501:
+       case TM6010_BOARD_GENERIC:
+               /* Turn xceive 3028 on */
+               tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN, dev->gpio.tuner_on, 0x01);
+               msleep(15);
+               /* Turn zarlink zl10353 on */
+               tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN, dev->gpio.demod_on, 0x00);
+               msleep(15);
+               /* Reset zarlink zl10353 */
+               tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN, dev->gpio.demod_reset, 0x00);
+               msleep(50);
+               tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN, dev->gpio.demod_reset, 0x01);
+               msleep(15);
+               /* Turn zarlink zl10353 off */
+               tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN, dev->gpio.demod_on, 0x01);
+               msleep(15);
+               /* ir ? */
+               tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN, dev->gpio.ir, 0x01);
+               msleep(15);
+               /* Power led on (blue) */
+               tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN, dev->gpio.power_led, 0x00);
+               msleep(15);
+               /* DVB led off (orange) */
+               tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN, dev->gpio.dvb_led, 0x01);
+               msleep(15);
+               /* Turn zarlink zl10353 on */
+               tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN, dev->gpio.demod_on, 0x00);
+               msleep(15);
+               break;
+       case TM6010_BOARD_BEHOLD_WANDER:
+       case TM6010_BOARD_BEHOLD_WANDER_LITE:
+               /* Power led on (blue) */
+               tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN, dev->gpio.power_led, 0x01);
+               msleep(15);
+               /* Reset zarlink zl10353 */
+               tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN, dev->gpio.demod_reset, 0x00);
+               msleep(50);
+               tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN, dev->gpio.demod_reset, 0x01);
+               msleep(15);
+               break;
+       case TM6010_BOARD_BEHOLD_VOYAGER:
+       case TM6010_BOARD_BEHOLD_VOYAGER_LITE:
+               /* Power led on (blue) */
+               tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN, dev->gpio.power_led, 0x01);
+               msleep(15);
+               break;
+       default:
+               break;
+       }
+       /*
+        * Default initialization. Most of the devices seem to use GPIO1
+        * and GPIO4.on the same way, so, this handles the common sequence
+        * used by most devices.
+        * If a device uses a different sequence or different GPIO pins for
+        * reset, just add the code at the board-specific part
+        */
+       if (dev->gpio.tuner_reset) {
+               int rc;
+               int i;
+               for (i = 0; i < 2; i++) {
+                       rc = tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN,
+                                               dev->gpio.tuner_reset, 0x00);
+                       if (rc < 0) {
+                               printk(KERN_ERR "Error %i doing tuner reset\n", rc);
+                               return rc;
+                       }
+                       msleep(10); /* Just to be conservative */
+                       rc = tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN,
+                                               dev->gpio.tuner_reset, 0x01);
+                       if (rc < 0) {
+                               printk(KERN_ERR "Error %i doing tuner reset\n", rc);
+                               return rc;
+                       }
+               }
+       } else {
+               printk(KERN_ERR "Tuner reset is not configured\n");
+               return -1;
+       }
+       msleep(50);
+       return 0;
+ };
+ static void tm6000_config_tuner(struct tm6000_core *dev)
+ {
+       struct tuner_setup tun_setup;
+       /* Load tuner module */
+       v4l2_i2c_new_subdev(&dev->v4l2_dev, &dev->i2c_adap,
+               "tuner", dev->tuner_addr, NULL);
+       memset(&tun_setup, 0, sizeof(tun_setup));
+       tun_setup.type = dev->tuner_type;
+       tun_setup.addr = dev->tuner_addr;
+       tun_setup.mode_mask = 0;
+       if (dev->caps.has_tuner)
+               tun_setup.mode_mask |= (T_ANALOG_TV | T_RADIO);
+       switch (dev->tuner_type) {
+       case TUNER_XC2028:
+               tun_setup.tuner_callback = tm6000_tuner_callback;
+               break;
+       case TUNER_XC5000:
+               tun_setup.tuner_callback = tm6000_xc5000_callback;
+               break;
+       }
+       v4l2_device_call_all(&dev->v4l2_dev, 0, tuner, s_type_addr, &tun_setup);
+       switch (dev->tuner_type) {
+       case TUNER_XC2028: {
+               struct v4l2_priv_tun_config xc2028_cfg;
+               struct xc2028_ctrl ctl;
+               memset(&xc2028_cfg, 0, sizeof(xc2028_cfg));
+               memset(&ctl, 0, sizeof(ctl));
+               ctl.demod = XC3028_FE_ZARLINK456;
+               xc2028_cfg.tuner = TUNER_XC2028;
+               xc2028_cfg.priv  = &ctl;
+               switch (dev->model) {
+               case TM6010_BOARD_HAUPPAUGE_900H:
+               case TM6010_BOARD_TERRATEC_CINERGY_HYBRID_XE:
+               case TM6010_BOARD_TWINHAN_TU501:
+                       ctl.max_len = 80;
+                       ctl.fname = "xc3028L-v36.fw";
+                       break;
+               default:
+                       if (dev->dev_type == TM6010)
+                               ctl.fname = "xc3028-v27.fw";
+                       else
+                               ctl.fname = "xc3028-v24.fw";
+               }
+               printk(KERN_INFO "Setting firmware parameters for xc2028\n");
+               v4l2_device_call_all(&dev->v4l2_dev, 0, tuner, s_config,
+                                    &xc2028_cfg);
+               }
+               break;
+       case TUNER_XC5000:
+               {
+               struct v4l2_priv_tun_config  xc5000_cfg;
+               struct xc5000_config ctl = {
+                       .i2c_address = dev->tuner_addr,
+                       .if_khz      = 4570,
+                       .radio_input = XC5000_RADIO_FM1_MONO,
+                       };
+               xc5000_cfg.tuner = TUNER_XC5000;
+               xc5000_cfg.priv  = &ctl;
+               v4l2_device_call_all(&dev->v4l2_dev, 0, tuner, s_config,
+                                    &xc5000_cfg);
+               }
+               break;
+       default:
+               printk(KERN_INFO "Unknown tuner type. Tuner is not configured.\n");
+               break;
+       }
+ }
+ static int fill_board_specific_data(struct tm6000_core *dev)
+ {
+       int rc;
+       dev->dev_type   = tm6000_boards[dev->model].type;
+       dev->tuner_type = tm6000_boards[dev->model].tuner_type;
+       dev->tuner_addr = tm6000_boards[dev->model].tuner_addr;
+       dev->gpio = tm6000_boards[dev->model].gpio;
+       dev->ir_codes = tm6000_boards[dev->model].ir_codes;
+       dev->demod_addr = tm6000_boards[dev->model].demod_addr;
+       dev->caps = tm6000_boards[dev->model].caps;
+       dev->vinput[0] = tm6000_boards[dev->model].vinput[0];
+       dev->vinput[1] = tm6000_boards[dev->model].vinput[1];
+       dev->vinput[2] = tm6000_boards[dev->model].vinput[2];
+       dev->rinput = tm6000_boards[dev->model].rinput;
+       /* setup per-model quirks */
+       switch (dev->model) {
+       case TM6010_BOARD_TERRATEC_CINERGY_HYBRID_XE:
+       case TM6010_BOARD_HAUPPAUGE_900H:
+               dev->quirks |= TM6000_QUIRK_NO_USB_DELAY;
+               break;
+       default:
+               break;
+       }
+       /* initialize hardware */
+       rc = tm6000_init(dev);
+       if (rc < 0)
+               return rc;
+       return v4l2_device_register(&dev->udev->dev, &dev->v4l2_dev);
+ }
+ static void use_alternative_detection_method(struct tm6000_core *dev)
+ {
+       int i, model = -1;
+       if (!dev->eedata_size)
+               return;
+       for (i = 0; i < ARRAY_SIZE(tm6000_boards); i++) {
+               if (!tm6000_boards[i].eename_size)
+                       continue;
+               if (dev->eedata_size < tm6000_boards[i].eename_pos +
+                                      tm6000_boards[i].eename_size)
+                       continue;
+               if (!memcmp(&dev->eedata[tm6000_boards[i].eename_pos],
+                           tm6000_boards[i].eename,
+                           tm6000_boards[i].eename_size)) {
+                       model = i;
+                       break;
+               }
+       }
+       if (model < 0) {
+               printk(KERN_INFO "Device has eeprom but is currently unknown\n");
+               return;
+       }
+       dev->model = model;
+       printk(KERN_INFO "Device identified via eeprom as %s (type = %d)\n",
+              tm6000_boards[model].name, model);
+ }
+ #if defined(CONFIG_MODULES) && defined(MODULE)
+ static void request_module_async(struct work_struct *work)
+ {
+       struct tm6000_core *dev = container_of(work, struct tm6000_core,
+                                              request_module_wk);
+       request_module("tm6000-alsa");
+       if (dev->caps.has_dvb)
+               request_module("tm6000-dvb");
+ }
+ static void request_modules(struct tm6000_core *dev)
+ {
+       INIT_WORK(&dev->request_module_wk, request_module_async);
+       schedule_work(&dev->request_module_wk);
+ }
+ static void flush_request_modules(struct tm6000_core *dev)
+ {
++      flush_work(&dev->request_module_wk);
+ }
+ #else
+ #define request_modules(dev)
+ #define flush_request_modules(dev)
+ #endif /* CONFIG_MODULES */
+ static int tm6000_init_dev(struct tm6000_core *dev)
+ {
+       struct v4l2_frequency f;
+       int rc = 0;
+       mutex_init(&dev->lock);
+       mutex_lock(&dev->lock);
+       if (!is_generic(dev->model)) {
+               rc = fill_board_specific_data(dev);
+               if (rc < 0)
+                       goto err;
+               /* register i2c bus */
+               rc = tm6000_i2c_register(dev);
+               if (rc < 0)
+                       goto err;
+       } else {
+               /* register i2c bus */
+               rc = tm6000_i2c_register(dev);
+               if (rc < 0)
+                       goto err;
+               use_alternative_detection_method(dev);
+               rc = fill_board_specific_data(dev);
+               if (rc < 0)
+                       goto err;
+       }
+       /* Default values for STD and resolutions */
+       dev->width = 720;
+       dev->height = 480;
+       dev->norm = V4L2_STD_PAL_M;
+       /* Configure tuner */
+       tm6000_config_tuner(dev);
+       /* Set video standard */
+       v4l2_device_call_all(&dev->v4l2_dev, 0, core, s_std, dev->norm);
+       /* Set tuner frequency - also loads firmware on xc2028/xc3028 */
+       f.tuner = 0;
+       f.type = V4L2_TUNER_ANALOG_TV;
+       f.frequency = 3092;     /* 193.25 MHz */
+       dev->freq = f.frequency;
+       v4l2_device_call_all(&dev->v4l2_dev, 0, tuner, s_frequency, &f);
+       if (dev->caps.has_tda9874)
+               v4l2_i2c_new_subdev(&dev->v4l2_dev, &dev->i2c_adap,
+                       "tvaudio", I2C_ADDR_TDA9874, NULL);
+       /* register and initialize V4L2 */
+       rc = tm6000_v4l2_register(dev);
+       if (rc < 0)
+               goto err;
+       tm6000_add_into_devlist(dev);
+       tm6000_init_extension(dev);
+       tm6000_ir_init(dev);
+       request_modules(dev);
+       mutex_unlock(&dev->lock);
+       return 0;
+ err:
+       mutex_unlock(&dev->lock);
+       return rc;
+ }
+ /* high bandwidth multiplier, as encoded in highspeed endpoint descriptors */
+ #define hb_mult(wMaxPacketSize) (1 + (((wMaxPacketSize) >> 11) & 0x03))
+ static void get_max_endpoint(struct usb_device *udev,
+                            struct usb_host_interface *alt,
+                            char *msgtype,
+                            struct usb_host_endpoint *curr_e,
+                            struct tm6000_endpoint *tm_ep)
+ {
+       u16 tmp = le16_to_cpu(curr_e->desc.wMaxPacketSize);
+       unsigned int size = tmp & 0x7ff;
+       if (udev->speed == USB_SPEED_HIGH)
+               size = size * hb_mult(tmp);
+       if (size > tm_ep->maxsize) {
+               tm_ep->endp = curr_e;
+               tm_ep->maxsize = size;
+               tm_ep->bInterfaceNumber = alt->desc.bInterfaceNumber;
+               tm_ep->bAlternateSetting = alt->desc.bAlternateSetting;
+               printk(KERN_INFO "tm6000: %s endpoint: 0x%02x (max size=%u bytes)\n",
+                                       msgtype, curr_e->desc.bEndpointAddress,
+                                       size);
+       }
+ }
+ /*
+  * tm6000_usb_probe()
+  * checks for supported devices
+  */
+ static int tm6000_usb_probe(struct usb_interface *interface,
+                           const struct usb_device_id *id)
+ {
+       struct usb_device *usbdev;
+       struct tm6000_core *dev = NULL;
+       int i, rc = 0;
+       int nr = 0;
+       char *speed;
+       usbdev = usb_get_dev(interface_to_usbdev(interface));
+       /* Selects the proper interface */
+       rc = usb_set_interface(usbdev, 0, 1);
+       if (rc < 0)
+               goto err;
+       /* Check to see next free device and mark as used */
+       nr = find_first_zero_bit(&tm6000_devused, TM6000_MAXBOARDS);
+       if (nr >= TM6000_MAXBOARDS) {
+               printk(KERN_ERR "tm6000: Supports only %i tm60xx boards.\n", TM6000_MAXBOARDS);
+               usb_put_dev(usbdev);
+               return -ENOMEM;
+       }
+       /* Create and initialize dev struct */
+       dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+       if (dev == NULL) {
+               printk(KERN_ERR "tm6000" ": out of memory!\n");
+               usb_put_dev(usbdev);
+               return -ENOMEM;
+       }
+       spin_lock_init(&dev->slock);
+       mutex_init(&dev->usb_lock);
+       /* Increment usage count */
+       set_bit(nr, &tm6000_devused);
+       snprintf(dev->name, 29, "tm6000 #%d", nr);
+       dev->model = id->driver_info;
+       if (card[nr] < ARRAY_SIZE(tm6000_boards))
+               dev->model = card[nr];
+       dev->udev = usbdev;
+       dev->devno = nr;
+       switch (usbdev->speed) {
+       case USB_SPEED_LOW:
+               speed = "1.5";
+               break;
+       case USB_SPEED_UNKNOWN:
+       case USB_SPEED_FULL:
+               speed = "12";
+               break;
+       case USB_SPEED_HIGH:
+               speed = "480";
+               break;
+       default:
+               speed = "unknown";
+       }
+       /* Get endpoints */
+       for (i = 0; i < interface->num_altsetting; i++) {
+               int ep;
+               for (ep = 0; ep < interface->altsetting[i].desc.bNumEndpoints; ep++) {
+                       struct usb_host_endpoint        *e;
+                       int dir_out;
+                       e = &interface->altsetting[i].endpoint[ep];
+                       dir_out = ((e->desc.bEndpointAddress &
+                                       USB_ENDPOINT_DIR_MASK) == USB_DIR_OUT);
+                       printk(KERN_INFO "tm6000: alt %d, interface %i, class %i\n",
+                              i,
+                              interface->altsetting[i].desc.bInterfaceNumber,
+                              interface->altsetting[i].desc.bInterfaceClass);
+                       switch (e->desc.bmAttributes) {
+                       case USB_ENDPOINT_XFER_BULK:
+                               if (!dir_out) {
+                                       get_max_endpoint(usbdev,
+                                                        &interface->altsetting[i],
+                                                        "Bulk IN", e,
+                                                        &dev->bulk_in);
+                               } else {
+                                       get_max_endpoint(usbdev,
+                                                        &interface->altsetting[i],
+                                                        "Bulk OUT", e,
+                                                        &dev->bulk_out);
+                               }
+                               break;
+                       case USB_ENDPOINT_XFER_ISOC:
+                               if (!dir_out) {
+                                       get_max_endpoint(usbdev,
+                                                        &interface->altsetting[i],
+                                                        "ISOC IN", e,
+                                                        &dev->isoc_in);
+                               } else {
+                                       get_max_endpoint(usbdev,
+                                                        &interface->altsetting[i],
+                                                        "ISOC OUT", e,
+                                                        &dev->isoc_out);
+                               }
+                               break;
+                       case USB_ENDPOINT_XFER_INT:
+                               if (!dir_out) {
+                                       get_max_endpoint(usbdev,
+                                                       &interface->altsetting[i],
+                                                       "INT IN", e,
+                                                       &dev->int_in);
+                               } else {
+                                       get_max_endpoint(usbdev,
+                                                       &interface->altsetting[i],
+                                                       "INT OUT", e,
+                                                       &dev->int_out);
+                               }
+                               break;
+                       }
+               }
+       }
+       printk(KERN_INFO "tm6000: New video device @ %s Mbps (%04x:%04x, ifnum %d)\n",
+               speed,
+               le16_to_cpu(dev->udev->descriptor.idVendor),
+               le16_to_cpu(dev->udev->descriptor.idProduct),
+               interface->altsetting->desc.bInterfaceNumber);
+ /* check if the the device has the iso in endpoint at the correct place */
+       if (!dev->isoc_in.endp) {
+               printk(KERN_ERR "tm6000: probing error: no IN ISOC endpoint!\n");
+               rc = -ENODEV;
+               goto err;
+       }
+       /* save our data pointer in this interface device */
+       usb_set_intfdata(interface, dev);
+       printk(KERN_INFO "tm6000: Found %s\n", tm6000_boards[dev->model].name);
+       rc = tm6000_init_dev(dev);
+       if (rc < 0)
+               goto err;
+       return 0;
+ err:
+       printk(KERN_ERR "tm6000: Error %d while registering\n", rc);
+       clear_bit(nr, &tm6000_devused);
+       usb_put_dev(usbdev);
+       kfree(dev);
+       return rc;
+ }
+ /*
+  * tm6000_usb_disconnect()
+  * called when the device gets diconencted
+  * video device will be unregistered on v4l2_close in case it is still open
+  */
+ static void tm6000_usb_disconnect(struct usb_interface *interface)
+ {
+       struct tm6000_core *dev = usb_get_intfdata(interface);
+       usb_set_intfdata(interface, NULL);
+       if (!dev)
+               return;
+       printk(KERN_INFO "tm6000: disconnecting %s\n", dev->name);
+       flush_request_modules(dev);
+       tm6000_ir_fini(dev);
+       if (dev->gpio.power_led) {
+               switch (dev->model) {
+               case TM6010_BOARD_HAUPPAUGE_900H:
+               case TM6010_BOARD_TERRATEC_CINERGY_HYBRID_XE:
+               case TM6010_BOARD_TWINHAN_TU501:
+                       /* Power led off */
+                       tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN,
+                               dev->gpio.power_led, 0x01);
+                       msleep(15);
+                       break;
+               case TM6010_BOARD_BEHOLD_WANDER:
+               case TM6010_BOARD_BEHOLD_VOYAGER:
+               case TM6010_BOARD_BEHOLD_WANDER_LITE:
+               case TM6010_BOARD_BEHOLD_VOYAGER_LITE:
+                       /* Power led off */
+                       tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN,
+                               dev->gpio.power_led, 0x00);
+                       msleep(15);
+                       break;
+               }
+       }
+       tm6000_v4l2_unregister(dev);
+       tm6000_i2c_unregister(dev);
+       v4l2_device_unregister(&dev->v4l2_dev);
+       dev->state |= DEV_DISCONNECTED;
+       usb_put_dev(dev->udev);
+       tm6000_close_extension(dev);
+       tm6000_remove_from_devlist(dev);
+       clear_bit(dev->devno, &tm6000_devused);
+       kfree(dev);
+ }
+ static struct usb_driver tm6000_usb_driver = {
+               .name = "tm6000",
+               .probe = tm6000_usb_probe,
+               .disconnect = tm6000_usb_disconnect,
+               .id_table = tm6000_id_table,
+ };
+ module_usb_driver(tm6000_usb_driver);
+ MODULE_DESCRIPTION("Trident TVMaster TM5600/TM6000/TM6010 USB2 adapter");
+ MODULE_AUTHOR("Mauro Carvalho Chehab");
+ MODULE_LICENSE("GPL");
Simple merge