/*	$OpenBSD: if_ie_gsc.c,v 1.26 2014/03/29 18:09:29 guenther 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.
 */

/*
 * References:
 * 1. 82596DX and 82596SX High-Performance 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/param.h>
#include <sys/systm.h>
#include <sys/device.h>
#include <sys/socket.h>
#include <sys/sockio.h>

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

#include <netinet/in.h>
#include <netinet/if_ether.h>

#include <machine/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 <dev/ic/i82596reg.h>
#include <dev/ic/i82596var.h>

#define	IEGSC_GECKO	IEMD_FLAG0

struct ie_gsc_regs {
	u_int32_t	ie_reset;
	u_int32_t	ie_port;
	u_int32_t	ie_attn;
};

#define	IE_SIZE	0x8000

int	ie_gsc_probe(struct device *, void *, void *);
void	ie_gsc_attach(struct device *, struct device *, void *);

struct cfattach ie_gsc_ca = {
	sizeof(struct ie_softc), ie_gsc_probe, ie_gsc_attach
};

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

char *ie_mem;

void ie_gsc_reset(struct ie_softc *sc, int what);
void ie_gsc_attend(struct ie_softc *sc);
void ie_gsc_run(struct ie_softc *sc);
void ie_gsc_port(struct ie_softc *sc, u_int);
#ifdef USELEDS
int ie_gsc_intrhook(struct ie_softc *sc, int what);
#endif
u_int16_t ie_gsc_read16(struct ie_softc *sc, int offset);
void ie_gsc_write16(struct ie_softc *sc, int offset, u_int16_t v);
void ie_gsc_write24(struct ie_softc *sc, int offset, int addr);
void ie_gsc_memcopyin(struct ie_softc *sc, void *p, int offset, size_t);
void ie_gsc_memcopyout(struct ie_softc *sc, const void *p, int, size_t);


void
ie_gsc_reset(sc, what)
	struct ie_softc *sc;
	int what;
{
	volatile struct ie_gsc_regs *r = (struct ie_gsc_regs *)sc->ioh;
	int i;

	r->ie_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);

	switch (what) {
	case IE_CHIP_PROBE:
		break;

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

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

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

void
ie_gsc_attend(sc)
	struct ie_softc *sc;
{
	volatile struct ie_gsc_regs *r = (struct ie_gsc_regs *)sc->ioh;

	fdcache(0, (vaddr_t)ie_mem, IE_SIZE);
	DELAY(1);
	r->ie_attn = 0;
	DELAY(1);
}

void
ie_gsc_run(sc)
	struct ie_softc *sc;
{
}

void
ie_gsc_port(sc, cmd)
	struct ie_softc *sc;
	u_int cmd;
{
	switch (cmd) {
	case IE_PORT_RESET:
		cmd = 0;
		break;
	case IE_PORT_TEST:
		cmd = ((u_int)sc->sc_maddr + sc->scp) | 1;
		break;
	case IE_PORT_SCP:
		cmd = ((u_int)sc->sc_maddr + sc->scp) | 2;
		break;
	case IE_PORT_DUMP:
		cmd = 3;
		break;
	}

	if (sc->sc_flags & IEGSC_GECKO) {
		volatile struct ie_gsc_regs *r = (struct ie_gsc_regs *)sc->ioh;
		r->ie_port = cmd & 0xffff;
		DELAY(1000);
		r->ie_port = cmd >> 16;
		DELAY(1000);
	} else {
		volatile struct ie_gsc_regs *r = (struct ie_gsc_regs *)sc->ioh;
		r->ie_port = cmd >> 16;
		DELAY(1000);
		r->ie_port = cmd & 0xffff;
		DELAY(1000);
	}
}

#ifdef USELEDS
int
ie_gsc_intrhook(sc, where)
	struct ie_softc *sc;
	int where;
{
	switch (where) {
	case IE_INTR_ENRCV:
		ledctl(PALED_NETRCV, 0, 0);
		break;
	case IE_INTR_ENSND:
		ledctl(PALED_NETSND, 0, 0);
		break;
	case IE_INTR_EXIT:
	case IE_INTR_LOOP:
		fdcache(0, (vaddr_t)ie_mem, IE_SIZE);
		break;
	}
	return 0;
}
#endif

u_int16_t
ie_gsc_read16(sc, offset)
	struct ie_softc *sc;
	int offset;
{
	volatile u_int16_t *addr = (volatile u_int16_t *)(sc->bh + offset);

	asm volatile ("fdc	%%r0(%%sr0, %0)" :: "r" (addr));
	return *addr;
}

void
ie_gsc_write16(sc, offset, v)
	struct ie_softc *sc;
	int offset;
	u_int16_t v;
{
	volatile u_int16_t *addr = (volatile u_int16_t *)(sc->bh + offset);

	*addr = v;
	asm volatile ("fdc	%%r0(%%sr0, %0)" :: "r" (addr));
}

void
ie_gsc_write24(sc, offset, v)
	struct ie_softc *sc;
	int offset;
	int v;
{
	volatile u_int16_t *addr = (volatile u_int16_t *)(sc->bh + offset);

	addr[0] = (v      ) & 0xffff;
	addr[1] = (v >> 16) & 0xffff;
	asm volatile ("fdc	%%r0(%%sr0, %0)" :: "r" (addr+0));
	asm volatile ("fdc	%%r0(%%sr0, %0)" :: "r" (addr+1));
}

void
ie_gsc_memcopyin(sc, p, offset, size)
	struct ie_softc	*sc;
	void *p;
	int offset;
	size_t size;
{
	pdcache(0, sc->bh + offset, size);
	bcopy ((void *)((u_long)sc->bh + offset), p, size);
}

void
ie_gsc_memcopyout(sc, p, offset, size)
	struct ie_softc	*sc;
	const void *p;
	int offset;
	size_t size;
{
	bcopy (p, (void *)((u_long)sc->bh + offset), size);
	fdcache(0, sc->bh + offset, size);
}

int
ie_gsc_probe(parent, match, aux)
	struct device *parent;
	void *match, *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(parent, self, aux)
	struct device *parent, *self;
	void *aux;
{
	struct pdc_lan_station_id pdc_mac PDC_ALIGNMENT;
	struct ie_softc *sc = (struct ie_softc *)self;
	struct gsc_attach_args *ga = aux;
	/*bus_dma_segment_t seg;
	int rseg;*/
	int rv;
#ifdef PMAPDEBUG
	extern int pmapdebug;
	int opmapdebug = pmapdebug;
	pmapdebug = 0;
#endif

	sc->iot = sc->bt = ga->ga_iot;
	if (bus_space_map(sc->iot, ga->ga_hpa, IOMOD_HPASIZE, 0, &sc->ioh)) {
		printf(": can't map IO space\n");
		return;
	}

	if (ga->ga_type.iodc_sv_model == HPPA_FIO_GLAN)
		sc->sc_flags |= IEGSC_GECKO;

	sc->sc_msize = IE_SIZE;
	/* XXX memory must be under 16M until the mi part is fixed */
#if 0
	if (bus_dmamem_alloc(ga->ga_dmatag, sc->sc_msize, NBPG, 0,
			     &seg, 1, &rseg, BUS_DMA_NOWAIT)) {
		printf (": cannot allocate %d bytes of DMA memory\n",
			sc->sc_msize);
		return;
	}
	if (bus_dmamem_map(ga->ga_dmatag, &seg, rseg, sc->sc_msize,
			   (caddr_t *)&sc->bh, BUS_DMA_NOWAIT)) {
		printf (": cannot map DMA memory\n");
		bus_dmamem_free(ga->ga_dmatag, &seg, rseg);
		return;
	}

	bzero((void *)sc->bh, sc->sc_msize);
	sc->sc_maddr = kvtop((caddr_t)sc->bh);

#else
	sc->bh = (u_int)ie_mem;
	sc->sc_maddr = sc->bh;
#endif
	sc->sysbus = 0x40 | IE_SYSBUS_82586 | IE_SYSBUS_INTLOW | IE_SYSBUS_TRG | IE_SYSBUS_BE;

	sc->do_xmitnopchain = 0;
	sc->hwreset = ie_gsc_reset;
	sc->chan_attn = ie_gsc_attend;
	sc->port = ie_gsc_port;
	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;
#ifdef USELEDS
	sc->intrhook = ie_gsc_intrhook;
#else
	sc->intrhook = NULL;
#endif

#ifdef I82596_DEBUG
	printf(" mem %x[%p]/%x", sc->bh, sc->sc_maddr, sc->sc_msize);
	sc->sc_debug = IED_ALL;
#endif
	rv = i82596_probe(sc);
	if (!rv) {
		/*bus_dmamem_free(ga->ga_dmatag, &seg, sc->sc_msize);*/
	}
#ifdef PMAPDEBUG
	pmapdebug = opmapdebug;
#endif
	if (!rv) {
		printf("\n");
		return;
	}

	if (pdc_call((iodcio_t)pdc, 0, PDC_LAN_STATION_ID,
		     PDC_LAN_STATION_ID_READ, &pdc_mac, ga->ga_hpa) < 0)
		bcopy((void *)ASP_PROM, sc->sc_arpcom.ac_enaddr,
		      ETHER_ADDR_LEN);
	else
		bcopy(pdc_mac.addr, sc->sc_arpcom.ac_enaddr, ETHER_ADDR_LEN);

	printf(":");

	sc->iscp = 0;
	sc->scp = 32;
	sc->scb = 94;
	sc->buf_area = 256;
	sc->buf_area_sz = sc->sc_msize - sc->buf_area;
	sc->sc_type = sc->sc_flags & IEGSC_GECKO? "LASI/i82596CA" : "i82596DX";
	sc->sc_vers = ga->ga_type.iodc_model * 10 + ga->ga_type.iodc_sv_rev;
	i82596_attach(sc, sc->sc_type, (char *)sc->sc_arpcom.ac_enaddr,
		      ie_gsc_media, IE_NMEDIA, ie_gsc_media[0]);

	sc->sc_ih = gsc_intr_establish((struct gsc_softc *)parent,
	    ga->ga_irq, IPL_NET, i82596_intr, sc, sc->sc_dev.dv_xname);
}
