From f3b37a1f47825ce9e61008752015be6bf856ebb7 Mon Sep 17 00:00:00 2001
From: Ruslan Bukin
Date: Thu, 17 Nov 2016 11:48:07 +0000
Subject: [PATCH] Add driver for DM9000 Ethernet MAC Controller.
This device found in the Ingenic jz4780 SoC.
Submitted by: kan
Sponsored by: DARPA, AFRL
---
sys/conf/files | 1 +
sys/dev/dme/if_dme.c | 1070 +++++++++++++++++++++++++++++++++++++++
sys/dev/dme/if_dmereg.h | 137 +++++
sys/dev/dme/if_dmevar.h | 39 ++
4 files changed, 1247 insertions(+)
create mode 100644 sys/dev/dme/if_dme.c
create mode 100644 sys/dev/dme/if_dmereg.h
create mode 100644 sys/dev/dme/if_dmevar.h
diff --git a/sys/conf/files b/sys/conf/files
index 07864e8ede95..07bbfb8c41c6 100644
--- a/sys/conf/files
+++ b/sys/conf/files
@@ -1402,6 +1402,7 @@ dev/dcons/dcons.c optional dcons
dev/dcons/dcons_crom.c optional dcons_crom
dev/dcons/dcons_os.c optional dcons
dev/de/if_de.c optional de pci
+dev/dme/if_dme.c optional dme
dev/dpt/dpt_eisa.c optional dpt eisa
dev/dpt/dpt_pci.c optional dpt pci
dev/dpt/dpt_scsi.c optional dpt
diff --git a/sys/dev/dme/if_dme.c b/sys/dev/dme/if_dme.c
new file mode 100644
index 000000000000..48f1c89ef092
--- /dev/null
+++ b/sys/dev/dme/if_dme.c
@@ -0,0 +1,1070 @@
+/*
+ * Copyright (C) 2015 Alexander Kabaev
+ * Copyright (C) 2010 Andrew Turner
+ * 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 AND CONTRIBUTORS ``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 CONTRIBUTORS 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.
+ */
+
+/*
+ * A driver for the DM9000 MAC
+ *
+ * TODO:
+ * Get the interrupt working
+ * Port to non-S3C2440 systems
+ * Test with 8 and 32 bit busses
+ * Test on a big endian machine
+ * Implement the rest of dme_detach
+ */
+
+#include
+__FBSDID("$FreeBSD$");
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+
+#include
+#include
+
+#include
+#include
+
+#include
+#include
+
+#include "miibus_if.h"
+
+struct dme_softc {
+ struct ifnet *dme_ifp;
+ device_t dme_dev;
+ device_t dme_miibus;
+ bus_space_handle_t dme_handle;
+ bus_space_tag_t dme_tag;
+ int dme_rev;
+ int dme_bits;
+ struct resource *dme_res;
+ struct resource *dme_irq;
+ void *dme_intrhand;
+ struct mtx dme_mtx;
+ struct callout dme_tick_ch;
+ struct gpiobus_pin *gpio_rset;
+ uint32_t dme_ticks;
+ uint8_t dme_macaddr[ETHER_ADDR_LEN];
+ regulator_t dme_vcc_regulator;
+ uint8_t dme_txbusy: 1;
+ uint8_t dme_txready: 1;
+ uint16_t dme_txlen;
+};
+
+#define DME_CHIP_DM9000 0x00
+#define DME_CHIP_DM9000A 0x19
+#define DME_CHIP_DM9000B 0x1a
+
+#define DME_INT_PHY 1
+
+static int dme_probe(device_t);
+static int dme_attach(device_t);
+static int dme_detach(device_t);
+
+static void dme_intr(void *arg);
+static void dme_init_locked(struct dme_softc *);
+
+static void dme_prepare(struct dme_softc *);
+static void dme_transmit(struct dme_softc *);
+
+static int dme_miibus_writereg(device_t dev, int phy, int reg, int data);
+static int dme_miibus_readreg(device_t dev, int phy, int reg);
+
+/* The bit on the address bus attached to the CMD pin */
+#define BASE_ADDR 0x000
+#define CMD_ADDR BASE_ADDR
+#define DATA_BIT 1
+#define DATA_ADDR 0x002
+
+#undef DME_TRACE
+
+#ifdef DME_TRACE
+#define DTR3 TR3
+#define DTR4 TR4
+#else
+#define NOTR(args...) (void)0
+#define DTR3 NOTR
+#define DTR4 NOTR
+#endif
+
+static uint8_t
+dme_read_reg(struct dme_softc *sc, uint8_t reg)
+{
+
+ /* Send the register to read from */
+ bus_space_write_1(sc->dme_tag, sc->dme_handle, CMD_ADDR, reg);
+ bus_space_barrier(sc->dme_tag, sc->dme_handle, CMD_ADDR, 1,
+ BUS_SPACE_BARRIER_WRITE);
+
+ /* Get the value of the register */
+ return bus_space_read_1(sc->dme_tag, sc->dme_handle, DATA_ADDR);
+}
+
+static void
+dme_write_reg(struct dme_softc *sc, uint8_t reg, uint8_t value)
+{
+
+ /* Send the register to write to */
+ bus_space_write_1(sc->dme_tag, sc->dme_handle, CMD_ADDR, reg);
+ bus_space_barrier(sc->dme_tag, sc->dme_handle, CMD_ADDR, 1,
+ BUS_SPACE_BARRIER_WRITE);
+
+ /* Write the value to the register */
+ bus_space_write_1(sc->dme_tag, sc->dme_handle, DATA_ADDR, value);
+ bus_space_barrier(sc->dme_tag, sc->dme_handle, DATA_ADDR, 1,
+ BUS_SPACE_BARRIER_WRITE);
+}
+
+static void
+dme_reset(struct dme_softc *sc)
+{
+ u_int ncr;
+
+ /* Send a soft reset #1 */
+ dme_write_reg(sc, DME_NCR, NCR_RST | NCR_LBK_MAC);
+ DELAY(100); /* Wait for the MAC to reset */
+ ncr = dme_read_reg(sc, DME_NCR);
+ if (ncr & NCR_RST)
+ device_printf(sc->dme_dev, "device did not complete first reset\n");
+
+ /* Send a soft reset #2 per Application Notes v1.22 */
+ dme_write_reg(sc, DME_NCR, 0);
+ dme_write_reg(sc, DME_NCR, NCR_RST | NCR_LBK_MAC);
+ DELAY(100); /* Wait for the MAC to reset */
+ ncr = dme_read_reg(sc, DME_NCR);
+ if (ncr & NCR_RST)
+ device_printf(sc->dme_dev, "device did not complete second reset\n");
+
+ /* Reset trasmit state */
+ sc->dme_txbusy = 0;
+ sc->dme_txready = 0;
+
+ DTR3("dme_reset, flags %#x busy %d ready %d",
+ sc->dme_ifp ? sc->dme_ifp->if_drv_flags : 0,
+ sc->dme_txbusy, sc->dme_txready);
+}
+
+/*
+ * Parse string MAC address into usable form
+ */
+static int
+dme_parse_macaddr(const char *str, uint8_t *mac)
+{
+ int count, i;
+ unsigned int amac[ETHER_ADDR_LEN]; /* Aligned version */
+
+ count = sscanf(str, "%x%*c%x%*c%x%*c%x%*c%x%*c%x",
+ &amac[0], &amac[1], &amac[2],
+ &amac[3], &amac[4], &amac[5]);
+ if (count < ETHER_ADDR_LEN) {
+ memset(mac, 0, ETHER_ADDR_LEN);
+ return (1);
+ }
+
+ /* Copy aligned to result */
+ for (i = 0; i < ETHER_ADDR_LEN; i ++)
+ mac[i] = (amac[i] & 0xff);
+
+ return (0);
+}
+
+/*
+ * Try to determine our own MAC address
+ */
+static void
+dme_get_macaddr(struct dme_softc *sc)
+{
+ char devid_str[32];
+ char *var;
+ int i;
+
+ /* Cannot use resource_string_value with static hints mode */
+ snprintf(devid_str, 32, "hint.%s.%d.macaddr",
+ device_get_name(sc->dme_dev),
+ device_get_unit(sc->dme_dev));
+
+ /* Try resource hints */
+ if ((var = kern_getenv(devid_str)) != NULL) {
+ if (!dme_parse_macaddr(var, sc->dme_macaddr)) {
+ device_printf(sc->dme_dev, "MAC address: %s (hints)\n", var);
+ return;
+ }
+ }
+
+ /*
+ * Try to read MAC address from the device, in case U-Boot has
+ * pre-programmed one for us.
+ */
+ for (i = 0; i < ETHER_ADDR_LEN; i++)
+ sc->dme_macaddr[i] = dme_read_reg(sc, DME_PAR(i));
+
+ device_printf(sc->dme_dev, "MAC address %6D (existing)\n",
+ sc->dme_macaddr, ":");
+}
+
+static void
+dme_config(struct dme_softc *sc)
+{
+ int i;
+
+ /* Mask all interrupts and reset receive pointer */
+ dme_write_reg(sc, DME_IMR, IMR_PAR);
+
+ /* Disable GPIO0 to enable the internal PHY */
+ dme_write_reg(sc, DME_GPCR, 1);
+ dme_write_reg(sc, DME_GPR, 0);
+
+#if 0
+ /*
+ * Supposedly requires special initialization for DSP PHYs
+ * used by DM9000B. Maybe belongs in dedicated PHY driver?
+ */
+ if (sc->dme_rev == DME_CHIP_DM9000B) {
+ dme_miibus_writereg(sc->dme_dev, DME_INT_PHY, MII_BMCR,
+ BMCR_RESET);
+ dme_miibus_writereg(sc->dme_dev, DME_INT_PHY, MII_DME_DSPCR,
+ DSPCR_INIT);
+ /* Wait 100ms for it to complete. */
+ for (i = 0; i < 100; i++) {
+ int reg;
+
+ reg = dme_miibus_readreg(sc->dme_dev, DME_INT_PHY, MII_BMCR);
+ if ((reg & BMCR_RESET) == 0)
+ break;
+ DELAY(1000);
+ }
+ }
+#endif
+
+ /* Select the internal PHY and normal loopback */
+ dme_write_reg(sc, DME_NCR, NCR_LBK_NORMAL);
+ /* Clear any TX requests */
+ dme_write_reg(sc, DME_TCR, 0);
+ /* Setup backpressure thresholds to 4k and 600us */
+ dme_write_reg(sc, DME_BPTR, BPTR_BPHW(3) | BPTR_JPT(0x0f));
+ /* Setup flow control */
+ dme_write_reg(sc, DME_FCTR, FCTR_HWOT(0x3) | FCTR_LWOT(0x08));
+ /* Enable flow control */
+ dme_write_reg(sc, DME_FCR, 0xff);
+ /* Clear special modes */
+ dme_write_reg(sc, DME_SMCR, 0);
+ /* Clear TX status */
+ dme_write_reg(sc, DME_NSR, NSR_WAKEST | NSR_TX2END | NSR_TX1END);
+ /* Clear interrrupts */
+ dme_write_reg(sc, DME_ISR, 0xff);
+ /* Set multicast address filter */
+ for (i = 0; i < 8; i++)
+ dme_write_reg(sc, DME_MAR(i), 0xff);
+ /* Set the MAC address */
+ for (i = 0; i < ETHER_ADDR_LEN; i++)
+ dme_write_reg(sc, DME_PAR(i), sc->dme_macaddr[i]);
+ /* Enable the RX buffer */
+ dme_write_reg(sc, DME_RCR, RCR_DIS_LONG | RCR_DIS_CRC | RCR_RXEN);
+
+ /* Enable interrupts we care about */
+ dme_write_reg(sc, DME_IMR, IMR_PAR | IMR_PRI | IMR_PTI);
+}
+
+void
+dme_prepare(struct dme_softc *sc)
+{
+ struct ifnet *ifp;
+ struct mbuf *m, *mp;
+ uint16_t total_len, len;
+
+ DME_ASSERT_LOCKED(sc);
+
+ KASSERT(sc->dme_txready == 0,
+ ("dme_prepare: called with txready set\n"));
+
+ ifp = sc->dme_ifp;
+ IFQ_DEQUEUE(&ifp->if_snd, m);
+ if (m == NULL) {
+ ifp->if_drv_flags &= ~IFF_DRV_OACTIVE;
+ DTR3("dme_prepare none, flags %#x busy %d ready %d",
+ sc->dme_ifp->if_drv_flags, sc->dme_txbusy, sc->dme_txready);
+ return; /* Nothing to transmit */
+ }
+
+ /* Element has now been removed from the queue, so we better send it */
+ BPF_MTAP(ifp, m);
+
+ /* Setup the controller to accept the writes */
+ bus_space_write_1(sc->dme_tag, sc->dme_handle, CMD_ADDR, DME_MWCMD);
+
+ /*
+ * TODO: Fix the case where an mbuf is
+ * not a multiple of the write size.
+ */
+ total_len = 0;
+ for (mp = m; mp != NULL; mp = mp->m_next) {
+ len = mp->m_len;
+
+ /* Ignore empty parts */
+ if (len == 0)
+ continue;
+
+ total_len += len;
+
+#if 0
+ bus_space_write_multi_2(sc->dme_tag, sc->dme_handle,
+ DATA_ADDR, mtod(mp, uint16_t *), (len + 1) / 2);
+#else
+ bus_space_write_multi_1(sc->dme_tag, sc->dme_handle,
+ DATA_ADDR, mtod(mp, uint8_t *), len);
+#endif
+ }
+
+ if (total_len % (sc->dme_bits >> 3) != 0)
+ panic("dme_prepare: length is not compatible with IO_MODE");
+
+ sc->dme_txlen = total_len;
+ sc->dme_txready = 1;
+ DTR3("dme_prepare done, flags %#x busy %d ready %d",
+ sc->dme_ifp->if_drv_flags, sc->dme_txbusy, sc->dme_txready);
+
+ m_freem(m);
+}
+
+void
+dme_transmit(struct dme_softc *sc)
+{
+
+ DME_ASSERT_LOCKED(sc);
+ KASSERT(sc->dme_txready, ("transmit without txready"));
+
+ dme_write_reg(sc, DME_TXPLL, sc->dme_txlen & 0xff);
+ dme_write_reg(sc, DME_TXPLH, (sc->dme_txlen >> 8) & 0xff );
+
+ /* Request to send the packet */
+ dme_read_reg(sc, DME_ISR);
+
+ dme_write_reg(sc, DME_TCR, TCR_TXREQ);
+
+ sc->dme_txready = 0;
+ sc->dme_txbusy = 1;
+ DTR3("dme_transmit done, flags %#x busy %d ready %d",
+ sc->dme_ifp->if_drv_flags, sc->dme_txbusy, sc->dme_txready);
+}
+
+
+static void
+dme_start_locked(struct ifnet *ifp)
+{
+ struct dme_softc *sc;
+
+ sc = ifp->if_softc;
+ DME_ASSERT_LOCKED(sc);
+
+ if ((ifp->if_drv_flags & (IFF_DRV_RUNNING | IFF_DRV_OACTIVE)) !=
+ IFF_DRV_RUNNING)
+ return;
+
+ DTR3("dme_start, flags %#x busy %d ready %d",
+ sc->dme_ifp->if_drv_flags, sc->dme_txbusy, sc->dme_txready);
+ KASSERT(sc->dme_txbusy == 0 || sc->dme_txready == 0,
+ ("dme: send without empty queue\n"));
+
+ dme_prepare(sc);
+
+ if (sc->dme_txbusy == 0) {
+ /* We are ready to transmit right away */
+ dme_transmit(sc);
+ dme_prepare(sc); /* Prepare next one */
+ }
+ /*
+ * We need to wait until the current packet has
+ * been transmitted.
+ */
+ if (sc->dme_txready != 0)
+ ifp->if_drv_flags |= IFF_DRV_OACTIVE;
+}
+
+static void
+dme_start(struct ifnet *ifp)
+{
+ struct dme_softc *sc;
+
+ sc = ifp->if_softc;
+ DME_LOCK(sc);
+ dme_start_locked(ifp);
+ DME_UNLOCK(sc);
+}
+
+static void
+dme_stop(struct dme_softc *sc)
+{
+ struct ifnet *ifp;
+
+ DME_ASSERT_LOCKED(sc);
+ /* Disable receiver */
+ dme_write_reg(sc, DME_RCR, 0x00);
+ /* Mask interrupts */
+ dme_write_reg(sc, DME_IMR, 0x00);
+ /* Stop poll */
+ callout_stop(&sc->dme_tick_ch);
+
+ ifp = sc->dme_ifp;
+ ifp->if_drv_flags &= ~(IFF_DRV_RUNNING | IFF_DRV_OACTIVE);
+
+ DTR3("dme_stop, flags %#x busy %d ready %d",
+ sc->dme_ifp->if_drv_flags, sc->dme_txbusy, sc->dme_txready);
+ sc->dme_txbusy = 0;
+ sc->dme_txready = 0;
+}
+
+static int
+dme_rxeof(struct dme_softc *sc)
+{
+ struct ifnet *ifp;
+ struct mbuf *m;
+ int len, i;
+
+ DME_ASSERT_LOCKED(sc);
+
+ ifp = sc->dme_ifp;
+
+ /* Read the first byte to check it correct */
+ (void)dme_read_reg(sc, DME_MRCMDX);
+ i = bus_space_read_1(sc->dme_tag, sc->dme_handle, DATA_ADDR);
+ switch(bus_space_read_1(sc->dme_tag, sc->dme_handle, DATA_ADDR)) {
+ case 1:
+ /* Correct value */
+ break;
+ case 0:
+ return 1;
+ default:
+ /* Error */
+ return -1;
+ }
+
+ i = dme_read_reg(sc, DME_MRRL);
+ i |= dme_read_reg(sc, DME_MRRH) << 8;
+
+ len = dme_read_reg(sc, DME_ROCR);
+
+ bus_space_write_1(sc->dme_tag, sc->dme_handle, CMD_ADDR, DME_MRCMD);
+ len = 0;
+ switch(sc->dme_bits) {
+ case 8:
+ i = bus_space_read_1(sc->dme_tag, sc->dme_handle, DATA_ADDR);
+ i <<= 8;
+ i |= bus_space_read_1(sc->dme_tag, sc->dme_handle, DATA_ADDR);
+
+ len = bus_space_read_1(sc->dme_tag, sc->dme_handle, DATA_ADDR);
+ len |= bus_space_read_1(sc->dme_tag, sc->dme_handle,
+ DATA_ADDR) << 8;
+ break;
+ case 16:
+ bus_space_read_2(sc->dme_tag, sc->dme_handle, DATA_ADDR);
+ len = bus_space_read_2(sc->dme_tag, sc->dme_handle, DATA_ADDR);
+ break;
+ case 32:
+ {
+ uint32_t reg;
+
+ reg = bus_space_read_4(sc->dme_tag, sc->dme_handle, DATA_ADDR);
+ len = reg & 0xFFFF;
+ break;
+ }
+ }
+
+ MGETHDR(m, M_NOWAIT, MT_DATA);
+ if (m == NULL)
+ return -1;
+
+ if (len > MHLEN - ETHER_ALIGN) {
+ MCLGET(m, M_NOWAIT);
+ if (!(m->m_flags & M_EXT)) {
+ m_freem(m);
+ return -1;
+ }
+ }
+
+ m->m_pkthdr.rcvif = ifp;
+ m->m_len = m->m_pkthdr.len = len;
+ m_adj(m, ETHER_ALIGN);
+
+ /* Read the data */
+#if 0
+ bus_space_read_multi_2(sc->dme_tag, sc->dme_handle, DATA_ADDR,
+ mtod(m, uint16_t *), (len + 1) / 2);
+#else
+ bus_space_read_multi_1(sc->dme_tag, sc->dme_handle, DATA_ADDR,
+ mtod(m, uint8_t *), len);
+#endif
+ if_inc_counter(ifp, IFCOUNTER_IPACKETS, 1);
+ DME_UNLOCK(sc);
+ (*ifp->if_input)(ifp, m);
+ DME_LOCK(sc);
+
+ return 0;
+}
+
+static void
+dme_tick(void *arg)
+{
+ struct dme_softc *sc;
+ struct mii_data *mii;
+
+ sc = (struct dme_softc *)arg;
+
+ /* Probably too frequent? */
+ mii = device_get_softc(sc->dme_miibus);
+ mii_tick(mii);
+
+ callout_reset(&sc->dme_tick_ch, hz, dme_tick, sc);
+}
+
+static void
+dme_intr(void *arg)
+{
+ struct dme_softc *sc;
+ uint32_t intr_status;
+
+ sc = (struct dme_softc *)arg;
+
+ DME_LOCK(sc);
+
+ intr_status = dme_read_reg(sc, DME_ISR);
+ dme_write_reg(sc, DME_ISR, intr_status);
+
+ DTR4("dme_intr flags %#x busy %d ready %d intr %#x",
+ sc->dme_ifp->if_drv_flags, sc->dme_txbusy,
+ sc->dme_txready, intr_status);
+
+ if (intr_status & ISR_PT) {
+ uint8_t nsr, tx_status;
+
+ sc->dme_txbusy = 0;
+
+ nsr = dme_read_reg(sc, DME_NSR);
+
+ if (nsr & NSR_TX1END)
+ tx_status = dme_read_reg(sc, DME_TSR1);
+ else if (nsr & NSR_TX2END)
+ tx_status = dme_read_reg(sc, DME_TSR2);
+ else
+ tx_status = 1;
+
+ DTR4("dme_intr flags %#x busy %d ready %d nsr %#x",
+ sc->dme_ifp->if_drv_flags, sc->dme_txbusy,
+ sc->dme_txready, nsr);
+
+ /* Prepare packet to send if none is currently pending */
+ if (sc->dme_txready == 0)
+ dme_prepare(sc);
+ /* Send the packet out of one is waiting for transmit */
+ if (sc->dme_txready != 0) {
+ /* Initiate transmission of the prepared packet */
+ dme_transmit(sc);
+ /* Prepare next packet to send */
+ dme_prepare(sc);
+ /*
+ * We need to wait until the current packet has
+ * been transmitted.
+ */
+ if (sc->dme_txready != 0)
+ sc->dme_ifp->if_drv_flags |= IFF_DRV_OACTIVE;
+ }
+ }
+
+ if (intr_status & ISR_PR) {
+ /* Read the packets off the device */
+ while (dme_rxeof(sc) == 0)
+ continue;
+ }
+ DME_UNLOCK(sc);
+}
+
+static void
+dme_setmode(struct dme_softc *sc)
+{
+}
+
+static int
+dme_ioctl(struct ifnet *ifp, u_long command, caddr_t data)
+{
+ struct dme_softc *sc;
+ struct mii_data *mii;
+ struct ifreq *ifr;
+ int error = 0;
+
+ sc = ifp->if_softc;
+ ifr = (struct ifreq *)data;
+
+ switch (command) {
+ case SIOCSIFFLAGS:
+ /*
+ * Switch interface state between "running" and
+ * "stopped", reflecting the UP flag.
+ */
+ DME_LOCK(sc);
+ if (ifp->if_flags & IFF_UP) {
+ if ((ifp->if_drv_flags & IFF_DRV_RUNNING) == 0) {
+ dme_init_locked(sc);
+ }
+ } else {
+ if ((ifp->if_drv_flags & IFF_DRV_RUNNING) != 0) {
+ dme_stop(sc);
+ }
+ }
+ dme_setmode(sc);
+ DME_UNLOCK(sc);
+ break;
+ case SIOCGIFMEDIA:
+ case SIOCSIFMEDIA:
+ mii = device_get_softc(sc->dme_miibus);
+ error = ifmedia_ioctl(ifp, ifr, &mii->mii_media, command);
+ break;
+ default:
+ error = ether_ioctl(ifp, command, data);
+ break;
+ }
+ return (error);
+}
+
+static void dme_init_locked(struct dme_softc *sc)
+{
+ struct ifnet *ifp = sc->dme_ifp;
+
+ DME_ASSERT_LOCKED(sc);
+
+ if ((ifp->if_drv_flags & IFF_DRV_RUNNING) != 0)
+ return;
+
+ dme_reset(sc);
+ dme_config(sc);
+
+ ifp->if_drv_flags |= IFF_DRV_RUNNING;
+ ifp->if_drv_flags &= ~IFF_DRV_OACTIVE;
+
+ callout_reset(&sc->dme_tick_ch, hz, dme_tick, sc);
+}
+
+static void
+dme_init(void *xcs)
+{
+ struct dme_softc *sc = xcs;
+
+ DME_LOCK(sc);
+ dme_init_locked(sc);
+ DME_UNLOCK(sc);
+}
+
+static int
+dme_ifmedia_upd(struct ifnet *ifp)
+{
+ struct dme_softc *sc;
+ struct mii_data *mii;
+
+ sc = ifp->if_softc;
+ mii = device_get_softc(sc->dme_miibus);
+
+ DME_LOCK(sc);
+ mii_mediachg(mii);
+ DME_UNLOCK(sc);
+
+ return (0);
+}
+
+static void
+dme_ifmedia_sts(struct ifnet *ifp, struct ifmediareq *ifmr)
+{
+ struct dme_softc *sc;
+ struct mii_data *mii;
+
+ sc = ifp->if_softc;
+ mii = device_get_softc(sc->dme_miibus);
+
+ DME_LOCK(sc);
+ mii_pollstat(mii);
+ ifmr->ifm_active = mii->mii_media_active;
+ ifmr->ifm_status = mii->mii_media_status;
+ DME_UNLOCK(sc);
+}
+
+static struct ofw_compat_data compat_data[] = {
+ { "davicom,dm9000", true },
+ { NULL, false }
+};
+
+static int
+dme_probe(device_t dev)
+{
+ if (!ofw_bus_search_compatible(dev, compat_data)->ocd_data)
+ return (ENXIO);
+ device_set_desc(dev, "Davicom DM9000");
+ return (0);
+}
+
+static int
+dme_attach(device_t dev)
+{
+ struct dme_softc *sc;
+ struct ifnet *ifp;
+ int error, rid;
+ uint32_t data;
+
+ sc = device_get_softc(dev);
+ sc->dme_dev = dev;
+
+ error = 0;
+
+ mtx_init(&sc->dme_mtx, device_get_nameunit(dev), MTX_NETWORK_LOCK,
+ MTX_DEF);
+ callout_init_mtx(&sc->dme_tick_ch, &sc->dme_mtx, 0);
+
+ rid = 0;
+ sc->dme_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid,
+ RF_ACTIVE);
+ if (sc->dme_res == NULL) {
+ device_printf(dev, "unable to map memory\n");
+ error = ENXIO;
+ goto fail;
+ }
+
+ rid = 0;
+ sc->dme_irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid,
+ RF_ACTIVE);
+ if (sc->dme_irq == NULL) {
+ device_printf(dev, "unable to map memory\n");
+ error = ENXIO;
+ goto fail;
+ }
+ /*
+ * Power the chip up, if necessary
+ */
+ error = regulator_get_by_ofw_property(dev, 0, "vcc-supply", &sc->dme_vcc_regulator);
+ if (error == 0) {
+ error = regulator_enable(sc->dme_vcc_regulator);
+ if (error != 0) {
+ device_printf(dev, "unable to enable power supply\n");
+ error = ENXIO;
+ goto fail;
+ }
+ }
+
+ /*
+ * Delay a little. This seems required on rev-1 boards (green.)
+ */
+ DELAY(100000);
+
+ /* Bring controller out of reset */
+ error = ofw_gpiobus_parse_gpios(dev, "reset-gpios", &sc->gpio_rset);
+ if (error > 1) {
+ device_printf(dev, "too many reset gpios\n");
+ sc->gpio_rset = NULL;
+ error = ENXIO;
+ goto fail;
+ }
+
+ if (sc->gpio_rset != NULL) {
+ error = GPIO_PIN_SET(sc->gpio_rset->dev, sc->gpio_rset->pin, 0);
+ if (error != 0) {
+ device_printf(dev, "Cannot configure GPIO pin %d on %s\n",
+ sc->gpio_rset->pin, device_get_nameunit(sc->gpio_rset->dev));
+ goto fail;
+ }
+
+ error = GPIO_PIN_SETFLAGS(sc->gpio_rset->dev, sc->gpio_rset->pin,
+ GPIO_PIN_OUTPUT);
+ if (error != 0) {
+ device_printf(dev, "Cannot configure GPIO pin %d on %s\n",
+ sc->gpio_rset->pin, device_get_nameunit(sc->gpio_rset->dev));
+ goto fail;
+ }
+
+ DELAY(2000);
+
+ error = GPIO_PIN_SET(sc->gpio_rset->dev, sc->gpio_rset->pin, 1);
+ if (error != 0) {
+ device_printf(dev, "Cannot configure GPIO pin %d on %s\n",
+ sc->gpio_rset->pin, device_get_nameunit(sc->gpio_rset->dev));
+ goto fail;
+ }
+
+ DELAY(4000);
+ } else
+ device_printf(dev, "Unable to find reset GPIO\n");
+
+ sc->dme_tag = rman_get_bustag(sc->dme_res);
+ sc->dme_handle = rman_get_bushandle(sc->dme_res);
+
+ /* Reset the chip as soon as possible */
+ dme_reset(sc);
+
+ /* Figure IO mode */
+ switch((dme_read_reg(sc, DME_ISR) >> 6) & 0x03) {
+ case 0:
+ /* 16 bit */
+ sc->dme_bits = 16;
+ break;
+ case 1:
+ /* 32 bit */
+ sc->dme_bits = 32;
+ break;
+ case 2:
+ /* 8 bit */
+ sc->dme_bits = 8;
+ break;
+ default:
+ /* reserved */
+ device_printf(dev, "Unable to determine device mode\n");
+ error = ENXIO;
+ goto fail;
+ }
+
+ DELAY(100000);
+
+ /* Read vendor and device id's */
+ data = dme_read_reg(sc, DME_VIDH) << 8;
+ data |= dme_read_reg(sc, DME_VIDL);
+ device_printf(dev, "Vendor ID: 0x%04x\n", data);
+
+ /* Read vendor and device id's */
+ data = dme_read_reg(sc, DME_PIDH) << 8;
+ data |= dme_read_reg(sc, DME_PIDL);
+ device_printf(dev, "Product ID: 0x%04x\n", data);
+
+ /* Chip revision */
+ data = dme_read_reg(sc, DME_CHIPR);
+ device_printf(dev, "Revision: 0x%04x\n", data);
+ if (data != DME_CHIP_DM9000A && data != DME_CHIP_DM9000B)
+ data = DME_CHIP_DM9000;
+ sc->dme_rev = data;
+
+ device_printf(dev, "using %d-bit IO mode\n", sc->dme_bits);
+ KASSERT(sc->dme_bits == 8, ("wrong io mode"));
+
+ /* Try to figure our mac address */
+ dme_get_macaddr(sc);
+
+ /* Configure chip after reset */
+ dme_config(sc);
+
+ ifp = sc->dme_ifp = if_alloc(IFT_ETHER);
+ if (ifp == NULL) {
+ device_printf(dev, "unable to allocate ifp\n");
+ error = ENOSPC;
+ goto fail;
+ }
+ ifp->if_softc = sc;
+
+ /* Setup MII */
+ error = mii_attach(dev, &sc->dme_miibus, ifp, dme_ifmedia_upd,
+ dme_ifmedia_sts, BMSR_DEFCAPMASK, MII_PHY_ANY, MII_OFFSET_ANY, 0);
+ /* This should never happen as the DM9000 contains it's own PHY */
+ if (error != 0) {
+ device_printf(dev, "PHY probe failed\n");
+ goto fail;
+ }
+
+ if_initname(ifp, device_get_name(dev), device_get_unit(dev));
+ ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST;
+ ifp->if_start = dme_start;
+ ifp->if_ioctl = dme_ioctl;
+ ifp->if_init = dme_init;
+ IFQ_SET_MAXLEN(&ifp->if_snd, IFQ_MAXLEN);
+
+ ether_ifattach(ifp, sc->dme_macaddr);
+
+ error = bus_setup_intr(dev, sc->dme_irq, INTR_TYPE_NET | INTR_MPSAFE,
+ NULL, dme_intr, sc, &sc->dme_intrhand);
+ if (error) {
+ device_printf(dev, "couldn't set up irq\n");
+ ether_ifdetach(ifp);
+ goto fail;
+ }
+fail:
+ if (error != 0)
+ dme_detach(dev);
+ return (error);
+}
+
+static int
+dme_detach(device_t dev)
+{
+ struct dme_softc *sc;
+ struct ifnet *ifp;
+
+ sc = device_get_softc(dev);
+ KASSERT(mtx_initialized(&sc->dme_mtx), ("dme mutex not initialized"));
+
+ ifp = sc->dme_ifp;
+
+ if (device_is_attached(dev)) {
+ DME_LOCK(sc);
+ dme_stop(sc);
+ DME_UNLOCK(sc);
+ ether_ifdetach(ifp);
+ callout_drain(&sc->dme_tick_ch);
+ }
+
+ if (sc->dme_miibus)
+ device_delete_child(dev, sc->dme_miibus);
+ bus_generic_detach(dev);
+
+ if (sc->dme_vcc_regulator != 0)
+ regulator_release(sc->dme_vcc_regulator);
+ if (sc->dme_intrhand)
+ bus_teardown_intr(dev, sc->dme_irq, sc->dme_intrhand);
+ if (sc->dme_irq)
+ bus_release_resource(dev, SYS_RES_IRQ, 0, sc->dme_irq);
+ if (sc->dme_res)
+ bus_release_resource(dev, SYS_RES_MEMORY, 0, sc->dme_res);
+
+ if (ifp != NULL)
+ if_free(ifp);
+
+ mtx_destroy(&sc->dme_mtx);
+
+ return (0);
+}
+
+/*
+ * The MII bus interface
+ */
+static int
+dme_miibus_readreg(device_t dev, int phy, int reg)
+{
+ struct dme_softc *sc;
+ int i, rval;
+
+ /* We have up to 4 PHY's */
+ if (phy >= 4)
+ return (0);
+
+ sc = device_get_softc(dev);
+
+ /* Send the register to read to the phy and start the read */
+ dme_write_reg(sc, DME_EPAR, (phy << 6) | reg);
+ dme_write_reg(sc, DME_EPCR, EPCR_EPOS | EPCR_ERPRR);
+
+ /* Wait for the data to be read */
+ for (i = 0; i < DME_TIMEOUT; i++) {
+ if ((dme_read_reg(sc, DME_EPCR) & EPCR_ERRE) == 0)
+ break;
+ DELAY(1);
+ }
+
+ /* Clear the comand */
+ dme_write_reg(sc, DME_EPCR, 0);
+
+ if (i == DME_TIMEOUT)
+ return (0);
+
+ rval = (dme_read_reg(sc, DME_EPDRH) << 8) | dme_read_reg(sc, DME_EPDRL);
+ return (rval);
+}
+
+static int
+dme_miibus_writereg(device_t dev, int phy, int reg, int data)
+{
+ struct dme_softc *sc;
+ int i;
+
+ /* We have up to 4 PHY's */
+ if (phy > 3)
+ return (0);
+
+ sc = device_get_softc(dev);
+
+ /* Send the register and data to write to the phy */
+ dme_write_reg(sc, DME_EPAR, (phy << 6) | reg);
+ dme_write_reg(sc, DME_EPDRL, data & 0xFF);
+ dme_write_reg(sc, DME_EPDRH, (data >> 8) & 0xFF);
+ /* Start the write */
+ dme_write_reg(sc, DME_EPCR, EPCR_EPOS | EPCR_ERPRW);
+
+ /* Wait for the data to be written */
+ for (i = 0; i < DME_TIMEOUT; i++) {
+ if ((dme_read_reg(sc, DME_EPCR) & EPCR_ERRE) == 0)
+ break;
+ DELAY(1);
+ }
+
+ /* Clear the comand */
+ dme_write_reg(sc, DME_EPCR, 0);
+
+ return (0);
+}
+
+static device_method_t dme_methods[] = {
+ /* Device interface */
+ DEVMETHOD(device_probe, dme_probe),
+ DEVMETHOD(device_attach, dme_attach),
+ DEVMETHOD(device_detach, dme_detach),
+
+ /* bus interface, for miibus */
+ DEVMETHOD(bus_print_child, bus_generic_print_child),
+ DEVMETHOD(bus_driver_added, bus_generic_driver_added),
+
+ /* MII interface */
+ DEVMETHOD(miibus_readreg, dme_miibus_readreg),
+ DEVMETHOD(miibus_writereg, dme_miibus_writereg),
+
+ { 0, 0 }
+};
+
+static driver_t dme_driver = {
+ "dme",
+ dme_methods,
+ sizeof(struct dme_softc)
+};
+
+static devclass_t dme_devclass;
+
+MODULE_DEPEND(dme, ether, 1, 1, 1);
+MODULE_DEPEND(dme, miibus, 1, 1, 1);
+DRIVER_MODULE(dme, simplebus, dme_driver, dme_devclass, 0, 0);
+DRIVER_MODULE(miibus, dme, miibus_driver, miibus_devclass, 0, 0);
+
diff --git a/sys/dev/dme/if_dmereg.h b/sys/dev/dme/if_dmereg.h
new file mode 100644
index 000000000000..9a5456f7d674
--- /dev/null
+++ b/sys/dev/dme/if_dmereg.h
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2010 Andrew Turner
+ * 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 AND CONTRIBUTORS ``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 CONTRIBUTORS 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.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef __IF_DMEREG_H__
+#define __IF_DMEREG_H__
+
+/*
+ * DM9000 register definitions
+ */
+#define DME_NCR 0x00
+#define NCR_EXT_PHY (1<<7)
+#define NCR_WAKEEN (1<<6)
+#define NCR_FCOL (1<<4)
+#define NCR_FDX (1<<3)
+#define NCR_LBK_NORMAL (0<<1)
+#define NCR_LBK_MAC (1<<1)
+#define NCR_LBK_PHY (2<<1)
+#define NCR_RST (1<<0)
+#define DME_NSR 0x01
+#define NSR_SPEED (1<<7)
+#define NSR_LINKST (1<<6)
+#define NSR_WAKEST (1<<5)
+#define NSR_TX2END (1<<3)
+#define NSR_TX1END (1<<2)
+#define NSR_RXOV (1<<1)
+#define DME_TCR 0x02
+#define TCR_TJDIS (1<<6)
+#define TCR_EXCECM (1<<5)
+#define TCR_PAD_DIS2 (1<<4)
+#define TCR_PAD_CRC2 (1<<3)
+#define TCR_PAD_DIS1 (1<<2)
+#define TCR_PAD_CRC1 (1<<1)
+#define TCR_TXREQ (1<<0)
+#define DME_TSR1 0x03
+#define DME_TSR2 0x04
+#define DME_RCR 0x05
+#define RCR_WTDIS (1<<6)
+#define RCR_DIS_LONG (1<<5)
+#define RCR_DIS_CRC (1<<4)
+#define RCR_ALL (1<<3)
+#define RCR_RUNT (1<<2)
+#define RCR_PRMSC (1<<1)
+#define RCR_RXEN (1<<0)
+#define DME_RSR 0x06
+#define DME_ROCR 0x07
+#define DME_BPTR 0x08
+#define BPTR_BPHW(v) (((v) & 0x0f) << 4)
+#define BPTR_JPT(v) (((v) & 0x0f) << 0)
+#define DME_FCTR 0x09
+#define FCTR_HWOT(v) (((v) & 0x0f) << 4)
+#define FCTR_LWOT(v) (((v) & 0x0f) << 0)
+#define DME_FCR 0x0A
+#define DME_EPCR 0x0B
+#define EPCR_REEP (1<<5)
+#define EPCR_WEP (1<<4)
+#define EPCR_EPOS (1<<3)
+#define EPCR_ERPRR (1<<2)
+#define EPCR_ERPRW (1<<1)
+#define EPCR_ERRE (1<<0)
+#define DME_EPAR 0x0C
+#define DME_EPDRL 0x0D
+#define DME_EPDRH 0x0E
+#define DME_WCR 0x0F
+#define DME_PAR_BASE 0x10
+#define DME_PAR(n) (DME_PAR_BASE + n)
+#define DME_MAR_BASE 0x16
+#define DME_MAR(n) (DME_MAR_BASE + n)
+#define DME_GPCR 0x1E
+#define DME_GPR 0x1F
+#define DME_TRPAL 0x22
+#define DME_TRPAH 0x23
+#define DME_RWPAL 0x24
+#define DME_RWPAH 0x25
+#define DME_VIDL 0x28
+#define DME_VIDH 0x29
+#define DME_PIDL 0x2A
+#define DME_PIDH 0x2B
+#define DME_CHIPR 0x2C
+#define DME_SMCR 0x2F
+#define DME_MRCMDX 0xF0
+#define DME_MRCMD 0xF2
+#define DME_MRRL 0xF4
+#define DME_MRRH 0xF5
+#define DME_MWCMDX 0xF6
+#define DME_MWCMD 0xF8
+#define DME_MWRL 0xFA
+#define DME_MWRH 0xFB
+#define DME_TXPLL 0xFC
+#define DME_TXPLH 0xFD
+#define DME_ISR 0xFE
+#define ISR_LNKCHG (1<<5)
+#define ISR_UDRUN (1<<4)
+#define ISR_ROO (1<<3)
+#define ISR_ROS (1<<2)
+#define ISR_PT (1<<1)
+#define ISR_PR (1<<0)
+
+#define DME_IMR 0xFF
+#define IMR_PAR (1<<7)
+#define IMR_LNKCHGI (1<<5)
+#define IMR_UDRUNI (1<<4)
+#define IMR_ROOI (1<<3)
+#define IMR_ROI (1<<2)
+#define IMR_PTI (1<<1)
+#define IMR_PRI (1<<0)
+
+/* Extra PHY register from DM9000B */
+#define MII_DME_DSPCR 0x1B
+#define DSPCR_INIT 0xE100
+
+#endif /* __DMEREGS_H__ */
+
diff --git a/sys/dev/dme/if_dmevar.h b/sys/dev/dme/if_dmevar.h
new file mode 100644
index 000000000000..49fdcac4549b
--- /dev/null
+++ b/sys/dev/dme/if_dmevar.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2010 Andrew Turner
+ * 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 AND CONTRIBUTORS ``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 CONTRIBUTORS 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.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef __IF_DMEVAR_H__
+#define __IF_DMEVAR_H__
+
+#define DME_LOCK(cs) mtx_lock(&(sc)->dme_mtx)
+#define DME_UNLOCK(cs) mtx_unlock(&(sc)->dme_mtx)
+#define DME_ASSERT_LOCKED(sc) mtx_assert(&(sc)->dme_mtx, MA_OWNED);
+
+#define DME_TIMEOUT 1000
+
+#endif /* __IF_DMEVAR_H__ */
+