#include <sys/param.h>
#include <sys/bus.h>
#include <sys/errno.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/module.h>
#include <sys/socket.h>
#include <sys/sockio.h>
#include <sys/sysctl.h>
#include <sys/systm.h>
#include <net/if.h>
#include <net/if_var.h>
#include <net/if_arp.h>
#include <net/ethernet.h>
#include <net/if_dl.h>
#include <net/if_media.h>
#include <net/if_types.h>
#include <machine/bus.h>
#include <dev/iicbus/iic.h>
#include <dev/iicbus/iiconf.h>
#include <dev/iicbus/iicbus.h>
#include <dev/mii/mii.h>
#include <dev/mii/miivar.h>
#include <dev/mdio/mdio.h>
#include <dev/clk/clk.h>
#include <dev/hwreset/hwreset.h>
#include <dev/fdt/fdt_common.h>
#include <dev/ofw/ofw_bus.h>
#include <dev/ofw/ofw_bus_subr.h>
#include <dev/etherswitch/etherswitch.h>
#include <dev/etherswitch/ar40xx/ar40xx_var.h>
#include <dev/etherswitch/ar40xx/ar40xx_reg.h>
#include <dev/etherswitch/ar40xx/ar40xx_hw.h>
#include <dev/etherswitch/ar40xx/ar40xx_phy.h>
#include <dev/etherswitch/ar40xx/ar40xx_hw_atu.h>
#include <dev/etherswitch/ar40xx/ar40xx_hw_mdio.h>
#include <dev/etherswitch/ar40xx/ar40xx_hw_psgmii.h>
#include "mdio_if.h"
#include "miibus_if.h"
#include "etherswitch_if.h"
static void
ar40xx_hw_psgmii_reg_write(struct ar40xx_softc *sc, uint32_t reg,
uint32_t val)
{
bus_space_write_4(sc->sc_psgmii_mem_tag, sc->sc_psgmii_mem_handle,
reg, val);
bus_space_barrier(sc->sc_psgmii_mem_tag, sc->sc_psgmii_mem_handle,
0, sc->sc_psgmii_mem_size, BUS_SPACE_BARRIER_WRITE);
}
static int
ar40xx_hw_psgmii_reg_read(struct ar40xx_softc *sc, uint32_t reg)
{
int ret;
bus_space_barrier(sc->sc_psgmii_mem_tag, sc->sc_psgmii_mem_handle,
0, sc->sc_psgmii_mem_size, BUS_SPACE_BARRIER_READ);
ret = bus_space_read_4(sc->sc_psgmii_mem_tag, sc->sc_psgmii_mem_handle,
reg);
return (ret);
}
int
ar40xx_hw_psgmii_set_mac_mode(struct ar40xx_softc *sc, uint32_t mac_mode)
{
if (mac_mode == PORT_WRAPPER_PSGMII) {
ar40xx_hw_psgmii_reg_write(sc, AR40XX_PSGMII_MODE_CONTROL,
0x2200);
ar40xx_hw_psgmii_reg_write(sc, AR40XX_PSGMIIPHY_TX_CONTROL,
0x8380);
} else {
device_printf(sc->sc_dev, "WARNING: unknown MAC_MODE=%u\n",
mac_mode);
}
return (0);
}
int
ar40xx_hw_psgmii_single_phy_testing(struct ar40xx_softc *sc, int phy)
{
int j;
uint32_t tx_ok, tx_error;
uint32_t rx_ok, rx_error;
uint32_t tx_ok_high16;
uint32_t rx_ok_high16;
uint32_t tx_all_ok, rx_all_ok;
MDIO_WRITEREG(sc->sc_mdio_dev, phy, 0x0, 0x9000);
MDIO_WRITEREG(sc->sc_mdio_dev, phy, 0x0, 0x4140);
for (j = 0; j < AR40XX_PSGMII_CALB_NUM; j++) {
uint16_t status;
status = MDIO_READREG(sc->sc_mdio_dev, phy, 0x11);
if (status & AR40XX_PHY_SPEC_STATUS_LINK)
break;
DELAY(8 * 1000);
}
ar40xx_hw_phy_mmd_write(sc, phy, 7, 0x8029, 0x0000);
ar40xx_hw_phy_mmd_write(sc, phy, 7, 0x8029, 0x0003);
ar40xx_hw_phy_mmd_write(sc, phy, 7, 0x8020, 0xa000);
DELAY(60 * 1000);
tx_ok = ar40xx_hw_phy_mmd_read(sc, phy, 7, 0x802e);
tx_ok_high16 = ar40xx_hw_phy_mmd_read(sc, phy, 7, 0x802d);
tx_error = ar40xx_hw_phy_mmd_read(sc, phy, 7, 0x802f);
rx_ok = ar40xx_hw_phy_mmd_read(sc, phy, 7, 0x802b);
rx_ok_high16 = ar40xx_hw_phy_mmd_read(sc, phy, 7, 0x802a);
rx_error = ar40xx_hw_phy_mmd_read(sc, phy, 7, 0x802c);
tx_all_ok = tx_ok + (tx_ok_high16 << 16);
rx_all_ok = rx_ok + (rx_ok_high16 << 16);
if (tx_all_ok == 0x1000 && tx_error == 0) {
sc->sc_psgmii.phy_t_status &= ~(1U << phy);
} else {
device_printf(sc->sc_dev, "TX_OK=%d, tx_error=%d RX_OK=%d"
" rx_error=%d\n",
tx_all_ok, tx_error, rx_all_ok, rx_error);
device_printf(sc->sc_dev,
"PHY %d single test PSGMII issue happen!\n", phy);
sc->sc_psgmii.phy_t_status |= BIT(phy);
}
MDIO_WRITEREG(sc->sc_mdio_dev, phy, 0x0, 0x1840);
return (0);
}
int
ar40xx_hw_psgmii_all_phy_testing(struct ar40xx_softc *sc)
{
int phy, j;
MDIO_WRITEREG(sc->sc_mdio_dev, 0x1f, 0x0, 0x9000);
MDIO_WRITEREG(sc->sc_mdio_dev, 0x1f, 0x0, 0x4140);
for (j = 0; j < AR40XX_PSGMII_CALB_NUM; j++) {
for (phy = 0; phy < AR40XX_NUM_PORTS - 1; phy++) {
uint16_t status;
status = MDIO_READREG(sc->sc_mdio_dev, phy, 0x11);
if (!(status & (1U << 10)))
break;
}
if (phy >= (AR40XX_NUM_PORTS - 1))
break;
DELAY(8*1000);
}
ar40xx_hw_phy_mmd_write(sc, 0x1f, 7, 0x8029, 0x0000);
ar40xx_hw_phy_mmd_write(sc, 0x1f, 7, 0x8029, 0x0003);
ar40xx_hw_phy_mmd_write(sc, 0x1f, 7, 0x8020, 0xa000);
DELAY(60*1000);
for (phy = 0; phy < AR40XX_NUM_PORTS - 1; phy++) {
uint32_t tx_ok, tx_error;
uint32_t rx_ok, rx_error;
uint32_t tx_ok_high16;
uint32_t rx_ok_high16;
uint32_t tx_all_ok, rx_all_ok;
tx_ok = ar40xx_hw_phy_mmd_read(sc, phy, 7, 0x802e);
tx_ok_high16 = ar40xx_hw_phy_mmd_read(sc, phy, 7, 0x802d);
tx_error = ar40xx_hw_phy_mmd_read(sc, phy, 7, 0x802f);
rx_ok = ar40xx_hw_phy_mmd_read(sc, phy, 7, 0x802b);
rx_ok_high16 = ar40xx_hw_phy_mmd_read(sc, phy, 7, 0x802a);
rx_error = ar40xx_hw_phy_mmd_read(sc, phy, 7, 0x802c);
tx_all_ok = tx_ok + (tx_ok_high16<<16);
rx_all_ok = rx_ok + (rx_ok_high16<<16);
if (tx_all_ok == 0x1000 && tx_error == 0) {
sc->sc_psgmii.phy_t_status &= ~(1U << (phy + 8));
} else {
device_printf(sc->sc_dev,
"PHY%d test see issue! (tx_all_ok=%u,"
" rx_all_ok=%u, tx_error=%u, rx_error=%u)\n",
phy, tx_all_ok, rx_all_ok, tx_error, rx_error);
sc->sc_psgmii.phy_t_status |= (1U << (phy + 8));
}
}
device_printf(sc->sc_dev, "PHY all test 0x%x\n",
sc->sc_psgmii.phy_t_status);
return (0);
}
int
ar40xx_hw_malibu_psgmii_ess_reset(struct ar40xx_softc *sc)
{
device_printf(sc->sc_dev, "%s: called\n", __func__);
uint32_t i;
MDIO_WRITEREG(sc->sc_mdio_dev, 5, 0x0, 0x005b);
MDIO_WRITEREG(sc->sc_mdio_dev, 5, 0x0, 0x001b);
MDIO_WRITEREG(sc->sc_mdio_dev, 5, 0x0, 0x005b);
for (i = 0; i < AR40XX_PSGMII_CALB_NUM; i++) {
uint32_t status;
status = ar40xx_hw_phy_mmd_read(sc, 5, 1, 0x28);
if (status & (1U << 0))
break;
DELAY(2000);
}
MDIO_WRITEREG(sc->sc_mdio_dev, 5, 0x1a, 0x2230);
ar40xx_hw_ess_reset(sc);
for (i = 0; i < AR40XX_PSGMII_CALB_NUM; i++) {
uint32_t status;
status = ar40xx_hw_psgmii_reg_read(sc, 0xa0);
if (status & (1U << 0))
break;
DELAY(2000);
}
MDIO_WRITEREG(sc->sc_mdio_dev, 5, 0x1a, 0x3230);
MDIO_WRITEREG(sc->sc_mdio_dev, 5, 0x0, 0x005f);
return (0);
}
int
ar40xx_hw_psgmii_self_test(struct ar40xx_softc *sc)
{
uint32_t i, phy, reg;
device_printf(sc->sc_dev, "%s: called\n", __func__);
ar40xx_hw_malibu_psgmii_ess_reset(sc);
MDIO_WRITEREG(sc->sc_mdio_dev, 4, 0x1f, 0x8500);
for (phy = 0; phy < AR40XX_NUM_PORTS - 1; phy++) {
ar40xx_hw_phy_mmd_write(sc, phy, 7, 0x8028, 0x801f);
}
MDIO_WRITEREG(sc->sc_mdio_dev, 0x1f, 0x0, 0x1840);
ar40xx_hw_phy_mmd_write(sc, 0x1f, 7, 0x8021, 0x1000);
ar40xx_hw_phy_mmd_write(sc, 0x1f, 7, 0x8062, 0x05e0);
MDIO_WRITEREG(sc->sc_mdio_dev, 0x1f, 0x10, 0x6800);
for (i = 0; i < AR40XX_PSGMII_CALB_NUM; i++) {
sc->sc_psgmii.phy_t_status = 0;
for (phy = 0; phy < AR40XX_NUM_PORTS - 1; phy++) {
AR40XX_REG_BARRIER_READ(sc);
reg = AR40XX_REG_READ(sc,
AR40XX_REG_PORT_LOOKUP(phy + 1));
reg |= AR40XX_PORT_LOOKUP_LOOPBACK;
AR40XX_REG_WRITE(sc,
AR40XX_REG_PORT_LOOKUP(phy + 1), reg);
AR40XX_REG_BARRIER_WRITE(sc);
}
for (phy = 0; phy < AR40XX_NUM_PORTS - 1; phy++)
ar40xx_hw_psgmii_single_phy_testing(sc, phy);
ar40xx_hw_psgmii_all_phy_testing(sc);
if (sc->sc_psgmii.phy_t_status)
ar40xx_hw_malibu_psgmii_ess_reset(sc);
else
break;
}
if (i >= AR40XX_PSGMII_CALB_NUM)
device_printf(sc->sc_dev, "PSGMII cannot recover\n");
else
device_printf(sc->sc_dev,
"PSGMII recovered after %d times reset\n", i);
ar40xx_hw_phy_mmd_write(sc, 0x1f, 7, 0x8021, 0x0);
ar40xx_hw_phy_mmd_write(sc, 0x1f, 7, 0x8029, 0x0);
ar40xx_hw_phy_mmd_write(sc, 0x1f, 7, 0x8020, 0x0);
return (0);
}
int
ar40xx_hw_psgmii_self_test_clean(struct ar40xx_softc *sc)
{
uint32_t reg;
int phy;
device_printf(sc->sc_dev, "%s: called\n", __func__);
MDIO_WRITEREG(sc->sc_mdio_dev, 0x1f, 0x10, 0x6860);
MDIO_WRITEREG(sc->sc_mdio_dev, 0x1f, 0x0, 0x9040);
for (phy = 0; phy < AR40XX_NUM_PORTS - 1; phy++) {
reg = AR40XX_REG_READ(sc, AR40XX_REG_PORT_LOOKUP(phy + 1));
reg &= ~AR40XX_PORT_LOOKUP_LOOPBACK;
AR40XX_REG_WRITE(sc, AR40XX_REG_PORT_LOOKUP(phy + 1), reg);
AR40XX_REG_BARRIER_WRITE(sc);
ar40xx_hw_phy_mmd_write(sc, phy, 7, 0x8028, 0x001f);
}
ar40xx_hw_atu_flush_all(sc);
return (0);
}
int
ar40xx_hw_psgmii_init_config(struct ar40xx_softc *sc)
{
uint32_t reg;
reg = ar40xx_hw_psgmii_reg_read(sc, 0x78c);
device_printf(sc->sc_dev,
"%s: PSGMIIPHY_PLL_VCO_RELATED_CTRL=0x%08x\n", __func__, reg);
reg = ar40xx_hw_psgmii_reg_read(sc, 0x09c);
device_printf(sc->sc_dev,
"%s: PSGMIIPHY_VCO_CALIBRATION_CTRL=0x%08x\n", __func__, reg);
return (0);
}