#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/bus.h>
#include <sys/rman.h>
#include <sys/malloc.h>
#include <sys/mbuf.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_dtsec.h"
#include "fman_if.h"
#include "fman_port_if.h"
#define DTSEC_MIN_FRAME_SIZE 64
#define DTSEC_MAX_FRAME_SIZE 9600
#define DTSEC_REG_MAXFRM 0x110
#define DTSEC_REG_IGADDR(i) (0x080 + 4 * (i))
#define DTSEC_REG_GADDR(i) (0x0a0 + 4 * (i))
#define DTSEC_ECNTRL 0x014
#define ECNTRL_R100M 0x00000008
#define DTSEC_TCTRL 0x040
#define TCTRL_GTS 0x00000020
#define DTSEC_RCTRL 0x050
#define RCTRL_CFA 0x00008000
#define RCTRL_GHTX 0x00000400
#define RCTRL_GRS 0x00000020
#define RCTRL_MPROM 0x00000008
#define DTSEC_MACCFG1 0x100
#define DTSEC_MACCFG2 0x104
#define MACCFG_IF_M 0x00000300
#define MACCFG_IF_10_100 0x00000100
#define MACCFG_IF_1G 0x00000200
#define MACCFG_FULLDUPLEX 0x00000001
#define DTSEC_MACSTNADDR1 0x140
#define DTSEC_MACSTNADDR2 0x144
static void dtsec_if_init_locked(struct dtsec_softc *sc);
static int
dtsec_fm_mac_init(struct dtsec_softc *sc, uint8_t *mac)
{
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);
return (0);
}
static int
dtsec_set_mtu(struct dtsec_softc *sc, unsigned int mtu)
{
mtu += ETHER_HDR_LEN + ETHER_VLAN_ENCAP_LEN + ETHER_CRC_LEN;
DTSEC_LOCK_ASSERT(sc);
if (mtu >= DTSEC_MIN_FRAME_SIZE && mtu <= DTSEC_MAX_FRAME_SIZE) {
bus_write_4(sc->sc_base.sc_mem, DTSEC_REG_MAXFRM, mtu);
return (mtu);
}
return (0);
}
static u_int
dtsec_hash_maddr(void *arg, struct sockaddr_dl *sdl, u_int cnt)
{
uint32_t h, *hashtable = arg;
h = (ether_crc32_be(LLADDR(sdl), ETHER_ADDR_LEN) >> 24) & 0xFF;
hashtable[(h >> 5)] |= 1 << (0x1F - (h & 0x1F));
return (1);
}
static void
dtsec_setup_multicast(struct dtsec_softc *sc)
{
uint32_t hashtable[8] = {};
int i;
if (if_getflags(sc->sc_base.sc_ifnet) & IFF_ALLMULTI) {
for (i = 0; i < 8; i++)
bus_write_4(sc->sc_base.sc_mem, DTSEC_REG_GADDR(i), 0xFFFFFFFF);
bus_write_4(sc->sc_base.sc_mem, DTSEC_RCTRL,
bus_read_4(sc->sc_base.sc_mem, DTSEC_RCTRL) | RCTRL_MPROM);
return;
}
bus_write_4(sc->sc_base.sc_mem, DTSEC_RCTRL,
bus_read_4(sc->sc_base.sc_mem, DTSEC_RCTRL) & ~RCTRL_MPROM);
if_foreach_llmaddr(sc->sc_base.sc_ifnet, dtsec_hash_maddr, hashtable);
for (i = 0; i < 8; i++)
bus_write_4(sc->sc_base.sc_mem, DTSEC_REG_GADDR(i),
hashtable[i]);
}
static void
dtsec_if_graceful_stop(struct dtsec_softc *sc)
{
bus_write_4(sc->sc_base.sc_mem, DTSEC_RCTRL,
bus_read_4(sc->sc_base.sc_mem, DTSEC_RCTRL) | RCTRL_GRS);
if (sc->sc_base.sc_rev_major == 2)
DELAY(100);
else
DELAY(10);
bus_write_4(sc->sc_base.sc_mem, DTSEC_TCTRL,
bus_read_4(sc->sc_base.sc_mem, DTSEC_TCTRL) | TCTRL_GTS);
}
static void
dtsec_if_graceful_start(struct dtsec_softc *sc)
{
bus_write_4(sc->sc_base.sc_mem, DTSEC_RCTRL,
bus_read_4(sc->sc_base.sc_mem, DTSEC_RCTRL) & ~RCTRL_GRS);
if (sc->sc_base.sc_rev_major == 2)
DELAY(100);
else
DELAY(10);
bus_write_4(sc->sc_base.sc_mem, DTSEC_TCTRL,
bus_read_4(sc->sc_base.sc_mem, DTSEC_TCTRL) & ~TCTRL_GTS);
}
static int
dtsec_if_enable_locked(struct dtsec_softc *sc)
{
int error;
DTSEC_LOCK_ASSERT(sc);
dtsec_if_graceful_start(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);
dtsec_setup_multicast(sc);
if_setdrvflagbits(sc->sc_base.sc_ifnet, IFF_DRV_RUNNING, 0);
dtsec_miibus_statchg(sc->sc_base.sc_dev);
return (0);
}
static int
dtsec_if_disable_locked(struct dtsec_softc *sc)
{
int error;
DTSEC_LOCK_ASSERT(sc);
dtsec_if_graceful_stop(sc);
error = FMAN_PORT_DISABLE(sc->sc_base.sc_rx_port);
if (error != 0)
return (EIO);
error = FMAN_PORT_DISABLE(sc->sc_base.sc_tx_port);
if (error != 0)
return (EIO);
if_setdrvflagbits(sc->sc_base.sc_ifnet, 0, IFF_DRV_RUNNING);
return (0);
}
static int
dtsec_if_ioctl(if_t ifp, u_long command, caddr_t data)
{
struct dtsec_softc *sc;
struct ifreq *ifr;
int error;
sc = if_getsoftc(ifp);
ifr = (struct ifreq *)data;
error = 0;
switch (command) {
case SIOCSIFMTU:
DTSEC_LOCK(sc);
if (dtsec_set_mtu(sc, ifr->ifr_mtu))
if_setmtu(ifp, ifr->ifr_mtu);
else
error = EINVAL;
DTSEC_UNLOCK(sc);
break;
case SIOCSIFFLAGS:
DTSEC_LOCK(sc);
if (if_getflags(ifp) & IFF_UP) {
if (!(if_getdrvflags(ifp) & IFF_DRV_RUNNING))
dtsec_if_init_locked(sc);
} else
error = dtsec_if_disable_locked(sc);
DTSEC_UNLOCK(sc);
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
dtsec_if_tick(void *arg)
{
struct dtsec_softc *sc;
sc = arg;
DTSEC_LOCK(sc);
mii_tick(sc->sc_base.sc_mii);
callout_reset(&sc->sc_base.sc_tick_callout, hz, dtsec_if_tick, sc);
DTSEC_UNLOCK(sc);
}
static void
dtsec_if_deinit_locked(struct dtsec_softc *sc)
{
DTSEC_LOCK_ASSERT(sc);
DTSEC_UNLOCK(sc);
callout_drain(&sc->sc_base.sc_tick_callout);
DTSEC_LOCK(sc);
}
static void
dtsec_if_set_macaddr(struct dtsec_softc *sc, const char *addr)
{
uint32_t reg;
reg = (addr[5] << 24) | (addr[4] << 16) | (addr[3] << 8) | addr[2];
bus_write_4(sc->sc_base.sc_mem, DTSEC_MACSTNADDR1, reg);
reg = (addr[1] << 24) | (addr[0] << 16);
bus_write_4(sc->sc_base.sc_mem, DTSEC_MACSTNADDR2, reg);
}
static void
dtsec_if_init_locked(struct dtsec_softc *sc)
{
int error;
const char *macaddr;
DTSEC_LOCK_ASSERT(sc);
macaddr = if_getlladdr(sc->sc_base.sc_ifnet);
dtsec_if_set_macaddr(sc, macaddr);
if (sc->sc_base.sc_mii)
callout_reset(&sc->sc_base.sc_tick_callout, hz,
dtsec_if_tick, sc);
if (if_getflags(sc->sc_base.sc_ifnet) & IFF_UP) {
error = dtsec_if_enable_locked(sc);
if (error != 0)
goto err;
} else {
error = dtsec_if_disable_locked(sc);
if (error != 0)
goto err;
}
return;
err:
dtsec_if_deinit_locked(sc);
device_printf(sc->sc_base.sc_dev, "initialization error.\n");
return;
}
static void
dtsec_if_init(void *data)
{
struct dtsec_softc *sc;
sc = data;
DTSEC_LOCK(sc);
dtsec_if_init_locked(sc);
DTSEC_UNLOCK(sc);
}
static void
dtsec_if_start(if_t ifp)
{
struct dtsec_softc *sc;
sc = if_getsoftc(ifp);
DTSEC_LOCK(sc);
dpaa_eth_if_start_locked(&sc->sc_base);
DTSEC_UNLOCK(sc);
}
static void
dtsec_if_watchdog(if_t ifp)
{
}
static int
dtsec_ifmedia_upd(if_t ifp)
{
struct dtsec_softc *sc = if_getsoftc(ifp);
DTSEC_LOCK(sc);
mii_mediachg(sc->sc_base.sc_mii);
DTSEC_UNLOCK(sc);
return (0);
}
static void
dtsec_ifmedia_sts(if_t ifp, struct ifmediareq *ifmr)
{
struct dtsec_softc *sc = if_getsoftc(ifp);
DTSEC_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;
DTSEC_UNLOCK(sc);
}
int
dtsec_attach(device_t dev)
{
struct dtsec_softc *sc;
cell_t ports[2];
phandle_t node;
int error;
if_t ifp;
sc = device_get_softc(dev);
sc->sc_base.sc_dev = dev;
node = ofw_bus_get_node(dev);
mtx_init(&sc->sc_base.sc_lock, device_get_nameunit(dev),
"DTSEC Global Lock", MTX_DEF);
mtx_init(&sc->sc_base.sc_mii_lock, device_get_nameunit(dev),
"DTSEC MII Lock", MTX_DEF);
callout_init(&sc->sc_base.sc_tick_callout, CALLOUT_MPSAFE);
error = dpaa_eth_pool_rx_init(&sc->sc_base);
if (error != 0)
return (EIO);
error = dpaa_eth_fq_rx_init(&sc->sc_base);
if (error != 0)
return (EIO);
error = dpaa_eth_fi_pool_init(&sc->sc_base);
if (error != 0)
return (EIO);
error = dpaa_eth_fq_tx_init(&sc->sc_base);
if (error != 0)
return (EIO);
if (OF_getencprop(node, "fsl,fman-ports", ports, sizeof(ports)) < 0) {
device_printf(dev, "missing ports in device tree\n");
return (ENXIO);
}
error = dtsec_fm_mac_init(sc, sc->sc_base.sc_mac_addr);
if (error != 0) {
dtsec_detach(dev);
return (ENXIO);
}
sc->sc_base.sc_rx_port = OF_device_from_xref(ports[0]);
sc->sc_base.sc_tx_port = OF_device_from_xref(ports[1]);
dpaa_eth_fm_port_rx_init(&sc->sc_base);
dpaa_eth_fm_port_tx_init(&sc->sc_base);
if (sc->sc_base.sc_rx_port == NULL || sc->sc_base.sc_tx_port == NULL) {
device_printf(dev, "invalid ports");
dtsec_detach(dev);
return (ENXIO);
}
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, dtsec_if_init);
if_setstartfn(ifp, dtsec_if_start);
if_setioctlfn(ifp, dtsec_if_ioctl);
if_setsendqlen(ifp, IFQ_MAXLEN);
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, "dtsec_phy",
device_get_unit(sc->sc_base.sc_dev));
#if 0
if_setsendqlen(ifp, TSEC_TX_NUM_DESC - 1);
if_setsendqready(ifp);
#endif
if_setcapabilities(ifp, IFCAP_JUMBO_MTU | IFCAP_VLAN_MTU);
if_setcapenable(ifp, if_getcapabilities(ifp));
error = mii_attach(sc->sc_base.sc_dev, &sc->sc_base.sc_mii_dev,
ifp, dtsec_ifmedia_upd, dtsec_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);
dtsec_detach(sc->sc_base.sc_dev);
return (error);
}
ether_ifattach(ifp, sc->sc_base.sc_mac_addr);
return (0);
}
int
dtsec_detach(device_t dev)
{
struct dtsec_softc *sc;
if_t ifp;
sc = device_get_softc(dev);
ifp = sc->sc_base.sc_ifnet;
if (device_is_attached(dev)) {
ether_ifdetach(ifp);
DTSEC_LOCK(sc);
dtsec_if_deinit_locked(sc);
DTSEC_UNLOCK(sc);
}
if (sc->sc_base.sc_ifnet) {
if_free(sc->sc_base.sc_ifnet);
sc->sc_base.sc_ifnet = NULL;
}
dpaa_eth_fq_rx_free(&sc->sc_base);
dpaa_eth_fq_tx_free(&sc->sc_base);
dpaa_eth_fi_pool_free(&sc->sc_base);
dpaa_eth_pool_rx_free(&sc->sc_base);
mtx_destroy(&sc->sc_base.sc_lock);
return (0);
}
int
dtsec_suspend(device_t dev)
{
return (0);
}
int
dtsec_resume(device_t dev)
{
return (0);
}
int
dtsec_shutdown(device_t dev)
{
return (0);
}
int
dtsec_miibus_readreg(device_t dev, int phy, int reg)
{
struct dtsec_softc *sc;
sc = device_get_softc(dev);
return (MIIBUS_READREG(sc->sc_base.sc_mdio, phy, reg));
}
int
dtsec_miibus_writereg(device_t dev, int phy, int reg, int value)
{
struct dtsec_softc *sc;
sc = device_get_softc(dev);
return (MIIBUS_WRITEREG(sc->sc_base.sc_mdio, phy, reg, value));
}
void
dtsec_miibus_statchg(device_t dev)
{
struct dtsec_softc *sc;
uint32_t reg;
bool duplex;
int speed;
sc = device_get_softc(dev);
DTSEC_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_1000_T:
case IFM_1000_SX:
if (!duplex) {
device_printf(sc->sc_base.sc_dev,
"Only full-duplex supported for 1Gbps speeds");
return;
}
speed = MACCFG_IF_1G;
break;
default:
speed = MACCFG_IF_10_100;
}
reg = bus_read_4(sc->sc_base.sc_mem, DTSEC_MACCFG2);
reg &= ~(MACCFG_IF_M | MACCFG_FULLDUPLEX);
if (duplex)
reg |= MACCFG_FULLDUPLEX;
reg |= speed;
bus_write_4(sc->sc_base.sc_mem, DTSEC_MACCFG2, reg);
reg = bus_read_4(sc->sc_base.sc_mem, DTSEC_ECNTRL) & ~ECNTRL_R100M;
if (IFM_SUBTYPE(sc->sc_base.sc_mii->mii_media_active) == IFM_100_TX)
reg |= ECNTRL_R100M;
bus_write_4(sc->sc_base.sc_mem, DTSEC_ECNTRL, reg);
}