/*
 * Copyright (c) 1997, 1999 Hellmuth Michaelis. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 *---------------------------------------------------------------------------
 *
 *	i4b_drn_ngo.c - Dr. Neuhaus Niccy GO@ and SAGEM Cybermod
 *	--------------------------------------------------------
 *
 *	$Id: isic_isapnp_drn_ngo.c,v 1.10 2007/10/19 12:00:32 ad Exp $
 *
 *      last edit-date: [Fri Jan  5 11:38:29 2001]
 *
 *---------------------------------------------------------------------------*/

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: isic_isapnp_drn_ngo.c,v 1.10 2007/10/19 12:00:32 ad Exp $");

#include "opt_isicpnp.h"
#ifdef ISICPNP_DRN_NGO

#include <sys/param.h>
#if defined(__FreeBSD__) && __FreeBSD__ >= 3
#include <sys/ioccom.h>
#else
#include <sys/ioctl.h>
#endif
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/mbuf.h>

#if defined(__NetBSD__) && __NetBSD_Version__ >= 104230000
#include <sys/callout.h>
#endif

#ifdef __FreeBSD__
#include <machine/clock.h>
#include <i386/isa/isa_device.h>
#else
#include <sys/bus.h>
#include <sys/device.h>
#endif

#include <sys/socket.h>
#include <net/if.h>

#ifdef __FreeBSD__
#include <machine/i4b_debug.h>
#include <machine/i4b_ioctl.h>
#else
#include <netisdn/i4b_debug.h>
#include <netisdn/i4b_ioctl.h>
#endif

#include <netisdn/i4b_global.h>
#include <netisdn/i4b_l2.h>
#include <netisdn/i4b_l1l2.h>

#include <dev/ic/isic_l1.h>
#include <dev/ic/isac.h>
#include <dev/ic/hscx.h>

/*---------------------------------------------------------------------------*
 *	Niccy GO@ definitions
 *
 *	the card uses 2 i/o addressranges each using 2 bytes
 *
 *	addressrange 0:
 *		offset 0 - ISAC dataregister
 *		offset 1 - HSCX dataregister
 *	addressrange 1:
 *		offset 0 - ISAC addressregister
 *		offset 1 - HSCX addressregister
 *
 *	to access an ISAC/HSCX register, you have to write the register
 *	number into the ISAC or HSCX addressregister and then read/write
 *	data for the ISAC/HSCX register into/from the corresponding
 *	dataregister.
 *
 *	Thanks to Klaus Muehle of Dr. Neuhaus Telekommunikation for giving
 *	out this information!
 *
 *---------------------------------------------------------------------------*/
#define NICCY_PORT_MIN	0x200
#define NICCY_PORT_MAX	0x3e0

#define HSCX_ABIT	0x1000		/* flag, HSCX A is meant */
#define HSCX_BBIT	0x2000		/* flag, HSCX B is meant */

#define HSCX_BOFF	0x40

#define ADDR_OFF	2		/* address register range offset XXX */

#define ISAC_DATA	0
#define HSCX_DATA	1

#define ISAC_ADDR	0
#define HSCX_ADDR	1

#ifdef __FreeBSD__

#if 0
#define HSCXADEBUG
#define HSCXBDEBUG
#define ISACDEBUG
#else
#undef HSCXADEBUG
#undef HSCXBDEBUG
#undef ISACDEBUG
#endif

/*---------------------------------------------------------------------------*
 *	Dr. Neuhaus Niccy GO@ read fifo routine
 *---------------------------------------------------------------------------*/
static void
drnngo_read_fifo(void *buf, const void *base, size_t len)
{
	register int offset;
	register u_int data;

	int x = SPLI4B();

	if((u_int)base & HSCX_ABIT)
	{
		(u_int)base &= ~HSCX_ABIT;
		(u_int)data = ((u_int)base + HSCX_DATA);
		(u_int)base +=  (ADDR_OFF + HSCX_ADDR);
		offset = 0;
#ifdef HSCXADEBUG
printf("GO/A/frd: base=0x%x, data=0x%x, len=%d\n", base, data, len);
#endif
	}
	else if((u_int)base & HSCX_BBIT)
	{
		(u_int)base &= ~HSCX_BBIT;
		(u_int)data = ((u_int)base + HSCX_DATA);
		(u_int)base +=  (ADDR_OFF + HSCX_ADDR);
		offset = HSCX_BOFF;
#ifdef HSCXBDEBUG
printf("GO/B/frd: base=0x%x, data=0x%x, len=%d\n", base, data, len);
#endif
	}
	else
	{
		(u_int)data = ((u_int)base + ISAC_DATA);
		(u_int)base +=  (ADDR_OFF + ISAC_ADDR);
		offset = 0;
#ifdef ISACDEBUG
printf("GO/I/frd: base=0x%x, data=0x%x, len=%d\n", base, data, len);
#endif
	}

	for(;len > 0; len--, offset++)
	{
		outb((int)base, (u_char)offset);
		*((u_char *)buf + offset) = inb((int)data);
	}

	splx(x);
}

/*---------------------------------------------------------------------------*
 *	Dr. Neuhaus Niccy GO@ write fifo routine
 *---------------------------------------------------------------------------*/
static void
drnngo_write_fifo(void *base, const void *buf, size_t len)
{
	register int offset;
	register u_int data;

	int x = SPLI4B();

	if((u_int)base & HSCX_ABIT)
	{
		(u_int)base &= ~HSCX_ABIT;
		(u_int)data = ((u_int)base + HSCX_DATA);
		(u_int)base +=  (ADDR_OFF + HSCX_ADDR);
		offset = 0;
#ifdef HSCXADEBUG
printf("GO/A/fwr: base=0x%x, data=0x%x, len=%d\n", base, data, len);
#endif
	}
	else if((u_int)base & HSCX_BBIT)
	{
		(u_int)base &= ~HSCX_BBIT;
		(u_int)data = ((u_int)base + HSCX_DATA);
		(u_int)base +=  (ADDR_OFF + HSCX_ADDR);
		offset = HSCX_BOFF;
#ifdef HSCXBDEBUG
printf("GO/B/fwr: base=0x%x, data=0x%x, len=%d\n", base, data, len);
#endif
	}
	else
	{
		(u_int)data = ((u_int)base + ISAC_DATA);
		(u_int)base +=  (ADDR_OFF + ISAC_ADDR);
		offset = 0;
#ifdef ISACDEBUG
printf("GO/I/fwr: base=0x%x, data=0x%x, len=%d\n", base, data, len);
#endif
	}

	for(;len > 0; len--, offset++)
	{
		outb((int)base, (u_char)offset);
		outb((int)data, *((u_char *)buf + offset));
	}

	splx(x);
}

/*---------------------------------------------------------------------------*
 *	Dr. Neuhaus Niccy GO@ write register routine
 *---------------------------------------------------------------------------*/
static void
drnngo_write_reg(u_char *base, u_int offset, u_int v)
{
	int x = SPLI4B();
	if((u_int)base & HSCX_ABIT)
	{
		(u_int)base &= ~HSCX_ABIT;
		outb((int)base + ADDR_OFF + HSCX_ADDR, (u_char)offset);
		outb((int)base + HSCX_DATA, (u_char)v);
#ifdef HSCXADEBUG
printf("GO/A/rwr: base=0x%x, addr=0x%x, offset=0x%x, val=0x%x\n",
	(int)base + ADDR_OFF + HSCX_ADDR, (int)base + HSCX_DATA,
	(u_char)offset, (u_char)v);
#endif
	}
	else if((u_int)base & HSCX_BBIT)
	{
		(u_int)base &= ~HSCX_BBIT;
		outb((int)base + ADDR_OFF + HSCX_ADDR, (u_char)(offset + HSCX_BOFF));
		outb((int)base + HSCX_DATA, (u_char)v);
#ifdef HSCXBDEBUG
printf("GO/B/rwr: base=0x%x, addr=0x%x, offset=0x%x, val=0x%x\n",
	(int)base + ADDR_OFF + HSCX_ADDR, (int)base + HSCX_DATA,
	(u_char)(offset + HSCX_BOFF), (u_char)v);
#endif
	}
	else
	{
		outb((int)base + ADDR_OFF + ISAC_ADDR, (u_char)offset);
		outb((int)base + ISAC_DATA, (u_char)v);
#ifdef ISACDEBUG
printf("GO/I/rwr: base=0x%x, addr=0x%x, offset=0x%x, val=0x%x\n",
	(int)base + ADDR_OFF + ISAC_ADDR, (int)base + ISAC_DATA,
	(u_char)offset, (u_char)v);
#endif
	}
	splx(x);
}

/*---------------------------------------------------------------------------*
 *	Dr. Neuhaus Niccy GO@ read register routine
 *---------------------------------------------------------------------------*/
static u_char
drnngo_read_reg(u_char *base, u_int offset)
{
	u_char val;
	int x = SPLI4B();

	if((u_int)base & HSCX_ABIT)
	{
		(u_int)base &= ~HSCX_ABIT;
		outb((int)base + ADDR_OFF + HSCX_ADDR, (u_char)offset);
		val = inb((int)base + HSCX_DATA);
#ifdef HSCXADEBUG
printf("GO/A/rrd: base=0x%x, addr=0x%x, offset=0x%x, val=0x%x\n",
	(int)base + ADDR_OFF + HSCX_ADDR, (int)base + HSCX_DATA,
	(u_char)offset, (u_char)val);
#endif
	}
	else if((u_int)base & HSCX_BBIT)
	{
		(u_int)base &= ~HSCX_BBIT;
		outb((int)base + ADDR_OFF + HSCX_ADDR, (u_char)(offset + HSCX_BOFF));
		val = inb((int)base + HSCX_DATA);
#ifdef HSCXBDEBUG
printf("GO/B/rrd: base=0x%x, addr=0x%x, offset=0x%x, val=0x%x\n",
	(int)base + ADDR_OFF + HSCX_ADDR, (int)base + HSCX_DATA,
	(u_char)(offset + HSCX_BOFF), (u_char)val);
#endif
	}
	else
	{
		outb((int)base + ADDR_OFF + ISAC_ADDR, (u_char)offset);
		val = inb((int)base + ISAC_DATA);
#ifdef ISACDEBUG
printf("GO/I/rrd: base=0x%x, addr=0x%x, offset=0x%x, val=0x%x\n",
	(int)base + ADDR_OFF + ISAC_ADDR, (int)base + ISAC_DATA,
	(u_char)offset, (u_char)val);
#endif
	}
	splx(x);
	return(val);
}

/*---------------------------------------------------------------------------*
 *	isic_probe_drnngo - probe for Dr. Neuhaus Niccy GO@
 *---------------------------------------------------------------------------*/
int
isic_probe_drnngo(struct isa_device *dev, unsigned int iobase2)
{
	struct isic_softc *sc = &l1_sc[dev->id_unit];

	/* check max unit range */

	if(dev->id_unit >= ISIC_MAXUNIT)
	{
		printf("isic%d: Error, unit %d >= ISIC_MAXUNIT for Dr. Neuhaus Niccy GO@!\n",
				dev->id_unit, dev->id_unit);
		return(0);
	}
	sc->sc_unit = dev->id_unit;

	/* check IRQ validity */

	switch(ffs(dev->id_irq)-1)
	{
		case 3:
		case 4:
		case 5:
		case 9:
		case 10:
		case 11:
		case 12:
		case 15:
			break;

		default:
			printf("isic%d: Error, invalid IRQ [%d] specified for Dr. Neuhaus Niccy GO@!\n",
				dev->id_unit, ffs(dev->id_irq)-1);
			return(0);
			break;
	}
	sc->sc_irq = dev->id_irq;

	/* check if memory addr specified */

	if(dev->id_maddr)
	{
		printf("isic%d: Error, mem addr 0x%lx specified for Dr. Neuhaus Niccy GO@!\n",
			dev->id_unit, (u_long)dev->id_maddr);
		return(0);
	}
	dev->id_msize = 0;

	/* check if we got an iobase */

	if(dev->id_iobase < NICCY_PORT_MIN || dev->id_iobase > NICCY_PORT_MAX)
	{
		printf("isic%d: Error, invalid iobase 0x%x specified for Dr. Neuhaus Niccy GO@!\n",
			dev->id_unit, dev->id_iobase);
		return(0);
	}
	sc->sc_port = dev->id_iobase;

	if(iobase2 == 0)
	{
		printf("isic%d: Error, iobase2 is 0 for Dr. Neuhaus Niccy GO@!\n",
			dev->id_unit);
		return(0);
	}

	if(iobase2 < NICCY_PORT_MIN || iobase2 > NICCY_PORT_MAX)
	{
		printf("isic%d: Error, invalid port1 0x%x specified for Dr. Neuhaus Niccy GO@!\n",
			dev->id_unit, iobase2);
		return(0);
	}

/*XXX*/	if((dev->id_iobase + 2) != iobase2)
	{
		printf("isic%d: Error, port1 must be (port0+2) for Dr.Neuhaus Niccy GO@!\n",
			dev->id_unit);
		return(0);
	}

	/* setup ISAC access routines */

	sc->clearirq = NULL;
	sc->readreg = drnngo_read_reg;
	sc->writereg = drnngo_write_reg;

	sc->readfifo = drnngo_read_fifo;
	sc->writefifo = drnngo_write_fifo;

	/* setup card type */

	sc->sc_cardtyp = CARD_TYPEP_DRNNGO;

	/* setup IOM bus type */

	sc->sc_bustyp = BUS_TYPE_IOM2;

	sc->sc_ipac = 0;
	sc->sc_bfifolen = HSCX_FIFO_LEN;

	/* setup ISAC and HSCX base addr */

	ISAC_BASE   = (void *)dev->id_iobase;
	HSCX_A_BASE = (void *)(((u_int)dev->id_iobase) | HSCX_ABIT);
	HSCX_B_BASE = (void *)(((u_int)dev->id_iobase) | HSCX_BBIT);

	/*
	 * Read HSCX A/B VSTR.  Expected value for Dr. Neuhaus Niccy GO@ based
	 * boards is 0x05 in the least significant bits.
	 */

	if( ((HSCX_READ(0, H_VSTR) & 0xf) != 0x5) ||
	    ((HSCX_READ(1, H_VSTR) & 0xf) != 0x5) )
	{
		printf("isic%d: HSCX VSTR test failed for Dr. Neuhaus Niccy GO@\n",
			dev->id_unit);
		printf("isic%d: HSC0: VSTR: %#x\n",
			dev->id_unit, HSCX_READ(0, H_VSTR));
		printf("isic%d: HSC1: VSTR: %#x\n",
			dev->id_unit, HSCX_READ(1, H_VSTR));
		return (0);
	}

	return (1);
}

/*---------------------------------------------------------------------------*
 *	isic_attach_drnngo - attach Dr. Neuhaus Niccy GO@
 *---------------------------------------------------------------------------*/
int
isic_attach_drnngo(struct isa_device *dev, unsigned int iobase2)
{
	return (1);
}

#else

static u_int8_t drnngo_read_reg(struct isic_softc *sc, int what, bus_size_t offs);
static void drnngo_write_reg(struct isic_softc *sc, int what, bus_size_t offs, u_int8_t data);
static void drnngo_read_fifo(struct isic_softc *sc, int what, void *buf, size_t size);
static void drnngo_write_fifo(struct isic_softc *sc, int what, const void *data, size_t size);
void isic_attach_drnngo(struct isic_softc *sc);

/*
 * Mapping from "what" parameter to offsets into the io map
 */
static struct {
	bus_size_t oa,	/* address register offset */
		   od, 	/* data register offset */
		   or;	/* additional chip register offset */
} offset[] =
{
	{ ISAC_ADDR, ISAC_DATA, 0 },		/* ISAC access */
	{ HSCX_ADDR, HSCX_DATA, 0 },		/* HSCX A access */
	{ HSCX_ADDR, HSCX_DATA, HSCX_BOFF }	/* HSCX B access */
};

static void
drnngo_read_fifo(struct isic_softc *sc, int what, void *buf, size_t size)
{
	bus_space_tag_t t = sc->sc_maps[0].t;
	bus_space_handle_t hd = sc->sc_maps[0].h, ha = sc->sc_maps[1].h;
	bus_space_write_1(t, ha, offset[what].oa, offset[what].or);
	bus_space_read_multi_1(t, hd, offset[what].od, buf, size);
}

static void
drnngo_write_fifo(struct isic_softc *sc, int what, const void *buf, size_t size)
{
	bus_space_tag_t t = sc->sc_maps[0].t;
	bus_space_handle_t hd = sc->sc_maps[0].h, ha = sc->sc_maps[1].h;
	bus_space_write_1(t, ha, offset[what].oa, offset[what].or);
	bus_space_write_multi_1(t, hd, offset[what].od, buf, size);
}

static void
drnngo_write_reg(struct isic_softc *sc, int what, bus_size_t offs, u_int8_t data)
{
	bus_space_tag_t t = sc->sc_maps[0].t;
	bus_space_handle_t hd = sc->sc_maps[0].h, ha = sc->sc_maps[1].h;
	bus_space_write_1(t, ha, offset[what].oa, offs+offset[what].or);
	bus_space_write_1(t, hd, offset[what].od, data);
}

static u_int8_t
drnngo_read_reg(struct isic_softc *sc, int what, bus_size_t offs)
{
	bus_space_tag_t t = sc->sc_maps[0].t;
	bus_space_handle_t hd = sc->sc_maps[0].h, ha = sc->sc_maps[1].h;
	bus_space_write_1(t, ha, offset[what].oa, offs+offset[what].or);
	return bus_space_read_1(t, hd, offset[what].od);
}

void
isic_attach_drnngo(struct isic_softc *sc)
{
	/* setup access routines */

	sc->clearirq = NULL;
	sc->readreg = drnngo_read_reg;
	sc->writereg = drnngo_write_reg;

	sc->readfifo = drnngo_read_fifo;
	sc->writefifo = drnngo_write_fifo;

	/* setup card type */

	sc->sc_cardtyp = CARD_TYPEP_DRNNGO;

	/* setup IOM bus type */

	sc->sc_bustyp = BUS_TYPE_IOM2;

	sc->sc_ipac = 0;
	sc->sc_bfifolen = HSCX_FIFO_LEN;
}

#endif

#endif /* ISICPNP_DRN_NGO */
