/*
 * This is a driver implementation for the Port features for the RTL827x platform
 * This code is in the Public Domain
 */

// #define REGDBG
// #define DEBUG

#include <stdint.h>
#include "rtl837x_common.h"
#include "rtl837x_sfr.h"
#include "rtl837x_regs.h"
#include "rtl837x_port.h"
#include "rtl837x_phy.h"
#include "phy.h"
#include "machine.h"

#pragma codeseg BANK1
#pragma constseg BANK1

extern __code uint8_t * __code hex;
extern __code uint16_t bit_mask[16];
extern __code struct machine machine;
extern __xdata uint8_t sfr_data[4];
extern __xdata uint16_t vlan_ptr;
extern __xdata uint8_t vlan_names[VLAN_NAMES_SIZE];
extern __xdata struct machine_runtime machine_detected;

__xdata	uint32_t l2_head;

__xdata struct vlan_settings vlan_settings;

void port_mirror_set(register uint8_t port, __xdata uint16_t rx_pmask, __xdata uint16_t tx_pmask) __banked
{
	print_string("\nport_mirror_set called \n");
	print_string("Mirroring port: "); print_byte(port); print_string(" with rx-mask: ");
	print_short(rx_pmask); print_string(", tx mask: "); print_short(tx_pmask);
	write_char('\n');

	REG_WRITE(RTL837x_MIRROR_CONF, rx_pmask >> 8, rx_pmask, tx_pmask >> 8, tx_pmask);
	REG_WRITE(RTL837x_MIRROR_CTRL, 0, 0, 0, (port << 1) | 0x1);
}


void port_mirror_del(void) __banked
{
	print_string("\nport_mirror_del called \n");
	REG_SET(RTL837x_MIRROR_CTRL, 0);
}


bool port_ingress_filter(__xdata uint8_t port, __xdata vlan_ingress_mode_t type) __banked
{
	if (port > 9 || type >= VLAN_INVALID) {
		print_string("Invalid port or ingress filter type\n");
		return false;
	}

	if (type & 0x1) {
		reg_bit_set(RTL837x_REG_INGRESS, port << 1);
	} else {
		reg_bit_clear(RTL837x_REG_INGRESS, port << 1);
	}
	if (type & 0x2) {
		reg_bit_set(RTL837x_REG_INGRESS, (port << 1) + 1);
	} else {
		reg_bit_clear(RTL837x_REG_INGRESS, (port << 1) + 1);
	}
	return true;
}

vlan_ingress_mode_t port_ingress_filter_get(__xdata uint8_t port) __banked
{
	reg_read_m(RTL837x_REG_INGRESS);
    if (port > 9) {
        return VLAN_INVALID;
    }

	// Each port is represented by 2 bits in the ingress register, starting from bit 0 for port 0
	const uint8_t sfr_index = 3 - (port / 4);
    const uint8_t shift = (port % 4) << 1;
	
    return (vlan_ingress_mode_t)((sfr_data[sfr_index] >> shift) & 0x03);
}


/*
 * Define a Primary VLAN ID for a port 
*/
void port_pvid_set(uint8_t port, __xdata uint16_t pvid) __banked
{
	// r4e1c:00001001 R4e1c-000017d0 r6738:00000000 R6738-00000000 (no filtering)
	print_string("\nport_pvid_set called \n");
	uint16_t reg = RTL837x_PVID_BASE_REG + ((port >> 1) << 2);

	reg_read_m(reg);
	if (port & 0x1) {
		REG_WRITE(reg, sfr_data[0], pvid >> 4, sfr_data[2] & 0x0f | (pvid << 4), sfr_data[3]);
	} else {
		REG_WRITE(reg, sfr_data[0], sfr_data[1], sfr_data[2] & 0xf0 | (pvid >> 8), pvid);
	}
}

uint16_t port_pvid_get(uint8_t port) __banked
{
	uint16_t reg = RTL837x_PVID_BASE_REG + ((port >> 1) << 2);
	reg_read_m(reg);
	if (port & 0x1) {
		return (sfr_data[1] << 4) | (sfr_data[2] >> 4);
	} else {
		return ((sfr_data[2] & 0x0f) << 8) | sfr_data[3];
	}
}


void vlan_delete(uint16_t vlan) __banked
{
	print_string("\nvlan_delete called \n"); print_short(vlan);
	vlan_name_remove(vlan);
	REG_WRITE(RTL837x_TBL_DATA_IN_A, 0, 0, 0, 0);
	REG_WRITE(RTL837X_TBL_CTRL, vlan >> 8, vlan, TBL_VLAN, TBL_WRITE | TBL_EXECUTE);
}


void vlan_name_remove(uint16_t vlan) __banked
{
	static __xdata uint16_t name_pos;
	static __xdata uint16_t entry_start;
	static __xdata uint16_t pos;
	static __xdata uint16_t entry_len;
	static __xdata uint16_t move_count;
	static __xdata uint16_t j;

	name_pos = vlan_name(vlan);
	if (name_pos == 0xffff)
		return;

	entry_start = name_pos - 3;
	pos = entry_start;

	while (pos < vlan_ptr && vlan_names[pos] != ' ')
		pos++;
	if (pos >= vlan_ptr)
		return;
	pos++;

	entry_len = pos - entry_start;
	move_count = vlan_ptr - pos;

	for (j = 0; j < move_count; j++)
		vlan_names[entry_start + j] = vlan_names[pos + j];

	vlan_ptr -= entry_len;
	vlan_names[vlan_ptr] = 0;
}


/*
 * Reads VLAN information from VLAN table
 * Returns data in sfr_data
 */
int8_t vlan_get(register uint16_t vlan) __banked
{
	if (vlan >= 0xfff) // VLAN 4095 is special
		return -1;

	REG_WRITE(RTL837X_TBL_CTRL, vlan >> 8, vlan, TBL_VLAN, TBL_EXECUTE);
	do {
		reg_read_m(RTL837X_TBL_CTRL);
	} while (sfr_data[3] & TBL_EXECUTE);
	reg_read_m(RTL837x_L2_DATA_OUT_A);

	return 0;
}


__xdata uint16_t vlan_name(register uint16_t vlan) __banked
{
	__xdata int16_t i = 0;
	__xdata uint8_t begin = 1;
	while (vlan_names[i]) {
		if (begin && vlan_names[i] == hex[(vlan >> 8) & 0xf] && vlan_names[i + 1] == hex[(vlan >> 4) & 0xf] && vlan_names[i + 2] == hex[vlan & 0xf])
			break;
		begin = vlan_names[i++] == ' ' ? 1 : 0;
	}
	if (vlan_names[i])
		return i + 3;

	return 0xffff;
}


/*
 * Create a VLAN
 * The arguments are passed in global structure vlan_settings
 * A member that is not tagged, is untagged
 */
void vlan_create(void) __banked
{
	// For now, the CPU-port is always a tagged member:
	vlan_settings.members |= 0x0200; // Set 10th bit
	vlan_settings.tagged |= 0x0200;

	print_string("\nvlan_create called\nvlan: "); print_short(vlan_settings.vlan);
	print_string(", members: "); print_short(vlan_settings.members);
	print_string(", tagged: "); print_short(vlan_settings.tagged); write_char('\n');

	uint16_t a = (~vlan_settings.members) ^ vlan_settings.tagged ^ vlan_settings.members;

	// On RTL8372, port-bits 0-2 must be 0, although they are not members
	if (!machine_detected.isRTL8373) {
		a &= 0x1f8;
		vlan_settings.tagged &= 0x3f8;
	}

	// Initialize VLAN table with VLAN 1
	REG_WRITE(RTL837x_TBL_DATA_IN_A, 0x02, (a >> 6) & 0x0f, (a << 2) | (vlan_settings.members >> 8), vlan_settings.members);
	REG_WRITE(RTL837X_TBL_CTRL, vlan_settings.vlan >> 8, vlan_settings.vlan, TBL_VLAN, TBL_WRITE | TBL_EXECUTE);
	do {
		reg_read_m(RTL837X_TBL_CTRL);
	} while (sfr_data[3] & TBL_EXECUTE);
	print_string("vlan_create done \n");
}


/*
 * Configures a default VLAN 1 and enables 4k VLAN tables
 * All ports are made members of the VLAN and VLAN filtering
 * is enabled on all ports
 * PVID is set to 1 for all ports
 * Called upon reboot
 */
void vlan_setup(void) __banked
{
	print_string("\nvlan_setup called \n");

	// No VLAN names set up so far
	vlan_ptr = 0;
	vlan_names[0] = 0;

	// Initialize VLAN table for VLAN 1, by disabling that entry
	REG_SET(RTL837x_TBL_DATA_IN_A, machine_detected.isRTL8373? 0x0007ffff : 0x0007e3f8);

	REG_SET(RTL837X_TBL_CTRL, 0x00010303);
	do {
		reg_read_m(RTL837X_TBL_CTRL);
	} while (sfr_data[3] & TBL_EXECUTE);

	// Set PVID 1 for every port. TODO: Skip unused ports!
	for (uint8_t i = machine.min_port; i <= machine.max_port + 1; i++) {  // Do this also for the CPU port (+1)
		uint16_t reg = RTL837x_PVID_BASE_REG + ((i >> 1) << 2);
#ifdef DEBUG
		print_byte(i); write_char(':'); write_char(' '); print_short(reg); write_char('=');
		print_short(reg);  write_char(' ');
#endif
		reg_read_m(reg);
		if (i & 0x1) {
			REG_WRITE(reg, sfr_data[0], 0, sfr_data[2] & 0x0f | 0x10, sfr_data[3]);
		} else {
			REG_WRITE(reg, sfr_data[0], sfr_data[1], sfr_data[2] & 0xf0, 0x01);
		}
#ifdef DEBUG
		reg_read_m(reg);
		write_char(' '); write_char('A'); write_char('>'); print_sfr_data();
#endif

		// EGRESS filtering for port: removal of additional VLAN tag (mode 0x3 for each port)
		reg_bit_clear(RTL837X_VLAN_PORT_EGR_TAG, i << 1);
		reg_bit_clear(RTL837X_VLAN_PORT_EGR_TAG, (i << 1) + 1);
		// Enable INGRESS filtering for port: discard packets not belonging to member VLAN on that port
		port_ingress_vlan_filter_set(i, true);

#ifdef DEBUG
		print_string("\n");
#endif
	}

	// Ingress filtering. 2 bits per port: allow tagged (01) / untagged (10) and all (00)
	REG_SET(RTL837x_REG_INGRESS, 0); // No filtering for all ports

	// Enable 4k VLAN
	REG_SET(RTL837X_VLAN_CTRL, VLAN_CVLAN_FILTER);
	REG_SET(RTL837X_VLAN_L2_LRN_DIS_0, 0);
	REG_SET(RTL837X_VLAN_L2_LRN_DIS_1, 0);

	// Enable VLAN 1: Ports 0-9, i.e. including the CPU port are untagged members
	REG_SET(RTL837x_TBL_DATA_IN_A, machine_detected.isRTL8373? 0x0207ffff : 0x0207e3f8);	// 02: Entry valid, 7...: membership

	REG_SET(RTL837X_TBL_CTRL, 0x00010303);	// Write VLAN 1
	do {
		reg_read_m(RTL837X_TBL_CTRL);
	} while (sfr_data[3] & TBL_EXECUTE);

#ifdef DEBUG
	print_string("\nvlan_setup, REG 0x6738: "); print_reg(0x6738);
	print_string("\nvlan_setup, REG 0x4e10: "); print_reg(0x4e10);
	print_string("\nvlan_setup, REG 0x4e18: "); print_reg(0x4e18);
	print_string("\nvlan_setup, REG 0x4e14: "); print_reg(0x4e14);
	print_string("\nvlan_setup, REG 0x4e30: "); print_reg(0x4e30);
	print_string("\nvlan_setup, REG 0x4e34: "); print_reg(0x4e34);
	print_string("\nvlan_setup, REG 0x4f48: "); print_reg(0x4f48);
	print_string("\nvlan_setup, REG 0x4f4c: "); print_reg(0x4f4c);
#endif

	print_string("vlan_setup done \n");
}


/*
 * Forget all dynamic L2 learned entries
 */
uint8_t port_l2_forget(void) __banked
{
	print_string("\nport_l2_forget called\n");
	// Configure the entries to be flushed:
	// port-based (bits 0-1 are 0 and dynamic entries, bit 2 specifies dynamic entries
	REG_SET(RTL837x_L2_TBL_FLUSH_CNF, 0x0);

	// Flush L2 table for all ports by setting the ports and the flush-exec bit (bit 16)
	REG_SET(RTL837x_L2_TBL_FLUSH_CTRL, L2_TBL_FLUSH_EXEC | (machine_detected.isRTL8373 ? PMASK_9 : PMASK_6));

	// Wait for flush completed
	do {
		reg_read_m(RTL837x_L2_TBL_FLUSH_CTRL);
	} while (sfr_data[1]);

	print_string("port_l2_forget done\n");
	return 0;
}


void port_l2_learned(void) __banked
{
	// Whait for any table action to be finished
	do {
		reg_read_m(RTL837X_TBL_CTRL);
	} while (sfr_data[3] & 0x01);
	print_string("\n\tMAC\t\tVLAN\ttype\tport\n");
	__xdata uint16_t entry = 0x0000;
	__xdata uint16_t first_entry = 0xffff; // Table does not have that many entries

	while (1) {
		uint8_t port = 0;
		reg_read_m(RTL837x_TBL_DATA_0);
		REG_WRITE(RTL837x_TBL_DATA_0, sfr_data[0], sfr_data[1],sfr_data[2] | 0xc0, sfr_data[3]);

		REG_WRITE(RTL837X_TBL_CTRL, (entry >> 8) & 0xf, entry, TBL_L2_UNICAST, TBL_EXECUTE);
		do {
			reg_read_m(RTL837X_TBL_CTRL);
		} while (sfr_data[3] & TBL_EXECUTE);

		reg_read_m(RTL837x_TBL_DATA_0);
		entry = (((uint16_t)sfr_data[2] & 0x0f) << 8) | sfr_data[3];
		if (first_entry == 0xffff) {
			first_entry = entry;
		} else {
			if (first_entry == entry)
				break;
		}

		// MAC
		reg_read_m(RTL837x_L2_DATA_OUT_B);
		if ((sfr_data[0] & 0x20)) {	// Check entry is valid
			print_byte(sfr_data[2]); write_char(':');
			print_byte(sfr_data[3]); write_char(':');
			port = (sfr_data[0] >> 6) & 0x3;
			reg_read_m(RTL837x_L2_DATA_OUT_A);
			print_byte(sfr_data[0]); write_char(':');
			print_byte(sfr_data[1]); write_char(':');
			print_byte(sfr_data[2]); write_char(':');
			print_byte(sfr_data[3]); write_char('\t');

			// VLAN
			reg_read_m(RTL837x_L2_DATA_OUT_B);
			print_short( (((uint16_t) (sfr_data[0] & 0x0f)) << 8) | sfr_data[1]); // VLAN

			// type
			reg_read_m(RTL837x_L2_DATA_OUT_C);
			if (sfr_data[2] & 0x1)
				print_string("\tstatic\t");
			else
				print_string("\tlearned\t");

			port |= (sfr_data[3] & 0x3) << 2;
			if (port < 9)
				write_char(machine.log_to_phys_port[port] + '0');
			else
				print_string("CPU");
		}

		entry++;
		print_string("\n");
	}
}


/*
 * Basic L2 configuration such as time to forget an entry
 */
void port_l2_setup(void) __banked
{
	print_string("\nport_l2_setup called\n");

	port_l2_forget();

	for (uint8_t i = machine.min_port; i <= machine.max_port; i++) {
		// Limit the number of automatically learned MAC-Entries per port to 0x1040
		uint16_t reg = RTL837X_L2_LRN_PORT_CONSTRAINT + (i << 2);
		REG_SET(reg, 0x00001040);

		// All ports may communicate with each other and CPU-Port
		reg = RTL837X_PORT_ISOLATION_BASE + (i << 2);
		REG_SET(reg, PMASK_CPU | (machine_detected.isRTL8373? PMASK_9 : PMASK_6));
	}
	// When maximim entries learned, then simply flood the packet
	reg_bit_set(RTL837X_L2_LRN_PORT_CONSTRT_ACT, 0);

	print_string("\nport_l2_setup done\n");
}


void port_stats_print(void) __banked
{
	print_string("\nPort\tState\tLink\tTxGood\t\tTxBad\t\tRxGood\t\tRxBad\n");
	for (uint8_t i = machine.min_port; i <= machine.max_port; i++) {
		write_char('0' + machine.log_to_phys_port[i]); write_char('\t');

		if (!machine.is_sfp[i]) {
			phy_read(i, PHY_MMD31, 0xa610);
			if (SFR_DATA_8 == 0x20)
				print_string("On\t");
			else
				print_string("Off\t");
		} else {  // An SFP Module
			if (!gpio_pin_test(machine.sfp_port[machine.is_sfp[i]-1].pin_detect)) {
				print_string("SFP IN\t");
			} else {
				print_string("NO SFP\t");
			}
		}

		uint8_t b = 0;

		// Determine link state
		reg_read_m(RTL837X_REG_LINKS_STS);
		if(!((sfr_data[(i / 8) + 1] >> ( i % 8  ) & 1)))
		{
			b = 99;
		}
		else
		{
			if (i < 8)
				reg_read_m(RTL837X_REG_LINKS);
			else
				reg_read_m(RTL837X_REG_LINKS_89);

			b = sfr_data[3 - ((i & 7) >> 1)];	
			b = (i & 1) ? b >> 4 : b & 0xf;
		}

		switch (b) {
		case 0:
			print_string("10M\t");
			break;
		case 1:
			print_string("100M\t");
			break;
		case 2:
			print_string("1000M\t");
			break;
		case 4:
			print_string("10G\t");
			break;
		case 5:
			print_string("2.5G\t");
			break;
		case 6:
			print_string("5G\t");
			break;
		case 99:
			print_string("Down\t");
			break;
		default:
			print_string("Up\t");
			break;
		}
		STAT_GET(STAT_COUNTER_TX_PKTS, i);
		print_reg(RTL837X_STAT_V_LOW); write_char('\t');

		STAT_GET(STAT_COUNTER_ERR_PKTS, i);
		print_reg(RTL837X_STAT_V_LOW); write_char('\t');

		STAT_GET(STAT_COUNTER_RX_PKTS, i);
		print_reg(RTL837X_STAT_V_LOW); write_char('\t');

		STAT_GET(STAT_COUNTER_ERR_PKTS, i);
		print_reg(RTL837X_STAT_V_HIGH); write_char('\t');
		print_string("\n");
	}
}


void port_isolate(register uint8_t port, __xdata uint16_t pmask) __banked
{
	if (port <= machine.max_port)
		REG_SET(RTL837X_PORT_ISOLATION_BASE + (port << 2), pmask);
}


uint16_t port_isolation_get(register uint8_t port) __banked
{
	if (port > machine.max_port)
		return 0;

	reg_read_m(RTL837X_PORT_ISOLATION_BASE + (port << 2));
	return ((uint16_t)sfr_data[2]) << 8 | sfr_data[3];
}


void port_eee_enable(__xdata uint8_t port,__xdata uint8_t speed) __banked
{

	if (machine.is_sfp[port])
	{
		print_string("EEE can't be enabled for SFP port "); print_byte(port); print_string("\n");
		return;
	}

	REG_SET(RTL837X_EEE_CTRL_BASE + (port << 8), EEE_RX_ENABLE | EEE_TX_ENABLE);
	print_string("EEE on for "); print_byte(port); print_string(" speed "); 
	// Enable all speeds up to the specified speed
	if (speed & EEE_100) {
			print_string("100m\n");
			// Enable EEE advertisement for 100BASE-T via EEE Advertisement Reg
			phy_write(port, PHY_MMD_AN, PHY_EEE_ADV, PHY_EEE_BIT_100M);
			if (!(speed & EEE_NORESET))
				phy_reset(port);
			return;
	}
	if (speed & EEE_1000) {
			print_string("1g\n");
			// Disable EEE advertisement for 2.5GBASE-T via EEE Advertisement Reg 2
			phy_write(port, PHY_MMD_AN, PHY_EEE_ADV2, 0);
			// Enable EEE advertisement for 100/1000BASE-T via EEE Advertisement Reg
			phy_write(port, PHY_MMD_AN, PHY_EEE_ADV, PHY_EEE_BIT_1G | PHY_EEE_BIT_100M);
			if (!(speed & EEE_NORESET))
				phy_reset(port);
			return;
	}
	if (speed & EEE_2G5) {
			print_string("2g5\n");
			// Enable EEE advertisement for 100/1000BASE-T via EEE Advertisement Reg
			phy_write(port, PHY_MMD_AN, PHY_EEE_ADV, PHY_EEE_BIT_1G | PHY_EEE_BIT_100M);
			// Enable EEE advertisement for 2.5GBASE-T via EEE Advertisement Reg 2
			phy_write(port, PHY_MMD_AN, PHY_EEE_ADV2, PHY_EEE_BIT_2G5);
			if (!(speed & EEE_NORESET))
				phy_reset(port);
			return;
	}
	if (speed & EEE_5G) {
			print_string("5g\n");
			// Enable EEE advertisement for 100/1000BASE-T via EEE Advertisement Reg
			phy_write(port, PHY_MMD_AN, PHY_EEE_ADV, PHY_EEE_BIT_1G | PHY_EEE_BIT_100M);
			// Enable EEE advertisement for 2.5GBASE-T via EEE Advertisement Reg 2
			phy_write(port, PHY_MMD_AN, PHY_EEE_ADV2, PHY_EEE_BIT_2G5 | PHY_EEE_BIT_5G);
			if (!(speed & EEE_NORESET))
				phy_reset(port);
			return;
	}
	if (speed & EEE_10G) {
			print_string("10g\n");
			// Enable EEE advertisement for 100/1000BASE-T via EEE Advertisement Reg
			phy_write(port, PHY_MMD_AN, PHY_EEE_ADV, PHY_EEE_BIT_10G | PHY_EEE_BIT_1G | PHY_EEE_BIT_100M);
			// Enable EEE advertisement for 2.5GBASE-T via EEE Advertisement Reg 2
			phy_write(port, PHY_MMD_AN, PHY_EEE_ADV2, PHY_EEE_BIT_2G5 | PHY_EEE_BIT_5G);
			if (!(speed & EEE_NORESET))
				phy_reset(port);
			return;
	}

}


void port_eee_disable(uint8_t port) __banked
{
	if (machine.is_sfp[port])
		return;

	print_string("EEE off for "); print_byte(port); write_char('\n');
	REG_SET(RTL837X_EEE_CTRL_BASE + (port << 8), 0);
	// Disable EEE advertisement for 100/1000BASE-T via EEE Advertisement Reg
	phy_write(port, PHY_MMD_AN, PHY_EEE_ADV, 0);
	// Disable EEE advertisement for 2.5GBASE-T via EEE Advertisement Reg 2
	phy_write(port, PHY_MMD_AN, PHY_EEE_ADV2, 0);
	phy_reset(port);
}


void port_eee_status(uint8_t port) __banked
{
	print_string("Port: "); write_char('0' + machine.log_to_phys_port[port]);
	print_string(": ");
	if (machine.is_sfp[port]) {
		print_string("SFP\n");
		return;
	}

	uint16_t v;
	print_string("Advertising: ");

	if (machine.n_10g) {
		phy_read(port, PHY_MMD_AN, PHY_EEE_ADV);
		v = SFR_DATA_U16;
		if (v & PHY_EEE_BIT_10G)
			print_string(" 10G");
		else
			print_string("    ");
	}
	phy_read(port, PHY_MMD_AN, PHY_EEE_ADV2);
	v = SFR_DATA_U16;
	if (machine.n_10g) {
		if (v & PHY_EEE_BIT_5G)
			print_string(" 5G");
		else
			print_string("   ");
	}
	v = SFR_DATA_U16;
	if (v & PHY_EEE_BIT_2G5)
		print_string(" 2.5G");
	else
		print_string("     ");
	phy_read(port, PHY_MMD_AN, PHY_EEE_ADV);
	v = SFR_DATA_U16;
	if (v & PHY_EEE_BIT_1G)
		print_string("  1G ");
	else
		print_string("     ");
	if (v & PHY_EEE_BIT_100M)
		print_string(" 100M");
	else
		print_string("     ");

	print_string("   Link Partner: ");
	if (machine.n_10g) {
		phy_read(port, PHY_MMD_AN, PHY_EEE_LP_ABILITY);
		v = SFR_DATA_U16;
		if (v & PHY_EEE_BIT_10G)
			print_string(" 10G");
		else
			print_string("    ");
	}
	phy_read(port, PHY_MMD_AN, PHY_EEE_LP_ABILITY2);
	v = SFR_DATA_U16;
	if (machine.n_10g) {
		if (v & PHY_EEE_BIT_5G)
			print_string(" 5G");
		else
			print_string("   ");
	}
	if (v & PHY_EEE_BIT_2G5)
		print_string(" 2.5G");
	else
		print_string("     ");
	phy_read(port, PHY_MMD_AN, PHY_EEE_LP_ABILITY);
	v = SFR_DATA_U16;
	if (v & PHY_EEE_BIT_1G)
		print_string("  1G ");
	else
		print_string("     ");
	if (v & PHY_EEE_BIT_100M)
		print_string(" 100M");
	else
		print_string("     ");

	reg_read_m(RTL8373_PHY_EEE_ABLTY);
	if (sfr_data[3] & (1 << port))
		print_string(" ACTIVE   ");
	else
		print_string(" INACTIVE ");
	write_char('\n');
}


void port_eee_enable_all(__xdata uint8_t speed) __banked
{
	for (uint8_t i = machine.min_port; i <= machine.max_port; i++) {
		if (i == 3 && machine.n_10g) {
			port_eee_enable(i, speed);
		} else if (i == 8 && machine.n_10g == 2) {
			port_eee_enable(i, speed);
		} else {
			if (speed & EEE_10G)
				port_eee_enable(i, speed & EEE_NORESET | EEE_2G5);
			else
				port_eee_enable(i, speed);
		}
	}
}


void port_eee_disable_all(void) __banked
{
	for (uint8_t i = machine.min_port; i <= machine.max_port; i++) {
		port_eee_disable(i);
	}
}


void port_eee_status_all(void) __banked
{
	for (uint8_t i = machine.min_port; i <= machine.max_port; i++) {
		port_eee_status(i);
	}
}

/*
 * Enable RLDP, Realtek's version of LLDP
 */
void port_rldp_on(__xdata uint16_t p_ms)
{
	REG_WRITE(RTL8373_RLDP_TIMER, p_ms >> 8, p_ms, p_ms >> 8, p_ms);

	REG_SET(RTL837X_RMA0_CONF, 0x00000000); // R4ecc
	REG_SET(RTL837X_RMA_CONF, 0x00000000); // R4ecc
}


/*
 * Configure LAGs
 * Sets the members via port bitmask of a given Link Aggregation Group
 * The groups have numbers 0-3
 * The bitmask represents up to 10 ports
 * If currently no LAG has algorithm used, a default is applied
 */
void port_lag_members_set(__xdata uint8_t lag, __xdata uint16_t members) __banked
{
	print_string("port_lag_members_set, lag: "); print_byte(lag); print_string(", members: "); print_short(members);
	write_char('\n');
	if (lag > 3)
		print_string("Link aggregation group must be 0-3!\n");
	reg_read_m(RTL837X_TRK_HASH_CTRL_BASE + (lag << 2));
	if (!(sfr_data[0] | sfr_data [1] | sfr_data [2] | sfr_data [3]))
		REG_SET(RTL837X_TRK_HASH_CTRL_BASE, LAG_HASH_DEFAULT);
	REG_WRITE(RTL837X_TRK_MBR_CTRL_BASE + (lag << 2), 0, 0, members >> 8, members & 0xff);
}


/*
 * Configures the hash algorithm used for a LAG
 * lag is the Group to configure and hash is a bitmask
 */
void port_lag_hash_set(__xdata uint8_t lag, __xdata uint8_t hash_bits) __banked
{
	print_string("port_lag_hash_set, lag: "); print_byte(lag); print_string(", hash: "); print_byte(hash_bits);
	write_char('\n');
	if (lag > 3)
		print_string("Link aggregation group must be 0-3!\n");
	REG_WRITE(RTL837X_TRK_HASH_CTRL_BASE + (lag << 2), 0, 0, 0, hash_bits);
}

void print_port_ingress_filter_mode(vlan_ingress_mode_t mode) __banked
{
	switch (mode) {
	case VLAN_UNTAGGED:
		print_string("Untag.");
		break;
	case VLAN_TAGGED:
		print_string("Tagged");
		break;
	case VLAN_ALL:
		print_string("Any");
		break;
	default:
		print_string("!!err!!");
	}
}

static void print_phys_port(uint8_t port) __banked
{
	if (port >= machine.min_port && port <= machine.max_port)
		write_char(machine.log_to_phys_port[port] + '0');
	else if (port == 9)
		write_char('9');
	else
		write_char('?');
}

void print_vlan_ingress_port(uint8_t log_port) __banked
{
	print_phys_port(log_port);write_char('\t');
	print_short(port_pvid_get(log_port));write_char('\t');
	print_port_ingress_filter_mode(port_ingress_filter_get(log_port));write_char('\t');
	port_ingress_vlan_filter_get(log_port) ? print_string("Enabled") : print_string("Disabled");
	write_char('\n');
}
/*
 * Dumps the VLAN ingress configuration
 */
void vlan_dump(void) __banked
{
	print_string("Ingress VLAN configuration:\n");
	print_string("Port\tPVID\tType\tFiltering\n");
	for (uint8_t port = machine.min_port; port <= machine.max_port; port++) {
		print_vlan_ingress_port(port);
	}
	print_vlan_ingress_port(9);

	write_char('\n');
	print_string("Type - Which frame types are allowed: untagged, tagged or any\n");
	print_string("Filtering - Whether packets not belonging to member VLANs on that port are dropped\n");
	print_string("PVID - Assumed VLAN for untagged packets\n");
}


/** Set the ingress VLAN filtering */
bool port_ingress_vlan_filter_set(__xdata uint8_t port, __xdata bool enabled) __banked
{
	if (port < machine.min_port || port > machine.max_port && port != 9) {
		return false;
	}
	reg_bit_set(RTL837X_VLAN_PORT_IGR_FLTR, port);
	return true;
}

/** Get the ingress VLAN filtering status */
bool port_ingress_vlan_filter_get(__xdata uint8_t port) __banked
{
	if (port < machine.min_port || port > machine.max_port && port != 9) {
		return false;
	}

	return reg_bit_test(RTL837X_VLAN_PORT_IGR_FLTR, port);
}
