Module Name:    src
Committed By:   nisimura
Date:           Sat Jan 28 08:29:55 UTC 2012

Modified Files:
        src/sys/dev/ic: dm9000.c dm9000reg.h dm9000var.h

Log Message:
- add DM9000 internal PHY manipulating routines.
- now capable of auto-negotiation, use this as default media selection.
- add multicast filter support.
- code cleanup.

Code update from Paul Fleischer.


To generate a diff of this commit:
cvs rdiff -u -r1.3 -r1.4 src/sys/dev/ic/dm9000.c
cvs rdiff -u -r1.1 -r1.2 src/sys/dev/ic/dm9000reg.h \
    src/sys/dev/ic/dm9000var.h

Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.

Modified files:

Index: src/sys/dev/ic/dm9000.c
diff -u src/sys/dev/ic/dm9000.c:1.3 src/sys/dev/ic/dm9000.c:1.4
--- src/sys/dev/ic/dm9000.c:1.3	Thu Jun 30 20:09:39 2011
+++ src/sys/dev/ic/dm9000.c	Sat Jan 28 08:29:55 2012
@@ -1,4 +1,4 @@
-/*	$NetBSD: dm9000.c,v 1.3 2011/06/30 20:09:39 wiz Exp $	*/
+/*	$NetBSD: dm9000.c,v 1.4 2012/01/28 08:29:55 nisimura Exp $	*/
 
 /*
  * Copyright (c) 2009 Paul Fleischer
@@ -89,6 +89,7 @@
 #include <sys/cdefs.h>
 
 #include <sys/param.h>
+#include <sys/kernel.h>
 #include <sys/systm.h>
 #include <sys/mbuf.h>
 #include <sys/syslog.h>
@@ -117,7 +118,7 @@
 
 #if 1
 #undef DM9000_DEBUG
-#undef  DM9000_TX_DEBUG
+#undef DM9000_TX_DEBUG
 #undef DM9000_TX_DATA_DEBUG
 #undef DM9000_RX_DEBUG
 #undef  DM9000_RX_DATA_DEBUG
@@ -159,9 +160,13 @@
 #define TX_DATA_DPRINTF(s) do {} while (/*CONSTCOND*/0)
 #endif
 
-
+/*** Internal PHY functions ***/
 uint16_t dme_phy_read(struct dme_softc *sc, int reg);
-void dme_phy_write(struct dme_softc *sc, int reg, uint16_t value);
+void	dme_phy_write(struct dme_softc *sc, int reg, uint16_t value);
+void	dme_phy_init(struct dme_softc *sc);
+void	dme_phy_reset(struct dme_softc *sc);
+void	dme_phy_update_media(struct dme_softc *sc);
+void	dme_phy_check_link(void *arg);
 
 /*** Methods registered in struct ifnet ***/
 void	dme_start_output(struct ifnet *ifp);
@@ -186,6 +191,17 @@ void    dme_receive(struct dme_softc *sc
 /* Software Initialize/Reset of the DM9000 */
 void    dme_reset(struct dme_softc *sc);
 
+/* Configure multicast filter */
+void	dme_set_addr_filter(struct dme_softc *sc);
+
+/* Set media */
+int	dme_set_media(struct dme_softc *sc, int media);
+
+/* Read/write packet data from/to DM9000 IC in various transfer sizes */
+int	dme_pkt_read_2(struct dme_softc *sc, struct ifnet *ifp, struct mbuf **outBuf);
+int	dme_pkt_write_2(struct dme_softc *sc, struct mbuf *bufChain);
+/* TODO: Implement 8 and 32 bit read/write functions */
+
 uint16_t
 dme_phy_read(struct dme_softc *sc, int reg)
 {
@@ -199,9 +215,6 @@ dme_phy_read(struct dme_softc *sc, int r
 	/* Wait until access to PHY has completed */
 	while (dme_read(sc, DM9000_EPCR) & DM9000_EPCR_ERRE);
 
-	/* XXX: The delay is probably not necessary as we just busy-waited */
-	delay(200);
-
 	/* Reset ERPRR-bit */
 	dme_write(sc, DM9000_EPCR, DM9000_EPCR_EPOS_PHY);
 
@@ -228,19 +241,172 @@ dme_phy_write(struct dme_softc *sc, int 
 	/* Wait until access to PHY has completed */
 	while(dme_read(sc, DM9000_EPCR) & DM9000_EPCR_ERRE);
 
-
-	/* XXX: The delay is probably not necessary as we just busy-waited */
-	delay(200);
-
 	/* Reset ERPRR-bit */
 	dme_write(sc, DM9000_EPCR, DM9000_EPCR_EPOS_PHY);
 }
 
+void
+dme_phy_init(struct dme_softc *sc)
+{
+	u_int ifm_media = sc->sc_media.ifm_media;
+	uint32_t bmcr, anar;
+
+	bmcr = dme_phy_read(sc, DM9000_PHY_BMCR);
+	anar = dme_phy_read(sc, DM9000_PHY_ANAR);
+
+	anar = anar & ~DM9000_PHY_ANAR_10_HDX
+		& ~DM9000_PHY_ANAR_10_FDX
+		& ~DM9000_PHY_ANAR_TX_HDX
+		& ~DM9000_PHY_ANAR_TX_FDX;
+
+	switch (IFM_SUBTYPE(ifm_media)) {
+	case IFM_AUTO:
+		bmcr |= DM9000_PHY_BMCR_AUTO_NEG_EN;
+		anar |= DM9000_PHY_ANAR_10_HDX |
+			DM9000_PHY_ANAR_10_FDX |
+			DM9000_PHY_ANAR_TX_HDX |
+			DM9000_PHY_ANAR_TX_FDX;
+		break;
+	case IFM_10_T:
+		//bmcr &= ~DM9000_PHY_BMCR_AUTO_NEG_EN;
+		bmcr &= ~DM9000_PHY_BMCR_SPEED_SELECT;
+		if (ifm_media & IFM_FDX)
+			anar |= DM9000_PHY_ANAR_10_FDX;
+		else
+			anar |= DM9000_PHY_ANAR_10_HDX;
+		break;
+	case IFM_100_TX:
+		//bmcr &= ~DM9000_PHY_BMCR_AUTO_NEG_EN;
+		bmcr |= DM9000_PHY_BMCR_SPEED_SELECT;
+		if (ifm_media & IFM_FDX)
+			anar |= DM9000_PHY_ANAR_TX_FDX;
+		else
+			anar |= DM9000_PHY_ANAR_TX_HDX;
+
+		break;
+	}
+
+	if(ifm_media & IFM_FDX) {
+		bmcr |= DM9000_PHY_BMCR_DUPLEX_MODE;
+	} else {
+		bmcr &= ~DM9000_PHY_BMCR_DUPLEX_MODE;
+	}
+
+	dme_phy_write(sc, DM9000_PHY_BMCR, bmcr);
+	dme_phy_write(sc, DM9000_PHY_ANAR, anar);
+}
+
+void
+dme_phy_reset(struct dme_softc *sc)
+{
+	uint32_t reg;
+
+	/* PHY Reset */
+	dme_phy_write(sc, DM9000_PHY_BMCR, DM9000_PHY_BMCR_RESET);
+
+	reg = dme_read(sc, DM9000_GPCR);
+	dme_write(sc, DM9000_GPCR, reg & ~DM9000_GPCR_GPIO0_OUT);
+	reg = dme_read(sc, DM9000_GPR);
+	dme_write(sc, DM9000_GPR, reg | DM9000_GPR_PHY_PWROFF);
+
+	dme_phy_init(sc);
+
+	reg = dme_read(sc, DM9000_GPR);
+	dme_write(sc, DM9000_GPR, reg & ~DM9000_GPR_PHY_PWROFF);
+	reg = dme_read(sc, DM9000_GPCR);
+	dme_write(sc, DM9000_GPCR, reg | DM9000_GPCR_GPIO0_OUT);
+
+	dme_phy_update_media(sc);
+}
+
+void
+dme_phy_update_media(struct dme_softc *sc)
+{
+	u_int ifm_media = sc->sc_media.ifm_media;
+	uint32_t reg;
+
+	if (IFM_SUBTYPE(ifm_media) == IFM_AUTO) {
+		/* If auto-negotiation is used, ensures that it is completed
+		 before trying to extract any media information. */
+		reg = dme_phy_read(sc, DM9000_PHY_BMSR);
+		if ((reg & DM9000_PHY_BMSR_AUTO_NEG_AB) == 0) {
+			/* Auto-negotation not possible, therefore there is no
+			   reason to try obtain any media information. */
+			return;
+		}
+
+		/* Then loop until the negotiation is completed. */
+		while ((reg & DM9000_PHY_BMSR_AUTO_NEG_COM) == 0) {
+			/* TODO: Bail out after a finite number of attempts
+			 in case something goes wrong. */
+			preempt();
+			reg = dme_phy_read(sc, DM9000_PHY_BMSR);
+		}
+	}
+
+
+	sc->sc_media_active = IFM_ETHER;
+	reg = dme_phy_read(sc, DM9000_PHY_BMCR);
+
+	if (reg & DM9000_PHY_BMCR_SPEED_SELECT) {
+		sc->sc_media_active |= IFM_100_TX;
+	} else {
+		sc->sc_media_active |= IFM_10_T;
+	}
+
+	if (reg & DM9000_PHY_BMCR_DUPLEX_MODE) {
+		sc->sc_media_active |= IFM_FDX;
+	}
+}
+
+void
+dme_phy_check_link(void *arg)
+{
+	struct dme_softc *sc = arg;
+	uint32_t reg;
+	int s;
+
+	s = splnet();
+
+	reg = dme_read(sc, DM9000_NSR) & DM9000_NSR_LINKST;
+
+	if( reg )
+		reg = IFM_ETHER | IFM_AVALID | IFM_ACTIVE;
+	else {
+		reg = IFM_ETHER | IFM_AVALID;
+		sc->sc_media_active = IFM_NONE;
+	}
+
+	if ( (sc->sc_media_status != reg) && (reg & IFM_ACTIVE)) {
+		dme_phy_reset(sc);
+	}
+
+	sc->sc_media_status = reg;
+
+	callout_schedule(&sc->sc_link_callout, mstohz(2000));
+	splx(s);
+}
+
 int
-dme_attach(struct dme_softc *sc, uint8_t *enaddr)
+dme_set_media(struct dme_softc *sc, int media)
 {
-	struct ifnet *ifp = &sc->sc_ethercom.ec_if;
-	uint8_t b[2];
+	int s;
+
+	s = splnet();
+	sc->sc_media.ifm_media = media;
+	dme_phy_reset(sc);
+
+	splx(s);
+
+	return 0;
+}
+
+int
+dme_attach(struct dme_softc *sc, const uint8_t *enaddr)
+{
+	struct ifnet	*ifp = &sc->sc_ethercom.ec_if;
+	uint8_t		b[2];
+	uint16_t	io_mode;
 
 	dme_read_c(sc, DM9000_VID0, b, 2);
 #if BYTE_ORDER == BIG_ENDIAN
@@ -260,16 +426,6 @@ dme_attach(struct dme_softc *sc, uint8_t
 		    sc->sc_product_id);
 	}
 
-#if 0
-	{
-		/* Force 10Mbps to test dme_phy_write */
-		uint16_t bmcr;
-		bmcr = dme_phy_read(sc, DM9000_PHY_BMCR);
-		bmcr &= ~DM9000_PHY_BMCR_AUTO_NEG_EN;
-		bmcr &= ~DM9000_PHY_BMCR_SPEED_SELECT; /* select 100Mbps */
-		dme_phy_write(sc, DM9000_PHY_BMCR, bmcr);
-	}
-#endif
 	/* Initialize ifnet structure. */
 	strlcpy(ifp->if_xname, device_xname(sc->sc_dev), IFNAMSIZ);
 	ifp->if_softc = sc;
@@ -278,17 +434,28 @@ dme_attach(struct dme_softc *sc, uint8_t
 	ifp->if_ioctl = dme_ioctl;
 	ifp->if_stop = dme_stop;
 	ifp->if_watchdog = NULL;	/* no watchdog at this stage */
-	ifp->if_flags = IFF_SIMPLEX | IFF_NOTRAILERS |
-		IFF_BROADCAST; /* No multicast support for now */
+	ifp->if_flags = IFF_SIMPLEX | IFF_NOTRAILERS | IFF_BROADCAST |
+			IFF_MULTICAST;
 	IFQ_SET_READY(&ifp->if_snd);
 
 	/* Initialize ifmedia structures. */
 	ifmedia_init(&sc->sc_media, 0, dme_mediachange, dme_mediastatus);
-	ifmedia_add(&sc->sc_media, IFM_ETHER|IFM_10_T|IFM_100_TX, 0, NULL);
-	ifmedia_set(&sc->sc_media, IFM_ETHER|IFM_10_T|IFM_100_TX);
+	ifmedia_add(&sc->sc_media, IFM_ETHER|IFM_AUTO, 0, NULL);
+	ifmedia_add(&sc->sc_media, IFM_ETHER|IFM_10_T|IFM_FDX, 0, NULL);
+	ifmedia_add(&sc->sc_media, IFM_ETHER|IFM_10_T, 0, NULL);
+	ifmedia_add(&sc->sc_media, IFM_ETHER|IFM_100_TX|IFM_FDX, 0, NULL);
+	ifmedia_add(&sc->sc_media, IFM_ETHER|IFM_100_TX, 0, NULL);
+
+	ifmedia_set(&sc->sc_media, IFM_ETHER|IFM_AUTO);
 
 	if (enaddr != NULL)
 		memcpy(sc->sc_enaddr, enaddr, sizeof(sc->sc_enaddr));
+	/* TODO: Support an EEPROM attached to the DM9000 chip */
+
+	callout_init(&sc->sc_link_callout, 0);
+	callout_setfunc(&sc->sc_link_callout, dme_phy_check_link, sc);
+
+	sc->sc_media_status = 0;
 
 	/* Configure DM9000 with the MAC address */
 	dme_write_c(sc, DM9000_PAB0, sc->sc_enaddr, 6);
@@ -325,28 +492,34 @@ dme_attach(struct dme_softc *sc, uint8_t
 	}
 #endif
 
-	sc->io_mode = (dme_read(sc, DM9000_ISR) &
+	io_mode = (dme_read(sc, DM9000_ISR) &
 	    DM9000_IOMODE_MASK) >> DM9000_IOMODE_SHIFT;
-	if (sc->io_mode != DM9000_MODE_16BIT )
+	if (io_mode != DM9000_MODE_16BIT )
 		panic("DM9000: Only 16-bit mode is supported!\n");
-#ifdef DM9000_DEBUG
-	printf("DM9000 Operation Mode: ");
-	switch( sc->io_mode) {
+
+	DPRINTF(("DM9000 Operation Mode: "));
+	switch( io_mode) {
 	case DM9000_MODE_16BIT:
-		printf("16-bit mode");
+		DPRINTF(("16-bit mode"));
+		sc->sc_data_width = 2;
+		sc->sc_pkt_write = dme_pkt_write_2;
+		sc->sc_pkt_read = dme_pkt_read_2;
 		break;
 	case DM9000_MODE_32BIT:
-		printf("32-bit mode");
+		DPRINTF(("32-bit mode"));
+		sc->sc_data_width = 4;
 		break;
 	case DM9000_MODE_8BIT:
-		printf("8-bit mode");
+		DPRINTF(("8-bit mode"));
+		sc->sc_data_width = 1;
 		break;
-	case 3:
-		printf("Invalid mode");
+	default:
+		DPRINTF(("Invalid mode"));
 		break;
 	}
-	printf("\n");
-#endif
+	DPRINTF(("\n"));
+
+	callout_schedule(&sc->sc_link_callout, mstohz(2000));
 
 	return 0;
 }
@@ -357,6 +530,9 @@ int dme_intr(void *arg)
 	struct ifnet *ifp = &sc->sc_ethercom.ec_if;
 	uint8_t status;
 
+
+	DPRINTF(("dme_intr: Begin\n"));
+
 	/* Disable interrupts */
 	dme_write(sc, DM9000_IMR, DM9000_IMR_PAR );
 
@@ -412,6 +588,8 @@ int dme_intr(void *arg)
 	dme_write(sc, DM9000_IMR, DM9000_IMR_PAR | DM9000_IMR_PRM |
 		 DM9000_IMR_PTM);
 
+	DPRINTF(("dme_intr: End\n"));
+
 	return 1;
 }
 
@@ -422,6 +600,8 @@ dme_start_output(struct ifnet *ifp)
 
 	sc = ifp->if_softc;
 
+	DPRINTF(("dme_start_output: Begin\n"));
+
 	if ((ifp->if_flags & (IFF_RUNNING | IFF_OACTIVE)) != IFF_RUNNING) {
 		printf("No output\n");
 		return;
@@ -444,15 +624,15 @@ dme_start_output(struct ifnet *ifp)
 		 */
 		ifp->if_flags |= IFF_OACTIVE;
 	}
+
+	DPRINTF(("dme_start_output: End\n"));
 }
 
 void
 dme_prepare(struct dme_softc *sc, struct ifnet *ifp)
 {
-	struct mbuf *buf;
 	struct mbuf *bufChain;
 	uint16_t length;
-	uint8_t *write_ptr;
 
 	TX_DPRINTF(("dme_prepare: Entering\n"));
 
@@ -471,106 +651,16 @@ dme_prepare(struct dme_softc *sc, struct
 	if (ifp->if_bpf)
 		bpf_mtap(ifp, bufChain);
 
-
-	length = 0;
-
-	/* XXX: This support 16-bit I/O mode only. */
-	/* XXX: This code must be factored out, such that architecture
-	   dependent versions can be supplied */
-
-	int left_over_count = 0; /* Number of bytes from previous mbuf, which
-				    need to be written with the next.*/
-	uint16_t left_over_buf = 0;
-
 	/* Setup the DM9000 to accept the writes, and then write each buf in
 	   the chain. */
 
 	TX_DATA_DPRINTF(("dme_prepare: Writing data: "));
 	bus_space_write_1(sc->sc_iot, sc->sc_ioh, sc->dme_io, DM9000_MWCMD);
-	for (buf = bufChain; buf != NULL; buf = buf->m_next) {
-		int to_write = buf->m_len;
-
-		length += to_write;
-
-		write_ptr = buf->m_data;
-		while (to_write > 0 ||
-		       (buf->m_next == NULL && left_over_count > 0)
-		       ) {
-			if (left_over_count > 0) {
-				uint8_t b = 0;
-				DPRINTF(("dme_prepare: "
-					"Writing left over byte\n"));
-
-				if (to_write > 0) {
-					b = *write_ptr;
-					to_write--;
-					write_ptr++;
-
-					DPRINTF(("Took single byte\n"));
-				} else {
-					DPRINTF(("Leftover in last run\n"));
-					length++;
-				}
-
-				/* Does shift direction depend on endianess? */
-				left_over_buf = left_over_buf | (b << 8);
-
-				bus_space_write_2(sc->sc_iot, sc->sc_ioh,
-						  sc->dme_data, left_over_buf);
-				TX_DATA_DPRINTF(("%02X ", left_over_buf));
-				left_over_count = 0;
-			} else if ((long)write_ptr % 2 != 0) {
-				/* Misaligned data */
-				DPRINTF(("dme_prepare: "
-					"Detected misaligned data\n"));
-				left_over_buf = *write_ptr;
-				left_over_count = 1;
-				write_ptr++;
-				to_write--;
-			} else {
-				int i;
-				uint16_t *dptr = (uint16_t*)write_ptr;
-
-				/* A block of aligned data. */
-				for(i = 0; i < to_write/2; i++) {
-					/* buf will be half-word aligned
-					 * all the time
-					 */
-					bus_space_write_2(sc->sc_iot,
-					    sc->sc_ioh, sc->dme_data, *dptr);
-					TX_DATA_DPRINTF(("%02X %02X ",
-					    *dptr & 0xFF, (*dptr>>8) & 0xFF));
-					dptr++;
-				}
-
-				write_ptr += i*2;
-				if (to_write % 2 != 0) {
-					DPRINTF(("dme_prepare: "
-						"to_write %% 2: %d\n",
-						to_write % 2));
-					left_over_count = 1;
-					/* XXX: Does this depend on
-					 * the endianess?
-					 */
-					left_over_buf = *write_ptr;
-					
-					write_ptr++;
-					to_write--;
-					DPRINTF(("dme_prepare: "
-						"to_write (after): %d\n",
-						to_write));
-					DPRINTF(("dme_prepare: i*2: %d\n",
-						i*2));
-				}
-				to_write -= i*2;
-			}
-		} /* while(...) */
-	} /* for(...) */
-
+	length = sc->sc_pkt_write(sc, bufChain);
 	TX_DATA_DPRINTF(("\n"));
 
-	if (length % 2 == 1) {
-		panic("dme_prepare: length is not a word-length");
+	if (length % sc->sc_data_width != 0) {
+		panic("dme_prepare: length is not compatible with IO_MODE");
 	}
 
 	sc->txready_length = length;
@@ -622,6 +712,14 @@ dme_ioctl(struct ifnet *ifp, u_long cmd,
 		break;
 	default:
 		error = ether_ioctl(ifp, cmd, data);
+		if (error == ENETRESET) {
+			if (ifp->if_flags && IFF_RUNNING) {
+				/* Address list has changed, reconfigure
+				   filter */
+				dme_set_addr_filter(sc);
+			}
+			error = 0;
+		}
 		break;
 	}
 
@@ -647,22 +745,18 @@ dme_stop(struct ifnet *ifp, int disable)
 int
 dme_mediachange(struct ifnet *ifp)
 {
-	/* TODO: Make this function do something useful. */
-	return 0;
+	struct dme_softc *sc = ifp->if_softc;
+
+	return dme_set_media(sc, sc->sc_media.ifm_cur->ifm_media);
 }
 
 void
 dme_mediastatus(struct ifnet *ifp, struct ifmediareq *ifmr)
 {
-	/* TODO: Make this function do something useful. */
 	struct dme_softc *sc = ifp->if_softc;
-	ifmr->ifm_active = sc->sc_media.ifm_cur->ifm_media;
 
-	if (ifp->if_flags & IFF_UP) {
-		ifmr->ifm_status = IFM_AVALID | IFM_ACTIVE;
-	} else {
-		ifmr->ifm_status = 0;
-	}
+	ifmr->ifm_active = sc->sc_media_active;
+	ifmr->ifm_status = sc->sc_media_status;
 }
 
 void
@@ -704,69 +798,14 @@ dme_receive(struct dme_softc *sc, struct
 		ready = bus_space_read_1(sc->sc_iot, sc->sc_ioh, sc->dme_data);
 		ready &= 0x03;	/* we only want bits 1:0 */
 		if (ready == 0x01) {
-			uint8_t rx_status;
-
-			uint16_t data;
-			uint16_t frame_length;
-			uint16_t i;
-			struct mbuf *m;
-			uint16_t *buf;
-			int pad;
-
-			/* TODO: Add support for 8-bit and
-			 *  32-bit transfer modes.
-			 */
+			uint8_t		rx_status;
+			struct mbuf	*m;
 
 			/* Read with address increment. */
 			bus_space_write_1(sc->sc_iot, sc->sc_ioh,
-			    sc->dme_io, DM9000_MRCMD);
-			data = bus_space_read_2(sc->sc_iot, sc->sc_ioh,
-			    sc->dme_data);
-
-			rx_status = data & 0xFF;
-			frame_length = bus_space_read_2(sc->sc_iot,
-			    sc->sc_ioh, sc->dme_data);
-
-			RX_DPRINTF(("dme_receive: "
-				"rx_statux: 0x%x, frame_length: %d\n",
-				rx_status, frame_length));
-
-
-			MGETHDR(m, M_DONTWAIT, MT_DATA);
-			m->m_pkthdr.rcvif = ifp;
-			/* Ensure that we always allocate an even number of
-			 * bytes in order to avoid writing beyond the buffer
-			 */
-			m->m_pkthdr.len = frame_length + (frame_length % 2);
-			pad = ALIGN(sizeof(struct ether_header)) -
-			    sizeof(struct ether_header);
-			/* All our frames have the CRC attached */
-			m->m_flags |= M_HASFCS;
-			if (m->m_pkthdr.len + pad > MHLEN )
-				MCLGET(m, M_DONTWAIT);
-
-			m->m_data += pad;
-			m->m_len = frame_length + (frame_length % 2);
-			buf = mtod(m, uint16_t*);
-
-			RX_DPRINTF(("dme_receive: "));
-
-			for(i=0; i< frame_length; i+=2 ) {
-				data = bus_space_read_2(sc->sc_iot,
-				    sc->sc_ioh, sc->dme_data);
-				if ( (frame_length % 2 != 0) &&
-				    (i == frame_length-1) ) {
-					data = data & 0xff;
-					RX_DPRINTF((" L "));
-				}
-				*buf = data;
-				buf++;
-				RX_DATA_DPRINTF(("%02X %02X ", data & 0xff,
-					    (data>>8) & 0xff));
-			}
+					  sc->dme_io, DM9000_MRCMD);
 
-			RX_DATA_DPRINTF(("\n"));
-			RX_DPRINTF(("Read %d bytes\n", i));
+			rx_status = sc->sc_pkt_read(sc, ifp, &m);
 
 			if (rx_status & (DM9000_RSR_CE | DM9000_RSR_PLE)) {
 				/* Error while receiving the packet,
@@ -786,7 +825,8 @@ dme_receive(struct dme_softc *sc, struct
 
 		} else if (ready != 0x00) {
 			/* Should this be logged somehow? */
-			DPRINTF(("DM9000: Resetting chip\n"));
+			printf("%s: Resetting chip\n",
+			       device_xname(sc->sc_dev));
 			dme_reset(sc);
 		}
 	}
@@ -797,28 +837,46 @@ dme_reset(struct dme_softc *sc)
 {
 	uint8_t var;
 
-	/* Enable PHY */
-	var = dme_read(sc, DM9000_GPCR);
-	dme_write(sc, DM9000_GPCR, var | DM9000_GPCR_GPIO0_OUT);
-	var = dme_read(sc, DM9000_GPR);
-	dme_write(sc, DM9000_GPR, var & ~DM9000_GPR_PHY_PWROFF);
+	/* We only re-initialized the PHY in this function the first time it is
+	   called. */
+	if( !sc->sc_phy_initialized) {
+		/* PHY Reset */
+		dme_phy_write(sc, DM9000_PHY_BMCR, DM9000_PHY_BMCR_RESET);
+
+		/* PHY Power Down */
+		var = dme_read(sc, DM9000_GPR);
+		dme_write(sc, DM9000_GPR, var | DM9000_GPR_PHY_PWROFF);
+	}
 
-	/* Reset the DM9000 twice, as describe din section 5.2 of the
-	 * Application Notes
+	/* Reset the DM9000 twice, as described in section 2 of the Programming
+	   Guide.
+	   The PHY is initialized and enabled between those two resets.
 	 */
+
+	/* Software Reset*/
 	dme_write(sc, DM9000_NCR,
 	    DM9000_NCR_RST | DM9000_NCR_LBK_MAC_INTERNAL);
-	
-	delay(20);
-	dme_write(sc, DM9000_NCR, 0x0);
-	dme_write(sc, DM9000_NCR,
-	    DM9000_NCR_RST | DM9000_NCR_LBK_MAC_INTERNAL);
-	
 	delay(20);
 	dme_write(sc, DM9000_NCR, 0x0);
 
+	if( !sc->sc_phy_initialized) {
+		/* PHY Initialization */
+		dme_phy_init(sc);
+
+		/* PHY Enable */
+		var = dme_read(sc, DM9000_GPR);
+		dme_write(sc, DM9000_GPR, var & ~DM9000_GPR_PHY_PWROFF);
+		var = dme_read(sc, DM9000_GPCR);
+		dme_write(sc, DM9000_GPCR, var | DM9000_GPCR_GPIO0_OUT);
+
+		dme_write(sc, DM9000_NCR,
+			  DM9000_NCR_RST | DM9000_NCR_LBK_MAC_INTERNAL);
+		delay(20);
+		dme_write(sc, DM9000_NCR, 0x0);
+	}
+
 	/* Select internal PHY, no wakeup event, no collosion mode,
-	 * normal loopback mode, and no full duplex mode
+	 * normal loopback mode.
 	 */
 	dme_write(sc, DM9000_NCR, DM9000_NCR_LBK_NORMAL );
 
@@ -831,9 +889,245 @@ dme_reset(struct dme_softc *sc)
 	dme_write(sc, DM9000_IMR,
 	    DM9000_IMR_PAR | DM9000_IMR_PRM | DM9000_IMR_PTM);
 
-	/* Enable RX without watchdog */
-	dme_write(sc, DM9000_RCR, DM9000_RCR_RXEN | DM9000_RCR_WTDIS);
+	/* Setup multicast address filter, and enable RX. */
+	dme_set_addr_filter(sc);
+
+	/* Obtain media information from PHY */
+	dme_phy_update_media(sc);
 
 	sc->txbusy = 0;
 	sc->txready = 0;
+	sc->sc_phy_initialized = 1;
+}
+
+void
+dme_set_addr_filter(struct dme_softc *sc)
+{
+	struct ether_multi	*enm;
+	struct ether_multistep	step;
+	struct ethercom		*ec;
+	struct ifnet		*ifp;
+	uint16_t		af[4];
+	int			i;
+
+	ec = &sc->sc_ethercom;
+	ifp = &ec->ec_if;
+
+	if (ifp->if_flags & IFF_PROMISC) {
+		dme_write(sc, DM9000_RCR, DM9000_RCR_RXEN  |
+					  DM9000_RCR_WTDIS |
+					  DM9000_RCR_PRMSC);
+		ifp->if_flags |= IFF_ALLMULTI;
+		return;
+	}
+
+	af[0] = af[1] = af[2] = af[3] = 0x0000;
+	ifp->if_flags &= ~IFF_ALLMULTI;
+
+	ETHER_FIRST_MULTI(step, ec, enm);
+	while (enm != NULL) {
+		uint16_t hash;
+		if (memcpy(enm->enm_addrlo, enm->enm_addrhi,
+		    sizeof(enm->enm_addrlo))) {
+			/*
+	                 * We must listen to a range of multicast addresses.
+	                 * For now, just accept all multicasts, rather than
+	                 * trying to set only those filter bits needed to match
+	                 * the range.  (At this time, the only use of address
+	                 * ranges is for IP multicast routing, for which the
+	                 * range is big enough to require all bits set.)
+	                 */
+			ifp->if_flags |= IFF_ALLMULTI;
+			af[0] = af[1] = af[2] = af[3] = 0xffff;
+			break;
+		} else {
+			hash = ether_crc32_le(enm->enm_addrlo, ETHER_ADDR_LEN) & 0x3F;
+			af[(uint16_t)(hash>>4)] |= (uint16_t)(1 << (hash % 16));
+			ETHER_NEXT_MULTI(step, enm);
+		}
+	}
+
+	/* Write the multicast address filter */
+	for(i=0; i<4; i++) {
+		dme_write(sc, DM9000_MAB0+i*2, af[i] & 0xFF);
+		dme_write(sc, DM9000_MAB0+i*2+1, (af[i] >> 8) & 0xFF);
+	}
+
+	/* Setup RX controls */
+	dme_write(sc, DM9000_RCR, DM9000_RCR_RXEN | DM9000_RCR_WTDIS);
+}
+
+int
+dme_pkt_write_2(struct dme_softc *sc, struct mbuf *bufChain)
+{
+	int left_over_count = 0; /* Number of bytes from previous mbuf, which
+				    need to be written with the next.*/
+	uint16_t left_over_buf = 0;
+	int length = 0;
+	struct mbuf *buf;
+	uint8_t *write_ptr;
+
+	/* We expect that the DM9000 has been setup to accept writes before
+	   this function is called. */
+
+	for (buf = bufChain; buf != NULL; buf = buf->m_next) {
+		int to_write = buf->m_len;
+
+		length += to_write;
+
+		write_ptr = buf->m_data;
+		while (to_write > 0 ||
+		       (buf->m_next == NULL && left_over_count > 0)
+		       ) {
+			if (left_over_count > 0) {
+				uint8_t b = 0;
+				DPRINTF(("dme_pkt_write_16: "
+					 "Writing left over byte\n"));
+
+				if (to_write > 0) {
+					b = *write_ptr;
+					to_write--;
+					write_ptr++;
+
+					DPRINTF(("Took single byte\n"));
+				} else {
+					DPRINTF(("Leftover in last run\n"));
+					length++;
+				}
+
+				/* Does shift direction depend on endianess? */
+				left_over_buf = left_over_buf | (b << 8);
+
+				bus_space_write_2(sc->sc_iot, sc->sc_ioh,
+						  sc->dme_data, left_over_buf);
+				TX_DATA_DPRINTF(("%02X ", left_over_buf));
+				left_over_count = 0;
+			} else if ((long)write_ptr % 2 != 0) {
+				/* Misaligned data */
+				DPRINTF(("dme_pkt_write_16: "
+					 "Detected misaligned data\n"));
+				left_over_buf = *write_ptr;
+				left_over_count = 1;
+				write_ptr++;
+				to_write--;
+			} else {
+				int i;
+				uint16_t *dptr = (uint16_t*)write_ptr;
+
+				/* A block of aligned data. */
+				for(i = 0; i < to_write/2; i++) {
+					/* buf will be half-word aligned
+					 * all the time
+					 */
+					bus_space_write_2(sc->sc_iot,
+							  sc->sc_ioh, sc->dme_data, *dptr);
+					TX_DATA_DPRINTF(("%02X %02X ",
+							 *dptr & 0xFF, (*dptr>>8) & 0xFF));
+					dptr++;
+				}
+
+				write_ptr += i*2;
+				if (to_write % 2 != 0) {
+					DPRINTF(("dme_pkt_write_16: "
+						 "to_write %% 2: %d\n",
+						 to_write % 2));
+					left_over_count = 1;
+					/* XXX: Does this depend on
+					 * the endianess?
+					 */
+					left_over_buf = *write_ptr;
+
+					write_ptr++;
+					to_write--;
+					DPRINTF(("dme_pkt_write_16: "
+						 "to_write (after): %d\n",
+						 to_write));
+					DPRINTF(("dme_pkt_write_16: i*2: %d\n",
+						 i*2));
+				}
+				to_write -= i*2;
+			}
+		} /* while(...) */
+	} /* for(...) */
+
+	return length;
+}
+
+int
+dme_pkt_read_2(struct dme_softc *sc, struct ifnet *ifp, struct mbuf **outBuf)
+{
+	uint8_t rx_status;
+	struct mbuf *m;
+	uint16_t data;
+	uint16_t frame_length;
+	uint16_t i;
+	uint16_t *buf;
+
+	data = bus_space_read_2(sc->sc_iot, sc->sc_ioh,
+				sc->dme_data);
+
+	rx_status = data & 0xFF;
+	frame_length = bus_space_read_2(sc->sc_iot,
+					sc->sc_ioh, sc->dme_data);
+	if (frame_length > ETHER_MAX_LEN) {
+		printf("Got frame of length: %d\n", frame_length);
+		printf("ETHER_MAX_LEN is: %d\n", ETHER_MAX_LEN);
+		panic("Something is rotten");
+	}
+	RX_DPRINTF(("dme_receive: "
+		    "rx_statux: 0x%x, frame_length: %d\n",
+		    rx_status, frame_length));
+
+
+	m = dme_alloc_receive_buffer(ifp, frame_length);
+
+	buf = mtod(m, uint16_t*);
+
+	RX_DPRINTF(("dme_receive: "));
+
+	for(i=0; i< frame_length; i+=2 ) {
+		data = bus_space_read_2(sc->sc_iot,
+					sc->sc_ioh, sc->dme_data);
+		if ( (frame_length % 2 != 0) &&
+		     (i == frame_length-1) ) {
+			data = data & 0xff;
+			RX_DPRINTF((" L "));
+		}
+		*buf = data;
+		buf++;
+		RX_DATA_DPRINTF(("%02X %02X ", data & 0xff,
+				 (data>>8) & 0xff));
+	}
+
+	RX_DATA_DPRINTF(("\n"));
+	RX_DPRINTF(("Read %d bytes\n", i));
+
+	*outBuf = m;
+	return rx_status;
+}
+
+struct mbuf*
+dme_alloc_receive_buffer(struct ifnet *ifp, unsigned int frame_length)
+{
+	struct dme_softc *sc = ifp->if_softc;
+	struct mbuf *m;
+	int pad;
+
+	MGETHDR(m, M_DONTWAIT, MT_DATA);
+	m->m_pkthdr.rcvif = ifp;
+	/* Ensure that we always allocate an even number of
+	 * bytes in order to avoid writing beyond the buffer
+	 */
+	m->m_pkthdr.len = frame_length + (frame_length % sc->sc_data_width);
+	pad = ALIGN(sizeof(struct ether_header)) -
+		sizeof(struct ether_header);
+	/* All our frames have the CRC attached */
+	m->m_flags |= M_HASFCS;
+	if (m->m_pkthdr.len + pad > MHLEN )
+		MCLGET(m, M_DONTWAIT);
+
+	m->m_data += pad;
+	m->m_len = frame_length + (frame_length % sc->sc_data_width);
+
+	return m;
 }

Index: src/sys/dev/ic/dm9000reg.h
diff -u src/sys/dev/ic/dm9000reg.h:1.1 src/sys/dev/ic/dm9000reg.h:1.2
--- src/sys/dev/ic/dm9000reg.h:1.1	Wed Sep  8 22:01:29 2010
+++ src/sys/dev/ic/dm9000reg.h	Sat Jan 28 08:29:55 2012
@@ -1,4 +1,4 @@
-/*	$NetBSD: dm9000reg.h,v 1.1 2010/09/08 22:01:29 ahoka Exp $	*/
+/*	$NetBSD: dm9000reg.h,v 1.2 2012/01/28 08:29:55 nisimura Exp $	*/
 
 /*
  * Copyright (c) 2009 Paul Fleischer
@@ -195,6 +195,10 @@
 #define DM9000_PHY_PHYID1	0x02
 #define DM9000_PHY_PHYID2 	0x03
 #define DM9000_PHY_ANAR 	0x04
+#define  DM9000_PHY_ANAR_10_HDX	 (1<<5)
+#define  DM9000_PHY_ANAR_10_FDX  (1<<6)
+#define  DM9000_PHY_ANAR_TX_HDX  (1<<7)
+#define  DM9000_PHY_ANAR_TX_FDX  (1<<8)
 #define DM9000_PHY_ANLPAR	0x05
 #define DM9000_PHY_ANER 	0x06
 #define DM9000_PHY_DSCR 	0x16
Index: src/sys/dev/ic/dm9000var.h
diff -u src/sys/dev/ic/dm9000var.h:1.1 src/sys/dev/ic/dm9000var.h:1.2
--- src/sys/dev/ic/dm9000var.h:1.1	Wed Sep  8 22:01:29 2010
+++ src/sys/dev/ic/dm9000var.h	Sat Jan 28 08:29:55 2012
@@ -1,4 +1,4 @@
-/*	$NetBSD: dm9000var.h,v 1.1 2010/09/08 22:01:29 ahoka Exp $	*/
+/*	$NetBSD: dm9000var.h,v 1.2 2012/01/28 08:29:55 nisimura Exp $	*/
 
 /*
  * Copyright (c) 2009 Paul Fleischer
@@ -63,7 +63,7 @@
 #ifndef _DEV_IC_DM9000VAR_H_
 #define _DEV_IC_DM9000VAR_H_
 
-#include <sys/mutex.h>
+#include <sys/callout.h>
 
 #define DM9000_MODE_8BIT 2
 #define DM9000_MODE_16BIT 0
@@ -75,6 +75,9 @@ struct dme_softc {
 	struct ethercom sc_ethercom;	/* Ethernet common data */
 	struct ifmedia	sc_media;	/* Media control structures */
 
+	uint		sc_media_active;
+	uint		sc_media_status;
+
 	bus_space_tag_t		sc_iot;
 	bus_space_handle_t	sc_ioh;
 	void		*sc_ih;
@@ -85,7 +88,7 @@ struct dme_softc {
 	uint16_t	sc_vendor_id;
 	uint16_t	sc_product_id;
 
-	uint16_t	io_mode;
+	uint8_t		sc_data_width;
 
 	uint8_t		sc_enaddr[ETHER_ADDR_LEN];
 
@@ -94,16 +97,26 @@ struct dme_softc {
 					   for transmission. */
 	uint16_t	txready_length;
 
+	int (*sc_pkt_write)(struct dme_softc*, struct mbuf *);
+	int (*sc_pkt_read)(struct dme_softc*, struct ifnet *, struct mbuf **);
+
+	callout_t	sc_link_callout;
+
+	bool		sc_phy_initialized;
+
 #ifdef DIAGNOSTIC
 	bool		sc_inside_interrupt;
 #endif
 };
 
 /* Function declarations */
-int	dme_attach(struct dme_softc *, uint8_t *);
+int	dme_attach(struct dme_softc *, const uint8_t *);
 int	dme_detach(struct dme_softc *);
 int	dme_intr(void *);
 
+/* Helper method used by sc_pkt_read */
+struct mbuf* dme_alloc_receive_buffer(struct ifnet *, unsigned int);
+
 /* Inline memory access methods */
 static inline uint8_t
 dme_read(struct dme_softc *sc, int reg)

Reply via email to