/*
 *
 * ===================================
 * HARP  |  Host ATM Research Platform
 * ===================================
 *
 *
 * This Host ATM Research Platform ("HARP") file (the "Software") is
 * made available by Network Computing Services, Inc. ("NetworkCS")
 * "AS IS".  NetworkCS does not provide maintenance, improvements or
 * support of any kind.
 *
 * NETWORKCS MAKES NO WARRANTIES OR REPRESENTATIONS, EXPRESS OR IMPLIED,
 * INCLUDING, BUT NOT LIMITED TO, IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE, AS TO ANY ELEMENT OF THE
 * SOFTWARE OR ANY SUPPORT PROVIDED IN CONNECTION WITH THIS SOFTWARE.
 * In no event shall NetworkCS be responsible for any damages, including
 * but not limited to consequential damages, arising from or relating to
 * any use of the Software or related support.
 *
 * Copyright 1994-1998 Network Computing Services, Inc.
 *
 * Copies of this Software may be made, however, the above copyright
 * notice must be reproduced on all copies.
 *
 *	@(#) $FreeBSD$
 *
 */

/*
 * Efficient ENI adapter support
 * -----------------------------
 *
 * Module supports PCI interface to ENI adapter
 *
 */

#include <netatm/kern_include.h>

#include <dev/hea/eni_stats.h>
#include <dev/hea/eni.h>
#include <dev/hea/eni_var.h>

#ifndef	lint
__RCSID("@(#) $FreeBSD$");
#endif

/*
 * Typedef local functions
 */
static const char	*eni_pci_probe __P((pcici_t, pcidi_t));
static void	eni_pci_attach __P((pcici_t, int));
static int 	eni_get_ack __P((Eni_unit *));
static int	eni_get_sebyte __P((Eni_unit *));
static void	eni_read_seeprom __P((Eni_unit *));
#ifdef	__FreeBSD__
#if BSD < 199506
static int	eni_pci_shutdown __P((struct kern_devconf *, int));
#else
static void	eni_pci_shutdown __P((void *, int));
#endif
static void	eni_pci_reset __P((Eni_unit *));
#endif	/* __FreeBSD__ */

/*
 * Used by kernel to return number of claimed devices
 */
#ifdef	__FreeBSD__
static u_long eni_nunits;

static struct pci_device eni_pci_device = {
	ENI_DEV_NAME,
	eni_pci_probe,
	eni_pci_attach,
	&eni_nunits,
#if BSD < 199506
	eni_pci_shutdown
#else
	NULL
#endif
};

COMPAT_PCI_DRIVER (eni_pci, eni_pci_device);
#endif	/* __FreeBSD__ */

/*
 * Called by kernel with PCI device_id which was read from the PCI
 * register set. If the identified vendor is Efficient, see if we
 * recognize the particular device. If so, return an identifying string,
 * if not, return null.
 *
 * Arguments:
 *	config_id	PCI config token
 *	device_id	contents of PCI device ID register
 *
 * Returns:
 *	string		Identifying string if we will handle this device
 *	NULL		unrecognized vendor/device
 *
 */
static const char *
eni_pci_probe ( pcici_t config_id, pcidi_t device_id )
{

	if ( (device_id & 0xFFFF) == EFF_VENDOR_ID ) {
		switch ( (device_id >> 16) ) {
			case EFF_DEV_ID:
				return ( "Efficient ENI ATM Adapter" );
/* NOTREACHED */
				break;
		}
	}

	return ( NULL );
}

/*
 * The ENI-155p adapter uses an ATMEL AT24C01 serial EEPROM to store
 * configuration information. The SEEPROM is accessed via two wires,
 * CLOCK and DATA, which are accessible via the PCI configuration
 * registers. The following macros manipulate the lines to access the
 * SEEPROM. See http://www.atmel.com/atmel/products/prod162.htm for
 * a description of the AT24C01 part. Value to be read/written is
 * part of the per unit structure.
 */
/*
 * Write bits to SEEPROM
 */
#define	WRITE_SEEPROM()	(						\
    {									\
	(void) pci_conf_write ( eup->eu_pcitag, SEEPROM,		\
		eup->eu_sevar );					\
	DELAY(SEPROM_DELAY);						\
    }									\
)
/*
 * Stobe first the DATA, then the CLK lines high
 */
#define	STROBE_HIGH()	(						\
    {									\
	eup->eu_sevar |= SEPROM_DATA; WRITE_SEEPROM();			\
	eup->eu_sevar |= SEPROM_CLK;  WRITE_SEEPROM();			\
    }									\
)
/*
 * Strobe first the CLK, then the DATA lines high
 */
#define	INV_STROBE_HIGH()	(					\
    {									\
	eup->eu_sevar |= SEPROM_CLK;  WRITE_SEEPROM();			\
	eup->eu_sevar |= SEPROM_DATA; WRITE_SEEPROM();			\
    }									\
)
/*
 * Strobe first the CLK, then the DATA lines low - companion to
 * STROBE_HIGH()
 */
#define	STROBE_LOW()	(						\
    {									\
	eup->eu_sevar &= ~SEPROM_CLK;  WRITE_SEEPROM();			\
	eup->eu_sevar &= ~SEPROM_DATA; WRITE_SEEPROM();			\
    }									\
)
/*
 * Strobe first the DATA, then the CLK lines low - companion to
 * INV_STROBE_HIGH()
 */
#define	INV_STROBE_LOW()	(					\
    {									\
	eup->eu_sevar &= ~SEPROM_DATA; WRITE_SEEPROM();			\
	eup->eu_sevar &= ~SEPROM_CLK;  WRITE_SEEPROM();			\
    }									\
)
/*
 * Strobe the CLK line high, then low
 */
#define	STROBE_CLK()	(						\
    {									\
	eup->eu_sevar |= SEPROM_CLK;   WRITE_SEEPROM();			\
	eup->eu_sevar &= ~SEPROM_CLK;  WRITE_SEEPROM();			\
    }									\
)

/*
 * Look for a positive ACK from the SEEPROM. Cycle begins by asserting
 * the DATA line, then the CLK line. The DATA line is then read to
 * retrieve the ACK status, and then the cycle is finished by deasserting
 * the CLK line, and asserting the DATA line.
 *
 * Arguments:
 *	eup		pointer to per unit structure
 *
 * Returns:
 *	0/1		value of ACK
 *
 */
static int
eni_get_ack ( eup )
	Eni_unit	*eup;
{
	int		ack;

	STROBE_HIGH();
	/*
	 * Read DATA line from SEPROM
	 */
	eup->eu_sevar = pci_conf_read ( eup->eu_pcitag, SEEPROM );
	DELAY ( SEPROM_DELAY );
	ack = eup->eu_sevar & SEPROM_DATA;

	eup->eu_sevar &= ~SEPROM_CLK;
	WRITE_SEEPROM ();
	eup->eu_sevar |= SEPROM_DATA;
	WRITE_SEEPROM ();

	return ( ack );
}

/*
 * Read a byte from the SEEPROM. Data is read as 8 bits. There are two types
 * of read operations. The first is a single byte read, the second is
 * multiple sequential bytes read. Both cycles begin with a 'START' operation,
 * followed by a memory address word. Following the memory address, the
 * SEEPROM will send a data byte, followed by an ACK. If the host responds
 * with a 'STOP' operation, then a single byte cycle is performed. If the
 * host responds with an 'ACK', then the memory address is incremented, and
 * the next sequential memory byte is serialized.
 *
 * Arguments:
 *	eup		pointer to per unit structure
 *
 * Returns:
 *	val		value of byte read from SEEPROM
 *
 */
static int
eni_get_sebyte( eup )
	Eni_unit	*eup;
{
	int	i;
	int	data;
	int	rval;

	/* Initial value */
	rval = 0;
	/* Read 8 bits */
	for ( i = 0; i < 8; i++ ) {
		/* Shift bits to left so the next bit goes to position 0 */
		rval <<= 1;
		/* Indicate we're ready to read bit */
		STROBE_HIGH();
		/*
		 * Read DATA line from SEPROM
		 */
		data = pci_conf_read ( eup->eu_pcitag, SEEPROM );
		DELAY ( SEPROM_DELAY );
		/* (Possibly) mask bit into accumulating value */
		if ( data & SEPROM_DATA )
			rval |= 1;		/* If DATA bit '1' */
		/* Indicate we're done reading this bit */
		STROBE_LOW();
	}

	/* Return acquired byte */
	return ( rval );
}

/*
 * The AT24C01 is a 1024 bit part organized as 128 words by 8 bits.
 * We will read the entire contents into the per unit structure. Later,
 * we'll retrieve the MAC address and serial number from the data read.
 *
 * Arguments:
 *	eup		pointer to per unit structure
 *
 * Returns:
 *	none
 *
 */
static void
eni_read_seeprom ( eup )
	Eni_unit	*eup;
{
	int	addr;
	int	i, j;

	/*
	 * Set initial state
	 */
	eup->eu_sevar = SEPROM_DATA | SEPROM_CLK;
	WRITE_SEEPROM ();

	/* Loop for all bytes */
	for ( i = 0; i < SEPROM_SIZE ; i++ ) {
		/* Send START operation */
		STROBE_HIGH();
		INV_STROBE_LOW();

		/*
		 * Send address. Addresses are sent as 7 bits plus
		 * last bit high.
		 */
		addr = ((i) << 1) + 1;
		/*
		 * Start with high order bit first working toward low
		 * order bit.
		 */
		for ( j = 7; j >= 0; j-- ) {
			/* Set current bit value */
			eup->eu_sevar = ( addr >> j ) & 1 ?
			    eup->eu_sevar | SEPROM_DATA :
				eup->eu_sevar & ~SEPROM_DATA;
			WRITE_SEEPROM ();
			/* Indicate we've sent it */
			STROBE_CLK();
		}
		/*
		 * We expect a zero ACK after sending the address
		 */
		if ( !eni_get_ack ( eup ) ) {
			/* Address okay - read data byte */
			eup->eu_seeprom[i] = eni_get_sebyte ( eup );
			/* Grab but ignore the ACK op */
			(void) eni_get_ack ( eup );
		} else {
			/* Address ACK was bad - can't retrieve data byte */
			eup->eu_seeprom[i] = 0xff;
		}
	}

	return;
}

/*
 * The kernel has found a device which we are willing to support.
 * We are now being called to do any necessary work to make the
 * device initially usable. In our case, this means allocating
 * structure memory, configuring registers, mapping device
 * memory, setting pointers, registering with the core services,
 * and doing the initial PDU processing configuration.
 *
 * Arguments:
 *	config_id		PCI device token
 *	unit			instance of the unit
 *
 * Returns:
 *	none		
 *
 */
static void
eni_pci_attach ( pcici_t config_id, int unit )
{
	vm_offset_t	va;
	vm_offset_t	pa;
	Eni_unit	*eup;
	long		val;

	/*
	 * Just checking...
	 */
	if ( unit >= ENI_MAX_UNITS ) {
		log ( LOG_ERR, "%s%d: too many devices\n",
			ENI_DEV_NAME, unit );
		return;
	}

	/*
	 * Make sure this isn't a duplicate unit
	 */
	if ( eni_units[unit] != NULL )
		return;

	/*
	 * Allocate a new unit structure
	 */
	eup = (Eni_unit *) atm_dev_alloc ( sizeof(Eni_unit), sizeof(int), 0 );
	if ( eup == NULL )
		return;

	/*
	 * Start initializing it
	 */
	eup->eu_unit = unit;
	eup->eu_mtu = ENI_IFF_MTU;
	eup->eu_pcitag = config_id;
	eup->eu_ioctl = eni_atm_ioctl;
	eup->eu_instvcc = eni_instvcc;
	eup->eu_openvcc = eni_openvcc;
	eup->eu_closevcc = eni_closevcc;
	eup->eu_output = eni_output;
	eup->eu_vcc_pool = &eni_vcc_pool;
	eup->eu_nif_pool = &eni_nif_pool;

 	/*
	 * Enable Memory Mapping / Bus Mastering 
	 */
	val = pci_conf_read(config_id, PCI_COMMAND_STATUS_REG);
	val |= (PCIM_CMD_MEMEN | PCIM_CMD_BUSMASTEREN);
	pci_conf_write(config_id, PCI_COMMAND_STATUS_REG, val);

	/*
	 * Map in adapter RAM
	 */
	val = pci_conf_read(config_id, PCI_COMMAND_STATUS_REG);
	if ((val & PCIM_CMD_MEMEN) == 0) {
		log(LOG_ERR, "%s%d: memory mapping not enabled\n", 
			ENI_DEV_NAME, unit);
		goto failed;
	}
	if ( ( pci_map_mem ( config_id, PCI_MAP_REG_START, &va, &pa ) ) == 0 )
	{
		log(LOG_ERR, "%s%d: unable to map memory\n", 
			ENI_DEV_NAME, unit);
		goto failed;
	}
	/*
	 * Map okay - retain address assigned
	 */
	eup->eu_base = (Eni_mem)va;
	eup->eu_ram = (Eni_mem)(eup->eu_base + RAM_OFFSET);

	/*
	 * Map memory structures into adapter space
	 */
	eup->eu_suni = (Eni_mem)(eup->eu_base + SUNI_OFFSET);
	eup->eu_midway = (Eni_mem)(eup->eu_base + MIDWAY_OFFSET);
	eup->eu_vcitbl = (VCI_Table *)(eup->eu_base + VCITBL_OFFSET);
	eup->eu_rxdma = (Eni_mem)(eup->eu_base + RXQUEUE_OFFSET);
	eup->eu_txdma = (Eni_mem)(eup->eu_base + TXQUEUE_OFFSET);
	eup->eu_svclist = (Eni_mem)(eup->eu_base + SVCLIST_OFFSET);
	eup->eu_servread = 0;

	/*
	 * Reset the midway chip
	 */
	eup->eu_midway[MIDWAY_ID] = MIDWAY_RESET;

	/*
	 * Size and test adapter memory. Initialize our adapter memory
	 * allocater.
	 */
	if ( eni_init_memory ( eup ) < 0 ) {
		/*
		 * Adapter memory test failed. Clean up and
		 * return.
		 */
		log(LOG_ERR, "%s%d: memory test failed\n", 
			ENI_DEV_NAME, unit);
		goto failed;
	}

	/*
	 * Read the contents of the SEEPROM
	 */
	eni_read_seeprom ( eup );
	/*
	 * Copy MAC address to PIF and config structures
	 */
	KM_COPY ( (caddr_t)&eup->eu_seeprom[SEPROM_MAC_OFF],
	    (caddr_t)&eup->eu_pif.pif_macaddr, sizeof(struct mac_addr) );
	eup->eu_config.ac_macaddr = eup->eu_pif.pif_macaddr;

	/*
	 * Copy serial number into config space
	 */
	eup->eu_config.ac_serial =
		ntohl(*(u_long *)&eup->eu_seeprom[SEPROM_SN_OFF]);

	/*
	 * Convert Endianess on DMA
	 */
	val = pci_conf_read ( config_id, PCI_CONTROL_REG );
	val |= ENDIAN_SWAP_DMA;
	pci_conf_write ( config_id, PCI_CONTROL_REG, val );

	/*
	 * Map interrupt in
	 */
	if ( !pci_map_int ( config_id, eni_intr, (void *)eup, &net_imask ) )
	{
		log(LOG_ERR, "%s%d: unable to map interrupt\n", 
			ENI_DEV_NAME, unit);
		goto failed;
	}

	/*
	 * Setup some of the adapter configuration
	 */
	/*
	 * Get MIDWAY ID
	 */
	val = eup->eu_midway[MIDWAY_ID];
	eup->eu_config.ac_vendor = VENDOR_ENI;
	eup->eu_config.ac_vendapi = VENDAPI_ENI_1;
	eup->eu_config.ac_device = DEV_ENI_155P;
	eup->eu_config.ac_media = val & MEDIA_MASK ? MEDIA_UTP155 : MEDIA_OC3C;
	eup->eu_pif.pif_pcr = ATM_PCR_OC3C;
	eup->eu_config.ac_bustype = BUS_PCI;
	eup->eu_config.ac_busslot = config_id->bus << 8 | config_id->slot;

	/*
	 * Make a hw version number from the ID register values.
	 * Format: {Midway ID}.{Mother board ID}.{Daughter board ID}
	 */
	snprintf ( eup->eu_config.ac_hard_vers,
	    sizeof ( eup->eu_config.ac_hard_vers ),
		"%ld/%ld/%ld",
		(val >> ID_SHIFT) & ID_MASK,
		(val >> MID_SHIFT) & MID_MASK,
		(val >> DID_SHIFT) & DID_MASK );

	/*
	 * There is no software version number
	 */
	eup->eu_config.ac_firm_vers[0] = '\0';

	/*
	 * Save device ram info for user-level programs
	 * NOTE: This really points to start of EEPROM
	 * and includes all the device registers in the
	 * lower 2 Megabytes.
	 */
	eup->eu_config.ac_ram = (long)eup->eu_base;
	eup->eu_config.ac_ramsize = eup->eu_ramsize + ENI_REG_SIZE;

	/*
	 * Setup max VPI/VCI values
	 */
	eup->eu_pif.pif_maxvpi = ENI_MAX_VPI;
	eup->eu_pif.pif_maxvci = ENI_MAX_VCI;

	/*
	 * Register this interface with ATM core services
	 */
	if ( atm_physif_register
		( (Cmn_unit *)eup, ENI_DEV_NAME, eni_services ) != 0 )
	{
		/*
		 * Registration failed - back everything out
		 */
		log(LOG_ERR, "%s%d: atm_physif_register failed\n", 
			ENI_DEV_NAME, unit);
		goto failed;
	}

	eni_units[unit] = eup;

#if BSD >= 199506
	/*
	 * Add hook to out shutdown function
	 */
	EVENTHANDLER_REGISTER(shutdown_post_sync, eni_pci_shutdown, eup,
			      SHUTDOWN_PRI_DEFAULT);
	
#endif

	/*
	 * Initialize driver processing
	 */
	if ( eni_init ( eup ) ) {
		log(LOG_ERR, "%s%d: adapter init failed\n", 
			ENI_DEV_NAME, unit);
		goto failed;
	}

	return;

failed:
	/*
	 * Attach failed - clean up
	 */
	eni_pci_reset(eup);
	(void) pci_unmap_int(config_id);
	atm_dev_free(eup);
	return;
}

/*
 * Device reset routine
 *
 * Arguments:
 *	eup			pointer to per unit structure
 *
 * Returns:
 *	none
 *
 */
static void
eni_pci_reset ( eup )
	Eni_unit *eup;
{

	/*
	 * We should really close down any open VCI's and
	 * release all memory (TX and RX) buffers. For now,
	 * we assume we're shutting the card down for good.
	 */

	if (eup->eu_midway) {
		/*
		 * Issue RESET command to Midway chip
		 */
		eup->eu_midway[MIDWAY_ID] = MIDWAY_RESET;

		/*
		 * Delay to allow everything to terminate
		 */
		DELAY ( MIDWAY_DELAY );
	}

	return;
}

#ifdef	__FreeBSD__
#if BSD < 199506
/*
 * Device shutdown routine
 *
 * Arguments:
 *	kdc		pointer to device's configuration table
 *	force		forced shutdown flag
 *
 * Returns:
 * 	none
 *
 */
static int
eni_pci_shutdown ( kdc, force )
	struct kern_devconf	*kdc;
	int			force;
{
	Eni_unit	*eup = NULL;

	if ( kdc->kdc_unit < eni_nunits ) {

		eup = eni_units[kdc->kdc_unit];
		if ( eup != NULL ) {
			/* Do device reset */
			eni_pci_reset ( eup );
		}
	}

	(void) dev_detach ( kdc );
	return ( 0 );
}
#else
/*
 * Device shutdown routine
 *
 * Arguments:
 *	howto		type of shutdown
 *	eup		pointer to device unit structure
 *
 * Returns:
 *	none
 *
 */
static void
eni_pci_shutdown ( eup, howto )
	void	*eup;
	int	howto;
{

	/* Do device reset */
	eni_pci_reset ( eup );

}
#endif	/* BSD < 199506 */
#endif	/* __FreeBSD__ */