diff options
Diffstat (limited to 'sys/arch/octeon/dev/cn30xxgmx.c')
-rw-r--r-- | sys/arch/octeon/dev/cn30xxgmx.c | 1733 |
1 files changed, 1733 insertions, 0 deletions
diff --git a/sys/arch/octeon/dev/cn30xxgmx.c b/sys/arch/octeon/dev/cn30xxgmx.c new file mode 100644 index 00000000000..14ad35c54b5 --- /dev/null +++ b/sys/arch/octeon/dev/cn30xxgmx.c @@ -0,0 +1,1733 @@ +/* $OpenBSD: cn30xxgmx.c,v 1.1 2011/06/16 11:22:30 syuu Exp $ */ + +/* + * Copyright (c) 2007 Internet Initiative Japan, Inc. + * 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 REGENTS 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 REGENTS 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. + */ + +/* + * support GMX0 interface only + * take no thought for other GMX interface + */ + +#include <sys/cdefs.h> + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/types.h> +#include <sys/device.h> +#include <sys/lock.h> +#include <sys/cdefs.h> +#include <sys/malloc.h> +#include <sys/syslog.h> + +#include <machine/bus.h> +#include <machine/octeon_model.h> + +#include <octeon/dev/iobusvar.h> +#include <octeon/dev/cn30xxciureg.h> +#include <octeon/dev/cn30xxgmxreg.h> +#include <octeon/dev/cn30xxipdvar.h> +#include <octeon/dev/cn30xxasxvar.h> +#include <octeon/dev/cn30xxgmxvar.h> + +#define dprintf(...) +#define OCTEON_ETH_KASSERT KASSERT + +#ifndef SET +#define SET(t, f) ((t) |= (f)) +#define ISSET(t, f) ((t) & (f)) +#define CLR(t, f) ((t) &= ~(f)) +#endif + +#define ADDR2UINT64(u, a) \ + do { \ + u = \ + (((uint64_t)a[0] << 40) | ((uint64_t)a[1] << 32) | \ + ((uint64_t)a[2] << 24) | ((uint64_t)a[3] << 16) | \ + ((uint64_t)a[4] << 8) | ((uint64_t)a[5] << 0)); \ + } while (0) +#define UINT642ADDR(a, u) \ + do { \ + a[0] = (uint8_t)((u) >> 40); a[1] = (uint8_t)((u) >> 32); \ + a[2] = (uint8_t)((u) >> 24); a[3] = (uint8_t)((u) >> 16); \ + a[4] = (uint8_t)((u) >> 8); a[5] = (uint8_t)((u) >> 0); \ + } while (0) + +#define _GMX_RD8(sc, off) \ + bus_space_read_8((sc)->sc_port_gmx->sc_regt, (sc)->sc_port_gmx->sc_regh, (off)) +#define _GMX_WR8(sc, off, v) \ + bus_space_write_8((sc)->sc_port_gmx->sc_regt, (sc)->sc_port_gmx->sc_regh, (off), (v)) +#define _GMX_PORT_RD8(sc, off) \ + bus_space_read_8((sc)->sc_port_gmx->sc_regt, (sc)->sc_port_regh, (off)) +#define _GMX_PORT_WR8(sc, off, v) \ + bus_space_write_8((sc)->sc_port_gmx->sc_regt, (sc)->sc_port_regh, (off), (v)) + +struct cn30xxgmx_port_ops { + int (*port_ops_enable)(struct cn30xxgmx_port_softc *, int); + int (*port_ops_speed)(struct cn30xxgmx_port_softc *); + int (*port_ops_timing)(struct cn30xxgmx_port_softc *); + int (*port_ops_set_mac_addr)(struct cn30xxgmx_port_softc *, + uint8_t *, uint64_t); + int (*port_ops_set_filter)(struct cn30xxgmx_port_softc *); +}; + +static int cn30xxgmx_match(struct device *, void *, void *); +static void cn30xxgmx_attach(struct device *, struct device *, void *); +static int cn30xxgmx_print(void *, const char *); +static int cn30xxgmx_submatch(struct device *, void *, void *); +static int cn30xxgmx_init(struct cn30xxgmx_softc *); +static int cn30xxgmx_rx_frm_ctl_xable(struct cn30xxgmx_port_softc *, + uint64_t, int); +static int cn30xxgmx_rgmii_enable(struct cn30xxgmx_port_softc *, int); +static int cn30xxgmx_rgmii_speed(struct cn30xxgmx_port_softc *); +static int cn30xxgmx_rgmii_speed_newlink(struct cn30xxgmx_port_softc *, + uint64_t *); +static int cn30xxgmx_rgmii_speed_newlink_log( + struct cn30xxgmx_port_softc *, uint64_t); +static int cn30xxgmx_rgmii_speed_speed(struct cn30xxgmx_port_softc *); +static int cn30xxgmx_rgmii_timing(struct cn30xxgmx_port_softc *); +static int cn30xxgmx_rgmii_set_mac_addr(struct cn30xxgmx_port_softc *, + uint8_t *, uint64_t); +static int cn30xxgmx_rgmii_set_filter(struct cn30xxgmx_port_softc *); + +#ifdef OCTEON_ETH_DEBUG +void cn30xxgmx_intr_evcnt_attach(struct cn30xxgmx_softc *); +void cn30xxgmx_dump(void); +void cn30xxgmx_debug_reset(void); +int cn30xxgmx_intr_drop(void *); +#endif + +static const int cn30xxgmx_rx_adr_cam_regs[] = { + GMX0_RX0_ADR_CAM0, GMX0_RX0_ADR_CAM1, GMX0_RX0_ADR_CAM2, + GMX0_RX0_ADR_CAM3, GMX0_RX0_ADR_CAM4, GMX0_RX0_ADR_CAM5 +}; + +struct cn30xxgmx_port_ops cn30xxgmx_port_ops_mii = { + /* XXX not implemented */ +}; + +struct cn30xxgmx_port_ops cn30xxgmx_port_ops_gmii = { + .port_ops_enable = cn30xxgmx_rgmii_enable, + .port_ops_speed = cn30xxgmx_rgmii_speed, + .port_ops_timing = cn30xxgmx_rgmii_timing, + .port_ops_set_mac_addr = cn30xxgmx_rgmii_set_mac_addr, + .port_ops_set_filter = cn30xxgmx_rgmii_set_filter +}; + +struct cn30xxgmx_port_ops cn30xxgmx_port_ops_rgmii = { + .port_ops_enable = cn30xxgmx_rgmii_enable, + .port_ops_speed = cn30xxgmx_rgmii_speed, + .port_ops_timing = cn30xxgmx_rgmii_timing, + .port_ops_set_mac_addr = cn30xxgmx_rgmii_set_mac_addr, + .port_ops_set_filter = cn30xxgmx_rgmii_set_filter +}; + +struct cn30xxgmx_port_ops cn30xxgmx_port_ops_spi42 = { + /* XXX not implemented */ +}; + +struct cn30xxgmx_port_ops *cn30xxgmx_port_ops[] = { + [GMX_MII_PORT] = &cn30xxgmx_port_ops_mii, + [GMX_GMII_PORT] = &cn30xxgmx_port_ops_gmii, + [GMX_RGMII_PORT] = &cn30xxgmx_port_ops_rgmii, + [GMX_SPI42_PORT] = &cn30xxgmx_port_ops_spi42 +}; + +#ifdef OCTEON_ETH_DEBUG +static void *cn30xxgmx_intr_drop_ih; +struct evcnt cn30xxgmx_intr_drop_evcnt = + EVCNT_INITIALIZER(EVCNT_TYPE_INTR, NULL, "octeon", + "gmx drop intr"); +struct evcnt cn30xxgmx_intr_evcnt = + EVCNT_INITIALIZER(EVCNT_TYPE_INTR, NULL, "octeon", + "gmx intr"); +EVCNT_ATTACH_STATIC(cn30xxgmx_intr_drop_evcnt); +EVCNT_ATTACH_STATIC(cn30xxgmx_intr_evcnt); + +struct cn30xxgmx_port_softc *__cn30xxgmx_port_softc[3/* XXX */]; +#endif + +struct cfattach cn30xxgmx_ca = {sizeof(struct cn30xxgmx_softc), + cn30xxgmx_match, cn30xxgmx_attach, NULL, NULL}; + +struct cfdriver cn30xxgmx_cd = {NULL, "cn30xxgmx", DV_DULL}; + +static int +cn30xxgmx_match(struct device *parent, void *match, void *aux) +{ + struct cfdata *cf = (struct cfdata *)match; + struct iobus_attach_args *aa = aux; + + if (strcmp(cf->cf_driver->cd_name, aa->aa_name) != 0) + return 0; + if (cf->cf_unit != aa->aa_unitno) + return 0; + return 1; +} + +static void +cn30xxgmx_attach(struct device *parent, struct device *self, void *aux) +{ + struct cn30xxgmx_softc *sc = (void *)self; + struct iobus_attach_args *aa = aux; + struct cn30xxgmx_attach_args gmx_aa; + int status; + int i; + struct cn30xxgmx_port_softc *port_sc; + + printf("\n"); + + sc->sc_regt = aa->aa_bust; /* XXX why there are iot? */ + + status = bus_space_map(sc->sc_regt, aa->aa_unit->addr, + GMX0_BASE_IF_SIZE, 0, &sc->sc_regh); + if (status != 0) + panic(": can't map register"); + + cn30xxgmx_init(sc); + + sc->sc_ports = malloc(sizeof(*sc->sc_ports) * sc->sc_nports, M_DEVBUF, + M_NOWAIT | M_ZERO); + + for (i = 0; i < sc->sc_nports; i++) { + port_sc = &sc->sc_ports[i]; + port_sc->sc_port_gmx = sc; + port_sc->sc_port_no = i; + port_sc->sc_port_type = sc->sc_port_types[i]; + port_sc->sc_port_ops = cn30xxgmx_port_ops[port_sc->sc_port_type]; + status = bus_space_map(sc->sc_regt, + aa->aa_unit->addr + GMX0_BASE_PORT_SIZE * i, + GMX0_BASE_PORT_SIZE, 0, &port_sc->sc_port_regh); + if (status != 0) + panic(": can't map port register"); + + (void)memset(&gmx_aa, 0, sizeof(gmx_aa)); + gmx_aa.ga_regt = aa->aa_bust; + gmx_aa.ga_dmat = aa->aa_dmat; + gmx_aa.ga_addr = aa->aa_unit->addr; + gmx_aa.ga_name = "cnmac"; + gmx_aa.ga_portno = i; + gmx_aa.ga_port_type = sc->sc_port_types[i]; + gmx_aa.ga_gmx = sc; + gmx_aa.ga_gmx_port = port_sc; + + config_found_sm(self, &gmx_aa, + cn30xxgmx_print, cn30xxgmx_submatch); + +#ifdef OCTEON_ETH_DEBUG + __cn30xxgmx_port_softc[i] = port_sc; +#endif + } + +#ifdef OCTEON_ETH_DEBUG + cn30xxgmx_intr_evcnt_attach(sc); + if (cn30xxgmx_intr_drop_ih == NULL) + cn30xxgmx_intr_drop_ih = octeon_intr_establish( + ffs64(CIU_INTX_SUM0_GMX_DRP) - 1, 0, IPL_NET, + cn30xxgmx_intr_drop, NULL); +#endif +} + +static int +cn30xxgmx_print(void *aux, const char *pnp) +{ + struct cn30xxgmx_attach_args *ga = aux; + static const char *types[] = { + [GMX_MII_PORT] = "MII", + [GMX_GMII_PORT] = "GMII", + [GMX_RGMII_PORT] = "RGMII" + }; + +#if DEBUG + if (pnp) + printf("%s at %s", ga->ga_name, pnp); +#endif + + printf(" address=0x%016llx: %s", ga->ga_addr, + types[ga->ga_port_type]); + + return UNCONF; +} + +static int +cn30xxgmx_submatch(struct device *parent, void *vcf, void *aux) +{ + struct cfdata *cf = vcf; + + return (*cf->cf_attach->ca_match)(parent, vcf, aux); +} + +static int +cn30xxgmx_init(struct cn30xxgmx_softc *sc) +{ + int result = 0; + uint64_t inf_mode; + int id; + + inf_mode = bus_space_read_8(sc->sc_regt, sc->sc_regh, GMX0_INF_MODE); + if ((inf_mode & INF_MODE_EN) == 0) { + printf("port are disable\n"); + sc->sc_nports = 0; + result = 1; + return result; + } + + id = octeon_get_chipid(); + + switch (octeon_model_family(id)) { + case OCTEON_MODEL_FAMILY_CN31XX: + /* + * CN31XX-HM-1.01 + * 14.1 Packet Interface Introduction + * Table 14-1 Packet Interface Configuration + * 14.8 GMX Registers, Interface Mode Register, GMX0_INF_MODE + */ + if ((inf_mode & INF_MODE_TYPE) == 0) { + /* all three ports configured as RGMII */ + sc->sc_nports = 3; + sc->sc_port_types[0] = GMX_RGMII_PORT; + sc->sc_port_types[1] = GMX_RGMII_PORT; + sc->sc_port_types[2] = GMX_RGMII_PORT; + } else { + /* port 0: RGMII, port 1: GMII, port 2: disabled */ + /* XXX CN31XX-HM-1.01 says "Port 3: disabled"; typo? */ + sc->sc_nports = 2; + sc->sc_port_types[0] = GMX_RGMII_PORT; + sc->sc_port_types[1] = GMX_GMII_PORT; + } + break; + case OCTEON_MODEL_FAMILY_CN30XX: + case OCTEON_MODEL_FAMILY_CN50XX: + /* + * CN30XX-HM-1.0 + * 13.1 Packet Interface Introduction + * Table 13-1 Packet Interface Configuration + * 13.8 GMX Registers, Interface Mode Register, GMX0_INF_MODE + */ + if ((inf_mode & INF_MODE_P0MII) == 0) + sc->sc_port_types[0] = GMX_RGMII_PORT; + else + sc->sc_port_types[0] = GMX_MII_PORT; + if ((inf_mode & INF_MODE_TYPE) == 0) { + /* port 1 and 2 are configred as RGMII ports */ + sc->sc_nports = 3; + sc->sc_port_types[1] = GMX_RGMII_PORT; + sc->sc_port_types[2] = GMX_RGMII_PORT; + } else { + /* port 1: GMII/MII, port 2: disabled */ + /* GMII or MII port is slected by GMX_PRT1_CFG[SPEED] */ + sc->sc_nports = 2; + sc->sc_port_types[1] = GMX_GMII_PORT; + } + /* port 2 is in CN3010/CN5010 only */ + if ((octeon_model(id) != OCTEON_MODEL_CN3010) && + (octeon_model(id) != OCTEON_MODEL_CN5010)) + if (sc->sc_nports == 3) + sc->sc_nports = 2; + break; + case OCTEON_MODEL_FAMILY_CN38XX: + case OCTEON_MODEL_FAMILY_CN56XX: + case OCTEON_MODEL_FAMILY_CN58XX: + default: + printf("unsupported octeon model: 0x%x\n", octeon_get_chipid()); + sc->sc_nports = 0; + result = 1; + break; + } + + return result; +} + +/* XXX RGMII specific */ +int +cn30xxgmx_link_enable(struct cn30xxgmx_port_softc *sc, int enable) +{ + uint64_t prt_cfg; + + cn30xxgmx_tx_int_enable(sc, enable); + cn30xxgmx_rx_int_enable(sc, enable); + + prt_cfg = _GMX_PORT_RD8(sc, GMX0_PRT0_CFG); + if (enable) { + if (cn30xxgmx_link_status(sc)) { + SET(prt_cfg, PRTN_CFG_EN); + } + } else { + CLR(prt_cfg, PRTN_CFG_EN); + } + _GMX_PORT_WR8(sc, GMX0_PRT0_CFG, prt_cfg); + /* + * According to CN30XX-HM-1.0, 13.4.2 Link Status Changes: + * > software should read back to flush the write operation. + */ + (void)_GMX_PORT_RD8(sc, GMX0_PRT0_CFG); + + return 0; +} + +/* XXX RGMII specific */ +int +cn30xxgmx_stats_init(struct cn30xxgmx_port_softc *sc) +{ + _GMX_PORT_WR8(sc, GMX0_RX0_STATS_PKTS, 0x0ULL); + _GMX_PORT_WR8(sc, GMX0_RX0_STATS_PKTS_DRP, 0x0ULL); + _GMX_PORT_WR8(sc, GMX0_RX0_STATS_PKTS_BAD, 0x0ULL); + _GMX_PORT_WR8(sc, GMX0_TX0_STAT0, 0x0ULL); + _GMX_PORT_WR8(sc, GMX0_TX0_STAT1, 0x0ULL); + _GMX_PORT_WR8(sc, GMX0_TX0_STAT3, 0x0ULL); + _GMX_PORT_WR8(sc, GMX0_TX0_STAT9, 0x0ULL); + return 0; +} + +int +cn30xxgmx_tx_stats_rd_clr(struct cn30xxgmx_port_softc *sc, int enable) +{ + _GMX_PORT_WR8(sc, GMX0_TX0_STATS_CTL, enable ? 0x1ULL : 0x0ULL); + return 0; +} + +int +cn30xxgmx_rx_stats_rd_clr(struct cn30xxgmx_port_softc *sc, int enable) +{ + _GMX_PORT_WR8(sc, GMX0_RX0_STATS_CTL, enable ? 0x1ULL : 0x0ULL); + return 0; +} + +void +cn30xxgmx_rx_stats_dec_bad(struct cn30xxgmx_port_softc *sc) +{ + uint64_t tmp; + + tmp = _GMX_PORT_RD8(sc, GMX0_RX0_STATS_PKTS_BAD); + _GMX_PORT_WR8(sc, GMX0_RX0_STATS_PKTS_BAD, tmp - 1); +} + +static int +cn30xxgmx_tx_ovr_bp_enable(struct cn30xxgmx_port_softc *sc, int enable) +{ + uint64_t ovr_bp; + + ovr_bp = _GMX_RD8(sc, GMX0_TX_OVR_BP); + if (enable) { + CLR(ovr_bp, (1 << sc->sc_port_no) << TX_OVR_BP_EN_SHIFT); + SET(ovr_bp, (1 << sc->sc_port_no) << TX_OVR_BP_BP_SHIFT); + /* XXX really??? */ + SET(ovr_bp, (1 << sc->sc_port_no) << TX_OVR_BP_IGN_FULL_SHIFT); + } else { + SET(ovr_bp, (1 << sc->sc_port_no) << TX_OVR_BP_EN_SHIFT); + CLR(ovr_bp, (1 << sc->sc_port_no) << TX_OVR_BP_BP_SHIFT); + /* XXX really??? */ + SET(ovr_bp, (1 << sc->sc_port_no) << TX_OVR_BP_IGN_FULL_SHIFT); + } + _GMX_WR8(sc, GMX0_TX_OVR_BP, ovr_bp); + return 0; +} + +static int +cn30xxgmx_rx_pause_enable(struct cn30xxgmx_port_softc *sc, int enable) +{ + if (enable) { + cn30xxgmx_rx_frm_ctl_enable(sc, RXN_FRM_CTL_CTL_BCK); + } else { + cn30xxgmx_rx_frm_ctl_disable(sc, RXN_FRM_CTL_CTL_BCK); + } + + return 0; +} + +void +cn30xxgmx_tx_int_enable(struct cn30xxgmx_port_softc *sc, int enable) +{ + uint64_t tx_int_xxx = 0; + + SET(tx_int_xxx, + TX_INT_REG_LATE_COL | + TX_INT_REG_XSDEF | + TX_INT_REG_XSCOL | + TX_INT_REG_UNDFLW | + TX_INT_REG_PKO_NXA); + _GMX_WR8(sc, GMX0_TX_INT_REG, tx_int_xxx); + _GMX_WR8(sc, GMX0_TX_INT_EN, enable ? tx_int_xxx : 0); +} + +void +cn30xxgmx_rx_int_enable(struct cn30xxgmx_port_softc *sc, int enable) +{ + uint64_t rx_int_xxx = 0; + + SET(rx_int_xxx, 0 | + RXN_INT_REG_PHY_DUPX | + RXN_INT_REG_PHY_SPD | + RXN_INT_REG_PHY_LINK | + RXN_INT_REG_IFGERR | + RXN_INT_REG_COLDET | + RXN_INT_REG_FALERR | + RXN_INT_REG_RSVERR | + RXN_INT_REG_PCTERR | + RXN_INT_REG_OVRERR | + RXN_INT_REG_NIBERR | + RXN_INT_REG_SKPERR | + RXN_INT_REG_RCVERR | + RXN_INT_REG_LENERR | + RXN_INT_REG_ALNERR | + RXN_INT_REG_FCSERR | + RXN_INT_REG_JABBER | + RXN_INT_REG_MAXERR | + RXN_INT_REG_CAREXT | + RXN_INT_REG_MINERR); + _GMX_PORT_WR8(sc, GMX0_RX0_INT_REG, rx_int_xxx); + _GMX_PORT_WR8(sc, GMX0_RX0_INT_EN, enable ? rx_int_xxx : 0); +} + +int +cn30xxgmx_rx_frm_ctl_enable(struct cn30xxgmx_port_softc *sc, + uint64_t rx_frm_ctl) +{ + /* + * XXX Jumbo-frame Workarounds + * Current implementation of cnmac is required to + * configure GMX0_RX0_JABBER[CNT] as follows: + * RX0_FRM_MAX(1536) <= GMX0_RX0_JABBER <= 1536(0x600) + */ + _GMX_PORT_WR8(sc, GMX0_RX0_JABBER, GMX_FRM_MAX_SIZ); + + return cn30xxgmx_rx_frm_ctl_xable(sc, rx_frm_ctl, 1); +} + +int +cn30xxgmx_rx_frm_ctl_disable(struct cn30xxgmx_port_softc *sc, + uint64_t rx_frm_ctl) +{ + return cn30xxgmx_rx_frm_ctl_xable(sc, rx_frm_ctl, 0); +} + +static int +cn30xxgmx_rx_frm_ctl_xable(struct cn30xxgmx_port_softc *sc, + uint64_t rx_frm_ctl, int enable) +{ + uint64_t tmp; + + tmp = _GMX_PORT_RD8(sc, GMX0_RX0_FRM_CTL); + if (enable) + SET(tmp, rx_frm_ctl); + else + CLR(tmp, rx_frm_ctl); + _GMX_PORT_WR8(sc, GMX0_RX0_FRM_CTL, tmp); + + return 0; +} + +int +cn30xxgmx_tx_thresh(struct cn30xxgmx_port_softc *sc, int cnt) +{ + _GMX_PORT_WR8(sc, GMX0_TX0_THRESH, cnt); + return 0; +} + +int +cn30xxgmx_set_mac_addr(struct cn30xxgmx_port_softc *sc, uint8_t *addr) +{ + uint64_t mac = 0; + + ADDR2UINT64(mac, addr); + (*sc->sc_port_ops->port_ops_set_mac_addr)(sc, addr, mac); + return 0; +} + +int +cn30xxgmx_set_filter(struct cn30xxgmx_port_softc *sc) +{ + (*sc->sc_port_ops->port_ops_set_filter)(sc); + return 0; +} + +int +cn30xxgmx_port_enable(struct cn30xxgmx_port_softc *sc, int enable) +{ + (*sc->sc_port_ops->port_ops_enable)(sc, enable); + return 0; +} + +int +cn30xxgmx_reset_speed(struct cn30xxgmx_port_softc *sc) +{ + struct ifnet *ifp = &sc->sc_port_ac->ac_if; + if (ISSET(sc->sc_port_mii->mii_flags, MIIF_DOINGAUTO)) { + log(LOG_WARNING, + "%s: autonegotiation has not been completed yet\n", + ifp->if_xname); + return 1; + } + (*sc->sc_port_ops->port_ops_speed)(sc); + return 0; +} + +int +cn30xxgmx_reset_timing(struct cn30xxgmx_port_softc *sc) +{ + (*sc->sc_port_ops->port_ops_timing)(sc); + return 0; +} + +int +cn30xxgmx_reset_board(struct cn30xxgmx_port_softc *sc) +{ + + return 0; +} + +int +cn30xxgmx_reset_flowctl(struct cn30xxgmx_port_softc *sc) +{ + struct ifmedia_entry *ife = sc->sc_port_mii->mii_media.ifm_cur; + + /* + * Get flow control negotiation result. + */ +#ifdef GMX_802_3X_DISABLE_AUTONEG + /* Tentative support for SEIL-compat.. */ + if (IFM_SUBTYPE(ife->ifm_media) == IFM_AUTO) { + sc->sc_port_flowflags &= ~IFM_ETH_FMASK; + } +#else + /* Default configuration of NetBSD */ + if (IFM_SUBTYPE(ife->ifm_media) == IFM_AUTO && + (sc->sc_port_mii->mii_media_active & IFM_ETH_FMASK) != + sc->sc_port_flowflags) { + sc->sc_port_flowflags = + sc->sc_port_mii->mii_media_active & IFM_ETH_FMASK; + sc->sc_port_mii->mii_media_active &= ~IFM_ETH_FMASK; + } +#endif /* GMX_802_3X_DISABLE_AUTONEG */ + + /* + * 802.3x Flow Control Capabilities + */ + if (sc->sc_port_flowflags & IFM_ETH_TXPAUSE) { + cn30xxgmx_tx_ovr_bp_enable(sc, 1); + } else { + cn30xxgmx_tx_ovr_bp_enable(sc, 0); + } + if (sc->sc_port_flowflags & IFM_ETH_RXPAUSE) { + cn30xxgmx_rx_pause_enable(sc, 1); + } else { + cn30xxgmx_rx_pause_enable(sc, 0); + } + + return 0; +} + +static int +cn30xxgmx_rgmii_enable(struct cn30xxgmx_port_softc *sc, int enable) +{ + uint64_t mode; + + /* XXX */ + mode = _GMX_RD8(sc, GMX0_INF_MODE); + if (ISSET(mode, INF_MODE_EN)) { + cn30xxasx_enable(sc->sc_port_asx, 1); + } + + return 0; +} + +static int +cn30xxgmx_rgmii_speed(struct cn30xxgmx_port_softc *sc) +{ + struct ifnet *ifp = &sc->sc_port_ac->ac_if; + uint64_t newlink; + int baudrate; + + /* XXX */ + cn30xxgmx_link_enable(sc, 1); + + cn30xxgmx_rgmii_speed_newlink(sc, &newlink); + if (sc->sc_link == newlink) { + return 0; + } + cn30xxgmx_rgmii_speed_newlink_log(sc, newlink); + sc->sc_link = newlink; + + switch (sc->sc_link & RXN_RX_INBND_SPEED) { + case RXN_RX_INBND_SPEED_2_5: + baudrate = IF_Mbps(10); + break; + case RXN_RX_INBND_SPEED_25: + baudrate = IF_Mbps(100); + break; + case RXN_RX_INBND_SPEED_125: + baudrate = IF_Mbps(1000); + break; + default: + baudrate = 0/* XXX */; + break; + } + ifp->if_baudrate = baudrate; + + cn30xxgmx_link_enable(sc, 0); + + /* + * According to CN30XX-HM-1.0, 13.4.2 Link Status Changes: + * wait a max_packet_time + * max_packet_time(us) = (max_packet_size(bytes) * 8) / link_speed(Mbps) + */ + delay((GMX_FRM_MAX_SIZ * 8) / (baudrate / 1000000)); + + cn30xxgmx_rgmii_speed_speed(sc); + + cn30xxgmx_link_enable(sc, 1); + cn30xxasx_enable(sc->sc_port_asx, 1); + + return 0; +} + +static int +cn30xxgmx_rgmii_speed_newlink(struct cn30xxgmx_port_softc *sc, + uint64_t *rnewlink) +{ + uint64_t newlink = 0; + + switch (sc->sc_quirks & + (OCTEON_ETH_QUIRKS_SEILX | OCTEON_ETH_QUIRKS_SEILX2PORT0 | + OCTEON_ETH_QUIRKS_L2SWPORT)) { + default: + /* Inband status does not seem to work */ + newlink = _GMX_PORT_RD8(sc, GMX0_RX0_RX_INBND); + break; + case OCTEON_ETH_QUIRKS_SEILX | OCTEON_ETH_QUIRKS_SEILX2PORT0: + SET(newlink, RXN_RX_INBND_SPEED_125); + SET(newlink, RXN_RX_INBND_DUPLEX); + SET(newlink, RXN_RX_INBND_STATUS); + break; + case OCTEON_ETH_QUIRKS_L2SWPORT: + SET(newlink, RXN_RX_INBND_SPEED_125); + SET(newlink, RXN_RX_INBND_DUPLEX); + SET(newlink, RXN_RX_INBND_STATUS); + break; + case OCTEON_ETH_QUIRKS_SEILX: + newlink = 0; + switch (IFM_SUBTYPE(sc->sc_port_mii->mii_media_active)) { + default: + SET(newlink, RXN_RX_INBND_SPEED_125); + break; + case IFM_100_TX: + SET(newlink, RXN_RX_INBND_SPEED_25); + break; + case IFM_10_T: + /* XXX how can this happen? */ + SET(newlink, RXN_RX_INBND_SPEED_2_5); + break; + } + SET(newlink, + ISSET(sc->sc_port_mii->mii_media_active, IFM_FDX) ? + RXN_RX_INBND_DUPLEX : 0); + SET(newlink, + ISSET(sc->sc_port_mii->mii_media_status, IFM_ACTIVE) ? + RXN_RX_INBND_STATUS : 0); + break; + case OCTEON_ETH_QUIRKS_SEILX2PORT0: + /* NOTREACHED */ + OCTEON_ETH_KASSERT(0); + break; + } + + *rnewlink = newlink; + return 0; +} + +static int +cn30xxgmx_rgmii_speed_newlink_log(struct cn30xxgmx_port_softc *sc, + uint64_t newlink) +{ + struct ifnet *ifp = &sc->sc_port_ac->ac_if; + const char *status_str; + const char *speed_str; + const char *duplex_str; + int is_status_changed; + int is_speed_changed; + int is_linked; + char status_buf[80/* XXX */]; + char speed_buf[80/* XXX */]; + + is_status_changed = (newlink & RXN_RX_INBND_STATUS) != + (sc->sc_link & RXN_RX_INBND_STATUS); + is_speed_changed = (newlink & RXN_RX_INBND_SPEED) != + (sc->sc_link & RXN_RX_INBND_SPEED); + is_linked = ISSET(newlink, RXN_RX_INBND_STATUS); + if (is_status_changed) { + if (is_linked) + status_str = "link up"; + else + status_str = "link down"; + } else { + if (is_linked) { + /* any other conditions? */ + if (is_speed_changed) + status_str = "link change"; + else + status_str = NULL; + } else { + status_str = NULL; + } + } + + if (status_str != NULL) { + if ((is_speed_changed && is_linked) || is_linked) { + switch (newlink & RXN_RX_INBND_SPEED) { + case RXN_RX_INBND_SPEED_2_5: + speed_str = "10baseT"; + break; + case RXN_RX_INBND_SPEED_25: + speed_str = "100baseTX"; + break; + case RXN_RX_INBND_SPEED_125: + speed_str = "1000baseT"; + break; + default: + panic("Unknown link speed"); + break; + } + + if (ISSET(newlink, RXN_RX_INBND_DUPLEX)) + duplex_str = "-FDX"; + else + duplex_str = ""; + + (void)snprintf(speed_buf, sizeof(speed_buf), "(%s%s)", + speed_str, duplex_str); + } else { + speed_buf[0] = '\0'; + } + (void)snprintf(status_buf, sizeof(status_buf), "%s: %s%s%s\n", + ifp->if_xname, status_str, (is_speed_changed | is_linked) ? " " : "", + speed_buf); + log(LOG_CRIT, status_buf); + } + + return 0; +} + +static int +cn30xxgmx_rgmii_speed_speed(struct cn30xxgmx_port_softc *sc) +{ + uint64_t prt_cfg; + uint64_t tx_clk, tx_slot, tx_burst; + + prt_cfg = _GMX_PORT_RD8(sc, GMX0_PRT0_CFG); + + switch (sc->sc_link & RXN_RX_INBND_SPEED) { + case RXN_RX_INBND_SPEED_2_5: + /* 10Mbps */ + /* + * "GMX Tx Clock Generation Registers", CN30XX-HM-1.0; + * > 8ns x 50 = 400ns (2.5MHz TXC clock) + */ + tx_clk = 50; + /* + * "TX Slottime Counter Registers", CN30XX-HM-1.0; + * > 10/100Mbps: set SLOT to 0x40 + */ + tx_slot = 0x40; + /* + * "TX Burst-Counter Registers", CN30XX-HM-1.0; + * > 10/100Mbps: set BURST to 0x0 + */ + tx_burst = 0; + /* + * "GMX Tx Port Configuration Registers", CN30XX-HM-1.0; + * > Slot time for half-duplex operation + * > 0 = 512 bittimes (10/100Mbps operation) + */ + CLR(prt_cfg, PRTN_CFG_SLOTTIME); + /* + * "GMX Port Configuration Registers", CN30XX-HM-1.0; + * > Link speed + * > 0 = 10/100Mbps operation + * > in RGMII mode: GMX0_TX(0..2)_CLK[CLK_CNT] > 1 + */ + CLR(prt_cfg, PRTN_CFG_SPEED); + break; + case RXN_RX_INBND_SPEED_25: + /* 100Mbps */ + /* + * "GMX Tx Clock Generation Registers", CN30XX-HM-1.0; + * > 8ns x 5 = 40ns (25.0MHz TXC clock) + */ + tx_clk = 5; + /* + * "TX Slottime Counter Registers", CN30XX-HM-1.0; + * > 10/100Mbps: set SLOT to 0x40 + */ + tx_slot = 0x40; + /* + * "TX Burst-Counter Registers", CN30XX-HM-1.0; + * > 10/100Mbps: set BURST to 0x0 + */ + tx_burst = 0; + /* + * "GMX Tx Port Configuration Registers", CN30XX-HM-1.0; + * > Slot time for half-duplex operation + * > 0 = 512 bittimes (10/100Mbps operation) + */ + CLR(prt_cfg, PRTN_CFG_SLOTTIME); + /* + * "GMX Port Configuration Registers", CN30XX-HM-1.0; + * > Link speed + * > 0 = 10/100Mbps operation + * > in RGMII mode: GMX0_TX(0..2)_CLK[CLK_CNT] > 1 + */ + CLR(prt_cfg, PRTN_CFG_SPEED); + break; + case RXN_RX_INBND_SPEED_125: + /* 1000Mbps */ + /* + * "GMX Tx Clock Generation Registers", CN30XX-HM-1.0; + * > 8ns x 1 = 8ns (125.0MHz TXC clock) + */ + tx_clk = 1; + /* + * "TX Slottime Counter Registers", CN30XX-HM-1.0; + * > 1000Mbps: set SLOT to 0x200 + */ + tx_slot = 0x200; + /* + * "TX Burst-Counter Registers", CN30XX-HM-1.0; + * > 1000Mbps: set BURST to 0x2000 + */ + tx_burst = 0x2000; + /* + * "GMX Tx Port Configuration Registers", CN30XX-HM-1.0; + * > Slot time for half-duplex operation + * > 1 = 4096 bittimes (1000Mbps operation) + */ + SET(prt_cfg, PRTN_CFG_SLOTTIME); + /* + * "GMX Port Configuration Registers", CN30XX-HM-1.0; + * > Link speed + * > 1 = 1000Mbps operation + */ + SET(prt_cfg, PRTN_CFG_SPEED); + break; + default: + /* NOT REACHED! */ + /* Following configuration is default value of system. + */ + tx_clk = 1; + tx_slot = 0x200; + tx_burst = 0x2000; + SET(prt_cfg, PRTN_CFG_SLOTTIME); + SET(prt_cfg, PRTN_CFG_SPEED); + break; + } + + /* Setup Duplex mode(negotiated) */ + /* + * "GMX Port Configuration Registers", CN30XX-HM-1.0; + * > Duplex mode: 0 = half-duplex mode, 1=full-duplex + */ + if (ISSET(sc->sc_link, RXN_RX_INBND_DUPLEX)) { + /* Full-Duplex */ + SET(prt_cfg, PRTN_CFG_DUPLEX); + } else { + /* Half-Duplex */ + CLR(prt_cfg, PRTN_CFG_DUPLEX); + } + + _GMX_PORT_WR8(sc, GMX0_TX0_CLK, tx_clk); + _GMX_PORT_WR8(sc, GMX0_TX0_SLOT, tx_slot); + _GMX_PORT_WR8(sc, GMX0_TX0_BURST, tx_burst); + _GMX_PORT_WR8(sc, GMX0_PRT0_CFG, prt_cfg); + + return 0; +} + +static int +cn30xxgmx_rgmii_timing(struct cn30xxgmx_port_softc *sc) +{ + int clk_set_setting; + uint64_t rx_frm_ctl; + + /* RGMII TX Threshold Registers, CN30XX-HM-1.0; + * > Number of 16-byte ticks to accumulate in the TX FIFO before + * > sending on the RGMII interface. This field should be large + * > enough to prevent underflow on the RGMII interface and must + * > never be set to less than 0x4. This register cannot exceed + * > the TX FIFO depth of 0x40 words. + */ + /* Default parameter of CN30XX */ + cn30xxgmx_tx_thresh(sc, 32); + + rx_frm_ctl = 0 | + /* RXN_FRM_CTL_NULL_DIS | (cn5xxx only) */ + /* RXN_FRM_CTL_PRE_ALIGN | (cn5xxx only) */ + /* RXN_FRM_CTL_PAD_LEN | (cn3xxx only) */ + /* RXN_FRM_CTL_VLAN_LEN | (cn3xxx only) */ + RXN_FRM_CTL_PRE_FREE | + RXN_FRM_CTL_CTL_SMAC | + RXN_FRM_CTL_CTL_MCST | + RXN_FRM_CTL_CTL_DRP | + RXN_FRM_CTL_PRE_STRP | + RXN_FRM_CTL_PRE_CHK; + if (sc->sc_quirks & OCTEON_ETH_QUIRKS_SEILX1_REVB) + rx_frm_ctl |= RXN_FRM_CTL_PRE_ALIGN; + cn30xxgmx_rx_frm_ctl_enable(sc, rx_frm_ctl); + + /* XXX PHY-dependent parameter */ + /* RGMII RX Clock-Delay Registers, CN30XX-HM-1.0; + * > Delay setting to place n RXC (RGMII receive clock) delay line. + * > The intrinsic delay can range from 50ps to 80ps per tap, + * > which corresponds to skews of 1.25ns to 2.00ns at 25 taps(CSR+1). + * > This is the best match for the RGMII specification which wants + * > 1ns - 2.6ns of skew. + */ + /* RGMII TX Clock-Delay Registers, CN30XX-HM-1.0; + * > Delay setting to place n TXC (RGMII transmit clock) delay line. + * > ... + */ + switch (sc->sc_quirks & OCTEON_ETH_QUIRKS_SEILX) { + case OCTEON_ETH_QUIRKS_SEILX: + /* + * Table.4-6, Summary of ASX Registers, SEIL_HS_v03; + */ + clk_set_setting = 0; + break; + default: + /* Default parameter of CN30XX */ + clk_set_setting = 24; + break; + } + + cn30xxasx_clk_set(sc->sc_port_asx, clk_set_setting); + + return 0; +} + +static int +cn30xxgmx_rgmii_set_mac_addr(struct cn30xxgmx_port_softc *sc, uint8_t *addr, + uint64_t mac) +{ + int i; + + cn30xxgmx_link_enable(sc, 0); + + sc->sc_mac = mac; + _GMX_PORT_WR8(sc, GMX0_SMAC0, mac); + for (i = 0; i < 6; i++) + _GMX_PORT_WR8(sc, cn30xxgmx_rx_adr_cam_regs[i], addr[i]); + + cn30xxgmx_link_enable(sc, 1); + + return 0; +} + +#define OCTEON_ETH_USE_GMX_CAM + +static int +cn30xxgmx_rgmii_set_filter(struct cn30xxgmx_port_softc *sc) +{ + struct ifnet *ifp = &sc->sc_port_ac->ac_if; +#ifdef OCTEON_ETH_USE_GMX_CAM + struct ether_multi *enm; + struct ether_multistep step; +#endif + uint64_t ctl = 0; + int multi = 0; + uint64_t cam_en = 0x01ULL; /* XXX */ + + cn30xxgmx_link_enable(sc, 0); + + if (ISSET(ifp->if_flags, IFF_BROADCAST)) { + dprintf("accept broadcast\n"); + SET(ctl, RXN_ADR_CTL_BCST); + } + if (ISSET(ifp->if_flags, IFF_PROMISC)) { + dprintf("promiscas(reject cam)\n"); + CLR(ctl, RXN_ADR_CTL_CAM_MODE); + } else { + dprintf("not promiscas(accept cam)\n"); + SET(ctl, RXN_ADR_CTL_CAM_MODE); + } + +#ifdef OCTEON_ETH_USE_GMX_CAM + /* + * Note first entry is self MAC address; other 7 entires are available + * for multicast addresses. + */ + + ETHER_FIRST_MULTI(step, sc->sc_port_ac, enm); + while (enm != NULL) { + int i; + + dprintf("%d: lo(%02x:%02x:%02x:%02x:%02x:%02x) - " + "hi(%02x:%02x:%02x:%02x:%02x:%02x)\n", + multi + 1, + enm->enm_addrlo[0], enm->enm_addrlo[1], + enm->enm_addrlo[2], enm->enm_addrlo[3], + enm->enm_addrlo[4], enm->enm_addrlo[5], + enm->enm_addrhi[0], enm->enm_addrhi[1], + enm->enm_addrhi[2], enm->enm_addrhi[3], + enm->enm_addrhi[4], enm->enm_addrhi[5]); + if (bcmp(enm->enm_addrlo, enm->enm_addrhi, ETHER_ADDR_LEN)) { + dprintf("all multicast\n"); + SET(ifp->if_flags, IFF_ALLMULTI); + goto setmulti; + } + multi++; + + /* XXX */ + if (multi >= 8) { + SET(ifp->if_flags, IFF_ALLMULTI); + goto setmulti; + } + + SET(cam_en, 1ULL << multi); /* XXX */ + + for (i = 0; i < 6; i++) { + uint64_t tmp; + + /* XXX */ + tmp = _GMX_PORT_RD8(sc, cn30xxgmx_rx_adr_cam_regs[i]); + CLR(tmp, 0xffULL << (8 * multi)); + SET(tmp, (uint64_t)enm->enm_addrlo[i] << (8 * multi)); + _GMX_PORT_WR8(sc, cn30xxgmx_rx_adr_cam_regs[i], tmp); + + } + for (i = 0; i < 6; i++) + dprintf("cam%d = %016llx\n", i, + _GMX_PORT_RD8(sc, cn30xxgmx_rx_adr_cam_regs[i])); + ETHER_NEXT_MULTI(step, enm); + } + CLR(ifp->if_flags, IFF_ALLMULTI); + + OCTEON_ETH_KASSERT(enm == NULL); +#else + /* + * XXX + * Never use DMAC filter for multicast addresses, but register only + * single entry for self address. FreeBSD code do so. + */ + SET(ifp->if_flags, IFF_ALLMULTI); + goto setmulti; +#endif + +setmulti: + if (ISSET(ifp->if_flags, IFF_ALLMULTI) || + ISSET(ifp->if_flags, IFF_PROMISC)) { + /* XXX */ + dprintf("accept all multicast\n"); + SET(ctl, RXN_ADR_CTL_MCST_ACCEPT); + } else if (multi) { + /* XXX */ + dprintf("use cam\n"); + SET(ctl, RXN_ADR_CTL_MCST_AFCAM); + } else { + /* XXX */ + dprintf("reject all multicast\n"); + SET(ctl, RXN_ADR_CTL_MCST_REJECT); + } + + /* XXX */ + if (ISSET(ifp->if_flags, IFF_PROMISC)) { + cam_en = 0x00ULL; + } else if (ISSET(ifp->if_flags, IFF_ALLMULTI)) { + cam_en = 0x01ULL; + } + + dprintf("ctl = %llx, cam_en = %llx\n", ctl, cam_en); + _GMX_PORT_WR8(sc, GMX0_RX0_ADR_CTL, ctl); + _GMX_PORT_WR8(sc, GMX0_RX0_ADR_CAM_EN, cam_en); + + cn30xxgmx_link_enable(sc, 1); + + return 0; +} + +void +cn30xxgmx_stats(struct cn30xxgmx_port_softc *sc) +{ + struct ifnet *ifp = &sc->sc_port_ac->ac_if; + uint64_t tmp; + + ifp->if_ipackets += + (uint32_t)_GMX_PORT_RD8(sc, GMX0_RX0_STATS_PKTS); + ifp->if_ierrors += + (uint32_t)_GMX_PORT_RD8(sc, GMX0_RX0_STATS_PKTS_BAD); + ifp->if_iqdrops += + (uint32_t)_GMX_PORT_RD8(sc, GMX0_RX0_STATS_PKTS_DRP); + ifp->if_opackets += + (uint32_t)_GMX_PORT_RD8(sc, GMX0_TX0_STAT3); + tmp = _GMX_PORT_RD8(sc, GMX0_TX0_STAT0); + ifp->if_oerrors += + (uint32_t)tmp + ((uint32_t)(tmp >> 32) * 16); + ifp->if_collisions += ((uint32_t)tmp) * 16; + tmp = _GMX_PORT_RD8(sc, GMX0_TX0_STAT1); + ifp->if_collisions += + ((uint32_t)tmp * 2) + (uint32_t)(tmp >> 32); + tmp = _GMX_PORT_RD8(sc, GMX0_TX0_STAT9); + ifp->if_oerrors += (uint32_t)(tmp >> 32); +} + +/* ---- DMAC filter */ + +#ifdef notyet +/* + * DMAC filter configuration + * accept all + * reject 0 addrs (virtually accept all?) + * reject N addrs + * accept N addrs + * accept 0 addrs (virtually reject all?) + * reject all + */ + +/* XXX local namespace */ +#define _POLICY CN30XXGMX_FILTER_POLICY +#define _POLICY_ACCEPT_ALL CN30XXGMX_FILTER_POLICY_ACCEPT_ALL +#define _POLICY_ACCEPT CN30XXGMX_FILTER_POLICY_ACCEPT +#define _POLICY_REJECT CN30XXGMX_FILTER_POLICY_REJECT +#define _POLICY_REJECT_ALL CN30XXGMX_FILTER_POLICY_REJECT_ALL + +static int cn30xxgmx_setfilt_addrs(struct cn30xxgmx_port_softc *, + size_t, uint8_t **); + +int +cn30xxgmx_setfilt(struct cn30xxgmx_port_softc *sc, enum _POLICY policy, + size_t naddrs, uint8_t **addrs) +{ + uint64_t rx_adr_ctl; + + KASSERT(policy >= _POLICY_ACCEPT_ALL); + KASSERT(policy <= _POLICY_REJECT_ALL); + + rx_adr_ctl = _GMX_PORT_RD8(sc, GMX0_RX0_ADR_CTL); + CLR(rx_adr_ctl, RXN_ADR_CTL_CAM_MODE | RXN_ADR_CTL_MCST); + + switch (policy) { + case _POLICY_ACCEPT_ALL: + case _POLICY_REJECT_ALL: + KASSERT(naddrs == 0); + KASSERT(addrs == NULL); + + SET(rx_adr_ctl, (policy == _POLICY_ACCEPT_ALL) ? + RXN_ADR_CTL_MCST_ACCEPT : RXN_ADR_CTL_MCST_REJECT); + break; + case _POLICY_ACCEPT: + case _POLICY_REJECT: + if (naddrs > CN30XXGMX_FILTER_NADDRS_MAX) + return E2BIG; + SET(rx_adr_ctl, (policy == _POLICY_ACCEPT) ? + RXN_ADR_CTL_CAM_MODE : 0); + SET(rx_adr_ctl, RXN_ADR_CTL_MCST_AFCAM); + /* set GMX0_RXN_ADR_CAM_EN, GMX0_RXN_ADR_CAM[0-5] */ + cn30xxgmx_setfilt_addrs(sc, naddrs, addrs); + break; + } + + /* set GMX0_RXN_ADR_CTL[MCST] */ + _GMX_PORT_WR8(sc, GMX0_RX0_ADR_CTL, rx_adr_ctl); + + return 0; +} + +static int +cn30xxgmx_setfilt_addrs(struct cn30xxgmx_port_softc *sc, size_t naddrs, + uint8_t **addrs) +{ + uint64_t rx_adr_cam_en; + uint64_t rx_adr_cam_addrs[CN30XXGMX_FILTER_NADDRS_MAX]; + int i, j; + + KASSERT(naddrs <= CN30XXGMX_FILTER_NADDRS_MAX); + + rx_adr_cam_en = 0; + (void)memset(rx_adr_cam_addrs, 0, sizeof(rx_adr_cam_addrs)); + + for (i = 0; i < naddrs; i++) { + SET(rx_adr_cam_en, 1ULL << i); + for (j = 0; j < 6; j++) + SET(rx_adr_cam_addrs[j], + (uint64_t)addrs[i][j] << (8 * i)); + } + + /* set GMX0_RXN_ADR_CAM_EN, GMX0_RXN_ADR_CAM[0-5] */ + _GMX_PORT_WR8(sc, GMX0_RX0_ADR_CAM_EN, rx_adr_cam_en); + for (j = 0; j < 6; j++) + _GMX_PORT_WR8(sc, cn30xxgmx_rx_adr_cam_regs[j], + rx_adr_cam_addrs[j]); + + return 0; +} +#endif + +/* ---- interrupt */ + +#ifdef OCTEON_ETH_DEBUG +void cn30xxgmx_intr_rml_gmx0(void); + +int cn30xxgmx_intr_rml_verbose; + +/* tx - per unit (gmx0, gmx1, ...) */ +static const struct octeon_evcnt_entry cn30xxgmx_intr_evcnt_tx_entries[] = { +#define _ENTRY(name, type, parent, descr) \ + OCTEON_EVCNT_ENTRY(struct cn30xxgmx_softc, name, type, parent, descr) + _ENTRY(latecol, MISC, NULL, "tx late collision"), + _ENTRY(xsdef, MISC, NULL, "tx excessive deferral"), + _ENTRY(xscol, MISC, NULL, "tx excessive collision"), + _ENTRY(undflw, MISC, NULL, "tx underflow"), + _ENTRY(pkonxa, MISC, NULL, "tx port addr out-of-range") +#undef _ENTRY +}; + +/* rx - per port (gmx0:0, gmx0:1, ...) */ +static const struct octeon_evcnt_entry cn30xxgmx_intr_evcnt_rx_entries[] = { +#define _ENTRY(name, type, parent, descr) \ + OCTEON_EVCNT_ENTRY(struct cn30xxgmx_port_softc, name, type, parent, descr) + _ENTRY(minerr, MISC, NULL, "rx min error"), + _ENTRY(carext, MISC, NULL, "rx carrier error"), + _ENTRY(maxerr, MISC, NULL, "rx max error"), + _ENTRY(jabber, MISC, NULL, "rx jabber error"), + _ENTRY(fcserr, MISC, NULL, "rx fcs error"), + _ENTRY(alnerr, MISC, NULL, "rx align error"), + _ENTRY(lenerr, MISC, NULL, "rx length error"), + _ENTRY(rcverr, MISC, NULL, "rx receive error"), + _ENTRY(skperr, MISC, NULL, "rx skip error"), + _ENTRY(niberr, MISC, NULL, "rx nibble error"), + _ENTRY(ovrerr, MISC, NULL, "rx overflow error"), + _ENTRY(pckterr, MISC, NULL, "rx packet error"), + _ENTRY(rsverr, MISC, NULL, "rx reserved opcode error"), + _ENTRY(falerr, MISC, NULL, "rx false carrier error"), + _ENTRY(coldet, MISC, NULL, "rx collision detect"), + _ENTRY(ifgerr, MISC, NULL, "rx ifg error") +#undef _ENTRY +}; + +void +cn30xxgmx_intr_evcnt_attach(struct cn30xxgmx_softc *sc) +{ + struct cn30xxgmx_port_softc *port_sc; + int i; + + OCTEON_EVCNT_ATTACH_EVCNTS(sc, cn30xxgmx_intr_evcnt_tx_entries, + sc->sc_dev.dv_xname); + for (i = 0; i < sc->sc_nports; i++) { + port_sc = &sc->sc_ports[i]; + OCTEON_EVCNT_ATTACH_EVCNTS(port_sc, cn30xxgmx_intr_evcnt_rx_entries, + sc->sc_dev.dv_xname); + } +} + +void +cn30xxgmx_intr_rml_gmx0(void) +{ + struct cn30xxgmx_port_softc *sc = NULL/* XXX gcc */; + int i; + uint64_t reg = 0/* XXX gcc */; + + cn30xxgmx_intr_evcnt.ev_count++; + + sc = __cn30xxgmx_port_softc[0]; + if (sc == NULL) + return; + + /* GMX0_RXn_INT_REG or GMX0_TXn_INT_REG */ + reg = cn30xxgmx_get_tx_int_reg(sc); + if (cn30xxgmx_intr_rml_verbose && reg != 0) + printf("%s: GMX_TX_INT_REG=0x%016" PRIx64 "\n", __func__, reg); + if (reg & TX_INT_REG_LATE_COL) + OCTEON_EVCNT_INC(sc->sc_port_gmx, latecol); + if (reg & TX_INT_REG_XSDEF) + OCTEON_EVCNT_INC(sc->sc_port_gmx, xsdef); + if (reg & TX_INT_REG_XSCOL) + OCTEON_EVCNT_INC(sc->sc_port_gmx, xscol); + if (reg & TX_INT_REG_UNDFLW) + OCTEON_EVCNT_INC(sc->sc_port_gmx, undflw); + if (reg & TX_INT_REG_PKO_NXA) + OCTEON_EVCNT_INC(sc->sc_port_gmx, pkonxa); + + for (i = 0; i < GMX_PORT_NUNITS; i++) { + sc = __cn30xxgmx_port_softc[i]; + if (sc == NULL) + continue; + reg = cn30xxgmx_get_rx_int_reg(sc); + if (cn30xxgmx_intr_rml_verbose) + printf("%s: GMX_RX_INT_REG=0x%016" PRIx64 "\n", __func__, reg); + if (reg & RXN_INT_REG_MINERR) + OCTEON_EVCNT_INC(sc, minerr); + if (reg & RXN_INT_REG_CAREXT) + OCTEON_EVCNT_INC(sc, carext); + if (reg & RXN_INT_REG_JABBER) + OCTEON_EVCNT_INC(sc, jabber); + if (reg & RXN_INT_REG_FCSERR) + OCTEON_EVCNT_INC(sc, fcserr); + if (reg & RXN_INT_REG_ALNERR) + OCTEON_EVCNT_INC(sc, alnerr); + if (reg & RXN_INT_REG_LENERR) + OCTEON_EVCNT_INC(sc, lenerr); + if (reg & RXN_INT_REG_RCVERR) + OCTEON_EVCNT_INC(sc, rcverr); + if (reg & RXN_INT_REG_SKPERR) + OCTEON_EVCNT_INC(sc, skperr); + if (reg & RXN_INT_REG_NIBERR) + OCTEON_EVCNT_INC(sc, niberr); + if (reg & RXN_INT_REG_OVRERR) + OCTEON_EVCNT_INC(sc, ovrerr); + if (reg & RXN_INT_REG_PCTERR) + OCTEON_EVCNT_INC(sc, pckterr); + if (reg & RXN_INT_REG_RSVERR) + OCTEON_EVCNT_INC(sc, rsverr); + if (reg & RXN_INT_REG_FALERR) + OCTEON_EVCNT_INC(sc, falerr); + if (reg & RXN_INT_REG_COLDET) + OCTEON_EVCNT_INC(sc, coldet); + if (reg & RXN_INT_REG_IFGERR) + OCTEON_EVCNT_INC(sc, ifgerr); + } +} + +#ifdef notyet +void +cn30xxgmx_intr_rml_gmx1(void) +{ + uint64_t reg = 0/* XXX gcc */; + + /* GMX1_RXn_INT_REG or GMX1_TXn_INT_REG */ +} +#endif + +int +cn30xxgmx_intr_drop(void *arg) +{ + octeon_write_csr(CIU_INT0_SUM0, CIU_INTX_SUM0_GMX_DRP); + cn30xxgmx_intr_drop_evcnt.ev_count++; + return (1); +} + +uint64_t +cn30xxgmx_get_rx_int_reg(struct cn30xxgmx_port_softc *sc) +{ + uint64_t reg; + uint64_t rx_int_reg = 0; + + reg = _GMX_PORT_RD8(sc, GMX0_RX0_INT_REG); + /* clear */ + SET(rx_int_reg, 0 | + RXN_INT_REG_PHY_DUPX | + RXN_INT_REG_PHY_SPD | + RXN_INT_REG_PHY_LINK | + RXN_INT_REG_IFGERR | + RXN_INT_REG_COLDET | + RXN_INT_REG_FALERR | + RXN_INT_REG_RSVERR | + RXN_INT_REG_PCTERR | + RXN_INT_REG_OVRERR | + RXN_INT_REG_NIBERR | + RXN_INT_REG_SKPERR | + RXN_INT_REG_RCVERR | + RXN_INT_REG_LENERR | + RXN_INT_REG_ALNERR | + RXN_INT_REG_FCSERR | + RXN_INT_REG_JABBER | + RXN_INT_REG_MAXERR | + RXN_INT_REG_CAREXT | + RXN_INT_REG_MINERR); + _GMX_PORT_WR8(sc, GMX0_RX0_INT_REG, rx_int_reg); + + return reg; +} + +uint64_t +cn30xxgmx_get_tx_int_reg(struct cn30xxgmx_port_softc *sc) +{ + uint64_t reg; + uint64_t tx_int_reg = 0; + + reg = _GMX_PORT_RD8(sc, GMX0_TX_INT_REG); + /* clear */ + SET(tx_int_reg, 0 | + TX_INT_REG_LATE_COL | + TX_INT_REG_XSDEF | + TX_INT_REG_XSCOL | + TX_INT_REG_UNDFLW | + TX_INT_REG_PKO_NXA); + _GMX_PORT_WR8(sc, GMX0_TX_INT_REG, tx_int_reg); + + return reg; +} +#endif /* OCTEON_ETH_DEBUG */ + +/* ---- debug */ + +#ifdef OCTEON_ETH_DEBUG +#define _ENTRY(x) { #x, x##_BITS, x } + +struct cn30xxgmx_dump_reg_ { + const char *name; + const char *format; + size_t offset; +}; + +static const struct cn30xxgmx_dump_reg_ cn30xxgmx_dump_regs_[] = { + _ENTRY(GMX0_SMAC0), + _ENTRY(GMX0_BIST0), + _ENTRY(GMX0_RX_PRTS), + _ENTRY(GMX0_RX_BP_DROP0), + _ENTRY(GMX0_RX_BP_DROP1), + _ENTRY(GMX0_RX_BP_DROP2), + _ENTRY(GMX0_RX_BP_ON0), + _ENTRY(GMX0_RX_BP_ON1), + _ENTRY(GMX0_RX_BP_ON2), + _ENTRY(GMX0_RX_BP_OFF0), + _ENTRY(GMX0_RX_BP_OFF1), + _ENTRY(GMX0_RX_BP_OFF2), + _ENTRY(GMX0_TX_PRTS), + _ENTRY(GMX0_TX_IFG), + _ENTRY(GMX0_TX_JAM), + _ENTRY(GMX0_TX_COL_ATTEMPT), + _ENTRY(GMX0_TX_PAUSE_PKT_DMAC), + _ENTRY(GMX0_TX_PAUSE_PKT_TYPE), + _ENTRY(GMX0_TX_OVR_BP), + _ENTRY(GMX0_TX_BP), + _ENTRY(GMX0_TX_CORRUPT), + _ENTRY(GMX0_RX_PRT_INFO), + _ENTRY(GMX0_TX_LFSR), + _ENTRY(GMX0_TX_INT_REG), + _ENTRY(GMX0_TX_INT_EN), + _ENTRY(GMX0_NXA_ADR), + _ENTRY(GMX0_BAD_REG), + _ENTRY(GMX0_STAT_BP), + _ENTRY(GMX0_TX_CLK_MSK0), + _ENTRY(GMX0_TX_CLK_MSK1), + _ENTRY(GMX0_RX_TX_STATUS), + _ENTRY(GMX0_INF_MODE), +}; + +static const struct cn30xxgmx_dump_reg_ cn30xxgmx_dump_port_regs_[] = { + _ENTRY(GMX0_RX0_INT_REG), + _ENTRY(GMX0_RX0_INT_EN), + _ENTRY(GMX0_PRT0_CFG), + _ENTRY(GMX0_RX0_FRM_CTL), + _ENTRY(GMX0_RX0_FRM_CHK), + _ENTRY(GMX0_RX0_FRM_MIN), + _ENTRY(GMX0_RX0_FRM_MAX), + _ENTRY(GMX0_RX0_JABBER), + _ENTRY(GMX0_RX0_DECISION), + _ENTRY(GMX0_RX0_UDD_SKP), + _ENTRY(GMX0_RX0_STATS_CTL), + _ENTRY(GMX0_RX0_IFG), + _ENTRY(GMX0_RX0_RX_INBND), + _ENTRY(GMX0_RX0_ADR_CTL), + _ENTRY(GMX0_RX0_ADR_CAM_EN), + _ENTRY(GMX0_RX0_ADR_CAM0), + _ENTRY(GMX0_RX0_ADR_CAM1), + _ENTRY(GMX0_RX0_ADR_CAM2), + _ENTRY(GMX0_RX0_ADR_CAM3), + _ENTRY(GMX0_RX0_ADR_CAM4), + _ENTRY(GMX0_RX0_ADR_CAM5), + _ENTRY(GMX0_TX0_CLK), + _ENTRY(GMX0_TX0_THRESH), + _ENTRY(GMX0_TX0_APPEND), + _ENTRY(GMX0_TX0_SLOT), + _ENTRY(GMX0_TX0_BURST), + _ENTRY(GMX0_TX0_PAUSE_PKT_TIME), + _ENTRY(GMX0_TX0_MIN_PKT), + _ENTRY(GMX0_TX0_PAUSE_PKT_INTERVAL), + _ENTRY(GMX0_TX0_SOFT_PAUSE), + _ENTRY(GMX0_TX0_PAUSE_TOGO), + _ENTRY(GMX0_TX0_PAUSE_ZERO), + _ENTRY(GMX0_TX0_STATS_CTL), + _ENTRY(GMX0_TX0_CTL), +}; + +static const struct cn30xxgmx_dump_reg_ cn30xxgmx_dump_port_stats_[] = { + _ENTRY(GMX0_RX0_STATS_PKTS), + _ENTRY(GMX0_RX0_STATS_OCTS), + _ENTRY(GMX0_RX0_STATS_PKTS_CTL), + _ENTRY(GMX0_RX0_STATS_OCTS_CTL), + _ENTRY(GMX0_RX0_STATS_PKTS_DMAC), + _ENTRY(GMX0_RX0_STATS_OCTS_DMAC), + _ENTRY(GMX0_RX0_STATS_PKTS_DRP), + _ENTRY(GMX0_RX0_STATS_OCTS_DRP), + _ENTRY(GMX0_RX0_STATS_PKTS_BAD), + _ENTRY(GMX0_TX0_STAT0), + _ENTRY(GMX0_TX0_STAT1), + _ENTRY(GMX0_TX0_STAT2), + _ENTRY(GMX0_TX0_STAT3), + _ENTRY(GMX0_TX0_STAT4), + _ENTRY(GMX0_TX0_STAT5), + _ENTRY(GMX0_TX0_STAT6), + _ENTRY(GMX0_TX0_STAT7), + _ENTRY(GMX0_TX0_STAT8), + _ENTRY(GMX0_TX0_STAT9), +}; + +void cn30xxgmx_dump_common(void); +void cn30xxgmx_dump_port0(void); +void cn30xxgmx_dump_port1(void); +void cn30xxgmx_dump_port2(void); +void cn30xxgmx_dump_port0_regs(void); +void cn30xxgmx_dump_port1_regs(void); +void cn30xxgmx_dump_port2_regs(void); +void cn30xxgmx_dump_port0_stats(void); +void cn30xxgmx_dump_port1_stats(void); +void cn30xxgmx_dump_port2_stats(void); +void cn30xxgmx_dump_port_regs(int); +void cn30xxgmx_dump_port_stats(int); +void cn30xxgmx_dump_common_x(int, const struct cn30xxgmx_dump_reg_ *, size_t); +void cn30xxgmx_dump_port_x(int, const struct cn30xxgmx_dump_reg_ *, size_t); +void cn30xxgmx_dump_x(int, const struct cn30xxgmx_dump_reg_ *, size_t, size_t, int); +void cn30xxgmx_dump_x_index(char *, size_t, int); + +void +cn30xxgmx_dump(void) +{ + cn30xxgmx_dump_common(); + cn30xxgmx_dump_port0(); + cn30xxgmx_dump_port1(); + cn30xxgmx_dump_port2(); +} + +void +cn30xxgmx_dump_common(void) +{ + cn30xxgmx_dump_common_x(0, cn30xxgmx_dump_regs_, + nitems(cn30xxgmx_dump_regs_)); +} + +void +cn30xxgmx_dump_port0(void) +{ + cn30xxgmx_dump_port_regs(0); + cn30xxgmx_dump_port_stats(0); +} + +void +cn30xxgmx_dump_port1(void) +{ + cn30xxgmx_dump_port_regs(1); + cn30xxgmx_dump_port_stats(1); +} + +void +cn30xxgmx_dump_port2(void) +{ + cn30xxgmx_dump_port_regs(2); + cn30xxgmx_dump_port_stats(2); +} + +void +cn30xxgmx_dump_port_regs(int portno) +{ + cn30xxgmx_dump_port_x(portno, cn30xxgmx_dump_port_regs_, + nitems(cn30xxgmx_dump_port_regs_)); +} + +void +cn30xxgmx_dump_port_stats(int portno) +{ + struct cn30xxgmx_port_softc *sc = __cn30xxgmx_port_softc[0]; + uint64_t rx_stats_ctl; + uint64_t tx_stats_ctl; + + rx_stats_ctl = _GMX_RD8(sc, GMX0_BASE_PORT_SIZE * portno + GMX0_RX0_STATS_CTL); + _GMX_WR8(sc, GMX0_BASE_PORT_SIZE * portno + GMX0_RX0_STATS_CTL, + rx_stats_ctl & ~RXN_STATS_CTL_RD_CLR); + tx_stats_ctl = _GMX_RD8(sc, GMX0_BASE_PORT_SIZE * portno + GMX0_TX0_STATS_CTL); + _GMX_WR8(sc, GMX0_BASE_PORT_SIZE * portno + GMX0_TX0_STATS_CTL, + tx_stats_ctl & ~TXN_STATS_CTL_RD_CLR); + cn30xxgmx_dump_port_x(portno, cn30xxgmx_dump_port_stats_, + nitems(cn30xxgmx_dump_port_stats_)); + _GMX_WR8(sc, GMX0_BASE_PORT_SIZE * portno + GMX0_RX0_STATS_CTL, rx_stats_ctl); + _GMX_WR8(sc, GMX0_BASE_PORT_SIZE * portno + GMX0_TX0_STATS_CTL, tx_stats_ctl); +} + +void +cn30xxgmx_dump_common_x(int portno, const struct cn30xxgmx_dump_reg_ *regs, size_t size) +{ + cn30xxgmx_dump_x(portno, regs, size, 0, 0); +} + +void +cn30xxgmx_dump_port_x(int portno, const struct cn30xxgmx_dump_reg_ *regs, size_t size) +{ + cn30xxgmx_dump_x(portno, regs, size, GMX0_BASE_PORT_SIZE * portno, 1); +} + +void +cn30xxgmx_dump_x(int portno, const struct cn30xxgmx_dump_reg_ *regs, size_t size, size_t base, int index) +{ + struct cn30xxgmx_port_softc *sc = __cn30xxgmx_port_softc[0]; + const struct cn30xxgmx_dump_reg_ *reg; + uint64_t tmp; + char name[64]; + char buf[512]; + int i; + + for (i = 0; i < (int)size; i++) { + reg = ®s[i]; + tmp = _GMX_RD8(sc, base + reg->offset); + + if (reg->format == NULL) + snprintf(buf, sizeof(buf), "%016" PRIx64, tmp); + else + bitmask_snprintf(tmp, reg->format, buf, sizeof(buf)); + + snprintf(name, sizeof(name), "%s", reg->name); + if (index > 0) + cn30xxgmx_dump_x_index(name, sizeof(name), portno); + + printf("\t%-24s: %s\n", name, buf); + } +} + +void +cn30xxgmx_dump_x_index(char *buf, size_t len, int index) +{ + static const char *patterns[] = { "_TX0_", "_RX0_", "_PRT0_" }; + int i; + + for (i = 0; i < (int)nitems(patterns); i++) { + char *p; + + p = strstr(buf, patterns[i]); + if (p == NULL) + continue; + p = strchr(p, '0'); + KASSERT(p != NULL); + *p = '0' + index; + return; + } +} + +void +cn30xxgmx_debug_reset(void) +{ + int i; + + for (i = 0; i < 3; i++) + cn30xxgmx_link_enable(__cn30xxgmx_port_softc[i], 0); + for (i = 0; i < 3; i++) + cn30xxgmx_link_enable(__cn30xxgmx_port_softc[i], 1); +} +#endif |