/*-
 * Copyright (c) 2005,2006 Option Wireless Sweden AB
 * Copyright (c) 2006 Sphere Systems Ltd
 * Copyright (c) 2006 Option Wireless n/v
 * Copyright (c) 2006 Nikolay Denev
 * All rights Reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 *Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

/* NOZOMI_BSD : was: $__: nozomi.c,v 1.8 2006/08/23 11:14:03 nike_d Exp $ */

/* nozomi.c port to FreeBSD $Id: nozomi.c,v 1.3 2008/05/15 06:51:22 guru Exp $ */

#include <sys/param.h>
#include <sys/module.h>
#include <sys/systm.h>
#include <sys/errno.h>
#include <sys/kernel.h>
#include <sys/conf.h>
#include <sys/uio.h>
#include <sys/malloc.h>
#include <sys/bus.h>
#include <sys/queue.h>
#include <sys/taskqueue.h>
#include <sys/bitstring.h>
#include <sys/ioccom.h>
#include <sys/termios.h>
#include <sys/tty.h>
#include <sys/ttycom.h>
#include <sys/socket.h>
#include <sys/mbuf.h>
#include <sys/clist.h>
#include <sys/selinfo.h>
#include <sys/serial.h>
#include <sys/fcntl.h>

#include <machine/bus.h>
#include <sys/rman.h>
#include <machine/resource.h>
#include <vm/vm.h>
#include <vm/pmap.h>

#include <dev/pci/pcivar.h>
#include <dev/pci/pcireg.h>

#define CFG_SIG 0xEFEFFEFE
#define TOGGLE_VALID 0x0000

/* Our fake UART values */
#define MCR_DTR      0x01
#define MCR_RTS      0x02
#define MCR_LOOP     0x04
#define MSR_CTS      0x08
#define MSR_CD       0x10
#define MSR_RI       0x20
#define MSR_DSR      0x40

#define R_IIR        0x0000	/* Interrupt Identity Register */
#define R_FCR        0x0000	/* Flow Control Register       */
#define R_IER        0x0004	/* Interrupt Enable Register   */

/* Definition of interrupt tokens */
#define MDM_DL1  0x0001
#define MDM_UL1  0x0002
#define MDM_DL2  0x0004
#define MDM_UL2  0x0008
#define DIAG_DL1 0x0010
#define DIAG_DL2 0x0020
#define DIAG_UL  0x0040
#define APP1_DL  0x0080
#define APP1_UL  0x0100
#define APP2_DL  0x0200
#define APP2_UL  0x0400
#define CTRL_DL  0x0800
#define CTRL_UL  0x1000
#define RESET    0x8000

#define MDM_DL  (MDM_DL1  | MDM_DL2)
#define MDM_UL  (MDM_UL1  | MDM_UL2)
#define DIAG_DL (DIAG_DL1 | DIAG_DL2)

#define ENABLE 1
#define DISABLE 0

#define MAX_PORT 4
#define NOZOMI_MAX_PORTS 5

/* Size of tmp send buffer to card */
#define SEND_BUF_MAX 1024
#define RECEIVE_BUF_MAX 4

#define SET_FCR(value__) bus_write_2(sc->res, (sc->card_type/2)+R_FCR, (value__));

#define SET_IER(value__, mask__) \
	sc->ier_last_written = (sc->ier_last_written & ~mask__) | (value__ & mask__ );\
	bus_write_2(sc->res, (sc->card_type/2)+R_IER, sc->ier_last_written);
	//printf("nzdebug: SET_IER: value %04x, mask %04x\n", value__, mask__); add a backsalsh here
	//bus_write_2(sc->res, (sc->card_type/2)+R_IER, sc->ier_last_written);

#define GET_IER(read_val__) (read_val__) = bus_read_2(sc->res, (sc->card_type/2)+R_IER);
#define GET_IIR(read_val__) (read_val__) = bus_read_2(sc->res, (sc->card_type/2)+R_IIR);

/*
 * There are two types of nozomi cards, one with 2048 memory and with 8192 memory
 * One has 512 bytes downlink and uplink * 2 -> 2048, the other has 3072 bytes downlink and 1024 bytes uplink * 2 -> 8192
 */
typedef enum {
	F32_2 = 2048,
	F32_8 = 8192,
} card_type_t;

/* Two different toggle channels exist */
typedef enum {
	CH_A=0,
	CH_B=1,
} channel_t;

/* Port definition for the card regarding flow control */
typedef enum {
	CTRL_CMD  = 0x00,
	CTRL_MDM  = 0x01,
	CTRL_DIAG = 0x02,
	CTRL_APP1 = 0x03,
	CTRL_APP2 = 0x04,
	CTRL_ERROR = -1,
} ctrl_port_t;

/* Ports that the nozomi has */
typedef enum {
	PORT_MDM = 0,
	PORT_DIAG= 1,
	PORT_APP1= 2,
	PORT_APP2= 3,
	PORT_CTRL= 4,
	PORT_ERROR=-1,
} port_type_t;

/* This represents the toggle information */
typedef struct {
	unsigned mdm_ul : 1;
	unsigned mdm_dl : 1;
	unsigned diag_dl : 1;
	unsigned enabled : 5; /* Toggle fields are valid if enabled is 0, else A-channels
                            must always be used. */
} __attribute__ ((packed)) toggles_t;

/* Configuration table to read at startup of card */
// note: this is only for LITLE ENDIAN machines
typedef struct {
	u_int32_t sig;
	u_int16_t version;
	u_int16_t product_information;
	toggles_t toggle;
	u_int8_t  pad1[7];
	u_int16_t dl_start;
	u_int16_t dl_mdm1_len; /* If this is 72, it can hold 68 bytes + 4 that is length field */
	u_int16_t dl_mdm2_len;
	u_int16_t dl_diag1_len;
	u_int16_t dl_diag2_len;
	u_int16_t dl_app1_len;
	u_int16_t dl_app2_len;
	u_int16_t dl_ctrl_len;
	u_int8_t  pad2[16];
	u_int16_t ul_start;
	u_int16_t ul_mdm2_len;
	u_int16_t ul_mdm1_len;
	u_int16_t ul_diag_len;
	u_int16_t ul_app1_len;
	u_int16_t ul_app2_len;
	u_int16_t ul_ctrl_len;
} __attribute__ ((packed)) config_table_t;


/* This stores all control downlink flags */
typedef struct {
	unsigned DSR : 1;
	unsigned DCD : 1;
	unsigned RI  : 1;
	unsigned CTS : 1;
	unsigned reserverd : 4;
	u_int8_t port;
} __attribute__ ((packed)) ctrl_dl_t;

/* This stores all control uplink flags */
typedef struct {
	unsigned DTR : 1;
	unsigned RTS : 1;
	unsigned reserved : 6;
	u_int8_t port;
} __attribute__ ((packed)) ctrl_ul_t;

/* This is a data packet that is read or written to/from card */
typedef struct {
	u_int32_t size; /* size is the length of the data buffer */
	u_int8_t *data;
} __attribute__ ((packed)) buf_t;

STAILQ_HEAD(, fifo_buf) fifo_head = STAILQ_HEAD_INITIALIZER(fifo_head);

// the card's i/o buffer have only room for 68 byte + 4 byte length
#define FIFO_BUF_MAX 68

struct fifo_buf {
	u_char data[FIFO_BUF_MAX];
	u_int size;
	STAILQ_ENTRY(fifo_buf) fifo_bufs;
};

struct async_icount {
	int dcd;
	int cts;
	int rng;
	int dsr;
};	

/* This holds all information that is needed regarding a port */
typedef struct {
	u_int8_t            update_flow_control;
	ctrl_ul_t           ctrl_ul;
	ctrl_dl_t           ctrl_dl;
	struct fifo_buf    *fifo_ul;
	u_int32_t           dl_offset[2];
	u_int32_t           dl_size[2];
	u_int8_t            toggle_dl;
	u_int32_t           ul_offset[2];
	u_int32_t           ul_size[2];
	u_int8_t            toggle_ul;
	u_int16_t           token_dl;

	struct tty         *tty;
	u_char              sc_lsr;
	u_char              sc_msr;
	u_char              sc_mcr;
	u_char              sc_state;

	int                 tty_open_count;
	//struct semaphore    tty_sem;
	//wait_queue_head_t   tty_wait;
	struct async_icount tty_icount;
	int                  tty_devunit;
	u_int32_t                             rx_data, tx_data;
	u_int8_t                              tty_dont_flip;
} port_t;

// ************************* LUNIX

/* The softc holds our per-instance data. */
struct nozomi_softc {
	device_t           dev;
	
	card_type_t        card_type;

	/* Memory Resources */
	int                rid;
	struct resource   *res;

	/* Interrupt Resource */
	int                intr_rid;
	struct resource   *intr_res;

	u_int16_t          ier_last_written;
	void              *intr_cookie;
	struct mtx         intr_lock;

	struct task        flip_tty_task;

	u_int8_t           dying;

	config_table_t     cfg_table;
	port_t             port[NOZOMI_MAX_PORTS];
	u_int8_t           send_buf[SEND_BUF_MAX];
};

// Guru: to check on interrupt if it is my devices
static device_t mydev = NULL;

/* nozomi device methods */
static int          nozomi_read_config_table(struct nozomi_softc *);
static void         nozomi_setup_private_data(struct nozomi_softc *);
static void         nozomi_interrupt_handler(void *);
static int          receive_flow_control(struct nozomi_softc *);
static int          nozomi_cards;
static void         intr_ul(struct nozomi_softc *, port_type_t, int);
//static void         intr_dl(struct nozomi_softc *, port_type_t, int);
static int          receive_data(port_type_t , struct nozomi_softc *);
static int          send_data(port_type_t, struct nozomi_softc *);
int                 portbytty(struct tty *);
static int          nzmodem(struct tty *, int, int);

static void
nozomi_rts(struct nozomi_softc *sc, int index, int rts)
{
	sc->port[index].ctrl_ul.RTS = rts;
	sc->port[index].update_flow_control = 1;
	intr_ul(sc, PORT_CTRL, ENABLE);
}

static void
nozomi_dtr(struct nozomi_softc *sc, int index, int dtr)
{
	sc->port[index].ctrl_ul.DTR = dtr;
	sc->port[index].update_flow_control = 1;
	intr_ul(sc, PORT_CTRL, ENABLE);
}

int
portbytty(struct tty *tty)
{
	struct nozomi_softc *sc;
	sc = tty->t_sc;
	int i;
	for(i=PORT_MDM; i<=MAX_PORT; i++) {
		if(sc->port[i].tty_devunit == tty->t_devunit)
			return i;
	}
	return -1;
}

static int
nzopen(struct tty *tty, struct cdev *dev)
{
	struct nozomi_softc *sc;
	sc = tty->t_sc;
	int pidx;
	port_t *port;

	pidx = portbytty(tty);
	port = &sc->port[pidx];
	port->tty_open_count++;

	if (port->tty_open_count == 1) {
		port->rx_data = port->tx_data = 0;
		mtx_lock_spin(&sc->intr_lock);
		SET_IER(port->token_dl, port->token_dl);
		mtx_unlock_spin(&sc->intr_lock);
	}

	return 0;
};

static void
nzclose(struct tty *tty)
{
	struct nozomi_softc *sc;
	int pidx;
	sc = tty->t_sc;
	port_t *port;

	pidx = portbytty(tty);
	port = &sc->port[pidx];

	port->tty_open_count--;

	if (port->tty_open_count == 0) {
		mtx_lock_spin(&sc->intr_lock);
		SET_IER(0, port->token_dl);
		mtx_unlock_spin(&sc->intr_lock);
	}

	return;
};

static void
nzstart(struct tty *tty)
{
	struct nozomi_softc *sc;
	sc = tty->t_sc;
	//int s;
	u_char *data;
	int cnt;
	int pidx = portbytty(tty);
	// struct cblock *cbp;
	struct fifo_buf *buf;
	int i;
	int n;

	if(sc->dying)
		return;
	
	if (tty->t_state & TS_TBLOCK) {
		if ((sc->port[pidx].sc_mcr & SER_RTS) &&
			(sc->port[pidx].sc_state & CRTS_IFLOW)) {
			printf("nz_start: clear RTS\n");
			nzmodem(tty, 0, SER_RTS);
			
		}
	} else {
		if (!(sc->port[pidx].sc_mcr & SER_RTS) &&
		    tty->t_rawq.c_cc <= tty->t_ilowat &&
		    (sc->port[pidx].sc_state & CRTS_IFLOW)) {
			printf("nz_start: set RTS\n");
			nzmodem(tty, SER_RTS, 0);
		}
	}

// seems that the code below was stolen from
// /usr/src/sys/dev/usb/ucom.c
// see also: /usr/src/sys/sys/tty.h
//           /usr/src/sys/kern/tty.c

#if 0
	if (tty->t_state & ( TS_BUSY | TS_TIMEOUT | TS_TTSTOP )) {
		ttwwakeup(tty);
		printf("ucomstart: stopped\n");
		goto out;
	}
#endif

	if (tty->t_outq.c_cc <= tty->t_olowat) {
		if (tty->t_state & TS_SO_OLOWAT) {
			tty->t_state &= ~(unsigned)TS_SO_OLOWAT;
			printf("nzdebug: nzstart() wakeup(TSA_OLOWAT(tty))\n");
			wakeup(TSA_OLOWAT(tty));
		}
		selwakeuppri(&tty->t_wsel, TTIPRI);
		if (tty->t_outq.c_cc == 0) {
			if ((tty->t_state & (TS_BUSY | TS_SO_OCOMPLETE)) ==
				TS_SO_OCOMPLETE && tty->t_outq.c_cc == 0) {
				tty->t_state &= ~(unsigned)TS_SO_OCOMPLETE;
				printf("nzdebug: nzstart() wakeup(TSA_OCOMPLETE(tty))\n");
				wakeup(TSA_OCOMPLETE(tty));
			}
			goto out;
		}
	}

	// we take tty->t_outq.c_cc and try to STAILQ_INSERT_TAIL all
	// what we have and queue it in pieces of FIFO_BUF_MAX (68) bytes
	// to make the card happy later in send_data()

	printf("nzdebug: nzstart() tty->t_outq.c_cc %d\n", tty->t_outq.c_cc);
	cnt = tty->t_outq.c_cc;

	if(cnt == 0)
		goto out;

	// malloc and start with the 1st buffer
	//
	buf = malloc(sizeof(struct fifo_buf), M_DEVBUF, M_NOWAIT);
	data = buf->data;
	n = 0;
	tty->t_state |= TS_BUSY;

	while ( tty->t_outq.c_cc > 0)	{

		*data = getc(&tty->t_outq);
		data++;
		n++;

		if ( (tty->t_outq.c_cc == 0) || (n == FIFO_BUF_MAX) )	{
			buf->size = n;
			printf("nzdebug: nzstart() -> STAILQ_INSERT_TAIL() of %d bytes, t_outq.c_cc now %d\n", buf->size, tty->t_outq.c_cc);
			// for (i=0; i<n; i++)
				// printf("%02x%c", buf->data[i], (i+1)%16 == 0 ? '\n' : ' ');
			// if ( i%16 > 0)
				// printf("\n");
			STAILQ_INSERT_TAIL(&fifo_head, buf, fifo_bufs);

			// malloc and start next fifo_bufs if we have more data
			if( tty->t_outq.c_cc > 0 )	{
				buf = malloc(sizeof(struct fifo_buf), M_DEVBUF, M_NOWAIT);
				data = buf->data;
				n = 0;
			}	
		}	
	}

	tty->t_state &= ~TS_BUSY;
	ttwwakeup(tty);

	// still needed to ndflush(&tty->t_outq, cnt);

	intr_ul(sc, pidx, ENABLE);

out:
	return;
};

static void
nzstop(struct tty *tty, int rw)
{
	struct nozomi_softc *sc;
	sc = tty->t_sc;

	if(rw & FWRITE) { /* 0x0002 */
	}
	
	if(rw & FREAD) { /* 0x0001 */
	}

	nzstart(tty);
	
	return;
}

static int
nzparam(struct tty *tty, struct termios *tios)
{
	struct nozomi_softc *sc;
	int pidx;
	int cflag;

	sc = tty->t_sc;
	pidx = portbytty(tty);

	if(sc->dying)
		return(EIO);

	if (tios->c_ospeed == 0)
		(void)nzmodem(tty, 0, SER_DTR);/* hang up line */
	else
		(void)nzmodem(tty, SER_DTR, 0);

	cflag = tios->c_cflag;
#if 0
	switch (cflag & CSIZE) {
	case CS5:
		cfcr = CFCR_5BITS;
		break;
	case CS6:
		cfcr = CFCR_6BITS;
		break;
	case CS7:
		cfcr = CFCR_7BITS;
		break;
	default:
		cfcr = CFCR_8BITS;
		break;
	}
	if (cflag & PARENB) {
		cfcr |= CFCR_PENAB;
		if (!(cflag & PARODD))
			cfcr |= CFCR_PEVEN;
	}
	if (cflag & CSTOPB)
		cfcr |= CFCR_STOPB;
#endif

	ttsetwater(tty);
	//ttyldoptim(tty);

	return 0;
}

static void
nzbreak(struct tty *tty, int flags)
{
	int pidx;
	pidx = portbytty(tty);
	printf("%s called for port %d!\n", __func__, pidx);
	return;
}

static int
nzmodem(struct tty *tty, int sigon, int sigoff)
{
	struct nozomi_softc *sc;
	int mcr;
	int msr;
	int onoff;
	int pidx;

	sc = tty->t_sc;
	pidx = portbytty(tty);

	if (sigon == 0 && sigoff == 0) {
		mcr = sc->port[pidx].sc_mcr;
		if (mcr & SER_DTR)
			sigon |= SER_DTR;
		if (mcr & SER_RTS)
			sigon |= SER_RTS;
		msr = sc->port[pidx].sc_msr;
		if (msr & SER_CTS)
			sigon |= SER_CTS;
		if (msr & SER_DCD)
		    sigon |= SER_DCD;
		if (msr & SER_DSR)
		    sigon |= SER_DSR;
		if (msr & SER_RI)
			sigon |= SER_RI;
		return (sigon);
	}

	mcr = sc->port[pidx].sc_mcr;
	if (sigon & SER_DTR)
		mcr |= SER_DTR;
	if (sigoff & SER_DTR)
		mcr &= ~SER_DTR;
	if (sigon & SER_RTS)
		mcr |= SER_RTS;
	if (sigoff & SER_RTS)
		mcr &= ~SER_RTS;
	sc->port[pidx].sc_mcr = mcr;

	onoff = (sc->port[pidx].sc_mcr & SER_DTR) ? 1 : 0;
	nozomi_dtr(sc, pidx, onoff);
	// printf("nzdebug: nozomi_dtr(): %d SER_DTR: %d\n", pidx, onoff);

	onoff = (sc->port[pidx].sc_mcr & SER_RTS) ? 1 : 0;
	nozomi_rts(sc, pidx, onoff);
	// printf("nzdebug: nozomi_rts(): %d SER_RTS: %d\n", pidx, onoff);

	return 0;
}

static int
nzioctl(struct tty *tty, u_long cmd, void *data, int flag, struct thread *td)
{
	printf("nozomi ioctl cmd = %ld\n", cmd);
	return 0;
}

#if 0
static void
tty_flip_queue_function(void *arg)
{
	struct nozomi_softc *sc = (struct nozomi_softc *)arg;
	int i;
	printf("taskqueue function run\n");
	/* Enable interrupt for that port */
	for(i=0;i<MAX_PORT;i++) {
		if (sc->port[i].tty_dont_flip) {
			sc->port[i].tty_dont_flip = 0;
			mtx_lock_spin(&sc->intr_lock);
			intr_dl(sc, i, ENABLE);
			mtx_unlock_spin(&sc->intr_lock);
		}
	}
}
#endif

void
intr_ul(struct nozomi_softc *sc,  port_type_t port, int set)
{
	// printf("nzdebug: intr_ul(): port %d set: %d\n", port, set);
	switch(port) {
		case PORT_MDM:  SET_IER( set ? MDM_UL  : 0 , MDM_UL  ); break;
		case PORT_DIAG: SET_IER( set ? DIAG_UL : 0 , DIAG_UL ); break;
		case PORT_APP1: SET_IER( set ? APP1_UL : 0 , APP1_UL ); break;
		case PORT_APP2: SET_IER( set ? APP2_UL : 0 , APP2_UL ); break;
		case PORT_CTRL: SET_IER( set ? CTRL_UL : 0 , CTRL_UL ); break;
		default: printf("%s wrong port?\n", __func__);  break;
	};
}

#if 0
void
intr_dl(struct nozomi_softc *sc,  port_type_t port, int set)
{
	switch(port) {
		case PORT_MDM:  SET_IER( set ? MDM_DL  : 0 , MDM_DL  ); break;
		case PORT_DIAG: SET_IER( set ? DIAG_DL : 0 , DIAG_DL ); break;
		case PORT_APP1: SET_IER( set ? APP1_DL : 0 , APP1_DL ); break;
		case PORT_APP2: SET_IER( set ? APP2_DL : 0 , APP2_DL ); break;
		case PORT_CTRL: SET_IER( set ? CTRL_DL : 0 , CTRL_DL ); break;
		default: printf("%s wrong port?\n", __func__); break;
	};
}
#endif

static u_int8_t
port2ctrl(port_type_t port)
{
	switch(port) {
		case PORT_MDM:  return CTRL_MDM;
		case PORT_DIAG: return CTRL_DIAG;
		case PORT_APP1: return CTRL_APP1;
		case PORT_APP2: return CTRL_APP2;
		default: printf("%s wrong port?\n", __func__);
	};
	return -1;
}

/* Return 0 - If we have updated all flow control */
/* Return 1 - If we need to update more flow control, ack current enable more */
static int
send_flow_control(struct nozomi_softc *sc)
{
	u_int32_t  i;
	u_int32_t  more_flow_control_to_be_updated = 0;
	u_int16_t *ctrl;

	for(i=PORT_MDM; i<MAX_PORT; i++) {
		if(sc->port[i].update_flow_control && more_flow_control_to_be_updated) return 1;
		sc->port[i].ctrl_ul.port = port2ctrl(i);
		ctrl = (u_int16_t *)&sc->port[i].ctrl_ul;
		/* D1( "sending flow control 0x%04X for port %d, %d", (u16) *ctrl, i, dc->port[i].ctrl_ul.port ); */
		/* XXX */
		//bus_write_4(sc->res, sc->port[PORT_CTRL].ul_offset[0], *(u_int32_t *)&ctrl);
		//printf("nzdebug: sending flow control 0x%02x for port %d, %d\n", (u_int16_t) *ctrl, i, sc->port[PORT_CTRL].ul_offset[0]);
		bus_write_2(sc->res, sc->port[PORT_CTRL].ul_offset[0], *ctrl);
		sc->port[i].update_flow_control = 0;
		more_flow_control_to_be_updated = 1;
	}
	return 0;
}


static int
receive_flow_control(struct nozomi_softc *sc)
{
	port_type_t port;
	ctrl_dl_t   ctrl_dl;
	ctrl_dl_t   old_ctrl;

	/* default port */
	port = PORT_MDM;
	/* read ctrl_dl */
	*(u_int32_t *)&ctrl_dl = bus_read_4(sc->res, sc->port[PORT_CTRL].dl_offset[CH_A]);

	switch(ctrl_dl.port) {
		/* XXX WTF is this? :) */
		case CTRL_CMD:
			printf("The Base Band sends this value as a response to a request for IMSI"
				" detach sent over the control channel uplink (see sec. 7.6.1).");
			break;
		case CTRL_MDM:  port = PORT_MDM;  break;
		case CTRL_DIAG: port = PORT_DIAG; break;
		case CTRL_APP1: port = PORT_APP1; break;
		case CTRL_APP2: port = PORT_APP2; break;
		default: printf("ERROR: flow control received for non-existing port");
		return 0;
	};
	printf("nzdebug: receive_flow_control for port %d\n", port);

	old_ctrl = sc->port[port].ctrl_dl;
	sc->port[port].ctrl_dl = ctrl_dl;

	if ( old_ctrl.CTS == 1 && ctrl_dl.CTS == 0 ) {
		printf("nzdebug: intr_ul(sc, port, DISABLE) port %d\n", port);
		intr_ul(sc, port, DISABLE);
	} else if( old_ctrl.CTS == 0 && ctrl_dl.CTS == 1 ) {
		if (!STAILQ_EMPTY(&fifo_head)) {
			printf("nzdebug: intr_ul(sc, port, ENABLE) port %d\n", port);
			intr_ul(sc, port, ENABLE);
		}	
	}

	/* return if no change in mctrl */
	if(*(u_int16_t *)&old_ctrl == *(u_int16_t *)&ctrl_dl) return 1;

	/* Update statistics */
	if(old_ctrl.CTS != ctrl_dl.CTS) {
		//printf("CTS change!\n");
		sc->port[port].tty_icount.cts++;
	}
	if(old_ctrl.DSR != ctrl_dl.DSR) {
		//printf("DSR change!\n");
		sc->port[port].tty_icount.dsr++;
	}
	if(old_ctrl.RI != ctrl_dl.RI) {
		//printf("RI change!\n");
		sc->port[port].tty_icount.rng++;
	}
	if(old_ctrl.DCD != ctrl_dl.DCD) {
		//printf("DCD change!\n");
		sc->port[port].tty_icount.dcd++;
	}
	device_printf(sc->dev, "port: %d DCD(%d), CTS(%d), RI(%d), DSR(%d)\n",
	port, sc->port[port].tty_icount.dcd, sc->port[port].tty_icount.cts,
	sc->port[port].tty_icount.rng, sc->port[port].tty_icount.dsr);

	return 1;
}

/* Handle donlink data, ports that are handled are modem and diagnostics */
/* Return 1 - ok */
/* Return 0 - toggle fields are out of sync */
static int
handle_data_dl(struct nozomi_softc *sc, u_int16_t irq, port_type_t port, u_int8_t *toggle, u_int16_t mask1, u_int16_t mask2)
{
	if(*toggle == 0 && irq & mask1) {
		if(receive_data(port, sc)) {
			SET_FCR(mask1);
			*toggle = !(*toggle);
		}
		if(irq & mask2) {
			if(receive_data(port, sc)) {
				SET_FCR(mask2);
				*toggle = !(*toggle);
			}
		}
	} else if(*toggle == 1 && irq & mask2) {
		if (receive_data(port, sc)) {
			SET_FCR(mask2);
			*toggle = !(*toggle);
		}
		if (irq & mask1) {
			if (receive_data(port, sc)) {
				SET_FCR(mask1);
				*toggle = !(*toggle);
			}
		}
	} else {
		printf("port out of sync!, toggle:%d\n", *toggle);
		return 0;
	}
	return 1;
}



/* Handle uplink data, this is currently for the modem port */
/* Return 1 - ok */
/* Return 0 - toggle field are out of sync */
static int
handle_data_ul(struct nozomi_softc *sc, u_int16_t irq, port_type_t port)
{
	u_int8_t *toggle = &(sc->port[port].toggle_ul);

	printf("nzdebug: handle_data_ul() entered\n");
	if(*toggle==0 && irq & MDM_UL1) {
		printf("nzdebug: *toggle==0 && irq & MDM_UL1 true\n");
		SET_IER(0, MDM_UL);
		if(send_data(port, sc)) {
			SET_FCR(MDM_UL1);
			SET_IER(MDM_UL, MDM_UL);
			*toggle = !(*toggle);
		}
		if(irq & MDM_UL2) {
			printf("nzdebug: irq & MDM_UL2 true\n");
			SET_IER(0, MDM_UL);
			if(send_data(port, sc)) {
				SET_FCR(MDM_UL2);
				SET_IER(MDM_UL, MDM_UL);
				*toggle = !(*toggle);
			}
		}
	} else if(*toggle==1 && irq & MDM_UL2) {
		printf("nzdebug: *toggle==1 && irq & MDM_UL2 true\n");
		SET_IER(0, MDM_UL);
		if (send_data(port, sc)) {
			SET_FCR(MDM_UL2);
			SET_IER(MDM_UL, MDM_UL);
			*toggle = !(*toggle);
		}
		if (irq & MDM_UL1 ) {
			printf("nzdebug: irq & MDM_UL1 true\n");
			SET_IER(0, MDM_UL);
			if (send_data(port, sc)) {
				SET_FCR(MDM_UL1);
				SET_IER(MDM_UL, MDM_UL);
				*toggle = !(*toggle);
			}
		}
	} else {
		SET_FCR(irq & MDM_UL);
		printf("port out of sync!\n");
		return 0;
	}
	printf("nzdebug: handle_data_ul() now leaving\n");
	return 1;
}

/* Return 1 - send buffer to card and ack. */
/* Return 0 - don't ack, don't send buffer to card. */
int
send_data(port_type_t index, struct nozomi_softc *sc)
{
	u_int32_t size = 0;
	port_t   *port = &sc->port[index];
	u_int8_t  toggle = port->toggle_ul;
	u_int32_t ul_offs = port->ul_offset[toggle];
	u_int32_t ul_size = port->ul_size[toggle];
	struct fifo_buf *buf;
	int s;
	int i;
	//struct tty *tty = port->tty;

	if (STAILQ_EMPTY(&fifo_head)) {
		// printf("nzdebug: no data?\n");
		return 0;
	}

	buf = STAILQ_FIRST(&fifo_head);
	size = buf->size;

	if ( size <= 0 ) {
		printf("nzdebug: incorrect buf size %d?", size);
	}
	if ( size > ul_size ) {
		printf("nzdebug: incorrect buf size %d, bigger then ul_size %d", size, ul_size);
	}

	memcpy(sc->send_buf, buf->data, size < SEND_BUF_MAX ? size : SEND_BUF_MAX );

	STAILQ_REMOVE_HEAD(&fifo_head, fifo_bufs);
	free(buf, M_DEVBUF);

	port->tx_data += size;

	s = size < ul_size ? size : ul_size ;
	
	printf("nzdebug: send_data(): punching %d bytes into port %d toggle %d\n", s, index, toggle);
	// for (i=0; i<s; i++)
	// 	printf("%02x%c", sc->send_buf[i], (i+1)%16 == 0 ? '\n' : ' ');
	// if ( i%16 > 0)
	// 	printf("\n");
	
	/* Write length + data */
	bus_write_4(sc->res, ul_offs, s);
	bus_write_region_4(sc->res, ul_offs + 4, (u_int32_t *)sc->send_buf, s);

	if (port->tty) {
		ttwwakeup(port->tty);
	}

	return 1;
}


/* If all data has been read, return 1, else 0 */
static int
receive_data(port_type_t index, struct nozomi_softc *sc)
{
	u_int8_t  buf[RECEIVE_BUF_MAX] = {0};
	int       i, size;
	port_t   *port = &sc->port[index];
	u_int8_t  toggle = port->toggle_dl;
	u_int32_t dl_offs   = port->dl_offset[toggle];
	u_int32_t offset = 4;
	struct tty *tty = port->tty;

	if (!tty) {
		printf("tty not open for port: %d?", index);
		return 1;
	}

#if 0
	if(bit_test(&tty->t_flags, TTY_DONT_FLIP)) {
		printf("TTY_DONT_FLIP set!! %d\n", index);
		/* Here we disable interrupt for that port and schedule */
		/* task. Task wakes up a little bit later and enables interrupt.. */
		port->tty_dont_flip = 1;
		intr_dl(sc, index, DISABLE);
		if(!taskqueue_enqueue(taskqueue_swi_giant, &sc->flip_tty_task)) {
			printf("error adding work to taskqueue\n");
			return 0;
		}
	}
#endif

	size = bus_read_4(sc->res, dl_offs);
	printf("nzdebug: receive_data() %d bytes on port %d toggle %d\n", size, index, toggle);

#if 0
	if(bit_test(&tty->flags, TTY_THROTTLED)) {
		printf("No room in tty, don't read data, don't ack interrupt, disable interrupt\n");
		/* disable interrupt in downlink... */
		intr_dl(sc, index, DISABLE);
		return 0;
	}
#endif
	if (size == 0) {
		printf("size == 0?\n");
		return 1;
	}

	while(size > 0) {
		*(u_int32_t *)&buf = bus_read_4(sc->res, dl_offs + offset);
		i = 0;
		while (i < 4 && size > 0) {
			if (ttyld_rint(tty, buf[i]) == -1) {
				printf("lost char! %c", buf[i]);
			}
			port->rx_data++;
			i++;
			size--;
		}
		offset += 4;
	}
	ttwwakeup(tty);

	return 1;
}

void
nozomi_interrupt_handler(void *arg)
{
	struct nozomi_softc *sc = (struct nozomi_softc *)arg;
	u_int16_t irq;

	// check if it is my device:
	if (sc->dev != mydev) {
	     printf("nzdebug: nozomi_interrupt_handler() but not my device\n");
	     return;
	}    

	/* get irq identity */	
	GET_IIR(irq);

	/* handle only interrupts set in IER */
	irq &= sc->ier_last_written;

	if(irq == 0)
		return;

	// printf("nzdebug: nozomi_interrupt_handler() handling interrupt\n");
	mtx_lock_spin(&sc->intr_lock);
	/* RESET interrupt */
	if (irq & RESET) {
		printf("nzdebug: RESET interrupt\n");
		if (!nozomi_read_config_table(sc)) {
			SET_IER(0, 0xFFFF);
			device_printf(sc->dev, "couldn't read status from card, disabling interrupts\n");
		} else {
			SET_FCR(RESET);
		}
		goto out;
	}
	/* CTRL_UL interrupt */
	if(irq & CTRL_UL) {
		printf("nzdebug: CTRL_UL interrupt\n");
		SET_IER(0, CTRL_UL);
		if(send_flow_control(sc)) {
			SET_FCR(CTRL_UL);
			SET_IER(CTRL_UL, CTRL_UL);
		}
	}
	/* CTRL_DL interrupt */
	if(irq & CTRL_DL) {
		printf("nzdebug: CTRL_DL interrupt\n");
		receive_flow_control(sc);
		SET_FCR(CTRL_DL);
	}
	/* MDM_DL interrupt */
	if(irq & MDM_DL) {
		printf("nzdebug: MDM_DL interrupt\n");
		if(!(handle_data_dl(sc, irq, PORT_MDM, &(sc->port[PORT_MDM].toggle_dl), MDM_DL1, MDM_DL2))) {
			printf("MDM_DL out of sync!\n");
			goto out;
		}
	}
	/* MDM_UL interrupt */
	if(irq & MDM_UL) {
		printf("nzdebug: MDM_UL interrupt\n");
		if(!handle_data_ul(sc, irq, PORT_MDM)) {
			printf("MDM_UL out of sync!\n");
			goto out;
		}
	}
	/* DIAG_DL interrupt */
	if(irq & DIAG_DL) {
		printf("nzdebug: DIAG_DL interrupt\n");
		if ( !(handle_data_dl(sc, irq, PORT_DIAG, &(sc->port[PORT_DIAG].toggle_dl), DIAG_DL1, DIAG_DL2)) ) {
			printf("DIAG_DL out of sync!\n");
			goto out;
		}
	}
	/* DIAG_UL interrupt */
	if(irq & DIAG_UL) {
		printf("nzdebug: DIAG_UL interrupt\n");
		SET_IER(0, DIAG_UL);
		if(send_data(PORT_DIAG, sc)) {
			SET_FCR(DIAG_UL);
			SET_IER(DIAG_UL, DIAG_UL);
		}
	}
	/* APP1_DL interrupt */
	if(irq & APP1_DL) {
		printf("nzdebug: APP1_DL interrupt\n");
		if (receive_data(PORT_APP1, sc)) {
			SET_FCR(APP1_DL);
		}
	}
	/* APP1_UL interrupt */
	if(irq & APP1_UL) {
		printf("nzdebug: APP1_UL interrupt\n");
		SET_IER(0, APP1_UL);
		if(send_data(PORT_APP1, sc)) {
			SET_FCR(APP1_UL);
			SET_IER(APP1_UL, APP1_UL);
		}
	}
	/* APP2_DL interrupt */
	if(irq & APP2_DL) {
		printf("nzdebug: APP2_DL interrupt\n");
		if (receive_data(PORT_APP2, sc)) {
			SET_FCR(APP2_DL);
		}
	}
	/* APP2_UL interrupt */
	if(irq & APP2_UL) {
		printf("nzdebug: APP2_UL interrupt\n");
		SET_IER(0, APP2_UL);
		if(send_data(PORT_APP2, sc)) {
			SET_FCR(APP2_UL);
			SET_IER(APP2_UL, APP2_UL);
		}
	}
out:
	mtx_unlock_spin(&sc->intr_lock);
}

/* Setup pointers to different channels and also setup buffer sizes. */
static void setup_memory(struct nozomi_softc *sc)
{
	u_int16_t offset = sc->cfg_table.dl_start;
	/* The length reported is including the length field of 4 bytes, hence subtract with 4. */
	u_int16_t buff_offset = 4;

	/* Modem port dl configuration */
	sc->port[PORT_MDM].dl_offset[CH_A] = offset;
	sc->port[PORT_MDM].dl_offset[CH_B] = (offset += sc->cfg_table.dl_mdm1_len);
	sc->port[PORT_MDM].dl_size[CH_A] = sc->cfg_table.dl_mdm1_len - buff_offset;
	sc->port[PORT_MDM].dl_size[CH_B] = sc->cfg_table.dl_mdm2_len - buff_offset;
	/* Diag port dl configuration */
	sc->port[PORT_DIAG].dl_offset[CH_A] = (offset += sc->cfg_table.dl_mdm2_len);
	sc->port[PORT_DIAG].dl_size[CH_A] = sc->cfg_table.dl_diag1_len - buff_offset;
	sc->port[PORT_DIAG].dl_offset[CH_B] = (offset += sc->cfg_table.dl_diag1_len);
	sc->port[PORT_DIAG].dl_size[CH_B] = sc->cfg_table.dl_diag2_len - buff_offset;
	/* App1 port dl configuration */
	sc->port[PORT_APP1].dl_offset[CH_A] = (offset += sc->cfg_table.dl_diag2_len);
	sc->port[PORT_APP1].dl_size[CH_A] = sc->cfg_table.dl_app1_len - buff_offset;
	/* App2 port dl configuration */
	sc->port[PORT_APP2].dl_offset[CH_A] = (offset += sc->cfg_table.dl_app1_len);
	sc->port[PORT_APP2].dl_size[CH_A] = sc->cfg_table.dl_app2_len - buff_offset;
	/* Ctrl dl configuration */
	sc->port[PORT_CTRL].dl_offset[CH_A] = (offset += sc->cfg_table.dl_app2_len);
	sc->port[PORT_CTRL].dl_size[CH_A] = sc->cfg_table.dl_ctrl_len - buff_offset;
	/* Modem Port ul configuration */
	sc->port[PORT_MDM].ul_offset[CH_A] = (offset = sc->cfg_table.ul_start);
	sc->port[PORT_MDM].ul_size[CH_A] = sc->cfg_table.ul_mdm1_len - buff_offset;
	sc->port[PORT_MDM].ul_offset[CH_B] = (offset += sc->cfg_table.ul_mdm1_len);
	sc->port[PORT_MDM].ul_size[CH_B] = sc->cfg_table.ul_mdm2_len - buff_offset;
	/* Diag port ul configuration */
	sc->port[PORT_DIAG].ul_offset[CH_A] = (offset += sc->cfg_table.ul_mdm2_len);
	sc->port[PORT_DIAG].ul_size[CH_A] = sc->cfg_table.ul_diag_len - buff_offset;
	/* App1 port ul configuration */
	sc->port[PORT_APP1].ul_offset[CH_A] = (offset += sc->cfg_table.ul_diag_len);
	sc->port[PORT_APP1].ul_size[CH_A] = sc->cfg_table.ul_app1_len - buff_offset;
	/* App2 port ul configuration */
	sc->port[PORT_APP2].ul_offset[CH_A] = (offset += sc->cfg_table.ul_app1_len);
	sc->port[PORT_APP2].ul_size[CH_A] = sc->cfg_table.ul_app2_len - buff_offset;
	/* Ctrl ul configuration */
	sc->port[PORT_CTRL].ul_offset[CH_A] = (offset += sc->cfg_table.ul_app2_len);
	sc->port[PORT_CTRL].ul_size[CH_A] = sc->cfg_table.ul_ctrl_len - buff_offset;
	offset = sc->cfg_table.ul_start;
}

static void
nozomi_tty_init(struct nozomi_softc *sc)
{
	int i;
	struct tty *tmptty;

	for(i=PORT_MDM; i<=MAX_PORT; i++) {
		tmptty = sc->port[i].tty = ttyalloc();
		tmptty->t_open =  nzopen;
		tmptty->t_close = nzclose;
		tmptty->t_oproc = nzstart;
		tmptty->t_stop =  nzstop;
		tmptty->t_param = nzparam;
		tmptty->t_break = nzbreak;
		tmptty->t_modem = nzmodem;
		tmptty->t_ioctl = nzioctl;
		tmptty->t_sc = sc;
		device_printf(sc->dev, "ttycreate for /dev/cuaN%d\n", i);
		ttycreate(tmptty, TS_CALLOUT, "N%r", i);
		sc->port[i].tty_devunit = tmptty->t_devunit;
		sc->port[i].tty_open_count = 0;
	}
}


static void dump_table(struct nozomi_softc *sc) {
    printf("signature: 0x%08X\n", sc->cfg_table.sig);
    printf("version: 0x%04X\n", sc->cfg_table.version);
    printf("product_information: 0x%04X\n", sc->cfg_table.product_information);
    printf("toggle enabled: %02x\n", sc->cfg_table.toggle.enabled);
    printf("toggle mdm_ul: %d\n", sc->cfg_table.toggle.mdm_ul);
    printf("toggle mdm_dl: %d\n", sc->cfg_table.toggle.mdm_dl);
    printf("toggle diag_dl: %d\n", sc->cfg_table.toggle.diag_dl);

    printf("dl_start: 0x%04X\n", sc->cfg_table.dl_start);
    printf("dl_mdm1_len: 0x%04X, %d\n", sc->cfg_table.dl_mdm1_len, sc->cfg_table.dl_mdm1_len);
    printf("dl_mdm2_len: 0x%04X, %d\n", sc->cfg_table.dl_mdm2_len, sc->cfg_table.dl_mdm2_len);
    printf("dl_diag1_len: 0x%04X, %d\n", sc->cfg_table.dl_diag1_len, sc->cfg_table.dl_diag1_len);
    printf("dl_diag2_len: 0x%04X, %d\n", sc->cfg_table.dl_diag2_len, sc->cfg_table.dl_diag2_len);
    printf("dl_app1_len: 0x%04X, %d\n", sc->cfg_table.dl_app1_len, sc->cfg_table.dl_app1_len);
    printf("dl_app2_len: 0x%04X, %d\n", sc->cfg_table.dl_app2_len, sc->cfg_table.dl_app2_len);
    printf("dl_ctrl_len: 0x%04X, %d\n", sc->cfg_table.dl_ctrl_len, sc->cfg_table.dl_ctrl_len);
    printf("ul_start: 0x%04X, %d\n", sc->cfg_table.ul_start, sc->cfg_table.ul_start);
    printf("ul_mdm1_len: 0x%04X, %d\n", sc->cfg_table.ul_mdm1_len, sc->cfg_table.ul_mdm1_len);
    printf("ul_mdm2_len: 0x%04X, %d\n", sc->cfg_table.ul_mdm2_len, sc->cfg_table.ul_mdm2_len);
    printf("ul_diag_len: 0x%04X, %d\n", sc->cfg_table.ul_diag_len, sc->cfg_table.ul_diag_len);
    printf("ul_app1_len: 0x%04X, %d\n", sc->cfg_table.ul_app1_len, sc->cfg_table.ul_app1_len);
    printf("ul_app2_len: 0x%04X, %d\n", sc->cfg_table.ul_app2_len, sc->cfg_table.ul_app2_len);
    printf("ul_ctrl_len: 0x%04X, %d\n", sc->cfg_table.ul_ctrl_len, sc->cfg_table.ul_ctrl_len);
}


static int
nozomi_read_config_table(struct nozomi_softc *sc)
{
	int i;
	//struct tty *tmptty;

	/* read config table in memory */
	bus_read_region_1(sc->res, 0, (u_int8_t *)&sc->cfg_table, sizeof(config_table_t));
	
	if(sc->cfg_table.sig != CFG_SIG) {
		device_printf(sc->dev, "config table bad : 0x%08X != 0x%08X\n",
			sc->cfg_table.sig, CFG_SIG);
		return 0;
	}
  
	// dump_table(sc);

	if((sc->cfg_table.version == 0) || (sc->cfg_table.toggle.enabled == TOGGLE_VALID) ) {

        	printf("Second phase, configuring card\n");

		setup_memory(sc);

		sc->port[PORT_MDM].toggle_ul = sc->cfg_table.toggle.mdm_ul;
		sc->port[PORT_MDM].toggle_dl = sc->cfg_table.toggle.mdm_dl;
		sc->port[PORT_DIAG].toggle_dl = sc->cfg_table.toggle.diag_dl;

		STAILQ_INIT(&fifo_head);

		for (i=PORT_MDM; i< MAX_PORT;i++) {
			memset(&sc->port[i].ctrl_dl, 0, sizeof(ctrl_dl_t));
			memset(&sc->port[i].ctrl_ul, 0, sizeof(ctrl_ul_t));
		}
		
		/* Enable control channel */
		SET_IER( CTRL_DL, CTRL_DL );

		device_printf(sc->dev, "initialization complete\n");
		return 1;
	}

	if((sc->cfg_table.version > 0) && (sc->cfg_table.toggle.enabled != TOGGLE_VALID)) {
		u_int32_t zero = 0;

        	printf("First phase: pushing upload buffers, clearing download\n");
		device_printf(sc->dev, "ver. %d with %u bytes of memory\n", sc->cfg_table.version, sc->card_type);

		/* Here we should disable all I/O over F32. */
		setup_memory(sc);

		/* We should send ALL channel pair tokens back along with reset token */
		/* push upload modem buffers */
		bus_write_4(sc->res, sc->port[PORT_MDM].ul_offset[CH_A], zero);
		bus_write_4(sc->res, sc->port[PORT_MDM].ul_offset[CH_B], zero);

		SET_FCR( MDM_UL | DIAG_DL | MDM_DL );

	}
    return 1;
}

static void
nozomi_setup_private_data(struct nozomi_softc *sc)
{
	int i;

	sc->ier_last_written = 0;
	sc->dying = 0;

	sc->port[PORT_MDM ].token_dl = MDM_DL;
	sc->port[PORT_DIAG].token_dl = DIAG_DL;
	sc->port[PORT_APP1].token_dl = APP1_DL;
	sc->port[PORT_APP2].token_dl = APP2_DL;

	for(i=PORT_MDM;i<MAX_PORT;i++) {
		sc->port[i].rx_data = sc->port[i].tx_data = 0;
		sc->port[i].tty_dont_flip = 0;
	}
}

static int
nozomi_probe(device_t dev)
{
	if (pci_get_vendor(dev) == 0x1931 && pci_get_device(dev) == 0x000c) {
		device_set_desc(dev, "Option N.V. GlobeTrotter 3G+");
		return (BUS_PROBE_DEFAULT);
	}
	return (ENXIO);
}



static int
nozomi_attach(device_t dev)
{
	struct nozomi_softc *sc;

	nozomi_cards++;

	if(nozomi_cards > 1) {
		printf("nozomi driver supports only one card per system!\n");
		return(ENXIO);
	}

	sc = device_get_softc(dev);
	sc->dev = dev;
	mydev = dev;

	sc->rid = PCIR_BAR(0);
	sc->res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &sc->rid, RF_ACTIVE);

	if(sc->res == NULL) {
		device_printf(sc->dev, "unable to allocate memory resource!\n");
		return(ENXIO);
	}
	
	sc->intr_rid = 0;
	sc->intr_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &sc->intr_rid, RF_SHAREABLE | RF_ACTIVE);

	if(sc->intr_res == NULL) {
		device_printf(dev, "unable to allocate interrupt resource!\n");
		return(ENXIO);
	}

	sc->card_type = rman_get_size(sc->res) == 2048 ? F32_2 : F32_8;
	
	nozomi_setup_private_data(sc);

	/* disable interrupts */
	SET_IER(0, 0xFFFF);

	/* setup ttys */
	nozomi_tty_init(sc);

	/* setup interrupt and interrupt handler */
	if(bus_setup_intr(dev, sc->intr_res, INTR_TYPE_TTY , NULL, nozomi_interrupt_handler, sc, &sc->intr_cookie)) {
		printf("unable to register interrupt handler\n");
		goto fail;
	}

	//TASK_INIT(&sc->flip_tty_task, 0, (task_fn_t *)tty_flip_queue_function, sc);
	
	/* initialize interrupt handler spin mutex */
	mtx_init(&sc->intr_lock, device_get_nameunit(dev), NULL, MTX_SPIN);

	/* enable RESET interrupt */
	SET_IER(RESET, 0xFFFF);

	return 0;

fail:
	nozomi_cards--;
	bus_release_resource(dev, SYS_RES_MEMORY, sc->rid, sc->res);
	bus_release_resource(dev, SYS_RES_IRQ, sc->intr_rid, sc->intr_res);
	return(ENXIO);
}

static int
nozomi_detach(device_t dev)
{
	struct nozomi_softc *sc;
	int i;
	ctrl_ul_t ctrl;

	sc = device_get_softc(dev);

	SET_IER(0, 0xFFFF);
	ctrl.port = 0x00; ctrl.reserved = 0; ctrl.RTS=0; ctrl.DTR=1;
	nozomi_setup_private_data(sc);

	bus_write_4(sc->res, sc->port[PORT_CTRL].ul_offset[0], *(u_int32_t *)&ctrl);

	SET_FCR(CTRL_UL);

	for(i=PORT_MDM; i<=MAX_PORT; i++) {
		if(sc->port[i].tty)
			ttyfree(sc->port[i].tty);
	}

	bus_release_resource(dev, SYS_RES_MEMORY, sc->rid, sc->res);
	bus_teardown_intr(dev, sc->intr_res, sc->intr_cookie);
	bus_release_resource(dev, SYS_RES_IRQ, sc->intr_rid, sc->intr_res);

	nozomi_cards--;

	return (0);
}

/* Called during system shutdown after sync. */

static int
nozomi_shutdown(device_t dev)
{
	return (0);
}

/*
 * Device suspend routine.
 */
static int
nozomi_suspend(device_t dev)
{
	return (0);
}

/*
 * Device resume routine.
 */
static int
nozomi_resume(device_t dev)
{
	return (0);
}

static device_method_t nozomi_methods[] = {
    /* Device interface */
    DEVMETHOD(device_probe,     nozomi_probe),
    DEVMETHOD(device_attach,    nozomi_attach),
    DEVMETHOD(device_detach,    nozomi_detach),
    DEVMETHOD(device_shutdown,  nozomi_shutdown),
    DEVMETHOD(device_suspend,   nozomi_suspend),
    DEVMETHOD(device_resume,    nozomi_resume),

    { 0, 0 }
};

static devclass_t nozomi_devclass;

DEFINE_CLASS_0(nozomi, nozomi_driver, nozomi_methods, sizeof(struct nozomi_softc));
DRIVER_MODULE(nozomi, pci, nozomi_driver, nozomi_devclass, 0, 0);
DRIVER_MODULE(nozomi, cardbus, nozomi_driver, nozomi_devclass, 0, 0);
