/*-
 * SPDX-License-Identifier: BSD-2-Clause
 *
 * Copyright (c) 2011-2012 Stefan Bethke.
 * Copyright (c) 2014 Adrian Chadd.
 * 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$
 */

#include <sys/param.h>
#include <sys/bus.h>
#include <sys/errno.h>
#include <sys/kernel.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_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/etherswitch/etherswitch.h>

#include <dev/etherswitch/arswitch/arswitchreg.h>
#include <dev/etherswitch/arswitch/arswitchvar.h>
#include <dev/etherswitch/arswitch/arswitch_reg.h>
#include <dev/etherswitch/arswitch/arswitch_phy.h>
#include <dev/etherswitch/arswitch/arswitch_vlans.h>

#include <dev/etherswitch/arswitch/arswitch_8327.h>

#include "mdio_if.h"
#include "miibus_if.h"
#include "etherswitch_if.h"

/*
 * AR8327 TODO:
 *
 * There should be a default hardware setup hint set for the default
 * switch config.  Otherwise the default is "all ports in one vlangroup",
 * which means both CPU ports can see each other and that will quickly
 * lead to traffic storms/loops.
 */

/* Map port+led to register+shift */
struct ar8327_led_mapping ar8327_led_mapping[AR8327_NUM_PHYS][ETHERSWITCH_PORT_MAX_LEDS] =
{
	{	/* PHY0 */
		{AR8327_REG_LED_CTRL0, 14 },
		{AR8327_REG_LED_CTRL1, 14 },
		{AR8327_REG_LED_CTRL2, 14 }
	},
	{	/* PHY1 */
		{AR8327_REG_LED_CTRL3, 8  },
		{AR8327_REG_LED_CTRL3, 10 },
		{AR8327_REG_LED_CTRL3, 12 }
	},
	{	/* PHY2 */
		{AR8327_REG_LED_CTRL3, 14 },
		{AR8327_REG_LED_CTRL3, 16 },
		{AR8327_REG_LED_CTRL3, 18 }
	},
	{	/* PHY3 */
		{AR8327_REG_LED_CTRL3, 20 },
		{AR8327_REG_LED_CTRL3, 22 },
		{AR8327_REG_LED_CTRL3, 24 }
	},
	{	/* PHY4 */
		{AR8327_REG_LED_CTRL0, 30 },
		{AR8327_REG_LED_CTRL1, 30 },
		{AR8327_REG_LED_CTRL2, 30 }
	}
};

static int
ar8327_vlan_op(struct arswitch_softc *sc, uint32_t op, uint32_t vid,
    uint32_t data)
{
	int err;

	/*
	 * Wait for the "done" bit to finish.
	 */
	if (arswitch_waitreg(sc->sc_dev, AR8327_REG_VTU_FUNC1,
	    AR8327_VTU_FUNC1_BUSY, 0, 5))
		return (EBUSY);

	/*
	 * If it's a "load" operation, then ensure 'data' is loaded
	 * in first.
	 */
	if ((op & AR8327_VTU_FUNC1_OP) == AR8327_VTU_FUNC1_OP_LOAD) {
		err = arswitch_writereg(sc->sc_dev, AR8327_REG_VTU_FUNC0, data);
		if (err)
			return (err);
	}

	/*
	 * Set the VID.
	 */
	op |= ((vid & 0xfff) << AR8327_VTU_FUNC1_VID_S);

	/*
	 * Set busy bit to start loading in the command.
	 */
	op |= AR8327_VTU_FUNC1_BUSY;
	arswitch_writereg(sc->sc_dev, AR8327_REG_VTU_FUNC1, op);

	/*
	 * Finally - wait for it to load.
	 */
	if (arswitch_waitreg(sc->sc_dev, AR8327_REG_VTU_FUNC1,
	    AR8327_VTU_FUNC1_BUSY, 0, 5))
		return (EBUSY);

	return (0);
}

static void
ar8327_phy_fixup(struct arswitch_softc *sc, int phy)
{
	if (bootverbose)
		device_printf(sc->sc_dev,
		    "%s: called; phy=%d; chiprev=%d\n", __func__,
		    phy,
		    sc->chip_rev);
	switch (sc->chip_rev) {
	case 1:
		/* For 100M waveform */
		arswitch_writedbg(sc->sc_dev, phy, 0, 0x02ea);
		/* Turn on Gigabit clock */
		arswitch_writedbg(sc->sc_dev, phy, 0x3d, 0x68a0);
		break;

	case 2:
		arswitch_writemmd(sc->sc_dev, phy, 0x7, 0x3c);
		arswitch_writemmd(sc->sc_dev, phy, 0x4007, 0x0);
		/* fallthrough */
	case 4:
		arswitch_writemmd(sc->sc_dev, phy, 0x3, 0x800d);
		arswitch_writemmd(sc->sc_dev, phy, 0x4003, 0x803f);

		arswitch_writedbg(sc->sc_dev, phy, 0x3d, 0x6860);
		arswitch_writedbg(sc->sc_dev, phy, 0x5, 0x2c46);
		arswitch_writedbg(sc->sc_dev, phy, 0x3c, 0x6000);
		break;
	}
}

static uint32_t
ar8327_get_pad_cfg(struct ar8327_pad_cfg *cfg)
{
	uint32_t t;

	if (!cfg)
		return (0);

	t = 0;
	switch (cfg->mode) {
	case AR8327_PAD_NC:
		break;

	case AR8327_PAD_MAC2MAC_MII:
		t = AR8327_PAD_MAC_MII_EN;
		if (cfg->rxclk_sel)
			t |= AR8327_PAD_MAC_MII_RXCLK_SEL;
		if (cfg->txclk_sel)
			t |= AR8327_PAD_MAC_MII_TXCLK_SEL;
		break;

	case AR8327_PAD_MAC2MAC_GMII:
		t = AR8327_PAD_MAC_GMII_EN;
		if (cfg->rxclk_sel)
			t |= AR8327_PAD_MAC_GMII_RXCLK_SEL;
		if (cfg->txclk_sel)
			t |= AR8327_PAD_MAC_GMII_TXCLK_SEL;
		break;

	case AR8327_PAD_MAC_SGMII:
		t = AR8327_PAD_SGMII_EN;

		/*
		 * WAR for the Qualcomm Atheros AP136 board.
		 * It seems that RGMII TX/RX delay settings needs to be
		 * applied for SGMII mode as well, The ethernet is not
		 * reliable without this.
		 */
		t |= cfg->txclk_delay_sel << AR8327_PAD_RGMII_TXCLK_DELAY_SEL_S;
		t |= cfg->rxclk_delay_sel << AR8327_PAD_RGMII_RXCLK_DELAY_SEL_S;
		if (cfg->rxclk_delay_en)
			t |= AR8327_PAD_RGMII_RXCLK_DELAY_EN;
		if (cfg->txclk_delay_en)
			t |= AR8327_PAD_RGMII_TXCLK_DELAY_EN;

		if (cfg->sgmii_delay_en)
			t |= AR8327_PAD_SGMII_DELAY_EN;

		break;

	case AR8327_PAD_MAC2PHY_MII:
		t = AR8327_PAD_PHY_MII_EN;
		if (cfg->rxclk_sel)
			t |= AR8327_PAD_PHY_MII_RXCLK_SEL;
		if (cfg->txclk_sel)
			t |= AR8327_PAD_PHY_MII_TXCLK_SEL;
		break;

	case AR8327_PAD_MAC2PHY_GMII:
		t = AR8327_PAD_PHY_GMII_EN;
		if (cfg->pipe_rxclk_sel)
			t |= AR8327_PAD_PHY_GMII_PIPE_RXCLK_SEL;
		if (cfg->rxclk_sel)
			t |= AR8327_PAD_PHY_GMII_RXCLK_SEL;
		if (cfg->txclk_sel)
			t |= AR8327_PAD_PHY_GMII_TXCLK_SEL;
		break;

	case AR8327_PAD_MAC_RGMII:
		t = AR8327_PAD_RGMII_EN;
		t |= cfg->txclk_delay_sel << AR8327_PAD_RGMII_TXCLK_DELAY_SEL_S;
		t |= cfg->rxclk_delay_sel << AR8327_PAD_RGMII_RXCLK_DELAY_SEL_S;
		if (cfg->rxclk_delay_en)
			t |= AR8327_PAD_RGMII_RXCLK_DELAY_EN;
		if (cfg->txclk_delay_en)
			t |= AR8327_PAD_RGMII_TXCLK_DELAY_EN;
		break;

	case AR8327_PAD_PHY_GMII:
		t = AR8327_PAD_PHYX_GMII_EN;
		break;

	case AR8327_PAD_PHY_RGMII:
		t = AR8327_PAD_PHYX_RGMII_EN;
		break;

	case AR8327_PAD_PHY_MII:
		t = AR8327_PAD_PHYX_MII_EN;
		break;
	}

	return (t);
}

/*
 * Map the hard-coded port config from the switch setup to
 * the chipset port config (status, duplex, flow, etc.)
 */
static uint32_t
ar8327_get_port_init_status(struct ar8327_port_cfg *cfg)
{
	uint32_t t;

	if (!cfg->force_link)
		return (AR8X16_PORT_STS_LINK_AUTO);

	t = AR8X16_PORT_STS_TXMAC | AR8X16_PORT_STS_RXMAC;
	t |= cfg->duplex ? AR8X16_PORT_STS_DUPLEX : 0;
	t |= cfg->rxpause ? AR8X16_PORT_STS_RXFLOW : 0;
	t |= cfg->txpause ? AR8X16_PORT_STS_TXFLOW : 0;

	switch (cfg->speed) {
	case AR8327_PORT_SPEED_10:
		t |= AR8X16_PORT_STS_SPEED_10;
		break;
	case AR8327_PORT_SPEED_100:
		t |= AR8X16_PORT_STS_SPEED_100;
		break;
	case AR8327_PORT_SPEED_1000:
		t |= AR8X16_PORT_STS_SPEED_1000;
		break;
	}

	return (t);
}

/*
 * Fetch the port data for the given port.
 *
 * This goes and does dirty things with the hints space
 * to determine what the configuration parameters should be.
 *
 * Returns 1 if the structure was successfully parsed and
 * the contents are valid; 0 otherwise.
 */
static int
ar8327_fetch_pdata_port(struct arswitch_softc *sc,
    struct ar8327_port_cfg *pcfg,
    int port)
{
	int val;
	char sbuf[128];

	/* Check if force_link exists */
	val = 0;
	snprintf(sbuf, 128, "port.%d.force_link", port);
	(void) resource_int_value(device_get_name(sc->sc_dev),
	    device_get_unit(sc->sc_dev),
	    sbuf, &val);
	if (val != 1)
		return (0);
	pcfg->force_link = 1;

	/* force_link is set; let's parse the rest of the fields */
	snprintf(sbuf, 128, "port.%d.speed", port);
	if (resource_int_value(device_get_name(sc->sc_dev),
	    device_get_unit(sc->sc_dev),
	    sbuf, &val) == 0) {
		switch (val) {
		case 10:
			pcfg->speed = AR8327_PORT_SPEED_10;
			break;
		case 100:
			pcfg->speed = AR8327_PORT_SPEED_100;
			break;
		case 1000:
			pcfg->speed = AR8327_PORT_SPEED_1000;
			break;
		default:
			device_printf(sc->sc_dev,
			    "%s: invalid port %d duplex value (%d)\n",
			    __func__,
			    port,
			    val);
			return (0);
		}
	}

	snprintf(sbuf, 128, "port.%d.duplex", port);
	if (resource_int_value(device_get_name(sc->sc_dev),
	    device_get_unit(sc->sc_dev),
	    sbuf, &val) == 0)
		pcfg->duplex = val;

	snprintf(sbuf, 128, "port.%d.txpause", port);
	if (resource_int_value(device_get_name(sc->sc_dev),
	    device_get_unit(sc->sc_dev),
	    sbuf, &val) == 0)
		pcfg->txpause = val;

	snprintf(sbuf, 128, "port.%d.rxpause", port);
	if (resource_int_value(device_get_name(sc->sc_dev),
	    device_get_unit(sc->sc_dev),
	    sbuf, &val) == 0)
		pcfg->rxpause = val;

#if 1
	device_printf(sc->sc_dev,
	    "%s: port %d: speed=%d, duplex=%d, txpause=%d, rxpause=%d\n",
	    __func__,
	    port,
	    pcfg->speed,
	    pcfg->duplex,
	    pcfg->txpause,
	    pcfg->rxpause);
#endif

	return (1);
}

/*
 * Parse the pad configuration from the boot hints.
 *
 * The (mostly optional) fields are:
 *
 * uint32_t mode;
 * uint32_t rxclk_sel;
 * uint32_t txclk_sel;
 * uint32_t txclk_delay_sel;
 * uint32_t rxclk_delay_sel;
 * uint32_t txclk_delay_en;
 * uint32_t rxclk_delay_en;
 * uint32_t sgmii_delay_en;
 * uint32_t pipe_rxclk_sel;
 *
 * If mode isn't in the hints, 0 is returned.
 * Else the structure is fleshed out and 1 is returned.
 */
static int
ar8327_fetch_pdata_pad(struct arswitch_softc *sc,
    struct ar8327_pad_cfg *pc,
    int pad)
{
	int val;
	char sbuf[128];

	/* Check if mode exists */
	val = 0;
	snprintf(sbuf, 128, "pad.%d.mode", pad);
	if (resource_int_value(device_get_name(sc->sc_dev),
	    device_get_unit(sc->sc_dev),
	    sbuf, &val) != 0)
		return (0);

	/* assume that 'mode' exists and was found */
	pc->mode = val;

	snprintf(sbuf, 128, "pad.%d.rxclk_sel", pad);
	if (resource_int_value(device_get_name(sc->sc_dev),
	    device_get_unit(sc->sc_dev),
	    sbuf, &val) == 0)
		pc->rxclk_sel = val;

	snprintf(sbuf, 128, "pad.%d.txclk_sel", pad);
	if (resource_int_value(device_get_name(sc->sc_dev),
	    device_get_unit(sc->sc_dev),
	    sbuf, &val) == 0)
		pc->txclk_sel = val;

	snprintf(sbuf, 128, "pad.%d.txclk_delay_sel", pad);
	if (resource_int_value(device_get_name(sc->sc_dev),
	    device_get_unit(sc->sc_dev),
	    sbuf, &val) == 0)
		pc->txclk_delay_sel = val;

	snprintf(sbuf, 128, "pad.%d.rxclk_delay_sel", pad);
	if (resource_int_value(device_get_name(sc->sc_dev),
	    device_get_unit(sc->sc_dev),
	    sbuf, &val) == 0)
		pc->rxclk_delay_sel = val;

	snprintf(sbuf, 128, "pad.%d.txclk_delay_en", pad);
	if (resource_int_value(device_get_name(sc->sc_dev),
	    device_get_unit(sc->sc_dev),
	    sbuf, &val) == 0)
		pc->txclk_delay_en = val;

	snprintf(sbuf, 128, "pad.%d.rxclk_delay_en", pad);
	if (resource_int_value(device_get_name(sc->sc_dev),
	    device_get_unit(sc->sc_dev),
	    sbuf, &val) == 0)
		pc->rxclk_delay_en = val;

	snprintf(sbuf, 128, "pad.%d.sgmii_delay_en", pad);
	if (resource_int_value(device_get_name(sc->sc_dev),
	    device_get_unit(sc->sc_dev),
	    sbuf, &val) == 0)
		pc->sgmii_delay_en = val;

	snprintf(sbuf, 128, "pad.%d.pipe_rxclk_sel", pad);
	if (resource_int_value(device_get_name(sc->sc_dev),
	    device_get_unit(sc->sc_dev),
	    sbuf, &val) == 0)
		pc->pipe_rxclk_sel = val;

	if (bootverbose) {
		device_printf(sc->sc_dev,
		    "%s: pad %d: mode=%d, rxclk_sel=%d, txclk_sel=%d, "
		    "txclk_delay_sel=%d, rxclk_delay_sel=%d, txclk_delay_en=%d, "
		    "rxclk_enable_en=%d, sgmii_delay_en=%d, pipe_rxclk_sel=%d\n",
		    __func__,
		    pad,
		    pc->mode,
		    pc->rxclk_sel,
		    pc->txclk_sel,
		    pc->txclk_delay_sel,
		    pc->rxclk_delay_sel,
		    pc->txclk_delay_en,
		    pc->rxclk_delay_en,
		    pc->sgmii_delay_en,
		    pc->pipe_rxclk_sel);
	}

	return (1);
}

/*
 * Fetch the SGMII configuration block from the boot hints.
 */
static int
ar8327_fetch_pdata_sgmii(struct arswitch_softc *sc,
    struct ar8327_sgmii_cfg *scfg)
{
	int val;

	/* sgmii_ctrl */
	val = 0;
	if (resource_int_value(device_get_name(sc->sc_dev),
	    device_get_unit(sc->sc_dev),
	    "sgmii.ctrl", &val) != 0)
		return (0);
	scfg->sgmii_ctrl = val;

	/* serdes_aen */
	val = 0;
	if (resource_int_value(device_get_name(sc->sc_dev),
	    device_get_unit(sc->sc_dev),
	    "sgmii.serdes_aen", &val) != 0)
		return (0);
	scfg->serdes_aen = val;

	return (1);
}

/*
 * Fetch the LED configuration from the boot hints.
 */
static int
ar8327_fetch_pdata_led(struct arswitch_softc *sc,
    struct ar8327_led_cfg *lcfg)
{
	int val;

	val = 0;
	if (resource_int_value(device_get_name(sc->sc_dev),
	    device_get_unit(sc->sc_dev),
	    "led.ctrl0", &val) != 0)
		return (0);
	lcfg->led_ctrl0 = val;

	val = 0;
	if (resource_int_value(device_get_name(sc->sc_dev),
	    device_get_unit(sc->sc_dev),
	    "led.ctrl1", &val) != 0)
		return (0);
	lcfg->led_ctrl1 = val;

	val = 0;
	if (resource_int_value(device_get_name(sc->sc_dev),
	    device_get_unit(sc->sc_dev),
	    "led.ctrl2", &val) != 0)
		return (0);
	lcfg->led_ctrl2 = val;

	val = 0;
	if (resource_int_value(device_get_name(sc->sc_dev),
	    device_get_unit(sc->sc_dev),
	    "led.ctrl3", &val) != 0)
		return (0);
	lcfg->led_ctrl3 = val;

	val = 0;
	if (resource_int_value(device_get_name(sc->sc_dev),
	    device_get_unit(sc->sc_dev),
	    "led.open_drain", &val) != 0)
		return (0);
	lcfg->open_drain = val;

	return (1);
}

/*
 * Initialise the ar8327 specific hardware features from
 * the hints provided in the boot environment.
 */
static int
ar8327_init_pdata(struct arswitch_softc *sc)
{
	struct ar8327_pad_cfg pc;
	struct ar8327_port_cfg port_cfg;
	struct ar8327_sgmii_cfg scfg;
	struct ar8327_led_cfg lcfg;
	uint32_t t, new_pos, pos;

	/* Port 0 */
	bzero(&port_cfg, sizeof(port_cfg));
	sc->ar8327.port0_status = 0;
	if (ar8327_fetch_pdata_port(sc, &port_cfg, 0))
		sc->ar8327.port0_status = ar8327_get_port_init_status(&port_cfg);

	/* Port 6 */
	bzero(&port_cfg, sizeof(port_cfg));
	sc->ar8327.port6_status = 0;
	if (ar8327_fetch_pdata_port(sc, &port_cfg, 6))
		sc->ar8327.port6_status = ar8327_get_port_init_status(&port_cfg);

	/* Pad 0 */
	bzero(&pc, sizeof(pc));
	t = 0;
	if (ar8327_fetch_pdata_pad(sc, &pc, 0))
		t = ar8327_get_pad_cfg(&pc);
#if 0
		if (AR8X16_IS_SWITCH(sc, AR8337))
			t |= AR8337_PAD_MAC06_EXCHANGE_EN;
#endif
	arswitch_writereg(sc->sc_dev, AR8327_REG_PAD0_MODE, t);

	/* Pad 5 */
	bzero(&pc, sizeof(pc));
	t = 0;
	if (ar8327_fetch_pdata_pad(sc, &pc, 5))
		t = ar8327_get_pad_cfg(&pc);
	arswitch_writereg(sc->sc_dev, AR8327_REG_PAD5_MODE, t);

	/* Pad 6 */
	bzero(&pc, sizeof(pc));
	t = 0;
	if (ar8327_fetch_pdata_pad(sc, &pc, 6))
		t = ar8327_get_pad_cfg(&pc);
	arswitch_writereg(sc->sc_dev, AR8327_REG_PAD6_MODE, t);

	pos = arswitch_readreg(sc->sc_dev, AR8327_REG_POWER_ON_STRIP);
	new_pos = pos;

	/* XXX LED config */
	bzero(&lcfg, sizeof(lcfg));
	if (ar8327_fetch_pdata_led(sc, &lcfg)) {
		if (lcfg.open_drain)
			new_pos |= AR8327_POWER_ON_STRIP_LED_OPEN_EN;
		else
			new_pos &= ~AR8327_POWER_ON_STRIP_LED_OPEN_EN;

		arswitch_writereg(sc->sc_dev, AR8327_REG_LED_CTRL0,
		    lcfg.led_ctrl0);
		arswitch_writereg(sc->sc_dev, AR8327_REG_LED_CTRL1,
		    lcfg.led_ctrl1);
		arswitch_writereg(sc->sc_dev, AR8327_REG_LED_CTRL2,
		    lcfg.led_ctrl2);
		arswitch_writereg(sc->sc_dev, AR8327_REG_LED_CTRL3,
		    lcfg.led_ctrl3);

		if (new_pos != pos)
			new_pos |= AR8327_POWER_ON_STRIP_POWER_ON_SEL;
	}

	/* SGMII config */
	bzero(&scfg, sizeof(scfg));
	if (ar8327_fetch_pdata_sgmii(sc, &scfg)) {
		device_printf(sc->sc_dev, "%s: SGMII cfg?\n", __func__);
		t = scfg.sgmii_ctrl;
		if (sc->chip_rev == 1)
			t |= AR8327_SGMII_CTRL_EN_PLL |
			    AR8327_SGMII_CTRL_EN_RX |
			    AR8327_SGMII_CTRL_EN_TX;
		else
			t &= ~(AR8327_SGMII_CTRL_EN_PLL |
			    AR8327_SGMII_CTRL_EN_RX |
			    AR8327_SGMII_CTRL_EN_TX);

		arswitch_writereg(sc->sc_dev, AR8327_REG_SGMII_CTRL, t);

		if (scfg.serdes_aen)
			new_pos &= ~AR8327_POWER_ON_STRIP_SERDES_AEN;
		else
			new_pos |= AR8327_POWER_ON_STRIP_SERDES_AEN;
	}

	arswitch_writereg(sc->sc_dev, AR8327_REG_POWER_ON_STRIP, new_pos);

	return (0);
}

static int
ar8327_hw_setup(struct arswitch_softc *sc)
{
	int i;
	int err;

	/* pdata fetch and setup */
	err = ar8327_init_pdata(sc);
	if (err != 0)
		return (err);

	/* XXX init leds */

	for (i = 0; i < AR8327_NUM_PHYS; i++) {
		/* phy fixup */
		ar8327_phy_fixup(sc, i);

		/* start PHY autonegotiation? */
		/* XXX is this done as part of the normal PHY setup? */

	}

	/* Let things settle */
	DELAY(1000);

	return (0);
}

static int
ar8327_atu_learn_default(struct arswitch_softc *sc)
{

	device_printf(sc->sc_dev, "%s: TODO!\n", __func__);
	return (0);
}

/*
 * Initialise other global values, for the AR8327.
 */
static int
ar8327_hw_global_setup(struct arswitch_softc *sc)
{
	uint32_t t;

	ARSWITCH_LOCK(sc);

	/* enable CPU port and disable mirror port */
	t = AR8327_FWD_CTRL0_CPU_PORT_EN |
	    AR8327_FWD_CTRL0_MIRROR_PORT;
	arswitch_writereg(sc->sc_dev, AR8327_REG_FWD_CTRL0, t);

	/* forward multicast and broadcast frames to CPU */
	t = (AR8327_PORTS_ALL << AR8327_FWD_CTRL1_UC_FLOOD_S) |
	    (AR8327_PORTS_ALL << AR8327_FWD_CTRL1_MC_FLOOD_S) |
	    (AR8327_PORTS_ALL << AR8327_FWD_CTRL1_BC_FLOOD_S);
	arswitch_writereg(sc->sc_dev, AR8327_REG_FWD_CTRL1, t);

	/* enable jumbo frames */
	/* XXX need to macro-shift the value! */
	arswitch_modifyreg(sc->sc_dev, AR8327_REG_MAX_FRAME_SIZE,
	    AR8327_MAX_FRAME_SIZE_MTU, 9018 + 8 + 2);

	/* Enable MIB counters */
	arswitch_modifyreg(sc->sc_dev, AR8327_REG_MODULE_EN,
	    AR8327_MODULE_EN_MIB, AR8327_MODULE_EN_MIB);

	/* Disable EEE on all ports due to stability issues */
	t = arswitch_readreg(sc->sc_dev, AR8327_REG_EEE_CTRL);
	t |= AR8327_EEE_CTRL_DISABLE_PHY(0) |
	    AR8327_EEE_CTRL_DISABLE_PHY(1) |
	    AR8327_EEE_CTRL_DISABLE_PHY(2) |
	    AR8327_EEE_CTRL_DISABLE_PHY(3) |
	    AR8327_EEE_CTRL_DISABLE_PHY(4);
	arswitch_writereg(sc->sc_dev, AR8327_REG_EEE_CTRL, t);

	/* Set the right number of ports */
	/* GMAC0 (CPU), GMAC1..5 (PHYs), GMAC6 (CPU) */
	sc->info.es_nports = 7;

	ARSWITCH_UNLOCK(sc);
	return (0);
}

/*
 * Port setup.  Called at attach time.
 */
static void
ar8327_port_init(struct arswitch_softc *sc, int port)
{
	uint32_t t;
	int ports;

	/* For now, port can see all other ports */
	ports = 0x7f;

	if (port == AR8X16_PORT_CPU)
		t = sc->ar8327.port0_status;
	else if (port == 6)
		t = sc->ar8327.port6_status;
        else
		t = AR8X16_PORT_STS_LINK_AUTO;

	arswitch_writereg(sc->sc_dev, AR8327_REG_PORT_STATUS(port), t);
	arswitch_writereg(sc->sc_dev, AR8327_REG_PORT_HEADER(port), 0);

	/*
	 * Default to 1 port group.
	 */
	t = 1 << AR8327_PORT_VLAN0_DEF_SVID_S;
	t |= 1 << AR8327_PORT_VLAN0_DEF_CVID_S;
	arswitch_writereg(sc->sc_dev, AR8327_REG_PORT_VLAN0(port), t);

	t = AR8327_PORT_VLAN1_OUT_MODE_UNTOUCH << AR8327_PORT_VLAN1_OUT_MODE_S;
	arswitch_writereg(sc->sc_dev, AR8327_REG_PORT_VLAN1(port), t);

	/*
	 * This doesn't configure any ports which this port can "see".
	 * bits 0-6 control which ports a frame coming into this port
	 * can be sent out to.
	 *
	 * So by doing this, we're making it impossible to send frames out
	 * to that port.
	 */
	t = AR8327_PORT_LOOKUP_LEARN;
	t |= AR8X16_PORT_CTRL_STATE_FORWARD << AR8327_PORT_LOOKUP_STATE_S;

	/* So this allows traffic to any port except ourselves */
	t |= (ports & ~(1 << port));
	arswitch_writereg(sc->sc_dev, AR8327_REG_PORT_LOOKUP(port), t);
}

static int
ar8327_port_vlan_setup(struct arswitch_softc *sc, etherswitch_port_t *p)
{

	/* Check: ADDTAG/STRIPTAG - exclusive */

	ARSWITCH_LOCK(sc);

	/* Set the PVID. */
	if (p->es_pvid != 0)
		sc->hal.arswitch_vlan_set_pvid(sc, p->es_port, p->es_pvid);

	/*
	 * DOUBLE_TAG
	 * VLAN_MODE_ADD
	 * VLAN_MODE_STRIP
	 */
	ARSWITCH_UNLOCK(sc);
	return (0);
}

/*
 * Get the port VLAN configuration.
 */
static int
ar8327_port_vlan_get(struct arswitch_softc *sc, etherswitch_port_t *p)
{

	ARSWITCH_LOCK(sc);

	/* Retrieve the PVID */
	sc->hal.arswitch_vlan_get_pvid(sc, p->es_port, &p->es_pvid);

	/* Retrieve the current port configuration from the VTU */
	/*
	 * DOUBLE_TAG
	 * VLAN_MODE_ADD
	 * VLAN_MODE_STRIP
	 */

	ARSWITCH_UNLOCK(sc);
	return (0);
}

static void
ar8327_port_disable_mirror(struct arswitch_softc *sc, int port)
{

	arswitch_modifyreg(sc->sc_dev,
	    AR8327_REG_PORT_LOOKUP(port),
	    AR8327_PORT_LOOKUP_ING_MIRROR_EN,
	    0);
	arswitch_modifyreg(sc->sc_dev,
	    AR8327_REG_PORT_HOL_CTRL1(port),
	    AR8327_PORT_HOL_CTRL1_EG_MIRROR_EN,
	    0);
}

static void
ar8327_reset_vlans(struct arswitch_softc *sc)
{
	int i;
	uint32_t t;
	int ports;

	ARSWITCH_LOCK_ASSERT(sc, MA_NOTOWNED);
	ARSWITCH_LOCK(sc);

	/* Clear the existing VLAN configuration */
	memset(sc->vid, 0, sizeof(sc->vid));

	/*
	 * Disable mirroring.
	 */
	arswitch_modifyreg(sc->sc_dev, AR8327_REG_FWD_CTRL0,
	    AR8327_FWD_CTRL0_MIRROR_PORT,
	    (0xF << AR8327_FWD_CTRL0_MIRROR_PORT_S));

	/*
	 * XXX TODO: disable any Q-in-Q port configuration,
	 * tagging, egress filters, etc.
	 */

	/*
	 * For now, let's default to one portgroup, just so traffic
	 * flows.  All ports can see other ports. There are two CPU GMACs
	 * (GMAC0, GMAC6), GMAC1..GMAC5 are external PHYs.
	 *
	 * (ETHERSWITCH_VLAN_PORT)
	 */
	ports = 0x7f;

	/*
	 * XXX TODO: set things up correctly for vlans!
	 */
	for (i = 0; i < AR8327_NUM_PORTS; i++) {
		int egress, ingress;

		if (sc->vlan_mode == ETHERSWITCH_VLAN_PORT) {
			sc->vid[i] = i | ETHERSWITCH_VID_VALID;
			/* set egress == out_keep */
			ingress = AR8X16_PORT_VLAN_MODE_PORT_ONLY;
			/* in_port_only, forward */
			egress = AR8327_PORT_VLAN1_OUT_MODE_UNTOUCH;
		} else if (sc->vlan_mode == ETHERSWITCH_VLAN_DOT1Q) {
			ingress = AR8X16_PORT_VLAN_MODE_SECURE;
			egress = AR8327_PORT_VLAN1_OUT_MODE_UNMOD;
		} else {
			/* set egress == out_keep */
			ingress = AR8X16_PORT_VLAN_MODE_PORT_ONLY;
			/* in_port_only, forward */
			egress = AR8327_PORT_VLAN1_OUT_MODE_UNTOUCH;
		}

		/* set pvid = 1; there's only one vlangroup to start with */
		t = 1 << AR8327_PORT_VLAN0_DEF_SVID_S;
		t |= 1 << AR8327_PORT_VLAN0_DEF_CVID_S;
		arswitch_writereg(sc->sc_dev, AR8327_REG_PORT_VLAN0(i), t);

		t = AR8327_PORT_VLAN1_PORT_VLAN_PROP;
		t |= egress << AR8327_PORT_VLAN1_OUT_MODE_S;
		arswitch_writereg(sc->sc_dev, AR8327_REG_PORT_VLAN1(i), t);

		/* Ports can see other ports */
		/* XXX not entirely true for dot1q? */
		t = (ports & ~(1 << i));	/* all ports besides us */
		t |= AR8327_PORT_LOOKUP_LEARN;

		t |= ingress << AR8327_PORT_LOOKUP_IN_MODE_S;
		t |= AR8X16_PORT_CTRL_STATE_FORWARD << AR8327_PORT_LOOKUP_STATE_S;
		arswitch_writereg(sc->sc_dev, AR8327_REG_PORT_LOOKUP(i), t);
	}

	/*
	 * Disable port mirroring entirely.
	 */
	for (i = 0; i < AR8327_NUM_PORTS; i++) {
		ar8327_port_disable_mirror(sc, i);
	}

	/*
	 * If dot1q - set pvid; dot1q, etc.
	 */
	if (sc->vlan_mode == ETHERSWITCH_VLAN_DOT1Q) {
		sc->vid[0] = 1;
		for (i = 0; i < AR8327_NUM_PORTS; i++) {
			/* Each port - pvid 1 */
			sc->hal.arswitch_vlan_set_pvid(sc, i, sc->vid[0]);
		}
		/* Initialise vlan1 - all ports, untagged */
		sc->hal.arswitch_set_dot1q_vlan(sc, ports, ports, sc->vid[0]);
		sc->vid[0] |= ETHERSWITCH_VID_VALID;
	}

	ARSWITCH_UNLOCK(sc);
}

static int
ar8327_vlan_get_port(struct arswitch_softc *sc, uint32_t *ports, int vid)
{
	int port;
	uint32_t reg;

	ARSWITCH_LOCK_ASSERT(sc, MA_OWNED);

	/* For port based vlans the vlanid is the same as the port index. */
	port = vid & ETHERSWITCH_VID_MASK;
	reg = arswitch_readreg(sc->sc_dev, AR8327_REG_PORT_LOOKUP(port));
	*ports = reg & 0x7f;
	return (0);
}

static int
ar8327_vlan_set_port(struct arswitch_softc *sc, uint32_t ports, int vid)
{
	int err, port;

	ARSWITCH_LOCK_ASSERT(sc, MA_OWNED);

	/* For port based vlans the vlanid is the same as the port index. */
	port = vid & ETHERSWITCH_VID_MASK;

	err = arswitch_modifyreg(sc->sc_dev, AR8327_REG_PORT_LOOKUP(port),
	    0x7f, /* vlan membership mask */
	    (ports & 0x7f));

	if (err)
		return (err);
	return (0);
}

static int
ar8327_vlan_getvgroup(struct arswitch_softc *sc, etherswitch_vlangroup_t *vg)
{

	return (ar8xxx_getvgroup(sc, vg));
}

static int
ar8327_vlan_setvgroup(struct arswitch_softc *sc, etherswitch_vlangroup_t *vg)
{

	return (ar8xxx_setvgroup(sc, vg));
}

static int
ar8327_get_pvid(struct arswitch_softc *sc, int port, int *pvid)
{
	uint32_t reg;

	ARSWITCH_LOCK_ASSERT(sc, MA_OWNED);

	/*
	 * XXX for now, assuming it's CVID; likely very wrong!
	 */
	port = port & ETHERSWITCH_VID_MASK;
	reg = arswitch_readreg(sc->sc_dev, AR8327_REG_PORT_VLAN0(port));
	reg = reg >> AR8327_PORT_VLAN0_DEF_CVID_S;
	reg = reg & 0xfff;

	*pvid = reg;
	return (0);
}

static int
ar8327_set_pvid(struct arswitch_softc *sc, int port, int pvid)
{
	uint32_t t;

	/* Limit pvid to valid values */
	pvid &= 0x7f;

	t = pvid << AR8327_PORT_VLAN0_DEF_SVID_S;
	t |= pvid << AR8327_PORT_VLAN0_DEF_CVID_S;
	arswitch_writereg(sc->sc_dev, AR8327_REG_PORT_VLAN0(port), t);

	return (0);
}

static int
ar8327_atu_wait_ready(struct arswitch_softc *sc)
{
	int ret;

	ret = arswitch_waitreg(sc->sc_dev,
	    AR8327_REG_ATU_FUNC,
	    AR8327_ATU_FUNC_BUSY,
	    0,
	    1000);

	return (ret);
}

static int
ar8327_atu_flush(struct arswitch_softc *sc)
{

	int ret;

	ARSWITCH_LOCK_ASSERT(sc, MA_OWNED);

	ret = ar8327_atu_wait_ready(sc);
	if (ret)
		device_printf(sc->sc_dev, "%s: waitreg failed\n", __func__);

	if (!ret)
		arswitch_writereg(sc->sc_dev,
		    AR8327_REG_ATU_FUNC,
		    AR8327_ATU_FUNC_OP_FLUSH | AR8327_ATU_FUNC_BUSY);
	return (ret);
}

static int
ar8327_atu_flush_port(struct arswitch_softc *sc, int port)
{
	int ret;
	uint32_t val;

	ARSWITCH_LOCK_ASSERT(sc, MA_OWNED);

	ret = ar8327_atu_wait_ready(sc);
	if (ret)
		device_printf(sc->sc_dev, "%s: waitreg failed\n", __func__);

	val = AR8327_ATU_FUNC_OP_FLUSH_UNICAST;
	val |= SM(port, AR8327_ATU_FUNC_PORT_NUM);

	if (!ret)
		arswitch_writereg(sc->sc_dev,
		    AR8327_REG_ATU_FUNC,
		    val | AR8327_ATU_FUNC_BUSY);

	return (ret);
}

/*
 * Fetch a single entry from the ATU.
 */
static int
ar8327_atu_fetch_table(struct arswitch_softc *sc, etherswitch_atu_entry_t *e,
    int atu_fetch_op)
{
	uint32_t ret0, ret1, ret2, val;

	ARSWITCH_LOCK_ASSERT(sc, MA_OWNED);

	switch (atu_fetch_op) {
	case 0:
		/* Initialise things for the first fetch */

		DPRINTF(sc, ARSWITCH_DBG_ATU, "%s: initializing\n", __func__);
		(void) ar8327_atu_wait_ready(sc);

		arswitch_writereg(sc->sc_dev,
		    AR8327_REG_ATU_FUNC, AR8327_ATU_FUNC_OP_GET_NEXT);
		arswitch_writereg(sc->sc_dev, AR8327_REG_ATU_DATA0, 0);
		arswitch_writereg(sc->sc_dev, AR8327_REG_ATU_DATA1, 0);
		arswitch_writereg(sc->sc_dev, AR8327_REG_ATU_DATA2, 0);

		return (0);
	case 1:
		DPRINTF(sc, ARSWITCH_DBG_ATU, "%s: reading next\n", __func__);
		/*
		 * Attempt to read the next address entry; don't modify what
		 * is there in these registers as its used for the next fetch
		 */
		(void) ar8327_atu_wait_ready(sc);

		/* Begin the next read event; not modifying anything */
		val = arswitch_readreg(sc->sc_dev, AR8327_REG_ATU_FUNC);
		val |= AR8327_ATU_FUNC_BUSY;
		arswitch_writereg(sc->sc_dev, AR8327_REG_ATU_FUNC, val);

		/* Wait for it to complete */
		(void) ar8327_atu_wait_ready(sc);

		/* Fetch the ethernet address and ATU status */
		ret0 = arswitch_readreg(sc->sc_dev, AR8327_REG_ATU_DATA0);
		ret1 = arswitch_readreg(sc->sc_dev, AR8327_REG_ATU_DATA1);
		ret2 = arswitch_readreg(sc->sc_dev, AR8327_REG_ATU_DATA2);

		/* If the status is zero, then we're done */
		if (MS(ret2, AR8327_ATU_FUNC_DATA2_STATUS) == 0)
			return (-1);

		/* MAC address */
		e->es_macaddr[5] = MS(ret0, AR8327_ATU_DATA0_MAC_ADDR3);
		e->es_macaddr[4] = MS(ret0, AR8327_ATU_DATA0_MAC_ADDR2);
		e->es_macaddr[3] = MS(ret0, AR8327_ATU_DATA0_MAC_ADDR1);
		e->es_macaddr[2] = MS(ret0, AR8327_ATU_DATA0_MAC_ADDR0);
		e->es_macaddr[0] = MS(ret1, AR8327_ATU_DATA1_MAC_ADDR5);
		e->es_macaddr[1] = MS(ret1, AR8327_ATU_DATA1_MAC_ADDR4);

		/* Bitmask of ports this entry is for */
		e->es_portmask = MS(ret1, AR8327_ATU_DATA1_DEST_PORT);

		/* TODO: other flags that are interesting */

		DPRINTF(sc, ARSWITCH_DBG_ATU, "%s: MAC %6D portmask 0x%08x\n",
		    __func__,
		    e->es_macaddr, ":", e->es_portmask);
		return (0);
	default:
		return (-1);
	}
	return (-1);
}
static int
ar8327_flush_dot1q_vlan(struct arswitch_softc *sc)
{

	return (ar8327_vlan_op(sc, AR8327_VTU_FUNC1_OP_FLUSH, 0, 0));
}

static int
ar8327_purge_dot1q_vlan(struct arswitch_softc *sc, int vid)
{

	return (ar8327_vlan_op(sc, AR8327_VTU_FUNC1_OP_PURGE, vid, 0));
}

static int
ar8327_get_dot1q_vlan(struct arswitch_softc *sc, uint32_t *ports,
    uint32_t *untagged_ports, int vid)
{
	int i, r;
	uint32_t op, reg, val;

	op = AR8327_VTU_FUNC1_OP_GET_ONE;

	/* Filter out the vid flags; only grab the VLAN ID */
	vid &= 0xfff;

	/* XXX TODO: the VTU here stores egress mode - keep, tag, untagged, none */
	r = ar8327_vlan_op(sc, op, vid, 0);
	if (r != 0) {
		device_printf(sc->sc_dev, "%s: %d: op failed\n", __func__, vid);
	}

	reg = arswitch_readreg(sc->sc_dev, AR8327_REG_VTU_FUNC0);
	DPRINTF(sc, ARSWITCH_DBG_REGIO, "%s: %d: reg=0x%08x\n", __func__, vid, reg);

	/*
	 * If any of the bits are set, update the port mask.
	 * Worry about the port config itself when getport() is called.
	 */
	*ports = 0;
	for (i = 0; i < AR8327_NUM_PORTS; i++) {
		val = reg >> AR8327_VTU_FUNC0_EG_MODE_S(i);
		val = val & 0x3;
		/* XXX KEEP (unmodified?) */
		if (val == AR8327_VTU_FUNC0_EG_MODE_TAG) {
			*ports |= (1 << i);
		} else if (val == AR8327_VTU_FUNC0_EG_MODE_UNTAG) {
			*ports |= (1 << i);
			*untagged_ports |= (1 << i);
		}
	}

	return (0);
}

static int
ar8327_set_dot1q_vlan(struct arswitch_softc *sc, uint32_t ports,
    uint32_t untagged_ports, int vid)
{
	int i;
	uint32_t op, val, mode;

	op = AR8327_VTU_FUNC1_OP_LOAD;
	vid &= 0xfff;

	DPRINTF(sc, ARSWITCH_DBG_VLAN,
	    "%s: vid: %d, ports=0x%08x, untagged_ports=0x%08x\n",
	    __func__,
	    vid,
	    ports,
	    untagged_ports);

	/*
	 * Mark it as valid; and that it should use per-VLAN MAC table,
	 * not VID=0 when doing MAC lookups
	 */
	val = AR8327_VTU_FUNC0_VALID | AR8327_VTU_FUNC0_IVL;

	for (i = 0; i < AR8327_NUM_PORTS; i++) {
		if ((ports & BIT(i)) == 0)
			mode = AR8327_VTU_FUNC0_EG_MODE_NOT;
		else if (untagged_ports & BIT(i))
			mode = AR8327_VTU_FUNC0_EG_MODE_UNTAG;
		else
			mode = AR8327_VTU_FUNC0_EG_MODE_TAG;

		val |= mode << AR8327_VTU_FUNC0_EG_MODE_S(i);
	}

	return (ar8327_vlan_op(sc, op, vid, val));
}

void
ar8327_attach(struct arswitch_softc *sc)
{

	sc->hal.arswitch_hw_setup = ar8327_hw_setup;
	sc->hal.arswitch_hw_global_setup = ar8327_hw_global_setup;

	sc->hal.arswitch_port_init = ar8327_port_init;

	sc->hal.arswitch_vlan_getvgroup = ar8327_vlan_getvgroup;
	sc->hal.arswitch_vlan_setvgroup = ar8327_vlan_setvgroup;
	sc->hal.arswitch_port_vlan_setup = ar8327_port_vlan_setup;
	sc->hal.arswitch_port_vlan_get = ar8327_port_vlan_get;
	sc->hal.arswitch_flush_dot1q_vlan = ar8327_flush_dot1q_vlan;
	sc->hal.arswitch_purge_dot1q_vlan = ar8327_purge_dot1q_vlan;
	sc->hal.arswitch_set_dot1q_vlan = ar8327_set_dot1q_vlan;
	sc->hal.arswitch_get_dot1q_vlan = ar8327_get_dot1q_vlan;

	sc->hal.arswitch_vlan_init_hw = ar8327_reset_vlans;
	sc->hal.arswitch_vlan_get_pvid = ar8327_get_pvid;
	sc->hal.arswitch_vlan_set_pvid = ar8327_set_pvid;

	sc->hal.arswitch_get_port_vlan = ar8327_vlan_get_port;
	sc->hal.arswitch_set_port_vlan = ar8327_vlan_set_port;

	sc->hal.arswitch_atu_learn_default = ar8327_atu_learn_default;
	sc->hal.arswitch_atu_flush = ar8327_atu_flush;
	sc->hal.arswitch_atu_flush_port = ar8327_atu_flush_port;
	sc->hal.arswitch_atu_fetch_table = ar8327_atu_fetch_table;

	/*
	 * Reading the PHY via the MDIO interface currently doesn't
	 * work correctly.
	 *
	 * So for now, just go direct to the PHY registers themselves.
	 * This has always worked  on external devices, but not internal
	 * devices (AR934x, AR724x, AR933x.)
	 */
	sc->hal.arswitch_phy_read = arswitch_readphy_external;
	sc->hal.arswitch_phy_write = arswitch_writephy_external;

	/* Set the switch vlan capabilities. */
	sc->info.es_vlan_caps = ETHERSWITCH_VLAN_DOT1Q |
	    ETHERSWITCH_VLAN_PORT | ETHERSWITCH_VLAN_DOUBLE_TAG;
	sc->info.es_nvlangroups = AR8X16_MAX_VLANS;
}