Add cxm(4), a driver for Hauppauge PVR-250/350 video capture cards.
authorSascha Wildner <saw@online.de>
Sun, 19 Apr 2009 00:49:17 +0000 (02:49 +0200)
committerSascha Wildner <saw@online.de>
Sun, 19 Apr 2009 00:54:46 +0000 (02:54 +0200)
Before the driver can be used, two firmware files have to be extracted
from the Windows driver which cannot be redistributed with DragonFly.
For further information see the manual page.

It was tested on a PVR-250 which was kindly donated by Hasso Tepper
(many thanks for this!).

Ported-from: FreeBSD (ports/multimedia/pvr250), with modifications

23 files changed:
etc/MAKEDEV
share/man/man4/Makefile
share/man/man4/cxm.4 [new file with mode: 0644]
sys/bus/iicbus/iicbb.c
sys/conf/files
sys/config/LINT
sys/dev/video/cxm/cxm.c [new file with mode: 0644]
sys/dev/video/cxm/cxm.h [new file with mode: 0644]
sys/dev/video/cxm/cxm_dec_fw.c [new file with mode: 0644]
sys/dev/video/cxm/cxm_eeprom.c [new file with mode: 0644]
sys/dev/video/cxm/cxm_enc_fw.c [new file with mode: 0644]
sys/dev/video/cxm/cxm_i2c.c [new file with mode: 0644]
sys/dev/video/cxm/cxm_ir.c [new file with mode: 0644]
sys/dev/video/cxm/cxm_msp34xxx.c [new file with mode: 0644]
sys/dev/video/cxm/cxm_saa7115.c [new file with mode: 0644]
sys/dev/video/cxm/cxm_tuner.c [new file with mode: 0644]
tools/multimedia/cxm/Makefile [new file with mode: 0644]
tools/multimedia/cxm/extract_fw/Makefile [new file with mode: 0644]
tools/multimedia/cxm/extract_fw/cxm_extract_fw.8 [new file with mode: 0644]
tools/multimedia/cxm/extract_fw/cxm_extract_fw.c [new file with mode: 0644]
tools/multimedia/cxm/setchannel/Makefile [new file with mode: 0644]
tools/multimedia/cxm/setchannel/cxm_setchannel.1 [new file with mode: 0644]
tools/multimedia/cxm/setchannel/cxm_setchannel.c [new file with mode: 0644]

index dfe9967..0fffde3 100644 (file)
 #      ctx*    Cortex-I video acquisition card
 #      meteor* Matrox Meteor video acquisition card (pci)
 #      bktr*   Bt848 based video acquisition card (pci)
+#      cxm*    Conexant iTVC MPEG Coder
 #      labpc*  National Instrument's Lab-PC and LAB-PC+
 #      perfmon CPU performance-monitoring counters
 #      pci     PCI configuration-space access from user mode
@@ -2000,6 +2001,14 @@ bktr?)
 
 #-----------------------------------------------------------------------------
 
+cxm?)
+       unit=`expr $i : 'cxm\(.*\)'`
+
+       mknod cxm   $unit c 93 `unit2minor $unit` root:wheel 444
+       ;;
+
+#-----------------------------------------------------------------------------
+
 tun*)
        ntun=`expr $i : 'tun\(.*\)$'`
        unit=0
index 6a9f3ff..ee9d46d 100644 (file)
@@ -52,6 +52,7 @@ MAN=  aac.4 \
        crypto.4 \
        csa.4 \
        cue.4 \
+       cxm.4 \
        da.4 \
        dc.4 \
        dcons.4 \
diff --git a/share/man/man4/cxm.4 b/share/man/man4/cxm.4
new file mode 100644 (file)
index 0000000..ba83d51
--- /dev/null
@@ -0,0 +1,176 @@
+.\"
+.\" Copyright (c) 2009
+.\"    The DragonFly Project.  All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\"
+.\" 1. Redistributions of source code must retain the above copyright
+.\"    notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\"    notice, this list of conditions and the following disclaimer in
+.\"    the documentation and/or other materials provided with the
+.\"    distribution.
+.\" 3. Neither the name of The DragonFly Project nor the names of its
+.\"    contributors may be used to endorse or promote products derived
+.\"    from this software without specific, prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+.\" ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+.\" LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+.\" FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+.\" COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+.\" INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
+.\" BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+.\" LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+.\" AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+.\" OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+.\" OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" $Id: cxm.4,v 1.1 2004/10/16 00:12:35 mavetju Exp $
+.\"
+.Dd April 19, 2009
+.Dt CXM 4
+.Os
+.Sh NAME
+.Nm cxm
+.Nd Conexant iTVC MPEG Coder
+.Sh SYNOPSIS
+To compile this driver into the kernel, place the following lines in
+your kernel configuration file:
+.Bd -ragged -offset indent
+.Cd "device iicbus"
+.Cd "device iic"
+.Cd "device iicbb"
+.Cd "device cxm"
+.Ed
+.\".Pp
+.\"Alternatively, to load the driver as a module at boot time, place the
+.\"following line in
+.\".Pa /boot/loader.conf :
+.\".Bd -literal -offset indent
+.\"cxm_load="YES"
+.\".Ed
+.Sh DESCRIPTION
+The
+.Nm
+driver provides support for the PCI
+.Em video
+capture on the Hauppauge PVR-250/350.
+It uses the
+.Xr bktr 4
+and
+.Xr meteor 4
+ioctls.
+.Pp
+Note that in order for the
+.Xr cxm 4
+driver to work properly, the necessary firmware files have to be extracted
+from the driver CD-ROM prior to compiling the kernel.
+The tool necessary to do this can be found in
+.Pa /usr/src/tools/multimedia/cxm/extract_fw .
+.Sh HARDWARE
+The following cards are known to work:
+.Pp
+.Bl -bullet -compact
+.It
+Hauppage PVR-250
+.It
+Hauppage PVR-350
+.El
+.Pp
+The following tuners are known to work:
+.Pp
+.Bl -bullet -compact
+.It
+Philips FI1216 MK2
+.It
+Philips FI1236 MK2
+.It
+Philips FI1246 MK2
+.It
+Philips FM1216
+.It
+Philips FM1216ME MK3
+.It
+Philips FM1236
+.It
+Philips FM1246
+.It
+Philips FQ1216ME
+.It
+Philips FQ1216ME MK3
+.It
+Temic 4006 FH5
+.It
+Temic 4009 FR5
+.It
+Temic 4036 FY5
+.It
+Temic 4039 FR5
+.It
+Temic 4066 FY5
+.It
+LG Innotek TPI8PSB11D
+.It
+LG Innotek TPI8PSB01N
+.It
+LG Innotek TAPC-H701F
+.It
+LG Innotek TAPC-H001F
+.It
+LG Innotek TAPE-H001F
+.It
+Microtune 4049 FM5
+.It
+TCL 2002N-6A
+.It
+TCL M2523-5N-E
+.El
+.Sh FILES
+.Bl -tag -width ".Pa /usr/src/tools/multimedia/cxm/extract_fw" -compact
+.It Pa /boot/modules/cxm.ko
+Kernel module
+.It Pa /boot/modules/cxm_iic.ko
+Kernel module
+.It Pa /dev/cxm0
+Device
+.It Pa /usr/src/tools/multimedia/cxm/extract_fw
+Firmware extraction tool
+.It Pa /usr/src/tools/multimedia/cxm/setchannel
+Channel switching tool
+.\".It Pa /usr/local/bin/pvr250-setsize
+.\"Set size of capture windows
+.El
+.Sh EXAMPLES
+To access the video card, use
+.Pa /dev/cxm0 .
+For example:
+.Pp
+.Dl "cat /dev/cxm0 > filename.mpg"
+.Sh SEE ALSO
+.Xr cxm_setchannel 1 ,
+.Xr bktr 4 ,
+.Xr meteor 4 ,
+.Xr cxm_extract_fw 8
+.Sh HISTORY
+The
+.Nm
+driver first appeared in the
+.Pa FreeBSD-multimedia@
+mailing list in January 2004 and in the
+.Fx
+Ports collection in October 2004.
+.Pp
+It was ported to
+.Dx 2.3
+by
+.An Sascha Wildner .
+.Sh AUTHORS
+.An -nosplit
+This driver was made by
+.An John Wehle Aq john@feith.com
+and this manual page was made by
+.An Edwin Groothuis Aq edwin@FreeBSD.org .
index 6f003f4..3fbb1e1 100644 (file)
@@ -375,5 +375,6 @@ iicbb_read(device_t dev, char * buf, int len, int *read, int last, int delay)
 }
 
 DRIVER_MODULE(iicbb, bti2c, iicbb_driver, iicbb_devclass, 0, 0);
+DRIVER_MODULE(iicbb, cxm_iic, iicbb_driver, iicbb_devclass, 0, 0);
 DRIVER_MODULE(iicbb, lpbb, iicbb_driver, iicbb_devclass, 0, 0);
 DRIVER_MODULE(iicbb, viapm, iicbb_driver, iicbb_devclass, 0, 0);
index ce3ebf8..bb7ac12 100644 (file)
@@ -1208,6 +1208,15 @@ dev/video/bktr/bktr_i2c.c        optional bktr pci smbus
 dev/video/bktr/bktr_os.c       optional bktr pci
 dev/video/bktr/bktr_tuner.c    optional bktr pci
 dev/video/bktr/msp34xx.c       optional bktr pci
+dev/video/cxm/cxm.c            optional cxm pci
+dev/video/cxm/cxm_dec_fw.c     optional cxm pci
+dev/video/cxm/cxm_eeprom.c     optional cxm pci
+dev/video/cxm/cxm_enc_fw.c     optional cxm pci
+dev/video/cxm/cxm_i2c.c                optional cxm pci smbus
+dev/video/cxm/cxm_ir.c         optional cxm pci
+dev/video/cxm/cxm_msp34xxx.c   optional cxm pci
+dev/video/cxm/cxm_saa7115.c    optional cxm pci
+dev/video/cxm/cxm_tuner.c      optional cxm pci
 dev/serial/cy/cy_pci.c         optional cy pci
 dev/netif/dc/if_dc.c           optional dc
 dev/netif/de/if_de.c           optional de
index b164260..62d3664 100644 (file)
@@ -1997,6 +1997,9 @@ device            meteor
 device         bktr
 options        BKTR_NEW_MSP34XX_DRIVER
 
+# WinTV PVR-250/350 driver
+device         cxm
+
 #
 # PCCARD/PCMCIA
 #
diff --git a/sys/dev/video/cxm/cxm.c b/sys/dev/video/cxm/cxm.c
new file mode 100644 (file)
index 0000000..589f4bf
--- /dev/null
@@ -0,0 +1,2929 @@
+/*
+ * Copyright (c) 2003, 2004, 2005
+ *     John Wehle <john@feith.com>.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *     This product includes software developed by John Wehle.
+ * 4. The name of the author may not be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * Conexant MPEG-2 Codec driver. Supports the CX23415 / CX23416
+ * chips that are on the Hauppauge PVR-250 and PVR-350 video
+ * capture cards.  Currently only the encoder is supported.
+ *
+ * This driver was written using the invaluable information
+ * compiled by The IvyTV Project (ivtv.sourceforge.net).
+ */
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/conf.h>
+#include <sys/uio.h>
+#include <sys/kernel.h>
+#include <sys/mman.h>
+#include <sys/module.h>
+#include <sys/poll.h>
+#include <sys/proc.h>
+#include <sys/signalvar.h>
+#include <sys/thread2.h>
+#include <sys/vnode.h>
+#include <sys/select.h>
+#include <sys/resource.h>
+#include <sys/bus.h>
+#include <sys/rman.h>
+
+#include <machine/clock.h>
+
+#include <dev/video/meteor/ioctl_meteor.h>
+#include <dev/video/bktr/ioctl_bt848.h>
+
+#include <bus/pci/pcireg.h>
+#include <bus/pci/pcivar.h>
+
+#include <dev/video/cxm/cxm.h>
+
+#include <bus/iicbus/iiconf.h>
+
+/*
+ * Various supported device vendors/types and their names.
+ */
+static struct cxm_dev cxm_devs[] = {
+       { PCI_VENDOR_ICOMPRESSION, PCI_PRODUCT_ICOMPRESSION_ITVC15,
+               "Conexant iTVC15 MPEG Coder" },
+       { PCI_VENDOR_ICOMPRESSION, PCI_PRODUCT_ICOMPRESSION_ITVC16,
+               "Conexant iTVC16 MPEG Coder" },
+       { 0, 0, NULL }
+};
+
+
+static int     cxm_probe(device_t dev);
+static int     cxm_attach(device_t dev);
+static int     cxm_detach(device_t dev);
+static int     cxm_shutdown(device_t dev);
+static void    cxm_intr(void *arg);
+
+static void    cxm_child_detached(device_t dev, device_t child);
+static int     cxm_read_ivar(device_t bus, device_t dev,
+                              int index, uintptr_t* val);
+static int     cxm_write_ivar(device_t bus, device_t dev,
+                               int index, uintptr_t val);
+
+
+static device_method_t cxm_methods[] = {
+       /* Device interface */
+       DEVMETHOD(device_probe,         cxm_probe),
+       DEVMETHOD(device_attach,        cxm_attach),
+       DEVMETHOD(device_detach,        cxm_detach),
+       DEVMETHOD(device_shutdown,      cxm_shutdown),
+
+       /* bus interface */
+       DEVMETHOD(bus_child_detached,   cxm_child_detached),
+       DEVMETHOD(bus_print_child,      bus_generic_print_child),
+       DEVMETHOD(bus_driver_added,     bus_generic_driver_added),
+       DEVMETHOD(bus_read_ivar,        cxm_read_ivar),
+       DEVMETHOD(bus_write_ivar,       cxm_write_ivar),
+
+       { 0, 0 }
+};
+
+static driver_t cxm_driver = {
+       "cxm",
+       cxm_methods,
+       sizeof(struct cxm_softc),
+};
+
+static devclass_t cxm_devclass;
+
+static d_open_t        cxm_open;
+static d_close_t       cxm_close;
+static d_read_t        cxm_read;
+static d_ioctl_t       cxm_ioctl;
+static d_poll_t        cxm_poll;
+
+#define CDEV_MAJOR 93
+
+static struct dev_ops cxm_ops = {
+       { "cxm", CDEV_MAJOR, 0 },
+       .d_open =       cxm_open,
+       .d_close =      cxm_close,
+       .d_read =       cxm_read,
+       .d_ioctl =      cxm_ioctl,
+       .d_poll =       cxm_poll
+};
+
+MODULE_DEPEND(cxm, cxm_iic, 1, 1, 1);
+DRIVER_MODULE(cxm, pci, cxm_driver, cxm_devclass, 0, 0);
+
+
+static struct cxm_codec_audio_format codec_audio_formats[] = {
+       { 44100, 0xb8 }, /* 44.1 Khz, MPEG-1 Layer II, 224 kb/s */
+       { 48000, 0xe9 }  /* 48 Khz, MPEG-1 Layer II, 384 kb/s */
+};
+
+
+/*
+ * Various profiles.
+ */
+static struct cxm_codec_profile vcd_ntsc_profile = {
+       "MPEG-1 VideoCD NTSC video and MPEG audio",
+       CXM_FW_STREAM_TYPE_VCD,
+       30,
+       352, 240, 480,
+       { 10, 12, 21 },
+       12,
+       0,
+       { 1, 1150000, 0 },
+       { 1, 15, 3},
+       /*
+        * Spatial filter = Manual, Temporal filter = Manual
+        * Median filter = Horizontal / Vertical
+        * Spatial filter value = 1, Temporal filter value = 4
+        */
+       { 0, 3, 1, 4 },
+       44100
+};
+
+static struct cxm_codec_profile vcd_pal_profile = {
+       "MPEG-1 VideoCD PAL video and MPEG audio",
+       CXM_FW_STREAM_TYPE_VCD,
+       25,
+       352, 288, 576,
+       { 6, 17, 22 },
+       8,
+       0,
+       { 1, 1150000, 0 },
+       { 1, 12, 3},
+       /*
+        * Spatial filter = Manual, Temporal filter = Manual
+        * Median filter = Horizontal / Vertical
+        * Spatial filter value = 1, Temporal filter value = 4
+        */
+       { 0, 3, 1, 4 },
+       44100
+};
+
+static struct cxm_codec_profile svcd_ntsc_profile = {
+       "MPEG-2 SuperVCD NTSC video and MPEG audio",
+       CXM_FW_STREAM_TYPE_SVCD,
+       30,
+       480, 480, 480,
+       { 10, 12, 21 },
+       2,
+       0,
+       /* 2.5 Mb/s peak limit to keep bbdmux followed by mplex -f 4 happy */
+       { 0, 1150000, 2500000 },
+       { 1, 15, 3},
+       /*
+        * Spatial filter = Manual, Temporal filter = Manual
+        * Median filter = Horizontal / Vertical
+        * Spatial filter value = 1, Temporal filter value = 4
+        */
+       { 0, 3, 1, 4 },
+       44100
+};
+
+static struct cxm_codec_profile svcd_pal_profile = {
+       "MPEG-2 SuperVCD PAL video and MPEG audio",
+       CXM_FW_STREAM_TYPE_SVCD,
+       25,
+       480, 576, 576,
+       { 6, 17, 22 },
+       2,
+       0,
+       /* 2.5 Mb/s peak limit to keep bbdmux followed by mplex -f 4 happy */
+       { 0, 1150000, 2500000 },
+       { 1, 12, 3},
+       /*
+        * Spatial filter = Manual, Temporal filter = Manual
+        * Median filter = Horizontal / Vertical
+        * Spatial filter value = 1, Temporal filter value = 4
+        */
+       { 0, 3, 1, 4 },
+       44100
+};
+
+static struct cxm_codec_profile dvd_half_d1_ntsc_profile = {
+       "MPEG-2 DVD NTSC video and MPEG audio",
+       CXM_FW_STREAM_TYPE_DVD,
+       30,
+       352, 480, 480,
+       { 10, 12, 21 },
+       2,
+       0,
+       { 0, 4000000, 4520000 }, /* 4 hours on 8.54 GB media */
+       { 1, 15, 3},
+       /*
+        * Spatial filter = Manual, Temporal filter = Manual
+        * Median filter = Horizontal / Vertical
+        * Spatial filter value = 1, Temporal filter value = 4
+        */
+       { 0, 3, 1, 4 },
+       48000
+};
+
+static struct cxm_codec_profile dvd_half_d1_pal_profile = {
+       "MPEG-2 DVD PAL video and MPEG audio",
+       CXM_FW_STREAM_TYPE_DVD,
+       25,
+       352, 576, 576,
+       { 6, 17, 22 },
+       2,
+       0,
+       { 0, 4000000, 4520000 }, /* 4 hours on 8.54 GB media */
+       { 1, 12, 3},
+       /*
+        * Spatial filter = Manual, Temporal filter = Manual
+        * Median filter = Horizontal / Vertical
+        * Spatial filter value = 1, Temporal filter value = 4
+        */
+       { 0, 3, 1, 4 },
+       48000
+};
+
+static struct cxm_codec_profile dvd_full_d1_ntsc_profile = {
+       "MPEG-2 DVD NTSC video and MPEG audio",
+       CXM_FW_STREAM_TYPE_DVD,
+       30,
+       720, 480, 480,
+       { 10, 12, 21 },
+       2,
+       0,
+       /* 9.52 Mb/s peak limit to keep bbdmux followed by mplex -f 8 happy */
+       { 0, 9000000, 9520000 }, /* 1 hour on 4.7 GB media */
+       { 1, 15, 3},
+       /*
+        * Spatial filter = Manual, Temporal filter = Manual
+        * Median filter = Horizontal / Vertical
+        * Spatial filter value = 1, Temporal filter value = 4
+        */
+       { 0, 3, 1, 4 },
+       48000
+};
+
+static struct cxm_codec_profile dvd_full_d1_pal_profile = {
+       "MPEG-2 DVD PAL video and MPEG audio",
+       CXM_FW_STREAM_TYPE_DVD,
+       25,
+       720, 576, 576,
+       { 6, 17, 22 },
+       2,
+       0,
+       /* 9.52 Mb/s peak limit to keep bbdmux followed by mplex -f 8 happy */
+       { 0, 9000000, 9520000 }, /* 1 hour on 4.7 GB media */
+       { 1, 12, 3},
+       /*
+        * Spatial filter = Manual, Temporal filter = Manual
+        * Median filter = Horizontal / Vertical
+        * Spatial filter value = 1, Temporal filter value = 4
+        */
+       { 0, 3, 1, 4 },
+       48000
+};
+
+
+static const struct cxm_codec_profile
+*codec_profiles[] = {
+       &vcd_ntsc_profile,
+       &vcd_pal_profile,
+       &svcd_ntsc_profile,
+       &svcd_pal_profile,
+       &dvd_half_d1_ntsc_profile,
+       &dvd_half_d1_pal_profile,
+       &dvd_full_d1_ntsc_profile,
+       &dvd_full_d1_pal_profile
+};
+
+
+static unsigned int
+cxm_queue_firmware_command(struct cxm_softc *sc,
+                           enum cxm_mailbox_name mbx_name, uint32_t cmd,
+                           uint32_t *parameters, unsigned int nparameters)
+{
+       unsigned int i;
+       unsigned int mailbox;
+       uint32_t completed_command;
+       uint32_t flags;
+
+       if (nparameters > CXM_MBX_MAX_PARAMETERS) {
+               device_printf(sc->dev, "too many parameters for mailbox\n");
+               return -1;
+       }
+
+       mailbox = 0;
+
+       switch (mbx_name) {
+       case cxm_dec_mailbox:
+               mailbox = sc->dec_mbx
+                         + CXM_MBX_FW_CMD_MAILBOX *sizeof(struct cxm_mailbox);
+               break;
+
+       case cxm_enc_mailbox:
+               mailbox = sc->enc_mbx
+                         + CXM_MBX_FW_CMD_MAILBOX *sizeof(struct cxm_mailbox);
+               break;
+
+       default:
+               return -1;
+       }
+
+       crit_enter();
+       for (i = 0; i < CXM_MBX_FW_CMD_MAILBOXES; i++) {
+               flags = CSR_READ_4(sc,
+                                  mailbox
+                                  + offsetof(struct cxm_mailbox, flags));
+               if (!(flags & CXM_MBX_FLAG_IN_USE))
+                       break;
+
+               /*
+                * Mail boxes containing certain completed commands
+                * for which the results are never needed can be reused.
+                */
+
+               if ((flags & (CXM_MBX_FLAG_DRV_DONE | CXM_MBX_FLAG_FW_DONE))
+                   == (CXM_MBX_FLAG_DRV_DONE | CXM_MBX_FLAG_FW_DONE)) {
+                       completed_command
+                        = CSR_READ_4(sc,
+                                     mailbox
+                                     + offsetof(struct cxm_mailbox, command));
+
+                       /*
+                        * DMA results are always check by reading the
+                        * DMA status register ... never by checking
+                        * the mailbox after the command has completed.
+                        */
+
+                       if (completed_command == CXM_FW_CMD_SCHED_DMA_TO_HOST)
+                               break;
+               }
+
+               mailbox += sizeof(struct cxm_mailbox);
+       }
+
+       if (i >= CXM_MBX_FW_CMD_MAILBOXES) {
+               crit_exit();
+               return -1;
+       }
+
+       CSR_WRITE_4(sc, mailbox + offsetof(struct cxm_mailbox, flags),
+                   CXM_MBX_FLAG_IN_USE);
+
+       /*
+        * PCI writes may be buffered so force the
+        * write to complete by reading the last
+        * location written.
+        */
+
+       CSR_READ_4(sc, mailbox + offsetof(struct cxm_mailbox, flags));
+
+       crit_exit();
+
+       CSR_WRITE_4(sc, mailbox + offsetof(struct cxm_mailbox, command), cmd);
+       CSR_WRITE_4(sc, mailbox + offsetof(struct cxm_mailbox, timeout),
+                   CXM_FW_STD_TIMEOUT);
+
+       for (i = 0; i < nparameters; i++)
+               CSR_WRITE_4(sc,
+                           mailbox
+                           + offsetof(struct cxm_mailbox, parameters)
+                           + i * sizeof(uint32_t),
+                           *(parameters + i));
+
+       for (; i < CXM_MBX_MAX_PARAMETERS; i++)
+               CSR_WRITE_4(sc,
+                           mailbox
+                           + offsetof(struct cxm_mailbox, parameters)
+                           + i * sizeof(uint32_t), 0);
+
+       CSR_WRITE_4(sc, mailbox + offsetof(struct cxm_mailbox, flags),
+                   CXM_MBX_FLAG_IN_USE | CXM_MBX_FLAG_DRV_DONE);
+
+       return mailbox;
+}
+
+
+static int
+cxm_firmware_command(struct cxm_softc *sc,
+                     enum cxm_mailbox_name mbx_name, uint32_t cmd,
+                     uint32_t *parameters, unsigned int nparameters)
+{
+       const char *wmesg;
+       unsigned int *bmp;
+       unsigned int i;
+       unsigned int mailbox;
+       uint32_t flags;
+       uint32_t result;
+
+       bmp = NULL;
+       wmesg = "";
+
+       switch (mbx_name) {
+       case cxm_dec_mailbox:
+               bmp = &sc->dec_mbx;
+               wmesg = "cxmdfw";
+               break;
+
+       case cxm_enc_mailbox:
+               bmp = &sc->enc_mbx;
+               wmesg = "cxmefw";
+               break;
+
+       default:
+               return -1;
+       }
+
+       mailbox = cxm_queue_firmware_command(sc, mbx_name, cmd,
+                                            parameters, nparameters);
+       if (mailbox == -1) {
+               device_printf(sc->dev, "no free mailboxes\n");
+               return -1;
+       }
+
+       /* Give the firmware a chance to start processing the request */
+       tsleep(bmp, 0, wmesg, hz / 100);
+
+       for (i = 0; i < 100; i++) {
+               flags = CSR_READ_4(sc,
+                                  mailbox
+                                  + offsetof(struct cxm_mailbox, flags));
+               if ((flags & CXM_MBX_FLAG_FW_DONE))
+                       break;
+
+               /* Wait for 10ms */
+               tsleep(bmp, 0, wmesg, hz / 100);
+       }
+
+       if (i >= 100) {
+               device_printf(sc->dev, "timeout\n");
+               return -1;
+       }
+
+       result = CSR_READ_4(sc,
+                           mailbox
+                           + offsetof(struct cxm_mailbox, result));
+
+       for (i = 0; i < nparameters; i++)
+               *(parameters + i)
+                 = CSR_READ_4(sc,
+                              mailbox
+                              + offsetof(struct cxm_mailbox, parameters)
+                              + i * sizeof(uint32_t));
+
+       CSR_WRITE_4(sc, mailbox + offsetof(struct cxm_mailbox, flags), 0);
+
+       return result == 0 ? 0 : -1;
+}
+
+
+static int
+cxm_firmware_command_nosleep(struct cxm_softc *sc,
+                             enum cxm_mailbox_name mbx_name, uint32_t cmd,
+                             uint32_t *parameters, unsigned int nparameters)
+{
+       unsigned int i;
+       unsigned int mailbox;
+       uint32_t flags;
+       uint32_t result;
+
+       for (i = 0; i < 100; i++) {
+               mailbox = cxm_queue_firmware_command(sc, mbx_name, cmd,
+                                                    parameters, nparameters);
+               if (mailbox != -1)
+                       break;
+
+               /* Wait for 10ms */
+               DELAY(10000);
+               }
+
+       if (i >= 100) {
+               device_printf(sc->dev, "no free mailboxes\n");
+               return -1;
+       }
+
+       /* Give the firmware a chance to start processing the request */
+       DELAY(10000);
+
+       for (i = 0; i < 100; i++) {
+               flags = CSR_READ_4(sc,
+                                  mailbox
+                                  + offsetof(struct cxm_mailbox, flags));
+               if ((flags & CXM_MBX_FLAG_FW_DONE))
+                       break;
+
+               /* Wait for 10ms */
+               DELAY(10000);
+       }
+
+       if (i >= 100) {
+               device_printf(sc->dev, "timeout\n");
+               return -1;
+       }
+
+       result = CSR_READ_4(sc,
+                           mailbox
+                           + offsetof(struct cxm_mailbox, result));
+
+       for (i = 0; i < nparameters; i++)
+               *(parameters + i)
+                 = CSR_READ_4(sc,
+                              mailbox
+                              + offsetof(struct cxm_mailbox, parameters)
+                              + i * sizeof(uint32_t));
+
+       CSR_WRITE_4(sc, mailbox + offsetof(struct cxm_mailbox, flags), 0);
+
+       return result == 0 ? 0 : -1;
+}
+
+
+static int
+cxm_stop_firmware(struct cxm_softc *sc)
+{
+
+       if (cxm_firmware_command_nosleep(sc, cxm_enc_mailbox,
+                                        CXM_FW_CMD_ENC_HALT_FW, NULL, 0) < 0)
+               return -1;
+
+       if (sc->type == cxm_iTVC15_type
+           && cxm_firmware_command_nosleep(sc, cxm_dec_mailbox,
+                                           CXM_FW_CMD_DEC_HALT_FW,
+                                           NULL, 0) < 0)
+               return -1;
+
+       /* Wait for 10ms */
+       DELAY(10000);
+
+       return 0;
+}
+
+
+static void
+cxm_set_irq_mask(struct cxm_softc *sc, uint32_t mask)
+{
+       crit_enter();
+
+       CSR_WRITE_4(sc, CXM_REG_IRQ_MASK, mask);
+
+       /*
+        * PCI writes may be buffered so force the
+        * write to complete by reading the last
+        * location written.
+        */
+
+       CSR_READ_4(sc, CXM_REG_IRQ_MASK);
+
+       sc->irq_mask = mask;
+
+       crit_exit();
+}
+
+
+static void
+cxm_set_irq_status(struct cxm_softc *sc, uint32_t status)
+{
+
+       CSR_WRITE_4(sc, CXM_REG_IRQ_STATUS, status);
+
+       /*
+        * PCI writes may be buffered so force the
+        * write to complete by reading the last
+        * location written.
+        */
+
+       CSR_READ_4(sc, CXM_REG_IRQ_STATUS);
+}
+
+
+static int
+cxm_stop_hardware(struct cxm_softc *sc)
+{
+       if (sc->cxm_iic) {
+               if (cxm_saa7115_mute(sc) < 0)
+                       return -1;
+               if (cxm_msp_mute(sc) < 0)
+                       return -1;
+       }
+
+       /* Halt the firmware */
+       if (sc->enc_mbx != -1) {
+               if (cxm_stop_firmware(sc) < 0)
+                       return -1;
+       }
+
+       /* Mask all interrupts */
+       cxm_set_irq_mask(sc, 0xffffffff);
+
+       /* Stop VDM */
+       CSR_WRITE_4(sc, CXM_REG_VDM, CXM_CMD_VDM_STOP);
+
+       /* Stop AO */
+       CSR_WRITE_4(sc, CXM_REG_AO, CXM_CMD_AO_STOP);
+
+       /* Ping (?) APU */
+       CSR_WRITE_4(sc, CXM_REG_APU, CXM_CMD_APU_PING);
+
+       /* Stop VPU */
+       CSR_WRITE_4(sc, CXM_REG_VPU, sc->type == cxm_iTVC15_type
+                                       ? CXM_CMD_VPU_STOP15
+                                       : CXM_CMD_VPU_STOP16);
+
+       /* Reset Hw Blocks */
+       CSR_WRITE_4(sc, CXM_REG_HW_BLOCKS, CXM_CMD_HW_BLOCKS_RST);
+
+       /* Stop SPU */
+       CSR_WRITE_4(sc, CXM_REG_SPU, CXM_CMD_SPU_STOP);
+
+       /* Wait for 10ms */
+       DELAY(10000);
+
+       return 0;
+}
+
+
+static int
+cxm_download_firmware(struct cxm_softc *sc)
+{
+       unsigned int i;
+       const uint32_t *fw;
+
+       /* Download the encoder firmware */
+       fw = (const uint32_t *)cxm_enc_fw;
+       for (i = 0; i < CXM_FW_SIZE; i += sizeof(*fw))
+               CSR_WRITE_4(sc, CXM_MEM_ENC + i, *fw++);
+
+       /* Download the decoder firmware */
+       if (sc->type == cxm_iTVC15_type) {
+               fw = (const uint32_t *)cxm_dec_fw;
+               for (i = 0; i < CXM_FW_SIZE; i += sizeof(*fw))
+                       CSR_WRITE_4(sc, CXM_MEM_DEC + i, *fw++);
+       }
+
+       return 0;
+}
+
+
+static int
+cxm_init_hardware(struct cxm_softc *sc)
+{
+       unsigned int i;
+       unsigned int mailbox;
+       uint32_t parameter;
+
+       if (cxm_stop_hardware(sc) < 0)
+               return -1;
+
+       /* Initialize encoder SDRAM pre-charge */
+       CSR_WRITE_4(sc, CXM_REG_ENC_SDRAM_PRECHARGE,
+                       CXM_CMD_SDRAM_PRECHARGE_INIT);
+
+       /* Initialize encoder SDRAM refresh to 1us */
+       CSR_WRITE_4(sc, CXM_REG_ENC_SDRAM_REFRESH,
+                       CXM_CMD_SDRAM_REFRESH_INIT);
+
+       /* Initialize decoder SDRAM pre-charge */
+       CSR_WRITE_4(sc, CXM_REG_DEC_SDRAM_PRECHARGE,
+                       CXM_CMD_SDRAM_PRECHARGE_INIT);
+
+       /* Initialize decoder SDRAM refresh to 1us */
+       CSR_WRITE_4(sc, CXM_REG_DEC_SDRAM_REFRESH,
+                       CXM_CMD_SDRAM_REFRESH_INIT);
+
+       /* Wait for 600ms */
+       DELAY(600000);
+
+       if (cxm_download_firmware(sc) < 0)
+               return -1;
+
+       /* Enable SPU */
+       CSR_WRITE_4(sc, CXM_REG_SPU,
+                       CSR_READ_4(sc, CXM_REG_SPU) & CXM_MASK_SPU_ENABLE);
+
+       /* Wait for 1 second */
+       DELAY(1000000);
+
+       /* Enable VPU */
+       CSR_WRITE_4(sc, CXM_REG_VPU,
+                       CSR_READ_4(sc, CXM_REG_VPU)
+                       & (sc->type == cxm_iTVC15_type
+                               ? CXM_MASK_VPU_ENABLE15
+                               : CXM_MASK_VPU_ENABLE16));
+
+       /* Wait for 1 second */
+       DELAY(1000000);
+
+       /* Locate encoder mailbox */
+       mailbox = CXM_MEM_ENC;
+       for (i = 0; i < CXM_MEM_ENC_SIZE; i += 0x100)
+               if (CSR_READ_4(sc, mailbox + i) == 0x12345678
+                   && CSR_READ_4(sc, mailbox + i + 4) == 0x34567812
+                   && CSR_READ_4(sc, mailbox + i + 8) == 0x56781234
+                   && CSR_READ_4(sc, mailbox + i + 12) == 0x78123456)
+                       break;
+
+       if (i >= CXM_MEM_ENC_SIZE)
+               return -1;
+
+       sc->enc_mbx = mailbox + i + 16;
+
+       /* Locate decoder mailbox */
+       if (sc->type == cxm_iTVC15_type) {
+               mailbox = CXM_MEM_DEC;
+               for (i = 0; i < CXM_MEM_DEC_SIZE; i += 0x100)
+                       if (CSR_READ_4(sc, mailbox + i) == 0x12345678
+                           && CSR_READ_4(sc, mailbox + i + 4) == 0x34567812
+                           && CSR_READ_4(sc, mailbox + i + 8) == 0x56781234
+                           && CSR_READ_4(sc, mailbox + i + 12) == 0x78123456)
+                               break;
+
+               if (i >= CXM_MEM_DEC_SIZE)
+                       return -1;
+
+               sc->dec_mbx = mailbox + i + 16;
+       }
+
+       /* Get encoder firmware version */
+       parameter = 0;
+       if (cxm_firmware_command_nosleep(sc, cxm_enc_mailbox,
+                                        CXM_FW_CMD_ENC_GET_FW_VER,
+                                        &parameter, 1) < 0)
+               return -1;
+
+       device_printf(sc->dev, "encoder firmware version %#x\n",
+           (unsigned int)parameter);
+
+       /* Get decoder firmware version */
+       if (sc->type == cxm_iTVC15_type) {
+               parameter = 0;
+               if (cxm_firmware_command_nosleep(sc, cxm_dec_mailbox,
+                                                CXM_FW_CMD_DEC_GET_FW_VER,
+                                                &parameter, 1) < 0)
+                       return -1;
+
+               device_printf(sc->dev, "decoder firmware version %#x\n",
+                   (unsigned int)parameter);
+       }
+
+       return 0;
+}
+
+
+static int
+cxm_configure_encoder(struct cxm_softc *sc)
+{
+       int fps;
+       unsigned int i;
+       uint32_t parameters[12];
+       const struct cxm_codec_profile *cpp;
+
+       if (sc->source == cxm_fm_source)
+               switch (cxm_tuner_selected_channel_set(sc)) {
+               case CHNLSET_NABCST:
+               case CHNLSET_CABLEIRC:
+               case CHNLSET_JPNBCST:
+               case CHNLSET_JPNCABLE:
+                       fps = 30;
+                       break;
+
+               default:
+                       fps = 25;
+                       break;
+               }
+       else
+               fps = cxm_saa7115_detected_fps(sc);
+
+       if (fps < 0)
+               return -1;
+
+       if (sc->profile->fps != fps) {
+
+               /*
+                * Pick a profile with the correct fps using the
+                * chosen stream type and width to decide between
+                * the VCD, SVCD, or DVD profiles.
+                */
+
+               for (i = 0; i < NUM_ELEMENTS(codec_profiles); i++)
+                       if (codec_profiles[i]->fps == fps
+                           && codec_profiles[i]->stream_type
+                              == sc->profile->stream_type
+                           && codec_profiles[i]->width == sc->profile->width)
+                               break;
+
+               if (i >= NUM_ELEMENTS(codec_profiles))
+                       return -1;
+
+               sc->profile = codec_profiles[i];
+       }
+
+       cpp = sc->profile;
+
+       if (cxm_saa7115_configure(sc,
+                                 cpp->width, cpp->source_height, fps,
+                                 cpp->audio_sample_rate) < 0)
+               return -1;
+
+       /* assign dma block len */
+       parameters[0] = 1; /* Transfer block size = 1 */
+       parameters[1] = 1; /* Units = 1 (frames) */
+       if (cxm_firmware_command(sc, cxm_enc_mailbox,
+                                CXM_FW_CMD_ASSIGN_DMA_BLOCKLEN,
+                                parameters, 2) != 0)
+               return -1;
+
+
+       /* assign program index info */
+       parameters[0] = 0; /* Picture mask = 0 (don't generate index) */
+       parameters[1] = 0; /* Num_req = 0 */
+       if (cxm_firmware_command(sc, cxm_enc_mailbox,
+                                CXM_FW_CMD_ASSIGN_PGM_INDEX_INFO,
+                                parameters, 2) != 0)
+               return -1;
+
+       /* assign stream type */
+       parameters[0] = cpp->stream_type;
+       if (cxm_firmware_command(sc, cxm_enc_mailbox,
+                                CXM_FW_CMD_ASSIGN_STREAM_TYPE,
+                                parameters, 1) != 0)
+               return -1;
+
+       /* assign output port */
+       parameters[0] = 0; /* 0 (Memory) */
+       if (cxm_firmware_command(sc, cxm_enc_mailbox,
+                                CXM_FW_CMD_ASSIGN_OUTPUT_PORT,
+                                parameters, 1) != 0)
+               return -1;
+
+       /* assign framerate */
+       parameters[0] = cpp->fps == 30 ? 0 : 1;
+       if (cxm_firmware_command(sc, cxm_enc_mailbox,
+                                CXM_FW_CMD_ASSIGN_FRAME_RATE,
+                                parameters, 1) != 0)
+               return -1;
+
+       /* assign frame size */
+       parameters[0] = cpp->height;
+       parameters[1] = cpp->width;
+       if (cxm_firmware_command(sc, cxm_enc_mailbox,
+                                CXM_FW_CMD_ASSIGN_FRAME_SIZE,
+                                parameters, 2) != 0)
+               return -1;
+
+       /* assign aspect ratio */
+       parameters[0] = cpp->aspect;
+       if (cxm_firmware_command(sc, cxm_enc_mailbox,
+                                CXM_FW_CMD_ASSIGN_ASPECT_RATIO,
+                                parameters, 1) != 0)
+               return -1;
+
+       /* assign bitrates */
+       parameters[0] = cpp->bitrate.mode;
+       parameters[1] = cpp->bitrate.average;
+       parameters[2] = cpp->bitrate.peak / 400;
+       if (cxm_firmware_command(sc, cxm_enc_mailbox,
+                                CXM_FW_CMD_ASSIGN_BITRATES,
+                                parameters, 3) != 0)
+               return -1;
+
+       /* assign gop closure */
+       parameters[0] = cpp->gop.closure;
+       if (cxm_firmware_command(sc, cxm_enc_mailbox,
+                                CXM_FW_CMD_ASSIGN_GOP_CLOSURE,
+                                parameters, 1) != 0)
+               return -1;
+
+       /* assign gop properties */
+       parameters[0] = cpp->gop.frames;
+       parameters[1] = cpp->gop.bframes;
+       if (cxm_firmware_command(sc, cxm_enc_mailbox,
+                                CXM_FW_CMD_ASSIGN_GOP_PROPERTIES,
+                                parameters, 2) != 0)
+               return -1;
+
+       /* assign 3 2 pulldown */
+       parameters[0] = cpp->pulldown;
+       if (cxm_firmware_command(sc, cxm_enc_mailbox,
+                                CXM_FW_CMD_ASSIGN_3_2_PULLDOWN,
+                                parameters, 1) != 0)
+               return -1;
+
+       /* assign dnr filter mode */
+       parameters[0] = cpp->dnr.mode;
+       parameters[1] = cpp->dnr.type;
+       if (cxm_firmware_command(sc, cxm_enc_mailbox,
+                                CXM_FW_CMD_ASSIGN_DNR_FILTER_MODE,
+                                parameters, 2) != 0)
+               return -1;
+
+       /* assign dnr filter props */
+       parameters[0] = cpp->dnr.spatial;
+       parameters[1] = cpp->dnr.temporal;
+       if (cxm_firmware_command(sc, cxm_enc_mailbox,
+                                CXM_FW_CMD_ASSIGN_DNR_FILTER_PROPERTIES,
+                                parameters, 2) != 0)
+               return -1;
+
+       /*
+        * assign audio properties
+        */
+
+       for (i = 0; i < NUM_ELEMENTS(codec_audio_formats); i++)
+               if (codec_audio_formats[i].sample_rate
+                   == cpp->audio_sample_rate)
+                       break;
+
+       if (i >= NUM_ELEMENTS(codec_audio_formats))
+               return -1;
+
+       parameters[0] = codec_audio_formats[i].format;
+       if (cxm_firmware_command(sc, cxm_enc_mailbox,
+                                CXM_FW_CMD_ASSIGN_AUDIO_PROPERTIES,
+                                parameters, 1) != 0)
+               return -1;
+
+       /* assign coring levels */
+       parameters[0] = 0; /* luma_h */
+       parameters[1] = 255; /* luma_l */
+       parameters[2] = 0; /* chroma_h */
+       parameters[3] = 255; /* chroma_l */
+       if (cxm_firmware_command(sc, cxm_enc_mailbox,
+                                CXM_FW_CMD_ASSIGN_CORING_LEVELS,
+                                parameters, 4) != 0)
+               return -1;
+
+       /* assign spatial filter type */
+       parameters[0] = 3; /* Luminance filter = 3 (2D H/V Separable) */
+       parameters[1] = 1; /* Chrominance filter = 1 (1D Horizontal) */
+       if (cxm_firmware_command(sc, cxm_enc_mailbox,
+                                CXM_FW_CMD_ASSIGN_SPATIAL_FILTER_TYPE,
+                                parameters, 2) != 0)
+               return -1;
+
+       /* assign frame drop rate */
+       parameters[0] = 0;
+       if (cxm_firmware_command(sc, cxm_enc_mailbox,
+                                CXM_FW_CMD_ASSIGN_FRAME_DROP_RATE,
+                                parameters, 1) != 0)
+               return -1;
+
+       /* assign placeholder */
+       parameters[0] = 0; /* type = 0 (Extension / UserData) */
+       parameters[1] = 0; /* period */
+       parameters[2] = 0; /* size_t */
+       parameters[3] = 0; /* arg0 */
+       parameters[4] = 0; /* arg1 */
+       parameters[5] = 0; /* arg2 */
+       parameters[6] = 0; /* arg3 */
+       parameters[7] = 0; /* arg4 */
+       parameters[8] = 0; /* arg5 */
+       parameters[9] = 0; /* arg6 */
+       parameters[10] = 0; /* arg7 */
+       parameters[11] = 0; /* arg8 */
+       if (cxm_firmware_command(sc, cxm_enc_mailbox,
+                                CXM_FW_CMD_ASSIGN_PLACEHOLDER,
+                                parameters, 12) != 0)
+               return -1;
+
+       /* assign VBI properties */
+       parameters[0] = 0xbd04; /* mode = 0 (sliced), stream and user data */
+       parameters[1] = 0; /* frames per interrupt (only valid in raw mode) */
+       parameters[2] = 0; /* total raw VBI frames (only valid in raw mode) */
+       parameters[3] = 0x25256262; /* ITU 656 start codes (saa7115 table 24)*/
+       parameters[4] = 0x38387f7f; /* ITU 656 stop codes (saa7115 table 24) */
+       parameters[5] = cpp->vbi.nlines; /* lines per frame */
+       parameters[6] = 1440; /* bytes per line = 720 pixels */
+       if (cxm_firmware_command(sc, cxm_enc_mailbox,
+                                CXM_FW_CMD_ASSIGN_VBI_PROPERTIES,
+                                parameters, 7) != 0)
+               return -1;
+
+       /* assign VBI lines */
+       parameters[0] = 0xffffffff; /* all lines */
+       parameters[1] = 0; /* disable VBI features */
+       parameters[2] = 0;
+       parameters[3] = 0;
+       parameters[4] = 0;
+       if (cxm_firmware_command(sc, cxm_enc_mailbox,
+                                CXM_FW_CMD_ASSIGN_VBI_LINE,
+                                parameters, 5) != 0)
+               return -1;
+
+       /* assign number of lines in fields 1 and 2 */
+       parameters[0] = cpp->source_height / 2 + cpp->vbi.nlines;
+       parameters[1] = cpp->source_height / 2 + cpp->vbi.nlines;
+       if (cxm_firmware_command(sc, cxm_enc_mailbox,
+                                CXM_FW_CMD_ASSIGN_NUM_VSYNC_LINES,
+                                parameters, 2) != 0)
+               return -1;
+
+       return 0;
+}
+
+
+static int
+cxm_start_encoder(struct cxm_softc *sc)
+{
+       uint32_t parameters[4];
+       uint32_t subtype;
+       uint32_t type;
+
+
+       if (sc->encoding)
+               return 0;
+
+       if (cxm_configure_encoder(sc) < 0)
+               return -1;
+
+       /* Mute the video input if necessary. */
+       parameters[0] = sc->source == cxm_fm_source ? 1 : 0;
+       if (cxm_firmware_command(sc, cxm_enc_mailbox,
+                                CXM_FW_CMD_MUTE_VIDEO_INPUT,
+                                parameters, 1) != 0)
+               return -1;
+
+       /* Clear pending encoder interrupts (which are currently masked) */
+       cxm_set_irq_status(sc, CXM_IRQ_ENC);
+
+       /* Enable event notification */
+       parameters[0] = 0; /* Event = 0 (refresh encoder input) */
+       parameters[1] = 1; /* Notification = 1 (enable) */
+       parameters[2] = 0x10000000; /* Interrupt bit */
+       parameters[3] = -1; /* Mailbox = -1 (no mailbox) */
+       if (cxm_firmware_command(sc, cxm_enc_mailbox,
+                                CXM_FW_CMD_ENC_EVENT_NOTIFICATION,
+                                parameters, 4) != 0)
+               return -1;
+
+       if (cxm_saa7115_mute(sc) < 0)
+               return -1;
+       if (cxm_msp_mute(sc) < 0)
+               return -1;
+
+       if (cxm_firmware_command(sc, cxm_enc_mailbox,
+                                CXM_FW_CMD_INITIALIZE_VIDEO_INPUT,
+                                NULL, 0) < 0)
+               return -1;
+
+       if (cxm_saa7115_unmute(sc) < 0)
+               return -1;
+       if (cxm_msp_unmute(sc) < 0)
+               return -1;
+
+       /* Wait for 100ms */
+       tsleep(&sc->encoding, 0, "cxmce", hz / 10);
+
+       type = sc->mpeg ? CXM_FW_CAPTURE_STREAM_TYPE_MPEG
+                       : CXM_FW_CAPTURE_STREAM_TYPE_RAW;
+       subtype = ((sc->mpeg || sc->source == cxm_fm_source)
+                  ? CXM_FW_CAPTURE_STREAM_PCM_AUDIO : 0)
+                 | ((sc->mpeg || sc->source != cxm_fm_source)
+                    ? CXM_FW_CAPTURE_STREAM_YUV : 0);
+
+       /* Start the encoder */
+       parameters[0] = type;
+       parameters[1] = subtype;
+       if (cxm_firmware_command(sc, cxm_enc_mailbox,
+                                CXM_FW_CMD_BEGIN_CAPTURE, parameters, 2) != 0)
+               return -1;
+
+       sc->enc_pool.offset = 0;
+       sc->enc_pool.read = 0;
+       sc->enc_pool.write = 0;
+
+       sc->encoding_eos = 0;
+
+       sc->encoding = 1;
+
+       /* Enable interrupts */
+       cxm_set_irq_mask(sc, sc->irq_mask & ~CXM_IRQ_ENC);
+
+       return 0;
+}
+
+
+static int
+cxm_stop_encoder(struct cxm_softc *sc)
+{
+       uint32_t parameters[4];
+       uint32_t subtype;
+       uint32_t type;
+
+       if (!sc->encoding)
+               return 0;
+
+       type = sc->mpeg ? CXM_FW_CAPTURE_STREAM_TYPE_MPEG
+                       : CXM_FW_CAPTURE_STREAM_TYPE_RAW;
+       subtype = ((sc->mpeg || sc->source == cxm_fm_source)
+                  ? CXM_FW_CAPTURE_STREAM_PCM_AUDIO : 0)
+                 | ((sc->mpeg || sc->source != cxm_fm_source)
+                    ? CXM_FW_CAPTURE_STREAM_YUV : 0);
+
+       /* Stop the encoder */
+       parameters[0] = sc->mpeg ? 0 : 1; /* When = 0 (end of GOP) */
+       parameters[1] = type;
+       parameters[2] = subtype;
+       if (cxm_firmware_command(sc, cxm_enc_mailbox,
+                                CXM_FW_CMD_END_CAPTURE, parameters, 3) != 0)
+               return -1;
+
+       /* Wait for up to 1 second */
+       crit_enter();
+       if (!sc->encoding_eos)
+               tsleep(&sc->encoding_eos, 0, "cxmeos", hz);
+       crit_exit();
+
+       if (sc->mpeg && !sc->encoding_eos)
+               device_printf(sc->dev, "missing encoder EOS\n");
+
+       /* Disable event notification */
+       parameters[0] = 0; /* Event = 0 (refresh encoder input) */
+       parameters[1] = 0; /* Notification = 0 (disable) */
+       parameters[2] = 0x10000000; /* Interrupt bit */
+       parameters[3] = -1; /* Mailbox = -1 (no mailbox) */
+       if (cxm_firmware_command(sc, cxm_enc_mailbox,
+                                CXM_FW_CMD_ENC_EVENT_NOTIFICATION,
+                                parameters, 4) != 0)
+               return -1;
+
+       /* Disable interrupts */
+       cxm_set_irq_mask(sc, sc->irq_mask | CXM_IRQ_ENC);
+
+       sc->encoding = 0;
+
+       return 0;
+}
+
+
+static int
+cxm_pause_encoder(struct cxm_softc *sc)
+{
+       uint32_t parameter;
+
+       /* Pause the encoder */
+       parameter = 0;
+       if (cxm_firmware_command(sc, cxm_enc_mailbox,
+                                CXM_FW_CMD_PAUSE_ENCODER, &parameter, 1) != 0)
+               return -1;
+
+       return 0;
+}
+
+
+static int
+cxm_unpause_encoder(struct cxm_softc *sc)
+{
+       uint32_t parameter;
+
+       /* Unpause the encoder */
+       parameter = 1;
+       if (cxm_firmware_command(sc, cxm_enc_mailbox,
+                                CXM_FW_CMD_PAUSE_ENCODER, &parameter, 1) != 0)
+               return -1;
+
+       return 0;
+}
+
+
+static unsigned int
+cxm_encoder_fixup_byte_order(struct cxm_softc *sc,
+                             unsigned int current, size_t offset)
+{
+       unsigned int    strips;
+       unsigned int    i;
+       unsigned int    j;
+       unsigned int    k;
+       unsigned int    macroblocks_per_line;
+       unsigned int    scratch;
+       unsigned int    words_per_line;
+       uint32_t        *ptr;
+       uint32_t        *src;
+       size_t          nbytes;
+
+       switch (sc->enc_pool.bufs[current].byte_order) {
+       case cxm_device_mpeg_byte_order:
+
+               /*
+                * Convert each 32 bit word to the proper byte ordering.
+                */
+
+               for (nbytes = 0,
+                    ptr = (uint32_t *)sc->enc_pool.bufs[current].vaddr;
+                    nbytes != sc->enc_pool.bufs[current].size;
+                    nbytes += sizeof(*ptr), ptr++)
+                       *ptr = bswap32(*ptr);
+               break;
+
+       case cxm_device_yuv12_byte_order:
+
+               /*
+                * Convert each macro block to planar using
+                * a scratch buffer (the buffer prior to the
+                * current buffer is always free since it marks
+                * the end of the ring buffer).
+                */
+
+               scratch = (current + (CXM_SG_BUFFERS - 1)) % CXM_SG_BUFFERS;
+
+               if (offset) {
+                       current = scratch;
+                       break;
+               }
+
+               src = (uint32_t *)sc->enc_pool.bufs[current].vaddr;
+               words_per_line = sc->profile->width / sizeof(*ptr);
+               macroblocks_per_line
+                 = sc->profile->width / CXM_MACROBLOCK_WIDTH;
+               strips = sc->enc_pool.bufs[current].size
+                          / (macroblocks_per_line * CXM_MACROBLOCK_SIZE);
+
+               for (i = 0; i < strips; i++) {
+                       ptr = (uint32_t *)sc->enc_pool.bufs[scratch].vaddr
+                             + i * macroblocks_per_line * CXM_MACROBLOCK_SIZE
+                               / sizeof(*ptr);
+                       for (j = 0; j < macroblocks_per_line; j++) {
+                               for (k = 0; k < CXM_MACROBLOCK_HEIGHT; k++) {
+#if CXM_MACROBLOCK_WIDTH != 16
+#  error CXM_MACROBLOCK_WIDTH != 16
+#endif
+                                       *(ptr + k * words_per_line)
+                                         = *src++;
+                                       *(ptr + k * words_per_line + 1)
+                                         = *src++;
+                                       *(ptr + k * words_per_line + 2)
+                                         = *src++;
+                                       *(ptr + k * words_per_line + 3)
+                                         = *src++;
+                               }
+                               ptr += CXM_MACROBLOCK_WIDTH / sizeof(*ptr);
+                       }
+               }
+
+               sc->enc_pool.bufs[scratch].size
+                 = sc->enc_pool.bufs[current].size;
+
+               current = scratch;
+               break;
+
+       default:
+               break;
+       }
+
+       sc->enc_pool.bufs[current].byte_order = cxm_host_byte_order;
+
+       return current;
+}
+
+
+static void
+cxm_encoder_dma_discard(struct cxm_softc *sc)
+{
+       uint32_t parameters[3];
+
+       /* Discard the DMA request */
+       parameters[0] = 0;
+       parameters[1] = 0;
+       parameters[2] = 0;
+       if (cxm_queue_firmware_command(sc, cxm_enc_mailbox,
+                                      CXM_FW_CMD_SCHED_DMA_TO_HOST,
+                                      parameters, 3) == -1) {
+               device_printf(sc->dev,
+                   "failed to discard encoder dma request\n");
+               return;
+       }
+
+       sc->encoding_dma = -1;
+}
+
+
+static void
+cxm_encoder_dma_done(struct cxm_softc *sc)
+{
+       int buffers_pending;
+       uint32_t status;
+
+       if (!sc->encoding_dma) {
+               device_printf(sc->dev,
+                   "encoder dma not already in progress\n");
+               return;
+       }
+
+       buffers_pending = sc->encoding_dma;
+       sc->encoding_dma = 0;
+
+       if (buffers_pending < 0)
+               return;
+
+       status = CSR_READ_4(sc, CXM_REG_DMA_STATUS) & 0x0000000f;
+
+       if ((status
+            & (CXM_DMA_ERROR_LIST | CXM_DMA_ERROR_WRITE | CXM_DMA_SUCCESS))
+           != CXM_DMA_SUCCESS) {
+               device_printf(sc->dev, "encoder dma status %#x\n",
+                   (unsigned int)status);
+               return;
+       }
+
+       /* Update the books */
+       crit_enter();
+       sc->enc_pool.write = (sc->enc_pool.write + buffers_pending)
+                                  % CXM_SG_BUFFERS;
+       crit_exit();
+
+       /* signal anyone requesting notification */
+       if (sc->enc_proc)
+               ksignal (sc->enc_proc, sc->enc_signal);
+
+       /* wakeup anyone waiting for data */
+       wakeup(&sc->enc_pool.read);
+
+       /* wakeup anyone polling for data */
+       selwakeup(&sc->enc_sel);
+}
+
+
+static void
+cxm_encoder_dma_request(struct cxm_softc *sc)
+{
+       enum cxm_byte_order byte_order;
+       int buffers_free;
+       int buffers_pending;
+       unsigned int current;
+       unsigned int i;
+       unsigned int mailbox;
+       unsigned int macroblocks_per_line;
+       unsigned int nrequests;
+       unsigned int strips;
+       uint32_t parameters[CXM_MBX_MAX_PARAMETERS];
+       uint32_t type;
+       size_t max_sg_segment;
+       struct {
+               size_t offset;
+               size_t size;
+       } requests[2];
+
+       if (sc->encoding_dma) {
+               device_printf(sc->dev, "encoder dma already in progress\n");
+               cxm_encoder_dma_discard(sc);
+               return;
+       }
+
+       mailbox = sc->enc_mbx
+                 + CXM_MBX_FW_DMA_MAILBOX * sizeof(struct cxm_mailbox);
+
+       for (i = 0; i < CXM_MBX_MAX_PARAMETERS; i++)
+               parameters[i]
+                 = CSR_READ_4(sc,
+                              mailbox
+                              + offsetof(struct cxm_mailbox, parameters)
+                              + i * sizeof(uint32_t)
+                             );
+
+       byte_order = cxm_device_mpeg_byte_order;
+       max_sg_segment = CXM_SG_SEGMENT;
+       nrequests = 0;
+       type = parameters[0];
+
+       switch (type) {
+       case 0: /* MPEG */
+               requests[nrequests].offset = parameters[1];
+               requests[nrequests++].size = parameters[2];
+               break;
+
+       case 1: /* YUV */
+               byte_order = cxm_device_yuv12_byte_order;
+
+               /*
+                * Simplify macroblock unpacking by ensuring
+                * that strips don't span buffers.
+                */
+
+#if CXM_MACROBLOCK_SIZE % 256
+#  error CXM_MACROBLOCK_SIZE not a multiple of 256
+#endif
+
+               macroblocks_per_line = sc->profile->width
+                                      / CXM_MACROBLOCK_WIDTH;
+               strips = CXM_SG_SEGMENT
+                        / (macroblocks_per_line * CXM_MACROBLOCK_SIZE);
+               max_sg_segment = strips
+                                * macroblocks_per_line * CXM_MACROBLOCK_SIZE;
+
+               requests[nrequests].offset = parameters[1]; /* Y */
+               requests[nrequests++].size = parameters[2];
+               requests[nrequests].offset = parameters[3]; /* UV */
+               requests[nrequests++].size = parameters[4];
+               break;
+
+       case 2: /* PCM (audio) */
+       case 3: /* VBI */
+       default:
+               device_printf(sc->dev, "encoder dma type %#x unsupported\n",
+                   (unsigned int)type);
+               cxm_encoder_dma_discard(sc);
+               return;
+       }
+
+       /*
+        * Determine the number of buffers free at this * instant *
+        * taking into consideration that the ring buffer wraps.
+        */
+       crit_enter();
+       buffers_free = sc->enc_pool.read - sc->enc_pool.write;
+       if (buffers_free <= 0)
+               buffers_free += CXM_SG_BUFFERS;
+       crit_exit();
+
+       /*
+        * Build the scatter / gather list taking in
+        * consideration that the ring buffer wraps,
+        * at least one free buffer must always be
+        * present to mark the end of the ring buffer,
+        * and each transfer must be a multiple of 256.
+        */
+
+       buffers_pending = 0;
+       current = sc->enc_pool.write;
+
+       for (i = 0; i < nrequests; i++) {
+               if (!requests[i].size) {
+                       device_printf(sc->dev, "encoder dma size is zero\n");
+                       cxm_encoder_dma_discard(sc);
+                       return;
+               }
+
+               while (requests[i].size) {
+                       sc->enc_pool.bufs[current].size
+                         = requests[i].size > max_sg_segment
+                           ? max_sg_segment : requests[i].size;
+                       sc->enc_pool.bufs[current].byte_order = byte_order;
+
+                       sc->enc_sg.vaddr[buffers_pending].src
+                         = requests[i].offset;
+                       sc->enc_sg.vaddr[buffers_pending].dst
+                         = sc->enc_pool.bufs[current].baddr;
+                       sc->enc_sg.vaddr[buffers_pending].size
+                         = (sc->enc_pool.bufs[current].size + 0x000000ff)
+                           & 0xffffff00;
+
+                       requests[i].offset += sc->enc_pool.bufs[current].size;
+                       requests[i].size -= sc->enc_pool.bufs[current].size;
+                       buffers_pending++;
+                       current = (current + 1) % CXM_SG_BUFFERS;
+
+                       if (buffers_pending >= buffers_free) {
+                               device_printf(sc->dev,
+                                   "encoder dma not enough buffer space free\n");
+                               cxm_encoder_dma_discard(sc);
+                               return;
+                       }
+               }
+       }
+
+       /* Mark the last transfer in the list */
+       sc->enc_sg.vaddr[buffers_pending - 1].size |= 0x80000000;
+
+       /* Schedule the DMA */
+       parameters[0] = sc->enc_sg.baddr;
+       parameters[1] = buffers_pending * sizeof(sc->enc_sg.vaddr[0]);
+       parameters[2] = type;
+       if (cxm_queue_firmware_command(sc, cxm_enc_mailbox,
+                                      CXM_FW_CMD_SCHED_DMA_TO_HOST,
+                                      parameters, 3) == -1) {
+               device_printf(sc->dev,
+                   "failed to schedule encoder dma request\n");
+               return;
+       }
+
+       /*
+        * Record the number of pending buffers for the
+        * benefit of cxm_encoder_dma_done.  Doing this
+        * after queuing the command doesn't introduce
+        * a race condition since we're already in the
+        * interrupt handler.
+        */
+
+       sc->encoding_dma = buffers_pending;
+}
+
+
+static int
+cxm_encoder_wait_for_lock(struct cxm_softc *sc)
+{
+       int muted;
+       int locked;
+       int result;
+
+       locked = 1;
+
+       /*
+        * Wait for the tuner to lock.
+        */
+       if (sc->source == cxm_fm_source || sc->source == cxm_tuner_source) {
+               result = cxm_tuner_wait_for_lock(sc);
+               if (result <= 0)
+                       return result;
+       }
+
+       /*
+        * Wait for the video decoder to lock.
+        */
+       if (sc->source != cxm_fm_source) {
+               result = cxm_saa7115_wait_for_lock(sc);
+               if (result < 0)
+                       return result;
+               else if (result == 0)
+                       locked = 0;
+               }
+
+       /*
+        * Wait for the audio decoder to lock.
+        */
+       if (sc->source == cxm_tuner_source) {
+               muted = cxm_msp_is_muted(sc);
+
+               result = cxm_msp_autodetect_standard(sc);
+               if (result < 0)
+                       return result;
+               else if (result == 0)
+                       locked = 0;
+
+               if (muted == 0 && cxm_msp_unmute(sc) < 0)
+                       return -1;
+       }
+
+       return locked;
+}
+
+
+static void
+cxm_mapmem(void *arg, bus_dma_segment_t *segs, int nseg, int error)
+{
+       bus_addr_t *busaddrp;
+
+       /*
+        * Only the first bus space address is needed
+        * since it's known that the memory is physically
+        * contiguous due to bus_dmamem_alloc.
+        */
+
+       busaddrp = (bus_addr_t *)arg;
+       *busaddrp = segs->ds_addr;
+}
+
+
+/*
+ * the boot time probe routine.
+ */
+static int
+cxm_probe(device_t dev)
+{
+       struct cxm_dev          *t;
+
+       t = cxm_devs;
+
+       while(t->name != NULL) {
+               if ((pci_get_vendor(dev) == t->vid) &&
+                   (pci_get_device(dev) == t->did)) {
+                       device_set_desc(dev, t->name);
+                       return 0;
+               }
+               t++;
+       }
+
+       return ENXIO;
+}
+
+
+/*
+ * the attach routine.
+ */
+static int
+cxm_attach(device_t dev)
+{
+       int             error;
+       int             rid;
+       int             unit;
+       unsigned int    i;
+       uint32_t        command;
+       struct cxm_softc *sc;
+
+       /* Get the device data */
+       sc = device_get_softc(dev);
+       unit = device_get_unit(dev);
+
+       sc->dev = dev;
+       sc->type = cxm_iTVC15_type;
+
+       switch(pci_get_device(dev)) {
+       case PCI_PRODUCT_ICOMPRESSION_ITVC16:
+               sc->type = cxm_iTVC16_type;
+               break;
+
+       default:
+               break;
+       }
+
+       /*
+        * Enable bus mastering and memory mapped I/O.
+        */
+       pci_enable_busmaster(dev);
+       pci_enable_io(dev, SYS_RES_MEMORY);
+       command = pci_read_config(dev, PCIR_COMMAND, 4);
+
+       if (!(command & PCIM_CMD_MEMEN)) {
+               device_printf(dev, "failed to enable memory mappings\n");
+               error = ENXIO;
+               goto fail;
+       }
+
+       /*
+        * Map control/status registers.
+        */
+       rid = CXM_RID;
+       sc->mem_res = bus_alloc_resource(dev, SYS_RES_MEMORY, &rid,
+                                       0, ~0, 1, RF_ACTIVE);
+
+       if (!sc->mem_res) {
+               device_printf(dev, "could not map memory\n");
+               error = ENXIO;
+               goto fail;
+       }
+
+       sc->btag = rman_get_bustag(sc->mem_res);
+       sc->bhandle = rman_get_bushandle(sc->mem_res);
+
+       /*
+        * Attach the I2C bus.
+        */
+       sc->cxm_iic = device_add_child(dev, "cxm_iic", unit);
+
+       if (!sc->cxm_iic) {
+               device_printf(dev, "could not add cxm_iic\n");
+               error = ENXIO;
+               goto fail;
+       }
+
+       error = device_probe_and_attach(sc->cxm_iic);
+
+       if (error) {
+               device_printf(dev, "could not attach cxm_iic\n");
+               goto fail;
+       }
+
+       /*
+        * Initialize the tuner.
+        */
+       if (cxm_tuner_init(sc) < 0) {
+               device_printf(dev, "could not initialize tuner\n");
+               error = ENXIO;
+               goto fail;
+       }
+
+       /*
+        * Initialize the SAA7115.
+        */
+       if (cxm_saa7115_init(sc) < 0) {
+               device_printf(dev, "could not initialize video decoder\n");
+               error = ENXIO;
+               goto fail;
+       }
+
+       /*
+        * Initialize the MSP3400.
+        */
+       if (cxm_msp_init(sc) < 0) {
+               device_printf(dev, "could not initialize audio decoder\n");
+               error = ENXIO;
+               goto fail;
+       }
+
+       /*
+        * Initialize the IR Remote.
+        */
+       if (cxm_ir_init(sc) < 0) {
+               device_printf(dev, "could not initialize IR remote\n");
+               error = ENXIO;
+               goto fail;
+       }
+
+       sc->dec_mbx = -1;
+       sc->enc_mbx = -1;
+
+       /*
+        * Disable the Conexant device.
+        *
+        * This is done * after * attaching the I2C bus so
+        * cxm_stop_hardware can mute the video and audio
+        * decoders.
+        */
+       cxm_stop_hardware(sc);
+
+       /*
+        * Allocate our interrupt.
+        */
+       rid = 0;
+       sc->irq_res = bus_alloc_resource(dev, SYS_RES_IRQ, &rid,
+                               0, ~0, 1, RF_SHAREABLE | RF_ACTIVE);
+
+       if (sc->irq_res == NULL) {
+               device_printf(dev, "could not map interrupt\n");
+               error = ENXIO;
+               goto fail;
+       }
+
+       error = bus_setup_intr(dev, sc->irq_res, 0,
+                              cxm_intr, sc, &sc->ih_cookie, NULL);
+       if (error) {
+               device_printf(dev, "could not setup irq\n");
+               goto fail;
+
+       }
+
+       /*
+        * Allocate a DMA tag for the parent bus.
+        */
+       error = bus_dma_tag_create(NULL, 1, 0,
+                                  BUS_SPACE_MAXADDR_32BIT,
+                                  BUS_SPACE_MAXADDR, NULL, NULL,
+                                  BUS_SPACE_MAXSIZE_32BIT, 1,
+                                  BUS_SPACE_MAXSIZE_32BIT, 0,
+                                  &sc->parent_dmat);
+       if (error) {
+               device_printf(dev, "could not create parent bus DMA tag\n");
+               goto fail;
+       }
+
+       /*
+        * Allocate a DMA tag for the encoder buffers.
+        */
+       error = bus_dma_tag_create(sc->parent_dmat, 256, 0,
+                                  BUS_SPACE_MAXADDR_32BIT,
+                                  BUS_SPACE_MAXADDR, NULL, NULL,
+                                  CXM_SG_SEGMENT, 1,
+                                  BUS_SPACE_MAXSIZE_32BIT, 0,
+                                  &sc->enc_pool.dmat);
+       if (error) {
+               device_printf(dev,
+                             "could not create encoder buffer DMA tag\n");
+               goto fail;
+       }
+
+       for (i = 0; i < CXM_SG_BUFFERS; i++) {
+
+               /*
+                * Allocate the encoder buffer.
+                */
+               error = bus_dmamem_alloc(sc->enc_pool.dmat,
+                                        (void **)&sc->enc_pool.bufs[i].vaddr,
+                                        BUS_DMA_NOWAIT,
+                                        &sc->enc_pool.bufs[i].dmamap);
+               if (error) {
+                       device_printf(dev,
+                                     "could not allocate encoder buffer\n");
+                       goto fail;
+               }
+
+               /*
+                * Map the encoder buffer.
+                */
+               error = bus_dmamap_load(sc->enc_pool.dmat,
+                                       sc->enc_pool.bufs[i].dmamap,
+                                       sc->enc_pool.bufs[i].vaddr,
+                                       CXM_SG_SEGMENT,
+                                       cxm_mapmem,
+                                       &sc->enc_pool.bufs[i].baddr, 0);
+               if (error) {
+                       device_printf(dev, "could not map encoder buffer\n");
+                       goto fail;
+               }
+       }
+
+       /*
+        * Allocate a DMA tag for the scatter / gather list.
+        */
+       error = bus_dma_tag_create(sc->parent_dmat, 1, 0,
+                                  BUS_SPACE_MAXADDR_32BIT,
+                                  BUS_SPACE_MAXADDR, NULL, NULL,
+                                  CXM_SG_BUFFERS
+                                  * sizeof(struct cxm_sg_entry), 1,
+                                  BUS_SPACE_MAXSIZE_32BIT, 0,
+                                  &sc->enc_sg.dmat);
+       if (error) {
+               device_printf(dev,
+                             "could not create scatter / gather DMA tag\n");
+               goto fail;
+       }
+
+       /*
+        * Allocate the scatter / gather list.
+        */
+       error = bus_dmamem_alloc(sc->enc_sg.dmat, (void **)&sc->enc_sg.vaddr,
+                                BUS_DMA_NOWAIT, &sc->enc_sg.dmamap);
+       if (error) {
+               device_printf(dev,
+                             "could not allocate scatter / gather list\n");
+               goto fail;
+       }
+
+       /*
+        * Map the scatter / gather list.
+        */
+       error = bus_dmamap_load(sc->enc_sg.dmat, sc->enc_sg.dmamap,
+                               sc->enc_sg.vaddr,
+                               CXM_SG_BUFFERS * sizeof(struct cxm_sg_entry),
+                               cxm_mapmem, &sc->enc_sg.baddr, 0);
+       if (error) {
+               device_printf(dev, "could not map scatter / gather list\n");
+               goto fail;
+       }
+
+       /*
+        * Initialize the hardware.
+        */
+       if (cxm_init_hardware(sc) < 0) {
+               device_printf(dev, "could not initialize hardware\n");
+               error = ENXIO;
+               goto fail;
+       }
+
+       sc->profile = &dvd_full_d1_ntsc_profile;
+
+       sc->source = cxm_tuner_source;
+
+
+       /* make the device entries */
+       dev_ops_add(&cxm_ops, -1, unit);
+       sc->cxm_dev_t = make_dev(&cxm_ops, unit,
+                               0, 0, 0444, "cxm%d",  unit);
+
+       return 0;
+
+fail:
+       if (sc->enc_sg.baddr)
+               bus_dmamap_unload(sc->enc_sg.dmat, sc->enc_sg.dmamap);
+       if (sc->enc_sg.vaddr)
+               bus_dmamem_free(sc->enc_sg.dmat, sc->enc_sg.vaddr,
+                               sc->enc_sg.dmamap);
+       if (sc->enc_sg.dmat)
+               bus_dma_tag_destroy(sc->enc_sg.dmat);
+
+       for (i = 0; i < CXM_SG_BUFFERS; i++) {
+               if (sc->enc_pool.bufs[i].baddr)
+                       bus_dmamap_unload(sc->enc_pool.dmat,
+                                         sc->enc_pool.bufs[i].dmamap);
+               if (sc->enc_pool.bufs[i].vaddr)
+                       bus_dmamem_free(sc->enc_pool.dmat,
+                                       sc->enc_pool.bufs[i].vaddr,
+                                       sc->enc_pool.bufs[i].dmamap);
+       }
+
+       if (sc->enc_pool.dmat)
+               bus_dma_tag_destroy(sc->enc_pool.dmat);
+
+       if (sc->parent_dmat)
+               bus_dma_tag_destroy(sc->parent_dmat);
+
+       /*
+        * Detach the I2C bus.
+        *
+        * This is done * after * deallocating the scatter / gather
+        * list and buffers so the kernel has a better chance of
+        * gracefully handling a memory shortage.
+        *
+        * Detach the children before recursively deleting
+        * in case a child has a pointer to a grandchild
+        * which is used by the child's detach routine.
+        */
+       bus_generic_detach(dev);
+       if (sc->cxm_iic)
+               device_delete_child(dev, sc->cxm_iic);
+
+       if (sc->ih_cookie)
+               bus_teardown_intr(dev, sc->irq_res, sc->ih_cookie);
+       if (sc->irq_res)
+               bus_release_resource(dev, SYS_RES_IRQ, 0, sc->irq_res);
+       if (sc->mem_res)
+               bus_release_resource(dev, SYS_RES_MEMORY, CXM_RID, sc->mem_res);
+
+       return error;
+}
+
+/*
+ * the detach routine.
+ */
+static int
+cxm_detach(device_t dev)
+{
+       unsigned int i;
+       struct cxm_softc *sc;
+       device_t child;
+
+       /* Get the device data */
+       sc = device_get_softc(dev);
+
+       /* Disable the Conexant device. */
+       cxm_stop_hardware(sc);
+
+       /* Unregister the /dev/cxmN device. */
+       dev_ops_remove(&cxm_ops, 0, device_get_unit(dev));
+
+       /*
+        * Deallocate scatter / gather list and buffers.
+        */
+       bus_dmamap_unload(sc->enc_sg.dmat, sc->enc_sg.dmamap);
+       bus_dmamem_free(sc->enc_sg.dmat, sc->enc_sg.vaddr, sc->enc_sg.dmamap);
+
+       bus_dma_tag_destroy(sc->enc_sg.dmat);
+
+       for (i = 0; i < CXM_SG_BUFFERS; i++) {
+               bus_dmamap_unload(sc->enc_pool.dmat,
+                                 sc->enc_pool.bufs[i].dmamap);
+               bus_dmamem_free(sc->enc_pool.dmat, sc->enc_pool.bufs[i].vaddr,
+                               sc->enc_pool.bufs[i].dmamap);
+       }
+
+       bus_dma_tag_destroy(sc->enc_pool.dmat);
+
+       bus_dma_tag_destroy(sc->parent_dmat);
+
+       /*
+        * Detach the I2C bus.
+        *
+        * This is done * after * deallocating the scatter / gather
+        * list and buffers so the kernel has a better chance of
+        * gracefully handling a memory shortage.
+        *
+        * Detach the children before recursively deleting
+        * in case a child has a pointer to a grandchild
+        * which is used by the child's detach routine.
+        *
+        * Remember the child before detaching so we can
+        * delete it (bus_generic_detach indirectly zeroes
+        * sc->child_dev).
+        */
+       child = sc->cxm_iic;
+       bus_generic_detach(dev);
+       if (child)
+               device_delete_child(dev, child);
+
+       /* Deallocate resources. */
+       bus_teardown_intr(dev, sc->irq_res, sc->ih_cookie);
+       bus_release_resource(dev, SYS_RES_IRQ, 0, sc->irq_res);
+       bus_release_resource(dev, SYS_RES_MEMORY, CXM_RID, sc->mem_res);
+
+       return 0;
+}
+
+/*
+ * the shutdown routine.
+ */
+static int
+cxm_shutdown(device_t dev)
+{
+       struct cxm_softc *sc = device_get_softc(dev);
+
+       /* Disable the Conexant device. */
+       cxm_stop_hardware(sc);
+
+       return 0;
+}
+
+/*
+ * the interrupt routine.
+ */
+static void
+cxm_intr(void *arg)
+{
+       uint32_t status;
+       struct cxm_softc *sc;
+
+       /* Get the device data */
+       sc = (struct cxm_softc *)arg;
+
+       status = CSR_READ_4(sc, CXM_REG_IRQ_STATUS);
+
+       status &= ~sc->irq_mask;
+
+       if (!status)
+               return;
+
+       /* Process DMA done before handling a new DMA request or EOS */
+       if (status & CXM_IRQ_ENC_DMA_DONE)
+               cxm_encoder_dma_done(sc);
+
+       if (status & CXM_IRQ_ENC_DMA_REQUEST)
+               cxm_encoder_dma_request(sc);
+
+       if (status & CXM_IRQ_ENC_EOS) {
+               sc->encoding_eos = 1;
+               wakeup(&sc->encoding_eos);
+       }
+
+       cxm_set_irq_status(sc, status);
+}
+
+
+/*
+ * the child detached routine.
+ */
+static void
+cxm_child_detached(device_t dev, device_t child)
+{
+       struct cxm_softc *sc;
+
+       /* Get the device data */
+       sc = device_get_softc(dev);
+
+       if (child == sc->cxm_iic)
+               sc->cxm_iic = NULL;
+}
+
+
+static int
+cxm_read_ivar(device_t dev, device_t child, int index, uintptr_t* val)
+{
+       struct cxm_softc *sc;
+
+       /* Get the device data */
+       sc = device_get_softc(dev);
+
+       switch (index) {
+       case CXM_IVAR_BHANDLE:
+               *(bus_space_handle_t **)val = &sc->bhandle;
+               break;
+
+       case CXM_IVAR_BTAG:
+               *(bus_space_tag_t **)val = &sc->btag;
+               break;
+
+       case CXM_IVAR_IICBUS:
+               *(device_t **)val = &sc->iicbus;
+               break;
+
+       default:
+               return ENOENT;
+       }
+
+       return 0;
+}
+
+
+static int
+cxm_write_ivar(device_t dev, device_t child, int index, uintptr_t val)
+{
+       struct cxm_softc *sc;
+
+       /* Get the device data */
+       sc = device_get_softc(dev);
+
+       switch (index) {
+       case CXM_IVAR_BHANDLE:
+               return EINVAL;
+
+       case CXM_IVAR_BTAG:
+               return EINVAL;
+
+       case CXM_IVAR_IICBUS:
+               if (sc->iicbus)
+                       return EINVAL;
+               sc->iicbus = val ? *(device_t *)val : NULL;
+               break;
+
+       default:
+               return ENOENT;
+       }
+
+       return 0;
+}
+
+
+/*---------------------------------------------------------
+**
+**     Conexant iTVC15 / iTVC16 character device driver routines
+**
+**---------------------------------------------------------
+*/
+
+#define UNIT(x)                ((x) & 0x0f)
+#define FUNCTION(x)    (x >> 4)
+
+/*
+ *
+ */
+int
+cxm_open(struct dev_open_args *ap)
+{
+       cdev_t          dev = ap->a_head.a_dev;
+       int             unit;
+       struct cxm_softc *sc;
+
+       unit = UNIT(minor(dev));
+
+       /* Get the device data */
+       sc = (struct cxm_softc*)devclass_get_softc(cxm_devclass, unit);
+       if (sc == NULL) {
+               /* the device is no longer valid/functioning */
+               return ENXIO;
+       }
+
+       if (sc->is_opened)
+               return EBUSY;
+
+       sc->is_opened = 1;
+       sc->mpeg = 1;
+
+       /* Record that the device is now busy */
+       device_busy(devclass_get_device(cxm_devclass, unit));
+
+       return 0;
+}
+
+
+/*
+ *
+ */
+int
+cxm_close(struct dev_close_args *ap)
+{
+       cdev_t          dev = ap->a_head.a_dev;
+       int             unit;
+       struct cxm_softc *sc;
+
+       unit = UNIT(minor(dev));
+
+       /* Get the device data */
+       sc = (struct cxm_softc*)devclass_get_softc(cxm_devclass, unit);
+       if (sc == NULL) {
+               /* the device is no longer valid/functioning */
+               return ENXIO;
+       }
+
+       if (cxm_stop_encoder(sc) < 0)
+               return ENXIO;
+
+       sc->enc_pool.offset = 0;
+       sc->enc_pool.read = 0;
+       sc->enc_pool.write = 0;
+
+       sc->enc_proc = NULL;
+       sc->enc_signal = 0;
+
+       device_unbusy(devclass_get_device(cxm_devclass, unit));
+
+       sc->is_opened = 0;
+
+       return 0;
+}
+
+
+/*
+ *
+ */
+int
+cxm_read(struct dev_read_args *ap)
+{
+       cdev_t          dev = ap->a_head.a_dev;
+       int             buffers_available;
+       int             buffers_read;
+       int             error;
+       int             unit;
+       unsigned int    current;
+       unsigned int    i;
+       size_t          nbytes;
+       size_t          offset;
+       struct cxm_softc *sc;
+
+       unit = UNIT(minor(dev));
+
+       /* Get the device data */
+       sc = (struct cxm_softc*)devclass_get_softc(cxm_devclass, unit);
+       if (sc == NULL) {
+               /* the device is no longer valid/functioning */
+               return ENXIO;
+       }
+
+       /* Only trigger the encoder if the ring buffer is empty */
+       if (!sc->encoding && sc->enc_pool.read == sc->enc_pool.write) {
+               if (cxm_start_encoder(sc) < 0)
+                       return ENXIO;
+               if (ap->a_ioflag & IO_NDELAY)
+                       return EWOULDBLOCK;
+       }
+
+       buffers_available = 0;
+
+       crit_enter();
+       while (sc->enc_pool.read == sc->enc_pool.write) {
+               error = tsleep(&sc->enc_pool.read, PCATCH, "cxmrd", 0);
+               if (error) {
+                       crit_exit();
+                       return error;
+               }
+       }
+
+       /*
+        * Determine the number of buffers available at this * instant *
+        * taking in consideration that the ring buffer wraps.
+        */
+       buffers_available = sc->enc_pool.write - sc->enc_pool.read;
+       if (buffers_available < 0)
+               buffers_available += CXM_SG_BUFFERS;
+       crit_exit();
+
+       offset = sc->enc_pool.offset;
+
+       for (buffers_read = 0, i = sc->enc_pool.read;
+            buffers_read != buffers_available && ap->a_uio->uio_resid;
+            buffers_read++, i = (i + 1) % CXM_SG_BUFFERS) {
+
+               current = cxm_encoder_fixup_byte_order (sc, i, offset);
+
+               nbytes = sc->enc_pool.bufs[current].size - offset;
+
+               /* Don't transfer more than requested */
+               if (nbytes > ap->a_uio->uio_resid)
+                       nbytes = ap->a_uio->uio_resid;
+
+               error = uiomove(sc->enc_pool.bufs[current].vaddr + offset,
+                               nbytes, ap->a_uio);
+               if (error)
+                       return error;
+
+               offset += nbytes;
+
+               /* Handle a partial read of a buffer */
+               if (!ap->a_uio->uio_resid && offset != sc->enc_pool.bufs[i].size)
+                       break;
+
+               offset = 0;
+       }
+
+       sc->enc_pool.offset = offset;
+
+       /* Update the books */
+       crit_enter();
+       sc->enc_pool.read = (sc->enc_pool.read + buffers_read)
+                           % CXM_SG_BUFFERS;
+       crit_exit();
+
+       return 0;
+}
+
+
+/*
+ *
+ */
+int
+cxm_ioctl(struct dev_ioctl_args *ap)
+{
+       cdev_t          dev = ap->a_head.a_dev;
+       int             brightness;
+       int             chroma_saturation;
+       int             contrast;
+       int             fps;
+       int             hue;
+       int             result;
+       int             status;
+       int             unit;
+       unsigned int    i;
+       unsigned int    sig;
+       unsigned long   freq;
+       struct cxm_softc *sc;
+       enum cxm_source source;
+       struct bktr_capture_area *cap;
+       struct bktr_remote *remote;
+
+       unit = UNIT(minor(dev));
+
+       /* Get the device data */
+       sc = (struct cxm_softc*)devclass_get_softc(cxm_devclass, unit);
+       if (sc == NULL) {
+               /* the device is no longer valid/functioning */
+               return ENXIO;
+       }
+
+       switch (ap->a_cmd) {
+       case BT848_GAUDIO:
+               switch (cxm_msp_selected_source(sc)) {
+               case cxm_tuner_source:
+                       *(int *) ap->a_data = AUDIO_TUNER;
+                       break;
+
+               case cxm_line_in_source_composite:
+               case cxm_line_in_source_svideo:
+                       *(int *) ap->a_data = AUDIO_EXTERN;
+                       break;
+
+               case cxm_fm_source:
+                       *(int *) ap->a_data = AUDIO_INTERN;
+                       break;
+
+               default:
+                       return ENXIO;
+               }
+
+               if (cxm_msp_is_muted(sc) == 1)
+                       *(int *) ap->a_data |= AUDIO_MUTE;
+               break;
+
+       case BT848_SAUDIO:
+               source = cxm_unknown_source;
+
+               switch (*(int *) ap->a_data) {
+               case AUDIO_TUNER:
+                       source = cxm_tuner_source;
+                       break;
+
+               case AUDIO_EXTERN:
+                       source = cxm_line_in_source_composite;
+                       break;
+
+               case AUDIO_INTERN:
+                       source = cxm_fm_source;
+                       break;
+
+               case AUDIO_MUTE:
+                       if (cxm_msp_mute(sc) < 0)
+                               return ENXIO;
+                       return 0;
+
+               case AUDIO_UNMUTE:
+                       if (cxm_msp_unmute(sc) < 0)
+                               return ENXIO;
+                       return 0;
+
+               default:
+                       return EINVAL;
+               }
+
+               if (sc->encoding) {
+
+                       /*
+                        * Switching between audio + video and audio only
+                        * subtypes isn't supported while encoding.
+                        */
+
+                       if (source != sc->source
+                           && (source == cxm_fm_source
+                               || sc->source == cxm_fm_source))
+                               return EBUSY;
+               }
+
+               if (cxm_pause_encoder(sc) < 0)
+                       return ENXIO;
+
+               if (cxm_msp_select_source(sc, source) < 0)
+                       return ENXIO;
+
+               if (source == cxm_fm_source)
+                       sc->source = source;
+
+               result = cxm_encoder_wait_for_lock(sc);
+               if (result < 0)
+                       return ENXIO;
+               else if (result == 0)
+                       return EINVAL;
+
+               if (cxm_unpause_encoder(sc) < 0)
+                       return ENXIO;
+               break;
+
+       case BT848_GBRIG:
+               brightness = cxm_saa7115_get_brightness(sc);
+
+               if (brightness < 0)
+                       return ENXIO;
+
+               /*
+                * Brooktree brightness:
+                * 0x80 = -50.0%, 0x00 = +0.0%, 0x7f = +49.6%
+                */
+               *(int *)ap->a_data = (int)(unsigned char)brightness - 128;
+               break;
+
+       case BT848_SBRIG:
+
+               /*
+                * Brooktree brightness:
+                * 0x80 = -50.0%, 0x00 = +0.0%, 0x7f = +49.6%
+                */
+               brightness = *(int *)ap->a_data + 128;
+
+               if (cxm_saa7115_set_brightness(sc, brightness) < 0)
+                       return ENXIO;
+               break;
+
+       case METEORGBRIG:
+               brightness = cxm_saa7115_get_brightness(sc);
+
+               if (brightness < 0)
+                       return ENXIO;
+
+               *(unsigned char *)ap->a_data = (unsigned char)brightness;
+               break;
+
+       case METEORSBRIG:
+               brightness = *(unsigned char *)ap->a_data;
+
+               if (cxm_saa7115_set_brightness(sc, brightness) < 0)
+                       return ENXIO;
+               break;
+
+       case BT848_GCSAT:
+               chroma_saturation = cxm_saa7115_get_chroma_saturation(sc);
+
+               if (chroma_saturation < 0)
+                       return ENXIO;
+
+               /*
+                * Brooktree chroma saturation:
+                * 0x000 = 0%, 0x0fe = 100%, 0x1ff = 201.18%
+                */
+               *(int *)ap->a_data = ((signed char)chroma_saturation > 0)
+                               ? (chroma_saturation * 4 - 2) : 0;
+               break;
+
+       case BT848_SCSAT:
+
+               /*
+                * Brooktree chroma saturation:
+                * 0x000 = 0%, 0x0fe = 100%, 0x1ff = 201.18%
+                */
+               chroma_saturation = (*(int *)ap->a_data & 0x1ff) < 510
+                                     ? ((*(int *)ap->a_data & 0x1ff) + 2) / 4 : 127;
+
+               if (cxm_saa7115_set_chroma_saturation(sc, chroma_saturation)
+                   < 0)
+                       return ENXIO;
+
+               break;
+
+       case METEORGCSAT:
+               chroma_saturation = cxm_saa7115_get_chroma_saturation(sc);
+
+               if (chroma_saturation < 0)
+                       return ENXIO;
+
+               *(unsigned char *)ap->a_data = (unsigned char)chroma_saturation;
+               break;
+
+       case METEORSCSAT:
+               chroma_saturation = *(unsigned char *)ap->a_data;
+
+               if (cxm_saa7115_set_chroma_saturation(sc, chroma_saturation)
+                   < 0)
+                       return ENXIO;
+               break;
+
+       case METEORGCONT:
+               contrast = cxm_saa7115_get_contrast(sc);
+
+               if (contrast < 0)
+                       return ENXIO;
+
+               *(unsigned char *)ap->a_data = (unsigned char)contrast;
+               break;
+
+       case METEORSCONT:
+               contrast = *(unsigned char *)ap->a_data;
+
+               if (cxm_saa7115_set_contrast(sc, contrast) < 0)
+                       return ENXIO;
+               break;
+
+       case BT848_GHUE:
+               hue = cxm_saa7115_get_hue(sc);
+
+               if (hue < 0)
+                       return ENXIO;
+
+               *(int *)ap->a_data = (signed char)hue;
+               break;
+
+       case BT848_SHUE:
+               hue = *(int *)ap->a_data;
+
+               if (cxm_saa7115_set_hue(sc, hue) < 0)
+                       return ENXIO;
+               break;
+
+       case METEORGHUE:
+               hue = cxm_saa7115_get_hue(sc);
+
+               if (hue < 0)
+                       return ENXIO;
+
+               *(signed char *)ap->a_data = (signed char)hue;
+               break;
+
+       case METEORSHUE:
+               hue = *(signed char *)ap->a_data;
+
+               if (cxm_saa7115_set_hue(sc, hue) < 0)
+                       return ENXIO;
+               break;
+
+       case METEORCAPTUR:
+               switch (*(int *) ap->a_data) {
+               case METEOR_CAP_CONTINOUS:
+                       if (cxm_start_encoder(sc) < 0)
+                               return ENXIO;
+                       break;
+
+               case METEOR_CAP_STOP_CONT:
+                       if (cxm_stop_encoder(sc) < 0)
+                               return ENXIO;
+                       break;
+
+               default:
+                       return EINVAL;
+               }
+               break;
+
+       case BT848_GCAPAREA:
+               cap = (struct bktr_capture_area *)ap->a_data;
+               memset (cap, 0, sizeof (*cap));
+               cap->x_offset = 0;
+               cap->y_offset = 0;
+               cap->x_size = sc->profile->width;
+               cap->y_size = sc->profile->height;
+               break;
+
+       case BT848_SCAPAREA:
+               if (sc->encoding)
+                       return EBUSY;
+
+               cap = (struct bktr_capture_area *)ap->a_data;
+               if (cap->x_offset || cap->y_offset
+                   || (cap->x_size % CXM_MACROBLOCK_WIDTH)
+                   || (cap->y_size % CXM_MACROBLOCK_HEIGHT))
+                       return EINVAL;
+
+               /*
+                * Setting the width and height has the side effect of
+                * chosing between the VCD, SVCD, and DVD profiles.
+                */
+
+               for (i = 0; i < NUM_ELEMENTS(codec_profiles); i++)
+                       if (codec_profiles[i]->width == cap->x_size
+                           && codec_profiles[i]->height == cap->y_size)
+                               break;
+
+               if (i >= NUM_ELEMENTS(codec_profiles))
+                       return EINVAL;
+
+               sc->profile = codec_profiles[i];
+               break;
+
+       case BT848GFMT:
+               switch (cxm_saa7115_detected_format(sc)) {
+               case cxm_ntsc_60hz_source_format:
+                       *(unsigned long *)ap->a_data = BT848_IFORM_F_NTSCM;
+                       break;
+
+               case cxm_pal_50hz_source_format:
+                       *(unsigned long *)ap->a_data = BT848_IFORM_F_PALBDGHI;
+                       break;
+
+               case cxm_secam_50hz_source_format:
+                       *(unsigned long *)ap->a_data = BT848_IFORM_F_SECAM;
+                       break;
+
+               case cxm_pal_60hz_source_format:
+                       *(unsigned long *)ap->a_data = BT848_IFORM_F_PALM;
+                       break;
+
+               case cxm_bw_50hz_source_format:
+               case cxm_bw_60hz_source_format:
+               case cxm_ntsc_50hz_source_format:
+                       *(unsigned long *)ap->a_data = BT848_IFORM_F_AUTO;
+                       break;
+
+               default:
+                       return ENXIO;
+               }
+               break;
+
+       case METEORGFMT:
+               switch (cxm_saa7115_detected_format(sc)) {
+               case cxm_ntsc_60hz_source_format:
+                       *(unsigned long *)ap->a_data = METEOR_FMT_NTSC;
+                       break;
+
+               case cxm_pal_50hz_source_format:
+                       *(unsigned long *)ap->a_data = METEOR_FMT_PAL;
+                       break;
+
+               case cxm_secam_50hz_source_format:
+                       *(unsigned long *)ap->a_data = METEOR_FMT_SECAM;
+                       break;
+
+               case cxm_bw_50hz_source_format:
+               case cxm_bw_60hz_source_format:
+               case cxm_ntsc_50hz_source_format:
+               case cxm_pal_60hz_source_format:
+                       *(unsigned long *)ap->a_data = METEOR_FMT_AUTOMODE;
+                       break;
+
+               default:
+                       return ENXIO;
+               }
+               break;
+
+       case METEORGFPS:
+               fps = cxm_saa7115_detected_fps(sc);
+
+               if (fps < 0)
+                       return ENXIO;
+
+               *(unsigned short *)ap->a_data = fps;
+               break;
+
+       case METEORGINPUT:
+               switch (sc->source) {
+               case cxm_tuner_source:
+                       *(unsigned long *)ap->a_data = METEOR_INPUT_DEV1;
+                       break;
+
+               case cxm_line_in_source_composite:
+                       *(unsigned long *)ap->a_data = METEOR_INPUT_DEV2;
+                       break;
+
+               case cxm_line_in_source_svideo:
+                       *(unsigned long *)ap->a_data = METEOR_INPUT_DEV_SVIDEO;
+                       break;
+
+               default:
+                       return ENXIO;
+               }
+               break;
+
+       case METEORSINPUT:
+               source = cxm_unknown_source;
+
+               switch (*(unsigned long *)ap->a_data & 0xf000) {
+               case METEOR_INPUT_DEV1:
+                       source = cxm_tuner_source;
+                       break;
+
+               case METEOR_INPUT_DEV2:
+                       source = cxm_line_in_source_composite;
+                       break;
+
+               case METEOR_INPUT_DEV_SVIDEO:
+                       source = cxm_line_in_source_svideo;
+                       break;
+
+               default:
+                        return EINVAL;
+               }
+
+               if (sc->encoding) {
+
+                       /*
+                        * Switching between audio + video and audio only
+                        * subtypes isn't supported while encoding.
+                        */
+
+                       if (source != sc->source
+                           && (source == cxm_fm_source
+                               || sc->source == cxm_fm_source))
+                               return EBUSY;
+               }
+
+               if (cxm_pause_encoder(sc) < 0)
+                       return ENXIO;
+
+               if (cxm_saa7115_select_source(sc, source) < 0)
+                       return ENXIO;
+               if (cxm_msp_select_source(sc, source) < 0)
+                       return ENXIO;
+               sc->source = source;
+
+               result = cxm_encoder_wait_for_lock(sc);
+               if (result < 0)
+                       return ENXIO;
+               else if (result == 0)
+                       return EINVAL;
+
+               if (cxm_unpause_encoder(sc) < 0)
+                       return ENXIO;
+               break;
+
+       case METEORGSIGNAL:
+               *(unsigned int *)ap->a_data = sc->enc_signal;
+               break;
+
+       case METEORSSIGNAL:
+               sig = *(unsigned int *)ap->a_data;
+
+               if (!_SIG_VALID(sig))
+                       return EINVAL;
+
+               /*
+                * Historically, applications used METEOR_SIG_MODE_MASK
+                * to reset signal delivery.
+                */
+               if (sig == METEOR_SIG_MODE_MASK)
+                       sig = 0;
+
+               crit_enter();
+               sc->enc_proc = sig ? curproc : NULL;
+               sc->enc_signal = sig;
+               crit_exit();
+               break;
+
+       case RADIO_GETFREQ:
+               /* Convert from kHz to MHz * 100 */
+               freq = sc->tuner_freq / 10;
+
+               *(unsigned int *)ap->a_data = freq;
+               break;
+
+       case RADIO_SETFREQ:
+               if (sc->source == cxm_fm_source)
+                       if (cxm_pause_encoder(sc) < 0)
+                               return ENXIO;
+
+               /* Convert from MHz * 100 to kHz */
+               freq = *(unsigned int *)ap->a_data * 10;
+
+               if (cxm_tuner_select_frequency(sc, cxm_tuner_fm_freq_type,
+                                              freq) < 0)
+                       return ENXIO;
+
+               /*
+                * Explicitly wait for the tuner lock so we
+                * can indicate if there's a station present.
+                */
+               if (cxm_tuner_wait_for_lock(sc) < 0)
+                       return EINVAL;
+
+               result = cxm_encoder_wait_for_lock(sc);
+               if (result < 0)
+                       return ENXIO;
+               else if (result == 0)
+                       return EINVAL;
+
+               if (sc->source == cxm_fm_source)
+                       if (cxm_unpause_encoder(sc) < 0)
+                               return ENXIO;
+               break;
+
+       case TVTUNER_GETAFC:
+               *(int *)ap->a_data = sc->tuner_afc;
+               break;
+
+       case TVTUNER_SETAFC:
+               sc->tuner_afc = (*(int *)ap->a_data != 0);
+               break;
+
+       case TVTUNER_GETTYPE:
+               *(unsigned int *)ap->a_data = cxm_tuner_selected_channel_set(sc);
+               break;
+
+       case TVTUNER_SETTYPE:
+               if (cxm_tuner_select_channel_set(sc, *(unsigned int *)ap->a_data) < 0)
+                       return EINVAL;
+               break;
+
+       case TVTUNER_SETCHNL:
+               if (sc->source == cxm_tuner_source)
+                       if (cxm_pause_encoder(sc) < 0)
+                               return ENXIO;
+
+               if (cxm_tuner_select_channel(sc, *(unsigned int *)ap->a_data) < 0)
+                       return ENXIO;
+
+               if (sc->tuner_afc)
+                       if (cxm_tuner_apply_afc(sc) < 0)
+                               return EINVAL;
+
+               /*
+                * Explicitly wait for the tuner lock so we
+                * can indicate if there's a station present.
+                */
+               if (cxm_tuner_wait_for_lock(sc) < 0)
+                       return EINVAL;
+
+               result = cxm_encoder_wait_for_lock(sc);
+               if (result < 0)
+                       return ENXIO;
+               else if (result == 0)
+                       return EINVAL;
+
+               if (sc->source == cxm_tuner_source)
+                       if (cxm_unpause_encoder(sc) < 0)
+                               return ENXIO;
+               break;
+
+       case TVTUNER_GETFREQ:
+               /* Convert from kHz to MHz * 16 */
+               freq = (sc->tuner_freq * 16) / 1000;
+
+               *(unsigned int *)ap->a_data = freq;
+               break;
+
+       case TVTUNER_SETFREQ:
+               if (sc->source == cxm_tuner_source)
+                       if (cxm_pause_encoder(sc) < 0)
+                               return ENXIO;
+
+               /* Convert from MHz * 16 to kHz */
+               freq = (*(unsigned int *)ap->a_data * 1000) / 16;
+
+               if (cxm_tuner_select_frequency(sc, cxm_tuner_tv_freq_type,
+                                              freq) < 0)
+                       return ENXIO;
+
+               /*
+                * Explicitly wait for the tuner lock so we
+                * can indicate if there's a station present.
+                */
+               if (cxm_tuner_wait_for_lock(sc) < 0)
+                       return EINVAL;
+
+               result = cxm_encoder_wait_for_lock(sc);
+               if (result < 0)
+                       return ENXIO;
+               else if (result == 0)
+                       return EINVAL;
+
+               if (sc->source == cxm_tuner_source)
+                       if (cxm_unpause_encoder(sc) < 0)
+                               return ENXIO;
+
+               break;
+
+       case TVTUNER_GETSTATUS:
+               status = cxm_tuner_status(sc);
+               if (status < 0)
+                       return ENXIO;
+               *(unsigned long *)ap->a_data = status & 0xff;
+               break;
+
+       case REMOTE_GETKEY:
+               remote = (struct bktr_remote *)ap->a_data;
+               if (cxm_ir_key(sc, (char *)remote, sizeof(*remote)) < 0)
+                       return ENXIO;
+               break;
+
+       default:
+               return ENOTTY;
+       }
+
+       return 0;
+}
+
+
+int
+cxm_poll(struct dev_poll_args *ap)
+{
+       cdev_t          dev = ap->a_head.a_dev;
+       int             revents;
+       int             unit;
+       struct cxm_softc *sc;
+
+       unit = UNIT(minor(dev));
+
+       /* Get the device data */
+       sc = (struct cxm_softc*)devclass_get_softc(cxm_devclass, unit);
+       if (sc == NULL) {
+               /* the device is no longer valid/functioning */
+               return POLLHUP;
+       }
+
+       revents = 0;
+
+       crit_enter();
+       if (ap->a_events & (POLLIN | POLLRDNORM)) {
+               if (sc->enc_pool.read == sc->enc_pool.write)
+                       selrecord(curthread, &sc->enc_sel);
+               else
+                       revents = ap->a_events & (POLLIN | POLLRDNORM);
+       }
+       crit_exit();
+
+       return revents;
+}
diff --git a/sys/dev/video/cxm/cxm.h b/sys/dev/video/cxm/cxm.h
new file mode 100644 (file)
index 0000000..363bbb7
--- /dev/null
@@ -0,0 +1,695 @@
+/*
+ * Copyright (c) 2003, 2004, 2005
+ *     John Wehle <john@feith.com>.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *     This product includes software developed by John Wehle.
+ * 4. The name of the author may not be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _CXM_H
+#define _CXM_H
+
+/*
+ * Header file for the Conexant MPEG-2 Codec driver.
+ */
+
+#include <sys/selinfo.h>
+
+#include <bus/pci/pcidevs.h>
+
+#include "opt_cxm.h"
+
+#define bswap32(X) ntohl(X)
+
+#define NUM_ELEMENTS(array) (sizeof(array) / sizeof(*array))
+
+/*
+ * For simplicity several large buffers allocate during
+ * driver attachment which normally occurs early on
+ * (when large areas of memory are available) are used
+ * to move data to / from the card.  It's not unusual
+ * for the memory allocation to fail due to fragmentation
+ * if the driver is loaded after the system has been
+ * running for a while.  One solution is to allocate
+ * several PAGE_SIZE buffers instead, however it doesn't
+ * seem worth the trouble.
+ */
+enum cxm_byte_order { cxm_unknown_byte_order,
+                     cxm_device_mpeg_byte_order, cxm_device_yuv12_byte_order,
+                     cxm_host_byte_order };
+
+struct cxm_buffer {
+       char            *vaddr;
+       bus_addr_t      baddr;
+       bus_dmamap_t    dmamap;
+       size_t          size;
+       enum cxm_byte_order byte_order;
+};
+
+#define CXM_SG_BUFFERS 50
+
+struct cxm_buffer_pool {
+       bus_dma_tag_t           dmat;
+       size_t                  offset;
+       unsigned int            read;
+       volatile unsigned int   write;
+       struct cxm_buffer       bufs[CXM_SG_BUFFERS];
+};
+
+/*
+ * Audio format encoding
+ *
+ * 7 6 5 4 3 2 1 0
+ *
+ *             0 0  44.1 kHz
+ *             0 1  48 kHz
+ *             1 0  32 kHz
+ *
+ *         0 1  Layer 1
+ *         1 0  Layer 2
+ *         1 1  Layer 3
+ *
+ *          L1 / L2
+ * 0 0 0 0  Free fmt
+ * 0 0 0 1  32k / 32k
+ * 0 0 1 0  64k / 48k
+ * 0 0 1 1  96k / 56k
+ * 0 1 0 0  128k / 64k
+ * 0 1 0 1  160k / 80k
+ * 0 1 1 0  192k / 96k
+ * 0 1 1 1  224k / 112k
+ * 1 0 0 0  256k / 128k
+ * 1 0 0 1  288k / 160k
+ * 1 0 1 0  320k / 192k
+ * 1 0 1 1  352k / 224k
+ * 1 1 0 0  384k / 256k
+ * 1 1 0 1  416k / 320k
+ * 1 1 1 0  448k / 384k
+ */
+struct cxm_codec_audio_format {
+       unsigned int    sample_rate;
+       uint32_t        format;
+};
+
+struct cxm_codec_profile {
+       const char      *name;
+       uint32_t        stream_type;
+       uint32_t        fps;
+       uint32_t        width;
+       uint32_t        height;
+       uint32_t        source_height;
+       struct {
+               uint32_t        start;
+               uint32_t        nlines;
+               uint32_t        cc;
+       } vbi;
+       uint32_t        aspect;
+       uint32_t        pulldown;
+       struct {
+               uint32_t        mode;
+               uint32_t        average;
+               uint32_t        peak;
+       } bitrate;
+       struct {
+               uint32_t        closure;
+               uint32_t        frames;
+               uint32_t        bframes;
+       } gop;
+       struct {
+               uint32_t        mode;
+               uint32_t        type;
+               uint32_t        spatial;
+               uint32_t        temporal;
+       } dnr;
+
+       unsigned int    audio_sample_rate;
+};
+
+struct cxm_dev {
+       uint16_t        vid;
+       uint16_t        did;
+       char            *name;
+};
+
+#define CXM_MBX_FW_CMD_MAILBOX   0
+#define CXM_MBX_FW_CMD_MAILBOXES 6
+
+#define CXM_MBX_FW_DMA_MAILBOX   9
+
+#define CXM_MBX_MAX_PARAMETERS   16
+
+/* Mailbox flags bit definitions */
+#define CXM_MBX_FLAG_DRV_DONE 0x00000002
+#define CXM_MBX_FLAG_FW_DONE  0x00000004
+#define CXM_MBX_FLAG_IN_USE   0x00000001
+
+struct cxm_mailbox {
+       uint32_t        flags;
+       uint32_t        command;
+       uint32_t        result;
+       uint32_t        timeout;
+       uint32_t        parameters[CXM_MBX_MAX_PARAMETERS];
+} __attribute__ ((packed));
+
+enum cxm_mailbox_name { cxm_unknown_mailbox,
+                       cxm_dec_mailbox, cxm_enc_mailbox };
+
+/*
+ * Scatter / gather is supported with the restriction
+ * that the size of each piece must be a multiple of
+ * 256 and less than 64k.
+ */
+#define CXM_SG_SEGMENT  (0xff00 & ~(PAGE_SIZE - 1))
+
+struct cxm_sg_entry {
+       uint32_t        src;
+       uint32_t        dst;
+       uint32_t        size;
+} __attribute__ ((packed));
+
+struct cxm_sg_list {
+       bus_dma_tag_t   dmat;
+       struct cxm_sg_entry *vaddr;
+       bus_addr_t      baddr;
+       bus_dmamap_t    dmamap;
+
+};
+
+enum cxm_source { cxm_unknown_source, cxm_fm_source, cxm_tuner_source,
+                 cxm_line_in_source_composite, cxm_line_in_source_svideo };
+
+enum cxm_source_format { cxm_unknown_source_format,
+                        cxm_bw_50hz_source_format,
+                        cxm_bw_60hz_source_format,
+                        cxm_ntsc_50hz_source_format,
+                        cxm_ntsc_60hz_source_format,
+                        cxm_pal_50hz_source_format,
+                        cxm_pal_60hz_source_format,
+                        cxm_secam_50hz_source_format };
+
+enum cxm_type { cxm_unknown_type, cxm_iTVC15_type, cxm_iTVC16_type };
+
+/*
+ * Conexant iTVC15 / iTVC16 info structure, one per card installed.
+ */
+struct cxm_softc {
+       enum cxm_type   type;
+
+       struct resource *mem_res;       /* Resource descriptor for registers */
+       bus_space_tag_t btag;           /* Bus space access functions */
+       bus_space_handle_t bhandle;     /* Bus space access functions */
+
+       struct resource *irq_res;       /* Resource descriptor for interrupt */
+       void            *ih_cookie;     /* Newbus interrupt handler cookie */
+
+       uint32_t        irq_mask;
+
+       bus_dma_tag_t   parent_dmat;
+
+       struct cxm_buffer_pool  enc_pool;
+       struct cxm_sg_list enc_sg;
+
+       struct selinfo  enc_sel;
+
+       struct proc     *enc_proc;
+       int             enc_signal;
+
+       unsigned int    dec_mbx;
+       unsigned int    enc_mbx;
+
+       device_t        cxm_iic;
+       device_t        iicbus;
+
+       const struct cxm_tuner *tuner;
+       const struct cxm_tuner_channels *tuner_channels;
+       int             tuner_afc;
+       unsigned long   tuner_freq;
+
+       char            msp_name[10];
+
+       const struct cxm_codec_profile *profile;
+
+       enum cxm_source source;
+
+       device_t        dev;            /* bus attachment */
+       cdev_t          cxm_dev_t;      /* control device */
+       int             is_opened;
+       int             mpeg;
+
+       int             encoding;
+       int             encoding_dma;
+       int             encoding_eos;
+       int             video_std;
+};
+
+/*
+ * Conexant iTVC15 / iTVC16 I2C info structure, one per card installed.
+ */
+struct cxm_iic_softc {
+       bus_space_tag_t btag;           /* Bus space access functions */
+       bus_space_handle_t bhandle;     /* Bus space access functions */
+
+       device_t        iicbb;
+
+};
+
+/*
+ * List of IVARS available to the I2C device driver
+ */
+#define CXM_IVAR_BHANDLE 0
+#define CXM_IVAR_BTAG    1
+#define CXM_IVAR_IICBUS  2
+
+/*
+ * Bus resource id
+ */
+#define CXM_RID PCIR_MAPS
+
+/*
+ * Access macros
+ */
+#define CSR_WRITE_4(sc, reg, val)       \
+       bus_space_write_4((sc)->btag, (sc)->bhandle, (reg), (val))
+#define CSR_WRITE_2(sc, reg, val)       \
+       bus_space_write_2((sc)->btag, (sc)->bhandle, (reg), val))
+#define CSR_WRITE_1(sc, reg, val)       \
+       bus_space_write_1((sc)->btag, (sc)->bhandle, (reg), val))
+#define CSR_READ_4(sc, reg)             \
+       bus_space_read_4((sc)->btag, (sc)->bhandle, (reg))
+#define CSR_READ_2(sc, reg)             \
+       bus_space_read_2((sc)->btag, (sc)->bhandle, (reg))
+#define CSR_READ_1(sc, reg)             \
+       bus_space_read_1((sc)->btag, (sc)->bhandle, (reg))
+
+/*
+ * Decoder / encoder firmware
+ */
+extern const char cxm_dec_fw[];
+extern const char cxm_enc_fw[];
+
+#define CXM_FW_SIZE (256 * 1024)
+
+/*
+ * Decoder / encoder memory offsets
+ */
+#define CXM_MEM_DEC 0x01000000
+#define CXM_MEM_ENC 0x00000000
+
+#define CXM_MEM_DEC_SIZE 0x01000000
+#define CXM_MEM_ENC_SIZE 0x01000000
+
+/*
+ * Register offsets
+ */
+#define CXM_REG_AO                  0x2002d00
+#define CXM_REG_APU                 0x200a064
+#define CXM_REG_DEC_SDRAM_PRECHARGE 0x20008fc
+#define CXM_REG_DEC_SDRAM_REFRESH   0x20008f8
+#define CXM_REG_DMA_STATUS          0x2000004
+#define CXM_REG_ENC_SDRAM_PRECHARGE 0x20007fc
+#define CXM_REG_ENC_SDRAM_REFRESH   0x20007f8
+#define CXM_REG_HW_BLOCKS           0x2009054
+#define CXM_REG_I2C_GETSCL          0x2007008
+#define CXM_REG_I2C_GETSDA          0x200700c
+#define CXM_REG_I2C_SETSCL          0x2007000
+#define CXM_REG_I2C_SETSDA          0x2007004
+#define CXM_REG_IRQ_MASK            0x2000048
+#define CXM_REG_IRQ_STATUS          0x2000040
+#define CXM_REG_SPU                 0x2009050
+#define CXM_REG_VDM                 0x2002800
+#define CXM_REG_VPU                 0x2009058
+
+/*
+ * Register values
+ */
+#define CXM_CMD_AO_STOP              0x00000005
+#define CXM_CMD_APU_PING             0x00000000
+#define CXM_CMD_HW_BLOCKS_RST        0xffffffff
+#define CXM_CMD_SDRAM_PRECHARGE_INIT 0x0000001a
+#define CXM_CMD_SDRAM_REFRESH_INIT   0x80000640
+#define CXM_CMD_SPU_STOP             0x00000001
+#define CXM_CMD_VDM_STOP             0x00000000
+#define CXM_CMD_VPU_STOP15           0xfffffffe
+#define CXM_CMD_VPU_STOP16           0xffffffee
+
+#define CXM_DMA_ERROR_LIST           0x00000008
+#define CXM_DMA_ERROR_READ           0x00000002
+#define CXM_DMA_ERROR_WRITE          0x00000004
+#define CXM_DMA_SUCCESS              0x00000001
+
+#define CXM_IRQ_DEC_DMA_DONE         (1 << 20)
+#define CXM_IRQ_DEC_DMA_REQUEST      (1 << 22)
+#define CXM_IRQ_DEC_VSYNC            (1 << 10)
+#define CXM_IRQ_ENC_DMA_DONE         (1 << 27)
+#define CXM_IRQ_ENC_DMA_REQUEST      (1 << 31)
+#define CXM_IRQ_ENC_EOS              (1 << 30)
+#define CXM_IRQ_ENC_EVENT            (1 << 28)
+
+#define CXM_IRQ_ENC (CXM_IRQ_ENC_DMA_REQUEST | CXM_IRQ_ENC_DMA_DONE \
+                    | CXM_IRQ_ENC_EOS | CXM_IRQ_ENC_EVENT)
+
+/*
+ * Register masks
+ */
+#define CXM_MASK_SPU_ENABLE          0xfffffffe
+#define CXM_MASK_VPU_ENABLE15        0xfffffff6
+#define CXM_MASK_VPU_ENABLE16        0xfffffffb
+
+/*
+ * Firmware commands
+ */
+#define CXM_FW_CMD_ASSIGN_3_2_PULLDOWN          0x000000b1
+#define CXM_FW_CMD_ASSIGN_ASPECT_RATIO          0x00000099
+#define CXM_FW_CMD_ASSIGN_AUDIO_PROPERTIES      0x000000bd
+#define CXM_FW_CMD_ASSIGN_BITRATES              0x00000095
+#define CXM_FW_CMD_ASSIGN_CORING_LEVELS         0x0000009f
+#define CXM_FW_CMD_ASSIGN_DMA_BLOCKLEN          0x000000c9
+#define CXM_FW_CMD_ASSIGN_DNR_FILTER_MODE       0x0000009b
+#define CXM_FW_CMD_ASSIGN_DNR_FILTER_PROPERTIES 0x0000009d
+#define CXM_FW_CMD_ASSIGN_FRAME_DROP_RATE       0x000000d0
+#define CXM_FW_CMD_ASSIGN_FRAME_RATE            0x0000008f
+#define CXM_FW_CMD_ASSIGN_FRAME_SIZE            0x00000091
+#define CXM_FW_CMD_ASSIGN_GOP_CLOSURE           0x000000c5
+#define CXM_FW_CMD_ASSIGN_GOP_PROPERTIES        0x00000097
+#define CXM_FW_CMD_ASSIGN_NUM_VSYNC_LINES       0x000000d6
+#define CXM_FW_CMD_ASSIGN_OUTPUT_PORT           0x000000bb
+#define CXM_FW_CMD_ASSIGN_PGM_INDEX_INFO        0x000000c7
+#define CXM_FW_CMD_ASSIGN_PLACEHOLDER           0x000000d8
+#define CXM_FW_CMD_ASSIGN_SPATIAL_FILTER_TYPE   0x000000a1
+#define CXM_FW_CMD_ASSIGN_STREAM_TYPE           0x000000b9
+#define CXM_FW_CMD_ASSIGN_VBI_LINE              0x000000b7
+#define CXM_FW_CMD_ASSIGN_VBI_PROPERTIES        0x000000c8
+#define CXM_FW_CMD_BEGIN_CAPTURE                0x00000081
+#define CXM_FW_CMD_DEC_EVENT_NOTIFICATION       0x00000017
+#define CXM_FW_CMD_DEC_GET_FW_VER               0x00000011
+#define CXM_FW_CMD_DEC_HALT_FW                  0x0000000e
+#define CXM_FW_CMD_ENC_EVENT_NOTIFICATION       0x000000d5
+#define CXM_FW_CMD_ENC_GET_FW_VER               0x000000c4
+#define CXM_FW_CMD_ENC_HALT_FW                  0x000000c3
+#define CXM_FW_CMD_END_CAPTURE                  0x00000082
+#define CXM_FW_CMD_INITIALIZE_VIDEO_INPUT       0x000000cd
+#define CXM_FW_CMD_MUTE_VIDEO_INPUT             0x000000d9
+#define CXM_FW_CMD_PAUSE_ENCODER                0x000000d2
+#define CXM_FW_CMD_SCHED_DMA_TO_HOST            0x000000cc
+
+#define CXM_FW_STD_TIMEOUT                          0x00010000
+
+#define CXM_FW_CAPTURE_STREAM_TYPE_MPEG             0x00000000
+#define CXM_FW_CAPTURE_STREAM_TYPE_RAW              0x00000001
+#define CXM_FW_CAPTURE_STREAM_TYPE_RAW_PASSTHROUGH  0x00000002
+#define CXM_FW_CAPTURE_STREAM_TYPE_VBI              0x00000003
+
+#define CXM_FW_CAPTURE_STREAM_YUV                   0x00000001
+#define CXM_FW_CAPTURE_STREAM_PCM_AUDIO             0x00000002
+#define CXM_FW_CAPTURE_STREAM_VBI                   0x00000004
+
+#define CXM_FW_STREAM_TYPE_DVD                      0x0000000a
+#define CXM_FW_STREAM_TYPE_MPEG1                    0x00000002
+#define CXM_FW_STREAM_TYPE_MPEG2_PROGRAM            0x00000000
+#define CXM_FW_STREAM_TYPE_SVCD                     0x0000000c
+#define CXM_FW_STREAM_TYPE_VCD                      0x0000000b
+
+#define CXM_MACROBLOCK_HEIGHT 16
+#define CXM_MACROBLOCK_WIDTH  16
+#define CXM_MACROBLOCK_SIZE   (CXM_MACROBLOCK_HEIGHT * CXM_MACROBLOCK_WIDTH)
+
+/*
+ * I2C addresses
+ */
+#define CXM_I2C_CX2584x  0x88
+#define CXM_I2C_EEPROM   0xa0
+#define CXM_I2C_IR       0x30
+#define CXM_I2C_MSP3400  0x80
+#define CXM_I2C_SAA7115  0x42
+#define CXM_I2C_TDA988x_W (0x43 << 1)          /* Write address */
+#define CXM_I2C_TDA988x_R ((0x43 << 1) | 0x01) /* Read address */
+#define CXM_I2C_TUNER    0xc2
+#define CXM_I2C_TUNER_IF 0x86
+#define CXM_I2C_WM8775   0x36
+
+#define CXM_I2C_TIMEOUT  1000
+
+/*
+ * EEPROM
+ */
+int cxm_eeprom_init(struct cxm_softc *sc);
+int cxm_eeprom_tuner_type(struct cxm_softc *sc);
+
+/*
+ * Infrared remote
+ */
+int cxm_ir_init(struct cxm_softc *sc);
+int cxm_ir_key(struct cxm_softc *sc, char *buf, int len);
+
+/*
+ * msp34xxx Audio decoder
+ */
+#define CXM_MSP3400C_DEM 0x10
+#define CXM_MSP3400C_DFP 0x12
+
+struct cxm_msp_setting {
+       unsigned char   dev;
+       unsigned int    addr;
+       char            value[2];
+};
+
+struct cxm_msp_command {
+       unsigned int nsettings;
+       struct cxm_msp_setting settings[5];
+};
+
+int cxm_msp_init(struct cxm_softc *sc);
+int cxm_msp_mute(struct cxm_softc *sc);
+int cxm_msp_unmute(struct cxm_softc *sc);
+int cxm_msp_is_muted(struct cxm_softc *sc);
+int cxm_msp_select_source(struct cxm_softc *sc, enum cxm_source source);
+enum cxm_source cxm_msp_selected_source(struct cxm_softc *sc);
+int cxm_msp_autodetect_standard(struct cxm_softc *sc);
+int cxm_msp_is_locked(struct cxm_softc *sc);
+int cxm_msp_wait_for_lock(struct cxm_softc *sc);
+
+/*
+ * wm8775 Audio ADC
+ */
+struct cxm_wm8775_setting {
+       unsigned char   addr;
+       uint16_t        value;
+};
+
+struct cxm_wm8775_command {
+       unsigned int nsettings;
+       struct cxm_wm8775_setting settings[13];
+};
+
+int cxm_wm8775_init(struct cxm_softc *sc);
+
+/*
+ * tda988x Demodulator
+ */
+int cxm_tda988x_init(struct cxm_softc *sc);
+int cxm_tda988x_diag(struct cxm_softc *sc);
+
+/*
+ * cx2584x Decoder
+ */
+struct cxm_cx2584x_setting {
+       uint16_t        addr;
+       unsigned char   value;
+};
+
+struct cxm_cx2584x_command {
+       unsigned int nsettings;
+       struct cxm_cx2584x_setting settings[21];
+};
+
+int cxm_cx2584x_init(struct cxm_softc *sc);
+int cxm_cx2584x_mute(struct cxm_softc *sc);
+int cxm_cx2584x_unmute(struct cxm_softc *sc);
+int cxm_cx2584x_select_source(struct cxm_softc *sc, enum cxm_source source);
+int cxm_cx2584x_set_std(struct cxm_softc *sc);
+
+/*
+ * Tuner
+ */
+#define CXM_TUNER_PHILIPS_FI1216_MK2   0
+#define CXM_TUNER_PHILIPS_FM1216       1
+#define CXM_TUNER_PHILIPS_FQ1216ME     2
+#define CXM_TUNER_PHILIPS_FQ1216ME_MK3 3
+#define CXM_TUNER_PHILIPS_FM1216ME_MK3 4
+#define CXM_TUNER_PHILIPS_FI1236_MK2   5
+#define CXM_TUNER_PHILIPS_FM1236       6
+#define CXM_TUNER_PHILIPS_FI1246_MK2   7
+#define CXM_TUNER_PHILIPS_FM1246       8
+#define CXM_TUNER_TEMIC_4006_FH5       9
+#define CXM_TUNER_TEMIC_4009_FR5      10
+#define CXM_TUNER_TEMIC_4036_FY5      11
+#define CXM_TUNER_TEMIC_4039_FR5      12
+#define CXM_TUNER_TEMIC_4066_FY5      13
+#define CXM_TUNER_LG_TPI8PSB11D       14
+#define CXM_TUNER_LG_TPI8PSB01N       15
+#define CXM_TUNER_LG_TAPC_H701F       16
+#define CXM_TUNER_LG_TAPC_H001F       17
+#define CXM_TUNER_LG_TAPE_H001F       18
+#define CXM_TUNER_MICROTUNE_4049_FM5  19
+#define CXM_TUNER_TCL_2002N_6A        20
+#define CXM_TUNER_TYPES               21
+
+#define CXM_TUNER_AFC_MASK           0x07
+
+#define CXM_TUNER_AFC_FREQ_MINUS_125 0x00
+#define CXM_TUNER_AFC_FREQ_MINUS_62  0x01
+#define CXM_TUNER_AFC_FREQ_CENTERED  0x02
+#define CXM_TUNER_AFC_FREQ_PLUS_62   0x03
+#define CXM_TUNER_AFC_FREQ_PLUS_125  0x04
+
+#define CXM_TUNER_PHASE_LOCKED       0x40
+
+#define CXM_TUNER_FM_SYSTEM          0x01
+#define CXM_TUNER_TV_SYSTEM_BG       0x02
+#define CXM_TUNER_TV_SYSTEM_DK       0x04
+#define CXM_TUNER_TV_SYSTEM_I        0x08
+#define CXM_TUNER_TV_SYSTEM_MN       0x10
+#define CXM_TUNER_TV_SYSTEM_L        0x20
+#define CXM_TUNER_TV_SYSTEM_L_PRIME  0x40
+
+struct cxm_tuner_band_code {
+       unsigned long   freq;
+       unsigned char   codes[2];
+};
+
+struct cxm_tuner_channel_assignment {
+       unsigned int    channel;
+       unsigned long   freq;
+       unsigned long   step;
+};
+
+struct cxm_tuner_channels {
+       const char      *name;
+       unsigned int    chnlset;
+       unsigned int    system;
+       unsigned int    min_channel;
+       unsigned int    max_channel;
+       unsigned long   if_freq;
+       struct cxm_tuner_channel_assignment assignments[17];
+};
+
+struct cxm_tuner_system_code {
+       unsigned int    system;
+       unsigned char   codes[4];
+};
+
+enum cxm_tuner_system_code_style { cxm_unknown_system_code_style,
+                                  cxm_none_system_code_style,
+                                  cxm_port_system_code_style,
+                                  cxm_if_system_code_style,
+                                  cxm_if_system_with_aux_code_style };
+
+struct cxm_tuner_system {
+       unsigned int                            supported;
+       enum cxm_tuner_system_code_style        code_style;
+       struct cxm_tuner_system_code            codes[6];
+};
+
+struct cxm_tuner {
+       const char      *name;
+       struct cxm_tuner_system systems;
+       unsigned long   min_freq;
+       unsigned long   max_freq;
+       struct cxm_tuner_band_code band_codes[3];
+       unsigned long fm_min_freq;
+       unsigned long fm_max_freq;
+       struct cxm_tuner_band_code fm_band_code;
+       const struct cxm_tuner_channels *default_channels;
+};
+
+enum cxm_tuner_freq_type { cxm_tuner_unknown_freq_type, cxm_tuner_fm_freq_type,
+                          cxm_tuner_tv_freq_type };
+
+extern const struct cxm_tuner cxm_tuners[];
+
+int cxm_tuner_init(struct cxm_softc *sc);
+int cxm_tuner_select_channel_set(struct cxm_softc *sc,
+                                 unsigned int channel_set);
+unsigned int cxm_tuner_selected_channel_set(struct cxm_softc *sc);
+int cxm_tuner_select_frequency(struct cxm_softc *sc,
+                               enum cxm_tuner_freq_type type,
+                               unsigned long freq);
+int cxm_tuner_select_channel(struct cxm_softc *sc, unsigned int channel);
+int cxm_tuner_apply_afc(struct cxm_softc *sc);
+int cxm_tuner_is_locked(struct cxm_softc *sc);
+int cxm_tuner_wait_for_lock(struct cxm_softc *sc);
+int cxm_tuner_status(struct cxm_softc *sc);
+
+/*
+ * Video decoder
+ */
+struct cxm_saa7115_setting {
+       unsigned char   addr;
+       unsigned int    nvalues;
+       char            values[32];
+};
+
+struct cxm_saa7115_command {
+       unsigned int nsettings;
+       struct cxm_saa7115_setting settings[20];
+};
+
+struct cxm_saa7115_audio_clock {
+       unsigned int sample_rate;
+       unsigned int fps;
+       const struct cxm_saa7115_command *clock;
+};
+
+struct cxm_saa7115_scaling {
+       unsigned int width;
+       unsigned int height;
+       unsigned int fps;
+       const struct cxm_saa7115_command *scaling;
+};
+
+int cxm_saa7115_init(struct cxm_softc *sc);
+int cxm_saa7115_mute(struct cxm_softc *sc);
+int cxm_saa7115_unmute(struct cxm_softc *sc);
+int cxm_saa7115_select_source(struct cxm_softc *sc, enum cxm_source source);
+int cxm_saa7115_configure(struct cxm_softc *sc,
+                          unsigned int width, unsigned int height,
+                          unsigned int fps, unsigned int audio_sample_rate);
+enum cxm_source_format cxm_saa7115_detected_format(struct cxm_softc *sc);
+int cxm_saa7115_detected_fps(struct cxm_softc *sc);
+int cxm_saa7115_get_brightness(struct cxm_softc *sc);
+int cxm_saa7115_set_brightness(struct cxm_softc *sc,
+                               unsigned char brightness);
+int cxm_saa7115_get_chroma_saturation(struct cxm_softc *sc);
+int cxm_saa7115_set_chroma_saturation(struct cxm_softc *sc,
+                                      unsigned char chroma_saturation);
+int cxm_saa7115_get_contrast(struct cxm_softc *sc);
+int cxm_saa7115_set_contrast(struct cxm_softc *sc, unsigned char contrast);
+int cxm_saa7115_get_hue(struct cxm_softc *sc);
+int cxm_saa7115_set_hue(struct cxm_softc *sc, unsigned char hue);
+int cxm_saa7115_is_locked(struct cxm_softc *sc);
+int cxm_saa7115_wait_for_lock(struct cxm_softc *sc);
+
+#endif /* !_CXM_H */
diff --git a/sys/dev/video/cxm/cxm_dec_fw.c b/sys/dev/video/cxm/cxm_dec_fw.c
new file mode 100644 (file)
index 0000000..3d7eaf0
--- /dev/null
@@ -0,0 +1,5 @@
+#include <sys/types.h>
+
+const uint8_t cxm_dec_fw[] __attribute__ ((aligned(4))) = {
+       0x4e, 0x4f, 0x46, 0x57, 0x00 /* NOFW */
+};
diff --git a/sys/dev/video/cxm/cxm_eeprom.c b/sys/dev/video/cxm/cxm_eeprom.c
new file mode 100644 (file)
index 0000000..b49b57f
--- /dev/null
@@ -0,0 +1,283 @@
+/*
+ * Copyright (c) 2003, 2004, 2005
+ *     John Wehle <john@feith.com>.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *     This product includes software developed by John Wehle.
+ * 4. The name of the author may not be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * EEPROM routines for the Conexant MPEG-2 Codec driver.
+ *
+ * Ideally these routines should be implemented as a separate
+ * driver which has a generic EEPROM interface so that it's
+ * not necessary for each multimedia driver to re-invent the
+ * wheel.
+ */
+
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/conf.h>
+#include <sys/uio.h>
+#include <sys/kernel.h>
+#include <sys/poll.h>
+#include <sys/select.h>
+#include <sys/resource.h>
+#include <sys/bus.h>
+#include <sys/rman.h>
+
+
+#include <machine/clock.h>
+
+#include <dev/video/cxm/cxm.h>
+
+#include <bus/iicbus/iiconf.h>
+#include <bus/iicbus/iicbus.h>
+
+#include "iicbb_if.h"
+
+
+static int
+cxm_eeprom_read(device_t iicbus, int i2c_addr,
+                char *buf, int len, unsigned int offset)
+{
+       char msg[1];
+       int received;
+       int sent;
+
+       msg[0] = (unsigned char)offset;
+
+       if (iicbus_start(iicbus, i2c_addr, CXM_I2C_TIMEOUT) != 0)
+               return -1;
+
+       if (iicbus_write(iicbus, msg, sizeof(msg), &sent, CXM_I2C_TIMEOUT) != 0
+           || sent != sizeof(msg))
+               goto fail;
+
+       if (iicbus_repeated_start(iicbus, i2c_addr + 1, CXM_I2C_TIMEOUT) != 0)
+               goto fail;
+
+       if (iicbus_read(iicbus, buf, len, &received, IIC_LAST_READ, 0) != 0)
+               goto fail;
+
+       iicbus_stop(iicbus);
+
+        return received;
+
+fail:
+       iicbus_stop(iicbus);
+       return -1;
+}
+
+
+int
+cxm_eeprom_init(struct cxm_softc *sc)
+{
+       unsigned char eeprom[1];
+
+       if (cxm_eeprom_read(sc->iicbus, CXM_I2C_EEPROM,
+                           eeprom, sizeof(eeprom), 0) != sizeof(eeprom))
+               return -1;
+
+       return 0;
+}
+
+
+int
+cxm_eeprom_tuner_type(struct cxm_softc *sc)
+{
+       unsigned char eeprom[256];
+       unsigned int i;
+       unsigned int len;
+       unsigned int subsystem_vendor_id;
+       unsigned int tuner_code;
+       int tuner_type;
+
+       if (cxm_eeprom_read(sc->iicbus, CXM_I2C_EEPROM,
+                           eeprom, sizeof(eeprom), 0) != sizeof(eeprom))
+               return -1;
+
+       subsystem_vendor_id = (unsigned int)eeprom[254] << 8 | eeprom[255];
+       tuner_type = -1;
+
+       switch (subsystem_vendor_id) {
+       case PCI_VENDOR_HAUPPAUGE:
+
+               /*
+                * The Hauppauge eeprom format is tagged.
+                */
+
+               if (eeprom[0] != 0x84) {
+                       device_printf(sc->dev,
+                           "unknown Hauppauge eeprom format %#x\n",
+                           (unsigned int)eeprom[0]);
+                       break;
+               }
+
+               tuner_code = -1;
+
+               for (i = 0; i < sizeof(eeprom); i += len) {
+                       len = 0;
+                       if (eeprom[i] == 0x84) {
+                               len = (unsigned int)eeprom[i + 2] << 8
+                                     | eeprom[i + 1];
+                               i += 3;
+                       } else if ((eeprom[i] & 0xf0) == 0x70) {
+                               if (eeprom[i] & 0x08)
+                                       break;
+                               len = eeprom[i] & 0x07;
+                               i++;
+                       } else {
+                               device_printf(sc->dev,
+                                   "unknown Hauppauge eeprom packet %#x\n",
+                                   (unsigned int)eeprom[i]);
+                               return -1;
+                       }
+
+                       if (i >= sizeof(eeprom)
+                           || (i + len) > sizeof(eeprom)) {
+                               device_printf(sc->dev,
+                                   "corrupt Hauppauge eeprom packet\n");
+                               return -1;
+                       }
+
+                       switch (eeprom[i]) {
+                       case 0x00:
+                               tuner_code = eeprom[i + 6];
+                               break;
+
+                       case 0x0a:
+                               tuner_code = eeprom[i + 2];
+                               break;
+
+                       default:
+                               break;
+                       }
+               }
+
+               switch (tuner_code) {
+               case 0x03: /* Philips FI1216 */
+               case 0x08: /* Philips FI1216 MK2 */
+                       tuner_type = CXM_TUNER_PHILIPS_FI1216_MK2;
+                       break;
+
+               case 0x22: /* Philips FQ1216ME */
+                       tuner_type = CXM_TUNER_PHILIPS_FQ1216ME;
+                       break;
+
+               case 0x37: /* Philips FQ1216ME MK3 */
+                       tuner_type = CXM_TUNER_PHILIPS_FQ1216ME_MK3;
+                       break;
+
+               case 0x1d: /* Temic 4006FH5 */
+                       tuner_type = CXM_TUNER_TEMIC_4006_FH5;
+                       break;
+
+               case 0x30: /* LG Innotek TPI8PSB11D */
+                       tuner_type = CXM_TUNER_LG_TPI8PSB11D;
+                       break;
+
+               case 0x34: /* Microtune 4049FM5 */
+                       tuner_type = CXM_TUNER_MICROTUNE_4049_FM5;
+                       break;
+
+               case 0x05: /* Philips FI1236 */
+               case 0x0a: /* Philips FI1236 MK2 */
+                       tuner_type = CXM_TUNER_PHILIPS_FI1236_MK2;
+                       break;
+
+               case 0x1a: /* Temic 4036FY5 */
+                       tuner_type = CXM_TUNER_TEMIC_4036_FY5;
+                       break;
+
+               case 0x52: /* LG Innotek TAPC-H701F */
+                       tuner_type = CXM_TUNER_LG_TAPC_H701F;
+                       break;
+
+               case 0x55: /* TCL 2002N-6A */
+                       tuner_type = CXM_TUNER_TCL_2002N_6A;
+                       break;
+
+               case 0x06: /* Philips FI1246 */
+               case 0x0b: /* Philips FI1246 MK2 */
+                       tuner_type = CXM_TUNER_PHILIPS_FI1246_MK2;
+                       break;
+
+               case 0x23: /* Temic 4066FY5 */
+                       tuner_type = CXM_TUNER_TEMIC_4066_FY5;
+                       break;
+
+               case 0x10: /* Philips FR1216 MK2 */
+               case 0x15: /* Philips FM1216 */
+                       tuner_type = CXM_TUNER_PHILIPS_FM1216;
+                       break;
+
+               case 0x39: /* Philips FM1216ME MK3 */
+                       tuner_type = CXM_TUNER_PHILIPS_FM1216ME_MK3;
+                       break;
+
+               case 0x2a: /* Temic 4009FR5 */
+                       tuner_type = CXM_TUNER_TEMIC_4009_FR5;
+                       break;
+
+               case 0x2f: /* LG Innotek TPI8PSB01N */
+                       tuner_type = CXM_TUNER_LG_TPI8PSB01N;
+                       break;
+
+               case 0x12: /* Philips FR1236 MK2 */
+               case 0x17: /* Philips FM1236 */
+                       tuner_type = CXM_TUNER_PHILIPS_FM1236;
+                       break;
+
+               case 0x21: /* Temic 4039FR5 */
+                       tuner_type = CXM_TUNER_TEMIC_4039_FR5;
+                       break;
+
+               case 0x44: /* LG Innotek TAPE-H001F */
+                       tuner_type = CXM_TUNER_LG_TAPE_H001F;
+                       break;
+
+               case 0x13: /* Philips FR1246 MK2 */
+               case 0x18: /* Philips FM1246 */
+                       tuner_type = CXM_TUNER_PHILIPS_FM1246;
+                       break;
+
+               default:
+                       device_printf(sc->dev, "unknown tuner code %#x\n",
+                           tuner_code);
+                       break;
+               }
+               break;
+
+       default:
+               device_printf(sc->dev, "unknown subsystem vendor id %#x\n",
+                   subsystem_vendor_id);
+               break;
+       }
+
+       return tuner_type;
+}
diff --git a/sys/dev/video/cxm/cxm_enc_fw.c b/sys/dev/video/cxm/cxm_enc_fw.c
new file mode 100644 (file)
index 0000000..b97a4ca
--- /dev/null
@@ -0,0 +1,5 @@
+#include <sys/types.h>
+
+const uint8_t cxm_enc_fw[] __attribute__ ((aligned(4))) = {
+       0x4e, 0x4f, 0x46, 0x57, 0x00 /* NOFW */
+};
diff --git a/sys/dev/video/cxm/cxm_i2c.c b/sys/dev/video/cxm/cxm_i2c.c
new file mode 100644 (file)
index 0000000..87078f2
--- /dev/null
@@ -0,0 +1,378 @@
+/*
+ * Copyright (c) 2003, 2004, 2005
+ *     John Wehle <john@feith.com>.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *     This product includes software developed by John Wehle.
+ * 4. The name of the author may not be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * I2c routines for the Conexant MPEG-2 Codec driver.
+ */
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/malloc.h>
+#include <sys/conf.h>
+#include <sys/uio.h>
+#include <sys/kernel.h>
+#include <sys/module.h>
+#include <sys/poll.h>
+#include <sys/select.h>
+#include <sys/resource.h>
+#include <sys/bus.h>
+#include <sys/rman.h>
+
+#include <machine/clock.h>
+
+#include <bus/pci/pcireg.h>
+#include <bus/pci/pcivar.h>
+
+#include <dev/video/cxm/cxm.h>
+
+#include <bus/iicbus/iiconf.h>
+
+#include "iicbb_if.h"
+
+
+static int     cxm_iic_probe(device_t dev);
+static int     cxm_iic_attach(device_t dev);
+static int     cxm_iic_detach(device_t dev);
+static void    cxm_iic_child_detached(device_t dev, device_t child);
+
+static int     cxm_iic_callback(device_t, int, caddr_t *);
+static int     cxm_iic_reset(device_t, u_char, u_char, u_char *);
+static int     cxm_iic_getscl(device_t);
+static int     cxm_iic_getsda(device_t);
+static void    cxm_iic_setscl(device_t, int);
+static void    cxm_iic_setsda(device_t, int);
+static void    cxm_iic_setlines(device_t, int, int);
+
+static device_method_t cxm_iic_methods[] = {
+       /* Device interface */
+       DEVMETHOD(device_probe,         cxm_iic_probe),
+       DEVMETHOD(device_attach,        cxm_iic_attach),
+       DEVMETHOD(device_detach,        cxm_iic_detach),
+
+       /* bus interface */
+       DEVMETHOD(bus_child_detached,   cxm_iic_child_detached),
+       DEVMETHOD(bus_print_child,      bus_generic_print_child),
+       DEVMETHOD(bus_driver_added,     bus_generic_driver_added),
+
+       /* iicbb interface */
+       DEVMETHOD(iicbb_callback,       cxm_iic_callback),
+       DEVMETHOD(iicbb_reset,          cxm_iic_reset),
+       DEVMETHOD(iicbb_getdataline,    cxm_iic_getsda),
+       DEVMETHOD(iicbb_setlines,       cxm_iic_setlines),
+
+       { 0, 0 }
+};
+
+static driver_t cxm_iic_driver = {
+       "cxm_iic",
+       cxm_iic_methods,
+       sizeof(struct cxm_iic_softc),
+};
+
+static devclass_t cxm_iic_devclass;
+
+MODULE_VERSION(cxm_iic, 1);
+DRIVER_MODULE(cxm_iic, cxm, cxm_iic_driver, cxm_iic_devclass, 0, 0);
+
+
+/*
+ * the boot time probe routine.
+ *
+ * The cxm_iic device is only probed after it has
+ * been established that the cxm device is present
+ * which means that the cxm_iic device * must *
+ * be present since it's built into the cxm hardware.
+ */
+static int
+cxm_iic_probe(device_t dev)
+{
+       device_set_desc(dev, "Conexant iTVC15 / iTVC16 I2C controller");
+
+       return 0;
+}
+
+
+/*
+ * the attach routine.
+ */
+static int
+cxm_iic_attach(device_t dev)
+{
+       device_t *kids;
+       device_t iicbus;
+       int error;
+       int numkids;
+       int i;
+       int unit;
+       bus_space_handle_t *bhandlep;
+       bus_space_tag_t *btagp;
+       struct cxm_iic_softc *sc;
+       device_t child;
+
+       /* Get the device data */
+       sc = device_get_softc(dev);
+       unit = device_get_unit(dev);
+
+       /* retrieve the cxm btag and bhandle */
+       if (BUS_READ_IVAR(device_get_parent(dev), dev,
+                         CXM_IVAR_BTAG, (uintptr_t *)&btagp)
+           || BUS_READ_IVAR(device_get_parent(dev), dev,
+                            CXM_IVAR_BHANDLE, (uintptr_t *)&bhandlep)) {
+               device_printf(dev,
+                             "could not retrieve bus space information\n");
+               return ENXIO;
+       }
+
+       sc->btag = *btagp;
+       sc->bhandle = *bhandlep;
+
+       /* add bit-banging generic code onto cxm_iic interface */
+       sc->iicbb = device_add_child(dev, "iicbb", -1);
+
+       if (!sc->iicbb) {
+               device_printf(dev, "could not add iicbb\n");
+               return ENXIO;
+       }
+
+       /* probed and attached the bit-banging code */
+       error = device_probe_and_attach(sc->iicbb);
+
+       if (error) {
+               device_printf(dev, "could not attach iicbb\n");
+               goto fail;
+       }
+
+       /* locate iicbus which was attached by the bit-banging code */
+       iicbus = NULL;
+       device_get_children(sc->iicbb, &kids, &numkids);
+       for (i = 0; i < numkids; i++)
+               if (strcmp(device_get_name(kids[i]), "iicbus") == 0) {
+                       iicbus = kids[i];
+                       break;
+               }
+       kfree(kids, M_TEMP);
+
+       if (!iicbus) {
+               device_printf(dev, "could not find iicbus\n");
+               error = ENXIO;
+               goto fail;
+       }
+
+       if (BUS_WRITE_IVAR(device_get_parent(dev), dev,
+                          CXM_IVAR_IICBUS, (uintptr_t)&iicbus)) {
+               device_printf(dev, "could not store iicbus information\n");
+               error = ENXIO;
+               goto fail;
+       }
+
+       return 0;
+
+fail:
+       /*
+        * Detach the children before recursively deleting
+        * in case a child has a pointer to a grandchild
+        * which is used by the child's detach routine.
+        *
+        * Remember the child before detaching so we can
+        * delete it (bus_generic_detach indirectly zeroes
+        * sc->child_dev).
+        */
+       child = sc->iicbb;
+       bus_generic_detach(dev);
+       if (child)
+               device_delete_child(dev, child);
+
+       return error;
+}
+
+
+/*
+ * the detach routine.
+ */
+static int
+cxm_iic_detach(device_t dev)
+{
+       struct cxm_iic_softc *sc;
+       device_t child;
+
+       /* Get the device data */
+       sc = device_get_softc(dev);
+
+       BUS_WRITE_IVAR(device_get_parent(dev), dev, CXM_IVAR_IICBUS, 0);
+
+       /*
+        * Detach the children before recursively deleting
+        * in case a child has a pointer to a grandchild
+        * which is used by the child's detach routine.
+        *
+        * Remember the child before detaching so we can
+        * delete it (bus_generic_detach indirectly zeroes
+        * sc->child_dev).
+        */
+       child = sc->iicbb;
+       bus_generic_detach(dev);
+       if (child)
+               device_delete_child(dev, child);
+
+       return 0;
+}
+
+
+/*
+ * the child detached routine.
+ */
+static void
+cxm_iic_child_detached(device_t dev, device_t child)
+{
+       struct cxm_iic_softc *sc;
+
+       /* Get the device data */
+       sc = device_get_softc(dev);
+
+       if (child == sc->iicbb)
+               sc->iicbb = NULL;
+}
+
+
+static int
+cxm_iic_callback(device_t dev, int index, caddr_t *data)
+{
+       return 0;
+}
+
+
+static int
+cxm_iic_reset(device_t dev, u_char speed, u_char addr, u_char * oldaddr)
+{
+       struct cxm_iic_softc *sc;
+
+       /* Get the device data */
+       sc = (struct cxm_iic_softc *)device_get_softc(dev);
+
+       /* Set scl to 1 */
+       CSR_WRITE_4(sc, CXM_REG_I2C_SETSCL, ~(int)1);
+
+       /* Set sda to 1 */
+       CSR_WRITE_4(sc, CXM_REG_I2C_SETSDA, ~(int)1);
+
+       /*
+        * PCI writes may be buffered so force the
+        * write to complete by reading the last
+        * location written.
+        */
+
+       CSR_READ_4(sc, CXM_REG_I2C_SETSDA);
+
+       /* Wait for 10 usec */
+       DELAY(10);
+
+       return IIC_ENOADDR;
+}
+
+
+static int
+cxm_iic_getscl(device_t dev)
+{
+       struct cxm_iic_softc *sc;
+
+       /* Get the device data */
+       sc = (struct cxm_iic_softc *)device_get_softc(dev);
+
+       /* Get sda */
+       return CSR_READ_1(sc, CXM_REG_I2C_GETSCL);
+}
+
+
+static int
+cxm_iic_getsda(device_t dev)
+{
+       struct cxm_iic_softc *sc;
+
+       /* Get the device data */
+       sc = (struct cxm_iic_softc *)device_get_softc(dev);
+
+       /* Get sda */
+       return CSR_READ_1(sc, CXM_REG_I2C_GETSDA);
+}
+
+
+static void
+cxm_iic_setscl(device_t dev, int val)
+{
+       struct cxm_iic_softc *sc;
+
+       /* Get the device data */
+       sc = (struct cxm_iic_softc *)device_get_softc(dev);
+
+       /* Set scl to the requested value */
+       CSR_WRITE_4(sc, CXM_REG_I2C_SETSCL, ~(int)(val ? 1 : 0));
+
+       /*
+        * PCI writes may be buffered so force the
+        * write to complete by reading the last
+        * location written.
+        */
+
+       CSR_READ_4(sc, CXM_REG_I2C_SETSCL);
+}
+
+
+static void
+cxm_iic_setsda(device_t dev, int val)
+{
+       struct cxm_iic_softc *sc;
+
+       /* Get the device data */
+       sc = (struct cxm_iic_softc *)device_get_softc(dev);
+
+       /* Set sda to the requested value */
+       CSR_WRITE_4(sc, CXM_REG_I2C_SETSDA, ~(int)(val ? 1 : 0));
+
+       /*
+        * PCI writes may be buffered so force the
+        * write to complete by reading the last
+        * location written.
+        */
+
+       CSR_READ_4(sc, CXM_REG_I2C_SETSDA);
+}
+
+
+static void
+cxm_iic_setlines(device_t dev, int ctrl, int data)
+{
+
+       cxm_iic_setscl(dev, ctrl);
+       cxm_iic_setsda(dev, data);
+
+       /* Wait for 10 usec */
+       DELAY(10);
+}
diff --git a/sys/dev/video/cxm/cxm_ir.c b/sys/dev/video/cxm/cxm_ir.c
new file mode 100644 (file)
index 0000000..4a70365
--- /dev/null
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) 2003, 2004
+ *     John Wehle <john@feith.com>.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *     This product includes software developed by John Wehle.
+ * 4. The name of the author may not be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * Infrared remote routines for the Conexant MPEG-2 Codec driver.
+ *
+ * Ideally these routines should be implemented as a separate
+ * driver which has a generic infrared remote interface so that
+ * it's not necessary for each multimedia driver to re-invent
+ * the wheel.
+ */
+
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/conf.h>
+#include <sys/uio.h>
+#include <sys/kernel.h>
+#include <sys/poll.h>
+#include <sys/select.h>
+#include <sys/resource.h>
+#include <sys/bus.h>
+#include <sys/rman.h>
+
+#include <machine/clock.h>
+
+#include <dev/video/cxm/cxm.h>
+
+#include <bus/iicbus/iiconf.h>
+#include <bus/iicbus/iicbus.h>
+
+#include "iicbb_if.h"
+
+
+static int
+cxm_ir_read(device_t iicbus, int i2c_addr, char *buf, int len)
+{
+       int received;
+
+       if (iicbus_start(iicbus, i2c_addr + 1, CXM_I2C_TIMEOUT) != 0)
+               return -1;
+
+       if (iicbus_read(iicbus, buf, len, &received, IIC_LAST_READ, 0) != 0)
+               goto fail;
+
+       iicbus_stop(iicbus);
+
+        return received;
+
+fail:
+       iicbus_stop(iicbus);
+       return -1;
+}
+
+
+int
+cxm_ir_init(struct cxm_softc *sc)
+{
+       unsigned char key[1];
+
+       if (cxm_ir_read(sc->iicbus, CXM_I2C_IR,
+                           key, sizeof(key)) != sizeof(key))
+               return -1;
+
+       device_printf(sc->dev, "IR Remote\n");
+
+       return 0;
+}
+
+
+int
+cxm_ir_key(struct cxm_softc *sc, char *buf, int len)
+{
+       int result;
+
+       result = cxm_ir_read(sc->iicbus, CXM_I2C_IR, buf, len);
+
+       if (result >= 0)
+               return result;
+
+       /*
+        * If the IR receiver didn't respond,
+        * then wait 50 ms and try again.
+        */
+
+       tsleep(&sc->iicbus, 0, "IR", hz / 20);
+
+       return cxm_ir_read(sc->iicbus, CXM_I2C_IR, buf, len);
+}
diff --git a/sys/dev/video/cxm/cxm_msp34xxx.c b/sys/dev/video/cxm/cxm_msp34xxx.c
new file mode 100644 (file)
index 0000000..d5843a4
--- /dev/null
@@ -0,0 +1,627 @@
+/*
+ * Copyright (c) 2003, 2004, 2005
+ *     John Wehle <john@feith.com>.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *     This product includes software developed by John Wehle.
+ * 4. The name of the author may not be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * Audio decoder routines for the Conexant MPEG-2 Codec driver.
+ *
+ * Ideally these routines should be implemented as a separate
+ * driver which has a generic audio decoder interface so that
+ * it's not necessary for each multimedia driver to re-invent
+ * the wheel.
+ */
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/conf.h>
+#include <sys/uio.h>
+#include <sys/kernel.h>
+#include <sys/poll.h>
+#include <sys/select.h>
+#include <sys/resource.h>
+#include <sys/bus.h>
+#include <sys/rman.h>
+
+#include <machine/clock.h>
+
+#include <dev/video/cxm/cxm.h>
+
+#include <bus/iicbus/iiconf.h>
+#include <bus/iicbus/iicbus.h>
+
+#include "iicbb_if.h"
+
+
+static const struct cxm_msp_command
+msp34x5G_init = {
+       5,
+       {
+               /* Enable Automatic Sound Select */
+               { CXM_MSP3400C_DEM, 0x0030, { 0x20, 0x03 } },
+               /* SCART Prescale = 0 dB */
+               { CXM_MSP3400C_DFP, 0x000d, { 0x19, 0x00 } },
+               /* FM / AM Prescale = 100 Khz and FM Matrix = Sound A Mono  */
+               { CXM_MSP3400C_DFP, 0x000e, { 0x24, 0x03 } },
+               /* NICAM Prescale = 9 dB  */
+               { CXM_MSP3400C_DFP, 0x0010, { 0x5a, 0x00 } },
+               /* Enable Automatic Standard Select */
+               { CXM_MSP3400C_DEM, 0x0020, { 0x00, 0x01 } },
+       }
+};
+
+static const struct cxm_msp_command
+msp34x5G_select_tuner = {
+       3,
+       {
+               /* Loudspeaker Source = demodulator (St or A), Matrix = St */
+               { CXM_MSP3400C_DFP, 0x0008, { 0x03, 0x20 } },
+               /* SCART1_L/R Source = demodulator (St or A), Matrix = St */
+               { CXM_MSP3400C_DFP, 0x000a, { 0x03, 0x20 } },
+               /* DSP In = mute, SC1_OUT_L/R Source = SCART1_L/R */
+               { CXM_MSP3400C_DFP, 0x0013, { 0x0f, 0x20 } }
+       }
+};
+
+static const struct cxm_msp_command
+msp34x5D_init = {
+       4,
+       {
+               /* Enable Automatic NICAM-FM/AM Switching */
+               { CXM_MSP3400C_DEM, 0x0021, { 0x00, 0x01 } },
+               /* SCART Prescale = 0 dB */
+               { CXM_MSP3400C_DFP, 0x000d, { 0x19, 0x00 } },
+               /* NICAM Prescale = 9 dB  */
+               { CXM_MSP3400C_DFP, 0x0010, { 0x5a, 0x00 } },
+               /* Enable Automatic Standard Select */
+               { CXM_MSP3400C_DEM, 0x0020, { 0x00, 0x01 } },
+       }
+};
+
+static const struct cxm_msp_command
+msp34x5D_select_tuner = {
+       5,
+       {
+               /* Loudspeaker Source = demodulator (NICAM), Matrix = St */
+               { CXM_MSP3400C_DFP, 0x0008, { 0x01, 0x20 } },
+               /* SCART1_L/R Source = demodulator (NICAM), Matrix = St */
+               { CXM_MSP3400C_DFP, 0x000a, { 0x01, 0x20 } },
+               /* FM / AM Prescale = 100 Khz and FM Matrix = No Matrix  */
+               { CXM_MSP3400C_DFP, 0x000e, { 0x24, 0x00 } },
+               /* FM Deemphasis = 50 us */
+               { CXM_MSP3400C_DFP, 0x000f, { 0x00, 0x00 } },
+               /* DSP In = mute, SC1_OUT_L/R Source = SCART1_L/R */
+               { CXM_MSP3400C_DFP, 0x0013, { 0x0f, 0x20 } }
+       }
+};
+
+static const struct cxm_msp_command
+msp34xxx_mute = {
+       2,
+       {
+               /* Loudspeaker volume = mute */
+               { CXM_MSP3400C_DFP, 0x0000, { 0x00, 0x00 } },
+               /* SC1_OUT_L/R volume = mute */
+               { CXM_MSP3400C_DFP, 0x0007, { 0x00, 0x01 } }
+       }
+};
+
+static const struct cxm_msp_command
+msp34xxx_unmute = {
+       2,
+       {
+               /* Loudspeaker volume = 0 db */
+               { CXM_MSP3400C_DFP, 0x0000, { 0x73, 0x00 } },
+               /* SC1_OUT_L/R volume = 0 db */
+               { CXM_MSP3400C_DFP, 0x0007, { 0x73, 0x01 } }
+       }
+};
+
+static const struct cxm_msp_command
+msp34xxx_select_fm = {
+       3,
+       {
+               /* Loudspeaker Source = SCART, Matrix = STEREO */
+               { CXM_MSP3400C_DFP, 0x0008, { 0x02, 0x20 } },
+               /* SCART1_L/R Source = SCART, Matrix = STEREO */
+               { CXM_MSP3400C_DFP, 0x000a, { 0x02, 0x20 } },
+               /* DSP In = SC2_IN_L/R, SC1_OUT_L/R Source = SCART1_L/R */
+               { CXM_MSP3400C_DFP, 0x0013, { 0x0e, 0x00 } }
+       }
+};
+
+static const struct cxm_msp_command
+msp34xxx_select_line_in = {
+       3,
+       {
+               /* Loudspeaker Source = SCART, Matrix = STEREO */
+               { CXM_MSP3400C_DFP, 0x0008, { 0x02, 0x20 } },
+               /* SCART1_L/R Source = SCART, Matrix = STEREO */
+               { CXM_MSP3400C_DFP, 0x000a, { 0x02, 0x20 } },
+               /* DSP In = SC1_IN_L/R, SC1_OUT_L/R Source = SCART1_L/R */
+               { CXM_MSP3400C_DFP, 0x0013, { 0x0c, 0x00 } }
+       }
+};
+
+
+/* Reset the MSP or DPL chip */
+static int
+cxm_msp_dpl_reset(device_t iicbus, int i2c_addr)
+{
+       unsigned char msg[3];
+       int sent;
+
+       /* put into reset mode */
+       msg[0] = 0x00;
+       msg[1] = 0x80;
+       msg[2] = 0x00;
+
+       if (iicbus_start(iicbus, i2c_addr, CXM_I2C_TIMEOUT) != 0)
+               return -1;
+
+       if (iicbus_write(iicbus, msg, sizeof(msg), &sent, CXM_I2C_TIMEOUT) != 0
+           || sent != sizeof(msg))
+               goto fail;
+
+       iicbus_stop(iicbus);
+
+       /* put back to operational mode */
+       msg[0] = 0x00;
+       msg[1] = 0x00;
+       msg[2] = 0x00;
+
+       if (iicbus_start(iicbus, i2c_addr, CXM_I2C_TIMEOUT) != 0)
+               return -1;
+
+       if (iicbus_write(iicbus, msg, sizeof(msg), &sent, CXM_I2C_TIMEOUT) != 0
+           || sent != sizeof(msg))
+               goto fail;
+
+       iicbus_stop(iicbus);
+
+       return 0;
+
+fail:
+       iicbus_stop(iicbus);
+       return -1;
+}
+
+
+/* Read from the MSP or DPL registers */
+static int
+cxm_msp_dpl_read(device_t iicbus, int i2c_addr,
+                 unsigned char dev, unsigned int addr,
+                 char *buf, int len)
+{
+       unsigned char msg[3];
+       int received;
+       int sent;
+
+       msg[0] = (unsigned char)(dev + 1);
+       msg[1] = (unsigned char)(addr >> 8);
+       msg[2] = (unsigned char)addr;
+
+       if (iicbus_start(iicbus, i2c_addr, CXM_I2C_TIMEOUT) != 0)
+               return -1;
+
+       if (iicbus_write(iicbus, msg, sizeof(msg), &sent, CXM_I2C_TIMEOUT) != 0
+           || sent != sizeof(msg))
+               goto fail;
+
+       if (iicbus_repeated_start(iicbus, i2c_addr + 1, CXM_I2C_TIMEOUT) != 0)
+               goto fail;
+
+       if (iicbus_read(iicbus, buf, len, &received, IIC_LAST_READ, 0) != 0)
+               goto fail;
+
+       iicbus_stop(iicbus);
+
+       return received;
+
+fail:
+       iicbus_stop(iicbus);
+       return -1;
+}
+
+
+/* Write to the MSP or DPL registers */
+static int
+cxm_msp_dpl_write(device_t iicbus, int i2c_addr,
+                  unsigned char dev, unsigned int addr,
+                  const char *buf, int len)
+{
+       unsigned char msg[3];
+       int sent;
+
+       msg[0] = (unsigned char)dev;
+       msg[1] = (unsigned char)(addr >> 8);
+       msg[2] = (unsigned char)addr;
+
+       if (iicbus_start(iicbus, i2c_addr, CXM_I2C_TIMEOUT) != 0)
+               return -1;
+
+       if (iicbus_write(iicbus, msg, sizeof(msg), &sent, CXM_I2C_TIMEOUT) != 0
+           || sent != sizeof(msg))
+               goto fail;
+
+       if (iicbus_write(iicbus, buf, len, &sent, CXM_I2C_TIMEOUT) != 0)
+               goto fail;
+
+       iicbus_stop(iicbus);
+
+       return sent;
+
+fail:
+       iicbus_stop(iicbus);
+       return -1;
+}
+
+
+int
+cxm_msp_init(struct cxm_softc *sc)
+{
+       unsigned char rev1[2];
+       unsigned char rev2[2];
+       unsigned int i;
+       unsigned int nsettings;
+       const struct cxm_msp_setting *settings;
+
+       if (cxm_msp_dpl_reset (sc->iicbus, CXM_I2C_MSP3400) < 0)
+               return -1;
+
+       if (cxm_msp_dpl_read(sc->iicbus, CXM_I2C_MSP3400, CXM_MSP3400C_DFP,
+                            0x001e, rev1, sizeof(rev1)) != sizeof(rev1))
+               return -1;
+
+       if (cxm_msp_dpl_read(sc->iicbus, CXM_I2C_MSP3400, CXM_MSP3400C_DFP,
+                            0x001f, rev2, sizeof(rev2)) != sizeof(rev2))
+               return -1;
+
+       ksnprintf(sc->msp_name, sizeof(sc->msp_name), "%c4%02d%c-%c%d",
+                ((rev1[1] >> 4) & 0x0f) + '3', rev2[0],
+                (rev1[1] & 0x0f) + '@', rev1[0] + '@', rev2[1] & 0x1f);
+
+       /*
+        * MSP 34x5D, 34x5G, and MSP 44x8G are the
+        * only audio decoders currently supported.
+        */
+
+       if (strncmp(&sc->msp_name[0], "34", 2) == 0
+           && strncmp(&sc->msp_name[3], "5D", 2) == 0)
+         ;
+       else if (strncmp(&sc->msp_name[0], "34", 2) == 0
+                && strncmp(&sc->msp_name[3], "5G", 2) == 0)
+         ;
+       else if (strncmp(&sc->msp_name[0], "44", 2) == 0
+                && strncmp(&sc->msp_name[3], "8G", 2) == 0)
+         ;
+       else {
+               device_printf(sc->dev, "unknown audio decoder MSP%s\n",
+                   sc->msp_name);
+               return -1;
+       }
+
+       nsettings = msp34x5G_init.nsettings;
+       settings = msp34x5G_init.settings;
+       if (sc->msp_name[4] == 'D') {
+               nsettings = msp34x5D_init.nsettings;
+               settings = msp34x5D_init.settings;
+         }
+
+       for (i = 0; i < nsettings; i++)
+               if (cxm_msp_dpl_write(sc->iicbus, CXM_I2C_MSP3400,
+                                     settings[i].dev, settings[i].addr,
+                                     settings[i].value,
+                                     sizeof(settings[i].value))
+                   != sizeof(settings[i].value))
+                       return -1;
+
+       if (cxm_msp_select_source(sc, cxm_tuner_source) < 0)
+               return -1;
+
+       device_printf(sc->dev, "MSP%s audio decoder\n", sc->msp_name);
+
+       return 0;
+}
+
+
+int
+cxm_msp_mute(struct cxm_softc *sc)
+{
+       unsigned int i;
+       unsigned int nsettings;
+       const struct cxm_msp_setting *settings;
+
+       nsettings = msp34xxx_mute.nsettings;
+       settings = msp34xxx_mute.settings;
+
+       for (i = 0; i < nsettings; i++)
+               if (cxm_msp_dpl_write(sc->iicbus, CXM_I2C_MSP3400,
+                                     settings[i].dev, settings[i].addr,
+                                     settings[i].value,
+                                     sizeof(settings[i].value))
+                   != sizeof(settings[i].value))
+                       return -1;
+
+       return 0;
+}
+
+
+int
+cxm_msp_unmute(struct cxm_softc *sc)
+{
+       unsigned int i;
+       unsigned int nsettings;
+       const struct cxm_msp_setting *settings;
+
+       nsettings = msp34xxx_unmute.nsettings;
+       settings = msp34xxx_unmute.settings;
+
+       for (i = 0; i < nsettings; i++)
+               if (cxm_msp_dpl_write(sc->iicbus, CXM_I2C_MSP3400,
+                                     settings[i].dev, settings[i].addr,
+                                     settings[i].value,
+                                     sizeof(settings[i].value))
+                   != sizeof(settings[i].value))
+                       return -1;
+
+       return 0;
+}
+
+
+int
+cxm_msp_is_muted(struct cxm_softc *sc)
+{
+       unsigned char volume[2];
+
+       if (cxm_msp_dpl_read(sc->iicbus, CXM_I2C_MSP3400, CXM_MSP3400C_DFP,
+                            0x0000, volume, sizeof(volume)) != sizeof(volume))
+               return -1;
+
+       return volume[0] == 0x00 || volume[0] == 0xff ? 1 : 0;
+}
+
+
+int
+cxm_msp_select_source(struct cxm_softc *sc, enum cxm_source source)
+{
+       unsigned int i;
+       unsigned int nsettings;
+       const struct cxm_msp_setting *settings;
+
+       switch (source) {
+       case cxm_fm_source:
+               nsettings = msp34xxx_select_fm.nsettings;
+               settings = msp34xxx_select_fm.settings;
+               break;
+
+       case cxm_line_in_source_composite:
+       case cxm_line_in_source_svideo:
+               nsettings = msp34xxx_select_line_in.nsettings;
+               settings = msp34xxx_select_line_in.settings;
+               break;
+
+       case cxm_tuner_source:
+               nsettings = msp34x5G_select_tuner.nsettings;
+               settings = msp34x5G_select_tuner.settings;
+               if (sc->msp_name[4] == 'D') {
+                       nsettings = msp34x5D_select_tuner.nsettings;
+                       settings = msp34x5D_select_tuner.settings;
+                 }
+               break;
+
+       default:
+               return -1;
+       }
+
+       for (i = 0; i < nsettings; i++)
+               if (cxm_msp_dpl_write(sc->iicbus, CXM_I2C_MSP3400,
+                                     settings[i].dev, settings[i].addr,
+                                     settings[i].value,
+                                     sizeof(settings[i].value))
+                   != sizeof(settings[i].value))
+                       return -1;
+
+       return 0;
+}
+
+
+enum cxm_source
+cxm_msp_selected_source(struct cxm_softc *sc)
+{
+       unsigned char dsp[2];
+       unsigned char source[2];
+
+       if (cxm_msp_dpl_read(sc->iicbus, CXM_I2C_MSP3400, CXM_MSP3400C_DFP,
+                            0x0008, source, sizeof(source)) != sizeof(source))
+               return cxm_unknown_source;
+
+       switch (source[0]) {
+       case 0: /* FM / AM mono signal */
+       case 1: /* Stereo or A / B */
+       case 3: /* Stereo or A */
+       case 4: /* Stereo or B */
+               return cxm_tuner_source;
+
+       case 2: /* SCART */
+               break;
+
+       default:
+               return cxm_unknown_source;
+       }
+
+       if (cxm_msp_dpl_read(sc->iicbus, CXM_I2C_MSP3400, CXM_MSP3400C_DFP,
+                            0x0013, dsp, sizeof(dsp)) != sizeof(dsp))
+               return cxm_unknown_source;
+
+       if (dsp[1] & 0x20)
+               return cxm_unknown_source;
+
+       switch (dsp[0] & 0x03) {
+       case 0:
+               return cxm_line_in_source_composite;
+
+       case 2:
+               return cxm_fm_source;
+
+       default:
+                return cxm_unknown_source;
+       }
+}
+
+
+int
+cxm_msp_autodetect_standard(struct cxm_softc *sc)
+{
+       unsigned int i;
+       int locked;
+       unsigned int nsettings;
+       const struct cxm_msp_setting *settings;
+
+       switch (cxm_msp_selected_source(sc)) {
+       case cxm_tuner_source:
+               break;
+
+       case cxm_fm_source:
+       case cxm_line_in_source_composite:
+       case cxm_line_in_source_svideo:
+               return 1;
+
+       default:
+               return -1;
+       }
+
+       /*
+        * Section 3.3.2.2 of the data sheet states:
+        *
+        *   A general refresh of the STANDARD SELECT
+        *   register is not allowed.
+        */
+
+       if (cxm_msp_dpl_reset (sc->iicbus, CXM_I2C_MSP3400) < 0)
+               return -1;
+
+       nsettings = msp34x5G_init.nsettings;
+       settings = msp34x5G_init.settings;
+       if (sc->msp_name[4] == 'D') {
+               nsettings = msp34x5D_init.nsettings;
+               settings = msp34x5D_init.settings;
+         }
+
+       for (i = 0; i < nsettings; i++)
+               if (cxm_msp_dpl_write(sc->iicbus, CXM_I2C_MSP3400,
+                                     settings[i].dev, settings[i].addr,
+                                     settings[i].value,
+                                     sizeof(settings[i].value))
+                   != sizeof(settings[i].value))
+                       return -1;
+
+       locked = cxm_msp_wait_for_lock(sc);
+
+       if (cxm_msp_select_source(sc, cxm_tuner_source) < 0)
+               return -1;
+
+       return locked;
+}
+
+
+int
+cxm_msp_is_locked(struct cxm_softc *sc)
+{
+       unsigned char source[2];
+       unsigned char standard[2];
+
+       if (cxm_msp_dpl_read(sc->iicbus, CXM_I2C_MSP3400, CXM_MSP3400C_DFP,
+                            0x0008, source, sizeof(source)) != sizeof(source))
+               return -1;
+
+       switch (source[0]) {
+       case 0: /* FM / AM mono signal */
+       case 1: /* Stereo or A / B */
+       case 3: /* Stereo or A */
+       case 4: /* Stereo or B */
+               break;
+
+       default:
+               return 1;
+       }
+
+       if (cxm_msp_dpl_read(sc->iicbus, CXM_I2C_MSP3400, CXM_MSP3400C_DEM,
+                            0x007e, standard, sizeof(standard))
+           != sizeof(standard))
+               return -1;
+
+       if (standard[0] >= 8 || (standard[0] == 0 && standard[1] == 0))
+               return 0;
+
+       return 1;
+}
+
+
+int
+cxm_msp_wait_for_lock(struct cxm_softc *sc)
+{
+       unsigned int i;
+
+       /*
+        * Section 3.3.2.1 of the data sheet states:
+        *
+        *   Within 0.5 s the detection and setup of the actual
+        *   TV sound standard is performed.  The detected result
+        *   can be read out of the STANDARD RESULT register by
+        *   the control processor.
+        */
+
+       for (i = 0; i < 10; i++) {
+
+               /*
+                * The input may have just changed (prior to
+                * cxm_msp_wait_for_lock) so start with the
+                * delay to give the audio decoder a chance
+                * to update its status.
+                */
+
+               tsleep(&sc->iicbus, 0, "audio", hz / 20);
+
+               switch (cxm_msp_is_locked(sc)) {
+               case 1:
+                       return 1;
+
+               case 0:
+                       break;
+
+               default:
+                       return -1;
+               }
+       }
+
+       device_printf(sc->dev, "audio decoder failed to lock\n");
+
+       return 0;
+}
diff --git a/sys/dev/video/cxm/cxm_saa7115.c b/sys/dev/video/cxm/cxm_saa7115.c
new file mode 100644 (file)
index 0000000..683b1f9
--- /dev/null
@@ -0,0 +1,1195 @@
+/*
+ * Copyright (c) 2003, 2004, 2005
+ *     John Wehle <john@feith.com>.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *     This product includes software developed by John Wehle.
+ * 4. The name of the author may not be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * Video decoder routines for the Conexant MPEG-2 Codec driver.
+ *
+ * Ideally these routines should be implemented as a separate
+ * driver which has a generic video decoder interface so that
+ * it's not necessary for each multimedia driver to re-invent
+ * the wheel.
+ */
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/conf.h>
+#include <sys/uio.h>
+#include <sys/kernel.h>
+#include <sys/poll.h>
+#include <sys/select.h>
+#include <sys/resource.h>
+#include <sys/bus.h>
+#include <sys/rman.h>
+
+#include <machine/clock.h>
+
+#include <dev/video/cxm/cxm.h>
+
+#include <bus/iicbus/iiconf.h>
+#include <bus/iicbus/iicbus.h>
+
+#include "iicbb_if.h"
+
+
+static const struct cxm_saa7115_command
+saa7115_init = {
+       19,
+       {
+               /* Full auto mode for CVBS */
+               { 0x01, 1, { 0x08 } },
+               { 0x03, 18, { 0x20, 0x90, 0x90, 0xeb, 0xe0, 0xb0, 0x40, 0x80,
+                             0x44, 0x40, 0x00, 0x03, 0x2a, 0x06, 0x00, 0x9d,
+                             0x80, 0x01 } },
+               { 0x17, 7, { 0x99, 0x40, 0x80, 0x77, 0x42, 0xa9, 0x01 } },
+
+               /*
+                * VBI data slicer
+                *
+                * NTSC raw VBI data on lines 10 through 21
+                * PAL raw VBI data on lines 6 through 22
+                *
+                * Actually lines 21 and 22 are set by the
+                * NTSC and PAL specific configurations.
+                */
+               { 0x40, 20, { 0x40, 0x00, 0x00, 0x00, 0x00, 0xdd, 0xdd, 0xdd,
+                             0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd,
+                             0xdd, 0xdd, 0xdd, 0xdd } },
+               { 0x56, 4, { 0x00, 0x00, 0x00, 0x47 } },
+               { 0x5c, 3, { 0x00, 0x1f, 0x35 } },
+
+               /* I-port and X-port configuration */
+               { 0x80, 2, { 0x00, 0x01 } },
+               { 0x83, 5, { 0x00, 0x20, 0x21, 0xc5, 0x01 } },
+
+               /* Scaler input configuration and output format settings */
+               { 0xc0, 4, { 0x00, 0x08, 0x00, 0x80 } },
+
+               /* VBI scaler configuration */
+               { 0x90, 4, { 0x80, 0x48, 0x00, 0x84 } },
+               { 0xa0, 3, { 0x01, 0x00, 0x00 } },
+               { 0xa4, 3, { 0x80, 0x40, 0x40 } },
+               { 0xa8, 3, { 0x00, 0x02, 0x00 } },
+               { 0xac, 3, { 0x00, 0x01, 0x00 } },
+               { 0xb0, 5, { 0x00, 0x04, 0x00, 0x04, 0x00 } },
+               { 0xb8, 8, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }},
+
+               /* Audio Master Clock to Audio Serial Clock ratio */
+               { 0x38, 3, { 0x03, 0x10, 0x00 } },
+
+               /* PLL2 target clock 27 MHz (using a 32.11 MHz crystal) */
+               { 0xf1, 4, { 0x05, 0xd0, 0x35, 0x00 } },
+
+               /* Pulse generator */
+               { 0xf6, 10, { 0x61, 0x0e, 0x60, 0x0e, 0x60, 0x0e, 0x00,
+                             0x00, 0x00, 0x88 } }
+       }
+};
+
+static const struct cxm_saa7115_command
+saa7115_mute = {
+       1,
+       {
+               /* Disable I-port */
+               { 0x87, 1, { 0x00 } },
+       }
+};
+
+static const struct cxm_saa7115_command
+saa7115_unmute = {
+       1,
+       {
+               /* Enable I-port */
+               { 0x87, 1, { 0x01 } },
+       }
+};
+
+static const struct cxm_saa7115_command
+saa7115_select_fm = {
+       1,
+       {
+               /* Enable audio clock */
+               { 0x88, 1, { 0x33 } }
+       }
+};
+
+static const struct cxm_saa7115_command
+saa7115_select_line_in_composite = {
+       3,
+       {
+               /* Amp plus anti-alias filter, CVBS from AI11 */
+               { 0x02, 1, { 0xc0 } },
+               /* Adaptive luminance comb filter */
+               { 0x09, 1, { 0x40 } },
+
+               /* Enable AD1, audio clock, scaler, decoder */
+               { 0x88, 1, { 0x70 } }
+       }
+};
+
+static const struct cxm_saa7115_command
+saa7115_select_line_in_svideo = {
+       3,
+       {
+               /* Amp plus anti-alias filter, Y / C from AI11 / AI21 */
+               { 0x02, 1, { 0xc8 } },
+               /* Bypass chrominance trap / comb filter */
+               { 0x09, 1, { 0x80 } },
+
+               /* Enable AD1 & 2, audio clock, scaler, decoder */
+               { 0x88, 1, { 0xf0 } }
+       }
+};
+
+static const struct cxm_saa7115_command
+saa7115_select_tuner = {
+       3,
+       {
+               /* Amp plus anti-alias filter, CVBS (auto gain) from AI23 */
+               { 0x02, 1, { 0xc4 } },
+               /* Adaptive luminance comb filter */
+               { 0x09, 1, { 0x40 } },
+
+               /* Enable AD2, audio clock, scaler, decoder */
+               { 0x88, 1, { 0xb0 } }
+       }
+};
+
+static const struct cxm_saa7115_command
+saa7115_audio_clock_44100_ntsc = {
+       2,
+       {
+               /* Audio clock 44.1 kHz NTSC (using a 32.11 MHz crystal) */
+               { 0x30, 3, { 0xbc, 0xdf, 0x02 } },
+               { 0x34, 3, { 0xf2, 0x00, 0x2d } }
+       }
+};
+
+static const struct cxm_saa7115_command
+saa7115_audio_clock_44100_pal = {
+       2,
+       {
+               /* Audio clock 44.1 kHz PAL (using a 32.11 MHz crystal) */
+               { 0x30, 3, { 0x00, 0x72, 0x03 } },
+               { 0x34, 3, { 0xf2, 0x00, 0x2d } }
+       }
+};
+
+static const struct cxm_saa7115_command
+saa7115_audio_clock_48000_ntsc = {
+       2,
+       {
+               /* Audio clock 48 kHz NTSC (using a 32.11 MHz crystal) */
+               { 0x30, 3, { 0xcd, 0x20, 0x03 } },
+               { 0x34, 3, { 0xce, 0xfb, 0x30 } }
+       }
+};
+
+static const struct cxm_saa7115_command
+saa7115_audio_clock_48000_pal = {
+       2,
+       {
+               /* Audio clock 48 kHz PAL (using a 32.11 MHz crystal) */
+               { 0x30, 3, { 0x00, 0xc0, 0x03 } },
+               { 0x34, 3, { 0xce, 0xfb, 0x30 } }
+       }
+};
+
+static const struct cxm_saa7115_command
+saa7115_scaler_vcd_ntsc_double_lines = {
+       13,
+       {
+               /*
+                * Input window = 720 x 240, output window = 352 x 240 with
+                * YS extended by 2 as per section 17.4 of the data sheet
+                * and YO accounting for scaler processing triggering at
+                * line 5 and active video starting at line 23 (see section
+                * 8.2 table 8 and section 8.3.1.1 table 11 of the data sheet).
+                * NTSC active video should actually start at line 22, however
+                * not all channels / programs do.
+                */
+               { 0xc4, 12, { 0x02, 0x00, 0xd0, 0x02, 0x12, 0x00, 0xf2, 0x00,
+                             0x60, 0x01, 0xf0, 0x00 } },
+
+               /* Prefiltering and prescaling */
+               { 0xd0, 3, { 0x02, 0x02, 0xaa } },
+
+               /* Brightness, contrast, and saturation */
+               { 0xd4, 3, { 0x80, 0x40, 0x40 } },
+
+               /* Horizontal phase scaling */
+               { 0xd8, 3, { 0x18, 0x04, 0x00 } },
+               { 0xdc, 3, { 0x0c, 0x02, 0x00 } },
+
+               /* Vertical scaling */
+               { 0xe0, 5, { 0x00, 0x04, 0x00, 0x04, 0x00 } },
+
+               /* Vertical phase offsets */
+               { 0xe8, 8, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }},
+
+               /*
+                * VBI input window = 720 x 12, output window = 1440 x 12.
+                */
+               { 0x94, 12, { 0x02, 0x00, 0xd0, 0x02, 0x05, 0x00, 0x0c, 0x00,
+                             0xa0, 0x05, 0x0c, 0x00 } },
+
+               /* Inverted VGATE start at line 23, stop after line 263 */
+               { 0x15, 2, { 0x02, 0x12 } },
+
+               /* VBI data slicer 525 lines, line 21 is closed caption */
+               { 0x54, 2, { 0x4d, 0x00 } },
+               { 0x5a, 2, { 0x06, 0x83 } },
+
+               /* PLL2 525 lines, 27 Mhz target clock */
+               { 0xf0, 1, { 0xad } },
+
+               /* Pulse generator 525 lines, 27 Mhz target clock */
+               { 0xf5, 1, { 0xad } }
+       }
+};
+
+static const struct cxm_saa7115_command
+saa7115_scaler_vcd_pal_double_lines = {
+       13,
+       {
+               /*
+                * Input window = 720 x 288, output window = 352 x 288 with
+                * YS extended by 2 as per section 17.4 of the data sheet
+                * and YO accounting for scaler processing triggering at
+                * line 2 and active video starting at line 25 (see section
+                * 8.2 table 8 and section 8.3.1.1 table 11 of the data sheet).
+                * PAL active video should actually start at line 24, however
+                * not all channels / programs do.
+                */
+               { 0xc4, 12, { 0x02, 0x00, 0xd0, 0x02, 0x17, 0x00, 0x22, 0x01,
+                             0x60, 0x01, 0x20, 0x01 } },
+
+               /* Prefiltering and prescaling */
+               { 0xd0, 3, { 0x02, 0x02, 0xaa } },
+
+               /* Brightness, contrast, and saturation */
+               { 0xd4, 3, { 0x80, 0x40, 0x40 } },
+
+               /* Horizontal phase scaling */
+               { 0xd8, 3, { 0x18, 0x04, 0x00 } },
+               { 0xdc, 3, { 0x0c, 0x02, 0x00 } },
+
+               /* Vertical scaling */
+               { 0xe0, 5, { 0x00, 0x04, 0x00, 0x04, 0x00 } },
+
+               /* Vertical phase offsets */
+               { 0xe8, 8, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }},
+
+               /*
+                * VBI input window = 720 x 17, output window = 1440 x 17.
+                */
+               { 0x94, 12, { 0x02, 0x00, 0xd0, 0x02, 0x04, 0x00, 0x11, 0x00,
+                             0xa0, 0x05, 0x11, 0x00 } },
+
+               /* Inverted VGATE start at line 25, stop after line 313 */
+               { 0x15, 2, { 0x37, 0x17 } },
+
+               /* VBI data slicer 625 lines, line 22 is closed caption */
+               { 0x54, 2, { 0xdd, 0x4d } },
+               { 0x5a, 2, { 0x03, 0x03 } },
+
+               /* PLL2 625 lines, 27 Mhz target clock */
+               { 0xf0, 1, { 0xb0 } },
+
+               /* Pulse generator 625 lines, 27 Mhz target clock */
+               { 0xf5, 1, { 0xb0 } }
+       }
+};
+
+static const struct cxm_saa7115_command
+saa7115_scaler_svcd_ntsc = {
+       13,
+       {
+               /*
+                * Input window = 720 x 240, output window = 480 x 240 with
+                * YS extended by 2 as per section 17.4 of the data sheet
+                * and YO accounting for scaler processing triggering at
+                * line 5 and active video starting at line 23 (see section
+                * 8.2 table 8 and section 8.3.1.1 table 11 of the data sheet).
+                * NTSC active video should actually start at line 22, however
+                * not all channels / programs do.
+                */
+               { 0xc4, 12, { 0x02, 0x00, 0xd0, 0x02, 0x12, 0x00, 0xf2, 0x00,
+                             0xe0, 0x01, 0xf0, 0x00 } },
+
+               /* Prefiltering and prescaling */
+               { 0xd0, 3, { 0x01, 0x00, 0x00 } },
+
+               /* Brightness, contrast, and saturation */
+               { 0xd4, 3, { 0x80, 0x40, 0x40 } },
+
+               /* Horizontal phase scaling */
+               { 0xd8, 3, { 0x00, 0x06, 0x00 } },
+               { 0xdc, 3, { 0x00, 0x03, 0x00 } },
+
+               /* Vertical scaling */
+               { 0xe0, 5, { 0x00, 0x04, 0x00, 0x04, 0x00 } },
+
+               /* Vertical phase offsets */
+               { 0xe8, 8, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }},
+
+               /*
+                * VBI input window = 720 x 12, output window = 1440 x 12.
+                */
+               { 0x94, 12, { 0x02, 0x00, 0xd0, 0x02, 0x05, 0x00, 0x0c, 0x00,
+                             0xa0, 0x05, 0x0c, 0x00 } },
+
+               /* Inverted VGATE start at line 23, stop after line 263 */
+               { 0x15, 2, { 0x02, 0x12 } },
+
+               /* VBI data slicer 525 lines, line 21 is closed caption */
+               { 0x54, 2, { 0x4d, 0x00 } },
+               { 0x5a, 2, { 0x06, 0x83 } },
+
+               /* PLL2 525 lines, 27 Mhz target clock */
+               { 0xf0, 1, { 0xad } },
+
+               /* Pulse generator 525 lines, 27 Mhz target clock */
+               { 0xf5, 1, { 0xad } }
+       }
+};
+
+static const struct cxm_saa7115_command
+saa7115_scaler_svcd_pal = {
+       13,
+       {
+               /*
+                * Input window = 720 x 288, output window = 480 x 288 with
+                * YS extended by 2 as per section 17.4 of the data sheet
+                * and YO accounting for scaler processing triggering at
+                * line 2 and active video starting at line 25 (see section
+                * 8.2 table 8 and section 8.3.1.1 table 11 of the data sheet).
+                * PAL active video should actually start at line 24, however
+                * not all channels / programs do.
+                */
+               { 0xc4, 12, { 0x02, 0x00, 0xd0, 0x02, 0x17, 0x00, 0x22, 0x01,
+                             0xe0, 0x01, 0x20, 0x01 } },
+
+               /* Prefiltering and prescaling */
+               { 0xd0, 3, { 0x01, 0x00, 0x00 } },
+
+               /* Brightness, contrast, and saturation */
+               { 0xd4, 3, { 0x80, 0x40, 0x40 } },
+
+               /* Horizontal phase scaling */
+               { 0xd8, 3, { 0x00, 0x06, 0x00 } },
+               { 0xdc, 3, { 0x00, 0x03, 0x00 } },
+
+               /* Vertical scaling */
+               { 0xe0, 5, { 0x00, 0x04, 0x00, 0x04, 0x00 } },
+
+               /* Vertical phase offsets */
+               { 0xe8, 8, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }},
+
+               /*
+                * VBI input window = 720 x 17, output window = 1440 x 17.
+                */
+               { 0x94, 12, { 0x02, 0x00, 0xd0, 0x02, 0x04, 0x00, 0x11, 0x00,
+                             0xa0, 0x05, 0x11, 0x00 } },
+
+               /* Inverted VGATE start at line 25, stop after line 313 */
+               { 0x15, 2, { 0x37, 0x17 } },
+
+               /* VBI data slicer 625 lines, line 22 is closed caption */
+               { 0x54, 2, { 0xdd, 0x4d } },
+               { 0x5a, 2, { 0x03, 0x03 } },
+
+               /* PLL2 625 lines, 27 Mhz target clock */
+               { 0xf0, 1, { 0xb0 } },
+
+               /* Pulse generator 625 lines, 27 Mhz target clock */
+               { 0xf5, 1, { 0xb0 } }
+       }
+};
+
+static const struct cxm_saa7115_command
+saa7115_scaler_dvd_ntsc = {
+       13,
+       {
+               /*
+                * Input window = 720 x 240, output window = 720 x 240 with
+                * YS extended by 2 as per section 17.4 of the data sheet
+                * and YO accounting for scaler processing triggering at
+                * line 5 and active video starting at line 23 (see section
+                * 8.2 table 8 and section 8.3.1.1 table 11 of the data sheet).
+                * NTSC active video should actually start at line 22, however
+                * not all channels / programs do.
+                */
+               { 0xc4, 12, { 0x02, 0x00, 0xd0, 0x02, 0x12, 0x00, 0xf2, 0x00,
+                             0xd0, 0x02, 0xf0, 0x00 } },
+
+               /* Prefiltering and prescaling */
+               { 0xd0, 3, { 0x01, 0x00, 0x00 } },
+
+               /* Brightness, contrast, and saturation */
+               { 0xd4, 3, { 0x80, 0x40, 0x40 } },
+
+               /* Horizontal phase scaling */
+               { 0xd8, 3, { 0x00, 0x04, 0x00 } },
+               { 0xdc, 3, { 0x00, 0x02, 0x00 } },
+
+               /* Vertical scaling */
+               { 0xe0, 5, { 0x00, 0x04, 0x00, 0x04, 0x00 } },
+
+               /* Vertical phase offsets */
+               { 0xe8, 8, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }},
+
+               /*
+                * VBI input window = 720 x 12, output window = 1440 x 12.
+                */
+               { 0x94, 12, { 0x02, 0x00, 0xd0, 0x02, 0x05, 0x00, 0x0c, 0x00,
+                             0xa0, 0x05, 0x0c, 0x00 } },
+
+               /* Inverted VGATE start at line 23, stop after line 263 */
+               { 0x15, 2, { 0x02, 0x12 } },
+
+               /* VBI data slicer 525 lines, line 21 is closed caption */
+               { 0x54, 2, { 0x4d, 0x00 } },
+               { 0x5a, 2, { 0x06, 0x83 } },
+
+               /* PLL2 525 lines, 27 Mhz target clock */
+               { 0xf0, 1, { 0xad } },
+
+               /* Pulse generator 525 lines, 27 Mhz target clock */
+               { 0xf5, 1, { 0xad } }
+       }
+};
+
+static const struct cxm_saa7115_command
+saa7115_scaler_dvd_pal = {
+       13,
+       {
+               /*
+                * Input window = 720 x 288, output window = 720 x 288 with
+                * YS extended by 2 as per section 17.4 of the data sheet
+                * and YO accounting for scaler processing triggering at
+                * line 2 and active video starting at line 25 (see section
+                * 8.2 table 8 and section 8.3.1.1 table 11 of the data sheet).
+                * PAL active video should actually start at line 24, however
+                * not all channels / programs do.
+                */
+               { 0xc4, 12, { 0x02, 0x00, 0xd0, 0x02, 0x17, 0x00, 0x22, 0x01,
+                             0xd0, 0x02, 0x20, 0x01 } },
+
+               /* Prefiltering and prescaling */
+               { 0xd0, 3, { 0x01, 0x00, 0x00 } },
+
+               /* Brightness, contrast, and saturation */
+               { 0xd4, 3, { 0x80, 0x40, 0x40 } },
+
+               /* Horizontal phase scaling */
+               { 0xd8, 3, { 0x00, 0x04, 0x00 } },
+               { 0xdc, 3, { 0x00, 0x02, 0x00 } },
+
+               /* Vertical scaling */
+               { 0xe0, 5, { 0x00, 0x04, 0x00, 0x04, 0x00 } },
+
+               /* Vertical phase offsets */
+               { 0xe8, 8, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }},
+
+               /*
+                * VBI input window = 720 x 17, output window = 1440 x 17.
+                */
+               { 0x94, 12, { 0x02, 0x00, 0xd0, 0x02, 0x04, 0x00, 0x11, 0x00,
+                             0xa0, 0x05, 0x11, 0x00 } },
+
+               /* Inverted VGATE start at line 25, stop after line 313 */
+               { 0x15, 2, { 0x37, 0x17 } },
+
+               /* VBI data slicer 625 lines, line 22 is closed caption */
+               { 0x54, 2, { 0xdd, 0x4d } },
+               { 0x5a, 2, { 0x03, 0x03 } },
+
+               /* PLL2 625 lines, 27 Mhz target clock */
+               { 0xf0, 1, { 0xb0 } },
+
+               /* Pulse generator 625 lines, 27 Mhz target clock */
+               { 0xf5, 1, { 0xb0 } }
+       }
+};
+
+
+static const struct cxm_saa7115_audio_clock
+saa7115_audio_clock[] = {
+       { 44100, 30, &saa7115_audio_clock_44100_ntsc },
+       { 44100, 25, &saa7115_audio_clock_44100_pal },
+       { 48000, 30, &saa7115_audio_clock_48000_ntsc },
+       { 48000, 25, &saa7115_audio_clock_48000_pal }
+};
+
+static const struct cxm_saa7115_scaling
+saa7115_scalings[] = {
+       { 352, 480, 30, &saa7115_scaler_vcd_ntsc_double_lines },
+       { 352, 576, 25, &saa7115_scaler_vcd_pal_double_lines },
+       { 480, 480, 30, &saa7115_scaler_svcd_ntsc },
+       { 480, 576, 25, &saa7115_scaler_svcd_pal },
+       { 720, 480, 30, &saa7115_scaler_dvd_ntsc },
+       { 720, 576, 25, &saa7115_scaler_dvd_pal }
+};
+
+
+/* Reset the SAA7115 chip */
+static int
+cxm_saa7115_reset(device_t iicbus, int i2c_addr)
+{
+       unsigned char msg[2];
+       int sent;
+
+       /* put into reset mode */
+       msg[0] = 0x88;
+       msg[1] = 0x0b;
+
+       if (iicbus_start(iicbus, i2c_addr, CXM_I2C_TIMEOUT) != 0)
+               return -1;
+
+       if (iicbus_write(iicbus, msg, sizeof(msg), &sent, CXM_I2C_TIMEOUT) != 0
+           || sent != sizeof(msg))
+               goto fail;
+
+       iicbus_stop(iicbus);
+
+       /* put back to operational mode */
+       msg[0] = 0x88;
+       msg[1] = 0x2b;
+
+       if (iicbus_start(iicbus, i2c_addr, CXM_I2C_TIMEOUT) != 0)
+               return -1;
+
+       if (iicbus_write(iicbus, msg, sizeof(msg), &sent, CXM_I2C_TIMEOUT) != 0
+           || sent != sizeof(msg))
+               goto fail;
+
+       iicbus_stop(iicbus);
+
+       return 0;
+
+fail:
+       iicbus_stop(iicbus);
+       return -1;
+}
+
+
+/* Read from the SAA7115 registers */
+static int
+cxm_saa7115_read(device_t iicbus, int i2c_addr,
+                 unsigned char addr, char *buf, int len)
+{
+       unsigned char msg[1];
+       int received;
+       int sent;
+
+       msg[0] = addr;
+
+       if (iicbus_start(iicbus, i2c_addr, CXM_I2C_TIMEOUT) != 0)
+               return -1;
+
+       if (iicbus_write(iicbus, msg, sizeof(msg), &sent, CXM_I2C_TIMEOUT) != 0
+           || sent != sizeof(msg))
+               goto fail;
+
+       if (iicbus_repeated_start(iicbus, i2c_addr + 1, CXM_I2C_TIMEOUT) != 0)
+               goto fail;
+
+       if (iicbus_read(iicbus, buf, len, &received, IIC_LAST_READ, 0) != 0)
+               goto fail;
+
+       iicbus_stop(iicbus);
+
+       return received;
+
+fail:
+       iicbus_stop(iicbus);
+       return -1;
+}
+
+
+/* Write to the SAA7115 registers */
+static int
+cxm_saa7115_write(device_t iicbus, int i2c_addr,
+                  unsigned char addr, const char *buf, int len)
+{
+       unsigned char msg[1];
+       int sent;
+
+       msg[0] = addr;
+
+       if (iicbus_start(iicbus, i2c_addr, CXM_I2C_TIMEOUT) != 0)
+               return -1;
+
+       if (iicbus_write(iicbus, msg, sizeof(msg), &sent, CXM_I2C_TIMEOUT) != 0
+           || sent != sizeof(msg))
+               goto fail;
+
+       if (iicbus_write(iicbus, buf, len, &sent, CXM_I2C_TIMEOUT) != 0)
+               goto fail;
+
+       iicbus_stop(iicbus);
+
+       return sent;
+
+fail:
+       iicbus_stop(iicbus);
+       return -1;
+}
+
+
+int
+cxm_saa7115_init(struct cxm_softc *sc)
+{
+       char name[5];
+       unsigned char id[1];
+       unsigned char rev;
+       unsigned int i;
+       unsigned int nsettings;
+       const struct cxm_saa7115_setting *settings;
+
+       if (cxm_saa7115_reset (sc->iicbus, CXM_I2C_SAA7115) < 0)
+               return -1;
+
+       name[4] = '\0';
+       for (i = 0; i < 4; i++) {
+               id[0] = 2 + i;
+
+               if (cxm_saa7115_write(sc->iicbus, CXM_I2C_SAA7115, 0x00,
+                                     id, sizeof(id)) != sizeof(id))
+                       return -1;
+
+               if (cxm_saa7115_read(sc->iicbus, CXM_I2C_SAA7115, 0x00,
+                                    id, sizeof(id)) != sizeof(id))
+                       return -1;
+
+               name[i] = '0' + (id[0] & 0x0f);
+               rev = id[0] >> 4;
+       }
+
+       /*
+        * SAA 7115 is the only video decoder currently supported.
+        */
+
+       nsettings = 0;
+       settings = NULL;
+
+       if (strcmp(name, "7115") == 0) {
+               nsettings = saa7115_init.nsettings;
+               settings = saa7115_init.settings;
+       } else {
+               device_printf(sc->dev, "unknown video decoder SAA%s\n", name);
+               return -1;
+       }
+
+       for (i = 0; i < nsettings; i++)
+               if (cxm_saa7115_write(sc->iicbus, CXM_I2C_SAA7115,
+                                     settings[i].addr,
+                                     settings[i].values, settings[i].nvalues)
+                   != settings[i].nvalues)
+                       return -1;
+
+       if (cxm_saa7115_select_source(sc, cxm_tuner_source) < 0)
+               return -1;
+
+       device_printf(sc->dev, "SAA%s rev %u video decoder\n",
+           name, (unsigned int)rev);
+
+       return 0;
+}
+
+
+int
+cxm_saa7115_mute(struct cxm_softc *sc)
+{
+       unsigned int i;
+       unsigned int nsettings;
+       const struct cxm_saa7115_setting *settings;
+
+       nsettings = saa7115_mute.nsettings;
+       settings = saa7115_mute.settings;
+
+       for (i = 0; i < nsettings; i++)
+               if (cxm_saa7115_write(sc->iicbus, CXM_I2C_SAA7115,
+                                     settings[i].addr,
+                                     settings[i].values, settings[i].nvalues)
+                   != settings[i].nvalues)
+                       return -1;
+
+       return 0;
+}
+
+
+int
+cxm_saa7115_unmute(struct cxm_softc *sc)
+{
+       unsigned int i;
+       unsigned int nsettings;
+       const struct cxm_saa7115_setting *settings;
+
+       nsettings = saa7115_unmute.nsettings;
+       settings = saa7115_unmute.settings;
+
+       for (i = 0; i < nsettings; i++)
+               if (cxm_saa7115_write(sc->iicbus, CXM_I2C_SAA7115,
+                                     settings[i].addr,
+                                     settings[i].values, settings[i].nvalues)
+                   != settings[i].nvalues)
+                       return -1;
+
+       return 0;
+}
+
+
+int
+cxm_saa7115_select_source(struct cxm_softc *sc, enum cxm_source source)
+{
+       unsigned int i;
+       unsigned int nsettings;
+       const struct cxm_saa7115_setting *settings;
+
+       switch (source) {
+       case cxm_fm_source:
+               nsettings = saa7115_select_fm.nsettings;
+               settings = saa7115_select_fm.settings;
+               break;
+
+       case cxm_line_in_source_composite:
+               nsettings = saa7115_select_line_in_composite.nsettings;
+               settings = saa7115_select_line_in_composite.settings;
+               break;
+
+       case cxm_line_in_source_svideo:
+               nsettings = saa7115_select_line_in_svideo.nsettings;
+               settings = saa7115_select_line_in_svideo.settings;
+               break;
+
+       case cxm_tuner_source:
+               nsettings = saa7115_select_tuner.nsettings;
+               settings = saa7115_select_tuner.settings;
+               break;
+
+       default:
+               return -1;
+       }
+
+       for (i = 0; i < nsettings; i++)
+               if (cxm_saa7115_write(sc->iicbus, CXM_I2C_SAA7115,
+                                     settings[i].addr,
+                                     settings[i].values, settings[i].nvalues)
+                   != settings[i].nvalues)
+                       return -1;
+
+       return 0;
+}
+
+
+int
+cxm_saa7115_configure(struct cxm_softc *sc,
+                      unsigned int width, unsigned int height,
+                      unsigned int fps, unsigned int audio_sample_rate)
+{
+       unsigned char power[1];
+       unsigned char task[1];
+       unsigned int i;
+       unsigned int nsettings;
+       const struct cxm_saa7115_setting *settings;
+
+       for (i = 0; NUM_ELEMENTS(saa7115_scalings); i++)
+               if (saa7115_scalings[i].width == width
+                   && saa7115_scalings[i].height == height
+                   && saa7115_scalings[i].fps == fps)
+                       break;
+
+       if (i >= NUM_ELEMENTS(saa7115_scalings))
+               return -1;
+
+       nsettings = saa7115_scalings[i].scaling->nsettings;
+       settings = saa7115_scalings[i].scaling->settings;
+
+       /*
+        * Reset the scaler.
+        */
+
+       if (cxm_saa7115_read(sc->iicbus, CXM_I2C_SAA7115, 0x88,
+                            power, sizeof(power)) != sizeof(power))
+               return -1;
+
+       power[0] &= ~0x20;
+
+       if (cxm_saa7115_write(sc->iicbus, CXM_I2C_SAA7115, 0x88,
+                             power, sizeof(power)) != sizeof(power))
+               return -1;
+
+       /*
+        * Configure the scaler.
+        */
+
+       for (i = 0; i < nsettings; i++)
+               if (cxm_saa7115_write(sc->iicbus, CXM_I2C_SAA7115,
+                                     settings[i].addr,
+                                     settings[i].values, settings[i].nvalues)
+                   != settings[i].nvalues)
+                       return -1;
+
+       /*
+        * Enable task register set A and B.
+        */
+
+       if (cxm_saa7115_read(sc->iicbus, CXM_I2C_SAA7115, 0x80,
+                            task, sizeof(task)) != sizeof(task))
+               return -1;
+
+       task[0] |= 0x30;
+
+       if (cxm_saa7115_write(sc->iicbus, CXM_I2C_SAA7115, 0x80,
+                             task, sizeof(task)) != sizeof(task))
+               return -1;
+
+       /*
+        * Enable the scaler.
+        */
+
+       if (cxm_saa7115_read(sc->iicbus, CXM_I2C_SAA7115, 0x88,
+                            power, sizeof(power)) != sizeof(power))
+               return -1;
+
+       power[0] |= 0x20;
+
+       if (cxm_saa7115_write(sc->iicbus, CXM_I2C_SAA7115, 0x88,
+                             power, sizeof(power)) != sizeof(power))
+               return -1;
+
+       /*
+        * Configure the audio clock.
+        */
+
+       for (i = 0; NUM_ELEMENTS(saa7115_audio_clock); i++)
+               if (saa7115_audio_clock[i].sample_rate == audio_sample_rate
+                   && saa7115_audio_clock[i].fps == fps)
+                       break;
+
+       if (i >= NUM_ELEMENTS(saa7115_audio_clock))
+               return -1;
+
+       nsettings = saa7115_audio_clock[i].clock->nsettings;
+       settings = saa7115_audio_clock[i].clock->settings;
+
+       for (i = 0; i < nsettings; i++)
+               if (cxm_saa7115_write(sc->iicbus, CXM_I2C_SAA7115,
+                                     settings[i].addr,
+                                     settings[i].values, settings[i].nvalues)
+                   != settings[i].nvalues)
+                       return -1;
+
+       return 0;
+}
+
+
+enum cxm_source_format
+cxm_saa7115_detected_format(struct cxm_softc *sc)
+{
+       unsigned char status[2];
+       enum cxm_source_format source_format;
+
+       if (cxm_saa7115_read(sc->iicbus, CXM_I2C_SAA7115, 0x1e,
+                            status, sizeof(status)) != sizeof(status))
+               return cxm_unknown_source_format;
+
+       if (!(status[1] & 0x01)) {
+               device_printf(sc->dev, "video decoder isn't locked\n");
+               return cxm_unknown_source_format;
+       }
+
+       source_format = cxm_unknown_source_format;
+
+       if (!(status[1] & 0x20)) {
+               switch (status[0] & 0x03) {
+               case 0:
+                       source_format = cxm_bw_50hz_source_format;
+                       break;
+
+               case 1:
+                       source_format = cxm_ntsc_50hz_source_format;
+                       break;
+
+               case 2:
+                       source_format = cxm_pal_50hz_source_format;
+                       break;
+
+               case 3:
+                       source_format = cxm_secam_50hz_source_format;
+                       break;
+
+               default:
+                       break;
+               }
+       } else {
+               switch (status[0] & 0x03) {
+               case 0:
+                       source_format = cxm_bw_60hz_source_format;
+                       break;
+
+               case 1:
+                       source_format = cxm_ntsc_60hz_source_format;
+                       break;
+
+               case 2:
+                       source_format = cxm_pal_60hz_source_format;
+                       break;
+
+               default:
+                       break;
+               }
+       }
+
+       return source_format;
+}
+
+
+int
+cxm_saa7115_detected_fps(struct cxm_softc *sc)
+{
+       unsigned char status[1];
+
+       if (cxm_saa7115_read(sc->iicbus, CXM_I2C_SAA7115, 0x1f,
+                            status, sizeof(status)) != sizeof(status))
+               return -1;
+
+       if (!(status[0] & 0x01)) {
+               device_printf(sc->dev, "video decoder isn't locked\n");
+               return -1;
+       }
+
+       return (status[0] & 0x20) ? 30 : 25;
+}
+
+
+int
+cxm_saa7115_get_brightness(struct cxm_softc *sc)
+{
+       unsigned char brightness;
+
+       /*
+        * Brightness is treated as an unsigned value by the decoder.
+        * 0 = dark, 128 = ITU level, 255 = bright
+        */
+       if (cxm_saa7115_read(sc->iicbus, CXM_I2C_SAA7115, 0x0a,
+                            &brightness, sizeof(brightness))
+           != sizeof(brightness))
+               return -1;
+
+       return brightness;
+}
+
+
+int
+cxm_saa7115_set_brightness(struct cxm_softc *sc, unsigned char brightness)
+{
+
+       /*
+        * Brightness is treated as an unsigned value by the decoder.
+        * 0 = dark, 128 = ITU level, 255 = bright
+        */
+       if (cxm_saa7115_write(sc->iicbus, CXM_I2C_SAA7115, 0x0a,
+                             &brightness, sizeof(brightness))
+           != sizeof(brightness))
+               return -1;
+
+       return 0;
+}
+
+
+int
+cxm_saa7115_get_chroma_saturation(struct cxm_softc *sc)
+{
+       unsigned char chroma_saturation;
+
+       /*
+        * Chroma saturation is treated as a signed value by the decoder.
+        * -128 = -2.0 (inverse chrominance), -64 = 1.0 (inverse chrominance),
+        * 0 = 0 (color off), 64 = 1.0 (ITU level), 127 = 1.984 (maximum)
+        */
+       if (cxm_saa7115_read(sc->iicbus, CXM_I2C_SAA7115, 0x0c,
+                            &chroma_saturation, sizeof(chroma_saturation))
+           != sizeof(chroma_saturation))
+               return -1;
+
+       return chroma_saturation;
+}
+
+
+int
+cxm_saa7115_set_chroma_saturation(struct cxm_softc *sc,
+                                  unsigned char chroma_saturation)
+{
+
+       /*
+        * Chroma saturation is treated as a signed value by the decoder.
+        * -128 = -2.0 (inverse chrominance), -64 = 1.0 (inverse chrominance),
+        * 0 = 0 (color off), 64 = 1.0 (ITU level), 127 = 1.984 (maximum)
+        */
+       if (cxm_saa7115_write(sc->iicbus, CXM_I2C_SAA7115, 0x0c,
+                             &chroma_saturation, sizeof(chroma_saturation))
+           != sizeof(chroma_saturation))
+               return -1;
+
+       return 0;
+}
+
+
+int
+cxm_saa7115_get_contrast(struct cxm_softc *sc)
+{
+       unsigned char contrast;
+
+       /*
+        * Contrast is treated as a signed value by the decoder.
+        * -128 = -2.0 (inverse luminance), -64 = 1.0 (inverse luminance),
+        * 0 = 0 (luminance off), 64 = 1.0, 68 = 1.063 (ITU level),
+        * 127 = 1.984 (maximum)
+        */
+       if (cxm_saa7115_read(sc->iicbus, CXM_I2C_SAA7115, 0x0b,
+                            &contrast, sizeof(contrast)) != sizeof(contrast))
+               return -1;
+
+       return contrast;
+}
+
+
+int
+cxm_saa7115_set_contrast(struct cxm_softc *sc, unsigned char contrast)
+{
+
+       /*
+        * Contrast is treated as a signed value by the decoder.
+        * -128 = -2.0 (inverse luminance), -64 = 1.0 (inverse luminance),
+        * 0 = 0 (luminance off), 64 = 1.0, 68 = 1.063 (ITU level),
+        * 127 = 1.984 (maximum)
+        */
+       if (cxm_saa7115_write(sc->iicbus, CXM_I2C_SAA7115, 0x0b,
+                             &contrast, sizeof(contrast)) != sizeof(contrast))
+               return -1;
+
+       return 0;
+}
+
+
+int
+cxm_saa7115_get_hue(struct cxm_softc *sc)
+{
+       unsigned char hue;
+
+       /*
+        * Hue is treated as a signed value by the decoder.
+        * -128 = -180.0, 0 = 0.0, 127 = +178.6
+        */
+       if (cxm_saa7115_read(sc->iicbus, CXM_I2C_SAA7115, 0x0d,
+                            &hue, sizeof(hue))
+           != sizeof(hue))
+               return -1;
+
+       return hue;
+}
+
+
+int
+cxm_saa7115_set_hue(struct cxm_softc *sc, unsigned char hue)
+{
+
+       /*
+        * Hue is treated as a signed value by the decoder.
+        * -128 = -180.0, 0 = 0.0, 127 = +178.6
+        */
+       if (cxm_saa7115_write(sc->iicbus, CXM_I2C_SAA7115, 0x0d,
+                             &hue, sizeof(hue))
+           != sizeof(hue))
+               return -1;
+
+       return 0;
+}
+
+
+int
+cxm_saa7115_is_locked(struct cxm_softc *sc)
+{
+       unsigned char status[1];
+
+       if (cxm_saa7115_read(sc->iicbus, CXM_I2C_SAA7115, 0x1f,
+                            status, sizeof(status)) != sizeof(status))
+               return -1;
+
+       return (status[0] & 0x01) ? 1 : 0;
+}
+
+
+int
+cxm_saa7115_wait_for_lock(struct cxm_softc *sc)
+{
+       unsigned int i;
+
+       /*
+        * Section 2.7 of the data sheet states:
+        *
+        *   Ultra-fast frame lock (almost 1 field)
+        *
+        * so hopefully 500 ms is enough (the lock
+        * sometimes takes a long time to occur ...
+        * possibly due to the time it takes to
+        * autodetect the format).
+        */
+
+       for (i = 0; i < 10; i++) {
+
+               /*
+                * The input may have just changed (prior to
+                * cxm_saa7115_wait_for_lock) so start with
+                * the delay to give the video decoder a
+                * chance to update its status.
+                */
+
+               tsleep(&sc->iicbus, 0, "video", hz / 20);
+
+               switch (cxm_saa7115_is_locked(sc)) {
+               case 1:
+                       return 1;
+
+               case 0:
+                       break;
+
+               default:
+                       return -1;
+               }
+       }
+
+       device_printf(sc->dev, "video decoder failed to lock\n");
+
+       return 0;
+}
diff --git a/sys/dev/video/cxm/cxm_tuner.c b/sys/dev/video/cxm/cxm_tuner.c
new file mode 100644 (file)
index 0000000..80a590d
--- /dev/null
@@ -0,0 +1,969 @@
+/*
+ * Copyright (c) 2003, 2004, 2005
+ *     John Wehle <john@feith.com>.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *     This product includes software developed by John Wehle.
+ * 4. The name of the author may not be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * Tuner routines for the Conexant MPEG-2 Codec driver.
+ *
+ * Ideally these routines should be implemented as a separate
+ * driver which has a generic tuner interface so that it's
+ * not necessary for each multimedia driver to re-invent the
+ * wheel.
+ */
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/conf.h>
+#include <sys/uio.h>
+#include <sys/kernel.h>
+#include <sys/poll.h>
+#include <sys/select.h>
+#include <sys/resource.h>
+#include <sys/bus.h>
+#include <sys/rman.h>
+
+#include <machine/clock.h>
+
+#include <dev/video/meteor/ioctl_meteor.h>
+#include <dev/video/bktr/ioctl_bt848.h>
+
+#include <dev/video/cxm/cxm.h>
+
+#include <bus/iicbus/iiconf.h>
+#include <bus/iicbus/iicbus.h>
+
+#include "iicbb_if.h"
+
+
+/*
+ * Channel mappings derived from
+ * http://developer.apple.com/technotes/tn/tn1012.html
+ *
+ * B/G Cable mapping based the bktr Western European mapping
+ */
+
+static const struct cxm_tuner_channels
+us_air_channels = {
+       "US Broadcast",
+       CHNLSET_NABCST,
+       CXM_TUNER_TV_SYSTEM_MN,
+       2,
+       69,
+       45750,
+       { { 14, 471250, 6000 },
+         { 7, 175250, 6000 },
+         { 5, 77250, 6000 },
+         { 2, 55250, 6000 } }
+};
+
+static const struct cxm_tuner_channels
+us_cable_channels = {
+       "US Cable",
+       CHNLSET_CABLEIRC,
+       CXM_TUNER_TV_SYSTEM_MN,
+       1,
+       125,
+       45750,
+       { { 100, 649250, 6000 },
+         { 95, 91250, 6000 },
+         { 23, 217250, 6000 },
+         { 14, 121250, 6000 },
+         { 7, 175250, 6000 },
+         { 5, 77250, 6000 },
+         { 2, 55250, 6000 },
+         { 1, 73250, 6000 } }
+};
+
+static const struct cxm_tuner_channels
+bg_air_channels = {
+       "B/G Broadcast",
+       0,
+       CXM_TUNER_TV_SYSTEM_BG,
+       2,
+       89,
+       38900,
+       { { 82, 175250, 7000 },
+         { 80, 55250, 7000 },
+         { 79, 217250, 7000 },
+         { 77, 209250, 7000 },
+         { 76, 138250, 9000 },
+         { 75, 102250, 9000 },
+         { 73, 86250, 9000 },
+         { 72, 64250, 7500 },
+         { 70, 49750, 7500 },
+         { 21, 471250, 8000 },
+         { 20, 210250, 8500 },
+         { 18, 192750, 8500 },
+         { 16, 175250, 8000 },
+         { 15, 82250, 8500 },
+         { 13, 53750, 8500 },
+         { 5, 175250, 7000 },
+         { 2, 48250, 7000 } }
+};
+
+static const struct cxm_tuner_channels
+bg_cable_channels = {
+       "B/G Cable",
+       CHNLSET_WEUROPE,
+       CXM_TUNER_TV_SYSTEM_BG,
+       2,
+       120,
+       38900,
+       { { 100, 303250, 8000 },
+         { 90, 231250, 7000 },
+         { 80, 105250, 7000 },
+         { 79, 0, 0 },
+         { 78, 0, 0 },
+         { 77, 0, 0 },
+         { 74, 69250, 7000 },
+         { 73, 0, 0 },
+         { 70, 45750, 8000 },
+         { 21, 471250, 8000 },
+         { 20, 210250, 8500 },
+         { 18, 192750, 8500 },
+         { 16, 175250, 8000 },
+         { 15, 82250, 8500 },
+         { 13, 53750, 8500 },
+         { 5, 175250, 7000 },
+         { 2, 48250, 7000 } }
+};
+
+static const struct cxm_tuner_channels
+bg_australia_channels = {
+       "B/G Australia",
+       CHNLSET_AUSTRALIA,
+       CXM_TUNER_TV_SYSTEM_BG,
+       1,
+       83,
+       38900,
+       { { 28, 527250, 7000 },
+         { 10, 209250, 7000 },
+         { 6, 175250, 7000 },
+         { 4, 95250, 7000 },
+         { 3, 86250, 7000 },
+         { 1, 57250, 7000 } }
+};
+
+static const struct cxm_tuner_channels
+i_air_channels = {
+       "I Broadcast",
+       0,
+       CXM_TUNER_TV_SYSTEM_I,
+       1,
+       83,
+       38900,
+       { { 75, 179750, 5000 },
+         { 71, 51750, 5000 },
+         { 70, 45000, 5000 },
+         { 21, 471250, 8000 },
+         { 20, 0, 0 },
+         { 19, 0, 0 },
+         { 18, 0, 0 },
+         { 17, 0, 0 },
+         { 16, 0, 0 },
+         { 15, 0, 0 },
+         { 14, 0, 0 },
+         { 13, 247250, 8000 },
+         { 12, 0, 0 },
+         { 4, 175250, 8000 },
+         { 1, 45750, 8000 } }
+};
+
+static const struct cxm_tuner_channels
+l_air_channels = {
+       "L Broadcast",
+       CHNLSET_FRANCE,
+       CXM_TUNER_TV_SYSTEM_L,
+       1,
+       69,
+       38900,
+       { { 21, 471250, 8000 },
+         { 20, 0, 0 },
+         { 19, 0, 0 },
+         { 18, 0, 0 },
+         { 17, 0, 0 },
+         { 16, 0, 0 },
+         { 15, 0, 0 },
+         { 14, 0, 0 },
+         { 8, 176000, 8000 },
+         { 5, 57250, 3250 },
+         { 4, 55750, 1500 },
+         { 3, 54000, 1750 },
+         { 2, 49250, 4750 },
+         { 1, 47750, 1500 } }
+};
+
+static const struct cxm_tuner_channels
+japan_air_channels = {
+       "Japan Broadcast",
+       CHNLSET_JPNBCST,
+       CXM_TUNER_TV_SYSTEM_MN,
+       1,
+       62,
+       45750,
+       { { 13, 471250, 6000 },
+         { 8, 193250, 6000 },
+         { 4, 171250, 6000 },
+         { 1, 91250, 6000 } }
+};
+
+static const struct cxm_tuner_channels
+japan_cable_channels = {
+       "Japan Cable",
+       CHNLSET_JPNCABLE,
+       CXM_TUNER_TV_SYSTEM_MN,
+       1,
+       63,
+       45750,
+       { { 23, 223250, 6000 },
+         { 22, 165250, 6000 },
+         { 13, 109250, 6000 },
+         { 8, 193250, 6000 },
+         { 4, 171250, 6000 },
+         { 1, 91250, 6000 } }
+};
+
+
+static const struct cxm_tuner_channels
+*channel_sets[] = {
+       &us_air_channels,
+       &us_cable_channels,
+       &bg_air_channels,
+       &bg_cable_channels,
+       &bg_australia_channels,
+       &i_air_channels,
+       &l_air_channels,
+       &japan_air_channels,
+       &japan_cable_channels
+};
+
+
+const struct cxm_tuner
+cxm_tuners[CXM_TUNER_TYPES] = {
+       { "Philips FI1216 MK2",
+               { CXM_TUNER_TV_SYSTEM_BG, cxm_none_system_code_style },
+               48250, 855250,
+               { { 450000, { 0x8e, 0x30 } },
+                 { 170000, { 0x8e, 0x90 } },
+                 { 48250, { 0x8e, 0xa0 } } },
+               0, 0,
+               { 0 },
+               &bg_air_channels },
+       { "Philips FM1216",
+               { CXM_TUNER_TV_SYSTEM_BG, cxm_none_system_code_style },
+               48250, 855250,
+               { { 450000, { 0xce, 0x30 } },
+                 { 170000, { 0xce, 0x90 } },
+                 { 48250, { 0xce, 0xa0 } } },
+               87500, 108000,
+               { 87500, { 0x88, 0xa5 } },
+               &bg_air_channels },
+       { "Philips FQ1216ME",
+               { CXM_TUNER_TV_SYSTEM_BG | CXM_TUNER_TV_SYSTEM_DK
+                 | CXM_TUNER_TV_SYSTEM_I
+                 | CXM_TUNER_TV_SYSTEM_L | CXM_TUNER_TV_SYSTEM_L_PRIME,
+                 cxm_port_system_code_style,
+                 { { CXM_TUNER_TV_SYSTEM_BG,      { 0x09 } },
+                   { CXM_TUNER_TV_SYSTEM_DK,      { 0x09 } },
+                   { CXM_TUNER_TV_SYSTEM_I,       { 0x01 } },
+                   { CXM_TUNER_TV_SYSTEM_L,       { 0x0b } },
+                   { CXM_TUNER_TV_SYSTEM_L_PRIME, { 0x0a } } } },
+               48250, 855250,
+               { { 450000, { 0x8e, 0x30 } },
+                 { 170000, { 0x8e, 0x90 } },
+                 { 48250, { 0x8e, 0xa0 } } },
+               0, 0,
+               { 0 },
+               &l_air_channels },
+       { "Philips FQ1216ME MK3",
+               { CXM_TUNER_TV_SYSTEM_BG | CXM_TUNER_TV_SYSTEM_DK
+                 | CXM_TUNER_TV_SYSTEM_I
+                 | CXM_TUNER_TV_SYSTEM_L | CXM_TUNER_TV_SYSTEM_L_PRIME,
+                 cxm_if_system_with_aux_code_style,
+                 { { CXM_TUNER_TV_SYSTEM_BG,      { 0x16, 0x70, 0x49, 0x40 }},
+                   { CXM_TUNER_TV_SYSTEM_DK,      { 0x16, 0x70, 0x4b, 0x40 }},
+                   { CXM_TUNER_TV_SYSTEM_I,       { 0x16, 0x70, 0x4a, 0x40 }},
+                   { CXM_TUNER_TV_SYSTEM_L,       { 0x06, 0x50, 0x4b, 0x50 }},
+                   { CXM_TUNER_TV_SYSTEM_L_PRIME, { 0x86, 0x50, 0x53, 0x50 }}
+                   } },
+               48250, 863250,
+               { { 442000, { 0xce, 0x04 } },
+                 { 160000, { 0xce, 0x02 } },
+                 { 48250, { 0xce, 0x01 } } },
+               0, 0,
+               { 0 },
+               &l_air_channels },
+       { "Philips FM1216ME MK3",
+               { CXM_TUNER_TV_SYSTEM_BG | CXM_TUNER_TV_SYSTEM_DK
+                 | CXM_TUNER_TV_SYSTEM_I
+                 | CXM_TUNER_TV_SYSTEM_L | CXM_TUNER_TV_SYSTEM_L_PRIME,
+                 cxm_if_system_with_aux_code_style,
+                 { { CXM_TUNER_TV_SYSTEM_BG,      { 0x16, 0x70, 0x49, 0x40 }},
+                   { CXM_TUNER_TV_SYSTEM_DK,      { 0x16, 0x70, 0x4b, 0x40 }},
+                   { CXM_TUNER_TV_SYSTEM_I,       { 0x16, 0x70, 0x4a, 0x40 }},
+                   { CXM_TUNER_TV_SYSTEM_L,       { 0x06, 0x50, 0x4b, 0x50 }},
+                   { CXM_TUNER_TV_SYSTEM_L_PRIME, { 0x86, 0x50, 0x53, 0x50 }},
+                   { CXM_TUNER_FM_SYSTEM,         { 0x0a, 0x90, 0x20, 0x40 }}
+                   } },
+               48250, 863250,
+               { { 442000, { 0xce, 0x04 } },
+                 { 160000, { 0xce, 0x02 } },
+                 { 48250, { 0xce, 0x01 } } },
+               87500, 108000,
+               { 87500, { 0x88, 0x19 } },
+               &l_air_channels },
+       { "Philips FI1236 MK2",
+               { CXM_TUNER_TV_SYSTEM_MN, cxm_none_system_code_style },
+               55250, 801250,
+               { { 454000, { 0x8e, 0x30 } },
+                 { 160000, { 0x8e, 0x90 } },
+                 { 55250, { 0x8e, 0xa0 } } },
+               0, 0,
+               { 0 },
+               &us_cable_channels },
+       { "Philips FM1236",
+               { CXM_TUNER_TV_SYSTEM_MN, cxm_none_system_code_style },
+               55250, 801250,
+               { { 454000, { 0xce, 0x30 } },
+                 { 160000, { 0xce, 0x90 } },
+                 { 55250, { 0xce, 0xa0 } } },
+               76000, 108000,
+               { 76000, { 0x88, 0xa5 } },
+               &us_cable_channels },
+       { "Philips FI1246 MK2",
+               { CXM_TUNER_TV_SYSTEM_I, cxm_none_system_code_style },
+               45750, 855250,
+               { { 450000, { 0x8e, 0x30 } },
+                 { 170000, { 0x8e, 0x90 } },
+                 { 45750, { 0x8e, 0xa0 } } },
+               0, 0,
+               { 0 },
+               &i_air_channels },
+       { "Philips FM1246",
+               { CXM_TUNER_TV_SYSTEM_I, cxm_none_system_code_style },
+               45750, 855250,
+               { { 450000, { 0xce, 0x30 } },
+                 { 170000, { 0xce, 0x90 } },
+                 { 45750, { 0xce, 0xa0 } } },
+               87500, 108000,
+               { 87500, { 0x88, 0xa5 } },
+               &i_air_channels },
+       { "Temic 4006 FH5",
+               { CXM_TUNER_TV_SYSTEM_BG, cxm_none_system_code_style },
+               48250, 855250,
+               { { 454000, { 0x8e, 0x30 } },
+                 { 169000, { 0x8e, 0x90 } },
+                 { 48250, { 0x8e, 0xa0 } } },
+               0, 0,
+               { 0 },
+               &bg_air_channels },
+       { "Temic 4009 FR5",
+               { CXM_TUNER_TV_SYSTEM_BG, cxm_none_system_code_style },
+               48250, 855250,
+               { { 464000, { 0x8e, 0x30 } },
+                 { 141000, { 0x8e, 0x90 } },
+                 { 48250, { 0x8e, 0xa0 } } },
+               87500, 108100,
+               { 87500, { 0x88, 0xa5 } },
+               &bg_air_channels },
+       { "Temic 4036 FY5",
+               { CXM_TUNER_TV_SYSTEM_MN, cxm_none_system_code_style },
+               55250, 801250,
+               { { 453000, { 0x8e, 0x30 } },
+                 { 158000, { 0x8e, 0x90 } },
+                 { 55250, { 0x8e, 0xa0 } } },
+               0, 0,
+               { 0 },
+               &us_cable_channels },
+       { "Temic 4039 FR5",
+               { CXM_TUNER_TV_SYSTEM_MN, cxm_none_system_code_style },
+               55250, 801250,
+               { { 453000, { 0x8e, 0x30 } },
+                 { 158000, { 0x8e, 0x90 } },
+                 { 55250, { 0x8e, 0xa0 } } },
+               75900, 108100,
+               { 75900, { 0x88, 0xa5 } },
+               &us_cable_channels },
+       { "Temic 4066 FY5",
+               { CXM_TUNER_TV_SYSTEM_I, cxm_none_system_code_style },
+               45750, 855250,
+               { { 454000, { 0x8e, 0x30 } },
+                 { 169000, { 0x8e, 0x90 } },
+                 { 45750, { 0x8e, 0xa0 } } },
+               0, 0,
+               { 0 },
+               &i_air_channels },
+       { "LG Innotek TPI8PSB11D",
+               { CXM_TUNER_TV_SYSTEM_BG, cxm_none_system_code_style },
+               48250, 855250,
+               { { 450000, { 0x8e, 0x30 } },
+                 { 170000, { 0x8e, 0x90 } },
+                 { 48250, { 0x8e, 0xa0 } } },
+               0, 0,
+               { 0 },
+               &bg_air_channels },
+       { "LG Innotek TPI8PSB01N",
+               { CXM_TUNER_TV_SYSTEM_BG, cxm_none_system_code_style },
+               48250, 855250,
+               { { 450000, { 0x8e, 0x30 } },
+                 { 170000, { 0x8e, 0x90 } },
+                 { 48250, { 0x8e, 0xa0 } } },
+               87500, 108000,
+               { 87500, { 0x88, 0xa5 } },
+               &bg_air_channels },
+       { "LG Innotek TAPC-H701F",
+               { CXM_TUNER_TV_SYSTEM_MN, cxm_none_system_code_style },
+               55250, 801250,
+               { { 450000, { 0xce, 0x08 } },
+                 { 165000, { 0xce, 0x02 } },
+                 { 55250, { 0xce, 0x01 } } },
+               0, 0,
+               { 0 },
+               &us_cable_channels },
+       { "LG Innotek TAPC-H001F",
+               { CXM_TUNER_TV_SYSTEM_MN, cxm_none_system_code_style },
+               55250, 801250,
+               { { 450000, { 0xce, 0x08 } },
+                 { 165000, { 0xce, 0x02 } },
+                 { 55250, { 0xce, 0x01 } } },
+               76000, 108000,
+               { 76000, { 0x88, 0x04 } },
+               &us_cable_channels },
+       { "LG Innotek TAPE-H001F",
+               { CXM_TUNER_TV_SYSTEM_MN,
+                 cxm_if_system_with_aux_code_style,
+                 { { CXM_TUNER_TV_SYSTEM_MN,      { 0x16, 0x30, 0x44, 0x30 }},
+                   { CXM_TUNER_FM_SYSTEM,         { 0x0a, 0x90, 0x20, 0x30 }}
+                   } },
+               48250, 801250,
+               { { 442000, { 0xce, 0x04 } },
+                 { 160000, { 0xce, 0x02 } },
+                 { 48250, { 0xce, 0x01 } } },
+               88000, 108000,
+               { 88000, { 0x88, 0x19 } },
+               &us_cable_channels },
+       { "Microtune 4049 FM5",
+               { CXM_TUNER_TV_SYSTEM_BG | CXM_TUNER_TV_SYSTEM_DK
+                 | CXM_TUNER_TV_SYSTEM_I
+                 | CXM_TUNER_TV_SYSTEM_L | CXM_TUNER_TV_SYSTEM_L_PRIME,
+                 cxm_if_system_code_style,
+                 { { CXM_TUNER_TV_SYSTEM_BG,      { 0xd4, 0x70, 0x09 } },
+                   { CXM_TUNER_TV_SYSTEM_DK,      { 0xd4, 0x70, 0x0b } },
+                   { CXM_TUNER_TV_SYSTEM_I,       { 0xd4, 0x70, 0x0a } },
+                   { CXM_TUNER_TV_SYSTEM_L,       { 0xc4, 0x10, 0x0b } },
+                   { CXM_TUNER_TV_SYSTEM_L_PRIME, { 0x84, 0x10, 0x13 } },
+                   { CXM_TUNER_FM_SYSTEM,         { 0xdc, 0x10, 0x1d } } } },
+               45750, 855250,
+               { { 464000, { 0x8e, 0x30 } },
+                 { 141000, { 0x8e, 0x90 } },
+                 { 45750, { 0x8e, 0xa0 } } },
+               87500, 108100,
+               { 87500, { 0x88, 0xa4 } },
+               &l_air_channels },
+       { "TCL 2002N-6A",
+               { CXM_TUNER_TV_SYSTEM_MN, cxm_none_system_code_style },
+               55250, 801250,
+               { { 446000, { 0x8e, 0x08 } },
+                 { 170000, { 0x8e, 0x02 } },
+                 { 55250, { 0x8e, 0x01 } } },
+               0, 0,
+               { 0 },
+               &us_cable_channels },
+};
+
+
+/* Read from the tuner registers */
+static int
+cxm_tuner_read(device_t iicbus, int i2c_addr, char *buf, int len)
+{
+       int received;
+
+       if (iicbus_start(iicbus, i2c_addr + 1, CXM_I2C_TIMEOUT) != 0)
+               return -1;
+
+       if (iicbus_read(iicbus, buf, len, &received, IIC_LAST_READ, 0) != 0)
+               goto fail;
+
+       iicbus_stop(iicbus);
+
+       return received;
+
+fail:
+       iicbus_stop(iicbus);
+       return -1;
+}
+
+
+/* Write to the tuner registers */
+static int
+cxm_tuner_write(device_t iicbus, int i2c_addr, const char *buf, int len)
+{
+       int sent;
+
+       if (iicbus_start(iicbus, i2c_addr, CXM_I2C_TIMEOUT) != 0)
+               return -1;
+
+       if (iicbus_write(iicbus, buf, len, &sent, CXM_I2C_TIMEOUT) != 0)
+               goto fail;
+
+       iicbus_stop(iicbus);
+
+       return sent;
+
+fail:
+       iicbus_stop(iicbus);
+       return -1;
+}
+
+
+int
+cxm_tuner_init(struct cxm_softc *sc)
+{
+       unsigned char status;
+       int tuner_type;
+
+       if (cxm_eeprom_init(sc) < 0)
+               return -1;
+
+       tuner_type = cxm_eeprom_tuner_type(sc);
+
+       if (tuner_type < 0 || tuner_type >= NUM_ELEMENTS(cxm_tuners))
+               return -1;
+
+       sc->tuner = &cxm_tuners[tuner_type];
+       sc->tuner_channels = sc->tuner->default_channels;
+       sc->tuner_freq = 0;
+
+       if (cxm_tuner_read(sc->iicbus, CXM_I2C_TUNER, &status, sizeof(status))
+           != sizeof(status))
+               return -1;
+
+       if (cxm_tuner_select_channel(sc, 4) < 0)
+               return -1;
+
+       device_printf(sc->dev, "%s tuner\n", sc->tuner->name);
+
+       return 0;
+}
+
+
+int
+cxm_tuner_select_channel_set(struct cxm_softc *sc, unsigned int channel_set)
+{
+       unsigned int i;
+
+       if (!channel_set) {
+               sc->tuner_channels = sc->tuner->default_channels;
+               return 0;
+       }
+
+       for (i = 0; i < NUM_ELEMENTS(channel_sets); i++)
+               if (channel_sets[i]->chnlset == channel_set)
+                       break;
+
+       if (i >= NUM_ELEMENTS(channel_sets))
+               return -1;
+
+       if (!(sc->tuner->systems.supported & channel_sets[i]->system))
+               return -1;
+
+       sc->tuner_channels = channel_sets[i];
+
+       return 0;
+}
+
+
+unsigned int
+cxm_tuner_selected_channel_set(struct cxm_softc *sc)
+{
+       return sc->tuner_channels->chnlset;
+}
+
+
+int
+cxm_tuner_select_frequency(struct cxm_softc *sc,
+                           enum cxm_tuner_freq_type freq_type,
+                           unsigned long freq)
+{
+       unsigned char msg[5];
+       unsigned char aux;
+       unsigned char pb;
+       unsigned int i;
+       unsigned int system;
+       unsigned int tuner_msg_len;
+       unsigned long N;
+       unsigned long osc_freq;
+       const struct cxm_tuner_band_code *band_code;
+       const struct cxm_tuner_system_code *system_code;
+
+       N = 0;
+       aux = 0;
+       pb = 0;
+
+       system_code = NULL;
+
+       tuner_msg_len = 4;
+
+       if (sc->tuner->systems.code_style != cxm_none_system_code_style) {
+               system = freq_type == cxm_tuner_fm_freq_type
+                        ? CXM_TUNER_FM_SYSTEM : sc->tuner_channels->system;
+
+               for (i = 0; i < NUM_ELEMENTS (sc->tuner->systems.codes); i++)
+                       if (sc->tuner->systems.codes[i].system & system)
+                               break;
+
+               if (i >= NUM_ELEMENTS (sc->tuner->systems.codes))
+                       return -1;
+
+               switch (sc->tuner->systems.code_style) {
+               case cxm_port_system_code_style:
+                       pb = sc->tuner->systems.codes[i].codes[0];
+                       break;
+
+               case cxm_if_system_with_aux_code_style:
+                       aux = sc->tuner->systems.codes[i].codes[3];
+                       tuner_msg_len = 5;
+
+                       /* FALLTHROUGH */
+
+               case cxm_if_system_code_style:
+                       system_code = &sc->tuner->systems.codes[i];
+                       break;
+
+               default:
+                       return -1;
+               }
+       }
+
+       switch (freq_type) {
+       case cxm_tuner_fm_freq_type:
+
+               if (freq < sc->tuner->fm_min_freq
+                   || freq > sc->tuner->fm_max_freq
+                   || !sc->tuner->fm_band_code.freq)
+                       return -1;
+
+               /*
+                * The Philips FM1216ME MK3 data sheet recommends
+                * first setting the tuner to TV mode at a high
+                * frequency (e.g. 150 MHz), then selecting the
+                * desired FM station in order to ensure that the
+                * tuning voltage does not stay locked at 0V.
+                */
+
+               if (cxm_tuner_select_frequency(sc, cxm_tuner_tv_freq_type,
+                                              150000) < 0)
+                       return -1;
+
+               /*
+                * N = { fRF(pc) + fIF(pc) } / step_size
+                *
+                * fRF = RF frequency in MHz
+                * fIF = Intermediate frequency in MHz (FM = 10.70 MHz)
+                * step_size = Step size in MHz (FM = 50 kHz)
+                */
+
+               osc_freq = freq + 10700;
+
+               N = (20 * osc_freq) / 1000;
+
+               msg[0] = (unsigned char)(N >> 8);
+               msg[1] = (unsigned char)N;
+               msg[2] = sc->tuner->fm_band_code.codes[0];
+               msg[3] = sc->tuner->fm_band_code.codes[1] | pb;
+               msg[4] = aux;
+               break;
+
+       case cxm_tuner_tv_freq_type:
+
+               if (freq < sc->tuner->min_freq
+                   || freq > sc->tuner->max_freq)
+                       return -1;
+
+               /*
+                * N = 16 * { fRF(pc) + fIF(pc) }
+                *
+                * fRF = RF frequency in MHz
+                * fIF = Intermediate frequency in MHz
+                *
+                * The data sheet doesn't state it, however
+                * this is probably the same equation as
+                * FM simply with 62.5 kHz as the step size.
+                */
+
+               osc_freq = freq + sc->tuner_channels->if_freq;
+
+               N = (16 * osc_freq) / 1000;
+
+               for (band_code = sc->tuner->band_codes;
+                    band_code->freq > freq; band_code++)
+                       ;
+
+               if (freq >= sc->tuner_freq) {
+                       msg[0] = (unsigned char)(N >> 8);
+                       msg[1] = (unsigned char)N;
+                       msg[2] = band_code->codes[0];
+                       msg[3] = band_code->codes[1] | pb;
+               } else {
+                       msg[0] = band_code->codes[0];
+                       msg[1] = band_code->codes[1] | pb;
+                       msg[2] = (unsigned char)(N >> 8);
+                       msg[3] = (unsigned char)N;
+               }
+               msg[4] = aux;
+               break;
+
+       default:
+               return -1;
+       }
+
+       if (N > 32767)
+               return -1;
+
+       if (cxm_tuner_write(sc->iicbus, CXM_I2C_TUNER, msg, tuner_msg_len)
+                           != tuner_msg_len)
+               return -1;
+
+       /*
+        * Program the IF processing after the tuner since some tuners
+        * use the control byte to set the address of the IF.
+        */
+
+       if (system_code) {
+               msg[0] = 0x00;
+               msg[1] = system_code->codes[0];
+               msg[2] = system_code->codes[1];
+               msg[3] = system_code->codes[2];
+
+               if (cxm_tuner_write(sc->iicbus, CXM_I2C_TUNER_IF, msg, 4) != 4)
+                       return -1;
+       }
+
+       sc->tuner_freq = freq;
+
+       return 0;
+}
+
+
+int
+cxm_tuner_select_channel(struct cxm_softc *sc, unsigned int channel)
+{
+       unsigned long freq;
+       const struct cxm_tuner_channel_assignment *assignments;
+       const struct cxm_tuner_channels *channels;
+
+       channels = sc->tuner_channels;
+
+       if (!channels
+           || channel < channels->min_channel
+           || channel > channels->max_channel)
+               return -1;
+
+       for (assignments = channels->assignments;
+            assignments->channel > channel; assignments++)
+               ;
+
+       if (!assignments->freq)
+               return -1;
+
+       freq = assignments->freq
+              + (channel - assignments->channel) * assignments->step;
+
+       return cxm_tuner_select_frequency(sc, cxm_tuner_tv_freq_type, freq);
+}
+
+
+int
+cxm_tuner_apply_afc(struct cxm_softc *sc)
+{
+       unsigned int i;
+       int status;
+       unsigned long freq;
+       unsigned long max_offset;
+       unsigned long original_freq;
+       unsigned long prev_freq;
+       unsigned long step_size;
+
+       if (cxm_tuner_wait_for_lock(sc) != 1)
+               return -1;
+
+       original_freq = sc->tuner_freq;
+
+       freq = sc->tuner_freq;
+       prev_freq = 0;
+       max_offset = 2000;
+       step_size = 63;
+
+       for (i = 0; i < (max_offset / step_size); i++) {
+               status = cxm_tuner_status(sc);
+
+               if (status == -1)
+                       break;
+
+               if (!(status & CXM_TUNER_PHASE_LOCKED))
+                       break;
+
+               switch (status & CXM_TUNER_AFC_MASK) {
+               case CXM_TUNER_AFC_FREQ_CENTERED:
+                       return 0;
+
+               case CXM_TUNER_AFC_FREQ_MINUS_125:
+               case CXM_TUNER_AFC_FREQ_MINUS_62:
+                       freq -= step_size;
+                       break;
+
+               case CXM_TUNER_AFC_FREQ_PLUS_62:
+               case CXM_TUNER_AFC_FREQ_PLUS_125:
+                       freq += step_size;
+                       break;
+
+               default:
+                       goto fail;
+               }
+
+               if (freq == prev_freq)
+                       return 0;
+               prev_freq = sc->tuner_freq;
+
+               if (cxm_tuner_select_frequency(sc, cxm_tuner_tv_freq_type,
+                                              freq) < 0)
+                       break;
+
+               /*
+                * Delay long enough for the tuner to update its status.
+                */
+
+               tsleep(&sc->iicbus, 0, "afc", hz / 10);
+       }
+
+fail:
+       cxm_tuner_select_frequency(sc, cxm_tuner_tv_freq_type, original_freq);
+       return -1;
+}
+
+
+int
+cxm_tuner_is_locked(struct cxm_softc *sc)
+{
+       int status;
+
+       status = cxm_tuner_status(sc);
+
+       if (status == -1)
+               return -1;
+
+       return (status & CXM_TUNER_PHASE_LOCKED) ? 1 : 0;
+}
+
+
+int
+cxm_tuner_wait_for_lock(struct cxm_softc *sc)
+{
+       unsigned int i;
+
+       /*
+        * The data sheet states the maximum lock-in time
+        * is 150 ms using fast tuning ... unfortunately
+        * it doesn't state the maximum lock-in time using
+        * moderate tuning.  Hopefully 300 ms is enough.
+        */
+
+       for (i = 0; i < 3; i++) {
+
+               /*
+                * The frequency may have just changed (prior to
+                * cxm_tuner_wait_for_lock) so start with the delay
+                * to give the tuner a chance to update its status.
+                */
+
+               tsleep(&sc->iicbus, 0, "tuner", hz / 10);
+
+               switch (cxm_tuner_is_locked(sc)) {
+               case 1:
+                       return 1;
+
+               case 0:
+                       break;
+
+               default:
+                       return -1;
+               }
+       }
+
+       device_printf(sc->dev, "tuner failed to lock\n");
+
+       return 0;
+}
+
+
+static const unsigned char afcmap[16] = {
+       CXM_TUNER_AFC_FREQ_CENTERED,
+       CXM_TUNER_AFC_FREQ_MINUS_62,
+       CXM_TUNER_AFC_FREQ_MINUS_62,
+       CXM_TUNER_AFC_FREQ_MINUS_62,
+       CXM_TUNER_AFC_FREQ_MINUS_125,
+       CXM_TUNER_AFC_FREQ_MINUS_125,
+       CXM_TUNER_AFC_FREQ_MINUS_125,
+       CXM_TUNER_AFC_FREQ_MINUS_125,
+       CXM_TUNER_AFC_FREQ_PLUS_125,
+       CXM_TUNER_AFC_FREQ_PLUS_125,
+       CXM_TUNER_AFC_FREQ_PLUS_125,
+       CXM_TUNER_AFC_FREQ_PLUS_125,
+       CXM_TUNER_AFC_FREQ_PLUS_62,
+       CXM_TUNER_AFC_FREQ_PLUS_62,
+       CXM_TUNER_AFC_FREQ_PLUS_62,
+       CXM_TUNER_AFC_FREQ_CENTERED
+};
+
+int
+cxm_tuner_status(struct cxm_softc *sc)
+{
+       unsigned char status;
+
+       if (cxm_tuner_read(sc->iicbus, CXM_I2C_TUNER, &status, sizeof(status))
+           != sizeof(status))
+               return -1;
+
+       if (sc->tuner->systems.code_style == cxm_if_system_code_style
+           || sc->tuner->systems.code_style
+              == cxm_if_system_with_aux_code_style) {
+               unsigned char if_status;
+
+               if (cxm_tuner_read(sc->iicbus, CXM_I2C_TUNER_IF,
+                                  &if_status, sizeof(if_status))
+                   != sizeof(if_status))
+                       return -1;
+
+               status &= ~CXM_TUNER_AFC_MASK;
+               status |= afcmap[(if_status >> 1) & 0x0f];
+       }
+
+       return status;
+}
diff --git a/tools/multimedia/cxm/Makefile b/tools/multimedia/cxm/Makefile
new file mode 100644 (file)
index 0000000..681e00f
--- /dev/null
@@ -0,0 +1,3 @@
+SUBDIR=        extract_fw setchannel
+
+.include <bsd.subdir.mk>
diff --git a/tools/multimedia/cxm/extract_fw/Makefile b/tools/multimedia/cxm/extract_fw/Makefile
new file mode 100644 (file)
index 0000000..828ced5
--- /dev/null
@@ -0,0 +1,7 @@
+PROG=  cxm_extract_fw
+BINDIR=        /usr/local/sbin
+MAN=   cxm_extract_fw.8
+MANDIR=        /usr/local/man/man
+WARNS?=        6
+
+.include <bsd.prog.mk>
diff --git a/tools/multimedia/cxm/extract_fw/cxm_extract_fw.8 b/tools/multimedia/cxm/extract_fw/cxm_extract_fw.8
new file mode 100644 (file)
index 0000000..f285d00
--- /dev/null
@@ -0,0 +1,81 @@
+.\"
+.\" Copyright (c) 2009
+.\"    The DragonFly Project.  All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\"
+.\" 1. Redistributions of source code must retain the above copyright
+.\"    notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\"    notice, this list of conditions and the following disclaimer in
+.\"    the documentation and/or other materials provided with the
+.\"    distribution.
+.\" 3. Neither the name of The DragonFly Project nor the names of its
+.\"    contributors may be used to endorse or promote products derived
+.\"    from this software without specific, prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+.\" ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+.\" LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+.\" FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+.\" COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+.\" INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
+.\" BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+.\" LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+.\" AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+.\" OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+.\" OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.Dd April 19, 2009
+.Dt CXM_EXTRACT_FW 8
+.Os
+.Sh NAME
+.Nm cxm_extract_fw
+.Nd convert Windows\[rg] firmware for use with
+.Xr cxm 4
+.Sh SYNOPSIS
+.Nm
+.Ar file
+.\".Op Ar file
+.Sh DESCRIPTION
+The
+.Nm
+utility transforms
+.Xr cxm 4
+.Tn Windows\[rg]
+firmware into C language files which are needed for the driver to work.
+.Pp
+In order to create those files, run the
+.Nm
+utility on the
+.Pa hcwPVRP2.sys
+file which came on the
+.Tn Windows\[rg]
+driver CD-ROM.
+The resulting
+.Pa cxm_dec_fw.c
+and
+.Pa cxm_enc_fw.c
+files have then to be copied to the
+.Xr cxm 4
+source directory (usually
+.Pa /usr/src/sys/dev/video/cxm ) before compiling the kernel.
+.Sh SEE ALSO
+.Xr cxm 4
+.Sh HISTORY
+The
+.Nm
+program was ported to
+.Dx 2.3
+by
+.An Sascha Wildner .
+.Sh AUTHORS
+.An -nosplit
+This program was made by
+.An John Wehle Aq john@feith.com .
+.Pp
+This manual page was written by
+.An Sascha Wildner Aq saw@online.de .
diff --git a/tools/multimedia/cxm/extract_fw/cxm_extract_fw.c b/tools/multimedia/cxm/extract_fw/cxm_extract_fw.c
new file mode 100644 (file)
index 0000000..8100af1
--- /dev/null
@@ -0,0 +1,193 @@
+/*-
+ * Copyright (c) 2003
+ *     John Wehle <john@feith.com>.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *     This product includes software developed by John Wehle.
+ * 4. The name of the author may not be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Conexant MPEG-2 Codec firmware extraction program.
+ *
+ * Generates:
+ *
+ * - cxm_dec_fw.c and cxm_enc_fw.c from the
+ * Hauppauge PVR-250 / PVR-350 Microsoft Windows driver
+ * (i.e. hcwpvrp2.sys).
+ *
+ * - cxm_cx2584x_fw.c from the Hauppauge PVR-150 / PVR-500
+ * Microsoft Windows driver (i.e. HcwMakoC.ROM). (optional)
+ *
+ * This was written using the invaluable information
+ * compiled by The IvyTV Project (ivtv.sourceforge.net).
+ */
+
+#include <sys/types.h>
+#include <sys/mman.h>
+#include <sys/param.h>
+#include <sys/stat.h>
+
+#include <err.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+const uint8_t decoder_magic[] = {
+       0xa7, 0x03, 0x00, 0x00, 0x66, 0xbb, 0x55, 0xaa
+};
+const uint8_t encoder_magic[] = {
+       0xa7, 0x0d, 0x00, 0x00, 0x66, 0xbb, 0x55, 0xaa
+};
+
+static int
+save_firmware(const char *name, const uint8_t *buf, size_t nbytes)
+{
+       FILE *ofp;
+       char outfile[MAXPATHLEN];
+       size_t i;
+
+       if (nbytes > (256 * 1024))
+               nbytes = 256 * 1024;
+
+       if ((size_t)snprintf(outfile, sizeof(outfile), "%s.c", name) >=
+           sizeof(outfile))
+               errx(1, "save_firmware -- firmware name is too long");
+
+       if (!(ofp = fopen(outfile, "w")))
+               err(1, "save_firmware -- can't open output file <%s>",
+                   outfile);
+
+       fprintf(ofp, "#include <sys/types.h>\n"
+           "\n"
+           "const uint8_t %s[] __attribute__ ((aligned(4))) = {",
+           name);
+
+       for (i = 0; i < nbytes; i++) {
+               if (i)
+                       fputc(',', ofp);
+               if ((i % 8) == 0)
+                       fputs("\n\t", ofp);
+               else
+                       fputc(' ', ofp);
+               fprintf(ofp, "0x%.2x", buf[i]);
+       }
+
+       fprintf(ofp, "\n};\n");
+
+       if (ferror(ofp)) {
+               fclose(ofp);
+               return -1;
+       }
+
+       fclose(ofp);
+       return 0;
+}
+
+
+int
+main(int argc, char **argv)
+{
+       uint8_t *end;
+       uint8_t *ptr;
+       uint8_t *start;
+       int decoder_fw_saved = 0;
+       int encoder_fw_saved = 0;
+       int fd, i;
+       struct stat statbuf;
+
+       if (argc != 2) {
+               fprintf(stderr, "usage: cxm_extract_fw file\n");
+               exit(1);
+       }
+
+       for (i = 1; i <= (argc - 1); i++) {
+               /*
+                * Open the file.
+                */
+               if ((fd = open(argv[i], O_RDONLY)) < 0)
+               err(1, "can't open %s for reading", argv[i]);
+
+               /*
+                * Determine how big it is.
+                */
+               if (fstat(fd, &statbuf) < 0) {
+                       close(fd);
+                       err(1, "can't fstat %s", argv[i]);
+               }
+
+               /*
+                * Map it into memory.
+                */
+               if (!(start = (uint8_t *)mmap(NULL, (size_t) statbuf.st_size,
+                           PROT_READ, MAP_SHARED, fd, (off_t) 0))) {
+                       close(fd);
+                       err(1, "can't mmap %s", argv[i]);
+               }
+               end = start + statbuf.st_size;
+
+               close(fd);
+
+               if (statbuf.st_size > 100000) {
+                       for (ptr = start; ptr != end; ptr++) {
+                               if ((size_t)(end - ptr) >= sizeof(decoder_magic) &&
+                                   memcmp(ptr, decoder_magic, sizeof(decoder_magic)) == 0) {
+                                       if (!decoder_fw_saved) {
+                                               if (save_firmware("cxm_dec_fw", ptr, end - ptr) < 0)
+                                                       errx(1, "save_firmware failed");
+                                               decoder_fw_saved = 1;
+                                       } else {
+                                               errx(1, "multiple decoder images present");
+                                       }
+                               }
+                               if ((size_t)(end - ptr) >= sizeof(encoder_magic) &&
+                                   memcmp(ptr, encoder_magic, sizeof(encoder_magic)) == 0) {
+                                       if (!encoder_fw_saved) {
+                                               if (save_firmware("cxm_enc_fw", ptr, end - ptr) < 0)
+                                                       errx(1, "save_firmware failed");
+                                               encoder_fw_saved = 1;
+                                       } else {
+                                               errx(1, "multiple encoder images present");
+                                       }
+                               }
+                       }
+               } else {
+                       errx(1, "save_firmware failed");
+               }
+
+               munmap((caddr_t)start, (size_t)statbuf.st_size);
+
+               if (!decoder_fw_saved)
+                       errx(1, "decoder image not present");
+
+               if (!encoder_fw_saved)
+                       errx(1, "encoder image not present");
+
+               if (!decoder_fw_saved || !encoder_fw_saved)
+                       exit(1);
+       }
+
+       exit(0);
+}
diff --git a/tools/multimedia/cxm/setchannel/Makefile b/tools/multimedia/cxm/setchannel/Makefile
new file mode 100644 (file)
index 0000000..91faadb
--- /dev/null
@@ -0,0 +1,6 @@
+PROG=  cxm_setchannel
+BINDIR=        /usr/local/bin
+MANDIR=        /usr/local/man/man
+WARNS?=        6
+
+.include <bsd.prog.mk>
diff --git a/tools/multimedia/cxm/setchannel/cxm_setchannel.1 b/tools/multimedia/cxm/setchannel/cxm_setchannel.1
new file mode 100644 (file)
index 0000000..579e3d7
--- /dev/null
@@ -0,0 +1,150 @@
+.\"
+.\" Copyright (c) 2009
+.\"    The DragonFly Project.  All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\"
+.\" 1. Redistributions of source code must retain the above copyright
+.\"    notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\"    notice, this list of conditions and the following disclaimer in
+.\"    the documentation and/or other materials provided with the
+.\"    distribution.
+.\" 3. Neither the name of The DragonFly Project nor the names of its
+.\"    contributors may be used to endorse or promote products derived
+.\"    from this software without specific, prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+.\" ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+.\" LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+.\" FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+.\" COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+.\" INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
+.\" BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+.\" LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+.\" AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+.\" OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+.\" OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" $Id: cxm.4,v 1.1 2004/10/16 00:12:35 mavetju Exp $
+.\"
+.Dd April 19, 2009
+.Dt CXM_SETCHANNEL 1
+.Os
+.Sh NAME
+.Nm cxm_setchannel
+.Nd Hauppauge channel selector
+.Sh SYNOPSIS
+.Nm
+.Op Fl a Ar on | off
+.Oo
+.Fl c | r | s | t
+.Oc
+.Op Fl d Ar unit
+.Op Fl g Ar geom
+.Op Fl m Ar chnl_set
+.Op Ar chnl | freq
+.Sh DESCRIPTION
+.Nm
+provides support for selecting channels on the PVR-250/350 cards via
+.Pa /dev/cxm0
+(unit is selectable with
+.Fl d ) .
+.Pp
+The options are as follows:
+.Bl -tag -width indent
+.It Fl a Ar on | off
+Enable/disable AFC (automatic frequency control).
+.It Fl c
+Select composite input.
+.It Fl r
+Select radio input.
+.It Fl s
+Select S-Video input.
+.It Fl t
+Select tuner.
+.It Fl d Ar unit
+Select unit (0..x).
+.It Fl g Ar geom
+Select geometry.
+Valid geometries are:
+.Pp
+.Bl -tag -compact -width indent
+.It Xo Li 352x240
+or
+.Li 352x288
+.Tn ( VCD )
+.Xc
+.It Xo Li 480x480
+or
+.Li 480x576
+.Tn ( SVCD )
+.Xc
+.It Xo Li 352x480
+or
+.Li 352x576
+.Tn ( DVD ,
+half D1)
+.Xc
+.It Xo Li 720x480
+or
+.Li 720x576
+.Tn ( DVD ,
+full D1)
+.Xc
+.El
+.It Fl m Ar chnl_set
+Select channel set/system.
+Valid sets are:
+.Pp
+.Bl -tag -compact -width indent
+.It Li 0
+Tuner default
+.It Li 1
+US Broadcast (NTSC)
+.It Li 2
+US Cable (NTSC)
+.It Li 4
+Western Europe (PAL)
+.It Li 5
+Japan Broadcast (NTSC)
+.It Li 6
+Japan Cable (NTSC)
+.It Li 8
+Australia (PAL)
+.It Li 9
+France (SECAM)
+.El
+.El
+.Pp
+The argument passed to
+.Nm
+has to be either a channel number or a frequency in MHz (which must include
+a decimal point).
+.Sh SEE ALSO
+.Xr bktr 4 ,
+.Xr cxm 4 ,
+.Xr meteor 4
+.Sh HISTORY
+The
+.Nm
+program first appeared in the
+.Pa FreeBSD-multimedia@
+mailing list in January 2004 and in the
+.Fx
+Ports collection in October 2004.
+.Pp
+It was ported to
+.Dx 2.3
+by
+.An Sascha Wildner .
+.Sh AUTHORS
+.An -nosplit
+This program was made by
+.An John Wehle Aq john@feith.com .
+.Pp
+This manual page was made by
+.An Edwin Groothuis Aq edwin@FreeBSD.org .
diff --git a/tools/multimedia/cxm/setchannel/cxm_setchannel.c b/tools/multimedia/cxm/setchannel/cxm_setchannel.c
new file mode 100644 (file)
index 0000000..977623c
--- /dev/null
@@ -0,0 +1,259 @@
+/*-
+ * Copyright (c) 2003, 2004, 2005
+ *     John Wehle <john@feith.com>.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *     This product includes software developed by John Wehle.
+ * 4. The name of the author may not be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Set the channel of the tuner card.
+ */
+
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/ioctl.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <dev/video/meteor/ioctl_meteor.h>
+#include <dev/video/bktr/ioctl_bt848.h>
+
+static void
+usage(void)
+{
+       fprintf(stderr,
+           "usage: cxm_setchannel [-a {on|off}] [-c | -r | -s | -t] [-d unit] [-g geom]\n"
+           "                      [-m chnl_set] [chnl | freq]\n");
+}
+
+int
+main(int argc, char *argv[])
+{
+       char *ptr;
+       char *endptr;
+       char buf[255];
+       int afc;
+       int audio;
+       int c;
+       int channel_set;
+       int i;
+       int status;
+       int tfd;
+       int unit;
+       unsigned int channel;
+       unsigned int fraction;
+       unsigned int freq;
+       unsigned int x_size;
+       unsigned int y_size;
+       unsigned long device;
+       struct bktr_capture_area cap;
+
+       afc = -1;
+       audio = -1;
+       channel = 0;
+       channel_set = -1;
+       device = 0;
+       unit = 0;
+       freq = 0;
+       status = 0;
+       x_size = 0;
+       y_size = 0;
+
+       while ((c = getopt(argc, argv, "a:crstg:m:d:")) != -1)
+               switch (c) {
+               case 'a':
+                       if (strcasecmp(optarg, "on") == 0)
+                               afc = 1;
+                       else if (strcasecmp(optarg, "off") == 0)
+                               afc = 0;
+                       else {
+                               usage();
+                               exit(1);
+                       }
+                       break;
+
+               case 'c':
+                       device = METEOR_INPUT_DEV2;
+                       audio = -1;
+                       break;
+
+               case 'd':
+                       unit = atoi(optarg);
+                       break;
+
+               case 'r':
+                       device = 0;
+                       audio = AUDIO_INTERN;
+                       break;
+
+               case 's':
+                       device = METEOR_INPUT_DEV_SVIDEO;
+                       audio = -1;
+                       break;
+
+               case 't':
+                       device = METEOR_INPUT_DEV1;
+                       audio = -1;
+                       break;
+
+               case 'g':
+                       if (sscanf(optarg, "%ux%u", &x_size, &y_size) != 2 ||
+                           x_size == 0 || y_size == 0) {
+                               usage();
+                               exit(1);
+                       }
+                       break;
+
+               case 'm':
+                       channel_set = atoi(optarg);
+                       if (channel_set < 0 || channel_set > CHNLSET_MAX) {
+                               usage();
+                               exit(1);
+                       }
+                       break;
+
+               default:
+                       usage();
+                       exit(1);
+               }
+
+       if (optind < argc) {
+
+               /*
+                * A number containing a decimal point is the frequency in
+                * MHz.
+                */
+
+               if ((ptr = strchr(argv[optind], '.')) != NULL) {
+                       freq = strtol(argv[optind], &endptr, 10) * 1000;
+                       if (ptr != endptr) {
+                               usage();
+                               exit(1);
+                       }
+                       ptr++;
+
+                       fraction = strtol(ptr, &endptr, 10);
+                       if (!isdigit(*ptr) || *endptr != '\0') {
+                               usage();
+                               exit(1);
+                       }
+                       for (i = endptr - ptr; i > 3; i--)
+                               fraction /= 10;
+                       for (; i < 3; i++)
+                               fraction *= 10;
+
+                       freq += fraction;
+               }
+               /*
+                * An integer is the channel.
+                */
+
+               else
+                       channel = atoi(argv[optind]);
+       }
+       if (afc == -1 && audio == -1 && !device && x_size == 0 &&
+           y_size == 0 && channel_set == -1 && !channel && !freq) {
+               usage();
+               exit(1);
+       }
+
+       sprintf(buf, "/dev/cxm%d", unit);
+
+       tfd = open(buf, O_RDONLY);
+       if (tfd < 0) {
+               warn("open() of /dev/cxm%d failed.", unit);
+               exit(1);
+       }
+
+       if (afc != -1) {
+               if (ioctl(tfd, TVTUNER_SETAFC, &afc) < 0) {
+                       warn("ioctl( tfd, TVTUNER_SETAFC ) failed.");
+                       status = 1;
+               }
+       }
+       if (device) {
+               if (ioctl(tfd, METEORSINPUT, &device) < 0) {
+                       warn("ioctl( tfd, METEORSINPUT ) failed.");
+                       status = 1;
+               }
+       }
+       if (audio != -1) {
+               if (ioctl(tfd, BT848_SAUDIO, &audio) < 0) {
+                       warn("ioctl( tfd, BT848_SAUDIO ) failed.");
+                       status = 1;
+               }
+       }
+       if (ioctl(tfd, BT848_GAUDIO, &audio) < 0) {
+               warn("ioctl( tfd, BT848_GAUDIO ) failed.");
+               status = 1;
+       }
+       if (x_size && y_size) {
+               memset(&cap, 0, sizeof(cap));
+               cap.x_size = x_size;
+               cap.y_size = y_size;
+               if (ioctl(tfd, BT848_SCAPAREA, &cap) < 0) {
+                       warn("ioctl( tfd, BT848_SCAPAREA ) failed.");
+                       status = 1;
+               }
+       }
+       if (channel_set != -1) {
+               if (ioctl(tfd, TVTUNER_SETTYPE, &channel_set) < 0) {
+                       warn("ioctl( tfd, TVTUNER_SETTYPE ) failed.");
+                       status = 1;
+               }
+       }
+       if (channel) {
+               if (ioctl(tfd, TVTUNER_SETCHNL, &channel) < 0) {
+                       warn("ioctl( tfd, TVTUNER_SETCHNL ) failed.");
+                       status = 1;
+               }
+       } else if (freq) {
+               if (audio == AUDIO_INTERN) {
+                       /* Convert from kHz to MHz * 100 */
+                       freq = freq / 10;
+
+                       if (ioctl(tfd, RADIO_SETFREQ, &freq) < 0) {
+                               warn("ioctl( tfd, RADIO_SETFREQ ) failed.");
+                               status = 1;
+                       }
+               } else {
+                       /* Convert from kHz to MHz * 16 */
+                       freq = (freq * 16) / 1000;
+
+                       if (ioctl(tfd, TVTUNER_SETFREQ, &freq) < 0) {
+                               warn("ioctl( tfd, TVTUNER_SETFREQ ) failed.");
+                               status = 1;
+                       }
+               }
+       }
+       close(tfd);
+       exit(status);
+}