root/sys/dev/dpaa/if_memac.c
/*
 * Copyright (c) 2026 Justin Hibbits
 *
 * SPDX-License-Identifier: BSD-2-Clause
 */

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/bus.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/mbuf.h>
#include <sys/module.h>
#include <sys/rman.h>
#include <sys/socket.h>
#include <sys/sysctl.h>
#include <sys/sockio.h>

#include <machine/bus.h>
#include <machine/resource.h>

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

#include <dev/mii/mii.h>
#include <dev/mii/miivar.h>
#include <dev/ofw/ofw_bus.h>
#include <dev/ofw/ofw_bus_subr.h>
#include <dev/ofw/openfirm.h>

#include "miibus_if.h"

#include "dpaa_eth.h"
#include "fman.h"
#include "fman_port.h"
#include "if_memac.h"

#include "fman_if.h"
#include "fman_port_if.h"

#define MEMAC_MIN_FRAME_SIZE    64
#define MEMAC_MAX_FRAME_SIZE    32736

#define MEMAC_COMMAND_CONFIG            0x008
#define   COMMAND_CONFIG_RXSTP            0x20000000
#define   COMMAND_CONFIG_NO_LEN_CHK       0x00020000
#define   COMMAND_CONFIG_SWR              0x00001000
#define   COMMAND_CONFIG_TXP              0x00000800
#define   COMMAND_CONFIG_CRC              0x00000040
#define   COMMAND_CONFIG_PROMISC          0x00000010
#define   COMMAND_CONFIG_RX_EN            0x00000002
#define   COMMAND_CONFIG_TX_EN            0x00000001
#define MEMAC_MAC_ADDR_0                0x00c
#define MEMAC_MAC_ADDR_1                0x010
#define MEMAC_REG_MAXFRM                0x14
#define MEMAC_REG_TX_FIFO_SECTIONS      0x020
#define   TX_FIFO_SECTIONS_TX_EMPTY_M     0xffff0000
#define   TX_FIFO_SECTIONS_TX_EMPTY_S     16
#define   TX_FIFO_SECTIONS_TX_AVAIL_M     0x0000ffff

#define HASHTABLE_CTRL          0x02c
#define   CTRL_MCAST              0x00000100
#define   CTRL_HASH_ADDR_M        0x0000003f
#define   HASHTABLE_SIZE          64
#define MEMAC_IEVENT            0x040
#define   IEVENT_RX_EMPTY         0x00000040
#define   IEVENT_TX_EMPTY         0x00000020
#define MEMAC_CL01_PAUSE_QUANTA 0x054
#define MEMAC_IF_MODE           0x300
#define   IF_MODE_ENA             0x00008000
#define   IF_MODE_SSP_M           0x00006000
#define   IF_MODE_SSP_100MB       0x00000000
#define   IF_MODE_SSP_10MB        0x00002000
#define   IF_MODE_SSP_1GB         0x00004000
#define   IF_MODE_SFD             0x00001000
#define   IF_MODE_MSG             0x00000200
#define   IF_MODE_HG              0x00000100
#define   IF_MODE_HD              0x00000040
#define   IF_MODE_RLP             0x00000020
#define   IF_MODE_RG              0x00000004
#define   IF_MODE_IFMODE_M        0x00000003
#define   IF_MODE_IFMODE_XGMII    0x00000000
#define   IF_MODE_IFMODE_MII      0x00000001
#define   IF_MODE_IFMODE_GMII     0x00000002

#define DEFAULT_PAUSE_QUANTA    0xf000

#define DPAA_CSUM_TX_OFFLOAD    (CSUM_IP | CSUM_DELAY_DATA | CSUM_DELAY_DATA_IPV6)


/**
 * @group FMan MAC routines.
 * @{
 */
#define MEMAC_MAC_EXCEPTIONS_END        (-1)

static void memac_if_init_locked(struct memac_softc *sc);

static int
memac_fm_mac_init(struct memac_softc *sc, uint8_t *mac)
{
        uint32_t reg;

        FMAN_GET_REVISION(device_get_parent(sc->sc_base.sc_dev), &sc->sc_base.sc_rev_major,
            &sc->sc_base.sc_rev_minor);

        if (FMAN_RESET_MAC(device_get_parent(sc->sc_base.sc_dev), sc->sc_base.sc_eth_id) != 0)
                return (ENXIO);

        reg = bus_read_4(sc->sc_base.sc_mem, MEMAC_COMMAND_CONFIG);
        reg |= COMMAND_CONFIG_SWR;
        bus_write_4(sc->sc_base.sc_mem, MEMAC_COMMAND_CONFIG, reg);

        while (bus_read_4(sc->sc_base.sc_mem, MEMAC_COMMAND_CONFIG) & COMMAND_CONFIG_SWR)
                ;

        /* TODO: TX_FIFO_SECTIONS */
        /* TODO: CL01 pause quantum */
        bus_write_4(sc->sc_base.sc_mem, MEMAC_COMMAND_CONFIG,
            COMMAND_CONFIG_NO_LEN_CHK | COMMAND_CONFIG_TXP | COMMAND_CONFIG_CRC);

        reg = bus_read_4(sc->sc_base.sc_mem, MEMAC_IF_MODE);
        reg &= ~(IF_MODE_IFMODE_M | IF_MODE_RG);
        switch (sc->sc_base.sc_mac_enet_mode) {
        case MII_CONTYPE_RGMII:
                reg |= IF_MODE_RG;
                /* FALLTHROUGH */
        case MII_CONTYPE_GMII:
        case MII_CONTYPE_SGMII:
        case MII_CONTYPE_QSGMII:
                reg |= IF_MODE_IFMODE_GMII;
                break;
        case MII_CONTYPE_RMII:
                reg |= IF_MODE_RG;
                /* FALLTHROUGH */
        case MII_CONTYPE_MII:
                reg |= IF_MODE_IFMODE_MII;
                break;
        }

        bus_write_4(sc->sc_base.sc_mem, MEMAC_IF_MODE, reg);

        return (0);
}
/** @} */


/**
 * @group IFnet routines.
 * @{
 */
static int
memac_set_mtu(struct memac_softc *sc, unsigned int mtu)
{

        mtu += ETHER_HDR_LEN + ETHER_VLAN_ENCAP_LEN + ETHER_CRC_LEN;

        MEMAC_LOCK_ASSERT(sc);

        if (mtu >= MEMAC_MIN_FRAME_SIZE && mtu <= MEMAC_MAX_FRAME_SIZE) {
                bus_write_4(sc->sc_base.sc_mem, MEMAC_REG_MAXFRM, mtu);
                return (mtu);
        }

        return (0);
}

static u_int
memac_hash_maddr(void *arg, struct sockaddr_dl *sdl, u_int cnt)
{
        struct memac_softc *sc = arg;
        uint8_t *addr = LLADDR(sdl);
        uint32_t hash = 0;
        uint8_t a, h;

        /* Hash is 6 bits, composed if [XOR{47:40},XOR{39:32},....] */
        for (int i = 0; i < 6; i++) {
                a = addr[i];
                h = 0;
                for (int j = 0; j < 8; j++, a >>= 1) {
                        h ^= (a & 0x1);
                }
                hash |= (h << i);
        }
        bus_write_4(sc->sc_base.sc_mem, HASHTABLE_CTRL, hash | CTRL_MCAST);

        return (1);
}

static void
memac_setup_multicast(struct memac_softc *sc)
{

        if (if_getflags(sc->sc_base.sc_ifnet) & IFF_ALLMULTI) {
                for (int i = 0; i < HASHTABLE_SIZE; i++)
                        bus_write_4(sc->sc_base.sc_mem,
                            HASHTABLE_CTRL, CTRL_MCAST | i);
        } else {
                /* Clear the hash table */
                for (int i = 0; i < HASHTABLE_SIZE; i++)
                        bus_write_4(sc->sc_base.sc_mem,
                            HASHTABLE_CTRL, i);
        }

        if_foreach_llmaddr(sc->sc_base.sc_ifnet, memac_hash_maddr, sc);
}

static void
memac_setup_promisc(struct memac_softc *sc)
{
        uint32_t reg;

        reg = bus_read_4(sc->sc_base.sc_mem, MEMAC_COMMAND_CONFIG);
        reg &= ~COMMAND_CONFIG_PROMISC;

        if ((if_getflags(sc->sc_base.sc_ifnet) & IFF_PROMISC) != 0)
                bus_write_4(sc->sc_base.sc_mem, MEMAC_COMMAND_CONFIG,
                    reg | COMMAND_CONFIG_PROMISC);
}

static void
memac_if_graceful_stop(struct memac_softc *sc)
{
        struct resource *regs = sc->sc_base.sc_mem;
        uint32_t reg;

        reg = bus_read_4(regs, MEMAC_COMMAND_CONFIG);
        reg |= COMMAND_CONFIG_RXSTP;

        bus_write_4(regs, MEMAC_COMMAND_CONFIG, reg);
        while ((bus_read_4(regs, MEMAC_IEVENT) & IEVENT_RX_EMPTY) == 0)
                ;
        reg &= COMMAND_CONFIG_RX_EN;
        bus_write_4(regs, MEMAC_COMMAND_CONFIG, reg);

        while ((bus_read_4(regs, MEMAC_IEVENT) & IEVENT_TX_EMPTY) == 0)
                ;
        bus_write_4(regs, MEMAC_COMMAND_CONFIG, reg & ~COMMAND_CONFIG_TX_EN);
}

static void
memac_mac_enable(struct memac_softc *sc)
{
        uint32_t reg = bus_read_4(sc->sc_base.sc_mem, MEMAC_COMMAND_CONFIG);

        reg |= (COMMAND_CONFIG_RX_EN | COMMAND_CONFIG_TX_EN);

        bus_write_4(sc->sc_base.sc_mem, MEMAC_COMMAND_CONFIG, reg);
}

static int
memac_if_enable_locked(struct memac_softc *sc)
{
        int error;

        MEMAC_LOCK_ASSERT(sc);

        memac_set_mtu(sc, if_getmtu(sc->sc_base.sc_ifnet));
        memac_mac_enable(sc);

        error = FMAN_PORT_ENABLE(sc->sc_base.sc_rx_port);
        if (error != 0)
                return (EIO);

        error = FMAN_PORT_ENABLE(sc->sc_base.sc_tx_port);
        if (error != 0)
                return (EIO);

        bus_write_4(sc->sc_base.sc_mem, MEMAC_IEVENT, 0);
        memac_setup_multicast(sc);
        memac_setup_promisc(sc);

        if_setdrvflagbits(sc->sc_base.sc_ifnet, IFF_DRV_RUNNING, 0);

        /* Refresh link state */
        memac_miibus_statchg(sc->sc_base.sc_dev);

        return (0);
}

static int
memac_if_disable_locked(struct memac_softc *sc)
{
        int error;

        MEMAC_LOCK_ASSERT(sc);

        error = FMAN_PORT_DISABLE(sc->sc_base.sc_tx_port);
        if (error != 0)
                return (EIO);

        memac_if_graceful_stop(sc);

        error = FMAN_PORT_DISABLE(sc->sc_base.sc_rx_port);
        if (error != 0)
                return (EIO);

        if_setdrvflagbits(sc->sc_base.sc_ifnet, 0, IFF_DRV_RUNNING);

        return (0);
}

static int
memac_if_ioctl(if_t ifp, u_long command, caddr_t data)
{
        struct memac_softc *sc;
        struct ifreq *ifr;
        uint32_t changed;
        int error;

        sc = if_getsoftc(ifp);
        ifr = (struct ifreq *)data;
        error = 0;

        /* Basic functionality to achieve media status reports */
        switch (command) {
        case SIOCSIFMTU:
                MEMAC_LOCK(sc);
                if (memac_set_mtu(sc, ifr->ifr_mtu))
                        if_setmtu(ifp, ifr->ifr_mtu);
                else
                        error = EINVAL;
                MEMAC_UNLOCK(sc);
                break;
        case SIOCSIFFLAGS:
                MEMAC_LOCK(sc);
                if (if_getflags(ifp) & IFF_UP) {
                        if (!(if_getdrvflags(ifp) & IFF_DRV_RUNNING))
                                memac_if_init_locked(sc);
                } else if (if_getdrvflags(ifp) & IFF_DRV_RUNNING)
                        error = memac_if_disable_locked(sc);

                MEMAC_UNLOCK(sc);
                break;

        case SIOCADDMULTI:
        case SIOCDELMULTI:
                if (if_getflags(sc->sc_base.sc_ifnet) & IFF_UP) {
                        MEMAC_LOCK(sc);
                        memac_setup_multicast(sc);
                        MEMAC_UNLOCK(sc);
                }
                break;

        case SIOCSIFCAP:
                changed = if_getcapenable(ifp) ^ ifr->ifr_reqcap;
                if ((changed & (IFCAP_RXCSUM | IFCAP_RXCSUM_IPV6)) != 0)
                        if_togglecapenable(ifp,
                            IFCAP_RXCSUM | IFCAP_RXCSUM_IPV6);
                if ((changed & (IFCAP_TXCSUM | IFCAP_TXCSUM_IPV6)) != 0) {
                        if_togglecapenable(ifp,
                            IFCAP_TXCSUM | IFCAP_TXCSUM_IPV6);
                        if_togglehwassist(ifp, DPAA_CSUM_TX_OFFLOAD);
                }
                break;

        case SIOCGIFMEDIA:
        case SIOCSIFMEDIA:
                error = ifmedia_ioctl(ifp, ifr, &sc->sc_base.sc_mii->mii_media,
                    command);
                break;

        default:
                error = ether_ioctl(ifp, command, data);
        }

        return (error);
}

static void
memac_if_tick(void *arg)
{
        struct memac_softc *sc;

        sc = arg;

        /* TODO */
        MEMAC_LOCK(sc);

        mii_tick(sc->sc_base.sc_mii);
        callout_reset(&sc->sc_base.sc_tick_callout, hz, memac_if_tick, sc);

        MEMAC_UNLOCK(sc);
}

static void
memac_if_deinit_locked(struct memac_softc *sc)
{

        MEMAC_LOCK_ASSERT(sc);

        MEMAC_UNLOCK(sc);
        callout_drain(&sc->sc_base.sc_tick_callout);
        MEMAC_LOCK(sc);
}

static void
memac_if_set_macaddr(struct memac_softc *sc, const char *addr)
{
        uint32_t reg;

        reg = (addr[3] << 24) | (addr[2] << 16) | (addr[1] << 8) | addr[0];
        bus_write_4(sc->sc_base.sc_mem, MEMAC_MAC_ADDR_0, reg);
        reg = (addr[5] << 8) | (addr[4]);
        bus_write_4(sc->sc_base.sc_mem, MEMAC_MAC_ADDR_1, reg);
}

static void
memac_if_init_locked(struct memac_softc *sc)
{
        int error;
        const char *macaddr;

        MEMAC_LOCK_ASSERT(sc);

        macaddr = if_getlladdr(sc->sc_base.sc_ifnet);
        memac_if_set_macaddr(sc, macaddr);

        /* Start MII polling */
        if (sc->sc_base.sc_mii)
                callout_reset(&sc->sc_base.sc_tick_callout, hz,
                    memac_if_tick, sc);

        if (if_getflags(sc->sc_base.sc_ifnet) & IFF_UP) {
                error = memac_if_enable_locked(sc);
                if (error != 0)
                        goto err;
        } else {
                error = memac_if_disable_locked(sc);
                if (error != 0)
                        goto err;
        }

        if_link_state_change(sc->sc_base.sc_ifnet, LINK_STATE_UP);

        bus_write_4(sc->sc_base.sc_mem, MEMAC_CL01_PAUSE_QUANTA,
            DEFAULT_PAUSE_QUANTA);

        return;

err:
        memac_if_deinit_locked(sc);
        device_printf(sc->sc_base.sc_dev, "initialization error.\n");
        return;
}

static void
memac_if_init(void *data)
{
        struct memac_softc *sc;

        sc = data;

        MEMAC_LOCK(sc);
        memac_if_init_locked(sc);
        MEMAC_UNLOCK(sc);
}

static void
memac_if_start(if_t ifp)
{
        struct memac_softc *sc;

        sc = if_getsoftc(ifp);
        MEMAC_LOCK(sc);
        dpaa_eth_if_start_locked(&sc->sc_base);
        MEMAC_UNLOCK(sc);
}

static void
memac_if_watchdog(if_t ifp)
{
        /* TODO */
}
/** @} */


/**
 * @group IFmedia routines.
 * @{
 */
static int
memac_ifmedia_upd(if_t ifp)
{
        struct memac_softc *sc = if_getsoftc(ifp);

        return (0);
        MEMAC_LOCK(sc);
        mii_mediachg(sc->sc_base.sc_mii);
        MEMAC_UNLOCK(sc);

        return (0);
}

static void
memac_ifmedia_fixed_sts(if_t ifp, struct ifmediareq *ifmr)
{
        struct memac_softc *sc = if_getsoftc(ifp);

        MEMAC_LOCK(sc);
        ifmr->ifm_count = 1;
        ifmr->ifm_mask = 0;
        ifmr->ifm_status = IFM_AVALID | IFM_ACTIVE;
        ifmr->ifm_current = ifmr->ifm_active =
            sc->sc_base.sc_mii->mii_media.ifm_cur->ifm_media;
        ifmr->ifm_active = ifmr->ifm_current;

        /*
         * In non-PHY usecases, we need to signal link state up, otherwise
         * certain things requiring a link event (e.g async DHCP client) from
         * devd do not happen.
         */
        if (if_getlinkstate(ifp) == LINK_STATE_UNKNOWN) {
                if_link_state_change(ifp, LINK_STATE_UP);
        }

        /* We assume the link is static, as in a peer switch. */

        MEMAC_UNLOCK(sc);

        return;
}

static void
memac_ifmedia_sts(if_t ifp, struct ifmediareq *ifmr)
{
        struct memac_softc *sc = if_getsoftc(ifp);

        MEMAC_LOCK(sc);

        mii_pollstat(sc->sc_base.sc_mii);

        ifmr->ifm_active = sc->sc_base.sc_mii->mii_media_active;
        ifmr->ifm_status = sc->sc_base.sc_mii->mii_media_status;

        MEMAC_UNLOCK(sc);
}
/** @} */


/**
 * @group dTSEC bus interface.
 * @{
 */

int
memac_attach(device_t dev)
{
        struct memac_softc *sc;
        int error;
        if_t ifp;

        sc = device_get_softc(dev);

        sc->sc_base.sc_dev = dev;

        /* Init locks */
        mtx_init(&sc->sc_base.sc_lock, device_get_nameunit(dev),
            "mEMAC Global Lock", MTX_DEF);

        mtx_init(&sc->sc_base.sc_mii_lock, device_get_nameunit(dev),
            "mEMAC MII Lock", MTX_DEF);

        /* Init callouts */
        callout_init(&sc->sc_base.sc_tick_callout, CALLOUT_MPSAFE);

        /* Create RX buffer pool */
        error = dpaa_eth_pool_rx_init(&sc->sc_base);
        if (error != 0)
                return (EIO);

        /* Create RX frame queue range */
        error = dpaa_eth_fq_rx_init(&sc->sc_base);
        if (error != 0)
                return (EIO);

        /* Create frame info pool */
        error = dpaa_eth_fi_pool_init(&sc->sc_base);
        if (error != 0)
                return (EIO);

        /* Create TX frame queue range */
        error = dpaa_eth_fq_tx_init(&sc->sc_base);
        if (error != 0)
                return (EIO);

        /* Init FMan MAC module. */
        error = memac_fm_mac_init(sc, sc->sc_base.sc_mac_addr);
        if (error != 0) {
                memac_detach(dev);
                return (ENXIO);
        }

        dpaa_eth_fm_port_rx_init(&sc->sc_base);
        dpaa_eth_fm_port_tx_init(&sc->sc_base);

        /* Create network interface for upper layers */
        ifp = sc->sc_base.sc_ifnet = if_alloc(IFT_ETHER);
        if_setsoftc(ifp, sc);

        if_setflags(ifp, IFF_SIMPLEX | IFF_BROADCAST | IFF_MULTICAST);
        if_setinitfn(ifp, memac_if_init);
        if_setstartfn(ifp, memac_if_start);
        if_setioctlfn(ifp, memac_if_ioctl);
        if_setsendqlen(ifp, IFQ_MAXLEN);
        if_setsendqready(ifp);

        if (sc->sc_base.sc_phy_addr >= 0)
                if_initname(ifp, device_get_name(sc->sc_base.sc_dev),
                    device_get_unit(sc->sc_base.sc_dev));
        else
                if_initname(ifp, "memac_phy",
                    device_get_unit(sc->sc_base.sc_dev));


        if_setcapabilities(ifp, IFCAP_JUMBO_MTU |
            IFCAP_VLAN_MTU | IFCAP_VLAN_HWCSUM |
            IFCAP_RXCSUM | IFCAP_RXCSUM_IPV6 |
            IFCAP_TXCSUM | IFCAP_TXCSUM_IPV6);
        if_setcapenable(ifp, if_getcapabilities(ifp));
        if_sethwassist(ifp, DPAA_CSUM_TX_OFFLOAD);

        /* Attach PHY(s) */
        if (!sc->sc_fixed_link) {
                error = mii_attach(sc->sc_base.sc_dev, &sc->sc_base.sc_mii_dev,
                    ifp, memac_ifmedia_upd, memac_ifmedia_sts, BMSR_DEFCAPMASK,
                    sc->sc_base.sc_phy_addr, MII_OFFSET_ANY, 0);
                if (error) {
                        device_printf(sc->sc_base.sc_dev,
                            "attaching PHYs failed: %d\n", error);
                        memac_detach(sc->sc_base.sc_dev);
                        return (error);
                }
                sc->sc_base.sc_mii = device_get_softc(sc->sc_base.sc_mii_dev);
        } else {
                phandle_t node;
                uint32_t type = IFM_ETHER;
                uint32_t speed;

                node = ofw_bus_find_child(ofw_bus_get_node(dev), "fixed-link");
                if (OF_getencprop(node, "speed", &speed, sizeof(speed)) <= 0) {
                        device_printf(dev,
                            "fixed link has no speed property\n");
                        memac_detach(sc->sc_base.sc_dev);
                        return (ENXIO);
                }
                switch (speed) {
                case 10:
                        type |= IFM_10_T;
                        break;
                case 100:
                        type |= IFM_100_TX;
                        break;
                case 1000:
                        type |= IFM_1000_T;
                        break;
                case 2500:
                        type |= IFM_2500_T;
                        break;
                case 5000:
                        type |= IFM_5000_T;
                        break;
                case 10000:
                        type |= IFM_10G_T;
                        break;
                }
                if (OF_hasprop(node, "full-duplex"))
                        type |= IFM_FDX;
                sc->sc_base.sc_mii = malloc(sizeof(*sc->sc_base.sc_mii),
                    M_DEVBUF, M_WAITOK | M_ZERO);
                ifmedia_init(&sc->sc_base.sc_mii->mii_media, 0,
                    memac_ifmedia_upd, memac_ifmedia_fixed_sts);
                ifmedia_add(&sc->sc_base.sc_mii->mii_media, type, 0, NULL);
                ifmedia_set(&sc->sc_base.sc_mii->mii_media, type);
        }

        /* Attach to stack */
        ether_ifattach(ifp, sc->sc_base.sc_mac_addr);

        return (0);
}

int
memac_detach(device_t dev)
{
        struct memac_softc *sc;
        if_t ifp;

        sc = device_get_softc(dev);
        ifp = sc->sc_base.sc_ifnet;

        if (device_is_attached(dev)) {
                ether_ifdetach(ifp);
                /* Shutdown interface */
                MEMAC_LOCK(sc);
                memac_if_deinit_locked(sc);
                MEMAC_UNLOCK(sc);
        }

        if (sc->sc_base.sc_ifnet) {
                if_free(sc->sc_base.sc_ifnet);
                sc->sc_base.sc_ifnet = NULL;
        }

        /* Free RX/TX FQRs */
        dpaa_eth_fq_rx_free(&sc->sc_base);
        dpaa_eth_fq_tx_free(&sc->sc_base);

        /* Free frame info pool */
        dpaa_eth_fi_pool_free(&sc->sc_base);

        /* Free RX buffer pool */
        dpaa_eth_pool_rx_free(&sc->sc_base);

        /* Destroy lock */
        mtx_destroy(&sc->sc_base.sc_lock);

        return (0);
}

int
memac_suspend(device_t dev)
{

        return (0);
}

int
memac_resume(device_t dev)
{

        return (0);
}

int
memac_shutdown(device_t dev)
{

        return (0);
}
/** @} */


/**
 * @group MII bus interface.
 * @{
 */
int
memac_miibus_readreg(device_t dev, int phy, int reg)
{
        struct memac_softc *sc;

        sc = device_get_softc(dev);

        return (MIIBUS_READREG(sc->sc_base.sc_mdio, phy, reg));
}

int
memac_miibus_writereg(device_t dev, int phy, int reg, int value)
{

        struct memac_softc *sc;

        sc = device_get_softc(dev);

        return (MIIBUS_WRITEREG(sc->sc_base.sc_mdio, phy, reg, value));
}

void
memac_miibus_statchg(device_t dev)
{
        struct memac_softc *sc;
        uint32_t reg;
        bool duplex;
        int speed;

        sc = device_get_softc(dev);

        MEMAC_LOCK_ASSERT(sc);

        duplex = ((sc->sc_base.sc_mii->mii_media_active & IFM_GMASK) == IFM_FDX);

        switch (IFM_SUBTYPE(sc->sc_base.sc_mii->mii_media_active)) {
        case IFM_AUTO:
                speed = IF_MODE_ENA;
                break;
        case IFM_1000_T:
        case IFM_1000_SX:
                if (!duplex) {
                        device_printf(sc->sc_base.sc_dev,
                            "Only full-duplex supported for 1Gbps speeds");
                        return;
                }
                speed = IF_MODE_SSP_1GB;
                break;

        case IFM_100_TX:
                speed = IF_MODE_SSP_100MB;
                break;
        default:
                speed = IF_MODE_SSP_10MB;
                break;
        }

        reg = bus_read_4(sc->sc_base.sc_mem, MEMAC_IF_MODE);
        reg &= ~(IF_MODE_ENA | IF_MODE_SSP_M | IF_MODE_SFD);
        reg |= 0x2;

        if (duplex)
                reg |= IF_MODE_SFD;
        else
                reg |= IF_MODE_HD;
        reg |= speed;
        bus_write_4(sc->sc_base.sc_mem, MEMAC_IF_MODE, reg);
}
/** @} */