/*---------------------------------------------------------------------------*\
	vtopenpci.c
	OpenPCI Kernel Module for Voicetronix modular OpenPCI cards
	Author: Ben Kramer, 24 October 2005
	        Ron Lee, 8 Jun 2006

	Copyright (C) 2005 - 2018 Voicetronix www.voicetronix.com.au
	Copyright (C) 2006 - 2018 Ron Lee <ron@voicetronix.com.au>

	This library is free software; you can redistribute it and/or
	modify it under the terms of the GNU General Public License
	as published by the Free Software Foundation; either version 2
	of the License, or (at your option) any later version.

	This library is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
	General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this library; if not, write to the Free Software
	Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
	MA  02110-1301  USA

\*---------------------------------------------------------------------------*/

/* Conditional debug options */
#define VERBOSE_TIMING      0
#define IO_WAIT_TIMING      0

/* Driver constants */
#define DRIVER_DESCRIPTION  "Voicetronix OpenPCI card driver"
#define DRIVER_AUTHOR       "Voicetronix <support@voicetronix.com.au>"

#define NAME      "vtopenpci"
#define MAX_PORTS 8            // Maximum number of ports on each carrier
#define MAX_CARDS 8            // Maximum number of carriers per host

#define VT_MOD_INFO_SIZE 16    // Maximum size of a module info data field.

#define DEFAULT_COUNTRY  "AUSTRALIA"


#include <linux/init.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/delay.h>
#include <linux/proc_fs.h>
#include <linux/interrupt.h>
#include <linux/uaccess.h>

#if VERBOSE_TIMING
 #include <asm/msr.h>          // for rdtscll, cputime.h is overkill here.
#endif

#include "vtcommon.h"
#include "vtopenpci.h"
#include "vtmodule.h"
#include "sivoicedaareg.h"
#include "siproslicreg.h"

#include "messages_dsp_pc.h"


// Backward compatibility for kernel < 2.6.20 or so.
#ifndef IRQF_SHARED
#define IRQF_SHARED SA_SHIRQ
#endif


#define INOUT 2

#define VT_PCIDMA_BLOCKSIZE (FRAME * INOUT * MAX_PORTS * 2)
#define VT_PCIDMA_MIDDLE    (FRAME * MAX_PORTS - 4)
#define VT_PCIDMA_END       (FRAME * MAX_PORTS * 2 - 4)

#define PCI_DEVICE_ID_VOICETRONIX_OPENPCI   PCI_ANY_ID


/* macro to address internal TJ registers */
#define TREG(addr)      (card->ioaddr + addr)

/* Tiger320 Registers */
#define TJ_CNTL         TREG(0x00)
#define TJ_OPER         TREG(0x01)
#define TJ_AUXC         TREG(0x02)    /* AUX DIR CONTROL */
#define TJ_AUXD         TREG(0x03)    /* AUX DATA READ/WRITE */
#define TJ_MASK0        TREG(0x04)    /* PCI & DMA CONTROLS */
#define TJ_MASK1        TREG(0x05)    /* SET AUX LINES THAT GEN. INTERUPTS */
#define TJ_INTSTAT      TREG(0x06)    /* SOURCE OF INTERUPT */
#define TJ_AUXR         TREG(0x07)    /* AUX PIN STATUS */

#define TJ_DMAWS        TREG(0x08)
#define TJ_DMAWI        TREG(0x0c)
#define TJ_DMAWE        TREG(0x10)
#define TJ_DMAWC        TREG(0x14)
#define TJ_DMARS        TREG(0x18)
#define TJ_DMARI        TREG(0x1c)
#define TJ_DMARE        TREG(0x20)
#define TJ_DMARC        TREG(0x24)

#define TJ_AUXPMEMSK    TREG(0x29)    /* AUX PME# MASK REGISTER */
#define TJ_AUXINTPOL    TREG(0x2A)    /* AUX INT POLARITY CONTROL */

#define TJ_AUXFUNC      TREG(0x2b)
#define TJ_SERCTL       TREG(0x2d)
#define TJ_FSCDELAY     TREG(0x2f)

#define TJ_REGBASE      TREG(0xc0)    /* HOST INTERFACE (STATUS/CONTROL) aka PIB(0) */
//#define TJ_HBDATA     TREG(0xc4)    /* Host Data Read/Write) aka PIB(1) */

#define TJ_RUNFLAGS     0x01          /* control register 'run mode' flags */

/* macro to address "Peripheral Bus Interface" */
#define PIB(addr)       (TJ_REGBASE + addr * 4)


#define HTXF_READY	(inb(PIB(0)) & 0x10)
#define HRXF_READY	(inb(PIB(0)) & 0x20)

#if IO_WAIT_TIMING
 #define REPORT_WAIT(n,x)						    \
	 cardinfo(card->cardnum, #n " wait at %d, " #x " = %d", __LINE__, x )
#else
 #define REPORT_WAIT(n,x)
#endif

#define BUSY_WAIT(countvar,cond,delay,iter,failret)			    \
	countvar=0;							    \
	while(cond){							    \
	    udelay(delay);						    \
	    if(++countvar > iter){					    \
		cardwarn(card,"busy wait failed at %d",__LINE__);	    \
		return failret;						    \
	    }								    \
	}								    \
	REPORT_WAIT(busy,i)

#define LOCKED_WAIT(countvar,cond,delay,iter,failret)			    \
	countvar=0;							    \
	while(cond){							    \
	    udelay(delay);						    \
	    if(++countvar > iter){					    \
		cardwarn(card,"busy wait failed at %d",__LINE__);	    \
		spin_unlock_irqrestore(&card->lock, flags);		    \
		return failret;						    \
	    }								    \
	}								    \
	REPORT_WAIT(locked,i)

#define HTXF_WAIT()		    BUSY_WAIT(i,HTXF_READY,5,500,RET_FAIL)
#define HRXF_WAIT()		    BUSY_WAIT(i,!HRXF_READY,5,70000,RET_FAIL)
#define HTXF_WAIT_RET(failret)	    BUSY_WAIT(i,HTXF_READY,5,500,failret)
#define HRXF_WAIT_RET(failret)	    BUSY_WAIT(i,!HRXF_READY,5,1000,failret)

#define HTXF_WAIT_LOCKED()	    LOCKED_WAIT(i,HTXF_READY,5,500,RET_FAIL)
#define HRXF_WAIT_LOCKED()	    LOCKED_WAIT(i,!HRXF_READY,5,1000,RET_FAIL)
#define HTXF_WAIT_LOCKED_RET(failret) LOCKED_WAIT(i,HTXF_READY,5,500,failret)
#define HRXF_WAIT_LOCKED_RET(failret) LOCKED_WAIT(i,!HRXF_READY,5,1000,failret)


/* Values for ProSlic */
#define VBAT	0xC7	/* 74.824/0.376 */
//#define VBAT	0xBA	/* 69.936/0.376 */

// hook modes (from Si doco)
#define HKMODE_OPEN	0
#define HKMODE_FWDACT	1
#define HKMODE_FWDONACT	2
#define HKMODE_TIPOPEN	3
#define HKMODE_RINGING	4
#define HKMODE_REVACT	5
#define HKMODE_REVONACT	6
#define	HKMODE_RINGOPEN	7

// hook states
#define HOOK_ONHOOK       0
#define HOOK_OFFHOOK      1
#define HOOK_FASTOFF      2

struct modinfo {
	char type[4];
	int  ports;
	int  rev;
	char serial[9];
	char date[9];
	struct proc_dir_entry *procfs_root;
};

static struct openpci {
	struct pci_dev *dev;

	struct vtboard  board;
	struct channel  chans[MAX_PORTS];

	int             cardnum;
	int             firmware;

	unsigned long   ioaddr;
	dma_addr_t      readdma;
	dma_addr_t      writedma;
	volatile int   *writeblock;
	volatile int   *readblock;

	spinlock_t      lock;

	unsigned int    interrupt_count;
	unsigned int    arm_interrupt_count;
	int             armbout;
	int             armbin;

	int             ppusage;
	int             ppdatamask;

	//XXX Don't enable this until the next iteration.
	//struct work_struct  arm_work;

	struct modinfo  moduleinfo[2];

    #if VERBOSE_TIMING
	unsigned long long time_tj_last;
	unsigned long long time_tj_last_rel;
	unsigned long long time_tj_max;
	unsigned long long delay_tj_max;
	unsigned long long time_arm_max;
	int                read_overflow;
	int                write_overflow;
    #endif
} *cards[MAX_CARDS];	//XXX Why do we have this?
			//    It is never used except to determine the
			//    board number.  Do we really care?

// You must hold this lock anytime you access or modify the cards[] array.
DEFINE_MUTEX(cards_mutex);

const unsigned char fxo_port_lookup[8] = {0x0, 0x8, 0x4, 0xc, 0x10, 0x18, 0x14, 0x1c};
const unsigned char fxs_port_lookup[8] = {0x0, 0x1, 0x2, 0x3, 0x10, 0x11, 0x12, 0x13};

#define PCI_DEVICE_ID_VOICETRONIX_OPENPCI	PCI_ANY_ID
#define PCI_DEVICE_ID_VOICETRONIX_OPENPCIE	0x0002

static struct pci_device_id openpci_idtable[] = {
	{
		.vendor		= PCI_VENDOR_ID_TIGERJET,
		.device		= PCI_DEVICE_ID_TIGERJET_300,
		.subvendor	= PCI_VENDOR_ID_VOICETRONIX,
		.subdevice	= PCI_DEVICE_ID_VOICETRONIX_OPENPCIE,
		.driver_data	= (kernel_ulong_t)"OpenPCIe",
	}, {
		.vendor		= PCI_VENDOR_ID_TIGERJET,
		.device		= PCI_DEVICE_ID_TIGERJET_300,
		.subvendor	= PCI_ANY_ID,
		.subdevice	= PCI_DEVICE_ID_VOICETRONIX_OPENPCI,
		.driver_data	= (kernel_ulong_t)"OpenPCI",
	},
	{}
};
MODULE_DEVICE_TABLE(pci, openpci_idtable);

const char *get_pci_boardname(const struct pci_device_id *ent)
{
	return (const char*)ent->driver_data;
}

static int openpci_probe_board(struct pci_dev *pdev, const struct pci_device_id *ent);
static void openpci_remove_board(struct pci_dev *pdev);

static struct pci_driver vtopenpci_driver = {
	.name     = NAME,
	.probe    = openpci_probe_board,
	.remove   = openpci_remove_board,
	.suspend  = NULL,
	.resume   = NULL,
	.id_table = openpci_idtable
};

//XXX Use me!
//static struct workqueue_struct *openpci_workqueue; // = NULL

static bool shareirq = 1;
static char *country = DEFAULT_COUNTRY;
static char *program; // = NULL
static int  debug;    // = 0;

module_param(shareirq, bool, 0444);
module_param(country, charp, 0444);
module_param(program, charp, 0444);
module_param(debug,     int, 0644);
MODULE_PARM_DESC(shareirq, "Enable (or disable) irq sharing");
MODULE_PARM_DESC(country,  "Set the default country name");
MODULE_PARM_DESC(program,  "Select CPLD or ARM programming mode");
MODULE_PARM_DESC(debug,    "Set the logging verbosity");


/* Convenience macros for logging */
#define cardinfo(cardnum,format,...) info("[%d] " format, cardnum , ## __VA_ARGS__)
#define portinfo(card,port,format,...)                                        \
	info("[%d] %2d: " format, (card)->cardnum, port , ## __VA_ARGS__)
#define cardwarn(card,format,...) warn("[%d] " format, (card)->cardnum , ## __VA_ARGS__)
#define portwarn(card,port,format,...)                                        \
	warn("[%d] %2d: " format, (card)->cardnum, port , ## __VA_ARGS__)
#define cardcrit(cardnum,format,...) crit("[%d] " format, cardnum , ## __VA_ARGS__)
#define portcrit(card,port,format,...)                                        \
	crit("[%d] %2d: " format, (card)->cardnum, port , ## __VA_ARGS__)
#define dbginfo(n,cardnum,format,...)                                         \
	if(debug>=n) info("[%d] " format, cardnum , ## __VA_ARGS__)
#define dbgport(n,card,port,format,...)	if(debug>=n)                          \
	info("[%d] %2d: " format, (card)->cardnum, port , ## __VA_ARGS__)


int __init vtopenpci_init(void)
{ //{{{
	int ret;

	info(DRIVER_DESCRIPTION " " VT_VERSION " for linux " UTS_RELEASE);
	if(debug) info("using debug level %d", debug);

      #if VERBOSE_TIMING
	info("cpu_khz = %u", cpu_khz);
      #endif

	ret = pci_register_driver(&vtopenpci_driver);
	if(ret < 0) {
		crit("Failed to register pci driver (%d)", ret);
		return ret;
	}
	//XXX
    #if 0
	openpci_workqueue = create_workqueue( NAME );
	if (!openpci_workqueue){
		crit("Failed to create workqueue");
		pci_unregister_driver(&vtopenpci_driver);
		return -ENOBUFS;
	}
    #endif
	for( ret=0; cards[ret]; ++ret );
	info("module loaded, driver bound to %d cards", ret);

	return 0;
} //}}}

void __exit vtopenpci_exit(void)
{ //{{{
	//XXX destroy_workqueue(openpci_workqueue);
	pci_unregister_driver(&vtopenpci_driver);
	info("module exit");
} //}}}


/* You must hold the card spinlock to call this function */
static int __ping_arm(struct openpci *card)
{ //{{{
	int i;
	int pong=0;
	int pong_count = 3;

	while( pong_count ) {
		outb(0x02, PIB(1)); ++card->armbout; HTXF_WAIT();
		HRXF_WAIT(); pong = inb(PIB(1)); ++card->armbin;
		if( pong == 0x02 ) --pong_count;
		else               pong_count = 3;
		dbginfo(5,card->cardnum, "ping_arm returned %x", pong);
	}
	while(pong == 0x02){
		// Poke no-ops into the arm while it is still returning data,
		// if 500 usec elapses with no further response from it then
		// the message queue is should be completely cleared.
		outb(0x00, PIB(1)); ++card->armbout; HTXF_WAIT();
		i = 100;
		while( !HRXF_READY && --i ) udelay(5);
		if( i == 0 ) break;
		pong = inb(PIB(1)); ++card->armbin;
		dbginfo(5,card->cardnum, "ping_arm flushing %x", pong);
	}
	return RET_OK;
} //}}}

#if 0
static int ping_arm(struct openpci *card)
{ //{{{
	unsigned long flags;

	// We may hold this lock for quite a while, but since we are flushing
	// the message queue, and never called from a context where we may be
	// servicing interrupts which might want it, that should be ok.
	spin_lock_irqsave(&card->lock, flags);
	__ping_arm( card );
	spin_unlock_irqrestore(&card->lock, flags);
	return RET_OK;
} //}}}
#endif


/* You must hold the card spinlock to call this function */
static int __read_reg_fxo(struct openpci *card, int port, unsigned char reg, unsigned char *value)
{ //{{{
	int i;
	unsigned char portadr = fxo_port_lookup[port];

	if (HRXF_READY){
		*value = inb(PIB(1)); ++card->armbin;
	}
	outb(0x11, PIB(1));    ++card->armbout; HTXF_WAIT();
	outb(0x2, PIB(1));     ++card->armbout; HTXF_WAIT();
	outb(portadr, PIB(1)); ++card->armbout; HTXF_WAIT();
	outb(reg, PIB(1));     ++card->armbout; HTXF_WAIT();

	HRXF_WAIT(); *value = inb(PIB(1)); ++card->armbin;

	return RET_OK;
} //}}}

static int read_reg_fxo(struct openpci *card, int port, unsigned char reg, unsigned char *value)
{ //{{{
	unsigned long flags;

	spin_lock_irqsave(&card->lock, flags);
	if( __read_reg_fxo(card, port, reg, value) ){
		spin_unlock_irqrestore(&card->lock, flags);
		return RET_OK;
	}
	//XXX We do need to recover if a reg write fails
	//XXX this is not a terribly good way to do it, but its the
	//XXX best we can do for now...
	{ int d = debug; debug = 5; __ping_arm(card); debug = d; }
	spin_unlock_irqrestore(&card->lock, flags);
	portcrit(card,port,"FXO reg %d read FAILED", reg);
	return RET_FAIL;
} //}}}

/* You must hold the card spinlock to call this function */
static int __read_reg_fxs(struct openpci *card, int port, unsigned char reg, unsigned char *value)
{ //{{{
	int i;
	unsigned char portadr = fxs_port_lookup[port];

	if (HRXF_READY){
		*value = inb(PIB(1)); ++card->armbin;
	}
	outb(0x13, PIB(1));    ++card->armbout; HTXF_WAIT();
	outb(0x2, PIB(1));     ++card->armbout; HTXF_WAIT();
	outb(portadr, PIB(1)); ++card->armbout; HTXF_WAIT();
	outb(reg, PIB(1));     ++card->armbout; HTXF_WAIT();

	HRXF_WAIT(); *value = inb(PIB(1)); ++card->armbin;

	return RET_OK;
} //}}}

#if 0
static int read_reg_fxs(struct openpci *card, int port, unsigned char reg, unsigned char *value)
{ //{{{
	unsigned long flags;

	spin_lock_irqsave(&card->lock, flags);
	if( __read_reg_fxs(card, port, reg, value) ) {
		spin_unlock_irqrestore(&card->lock, flags);
		return RET_OK;
	}
	spin_unlock_irqrestore(&card->lock, flags);
	portcrit(card,port,"FXS reg %d read FAILED", reg);
	return RET_FAIL;
} //}}}
#endif

/* You must hold the card spinlock to call this function */
static int __write_reg_fxo(struct openpci *card, int port, unsigned char reg, unsigned char value)
{ //{{{
	int i;
	unsigned char portadr = fxo_port_lookup[port];

	outb(0x10, PIB(1));    ++card->armbout; HTXF_WAIT();
	outb(0x3, PIB(1));     ++card->armbout; HTXF_WAIT();
	outb(portadr, PIB(1)); ++card->armbout; HTXF_WAIT();
	outb(reg, PIB(1));     ++card->armbout; HTXF_WAIT();
	outb(value, PIB(1));   ++card->armbout; HTXF_WAIT();

	return RET_OK;
} //}}}

static int write_reg_fxo(struct openpci *card, int port, unsigned char reg, unsigned char value)
{ //{{{
	unsigned long flags;

	spin_lock_irqsave(&card->lock, flags);
	if( __write_reg_fxo(card, port, reg, value) ){
		spin_unlock_irqrestore(&card->lock, flags);
		return RET_OK;
	}
	spin_unlock_irqrestore(&card->lock, flags);
	portcrit(card,port,"FXO reg %d write(%d) failed", reg, value);
	return RET_FAIL;
} //}}}

/* You must hold the card spinlock to call this function */
static int __write_reg_fxs(struct openpci *card, int port, unsigned char reg, unsigned char value)
{ //{{{
	int i;
	unsigned char portadr = fxs_port_lookup[port];

	outb(0x12, PIB(1));    ++card->armbout; HTXF_WAIT();
	outb(0x3, PIB(1));     ++card->armbout; HTXF_WAIT();
	outb(portadr, PIB(1)); ++card->armbout; HTXF_WAIT();
	outb(reg, PIB(1));     ++card->armbout; HTXF_WAIT();
	outb(value, PIB(1));   ++card->armbout; HTXF_WAIT();

	return RET_OK;
} //}}}

static int write_reg_fxs(struct openpci *card, int port, unsigned char reg, unsigned char value)
{ //{{{
	unsigned long flags;

	spin_lock_irqsave(&card->lock, flags);
	if( __write_reg_fxs(card, port, reg, value) ){
		spin_unlock_irqrestore(&card->lock, flags);
		return RET_OK;
	}
	spin_unlock_irqrestore(&card->lock, flags);
	portcrit(card,port,"FXS reg %d write failed", reg);
	return RET_FAIL;
} //}}}

/* You must hold the card spinlock to call this function */
static int __wait_indreg_fxs(struct openpci *card, int port)
{ //{{{
	unsigned char value;
	int count=100;

	while(--count){
		if( __read_reg_fxs(card,port,PS_IND_ADDR_ST,&value) ) {
			if(value) continue;
			dbgport(7,card,port,"__wait_indreg_fxs %d",100-count);
			return RET_OK;
		} else {
			dbgport(3,card,port,
				"failed to read PS_IND_ADDR_ST, retrying...");
		}
	}
	portcrit(card,port,"FAILED to wait for indirect reg write");
	return RET_FAIL;
} //}}}

static int write_indreg_fxs(struct openpci *card, int port, unsigned char reg, unsigned short value)
{ //{{{
	unsigned long flags;

	spin_lock_irqsave(&card->lock, flags);
	if( __wait_indreg_fxs(card,port)
	 && __write_reg_fxs(card, port, PS_IND_DATA_L, (value & 0xFF))
	 && __write_reg_fxs(card, port, PS_IND_DATA_H, ((value & 0xFF00)>>8))
	 && __write_reg_fxs(card, port, PS_IND_ADDR, reg)
	 && __wait_indreg_fxs(card,port))
	{
		spin_unlock_irqrestore(&card->lock, flags);
		return RET_OK;
	}
	spin_unlock_irqrestore(&card->lock, flags);
	portcrit(card,port,"FXS indreg %d write failed", reg);
	return RET_FAIL;
} //}}}

static int read_indreg_fxs(struct openpci *card, int port, unsigned char reg, unsigned short *value)
{ //{{{
	unsigned long flags;
	unsigned char tmp1, tmp2;

	spin_lock_irqsave(&card->lock, flags);
	if( __wait_indreg_fxs(card,port)
	 && __write_reg_fxs(card, port, PS_IND_ADDR, reg)
	 && __wait_indreg_fxs(card,port)
	 && __read_reg_fxs(card,port,PS_IND_DATA_L,&tmp1)
	 && __read_reg_fxs(card,port,PS_IND_DATA_H,&tmp2) )
	{
		*value = (tmp1 & 0xff) | ((tmp2 & 0xff)<<8);
		// Wait until it has finished!	    //XXX ???
		if(__wait_indreg_fxs(card,port)){
			spin_unlock_irqrestore(&card->lock, flags);
			return RET_OK;
		}
	}
	spin_unlock_irqrestore(&card->lock, flags);
	portcrit(card,port,"FXS indreg %d read failed", reg);
	return RET_FAIL;
} //}}}


static int configure_vdaa_country(struct openpci *card, int port, const char *name)
{ //{{{
	unsigned char value;
	int i;

	for (i=0; i < sizeof(country_regs)/sizeof(struct country_reg); ++i){
		if(!strcmp(country_regs[i].country, name)){
			dbgport(1,card,port, "Setting country to %s", name);
			goto part2;
		}
	}
	return -EINVAL;

    part2:
	/* International control 1 - Register 16 */
	value  = (country_regs[i].ohs << 6);
	value |= (country_regs[i].rz << 1);
	value |=  country_regs[i].rt;
	if( ! write_reg_fxo(card, port, VDAR_INTC1, value) ){
		cardcrit(card->cardnum,
			 "write VDAR_INTC1=%x failed on port %d", value, port);
		return -EIO;
	}

	/* DC Termination Control - Register 26 */
	value = (country_regs[i].dcv << 6);
	value |= (country_regs[i].mini << 4);
	value |= (country_regs[i].ilim << 1);
	if( ! write_reg_fxo(card, port, VDAR_DCTERMC, value) ){
		cardcrit(card->cardnum,
			 "write VDAR_DCTERMC=%x failed on port %d", value, port);
		return -EIO;
	}

	/* AC Termination Control - Register 30 */
	value = country_regs[i].acim;
	if( ! write_reg_fxo(card, port, VDAR_ACTERMC, value) ){
		cardcrit(card->cardnum,
			 "write VDAR_ACTERMC=%x failed on port %d", value, port);
		return -EIO;
	}

	/* DAA Control 5 - Register 31 */
	msleep(1);
	if( ! read_reg_fxo(card, port, VDAR_DAAC5, &value) ){
		cardcrit(card->cardnum,
			 "read VDAR_DAAC5 failed on port %d", port);
		return -EIO;
	}
	value = (value & 0xf7) | (country_regs[i].ohs2 << 3);
	value = value | 0x02;
	if( ! write_reg_fxo(card, port, VDAR_DAAC5, value) ){
		cardcrit(card->cardnum,
			 "write VDAR_DAAC5=%x failed on port %d", value, port);
		return -EIO;
	}
	card->chans[port].country = country_regs[i].country;

	if( ! write_reg_fxo(card, port, VDAR_RVC1, 0x16) ){
		cardcrit(card->cardnum,
			 "write VDAR_RVC1=0x16 failed on port %d", port);
		return -EIO;
	}
	return 0;
} //}}}

static int configure_vdaa_port(struct openpci *card, int port)
{ //{{{
	/* Set Country - default to Australia */
	switch( configure_vdaa_country(card, port, country) ){
	    case 0: break;
	    case -EINVAL:
		portwarn(card,port, "Country '%s' unknown, using default", country);
		if( configure_vdaa_country(card, port, DEFAULT_COUNTRY) == 0 )
			break;
	    default:
		return RET_FAIL;
	}
	return RET_OK;
} //}}}

#if 0
static void proslic_voltages(struct openpci *card, int port)
{ //{{{
	int tmp;
	read_reg_fxs(card, port, 82, &tmp);
	portinfo(card,port, "ProSLIC powered up to -%d volts (%02x) at VBAT1\n",
			    tmp * 376 / 1000, tmp );
	
//	read_reg_fxs(card, port, 83, &tmp);
//	portinfo(card,port, "ProSLIC powered up to -%d volts (%02x) at VBAT2\n",
//			    tmp * 376 / 1000, tmp );

	read_reg_fxs(card, port, 80, &tmp);
	portinfo(card,port, "ProSLIC powered up to -%d volts (%02x) at TIP\n",
			    tmp * 376 / 1000, tmp);

	read_reg_fxs(card, port, 81, &tmp);
	portinfo(card,port, "ProSLIC powered up to -%d volts (%02x) at RING\n",
			    tmp * 376 / 1000, tmp);
} //}}}
#endif

static int configure_proslic_country(struct openpci *card, int port, const char *name)
{ //{{{
	int i;

	for (i=0; i < sizeof(ps_country_regs)/sizeof(struct ps_country_reg); ++i){
		if(!strcmp(ps_country_regs[i].country, name)){
			dbgport(1,card,port, "Setting country to %s", name);
			goto part2;
		}
	}
	return -EINVAL;

    part2:

	if( ! write_reg_fxs(card, port, PS_IMPEDANCE, ps_country_regs[i].value) ){
		portcrit(card,port,"failed to write PS_IMPEDANCE");
		return -EIO;
	}
	card->chans[port].country = ps_country_regs[i].country;
	return 0;
} //}}}

static int set_proslic_dtmf_detector(struct openpci *card, int port, int enable)
{ //{{{
	unsigned long flags;
	int i;

	if( port != 0xff && card->chans[port].porttype != VT_PORT_PROSLIC )
		return -EINVAL;

	spin_lock_irqsave(&card->lock, flags);
	outb(enable ? 0x2b : 0x2c, PIB(1));
			       ++card->armbout; HTXF_WAIT_LOCKED_RET(-EIO);
	outb(port, PIB(1));    ++card->armbout; HTXF_WAIT_LOCKED_RET(-EIO);
	spin_unlock_irqrestore(&card->lock, flags);
	return 0;
} //}}}

static int configure_proslic_port(struct openpci *card, int port)
{ //{{{
	/* Set Country - default to Australia */
	switch( configure_proslic_country(card, port, country) ) {
	    case 0: return RET_OK;
	    case -EINVAL:
		portwarn(card,port, "Country '%s' unknown, using default", country);
		if( configure_proslic_country(card, port, DEFAULT_COUNTRY) == 0 )
			return RET_OK;
	}
	return RET_FAIL;
} //}}}


static void vdaa_gain(int scaled_gain, unsigned char *maj, unsigned char *min)
{ //{{{
	int gain = (scaled_gain - 0x80)/10;
//	USHORT scaled_gain = (USHORT)(gain*10.0 + 0x80);

	// do the major
	if(gain >  12)     gain =  12;
	if(gain < -15)     gain = -15;
	if(gain >   0)     *maj = (unsigned char)gain;
	else if(gain == 0) *maj = 0;
	else {  //if (gain < 0)
			   *maj  = (unsigned char)(-gain);
			   *maj |= 1<<4;
	}
	// do the minor
	gain = (scaled_gain - 0x80) - (gain*10);
	if(gain >  9)      gain =  9;
	if(gain < -9)      gain = -9;
	if(gain >  0)      *min = (unsigned char)gain;
	else if(gain == 0) *min = 0;
	else {  //if (gain < 0)
			   *min  = (unsigned char)(-gain);
			   *min |= 1<<4;
	}
} //}}}

static unsigned short proslic_gain(int gain)
{ //{{{
	if(gain < 0)    gain = 0;
	if(gain > 0xff) gain = 0xff;
	return gain << 7;
} //}}}

static int set_play_gain(struct openpci *card, int port, int gain)
{ //{{{
	unsigned char maj,min;

	switch( card->chans[port].porttype ){
	    case VT_PORT_VDAA:
		vdaa_gain(gain,&maj,&min);
		if( ! write_reg_fxo(card, port, VDAR_TXGAIN2, maj)
		 || ! write_reg_fxo(card, port, VDAR_TXGAIN3, min) )
			return -EIO;
		dbgport(4,card,port,"set vdaa tx gain to %#x:%#x", maj, min);
		break;

	    case VT_PORT_PROSLIC:
		// Set just the digital gain, but there is an analogue
		// gain stage that we can manipulate too if required.
		// For SLIC, tx and rx are relative to the handset.
		if( ! write_indreg_fxs(card, port, PSI_RX_DIG_GAIN, proslic_gain(gain)) )
			return -EIO;
		dbgport(4,card,port,"set proslic rx gain to %#x", proslic_gain(gain));
		break;

	    default:
		return -EINVAL;
	}
	return 0;
} //}}}

static int set_rec_gain(struct openpci *card, int port, int gain)
{ //{{{
	unsigned char maj,min;

	switch( card->chans[port].porttype ){
	    case VT_PORT_VDAA:
		vdaa_gain(gain,&maj,&min);
		if( ! write_reg_fxo(card, port, VDAR_RXGAIN2, maj)
		 || ! write_reg_fxo(card, port, VDAR_RXGAIN3, min) )
			return -EIO;
		dbgport(4,card,port,"set vdaa rx gain to %#x:%#x", maj, min);
		break;

	    case VT_PORT_PROSLIC:
		// Set just the digital gain, but there is an analogue
		// gain stage that we can manilpulate too if required.
		// For SLIC, tx and rx are relative to the handset.
		if( ! write_indreg_fxs(card, port, PSI_TX_DIG_GAIN, proslic_gain(gain)) )
			return -EIO;
		dbgport(4,card,port,"set proslic tx gain to %#x", proslic_gain(gain));
		break;

	    default:
		return -EINVAL;
	}
	return 0;
} //}}}


static inline const char *porttype(struct openpci *card, int port)
{ //{{{
	switch( card->chans[port].porttype ) {
	    case VT_PORT_VDAA:    return "VDAA";
	    case VT_PORT_PROSLIC: return "ProSLIC";
	    case VT_PORT_EMPTY:   return "empty port";
	    default:              return "unknown type";
	}
} //}}}

static int set_hook_state(struct openpci *card, int port, int state)
{ //{{{
	unsigned long flags;
	int i;

	if( port != 0xff && card->chans[port].porttype != VT_PORT_VDAA ) {
		portwarn(card,port,"cant change %s hook state", porttype(card,port));
		return -EINVAL;
	}

	spin_lock_irqsave(&card->lock, flags);
	switch( state ) {
	    case HOOK_OFFHOOK:
		dbgport(5, card, port, "taking loop offhook");
		card->chans[port].state = CH_OFFHOOK;
		outb(0x2d,PIB(1)); ++card->armbout; HTXF_WAIT_LOCKED_RET(-EIO);
		break;

	    case HOOK_ONHOOK:
		dbgport(5,card, port, "putting loop onhook");
		card->chans[port].state = CH_IDLE;
		outb(0x2e,PIB(1)); ++card->armbout; HTXF_WAIT_LOCKED_RET(-EIO);
		break;

	    case HOOK_FASTOFF:
		dbgport(5,card, port, "putting loop offhook fast");
		card->chans[port].state = CH_OFFHOOK;
		outb(0x37,PIB(1)); ++card->armbout; HTXF_WAIT_LOCKED_RET(-EIO);
		break;
	}
	outb(port, PIB(1)); ++card->armbout; HTXF_WAIT_LOCKED_RET(-EIO);
	spin_unlock_irqrestore(&card->lock, flags);

	return 0;
} //}}}

static int arm_monitor(struct openpci *card, int on)
{ //{{{
	unsigned long flags;
	int i;

	spin_lock_irqsave(&card->lock, flags);
	outb( on ? 0x06 : 0x07, PIB(1) ); ++card->armbout; HTXF_WAIT_LOCKED();
	spin_unlock_irqrestore(&card->lock, flags);

	return RET_OK;
} //}}}


static int vtopenpci_set_country(struct vtboard *board, int port, const char *name)
{ //{{{
	struct openpci *card = container_of( board, struct openpci, board );

	switch( card->chans[port].porttype ){
	    case VT_PORT_VDAA:
		return configure_vdaa_country(card,port,name);

	    case VT_PORT_PROSLIC:
		return configure_proslic_country(card,port,name);
	}
	return 0;
} //}}}

static int vtopenpci_set_playgain(struct vtboard *board, int port, int gain)
{ //{{{
	struct openpci *card = container_of( board, struct openpci, board );
	return set_play_gain( card, port, gain );
} //}}}

static int vtopenpci_get_playgain(struct vtboard *board, int port, int *err)
{ //{{{
	struct openpci *card = container_of( board, struct openpci, board );
	signed char    maj,min;
	unsigned short gain;

	switch( card->chans[port].porttype ){
	    case VT_PORT_VDAA:
		if( read_reg_fxo(card, port, VDAR_TXGAIN2, &maj) &&
		    read_reg_fxo(card, port, VDAR_TXGAIN3, &min) )
		{
			dbgport(4,card,port,"got vdaa tx gain: %#x:%#x", maj, min);
			if( maj & (1<<4) ){
				maj &= 0xf;
				maj = -maj;
			}
			if( min & (1<<4) ){
				min &= 0x0f;
				min = -min;
			}
			*err = 0;
			return maj * 10 + min + 0x80;
		} else  *err = -EIO;
		break;

	    case VT_PORT_PROSLIC:
		// For SLIC, tx and rx are relative to the handset.
		if( read_indreg_fxs(card, port, PSI_RX_DIG_GAIN, &gain) ){
			dbgport(4,card,port,"got proslic rx gain: %#x", gain);
			*err = 0;
			return gain >> 7;
		} else  *err = -EIO;
		break;

	    default:
		*err = -EINVAL;
	}
	return 0;
} //}}}

static int vtopenpci_set_recgain(struct vtboard *board, int port, int gain)
{ //{{{
	struct openpci *card = container_of( board, struct openpci, board );
	return set_rec_gain( card, port, gain );
} //}}}

static int vtopenpci_get_recgain(struct vtboard *board, int port, int *err)
{ //{{{
	struct openpci *card = container_of( board, struct openpci, board );
	signed char    maj,min;
	unsigned short gain;

	switch( card->chans[port].porttype ){
	    case VT_PORT_VDAA:
		if( read_reg_fxo(card, port, VDAR_RXGAIN2, &maj) &&
		    read_reg_fxo(card, port, VDAR_RXGAIN3, &min) )
		{
			dbgport(4,card,port,"got vdaa rx gain: %#x:%#x", maj, min);
			if( maj & (1<<4) ){
				maj &= 0xf;
				maj = -maj;
			}
			if( min & (1<<4) ){
				min &= 0x0f;
				min = -min;
			}
			*err = 0;
			return maj * 10 + min + 0x80;
		} else *err = -EIO;
		break;

	    case VT_PORT_PROSLIC:
		// For SLIC, tx and rx are relative to the handset.
		if( read_indreg_fxs(card, port, PSI_TX_DIG_GAIN, &gain) ){
			dbgport(4,card,port,"got proslic tx gain: %#x", gain);
			*err = 0;
			return gain >> 7;
		} else *err = -EIO;
		break;

	    default:
		*err = -EINVAL;
	}
	return 0;
} //}}}

static int send_hookflash(struct vtboard *board, int port, int duration)
{ //{{{
	struct openpci *card = container_of( board, struct openpci, board );
	struct channel *chan = &card->chans[port];
	int ret;

	if( chan->state != CH_OFFHOOK ) {
		portwarn(card,port,"send_hookflash: not off-hook (%d)", chan->state);
		return -EINVAL;
	}

	ret = set_hook_state( card, port, HOOK_ONHOOK );
	if( ret ) return ret;

	msleep( duration );

	return set_hook_state( card, port, HOOK_FASTOFF );
} //}}}

static int vtopenpci_set_hook(struct vtboard *board, int port, int hookstate)
{ //{{{
	struct openpci *card = container_of( board, struct openpci, board );
	switch( hookstate )
	{
	    case HOOK_ONHOOK:
	    case HOOK_OFFHOOK:
	    case HOOK_FASTOFF:
		return set_hook_state(card, port, hookstate);

	    default:
		if( hookstate >= 50 && hookstate <= 1500 )
			return send_hookflash(board, port, hookstate);

		portwarn(card,port,"vtopenpci_set_hook: bad value %d", hookstate);
		return -EINVAL;
	}
} //}}}

static int vtopenpci_get_hook(struct vtboard *board, int port, int *err)
{ //{{{
	struct openpci *card = container_of( board, struct openpci, board );
	struct channel *chan = &card->chans[port];

	*err = chan->porttype ? 0 : -EINVAL;
	return chan->state;
} //}}}

static int vtopenpci_set_polarity(struct vtboard *board, int port, int polarity)
{ //{{{
	struct openpci *card = container_of( board, struct openpci, board );
	struct channel *chan = &card->chans[port];
	unsigned long flags;
	int i;

	if( chan->porttype != VT_PORT_PROSLIC ) return -EINVAL;
	if( polarity < -1 || polarity > 1 )     return -EINVAL;

	spin_lock_irqsave(&card->lock, flags);
	outb(0x23,PIB(1));     ++card->armbout; HTXF_WAIT_LOCKED_RET(-EIO);
	outb(port,PIB(1));     ++card->armbout; HTXF_WAIT_LOCKED_RET(-EIO);
	outb(polarity,PIB(1)); ++card->armbout; HTXF_WAIT_LOCKED_RET(-EIO);
	spin_unlock_irqrestore(&card->lock, flags);
	return 0;
} //}}}

static int do_get_polarity(struct openpci *card, int port, int *value)
{ //{{{
	unsigned long flags;
	int i;

	spin_lock_irqsave(&card->lock, flags);
	outb(0x24,PIB(1));  ++card->armbout; HTXF_WAIT_LOCKED();
	outb(port,PIB(1));  ++card->armbout; HTXF_WAIT_LOCKED();
	HRXF_WAIT_LOCKED(); ++card->armbin; *value = inb(PIB(1));
	spin_unlock_irqrestore(&card->lock, flags);

	return RET_OK;
} //}}}

static int vtopenpci_get_polarity(struct vtboard *board, int port, int *err)
{ //{{{
	struct openpci *card = container_of( board, struct openpci, board );
	struct channel *chan = &card->chans[port];
	int value = 0;

	if( chan->porttype != VT_PORT_PROSLIC ) {
		*err = -EINVAL;
		goto done;
	}
	*err = 0;
	if( ! do_get_polarity( card, port, &value ) ) *err = -EIO;

    done:
	return (signed char)value;
} //}}}

static int vtopenpci_get_linevolt(struct vtboard *board, int port, int *err)
{ //{{{
	struct openpci *card = container_of( board, struct openpci, board );
	struct channel *chan = &card->chans[port];
	unsigned char  value = 0;

	if( chan->porttype != VT_PORT_VDAA ) {
		*err = -EINVAL;
		goto done;
	}
	*err = 0;

	if( ! read_reg_fxo(card, port, VDAR_LOOPLV, &value) ) *err = -EIO;
	if( value > 127 ) value -= 256;

    done:
	return (signed char)value;
} //}}}

static int vtopenpci_set_ring(struct vtboard *board, int port, int ringing)
{ //{{{
	struct openpci *card = container_of( board, struct openpci, board );
	struct channel *chan = &card->chans[port];
	unsigned long flags;
	int i;

	if( chan->porttype != VT_PORT_PROSLIC ) return -EINVAL;

	// Is it a ringer cadence?
	if( ringing >= 0 && ringing <= 255 ) {
		spin_lock_irqsave(&card->lock, flags);
		outb(0x21,PIB(1));    ++card->armbout; HTXF_WAIT_LOCKED_RET(-EIO);
		outb(port,PIB(1));    ++card->armbout; HTXF_WAIT_LOCKED_RET(-EIO);
		outb(ringing,PIB(1)); ++card->armbout; HTXF_WAIT_LOCKED_RET(-EIO);
		spin_unlock_irqrestore(&card->lock, flags);
		return 0;
	}
	// A loop drop request perhaps?
	if( ringing <= -50 && ringing >= -1500 ) {
		int polarity = 0;
		int ret;

		if( ! do_get_polarity(card, port, &polarity) ) return -EIO;
		if((ret = vtopenpci_set_polarity(board, port, 0))) return ret;
		msleep( -ringing );
		return vtopenpci_set_polarity(board, port, (signed char)polarity);
	}
	return -EINVAL;
} //}}}

static int do_get_ring(struct openpci *card, int port, int *value)
{ //{{{
	unsigned long flags;
	int i;

	spin_lock_irqsave(&card->lock, flags);
	outb(0x22,PIB(1));  ++card->armbout; HTXF_WAIT_LOCKED();
	outb(port,PIB(1));  ++card->armbout; HTXF_WAIT_LOCKED();
	HRXF_WAIT_LOCKED(); ++card->armbin; *value = inb(PIB(1));
	spin_unlock_irqrestore(&card->lock, flags);

	return RET_OK;
} //}}}

static int vtopenpci_get_ring(struct vtboard *board, int port, int *err)
{ //{{{
	struct openpci *card = container_of( board, struct openpci, board );
	struct channel *chan = &card->chans[port];
	int value = 0;

	*err = chan->porttype ? 0 : -EINVAL;
	if( ! do_get_ring( card, port, &value ) ) *err = -EIO;
	return value;
} //}}}


static int vtopenpci_ioctl(struct vtboard *board, int port, unsigned int cmd, void *data, int length)
{ //{{{
	struct openpci *card = container_of( board, struct openpci, board );
	unsigned long   flags;
	int             i;
	int             tmp, tmp2;
	unsigned char   tmpch;

	if( cmd < VT_IOC_PPWDATA && ! card->chans[port].porttype ){
		portcrit(card,port,"not available");
		return -ENODEV;
	}
	switch(cmd){
	    case VT_IOC_HOOK:
	    {
		// If we're here to send a hook flash, then its probably
		// because we got a PC_CODEC_BREAK message in libvpb.
		// In that case, we want to send a DSP_CODEC_BKEN reply
		// to signal completion of the flash.
		// It's all a bit convoluted, but keep us compatible with
		// the OpenLine DSP.
		unsigned char mess[DSP_LCODEC_BKEN] = { DSP_LCODEC_BKEN,
							DSP_CODEC_BKEN,
							port };
		int ret;

		dbgport(3,card,port, "VT_IOC_HOOK %d", length);
		if( length < 50 )
			return vtopenpci_set_hook(board, port, length);

		if( length > 1500 ) return -EINVAL;

		ret = send_hookflash(board, port, length);

		// Wait for the off hook calibration to complete before notifying
		// the caller.  They will expect the line to be usable again when
		// this event is received.
		if( ret == 0 ) msleep(350);

		if( vt_send_event(&card->board, mess, mess[0]) )
			portcrit(card,port,"FAILED to forward DSP_CODEC_BKEN into MSGQ");

		return ret;
	    }
	    case VT_IOC_SET_RECGAIN:
		dbgport(3,card,port,"VT_IOC_SET_RECGAIN %s %#x",
			porttype(card,port), length );
		return set_rec_gain(card, port, length);

	    case VT_IOC_SET_PLAYGAIN:
		dbgport(3,card,port, "VT_IOC_SET_PLAYGAIN %s %#x",
			porttype(card,port), length );
		return set_play_gain(card, port, length);

	    case VT_IOC_SET_LOGGING:
		dbgport(3,card,port,"VT_IOC_SET_LOGGING %s",
				    length == 1 ? "on" : "off" );
		switch(card->chans[port].porttype){
		    case VT_PORT_VDAA:
			if(length == 1){
				// Mute transmit side (reg15), then:
				// make sure that on-hook monitoring is enabled (reg5)
				// and that the line is on-hook
				if( ! write_reg_fxo(card, port, VDAR_GAIN, 0x80)
				 || ! write_reg_fxo(card, port, VDAR_DAAC1, 0x08))
					return -EIO;

				card->chans[port].state = CH_MONITOR;
				dbgport(5,card,port,"enabled logging mode");
			} else {
				// Un-mute transmit side
				// No need to reset DAAC1, we usually want it
				// set for CID detection in other modes anyway.
				if( ! write_reg_fxo(card, port, VDAR_GAIN, 0x00) )
					return -EIO;
				card->chans[port].state = CH_IDLE;
				dbgport(5,card,port,"disabled logging mode");
			}
			break;

		    default:
			portwarn(card,port,"cant log from a %s port", porttype(card,port));
			return -EINVAL;
		}
		break;


	    case VT_IOC_SETFLASH:
		dbgport(4,card,port, "VT_IOC_SETFLASH 0x%x", length);

		switch (card->chans[port].porttype) {
		case VT_PORT_PROSLIC:
		case VT_PORT_VDAA:
			break;
		default:
			return -EINVAL;
		}

		spin_lock_irqsave(&card->lock, flags);
		outb(0x27,PIB(1));  ++card->armbout; HTXF_WAIT_LOCKED_RET(-EIO);
		outb(port,PIB(1));  ++card->armbout; HTXF_WAIT_LOCKED_RET(-EIO);
		outb((length & 0xffff) / 10, PIB(1));
				    ++card->armbout; HTXF_WAIT_LOCKED_RET(-EIO);
		outb((length >> 16) / 10, PIB(1));
				    ++card->armbout; HTXF_WAIT_LOCKED_RET(-EIO);
		spin_unlock_irqrestore(&card->lock, flags);
		break;

	    case VT_IOC_SETHOOKTHRESH:
		dbgport(4,card,port, "VT_IOC_SETHOOKTHRESH 0x%x", length);

		if( card->chans[port].porttype != VT_PORT_VDAA ) return -EINVAL;

		spin_lock_irqsave(&card->lock, flags);
		outb(0x25,PIB(1));          ++card->armbout; HTXF_WAIT_LOCKED_RET(-EIO);
		outb(port,PIB(1));          ++card->armbout; HTXF_WAIT_LOCKED_RET(-EIO);
		outb(length & 0xff,PIB(1)); ++card->armbout; HTXF_WAIT_LOCKED_RET(-EIO);
		outb(length >> 16, PIB(1)); ++card->armbout; HTXF_WAIT_LOCKED_RET(-EIO);
		spin_unlock_irqrestore(&card->lock, flags);
		break;

	    case VT_IOC_SETLOOPDROP:
		dbgport(4,card,port, "VT_IOC_SETLOOPDROP 0x%x", length);

		if( card->chans[port].porttype != VT_PORT_VDAA ) return -EINVAL;

		spin_lock_irqsave(&card->lock, flags);
		outb(0x26,PIB(1));  ++card->armbout; HTXF_WAIT_LOCKED_RET(-EIO);
		outb(port,PIB(1));  ++card->armbout; HTXF_WAIT_LOCKED_RET(-EIO);
		outb((length & 0xffff) / 10, PIB(1));
				    ++card->armbout; HTXF_WAIT_LOCKED_RET(-EIO);
		outb((length >> 16) / (card->firmware < 15 ? 10 : 100), PIB(1));
				    ++card->armbout; HTXF_WAIT_LOCKED_RET(-EIO);
		spin_unlock_irqrestore(&card->lock, flags);
		break;

	    case VT_IOC_SETVDAAIMPEDANCE:
		dbgport(3,card,port, "VT_IOC_SETVDAAIMPEDANCE %s %#x",
			porttype(card,port), length );

		if( ! write_reg_fxo(card, port, VDAR_ACTERMC, length) ) {
			cardcrit(card->cardnum,
				 "ioctl VDAR_ACTERMC=%x failed on port %d",
				 length, port);
			return -EIO;
		}
		break;


	    case VT_IOC_ARMWR:
		dbgport(4,card,port,"VT_IOC_ARMWR %#x", length);
		spin_lock_irqsave(&card->lock, flags);
		outb(length, PIB(1)); ++card->armbout; HTXF_WAIT_LOCKED_RET(-EIO);
		spin_unlock_irqrestore(&card->lock, flags);
		break;

	    case VT_IOC_ARMR:
		spin_lock_irqsave(&card->lock, flags);
		HRXF_WAIT_LOCKED_RET(-EIO); tmp = inb(PIB(1)); ++card->armbin;
		spin_unlock_irqrestore(&card->lock, flags);
		dbgport(4,card,port,"VT_IOC_ARMR %#x", tmp);
		if(copy_to_user(data,&tmp,sizeof(int))){
			portcrit(card,port,"VT_IOC_ARMR: failed to copy to user");
			return -EFAULT;
		}
		break;

	    case VT_IOC_JTAGWRITE:
		spin_lock_irqsave(&card->lock, flags);
		// Select CPLD or ARM using ..KO_WRITE apply to HCT241 enable pin's
		tmp = card->ppdatamask;
		// Map the Tiger AUXn pin's to Xilinx DLC5 (JTAG Parallel Cable III)
		tmp |= (length & 0x10 ? 0 : 0x80 )  // Reset to Aux7        D4
		    |  (length & 4 ? 0x10 : 0 )     // TMS to Aux4          D2
		    |  (length & 2 ? 0x20 : 0 )     // TCK to AUX5          D1
		    |  (length & 1 ? 0x40 : 0 );    // TDI to AUX6          D0
		dbgport(5,card,port,"VT_IOC_JTAGWRITE %#x %#x", length, tmp);
		outb(tmp, TJ_AUXD);
		tmp = inb(TJ_CNTL); // Apply reset signal as required (for ARM JTag only).
		if (length & 0x10) {
			outb(tmp | 0x01, TJ_CNTL);
			dbgport(6,card,port,"run mode ERST = high");
		} else {
			outb(tmp & 0xfe, TJ_CNTL);
			dbgport(6,card,port,"reset mode ERST = low");
		}
		spin_unlock_irqrestore(&card->lock, flags);
		break;

	    case VT_IOC_JTAGSTATUS:
		spin_lock_irqsave(&card->lock, flags);
		tmpch = (inb(TJ_AUXR) & 0x04 ? 0x10 : 0 );
		spin_unlock_irqrestore(&card->lock, flags);
		dbgport(5,card,port,"VT_IOC_JTAGSTATUS %#x", tmpch);
		if(copy_to_user(data,&tmpch,sizeof(char))) return -EFAULT;
		break;

	    case VT_IOC_PPRELEASE:
		dbgport(4,card,port,"VT_IOC_PPRELEASE");
		card->ppusage = 0;
		break;

	    case VT_IOC_PPCLAIM:
		// We need to lock this atomically if we are serious,
		// but contention for this should be _extremely_ rare
		// so look elsewhere for the premium sporting events.
		dbgport(4,card,port,"VT_IOC_PPCLAIM");
		if(card->ppusage) return -EBUSY;
		card->ppusage = 1;
		break;

	    //XXX Remove the extra copy from this one too
	    case VT_IOC_OPCI_ARM_RST:
		if(copy_from_user(&tmpch,data,sizeof(char)))
			return -EFAULT;

		spin_lock_irqsave(&card->lock, flags);
		tmp2 = inb(TJ_CNTL);
		outb(tmp2 & (tmpch ? 0x01 : 0xfe), TJ_CNTL);
		spin_unlock_irqrestore(&card->lock, flags);

		dbgport(4,card,port,"VT_IOC_OPCI_ARM_RST set ERST %s",
				    tmpch ? "High" : "Low");
		break;

	    //XXX We need to keep these next three for a little longer,
	    //    but don't use them in new code.
	    case VT_IOC_PPWDATA:
		if(copy_from_user(&tmpch,data,sizeof(char))) return -EFAULT;
		dbgport(5,card,port,"VT_IOC_PPWDATA %#x", tmpch);

		// Select CPLD or ARM using ..KO_WRITE apply to HCT241 enable pin's
		tmp = card->ppdatamask;
		// Map the Tiger AUXn pin's to Xilinx DLC5 (JTAG Parallel Cable III)
		tmp |= (tmpch & 0x10 ? 0 : 0x80 )  // Reset to Aux7        D4
		    |  (tmpch & 4 ? 0x10 : 0 )     // TMS to Aux4          D2
		    |  (tmpch & 2 ? 0x20 : 0 )     // TCK to AUX5          D1
		    |  (tmpch & 1 ? 0x40 : 0 );    // TDI to AUX6          D0
		spin_lock_irqsave(&card->lock, flags);
		outb(tmp, TJ_AUXD);
		tmp = inb(TJ_CNTL); // Apply reset signal as required (for ARM JTag only).
		if (tmpch & 0x10) {
			outb(tmp | 0x01, TJ_CNTL);
			dbgport(6,card,port,"run mode ERST = high");
		} else {
			outb(tmp & 0xfe, TJ_CNTL);
			dbgport(6,card,port,"reset mode ERST = low");
		}
		spin_unlock_irqrestore(&card->lock, flags);
		break;

	    case VT_IOC_PPRSTATUS:
		spin_lock_irqsave(&card->lock, flags);
		tmp = (inb(TJ_AUXR) & 0x04 ? 0x10 : 0 );
		spin_unlock_irqrestore(&card->lock, flags);
		dbgport(5,card,port,"VT_IOC_PPRSTATUS %#x", tmp);
		if(copy_to_user(data,&tmp,sizeof(int))) return -EFAULT;
		break;

	    case VT_IOC_ARMW:
		if(copy_from_user(&tmp,data,sizeof(int))){
			portcrit(card,port,"VT_IOC_ARMW: failed to copy from user");
			return -EFAULT;
		}
		dbgport(4,card,port,"VT_IOC_ARMW %#x", tmp);
		spin_lock_irqsave(&card->lock, flags);
		outb(tmp, PIB(1)); ++card->armbout; HTXF_WAIT_LOCKED_RET(-EIO);
		spin_unlock_irqrestore(&card->lock, flags);
		break;

	    #if 0
	    case VT_IOC_OPCI_ARM_RESET:
		portcrit(card,port,"VT_IOC_OPCI_ARM_RESET is obsolete");
		return -EINVAL;
		// This ioctl is no longer used and apparently never really
		// worked properly anyway.  Use ping_arm to flush it instead.
		reset_arm(card);
		if( ! flush_arm(card) ){
			portcrit(card,port,"failed to flush ARM");
			return -EIO;
		}
		cardinfo(card->cardnum, "ARM Reset");
		break;
	    #endif

	    #if 0
	    case VT_IOC_PORT_RST: //{{{
		dbgport(4,card,port,"VT_IOC_PORT_RST");
		if( card->chans[port].porttype == VT_PORT_VDAA
		 && ! configure_vdaa_port(card,port) ){
			portwarn(card,port,"VT_IOC_PORT_RST - failed to reset VDAA port");
			return -EIO;
		}
		//spin_lock_irqsave(&card->lock, flags);
		if(card->firmware > 2){
			/* disable module monitoring on Arm */
			arm_monitor(card, 0);
		}
		disable_interrupts(card);
		if (card->chans[port].porttype == VT_PORT_VDAA){
			if( ! configure_vdaa_port(card,port) ){
				cardinfo(card->cardnum,
					 "%02d: VT_IOC_PORT_RST failed to reset VDAA",
					 port);
				return -EIO;
			}
		}
		enable_interrupts(card);
		if(card->firmware > 2){
			/* Ping Arm to make sure its buffers are clean */
			if( ! ping_arm(card) )
				cardinfo(card->cardnum, "ping_arm failed");
			/* enable module monitoring on Arm */
			arm_monitor(card, 1);
			cardinfo(card->cardnum,
				 "%02d: enabling Arm to monitor ports", port);
		}
		//spin_unlock_irqrestore(&card->lock, flags);
		break;

	    case VT_IOC_REGWRITE:
		VT_REG_RW   reg;
		if( copy_from_user(&reg,(void *)data,sizeof(VT_REG_RW)) ){
			portcrit(card,port,"VT_IOC_REGWRITE copy of VT_REG_RW failed");
			return -EFAULT;
		}
		dbgport(4,card,port,"VT_IOC_REGWRITE %s %x = %x",
			porttype(card,port), reg.reg, reg.value );
		if( card->chans[port].porttype == VT_PORT_VDAA
		 && ! write_reg_fxo(card, port, reg.reg, reg.value) )
			return -EIO;
		if( card->chans[port].porttype == VT_PORT_PROSLIC
		 && ! write_reg_fxs(card, port, reg.reg, reg.value) )
			return -EIO;
		break;

	    case VT_IOC_REGREAD:
		VT_REG_RW   reg;
		if( copy_from_user(&reg,(void *)data,sizeof(VT_REG_RW)) ){
			portcrit(card,port,"VT_IOC_REGREAD copy of VT_REG_RW failed");
			return -EFAULT;
		}
		if( card->chans[port].porttype == VT_PORT_VDAA
		 && ! read_reg_fxo(card, port, reg.reg, &reg.value) ){
			return -EIO;
		} else if( card->chans[port].porttype == VT_PORT_PROSLIC
			&& ! read_reg_fxs(card, port, reg.reg, &reg.value) ){
			return -EIO;
		}
		dbgport(4,card,port,"VT_IOC_REGREAD %s %x = %x",
			porttype(card,port), reg.reg, reg.value );
		if(copy_to_user(data,&reg,sizeof(VT_REG_RW))) return -EFAULT;
		break; //}}}
	    #endif

	    default:
		portcrit(card,port,"unknown ioctl type %d", cmd);
		return -EINVAL;
	}
	return 0;
} //}}}


/* You must hold the card spinlock to call this function */
static int __get_module_info(struct openpci *card, int module, int field, char *value)
{ //{{{
	int i;
	int x=0;
	int count=0;

	*value = 0;
	outb(0x40,   PIB(1)); ++card->armbout; HTXF_WAIT();
	outb(module, PIB(1)); ++card->armbout; HTXF_WAIT();
	outb(field,  PIB(1)); ++card->armbout; HTXF_WAIT();
	HRXF_WAIT(); count = inb(PIB(1)); ++card->armbin;
	if (count > VT_MOD_INFO_SIZE){
		cardcrit(card->cardnum, "Too many bytes of module(%d:%d) data %d/%d",
					 module, field, count, VT_MOD_INFO_SIZE);
		return RET_FAIL;
	}
	dbginfo(4,card->cardnum, "get_module_info(%d:%d): byte count %d",
				 module, field, count);
	for(; x < count; ++x){
		HRXF_WAIT(); *value = inb(PIB(1)); ++card->armbin;
		dbginfo(4,card->cardnum, "get_module_info(%d:%d): byte %d => 0x%02x",
					 module, field, x, *value);
		++value;
	}
	return RET_OK;
} //}}}

static int get_port_type(struct openpci *card, int port)
{ //{{{
	int i, type;
	unsigned long flags;

	spin_lock_irqsave(&card->lock, flags);
	outb(0x20, PIB(1)); ++card->armbout; HTXF_WAIT_LOCKED_RET(VT_PORT_EMPTY);
	outb(port, PIB(1)); ++card->armbout; HTXF_WAIT_LOCKED_RET(VT_PORT_EMPTY);
	HRXF_WAIT_LOCKED_RET(VT_PORT_EMPTY); type = inb(PIB(1)); ++card->armbin;
	spin_unlock_irqrestore(&card->lock, flags);

	return type;
} //}}}

static int check_ports(struct openpci *card)
{ //{{{
	int portcount = 0;
	int i = 0;

	for(; i < MAX_PORTS; ++i ){
		struct channel *chan = &card->chans[i];

		chan->porttype = get_port_type(card, i);
		dbgport(1,card,i,"%s", porttype(card,i));

		if( chan->porttype != VT_PORT_EMPTY ){
			chan->codec = VT_MULAW;
			++portcount;
		}
	}
	// we 'succeed' if any ports were discovered.
	return portcount;
} //}}}


// Do not call this from an interrupt context, it may (indirectly) sleep.
static int configure_ports(struct openpci *card)
{ //{{{
	int portcount = 0;
	int i = 0;

	for(; i < MAX_PORTS; ++i){
	    switch( card->chans[i].porttype ){
		case VT_PORT_VDAA:
			if( ! configure_vdaa_port(card,i) ){
				cardcrit(card->cardnum,
					 "FAILED to configure vdaa port %d.  Disabled.", i);
				card->chans[i].porttype = VT_PORT_EMPTY;
			} else
				++portcount;
			break;

		case VT_PORT_PROSLIC:
			if( ! configure_proslic_port(card,i) ){
				cardcrit(card->cardnum,
					 "FAILED to configure proslic port %d.  Disabled.", i);
				card->chans[i].porttype = VT_PORT_EMPTY;
			} else
				++portcount;
			break;

		case VT_PORT_EMPTY:
			break;

		default:
			portcrit(card,i,"unknown port type %d unconfigured",
				 card->chans[i].porttype);
			break;
	    }
	}

	//XXX We disable these for now as this release has no way to easily
	//    and cleanly replace the software detector with the hardware ones.
	//    But they are available now and future releases will exploit them.
	if( set_proslic_dtmf_detector(card, 0xff, 0) < 0 )
		cardwarn(card,"Failed to disable hardware dtmf detectors");

	// we 'succeed' if any ports were configured successfully.
	return portcount;
} //}}}

/* You must hold the card spinlock to call this function */
static int __get_arm_id(struct openpci *card, int field, char *value)
{ //{{{
	int i;
	int x=0;
	int count=0;

	outb(0x01,  PIB(1)); ++card->armbout; HTXF_WAIT();
	outb(field, PIB(1)); ++card->armbout; HTXF_WAIT();
	HRXF_WAIT(); count = inb(PIB(1)); ++card->armbin;
	if (count > ID_DATA_MAXSIZE){
		cardcrit(card->cardnum, "Too many bytes of id(%d) data %d/%d",
					 field, count, ID_DATA_MAXSIZE);
		return RET_FAIL;
	}
	dbginfo(4,card->cardnum, "get_arm_id(%d): byte count %d",field,count);
	for(; x < count; ++x){
		HRXF_WAIT(); *value = inb(PIB(1)); ++card->armbin;
		dbginfo(4,card->cardnum, "get_arm_id(%d): byte %d => 0x%02x",field,x,*value);
		++value;
	}
	return RET_OK;
} //}}}

static int check_arm(struct openpci *card)
{ //{{{
	char model[ID_DATA_MAXSIZE+1] = { 0 };
	char date[ID_DATA_MAXSIZE+1]  = { 0 };
	const char *pciname = NULL;
	unsigned long flags;
	int i=0, tmp=0;

	spin_lock_irqsave(&card->lock, flags);
	card->firmware = 0;
	while(tmp != 0x88 && ++i < 1000) {
		outb(0x88, PIB(0)); ++card->armbout;
		mdelay(1);
		tmp = inb(PIB(1)); ++card->armbin;
	}
	if(i>=1000) goto limbo;
	dbginfo(1,card->cardnum, "Arm responded on attempt %d",i);

	// Flush out the queue if we sent several pings before a response.
	if(i>1)	__ping_arm(card);

	if( ! __get_arm_id(card, 0, model) )  goto hell;
	cardinfo(card->cardnum, "  model: %s", model);

	if (sscanf(model, "OpenPCIe8.%02d", &card->firmware) == 1)
		pciname = get_pci_boardname(&openpci_idtable[0]);
	else if (sscanf(model, "OpenPCI8.%02d", &card->firmware) == 1)
		pciname = get_pci_boardname(&openpci_idtable[1]);

	/* If in doubt, trust the firmware ... */
	if (pciname && card->board.name != pciname) {
		cardwarn(card, "PCI id says board is %s, firmware says %s\n",
						card->board.name, model);
		card->board.name = pciname;
	}

	if( ! __get_arm_id(card, 1, date) )   goto hell;
	cardinfo(card->cardnum, "   date: %s", date);

	if( ! __get_arm_id(card, 2, card->board.serial) ) goto hell;
	cardinfo(card->cardnum, "     id: %s", card->board.serial);

	spin_unlock_irqrestore(&card->lock, flags);
	return RET_OK;

    hell:
	spin_unlock_irqrestore(&card->lock, flags);
	cardwarn(card, "Found ARM processor, dumb firmware.");
	return RET_OK;

    limbo:
	spin_unlock_irqrestore(&card->lock, flags);
	return RET_FAIL;
} //}}}


static void arm_event(struct openpci *card, unsigned char *msg)
{ //{{{
	unsigned char mess[3];

	// WARNING: Enabling the debug output here can make the time taken
	//          to service an arm interrupt > 5ms.  This will cause loss
	//          and corruption of the audio stream.

	if( msg[1] >= DSP_DTMF_0 && msg[1] <= DSP_DTMF_HASH ) {
		dbgport(3,card,msg[0],"DTMF 0x%x", msg[1] & 0x0f);
		mess[0] = DSP_LDTMF_DIGIT;
		mess[1] = msg[1];
	}
	else switch(msg[1])
	{
	    case DSP_TONEG:
		dbgport(3,card,msg[0],"Tone end");
		mess[0] = DSP_LTONEG;
		mess[1] = DSP_TONEG;
		break;

	    case DSP_CODEC_RING:
		dbgport(3,card,msg[0],"Ring On");
		mess[0] = DSP_LCODEC_RING;
		mess[1] = DSP_CODEC_RING;
		break;
	    case DSP_RING_OFF:
		dbgport(3,card,msg[0],"Ring Off");
		mess[0] = DSP_LRING_OFF;
		mess[1] = DSP_RING_OFF;
		break;
	    case DSP_DROP:
		dbgport(3,card,msg[0],"Loop Drop");
		mess[0] = DSP_LDROP;
		mess[1] = DSP_DROP;
		break;
	    case DSP_LOOP_OFFHOOK:
		dbgport(3,card,msg[0],"Loop OffHook");
		mess[0] = DSP_LLOOP_OFFHOOK;
		mess[1] = DSP_LOOP_OFFHOOK;
		break;
	    case DSP_LOOP_ONHOOK:
		dbgport(3,card,msg[0],"Loop OnHook");
		mess[0] = DSP_LLOOP_ONHOOK;
		mess[1] = DSP_LOOP_ONHOOK;
		break;
	    case DSP_LOOP_POLARITY:
		dbgport(3,card,msg[0],"Loop Polarity");
		mess[0] = DSP_LLOOP_POLARITY;
		mess[1] = DSP_LOOP_POLARITY;
		break;
	    case DSP_LOOP_NOBATT:
		dbgport(3,card,msg[0],"Loop no battery");
		mess[0] = DSP_LLOOP_NOBATT;
		mess[1] = DSP_LOOP_NOBATT;
		break;
	    case DSP_LOOP_FLASH:
		dbgport(3,card,msg[0],"Loop Flash");
		mess[0] = DSP_LLOOP_FLASH;
		mess[1] = DSP_LOOP_FLASH;
		break;

	    case DSP_CODEC_HKOFF:
	    {
		struct channel *chan = &card->chans[msg[0]];
		dbgport(3,card,msg[0],"Station OffHook");
		mess[0] = DSP_LCODEC_HKOFF;
		mess[1] = DSP_CODEC_HKOFF;
		chan->state = CH_OFFHOOK;  // start audio 
		break;
	    }
	    case DSP_CODEC_HKON:
		dbgport(3,card,msg[0],"Station OnHook");
		mess[0] = DSP_LCODEC_HKON;
		mess[1] = DSP_CODEC_HKON;
		card->chans[msg[0]].state = CH_IDLE;
		break;
	    case DSP_CODEC_FLASH:
		dbgport(3,card,msg[0],"Station Flash");
		mess[0] = DSP_LCODEC_FLASH;
		mess[1] = DSP_CODEC_FLASH;
		break;

	    case DSP_ERROR_PROSLIC:
		switch( msg[2] )
		{
		    case 22:
			portcrit(card,msg[0],"FAILED ProSLIC calibration, station off-hook");
			break;

		    default:
			portcrit(card,msg[0],"ProSLIC error %d", msg[2]);
		}
		return;
	    case DSP_PROSLIC_SANITY:
		portcrit(card,msg[0],"ProSLIC has gone INSANE");
		return;
	    case DSP_PROSLIC_PWR_ALARM:
	    {
		char errbuf[32] = " Unknown", *p = errbuf;
		int i = 49;

		msg[2] >>= 2;
		for(; i < 55; ++i, msg[2] >>= 1 )
		    if(msg[2] & 1){ *(++p)='Q'; *(++p)=i; *(++p)=','; }
		if( p != errbuf ) *p = '\0';
		portcrit(card,msg[0],"ProSlic power ALARM:%s",errbuf);
		return;
	    }

	    case DSP_ERROR_VDAA:
		portcrit(card,msg[0],"VDAA error %d", msg[2]);
		return;
	    case DSP_VDAA_ISO_FRAME_E:
		portcrit(card,msg[0],"VDAA lost ISO-Cap frame lock");
		return;

	    case DSP_ERROR_OPCI:
		portcrit(card,msg[0],"OPCI error %d", msg[2]);
		return;

	    default:
		portwarn(card,msg[0],"Unknown message [%d] from Arm", msg[1]);
		return;
	}
	mess[2] = msg[0];
	if( vt_send_event(&card->board, mess, mess[0]) )
		portcrit(card,msg[0],"FAILED to forward arm event into MSGQ");
} //}}}

/* You must hold the card spinlock to call this function */
static inline int __read_arm_byte( struct openpci *card, unsigned char *msg )
{ //{{{
	int i;

	HRXF_WAIT(); *msg = inb(PIB(1)); ++card->armbin;
	return RET_OK;
} //}}}

static inline int read_arm_msg( struct openpci *card, unsigned char *msg )
{ //{{{
	unsigned long flags;
	int i, d, count;
	int ret = RET_OK;

	spin_lock_irqsave(&card->lock, flags);
	outb(0x08, PIB(1)); ++card->armbout; HTXF_WAIT_LOCKED();
	//XXX Do we need to clear the interrupt flag even if this fails?
	HRXF_WAIT_LOCKED(); count = inb(PIB(1)); ++card->armbin;
	if( unlikely(count == 0) ) {
		cardwarn(card, "EMPTY arm message");
		ret = RET_FAIL;
	} else if( unlikely(count < 3 || count > 4) ) {
		cardcrit(card->cardnum, "BOGUS arm message size %d, flushing queue", count);
		// NB: This may take a while (up to 500usec or more) to complete
		//     and we are in the isr at present when this is called, so
		//     we may miss an interrupt or two while this is done in the
		//     bottom half, but we are already in trouble, so...
		d = debug; debug = 5; __ping_arm( card ); debug = d;
		ret = RET_FAIL;
	} else while( --count ){
		if( ! __read_arm_byte(card, msg) ){
			cardcrit(card->cardnum,
				 "Failed to read arm message %d more bytes expected",
				 count);
			ret = RET_FAIL;
			break;
		}
		++msg;
	}
	outb(0x09, PIB(1)); ++card->armbout; HTXF_WAIT_LOCKED();
	spin_unlock_irqrestore(&card->lock, flags);
	return ret;
} //}}}

static void openpci_arm_work( void *cardptr )
{ //{{{
	struct openpci *card = (struct openpci*)cardptr;
	unsigned char armmsg[4];

	if( read_arm_msg(card, armmsg) ) arm_event(card, armmsg);
} //}}}


/* You must hold the card spinlock to call this function */
static void __start_dma(struct openpci *card)
{ //{{{
	outb(0x0f, TJ_CNTL);       // Reset TDM bus
	udelay(100);               // Wait two cycles of the ARM slow clock
	outb(TJ_RUNFLAGS, TJ_CNTL);
	outb(0x01, TJ_OPER);
} //}}}


static void openpci_write(struct openpci *card, int status)
{ //{{{
	struct vtboard *board = &card->board;
	struct channel *chans = card->chans;
	volatile unsigned int *writeblock;
	int x,y;

	if(status & 0x01) {
		writeblock = card->writeblock;

	    #if VERBOSE_TIMING
	    //{{{
		if(status & 0x02) {
			++card->write_overflow;
			#if 0
			cardcrit(card->cardnum,
				 "Write buffer overrun at interrupt %u (%lld),"
				 " flags = %x, current = %x:%x",
				 card->interrupt_count, card->time_tj_last_rel,
				 status, inb(TJ_DMAWC), inb(TJ_DMARC));
			#endif
		}
	    //}}}
	    #endif
	} else if( status & 0x02 ) {
		writeblock = card->writeblock + FRAME*2;
	} else {
		cardcrit(card->cardnum, "bad write interrupt flags %x, at %x / %d",
			status, inb(TJ_DMAWC), card->interrupt_count);
		return;
	}
	vt_write(board);
	for(y=0,x=0; x < FRAME; ++x) {
		if(chans[0].porttype) writeblock[y]  = chans[0].txbuf[x] << 24;
		else writeblock[y] = 0;
		if(chans[1].porttype) writeblock[y] |= chans[1].txbuf[x] << 16;
		if(chans[2].porttype) writeblock[y] |= chans[2].txbuf[x] << 8;
		if(chans[3].porttype) writeblock[y] |= chans[3].txbuf[x];
		++y;
		writeblock[y] = 0;
		if(chans[4].porttype) writeblock[y] |= chans[4].txbuf[x] << 24;
		// ensure first port is never 0x00
		if( ! writeblock[y] ) writeblock[y] = 0x01000000;
		if(chans[5].porttype) writeblock[y] |= chans[5].txbuf[x] << 16;
		if(chans[6].porttype) writeblock[y] |= chans[6].txbuf[x] << 8;
		if(chans[7].porttype) writeblock[y] |= chans[7].txbuf[x];
		++y;
	}
} //}}}

static void openpci_read(struct openpci *card, int status)
{ //{{{{
	struct vtboard *board = &card->board;
	struct channel *chans = card->chans;
	volatile unsigned int *readblock;
	int x,y;

	if(status & 0x08) {
		readblock = card->readblock + FRAME*2;

	    #if VERBOSE_TIMING
	    //{{{
		if(status & 0x04) {
			++card->read_overflow;
			#if 0
			cardcrit(card->cardnum,
				 "Read buffer overrun at interrupt %u (%lld),"
				 " flags = %x, current = %x:%x",
				 card->interrupt_count, card->time_tj_last_rel,
				 status, inb(TJ_DMARC), inb(TJ_DMAWC));
			#endif
		}
	    //}}}
	    #endif
	} else if( status & 0x04 ) {
		readblock = card->readblock;
	} else {
		cardcrit(card->cardnum, "bad read interrupt flags %x, at %x / %d",
			status, inb(TJ_DMARC), card->interrupt_count);
		return;
	}
	for(y=0,x=0; x < FRAME; ++x) {
		if(chans[4].porttype) chans[4].rxbuf[x] = (readblock[y] >> 24) & 0xff;
		if(chans[5].porttype) chans[5].rxbuf[x] = (readblock[y] >> 16) & 0xff;
		if(chans[6].porttype) chans[6].rxbuf[x] = (readblock[y] >> 8) & 0xff;
		if(chans[7].porttype) chans[7].rxbuf[x] =  readblock[y] & 0xff;
		++y;
		if(chans[0].porttype) chans[0].rxbuf[x] = (readblock[y] >> 24) & 0xff;
		if(chans[1].porttype) chans[1].rxbuf[x] = (readblock[y] >> 16) & 0xff;
		if(chans[2].porttype) chans[2].rxbuf[x] = (readblock[y] >> 8) & 0xff;
		if(chans[3].porttype) chans[3].rxbuf[x] =  readblock[y] & 0xff;
		++y;
	}
	vt_read(board);
} //}}}

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,19)
static irqreturn_t openpci_isr(int irq, void *dev_id, struct pt_regs *regs)
#else
static irqreturn_t openpci_isr(int irq, void *dev_id)
#endif
{ //{{{
	struct openpci *card = dev_id;
	unsigned long flags;
	unsigned char status;

    #if VERBOSE_TIMING
    //{{{
	unsigned long long start_isr, end_isr, time_tmp;

	rdtscll(start_isr);

	if( card->time_tj_last != 0 ){
	    time_tmp = start_isr - card->time_tj_last;
	    if( card->delay_tj_max < time_tmp ){
		card->delay_tj_max = time_tmp;
		#if 0
		cardinfo(card->cardnum, "tj interrupt delay time = %lld (%ldms)",
                                        card->delay_tj_max,
                                        (long)card->delay_tj_max / cpu_khz);
		#endif
	    }
	    card->time_tj_last_rel = time_tmp;
	}
	card->time_tj_last = start_isr;
    //}}}
    #endif

	/* Check what interrupted us */
	spin_lock_irqsave(&card->lock, flags);
	status = inb(TJ_INTSTAT);

	/* No TJ status flags?  Check for an ARM interrupt */
	if( unlikely( ! status ) ) {

		/* Was it really our arm? */
		if(unlikely(inb(TJ_AUXR) & 0x2)) {
			spin_unlock_irqrestore(&card->lock, flags);
			return IRQ_NONE;
		}
		spin_unlock_irqrestore(&card->lock, flags);
		++card->arm_interrupt_count;

		//XXX schedule_work( &card->arm_work );
		//queue_work( openpci_workqueue, &card->arm_work );
		openpci_arm_work( card );

	    #if VERBOSE_TIMING
	    //{{{
		rdtscll( end_isr );
		time_tmp = end_isr - start_isr;
		if( time_tmp > card->time_arm_max ) {
			card->time_arm_max = time_tmp;
			#if 0
			cardinfo(card->cardnum, "arm interrupt service time = %lld (%ldms)",
						 card->time_arm_max,
						 (long)card->time_arm_max / cpu_khz);
			#endif
		}
	    //}}}
	    #endif

            #if 0
		// Servicing arm interupts can take a little while...
		// Do we need to re-check the DMA status before we return?
		// Can we miss an interrupt if we don't?
		spin_lock_irqsave(&card->lock, flags);
		status = inb(TJ_INTSTAT);
		outb(status, TJ_INTSTAT);
		if( ! status ) {
			spin_unlock_irqrestore(&card->lock, flags);
			return IRQ_HANDLED;
		}
		dbginfo(3,card->cardnum, "LATE TJ service %x", status);
            #else
                return IRQ_HANDLED;
            #endif
	}

	if (unlikely((status & 0x03) == 0))
		status |= status >> 2 & 0x03;

	outb(status, TJ_INTSTAT);

	if(unlikely(status & 0x10)) {
		/* To recover the PCI master state machine set the reset bit
		   in Reg0 to 1 and reset it to 0, also enable DMA operation
		   by setting Reg1 bit0 to 1.
		   Just starting the DMA should do all this
		 */
		__start_dma(card);
		spin_unlock_irqrestore(&card->lock, flags);

		cardcrit(card->cardnum, "PCI Master Abort.");
		return IRQ_HANDLED;
	}
	spin_unlock_irqrestore(&card->lock, flags);

	if(unlikely(status & 0x20)) {
		/* PCI Target abort */
		cardcrit(card->cardnum, "PCI Target Abort.");
		return IRQ_HANDLED;
	}
	if(status & 0x03) {
		openpci_write(card, status);
	}
	if(status & 0x0c) {
		/* Only count read interrupts, we use this count for timing
		 * elsewhere, and the 'per 1ms' assumption breaks if we clock
		 * both read and write, which may arrive independently */
		++card->interrupt_count;

		/* Transfer Audio */
		/* de-multiplex to tmp buffers, need to add tmp buffers to structure some where */
		/* pass to vtcore to put into user space */
		openpci_read(card, status);
	}

    #if VERBOSE_TIMING
    //{{{
	rdtscll( end_isr );
	time_tmp = end_isr - start_isr;
	if( time_tmp > card->time_tj_max ) {
		card->time_tj_max = time_tmp;
		#if 0
		cardinfo(card->cardnum, "tj interrupt service time = %lld (%ldms)",
					card->time_tj_max,
					(long)card->time_tj_max / cpu_khz);
		#endif
	}
	if(unlikely( ! (card->interrupt_count % 10000) )) {
		// NOTE: just printing this out can add 5 - 8ms of delay.
		//       so don't do this here unless you are actually
		//       debugging, and if you are, expect to see that
		//       (unless the tally is reset each time this is output).
		//if( card->read_overflow > 0 || card->write_overflow > 0 )
		cardinfo(card->cardnum, "prev = %lld, dma = %lld, arm = %lld"
					" (%ld %ld %ldms) ro = %d, wo = %d",
			 card->delay_tj_max, card->time_tj_max, card->time_arm_max,
			 (long)card->delay_tj_max / cpu_khz,
			 (long)card->time_tj_max  / cpu_khz,
			 (long)card->time_arm_max / cpu_khz,
			 card->read_overflow, card->write_overflow);
	#if 0
	} //{{{
	// Some values get distorted due to the time taken to probe each card
	// clear them every 5 mins to get a better baseline.
	if( ! (card->interrupt_count % 300000) ) {
		cardinfo(card->cardnum, "Clearing delay counts");
	//}}}
	#endif
		card->time_tj_last     = 0;
		card->time_tj_last_rel = 0;
		card->time_tj_max      = 0;
		card->delay_tj_max     = 0;
		card->time_arm_max     = 0;
		card->read_overflow    = -1;	// Allow for the message above
		card->write_overflow   = -1;	// creating a buffer overflow
	}
    //}}}
    #endif

	return IRQ_HANDLED;
} //}}}


static void get_module_info( struct openpci *card )
{ //{{{
	unsigned long flags;
	char buf[VT_MOD_INFO_SIZE + 1] = { 0 };
	int i = 0;

	spin_lock_irqsave(&card->lock, flags);
	for(; i < 2; ++i){
		struct modinfo *m = &card->moduleinfo[i];

		if( ! __get_module_info( card, i, 0, m->type ) )   goto hell;
		if( ! __get_module_info( card, i, 1, buf ) )       goto hell;
		m->ports = *buf;
		if( ! __get_module_info( card, i, 2, buf ) )       goto hell;
		m->rev = *buf;
		if( ! __get_module_info( card, i, 3, m->serial ) ) goto hell;
		if( ! __get_module_info( card, i, 4, m->date ) )   goto hell;

		if( m->type[0] == 'F' ){
			cardinfo(card->cardnum, "module %d: %d port %s, revision %d",
						i, m->ports, m->type, m->rev);
			cardinfo(card->cardnum, "  serial: %s", m->serial);
			cardinfo(card->cardnum, "    date: %s", m->date);
		} else  cardinfo(card->cardnum, "No module %d", i);
	}
	spin_unlock_irqrestore(&card->lock, flags);
	return;

    hell:
	spin_unlock_irqrestore(&card->lock, flags);
	cardinfo(card->cardnum, "failed to get module info");
} //}}}

static void create_module_proc_const_int(struct openpci *card, int module,
					const char *node, long val)
{ //{{{
	if (!proc_create_data(node, S_IRUGO, card->moduleinfo[module].procfs_root,
				 &vt_int_proc_fops, (void*)val))
		crit("failed to create procfs module%d/%s", module, node);
} //}}}

static void create_module_proc_const_string(struct openpci *card, int module,
					   const char *node, const char *str)
{ //{{{
	if (!proc_create_data(node, S_IRUGO, card->moduleinfo[module].procfs_root,
				 &vt_string_proc_fops, (void*)str))
		crit("failed to create procfs module%d/%s", module, node);
} //}}}

// The card must be registered with vtcore before this function is called.
static void export_module_info( struct openpci *card )
{ //{{{
	int i = 0;

	if( ! card->board.procfs_root ) return;
	for(; i < 2; ++i){
		struct modinfo *mod = &card->moduleinfo[i];
		struct proc_dir_entry **modroot = &mod->procfs_root;

		if( mod->type[0] != 'F' ) continue;
		if( ! *modroot ) {
			char  dirname[16] = { 0 };

			snprintf(dirname, sizeof(dirname)-1, "module%d", i);
			*modroot = proc_mkdir(dirname, card->board.procfs_root);
		}
		if( ! *modroot ){
			cardcrit(card->cardnum, "failed to create procfs module%d", i);
			continue;
		}

		create_module_proc_const_string(card, i, "type", mod->type );
		create_module_proc_const_int(card, i, "ports", mod->ports );
		create_module_proc_const_int(card, i, "revision", mod->rev );
		create_module_proc_const_string(card, i, "id", mod->serial );
		create_module_proc_const_string(card, i, "date", mod->date );
	}
} //}}}

static void unexport_module_info( struct openpci *card )
{ //{{{
	int i = 0;

	if( ! card->board.procfs_root ) return;
	for(; i < 2; ++i){
		char  dirname[16] = { 0 };
		struct proc_dir_entry *modroot = card->moduleinfo[i].procfs_root;

		// We could just replace all of this with a call to:
		// proc_remove(modroot);
		// but it and remove_proc_subtree didn't appear until v3.9-rc5
		// so stick with what works with older kernels for a bit longer.

		snprintf(dirname, sizeof(dirname)-1, "module%d", i);

		remove_proc_entry("type", modroot);
		remove_proc_entry("ports", modroot);
		remove_proc_entry("revision", modroot);
		remove_proc_entry("id", modroot);
		remove_proc_entry("date", modroot);
		remove_proc_entry(dirname, card->board.procfs_root);
	}
} //}}}

static int vtopenpci_board_register( struct openpci *card )
{ //{{{
	struct vtboard *board = &card->board;

	/* Register with vtcore */
	board->owner        = THIS_MODULE;
	board->maxports     = MAX_PORTS;
	board->chans        = card->chans;
	board->ioctl        = vtopenpci_ioctl;
	board->set_country  = vtopenpci_set_country;
	board->set_playgain = vtopenpci_set_playgain;
	board->get_playgain = vtopenpci_get_playgain;
	board->set_recgain  = vtopenpci_set_recgain;
	board->get_recgain  = vtopenpci_get_recgain;
	board->set_hook     = vtopenpci_set_hook;
	board->get_hook     = vtopenpci_get_hook;
	board->set_ring     = vtopenpci_set_ring;
	board->get_ring     = vtopenpci_get_ring;
	board->set_polarity = vtopenpci_set_polarity;
	board->get_polarity = vtopenpci_get_polarity;
	board->get_linevolt = vtopenpci_get_linevolt;

	if( ! vt_board_register(board) ) return RET_FAIL;
	dbginfo(2,card->cardnum, "registered for %d ports with vtcore",
				  board->maxports);

	vt_create_board_proc_const_int(board, "firmware", card->firmware);

	return RET_OK;
} //}}}

static void vtopenpci_board_unregister( struct openpci *card )
{ //{{{
	struct vtboard *board = &card->board;

	remove_proc_entry("firmware", board->procfs_root);
	vt_board_unregister(board);
} //}}}

static int openpci_probe_board(struct pci_dev *pdev, const struct pci_device_id *ent)
{ //{{{
	struct openpci *card;
	int cardnum = 0;
	int failret = -ENOMEM;
	unsigned long flags;

	card = kzalloc(sizeof(struct openpci), GFP_KERNEL);
	if(!card) {
		crit("FAILED to allocate memory for new card");
		return -ENOMEM;
	}

	mutex_lock(&cards_mutex);
	for(; cardnum < MAX_CARDS && cards[cardnum]; ++cardnum);
	if (cardnum >= MAX_CARDS){
		crit("Too many OpenPCI cards(%d), max is %d.", cardnum, MAX_CARDS);
		mutex_unlock(&cards_mutex);
		goto hell;
	}
	cards[cardnum]=card;
	mutex_unlock(&cards_mutex);

	spin_lock_init(&card->lock);
	pci_set_drvdata(pdev,card);

	card->cardnum = cardnum;
	card->dev     = pdev;
	card->armbout = 0;
	card->board.name = get_pci_boardname(ent);

    #if VERBOSE_TIMING
	card->time_tj_last     = 0;
	card->time_tj_last_rel = 0;
	card->time_tj_max      = 0;
	card->delay_tj_max     = 0;
	card->time_arm_max     = 0;
	card->read_overflow    = 0;
	card->write_overflow   = 0;
    #endif

	dbginfo(1,cardnum, "initialising card");
	if (pci_enable_device(pdev)){
		failret = -EIO;
		goto hell_2;
	}
	//XXX should we call pci_disable_device if we fail beyond this point.
	//    yes, but not if request_region fails, meaning some other driver
	//    already has the card.  This shouldn't usually happen though.

	card->ioaddr = pci_resource_start(pdev,0);
	if( ! request_region(card->ioaddr, pci_resource_len(pdev,0), NAME) ){
		cardcrit(cardnum, "FAILED to lock IO region, another driver using it?");
		failret = -EBUSY;
		goto hell_2;
	}

	spin_lock_irqsave(&card->lock, flags);
	outb(0xff, TJ_AUXD);            /* Set up TJ to access the ARM */
	outb(0x78, TJ_AUXC);            /* Set up for Jtag */
	outb(0x00, TJ_CNTL);            /* pull ERST low */
	spin_unlock_irqrestore(&card->lock, flags);
	msleep(1);	                /* Wait a bit */

	dbginfo(1,cardnum,"Starting ARM");
	spin_lock_irqsave(&card->lock, flags);
	outb(TJ_RUNFLAGS, TJ_CNTL);     /* pull ERST high again */
	spin_unlock_irqrestore(&card->lock, flags);
	msleep(100);                    /* Give it all a chance to boot */

	if(program == NULL) {
		int i = 0, tmp = 0, portcount = 0;

		if( ! check_arm(card) ){
			cardcrit(cardnum, "FAILED to find ARM processor");
			failret = -EIO;
			goto hell_3;
		}
		if( card->firmware < 14 ){
			cardcrit(cardnum,
				 "Firmware version %d not supported by this driver",
				 card->firmware);
			cardcrit(cardnum, " contact Voicetronix to have it updated");
			failret = -ENODEV;
			goto hell_3;
		}
		if( ! check_ports(card) ){
			cardcrit(cardnum, "FAILED to find any ports");
			failret = -EIO;
			goto hell_3;
		}

		card->writeblock = (int *)pci_alloc_consistent(pdev,
							       VT_PCIDMA_BLOCKSIZE,
							       &card->writedma);
		if (!card->writeblock){
			cardcrit(cardnum, "FAILED to get DMA memory.");
			goto hell_3;
		}
		card->readblock = card->writeblock + FRAME * (MAX_PORTS*2 / sizeof(int));
		card->readdma = card->writedma + FRAME * (MAX_PORTS*2);

		/* Initialise the DMA area */
		memset((void*)card->writeblock, 0x55, VT_PCIDMA_BLOCKSIZE);

		spin_lock_irqsave(&card->lock, flags);
		/* Configure the TDM bus for MSB->LSB operation */
		outb(0xc1, TJ_SERCTL);

		/* Set FSC delay to 0 so it's properly aligned */
		outb(0x0, TJ_FSCDELAY);

		/* Setup DMA access */
		outl(card->writedma,                    TJ_DMAWS); /* Start */
		outl(card->writedma + VT_PCIDMA_MIDDLE, TJ_DMAWI); /* Middle */
		outl(card->writedma + VT_PCIDMA_END,    TJ_DMAWE); /* End */
		outl(card->readdma,                     TJ_DMARS); /* Start */
		outl(card->readdma + VT_PCIDMA_MIDDLE,  TJ_DMARI); /* Middle */
		outl(card->readdma + VT_PCIDMA_END,     TJ_DMARE); /* End */

		/* Clear interrupts */
		outb(0xff, TJ_INTSTAT);
		spin_unlock_irqrestore(&card->lock, flags);

		dbginfo(1,cardnum,"calibrating ports...");
		if( ! arm_monitor(card,1) ) {
			cardcrit(card->cardnum, "FAILED to start arm monitoring");
			goto hell_4;
		}
		msleep(1000);
		while(tmp != 0x88 && ++i < 1000) {
			outb(0x88, PIB(0)); ++card->armbout;
			msleep(250);
			tmp = inb(PIB(1)); ++card->armbin;
		}
		if(i>=1000) {
			cardcrit(card->cardnum, "FAILED: calibration timeout");
			goto hell_4;
		}
		dbginfo(2,card->cardnum, "calibration completed (%d)",i);
		if( ! check_ports(card) ) {
			cardcrit(cardnum, "FAILED to calibrate ports");
			failret = -EIO;
			goto hell_4;
		}
		if( (portcount = configure_ports(card)) == 0 ) {
			cardcrit(cardnum, "FAILED to configure ports");
			goto hell_4;
		}
		cardinfo(card->cardnum, "have %d configured ports", portcount);

		if( ! vtopenpci_board_register( card ) ) {
			cardcrit(cardnum, "FAILED to register with vtcore");
			failret = -EIO;
			goto hell_4;
		}

		/* Enable bus mastering */
		pci_set_master(pdev);

		/* we must be ready to handle requests before this point */
		if (request_irq(pdev->irq, openpci_isr, shareirq ? IRQF_SHARED : 0, NAME, card)){
			cardcrit(cardnum, "FAILED to get IRQ");
			failret = -EIO;
			goto hell_5;
		}
		dbginfo(1,cardnum, "using IRQ %d", pdev->irq);

		//XXX Is this the right place for these?
		//INIT_WORK( &card->arm_work, openpci_arm_work, card );

		/* Enable interrupts */
		spin_lock_irqsave(&card->lock, flags);
		outb(0x3f, TJ_MASK0);
	      //outb(0x0,  TJ_AUXINTPOL);  This is the default already.
		outb(0x02, TJ_MASK1);

		__start_dma(card);
		spin_unlock_irqrestore(&card->lock, flags);
	}
	else {
		/* We going into program card mode! */
		if(!strcmp(program,"cpld")){
			card->ppdatamask = 0x8;
			cardinfo(cardnum, "Setting card into CPLD program mode");
		} else {
			if( check_arm(card) ) get_module_info(card);
			else cardinfo(cardnum, "failed to boot ARM processor");
			card->ppdatamask = 0x0;
			cardinfo(cardnum, "Setting card into ARM program mode");
		}
		if( ! vtopenpci_board_register( card ) ){
			cardcrit(cardnum, "FAILED to register with vtcore");
			failret = -EIO;
			goto hell_3;
		}
		if( card->ppdatamask == 0 ) export_module_info(card);
	}
	dbginfo(1,cardnum,"card initialised");

	return 0;

    hell_5:
	vtopenpci_board_unregister( card );
    hell_4:
	if (card->writeblock){
		pci_free_consistent(pdev, VT_PCIDMA_BLOCKSIZE,
				    (void*)card->writeblock, card->writedma);
	}
    hell_3:
	outb(0x00, TJ_CNTL);
	release_region(card->ioaddr, pci_resource_len(pdev,0));
    hell_2:
	cards[cardnum] = NULL;
    hell:
	kfree(card);
	return failret;
} //}}}

// Clearly this can't really put a station on-hook, but since we're about
// to unplug it from our side anyway, that soon will be moot.  This signals
// all our dependents to consider the station inactive.
static void force_station_onhook(struct openpci *card, int port)
{ //{{{
	unsigned char mess[] = {DSP_LCODEC_HKON, DSP_CODEC_HKON, port};

	if( card->chans[port].state != CH_IDLE ){
		dbgport(1,card,port,"forcing station from state %d to idle",
			card->chans[port].state );
		card->chans[port].state = CH_IDLE;
		vt_send_event(&card->board, mess, mess[0]);
	}
} //}}}

static void openpci_remove_board(struct pci_dev *pdev)
{ //{{{
	struct openpci *card = pci_get_drvdata(pdev);
	unsigned long flags;
	int i=0, n;

	if (!card)
		return;

	n = card->cardnum;
	dbginfo(1,n, "releasing card");
	if(program == NULL){
		spin_lock_irqsave(&card->lock, flags);
		outb(0x07, PIB(1));                   /* Stop arm monitoring */
		//XXX ++card->armbout; HTXF_WAIT_LOCKED();
		outb(0x00, TJ_OPER);                  /* Stop DMA access */
		outb(0x00, TJ_MASK0);                 /* Disable interrupts */
		outb(0x00, TJ_MASK1);
		spin_unlock_irqrestore(&card->lock, flags);

		free_irq(pdev->irq, card);

		// Hangup on anyone still offhook, vtcore will refuse
		// to unregister a channel that is not already idle.
		for(; i < MAX_PORTS; ++i) {
			if( card->chans[i].state != CH_IDLE ) {
				switch( card->chans[i].porttype ){
				    case VT_PORT_VDAA:
					set_hook_state(card, i, HOOK_ONHOOK);
					break;
				    case VT_PORT_PROSLIC:
					force_station_onhook(card, i);
					break;
				    default:
					portcrit(card,i,
						 "%s in state %d not idle?",
						 porttype(card,i),
						 card->chans[i].state );
				}
			}
		}
		vtopenpci_board_unregister( card );
		outb(0x00, TJ_CNTL);

		pci_free_consistent(pdev, VT_PCIDMA_BLOCKSIZE,
				    (void*)card->writeblock, card->writedma);
	} else {
		//XXX Do we need to do a forceful hangup here too?
		if( card->ppdatamask == 0 ) unexport_module_info(card);
		vtopenpci_board_unregister( card );
		outb(0x00, TJ_CNTL);
	}
	release_region(card->ioaddr, pci_resource_len(pdev,0));

	dbginfo(1,n, "Serviced %d interrupts",card->interrupt_count);
	dbginfo(1,n, "Serviced %d Arm interrupts",card->arm_interrupt_count);
	dbginfo(1,n, "Sent %d bytes to the Arm",card->armbout);
	dbginfo(1,n, "Received %d bytes from the Arm",card->armbin);

	mutex_lock(&cards_mutex);
	cards[n] = NULL;
	mutex_unlock(&cards_mutex);

	kfree(card);
	cardinfo(n, "card released");
} //}}}


module_init(vtopenpci_init);
module_exit(vtopenpci_exit);

MODULE_DESCRIPTION(DRIVER_DESCRIPTION);
MODULE_AUTHOR(DRIVER_AUTHOR);
MODULE_VERSION(VT_VERSION);
MODULE_LICENSE("GPL");

// vi:sts=8:sw=8:ts=8:noet:foldmethod=marker
