text: /*	$OpenBSD: qec.c,v 1.19 2010/09/05 18:10:10 kettenis Exp $	*/
  
  /*
   * Copyright (c) 1998 Theo de Raadt and Jason L. Wright.
   * 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 AUTHORS ``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 AUTHORS 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.
   */
  
  #include <sys/types.h>
  #include <sys/param.h>
  #include <sys/systm.h>
  #include <sys/kernel.h>
  #include <sys/errno.h>
  #include <sys/ioctl.h>
  #include <sys/device.h>
  #include <sys/malloc.h>
  #include <sys/buf.h>
  #include <sys/proc.h>
  #include <sys/mbuf.h>
  #include <sys/socket.h>
  
  #include <net/if.h>
  #include <net/if_dl.h>
  #include <net/if_types.h>
  #include <net/netisr.h>
  #include <net/if_media.h>
  
  #ifdef INET
  #include <netinet/in.h>
  #include <netinet/in_systm.h>
  #include <netinet/in_var.h>
  #include <netinet/ip.h>
  #include <netinet/if_ether.h>
  #endif
  
  #include <sparc/autoconf.h>
  #include <sparc/cpu.h>
  
  #include <sparc/dev/sbusvar.h>
  #include <sparc/dev/dmareg.h>
  #include <sparc/dev/qecreg.h>
  #include <sparc/dev/qecvar.h>
  
  int	qecprint(void *, const char *);
  int	qecmatch(struct device *, void *, void *);
  void	qecattach(struct device *, struct device *, void *);
  void	qec_fix_range(struct qec_softc *, struct sbus_softc *);
  void	qec_translate(struct qec_softc *, struct confargs *);
  
  struct cfattach qec_ca = {
  	sizeof(struct qec_softc), qecmatch, qecattach
  };
  
  struct cfdriver qec_cd = {
  	NULL, "qec", DV_DULL
  };
  
  int
  qecprint(aux, name)
  	void *aux;
  	const char *name;
  {
  	register struct confargs *ca = aux;
  
  	if (name)
  		printf("%s at %s", ca->ca_ra.ra_name, name);
  	printf(" offset 0x%x", ca->ca_offset);
  	return (UNCONF);
  }
  
  /*
   * match a QEC device in a slot capable of DMA
   */
  int
  qecmatch(parent, vcf, aux)
  	struct device *parent;
  	void *vcf, *aux;
  {
  	struct cfdata *cf = vcf;
  	struct confargs *ca = aux;
  	struct romaux *ra = &ca->ca_ra;
  
  	if (strcmp(cf->cf_driver->cd_name, ra->ra_name))
  		return (0);
  
  	if (!sbus_testdma((struct sbus_softc *)parent, ca))
  		return (0);
  
  	return (1);
  }
  
  /*
   * Attach all the sub-devices we can find
   */
  void
  qecattach(parent, self, aux)
  	struct device *parent, *self;
  	void *aux;
  {
  	register struct confargs *ca = aux;
  	struct qec_softc *sc = (void *)self;
  	int node;
  	struct confargs oca;
  	char *name;
  	int sbusburst;
  
  	/*
  	 * The first i/o space is the qec global registers, and
  	 * the second is a buffer used by the qec channels internally.
  	 * (It's not necessary to map the second i/o space, but knowing
  	 * its size is necessary).
  	 */
  	sc->sc_regs = mapiodev(&ca->ca_ra.ra_reg[0], 0,
  	    sizeof(struct qecregs));
  	sc->sc_bufsiz = ca->ca_ra.ra_reg[1].rr_len;
  	sc->sc_paddr = ca->ca_ra.ra_reg[0].rr_paddr;
  
  	/*
  	 * On qec+qe, the qec has the interrupt priority, but we
  	 * need to pass that down so that the qe's can handle them.
  	 */
  	if (ca->ca_ra.ra_nintr == 1)
  		sc->sc_pri = ca->ca_ra.ra_intr[0].int_pri;
  
  	node = sc->sc_node = ca->ca_ra.ra_node;
  
  	qec_fix_range(sc, (struct sbus_softc *)parent);
  
  	/*
  	 * Get transfer burst size from PROM
  	 */
  	sbusburst = ((struct sbus_softc *)parent)->sc_burst;
  	if (sbusburst == 0)
  		sbusburst = SBUS_BURST_32 - 1; /* 1->16 */
  
  	sc->sc_nchannels = getpropint(ca->ca_ra.ra_node, "#channels", -1);
  	if (sc->sc_nchannels == -1) {
  		printf(": no channels\n");
  		return;
  	}
  	else if (sc->sc_nchannels < 1 || sc->sc_nchannels > 4) {
  		printf(": invalid number of channels: %d\n", sc->sc_nchannels);
  		return;
  	}
  
  	sc->sc_burst = getpropint(ca->ca_ra.ra_node, "burst-sizes", -1);
  	if (sc->sc_burst == -1)
  		/* take SBus burst sizes */
  		sc->sc_burst = sbusburst;
  
  	/* Clamp at parent's burst sizes */
  	sc->sc_burst &= sbusburst;
  
  	printf(": %dK memory %d channel%s",
  	    sc->sc_bufsiz / 1024, sc->sc_nchannels,
  	    (sc->sc_nchannels == 1) ? "" : "s");
  
  	node = sc->sc_node = ca->ca_ra.ra_node;
  
  	/* Propagate bootpath */
  	if (ca->ca_ra.ra_bp != NULL)
  		oca.ca_ra.ra_bp = ca->ca_ra.ra_bp + 1;
  	else
  		oca.ca_ra.ra_bp = NULL;
  
  	printf("\n");
  
  	qec_reset(sc);
  
  	/* search through children */
  	for (node = firstchild(node); node; node = nextsibling(node)) {
  		name = getpropstring(node, "name");
  		if (!romprop(&oca.ca_ra, name, node))
  			continue;
  
  		qec_translate(sc, &oca);
  		oca.ca_bustype = BUS_SBUS;
  		(void) config_found(&sc->sc_dev, (void *)&oca, qecprint);
  	}
  }
  
  void
  qec_fix_range(sc, sbp)
  	struct qec_softc *sc;
  	struct sbus_softc *sbp;
  {
  	int rlen, i, j;
  
  	rlen = getproplen(sc->sc_node, "ranges");
  	sc->sc_range =
  		(struct rom_range *)malloc(rlen, M_DEVBUF, M_NOWAIT);
  	if (sc->sc_range == NULL) {
  		printf("%s: PROM ranges too large: %d\n",
  				sc->sc_dev.dv_xname, rlen);
  		return;
  	}
  	sc->sc_nrange = rlen / sizeof(struct rom_range);
  	(void)getprop(sc->sc_node, "ranges", sc->sc_range, rlen);
  
  	for (i = 0; i < sc->sc_nrange; i++) {
  		for (j = 0; j < sbp->sc_nrange; j++) {
  			if (sc->sc_range[i].pspace == sbp->sc_range[j].cspace) {
  				sc->sc_range[i].poffset +=
  				    sbp->sc_range[j].poffset;
  				sc->sc_range[i].pspace =
  				    sbp->sc_range[j].pspace;
  				break;
  			}
  		}
  	}
  }
  
  /*
   * Translate the register addresses of our children 
   */
  void
  qec_translate(sc, ca)
  	struct qec_softc *sc;
  	struct confargs *ca;
  {
  	register int i;
  
  	ca->ca_slot = ca->ca_ra.ra_iospace;
  	ca->ca_offset = sc->sc_range[ca->ca_slot].poffset - (long)sc->sc_paddr;
  
  	/* Translate into parent address spaces */
  	for (i = 0; i < ca->ca_ra.ra_nreg; i++) {
  		int j, cspace = ca->ca_ra.ra_reg[i].rr_iospace;
  
  		for (j = 0; j < sc->sc_nrange; j++) {
  			if (sc->sc_range[j].cspace == cspace) {
  				ca->ca_ra.ra_reg[i].rr_paddr +=
  					sc->sc_range[j].poffset;
  				ca->ca_ra.ra_reg[i].rr_iospace =
  					sc->sc_range[j].pspace;
  				break;
  			}
  		}
  	}
  }
  
  /*
   * Reset the QEC and initialize its global registers.
   */
  void
  qec_reset(sc)
  	struct qec_softc *sc;
  {
  	struct qecregs *qr = sc->sc_regs;
  	int i = 200;
  
  	qr->ctrl = QEC_CTRL_RESET;
  	while (--i) {
  		if ((qr->ctrl & QEC_CTRL_RESET) == 0)
  			break;
  		DELAY(20);
  	}
  	if (i == 0) {
  		printf("%s: reset failed.\n", sc->sc_dev.dv_xname);
  		return;
  	}
  
  	qr->msize = sc->sc_bufsiz / sc->sc_nchannels;
  	sc->sc_msize = qr->msize;
  
  	qr->rsize = sc->sc_bufsiz / (sc->sc_nchannels * 2);
  	sc->sc_rsize = qr->rsize;
  
  	qr->tsize = sc->sc_bufsiz / (sc->sc_nchannels * 2);
  
  	qr->psize = QEC_PSIZE_2048;
  
          if (sc->sc_burst & SBUS_BURST_64)
  		i = QEC_CTRL_B64;
  	else if (sc->sc_burst & SBUS_BURST_32)
  		i = QEC_CTRL_B32;
  	else
  		i = QEC_CTRL_B16;
  
  	qr->ctrl = (qr->ctrl & QEC_CTRL_MODEMASK) | i;
  }
  
  /*
   * Routine to copy from mbuf chain to transmit buffer in
   * network buffer memory.
   */
  int
  qec_put(buf, m0)
  	u_int8_t *buf;
  	struct mbuf *m0;
  {
  	struct mbuf *m;
  	int len, tlen = 0;
  
  	for (m = m0; m != NULL; m = m->m_next) {
  		len = m->m_len;
  		bcopy(mtod(m, caddr_t), buf, len);
  		buf += len;
  		tlen += len;
  	}
  	m_freem(m0);
  	return (tlen);
  }
  
  /*
   * Pull data off an interface.
   * Len is the length of data, with local net header stripped.
   * We copy the data into mbufs.  When full cluster sized units are present,
   * we copy into clusters.
   */
  struct mbuf *
  qec_get(ifp, buf, totlen)
  	struct ifnet *ifp;
  	u_int8_t *buf;
  	int totlen;
  {
  	struct mbuf *m, *top, **mp;
  	int len, pad;
  
  	MGETHDR(m, M_DONTWAIT, MT_DATA);
  	if (m == NULL)
  		return (NULL);
  	m->m_pkthdr.rcvif = ifp;
  	m->m_pkthdr.len = totlen;
  	pad = ALIGN(sizeof(struct ether_header)) - sizeof(struct ether_header);
  	len = MHLEN;
  	if (totlen >= MINCLSIZE) {
  		MCLGET(m, M_DONTWAIT);
  		if (m->m_flags & M_EXT)
  			len = MCLBYTES;
  	}
  	m->m_data += pad;
  	len -= pad;
  	top = NULL;
  	mp = &top;
  
  	while (totlen > 0) {
  		if (top) {
  			MGET(m, M_DONTWAIT, MT_DATA);
  			if (m == NULL) {
  				m_freem(top);
  				return NULL;
  			}
  			len = MLEN;
  		}
  		if (top && totlen >= MINCLSIZE) {
  			MCLGET(m, M_DONTWAIT);
  			if (m->m_flags & M_EXT)
  				len = MCLBYTES;
  		}
  		m->m_len = len = min(totlen, len);
  		bcopy(buf, mtod(m, caddr_t), len);
  		buf += len;
  		totlen -= len;
  		*mp = m;
  		mp = &m->m_next;
  	}
  
  	return (top);
  }
  