/*
 * FreeBSD device driver for B004-compatible Transputer boards.
 *
 * based on Linux version Copyright (C) 1993 by Christoph Niemann
 *
 * Rewritten for FreeBSD by
 * Luigi Rizzo (luigi@iet.unipi.it) and
 * Lorenzo Vicisano (l.vicisano@iet.unipi.it)
 *    Dipartimento di Ingegneria dell'Informazione
 *    Universita` di Pisa
 *    via Diotisalvi 2, 56126 Pisa, ITALY
 * 14 september 1994
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *      This product includes software developed by Christoph Niemann,
 *      Luigi Rizzo and Lorenzo Vicisano - Dipartimento di Ingegneria
 *		dell'Informazione
 * 4. The names of these contributors may not be used to endorse or promote
 *  products derived from this software without specific prior written
 *  permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * NOTE NOTE NOTE
 * The assembler version is still under development.
 */

/* #define USE_ASM */

#include "bqu.h"
#if NBQU > 0

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/uio.h>
#include <sys/conf.h>
#include <sys/kernel.h>

#include "opt_devfs.h"

#ifdef DEVFS
#include <sys/devfsext.h>
#endif /*DEVFS*/

#include <machine/clock.h>

#include <i386/isa/b004.h>
#include <i386/isa/isa_device.h>

#define IOCTL_OUT(arg, ret)             *(int*)arg = ret

#define	B004PRI	(PZERO+8)

#define B004_CHANCE	8

/*
 * Define these symbols if you want to debug the code.
 */
#undef B004_DEBUG
#undef B004_DEBUG_2

#ifdef B004_DEBUG
static u_char d_inb(u_int port);
static void d_outb(u_int port, u_char data);

#define out(port,data)	d_outb(port, data)
#define in(a)		d_inb(((u_int)a))
#else
#define out(port, data)		outb(port,data)
#define in(port)		inb(((u_int)port))
#endif B004_DEBUG

#ifdef B004_DEBUG
#define DEB(x) x
#define NO_DEB(x) /* */
#else
#define DEB(x) /* */
#define NO_DEB(x) x
#endif

#ifdef B004_DEBUG_2
#define DEB2(x) x
#else
#define DEB2(x)
#endif

static int bquprobe(struct isa_device *idp);
static int bquattach(struct isa_device *idp);


struct isa_driver bqudriver = {
   bquprobe, bquattach, "bqu"
};

static	d_open_t	bquopen;
static	d_close_t	bquclose;
static	d_read_t	bquread;
static	d_write_t	bquwrite;
static	d_ioctl_t	bquioctl;
static	d_poll_t	bqupoll;

#define CDEV_MAJOR 8
static struct cdevsw bqu_cdevsw = 
	{ bquopen,      bquclose,       bquread,        bquwrite,       /*8*/
	  bquioctl,     nostop,         nullreset,      nodevtotty,/* tputer */
	  bqupoll,    nommap,         NULL,	"bqu",	NULL,	-1 };

static int b004_sleep; /* wait address */

static struct b004_struct b004_table[NBQU];

static int first_time=1;

/*
 * At these addresses the driver will search for B004-compatible boards
 */
static int
b004_base_addresses[B004_CHANCE] = {
    /* 0x150, 0x170, 0x190, 0x200, 0x300, 0x320, 0x340, 0x360 */
    0x150, 0x190, 0, 0, 0, 0, 0, 0
};

#ifdef B004_DEBUG
static void
d_outb(u_int port, u_char data)
{

	printf("OUT 0x%x TO 0x%x\n",data,port);
	outb(port,data);
}

static u_char
d_inb(u_int port)
{
u_char ap;
	ap=inb(port);
	printf("INPUT 0x%x FROM 0x%x\n",ap,port);
	return(ap);
}
#endif

static int
detected(int base)
{
    int i;
    for(i=0;i<NBQU;i++)
	if ((B004_F(i) & B004_EXIST) && (B004_BASE(i)==base)) return 1;
    return (0);
}

#define b004_delay(a)	DELAY(10000)

/*
 * static void bqureset(): reset transputer network.
 *
 */

static void
bqureset( const int dev_min )
{
    DEB(printf("B004 resetting transputer at link %d.\n", dev_min);)
    out(B004_BASE(dev_min)+B004_ANALYSE_OFFSET, B004_DEASSERT_ANALYSE);
    b004_delay(dev_min);

    out(B004_BASE(dev_min) + B004_RESET_OFFSET, B004_DEASSERT_RESET);
    b004_delay(dev_min);

    out(B004_BASE(dev_min) + B004_RESET_OFFSET, B004_ASSERT_RESET);
    b004_delay(dev_min);

    out(B004_BASE(dev_min) + B004_RESET_OFFSET, B004_DEASSERT_RESET);
    b004_delay(dev_min);

    DEB(printf("B004 reset done.\n");)
}

/*
 * static void bquanalyse(): switch transputer network to analyse mode.
 *
 */

static void
bquanalyse( const int dev_min )
{
    DEB(printf("B004 analysing transputer at link %d.\n", dev_min);)

    out(B004_BASE(dev_min) + B004_ANALYSE_OFFSET, B004_DEASSERT_ANALYSE);
    b004_delay(dev_min);

    out(B004_BASE(dev_min) + B004_ANALYSE_OFFSET, B004_ASSERT_ANALYSE);
    b004_delay(dev_min);

    out(B004_BASE(dev_min) + B004_RESET_OFFSET, B004_ASSERT_RESET);
    b004_delay(dev_min);

    out(B004_BASE(dev_min) + B004_RESET_OFFSET, B004_DEASSERT_RESET);
    b004_delay(dev_min);

    out(B004_BASE(dev_min) + B004_ANALYSE_OFFSET, B004_DEASSERT_ANALYSE);
    b004_delay(dev_min);

    DEB(printf("B004 switching to analyse-mode done.\n");)
}


/****************************************************************************
 *
 * int bquread() - read bytes from the link interface.
 *
 * At first, the driver checks if the link-interface is ready to send a byte
 * to the PC. If not, this check is repeated up to B004_MAXTRY times.
 * If the link-interface is not ready after this loop, the driver sleeps for
 * an NO=1 ticks and then checks the link-interface again.
 * If the interface is still not ready, repeats as above incrementing NO.
 * Once almost one byte is read N0 is set to 1.
 * If B004_TIMEOUT != 0 and the link-interface is not ready for more than
 * B004_TIMEOUT ticks read aborts returnig with the number of bytes read
 * or with an error if no byte was read.
 *
 * By default, B004_TIMEOUT is = 0 (read is blocking)
 *
 *****************************************************************************/

static int
bquread(dev_t dev, struct uio *uio, int flag)
{
    unsigned int dev_min = minor(dev) & 7;

    int	timeout=B004_TIMEOUT(dev_min);
    int	Timeout=timeout;
    int idr=B004_IDR(dev_min);
    int isr=B004_ISR(dev_min);
    char	buffer[B004_MAX_BYTES];

    if ( uio->uio_resid < 0) {
	DEB(printf("B004: invalid count for reading = %d.\n", uio->uio_resid);)
	return EINVAL;
    }

    while ( uio->uio_resid ) {
	int sleep_ticks=0;
	char *p, *last, *lim;
	int i, end = min(B004_MAX_BYTES,uio->uio_resid);
	lim= &buffer[end];
	for (p= buffer; p<lim;) {
	    last=p;
	    /*** try to read as much as possible ***/
#ifdef USE_ASM
	/* assembly code uses a very tight loop, with
	 * BX= data port, DX= address port, CX=count, ES:DI=p, AL=data, AH=1
	 * SI=retry counter
	 */
	__asm__ (
	    "movl %1, %%edx\n\t" /* isr */
	    "movl %2, %%ebx\n\t" /* idr */
	    "movl %3, %%edi\n" /* p */
	    "movl %4, %%ecx\n\t" /* lim */
	    "subl %%edi, %%ecx\n\t"

	    "push %%es\n\t"
	    "movw %%ss, %%ax\n\t" /** prepare ES, DF for transfer */
	    "movw %%ax, %%es\n\t"
	    "cld\n\t"
	    "movb $1, %%ah\n\t"

	    "1:\tinb %%dx, %%al\n\t"
	    "testb %%ah, %%al\n\t"
	    "jz 2f\n\t"
	    "xchgl %%edx, %%ebx\n\t"
	    "insb\n\t"
	    "xchgl %%edx, %%ebx\n"
	    "2:\tloop 1b\n\t"

	    "pop %%es\n\t"
	    "movl %%edi, %0\n\t" /* store p */
		: /* out */ "=g" (p)
		: /* in */ "g" (isr), "g" (idr), "g" (p), "g" (lim)
		: /* regs */ "eax", "ebx", "edx", "ecx", "edi");
#else
	    for (i=lim - p; i-- ;)
		if (inb(isr)&B004_READBYTE) *p++ =(char) inb(idr);
#endif
	    if (last!=p) {
		sleep_ticks = 0;
	    } else {
		/*** no new data read, must sleep ***/
		sleep_ticks= (sleep_ticks<20 ? sleep_ticks+1 : sleep_ticks);
		if (Timeout) {
		    if (timeout <=0) {
			DEB2(printf("Read : TIMEOUT OCCURRED XXXXXXXXXXX\n");)
			break;
		    }
		    if (timeout < sleep_ticks) sleep_ticks=timeout;
		    timeout -= sleep_ticks;
		}
		DEB2(printf("Read: SLEEPING FOR %d TICKS XXXXX\n",sleep_ticks);)
		if (tsleep((caddr_t)&b004_sleep, B004PRI | PCATCH,
		    "b004_rd", sleep_ticks)!=EWOULDBLOCK) return 1;
	    }
	}
	if (p != buffer) {
	    uiomove((caddr_t)buffer, p - buffer, uio);
	}
	if( (Timeout) && (timeout <= 0) )
	    break;
    }
    return 0;
} /* bquread() */


/*
 * int bquwrite() - write to the link interface.
 */

static int
bquwrite(dev_t dev, struct uio *uio, int flag)
{
    unsigned int dev_min = minor(dev) & 7;

    int	i, end;
    int	timeout=B004_TIMEOUT(dev_min);
    int	Timeout=timeout;
    int odr=B004_ODR(dev_min);
    int osr=B004_OSR(dev_min);
    char	buffer[B004_MAX_BYTES];

    if ( uio->uio_resid < 0) {
	DEB(printf("B004 invalid argument for writing: count = %d.\n", uio->uio_resid);)
	return EINVAL;
    }

    while ( uio->uio_resid ) {
	int sleep_ticks=0;
	char *p, *last, *lim;
	end = min(B004_MAX_BYTES,uio->uio_resid);
	uiomove((caddr_t)buffer, end, uio);

	lim= &buffer[end];
	for (p= &buffer[0]; p<lim;) {
	    last=p;
#ifdef USE_ASM
	/* assembly code uses a very tight loop, with
	 * BX= data port, DX= address port, CX=count, DS:SI=p, AL=data, AH=1
	 * DI= retry counter
	 * Unfortunately, C is almost as fast as this!
	 */
	__asm__ (
	    "movl %1, %%edx\n\t" /* osr */
	    "movl %2, %%ebx\n\t" /* odr */
	    "movl %3, %%esi\n" /* p */
	    "movl %4, %%ecx\n\t" /* lim */
	    "subl %%esi, %%ecx\n\t"

	    "push %%ds\n\t"
	    "movw %%ss, %%ax\n\t" /** prepare DS, DF for transfer */
	    "movw %%ax, %%ds\n\t"
	    "cld\n\t"
	    "movb $1, %%ah\n\t"
	    "movw $100, %%di\n\t"

	    "1:\tinb %%dx, %%al\n\t"
	    "testb %%ah, %%al\n\t"
	    "jz 2f\n\t"
	    "xchgl %%edx, %%ebx\n\t"
	    "outsb\n\t"
	    "xchgl %%edx, %%ebx\n\t"
	    "loop 1b\n\t"
	    "jmp 3f\n"

	"2:\tdec %%di\n\t"
	"jnc 1b\n\t"

	    "3:\tpop %%ds\n"
	    "movl %%esi, %0\n\t" /* store p */
		: /* out */ "=g" (p)
		: /* in */ "g" (osr), "g" (odr), "g" (p), "g" (lim)
		: /* regs */ "eax", "ebx", "edx", "ecx", "esi", "edi");
#else
	    for (i=lim - p; i-- ; ) {
		if (inb(osr)&B004_WRITEBYTE) outb(odr, *p++);
	    }
#endif
	    if (p != last ) {
		sleep_ticks=0;
	    } else {
		sleep_ticks= (sleep_ticks<20 ? sleep_ticks+1 : sleep_ticks);
		if (Timeout) {
		    if (timeout <=0) {
			DEB2(printf("Write : TIMEOUT OCCURRED XXXXXXXXXXX\n");)
			uio->uio_resid += (lim - p);
			break;
		    }
		    if (timeout < sleep_ticks) sleep_ticks=timeout;
		    timeout -= sleep_ticks;
		}
		DEB2(printf("Write: SLEEPING FOR %d TICKS XXXXXXX\n",sleep_ticks);)
		if (tsleep((caddr_t)&b004_sleep, B004PRI | PCATCH,
		    "b004_rd", sleep_ticks)!=EWOULDBLOCK) return 1;
	    }
	}
	if( (Timeout) && (timeout <= 0) )
	    break;
    }
    return 0;
} /* bquwrite() */

/*
 * int bquopen() -- open the link-device.
 *
 */

static int
bquopen(dev_t dev, int flags, int fmt, struct proc *p)
{
    unsigned int dev_min = minor(dev) & 7;

    if (dev_min >= NBQU) {
	DEB(printf("B004 not opened, minor number >= %d.\n", NBQU);)
	return ENXIO;
    }
    if ((B004_F(dev_min) & B004_EXIST) == 0) {
	DEB(printf("B004 not opened, board %d does not exist.\n", dev_min);)
	return ENXIO;
    }
    if (B004_F(dev_min) & B004_BUSY) {
	DEB(printf("B004 not opened, board busy (minor = %d).\n", dev_min);)
	return EBUSY;
    }
    B004_F(dev_min) |= B004_BUSY;
    B004_TIMEOUT(dev_min) = 0;
    DEB(printf( "B004 opened, minor = %d.\n", dev_min );)
    return 0;
} /* bquopen() */


/*
 * int b004close() -- close the link device.
 */

static int
bquclose(dev_t dev, int flags, int fmt, struct proc *p)
{
    unsigned int dev_min = minor(dev) & 7;

    if (dev_min >= NBQU) {
	DEB(printf("B004 not released, minor number >= %d.\n", NBQU);)
	return ENXIO;
    }
    B004_F(dev_min) &= ~B004_BUSY;
    DEB(printf("B004(%d) released.\n", dev_min );)
    return 0;
}

static int
bqupoll(dev_t dev, int events, struct proc *p)
{
    /* still unimplemented */
    return(seltrue(dev, events, p));
}

/*
 * int bquioctl()
 *
 * Supported functions:
 * - reset
 * - analyse
 * - test error flag
 * - set timeout
 */

static int
bquioctl(dev_t dev, u_long cmd, caddr_t addr, int flag, struct proc *p)
{
    unsigned int dev_min = minor(dev) & 7;
    int result = 0;

    if (dev_min >= NBQU) {
	DEB(printf("B004 ioctl exit, minor >= %d.\n", NBQU );)
	return ENODEV;
    }

    if ((B004_F(dev_min) & B004_EXIST) == 0) {
	DEB(printf("B004 ioctl exit, (B004_F & B004_EXIST) == 0.\n" );)
	return ENODEV;
    }

    switch ( cmd ) {
	case B004RESET:		/* reset transputer */
	    bqureset(dev_min);
	DEB(printf("B004 ioctl B004RESET, done\n" );)
	    break;
	case B004WRITEABLE:	/* can we write a byte to the C012 ? */
	    IOCTL_OUT (addr, ((in(B004_OSR(dev_min))&B004_WRITEBYTE) != 0 ));
	    break;
	case B004READABLE:	/* can we read a byte from C012 ? */
	    IOCTL_OUT (addr, ((in(B004_ISR(dev_min)) & B004_READBYTE) != 0 ));
	    break;
	case B004ANALYSE:	/* switch transputer to analyse mode */
	    bquanalyse(dev_min);
	    break;
	case B004ERROR:		/* test error-flag */
	    IOCTL_OUT (addr,
		((inb(B004_BASE(dev_min)+B004_ERROR_OFFSET) &
		B004_TEST_ERROR) ? 0 : 1));
	    break;
	case B004TIMEOUT:	/* set, retrieve timeout for writing & reading*/
	    B004_TIMEOUT(dev_min) = *((int *)addr);
	    break;
	default: result = EINVAL;
    }
    return result;
} /* bquioctl() */


static int
bquattach(struct isa_device *idp)
{
	int unit = idp->id_unit;
	struct b004_struct *bp;
	int	i;

#ifdef DEVFS
#define BQU_UID 66
#define BQU_GID 66
#define BQU_PERM 0600
	bp = &b004_table[unit];
	for ( i = 0; i < 8; i++) {
#ifdef NOTYET
	/*	if (we've done all the ports found) break; */
#endif
		bp->devfs_token[i][0]=
			devfs_add_devswf(&bqu_cdevsw, i, DV_CHR, BQU_UID, 
				 	 BQU_GID, BQU_PERM, "ttyba%d", i);
		bp->devfs_token[i][0]=
			devfs_add_devswf(&bqu_cdevsw, i+64, DV_CHR, BQU_UID, 
					 BQU_GID, BQU_PERM, "ttybb%d", i);
		bp->devfs_token[i][0]=
			devfs_add_devswf(&bqu_cdevsw, i+128, DV_CHR, BQU_UID, 
					 BQU_GID, BQU_PERM, "ttybc%d", i);
		bp->devfs_token[i][0]=
			devfs_add_devswf(&bqu_cdevsw, i+192, DV_CHR, BQU_UID, 
					 BQU_GID, BQU_PERM, "ttybd%d", unit);
	}
#endif
	return 1;
}

/*
 * int bquprobe
 *
 * Initializes the driver. It tries to detect the hardware
 * and sets up all relevant data-structures.
 */

static int
bquprobe(struct isa_device *idp)
{
    unsigned int test;
    unsigned int dev_min = idp->id_unit;
    int i,found = 0;
    /* After a reset it should be possible to write a byte to
       the B004. So let'S do a reset and then test the output status
       register
    */
#ifdef undef
	printf(
	  "bquprobe::\nIOBASE 0x%x\nIRQ %d\nDRQ %d\nMSIZE %d\nUNIT %d\nFLAGS"
	  "x0%x\nALIVE %d\n",idp->id_iobase,idp->id_irq,
	  idp->id_drq,idp->id_msize,idp->id_unit,idp->id_flags,idp->id_alive);
#endif
    if(first_time){
	for(i=0;i<NBQU;i++) B004_F(i) &= ~B004_EXIST;
	first_time=0;
    }

    if(dev_min >= NBQU) return (0);	/* No more descriptors */
    if ((idp->id_iobase < 0x100) || (idp->id_iobase >= 0x1000))
	idp->id_iobase=0;		/* Dangerous isa addres ) */

    for (test = 0; (test < B004_CHANCE); test++) {
	if((idp->id_iobase==0)&&((!b004_base_addresses[test])||
	   detected(b004_base_addresses[test])))
	    continue;
	idp->id_iobase=b004_base_addresses[test];

        DEB(printf("Probing device %d at address 0x%x\n",dev_min,
		idp->id_iobase);
	)
	b004_delay(test);
	B004_F(dev_min) = 0;
	B004_TIMEOUT(dev_min) = B004_INIT_TIMEOUT;
	B004_BASE(dev_min) = idp->id_iobase;
	B004_ODR(dev_min) = B004_BASE(dev_min) + B004_ODR_OFFSET;
	B004_ISR(dev_min) = B004_BASE(dev_min) + B004_ISR_OFFSET;
	B004_OSR(dev_min) = B004_BASE(dev_min) + B004_OSR_OFFSET;
	bqureset(dev_min);

	for (i = 0; i < B004_MAXTRY; i++)
	    if ( in(B004_OSR(dev_min))  == B004_WRITEBYTE) {
		B004_F(dev_min) |= B004_EXIST;
		out(B004_BASE(dev_min) + B008_INT_OFFSET, 0);
		b004_delay(test);
		if (in(B004_BASE(dev_min) + B008_INT_OFFSET) & 0x0f == 0)
		    B004_BOARDTYPE(dev_min) = B008;
		else
		    B004_BOARDTYPE(dev_min) = B004;
		printf("bqu%d at 0x0%x (polling) is a B00%s\n",
		    dev_min,B004_IDR(dev_min),
		    (B004_BOARDTYPE(dev_min) == B004) ? "4" : "8");
		found = 1;
		break;
	    }
	if(!found) {
	    idp->id_iobase=0;
	}
	else break;

    }

    if (!found){
	DEB(printf("b004probe(): no B004-board found.\n"));
	return (0);
    }

    idp->id_maddr=NULL;
    idp->id_irq=0;
    if(B004_BOARDTYPE(dev_min) == B004)
	return(18);
    else
	return(20);
} /* bquprobe() */


static bqu_devsw_installed = 0;

static void
bqu_drvinit(void *unused)
{
	dev_t dev;

	if( ! bqu_devsw_installed ) {
		dev = makedev(CDEV_MAJOR, 0);
		cdevsw_add(&dev,&bqu_cdevsw, NULL);
		bqu_devsw_installed = 1;
    	}
}

SYSINIT(bqudev,SI_SUB_DRIVERS,SI_ORDER_MIDDLE+CDEV_MAJOR,bqu_drvinit,NULL)


#endif /* NBQU */
