/*	$NetBSD: if_ie_gsc.c,v 1.1 2014/02/24 07:23:43 skrll Exp $	*/

/*	$OpenBSD: if_ie_gsc.c,v 1.6 2001/01/12 22:57:04 mickey Exp $	*/

/*
 * Copyright (c) 1998-2004 Michael Shalayeff
 * 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 ``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 HIS RELATIVES 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 MIND, 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.
 */

/*
 * Referencies:
 * 1. 82596DX and 82596SX High-Perfomance 32-bit Local Area Network Coprocessor
 *    Intel Corporation, November 1996, Order Number: 290219-006
 *
 * 2. 712 I/O Subsystem ERS Rev 1.0
 *    Hewlett-Packard, June 17 1992, Dwg No. A-A2263-66510-31
 */

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: if_ie_gsc.c,v 1.1 2014/02/24 07:23:43 skrll Exp $");

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/device.h>
#include <sys/socket.h>
#include <sys/sockio.h>

#include <uvm/uvm_extern.h>

#include <net/if.h>
#include <net/if_dl.h>
#include <net/if_ether.h>
#include <net/if_types.h>
#include <net/if_media.h>

#include <netinet/in.h>

#include <sys/bus.h>
#include <machine/intr.h>
#include <machine/iomod.h>
#include <machine/autoconf.h>

#include <hppa/dev/cpudevs.h>
#include <hppa/gsc/gscbusvar.h>
#include <hppa/hppa/machdep.h>

#include <dev/ic/i82586reg.h>
#include <dev/ic/i82586var.h>

#define	I82596_DEBUG	I82586_DEBUG

#ifdef __for_reference_only
struct ie_gsc_regs {
	uint32_t	ie_reset;
	uint32_t	ie_port;
	uint32_t	ie_attn;
};
#endif

#define	IE_GSC_BANK_SZ		(12)
#define	IE_GSC_REG_RESET	(0)
#define	IE_GSC_REG_PORT		(4)
#define	IE_GSC_REG_ATTN		(8)

#define	IE_GSC_ALIGN(v)	((((u_int) (v)) + 0xf) & ~0xf)

#define	IE_GSC_SYSBUS	(IE_SYSBUS_596_RSVD_SET	| \
			 IE_SYSBUS_596_82586	| \
			 IE_SYSBUS_596_INTLOW	| \
			 IE_SYSBUS_596_TRGEXT	| \
			 IE_SYSBUS_596_BE)

#define	IE_SIZE	0x8000

struct ie_gsc_softc {
	struct ie_softc ie;
	
	/* tag and handle to hppa-specific adapter registers. */
	bus_space_tag_t iot;
	bus_space_handle_t ioh;

	/* bus_dma_tag_t for the memory used by the adapter. */
	bus_dma_tag_t iemt;

	/* interrupt handle. */
	void *sc_ih;

	/* miscellaneous flags. */
	int flags;
#define	IEGSC_GECKO	(1 << 0)
};

int	ie_gsc_probe(device_t, cfdata_t, void *);
void	ie_gsc_attach(device_t, device_t, void *);

CFATTACH_DECL_NEW(ie_gsc, sizeof(struct ie_gsc_softc),
    ie_gsc_probe, ie_gsc_attach, NULL, NULL);

static int ie_gsc_media[] = {
	IFM_ETHER | IFM_10_2,
};
#define	IE_NMEDIA	(sizeof(ie_gsc_media) / sizeof(ie_gsc_media[0]))

void ie_gsc_reset(struct ie_softc *, int);
void ie_gsc_attend(struct ie_softc *, int);
void ie_gsc_run(struct ie_softc *);
void ie_gsc_port(struct ie_softc *, u_int);
uint16_t ie_gsc_read16(struct ie_softc *, int);
void ie_gsc_write16(struct ie_softc *, int, uint16_t);
void ie_gsc_write24(struct ie_softc *, int, int);
void ie_gsc_memcopyin(struct ie_softc *, void *, int, size_t);
void ie_gsc_memcopyout(struct ie_softc *, const void *, int, size_t);


/* Reset the adapter. */
void
ie_gsc_reset(struct ie_softc *sc, int what)
{
	struct ie_gsc_softc *gsc = (struct ie_gsc_softc *) sc;
	int i;
	
	switch (what) {
	case CHIP_PROBE:
		bus_space_write_4(gsc->iot, gsc->ioh, IE_GSC_REG_RESET, 0);
		break;

	case CARD_RESET:
		bus_space_write_4(gsc->iot, gsc->ioh, IE_GSC_REG_RESET, 0);

		/*
		 * per [2] 4.6.2.1
		 * delay for 10 system clocks + 5 transmit clocks,
		 * NB: works for system clocks over 10MHz
		 */
		DELAY(1000);

		/*
		 * after the hardware reset:
		 * inform i825[89]6 about new SCP address,
		 * which must be at least 16-byte aligned
		 */
		ie_gsc_port(sc, IE_PORT_ALT_SCP);
		ie_gsc_attend(sc, what);

		for (i = 9000; i-- && ie_gsc_read16(sc, IE_ISCP_BUSY(sc->iscp));
		     DELAY(100))
			pdcache(0, (vaddr_t)sc->sc_maddr + sc->iscp, IE_ISCP_SZ);

#if I82596_DEBUG
		if (i < 0) {
			printf("timeout for PORT command (%x)%s\n",
			       ie_gsc_read16(sc, IE_ISCP_BUSY(sc->iscp)),
			       (gsc->flags & IEGSC_GECKO)? " on gecko":"");
			return;
		}
#endif
		break;
	}
}

/* Do a channel attention on the adapter. */
void
ie_gsc_attend(struct ie_softc *sc, int why)
{
	struct ie_gsc_softc *gsc = (struct ie_gsc_softc *) sc;

	bus_space_write_4(gsc->iot, gsc->ioh, IE_GSC_REG_ATTN, 0);
}

/* Enable the adapter. */
void
ie_gsc_run(struct ie_softc *sc)
{
}

/* Run an i82596 PORT command on the adapter. */
void
ie_gsc_port(struct ie_softc *sc, u_int cmd)
{
	struct ie_gsc_softc *gsc = (struct ie_gsc_softc *) sc;

	switch (cmd) {
	case IE_PORT_RESET:
	case IE_PORT_DUMP:
		break;
	case IE_PORT_SELF_TEST:
		cmd |= (sc->sc_dmamap->dm_segs[0].ds_addr + 0);
		break;
	case IE_PORT_ALT_SCP:
		cmd |= (sc->sc_dmamap->dm_segs[0].ds_addr + sc->scp);
		break;
	}

	if (gsc->flags & IEGSC_GECKO) {
		bus_space_write_4(gsc->iot, gsc->ioh,
				  IE_GSC_REG_PORT, (cmd & 0xffff));
		DELAY(1000);
		bus_space_write_4(gsc->iot, gsc->ioh,
				  IE_GSC_REG_PORT, (cmd >> 16));
		DELAY(1000);
	} else {
		bus_space_write_4(gsc->iot, gsc->ioh,
				  IE_GSC_REG_PORT, (cmd >> 16));
		DELAY(1000);
		bus_space_write_4(gsc->iot, gsc->ioh,
				  IE_GSC_REG_PORT, (cmd & 0xffff));
		DELAY(1000);
	}
}

uint16_t
ie_gsc_read16(struct ie_softc *sc, int offset)
{
	uint16_t val;

	__asm volatile(
	"	ldh	0(%1), %0	\n"
	"	fdc	%%r0(%1)	\n"
	: "=&r" (val)
	: "r" ((char *)sc->sc_maddr + offset));
	return (val);
}

void
ie_gsc_write16(struct ie_softc *sc, int offset, uint16_t v)
{

	__asm volatile(
	"	sth	%0, 0(%1)	\n"
	"	fdc	%%r0(%1)	\n"
	: /* no outputs */
	: "r" (v), "r" ((char *)sc->sc_maddr + offset));
}

void
ie_gsc_write24(struct ie_softc *sc, int offset, int addr)
{

	/*
	 * i82586.c assumes that the chip address space starts at
	 * zero, so we have to add in the appropriate offset here.
	 */
	addr += sc->sc_dmamap->dm_segs[0].ds_addr;
	__asm volatile(
	"	ldi	2, %%r21		\n"
	"	extru	%0, 15, 16, %%r22	\n"
	"	sth	%0, 0(%1)		\n"
	"	sth	%%r22, 2(%1)		\n"
	"	fdc	%%r0(%1)		\n"
	"	fdc	%%r21(%1)		\n"
	: /* No outputs */
	: "r" (addr), "r" ((char *)sc->sc_maddr + offset)
	: "r21", "r22");
}

void
ie_gsc_memcopyin(struct ie_softc *sc, void *p, int offset, size_t size)
{
	struct ie_gsc_softc *gsc = (struct ie_gsc_softc *) sc;

	if (size == 0)
		return;

	memcpy(p, (char *)sc->sc_maddr + offset, size);
	bus_dmamap_sync(gsc->iemt, sc->sc_dmamap, offset, size,
			BUS_DMASYNC_PREREAD);
	hppa_led_blink(HPPA_LED_NETRCV);
}

void
ie_gsc_memcopyout(struct ie_softc *sc, const void *p, int offset, size_t size)
{
	struct ie_gsc_softc *gsc = (struct ie_gsc_softc *) sc;

	if (size == 0)
		return;

	memcpy((char *)sc->sc_maddr + offset, p, size);
	bus_dmamap_sync(gsc->iemt, sc->sc_dmamap, offset, size,
			BUS_DMASYNC_PREWRITE | BUS_DMASYNC_PREREAD);
	hppa_led_blink(HPPA_LED_NETSND);
}

/*
 * i82596 probe routine
 */
int i82596_probe(struct ie_softc *);
int
i82596_probe(struct ie_softc *sc)
{
	struct ie_gsc_softc *gsc = (struct ie_gsc_softc *) sc;
	int i;

	/* Set up the SCP. */
	sc->ie_bus_write16(sc, IE_SCP_BUS_USE(sc->scp), IE_GSC_SYSBUS);
	sc->ie_bus_write24(sc, IE_SCP_ISCP(sc->scp), sc->iscp);

	/* Set up the ISCP. */
	sc->ie_bus_write16(sc, IE_ISCP_SCB(sc->iscp), sc->scb);
	sc->ie_bus_write24(sc, IE_ISCP_BASE(sc->iscp), 0);

	/* Set BUSY in the ISCP. */
	sc->ie_bus_write16(sc, IE_ISCP_BUSY(sc->iscp), 1);

	/* Reset the adapter. */
	bus_dmamap_sync(gsc->iemt, sc->sc_dmamap, 0, sc->sc_msize,
			BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE);
	sc->hwreset(sc, CARD_RESET);

	/* Make sure that BUSY got cleared. */
	if (sc->ie_bus_read16(sc, IE_ISCP_BUSY(sc->iscp))) {
#if I82596_DEBUG
		printf ("%s: ISCP set failed\n", device_xname(sc->sc_dev));
#endif
		return 0;
	}

	/* Run the chip self-test. */
	sc->ie_bus_write24(sc, 0, -sc->sc_dmamap->dm_segs[0].ds_addr);
	sc->ie_bus_write24(sc, 4, -(sc->sc_dmamap->dm_segs[0].ds_addr + 1));
	bus_dmamap_sync(gsc->iemt, sc->sc_dmamap, 0, sc->sc_msize,
			BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE);
	ie_gsc_port(sc, IE_PORT_SELF_TEST);
	for (i = 9000; i-- &&
		     sc->ie_bus_read16(sc, 4);
	     DELAY(100))
		pdcache(0, (vaddr_t)sc->sc_maddr, sc->sc_msize);

#if I82596_DEBUG
	printf (": test %x:%x\n%s",
		*((volatile int32_t *)((char *)sc->sc_maddr + 0)),
		*((volatile int32_t *)((char *)sc->sc_maddr + 4)),
		device_xname(sc->sc_dev));
#endif
	return 1;
}

int
ie_gsc_probe(device_t parent, cfdata_t match, void *aux)
{
	struct gsc_attach_args *ga = aux;

	if (ga->ga_type.iodc_type != HPPA_TYPE_FIO ||
	    (ga->ga_type.iodc_sv_model != HPPA_FIO_LAN &&
	     ga->ga_type.iodc_sv_model != HPPA_FIO_GLAN))
		return 0;

	return 1;
}

void
ie_gsc_attach(device_t parent, device_t self, void *aux)
{
	struct ie_gsc_softc *gsc = device_private(self);
	struct ie_softc *sc = &gsc->ie;
	struct gsc_attach_args *ga = aux;
	bus_dma_segment_t seg;
	int rseg;
	int rv;
	uint8_t myaddr[ETHER_ADDR_LEN];
#ifdef PMAPDEBUG
	extern int pmapdebug;
	int opmapdebug = pmapdebug;
	pmapdebug = 0;
#endif

	if (ga->ga_type.iodc_sv_model == HPPA_FIO_GLAN)
		gsc->flags |= IEGSC_GECKO;

	/*
	 * Map the GSC registers.
	 */
	if (bus_space_map(ga->ga_iot, ga->ga_hpa,
			  IE_GSC_BANK_SZ, 0, &gsc->ioh)) {
		printf(": can't map i/o space\n");
		return;
	}

	/* Set up some initial glue. */
	sc->sc_dev = self;
	gsc->iot = ga->ga_iot;
	gsc->iemt = ga->ga_dmatag;
	sc->bt = ga->ga_iot;
	sc->sc_msize = IE_SIZE;

	/*
	 * Allocate one contiguous segment of physical memory
	 * to be used with the i82596.  Since we're running the
	 * chip in i82586 mode, we're restricted to 24-bit
	 * physical addresses.
	 */
	if (bus_dmamem_alloc(gsc->iemt, sc->sc_msize, PAGE_SIZE, 0,
	    &seg, 1, &rseg, BUS_DMA_NOWAIT | BUS_DMA_24BIT)) {
		printf (": can't allocate %d bytes of DMA memory\n",
			sc->sc_msize);
		return;
	}

	/*
	 * Map that physical memory into kernel virtual space.
	 */
	if (bus_dmamem_map(gsc->iemt, &seg, rseg, sc->sc_msize,
			   (void **)&sc->sc_maddr, BUS_DMA_NOWAIT)) {
		printf (": can't map DMA memory\n");
		bus_dmamem_free(gsc->iemt, &seg, rseg);
		return;
	}

	/*
	 * Create a DMA map for the memory.
	 */
	if (bus_dmamap_create(gsc->iemt, sc->sc_msize, rseg, sc->sc_msize,
			      0, BUS_DMA_NOWAIT, &sc->sc_dmamap)) {
		printf(": can't create DMA map\n");
		bus_dmamem_unmap(gsc->iemt,
				 (void *)sc->sc_maddr, sc->sc_msize);
		bus_dmamem_free(gsc->iemt, &seg, rseg);
		return;
	}

	/*
	 * Load the mapped DMA memory into the DMA map.
	 */
	if (bus_dmamap_load(gsc->iemt, sc->sc_dmamap,
			    sc->sc_maddr, sc->sc_msize,
			    NULL, BUS_DMA_NOWAIT)) {
		printf(": can't load DMA map\n");
		bus_dmamap_destroy(gsc->iemt, sc->sc_dmamap);
		bus_dmamem_unmap(gsc->iemt,
				 (void *)sc->sc_maddr, sc->sc_msize);
		bus_dmamem_free(gsc->iemt, &seg, rseg);
		return;
	}

#if 1
	/* XXX - this should go away. */
	sc->bh = (bus_space_handle_t) sc->sc_maddr;
#endif

#if I82596_DEBUG
	printf(" mem %x[%p]/%x\n%s",
		(u_int)sc->sc_dmamap->dm_segs[0].ds_addr,
		sc->sc_maddr,
		sc->sc_msize,
		device_xname(self));
	sc->sc_debug = IED_ALL;
#endif

	/* Initialize our bus glue. */
	sc->hwreset = ie_gsc_reset;
	sc->chan_attn = ie_gsc_attend;
	sc->hwinit = ie_gsc_run;
	sc->memcopyout = ie_gsc_memcopyout;
	sc->memcopyin = ie_gsc_memcopyin;
	sc->ie_bus_read16 = ie_gsc_read16;
	sc->ie_bus_write16 = ie_gsc_write16;
	sc->ie_bus_write24 = ie_gsc_write24;
	sc->intrhook = NULL;

	/* Clear all RAM. */
	memset(sc->sc_maddr, 0, sc->sc_msize);
	bus_dmamap_sync(gsc->iemt, sc->sc_dmamap, 0, sc->sc_msize,
			BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE);

	/*
	 * We use low memory to set up SCP, ICSP and SCB data
	 * structures. The remaining pages become the buffer area
	 * (managed in i82586.c).
	 */

	/*
	 * Since we have an i82596, we can control where where
	 * the chip looks for SCP.  We plan to use the first
	 * two 32-bit words of memory for the self-test, so the
	 * SCP can go after that.
	 */
	sc->scp = IE_GSC_ALIGN(8);
	
	/* ISCP follows SCP */
	sc->iscp = IE_GSC_ALIGN(sc->scp + IE_SCP_SZ);

	/* SCB follows ISCP */
	sc->scb = IE_GSC_ALIGN(sc->iscp + IE_ISCP_SZ);

	/* The remainder of the memory is for buffers. */
	sc->buf_area = IE_GSC_ALIGN(sc->scb + IE_SCB_SZ);
	sc->buf_area_sz = sc->sc_msize - sc->buf_area;

	/* Finally, we can probe the chip. */
	rv = i82596_probe(sc);
	if (!rv) {
		bus_dmamap_destroy(gsc->iemt, sc->sc_dmamap);
		bus_dmamem_unmap(gsc->iemt,
				 (void *)sc->sc_maddr, sc->sc_msize);
		bus_dmamem_free(gsc->iemt, &seg, rseg);
		return;
	}
#ifdef PMAPDEBUG
	pmapdebug = opmapdebug;
#endif
	if (!rv)
		return;

	/* Get our Ethernet address. */
	memcpy(myaddr, ga->ga_ether_address, ETHER_ADDR_LEN);

	/* Set up the SCP. */
	sc->ie_bus_write16(sc, IE_SCP_BUS_USE(sc->scp), IE_GSC_SYSBUS);
	sc->ie_bus_write24(sc, IE_SCP_ISCP(sc->scp), sc->iscp);

	/* Set up the ISCP. */
	sc->ie_bus_write16(sc, IE_ISCP_SCB(sc->iscp), sc->scb);
	sc->ie_bus_write24(sc, IE_ISCP_BASE(sc->iscp), 0);

	/* Set BUSY in the ISCP. */
	sc->ie_bus_write16(sc, IE_ISCP_BUSY(sc->iscp), 1);

	/* Reset the adapter. */
	bus_dmamap_sync(gsc->iemt, sc->sc_dmamap, 0, sc->sc_msize,
			BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE);
	sc->hwreset(sc, CARD_RESET);
	bus_dmamap_sync(gsc->iemt, sc->sc_dmamap, 0, sc->sc_msize,
			BUS_DMASYNC_PREREAD);

	/* Now call the MI attachment. */
	printf(": v%d.%d", ga->ga_type.iodc_model, ga->ga_type.iodc_sv_rev);
	i82586_attach(sc,
		      (gsc->flags & IEGSC_GECKO) ?
		      "LASI/i82596CA" :
		      "i82596DX",
		      myaddr, ie_gsc_media, IE_NMEDIA, ie_gsc_media[0]);
	gsc->sc_ih = hppa_intr_establish(IPL_NET, i82586_intr, sc,
	    ga->ga_ir, ga->ga_irq);
}
