⛏️ index : haiku.git

/*
 *	ASIX AX88172/AX88772/AX88178 USB 2.0 Ethernet Driver.
 *	Copyright (c) 2008, 2011 S.Zharski <imker@gmx.li>
 *	Distributed under the terms of the MIT license.
 *
 *	Heavily based on code of the
 *	Driver for USB Ethernet Control Model devices
 *	Copyright (C) 2008 Michael Lotz <mmlr@mlotz.ch>
 *	Distributed under the terms of the MIT license.
 *
 */


#include "AX88772Device.h"

#include <net/if_media.h>

#include "ASIXVendorRequests.h"
#include "Settings.h"


// Most of vendor requests for all supported chip types use the same
// constants (see ASIXVendorRequests.h) but the layout of request data
// may be slightly diferrent for specific chip type. Below is a quick
// reference for AX88772 vendor requests data layout.

// READ_RXTX_SRAM,		//C002_AA0B_0C00_0800 Rx/Tx SRAM Read
// WRITE_RXTX_SRAM,		//4003_AA0B_0C00_0800 Rx/Tx SRAM Write
// SW_MII_OP,			//4006_0000_0000_0000 SW Serial Management Control
// READ_MII,			//c007_aa00_cc00_0200 PHY Read
// WRITE_MII,			//4008_aa00_cc00_0200 PHY Write
// READ_MII_OP_MODE,	//c009_0000_0000_0100 Serial Management Status
// HW_MII_OP,			//400a_0000_0000_0000 HW Serial Management Control
// READ_SROM,			//C00B_AA00_0000_0200 SROM Read
// WRITE_SROM,			//400C_AA00_CCDD_0000 SROM Write
// WRITE_SROM_ENABLE,	//400D_0000_0000_0000 SROM Write Enable
// WRITE_SROM_DISABLE,	//400E_0000_0000_0000 SROM Write Disable
// READ_RX_CONTROL,		//C00F_0000_0000_0200 Read Rx Control
// WRITE_RX_CONTROL,	//4010_AABB_0000_0000 Write Rx Control
// READ_IPGS,			//C011_0000_0000_0300 Read IPG/IPG1/IPG2 Register
// WRITE_IPGS,			//4012_AABB_CC00_0000 Write IPG/IPG1/IPG2 Register
// READ_NODEID,			//C013_0000_0000_0600 Read Node ID
// WRITE_NODEID,		//4014_0000_0000_0600 Write Node ID
// READ_MF_ARRAY,		//C015_0000_0000_0800 Read Multicast Filter Array
// WRITE_MF_ARRAY,		//4016_0000_0000_0800 Write Multicast Filter Array
// READ_TEST,			//4017_AA00_0000_0000 Write Test Register
// READ_PHYID,			//C019_0000_0000_0200 Read Ethernet/HomePNA PHY Address
// READ_MEDIUM_STATUS,	//C01A_0000_0000_0200 Read Medium Status
// WRITE_MEDIUM_MODE,	//401B_AABB_0000_0000 Write Medium Mode Register
// GET_MONITOR_MODE,	//C01C_0000_0000_0100 Read Monitor Mode Status
// SET_MONITOR_MODE,	//401D_AA00_0000_0000 Write Monitor Mode Register
// READ_GPIOS,			//C01E_0000_0000_0100 Read GPIOs Status
// WRITE_GPIOS,			//401F_AA00_0000_0000 Write GPIOs
// WRITE_SOFT_RESET,	//4020_AA00_0000_0000 Write Software Reset
// READ_PHY_SEL_STATE,	//C021_AA00_0000_0100 Read Software PHY Select Status
// WRITE_PHY_SEL,		//4022_AA00_0000_0000 Write Software PHY Select

// RX Control Register bits
// RXCTL_PROMISCUOUS,	// forward all frames up to the host
// RXCTL_ALL_MULTICAT,	// forward all multicast frames up to the host
// RXCTL_SEP,			// forward frames with CRC error up to the host
// RXCTL_BROADCAST,		// forward broadcast frames up to the host
// RXCTL_MULTICAST,		// forward multicast frames that are
//							matching to multicast filter up to the host
// RXCTL_AP,			// forward unicast frames that are matching
//							to multicast filter up to the host
// RXCTL_START,			// ethernet MAC start operating
// RXCTL_USB_MFB,		// Max Frame Burst TX on USB


// PHY IDs request answer data layout
struct AX88772_PhyIDs {
	uint8 SecPhyID;
	uint8 PriPhyID2;
} _PACKED;


// Medium state bits
enum AX88772_MediumState {
	MEDIUM_STATE_FD		= 0x0002,
	MEDIUM_STATE_BIT2	= 0x0004, // must be always set
	MEDIUM_STATE_RFC	= 0x0010,
	MEDIUM_STATE_TFC	= 0x0020,
	MEDIUM_STATE_PF_ON	= 0x0040,
	MEDIUM_STATE_PF_OFF	= 0x0000,
	MEDIUM_STATE_RE	   	= 0x0100,
	MEDIUM_STATE_PS_100	= 0x0200,
	MEDIUM_STATE_PS_10 	= 0x0000,
	MEDIUM_STATE_SBP1  	= 0x0800,
	MEDIUM_STATE_SBP0  	= 0x0000,
	MEDIUM_STATE_SM_ON 	= 0x1000
};


// Monitor Mode bits
enum AX88772_MonitorMode {
	MONITOR_MODE_MOM	= 0x01,
	MONITOR_MODE_RWLU	= 0x02,
	MONITOR_MODE_RWMP	= 0x04,
	MONITOR_MODE_US 	= 0x10
};


// General Purpose I/O Register
enum AX88772_GPIO {
	GPIO_OO_0EN	= 0x01,
	GPIO_IO_0	= 0x02,
	GPIO_OO_1EN	= 0x04,
	GPIO_IO_1	= 0x08,
	GPIO_OO_2EN	= 0x10,
	GPIO_IO_2	= 0x20,
	GPIO_RSE	= 0x80
};


// Software Reset Register bits
enum AX88772_SoftwareReset {
	SW_RESET_CLR	= 0x00,
	SW_RESET_RR		= 0x01,
	SW_RESET_RT		= 0x02,
	SW_RESET_PRTE	= 0x04,
	SW_RESET_PRL	= 0x08,
	SW_RESET_BZ		= 0x10,
	SW_RESET_IPRL	= 0x20,
	SW_RESET_IPPD	= 0x40
};


// Software PHY Select Status
enum AX88772_SoftwarePHYSelStatus {
	SW_PHY_SEL_STATUS_EXT		= 0x00,
	SW_PHY_SEL_STATUS_INT		= 0x01,
	SW_PHY_SEL_STATUS_ASEL		= 0x02,
	SW_PHY_SEL_STATUS_SS_MII	= 0x04,
	SW_PHY_SEL_STATUS_SS_RVRS_MII	= 0x08,
	SW_PHY_SEL_STATUS_SS_RVRS_GMII	= 0x0C,
	SW_PHY_SEL_STATUS_SS_ENB	= 0x10
};


// Notification data layout
struct AX88772_Notify {
	uint8  btA1;
	uint8  bt01;
	uint8  btBB; // AX88772_BBState below
	uint8  bt03;
	uint16 regCCDD;
	uint16 regEEFF;
} _PACKED;


// Link-State bits
enum AX88772_BBState {
	LINK_STATE_PPLS		= 0x01,
	LINK_STATE_SPLS		= 0x02,
	LINK_STATE_FLE		= 0x04,
	LINK_STATE_MDINT	= 0x08
};

// RX Control Register bits (772B)
enum ASIX772RXControl {
	RXCTL_HDR_TYPE_0	= 0x0000,
	RXCTL_HDR_TYPE_1	= 0x0100,
	RXCTL_HDR_IPALIGN	= 0x0200,
	RXCTL_ADD_CHKSUM	= 0x0400,
};

// EEPROM Map.
enum AX88772B_EEPROM {
	EEPROM_772B_NODE_ID	= 0x04,
	EEPROM_772B_PHY_PWRCFG	= 0x18
};

enum AX88772B_MFB {
	AX88772B_MFB_2K = 0,
	AX88772B_MFB_4K = 1,
	AX88772B_MFB_6K = 2,
	AX88772B_MFB_8K = 3,
	AX88772B_MFB_16K = 4,
	AX88772B_MFB_20K = 5,
	AX88772B_MFB_24K = 6,
	AX88772B_MFB_32K = 7,
	AX88772B_MFB_Count = 8
};

struct _AX88772B_MFB {
	size_t ByteCount;
	size_t Threshold;
	size_t Size;

} AX88772B_MFBTable[AX88772B_MFB_Count] = {
	{ 0x8000, 0x8001, 2048 },
	{ 0x8100, 0x8147, 4096 },
	{ 0x8200, 0x81EB, 6144 },
	{ 0x8300, 0x83D7, 8192 },
	{ 0x8400, 0x851E, 16384 },
	{ 0x8500, 0x8666, 20480 },
	{ 0x8600, 0x87AE, 24576 },
	{ 0x8700, 0x8A3D, 32768 }
};

const uint16 maxFrameSize = 1536;


AX88772Device::AX88772Device(usb_device device, DeviceInfo& deviceInfo)
	:
	ASIXDevice(device, deviceInfo)
{
	fStatus = InitDevice();
}


status_t
AX88772Device::InitDevice()
{
	fFrameSize = maxFrameSize;
	fUseTRXHeader = true;

	fReadNodeIDRequest = READ_NODEID;

	fNotifyBufferLength = sizeof(AX88772_Notify);
	fNotifyBuffer = (uint8 *)malloc(fNotifyBufferLength);
	if (fNotifyBuffer == NULL) {
		TRACE_ALWAYS("Error of allocating memory for notify buffer.\n");
		return B_NO_MEMORY;
	}

	return B_OK;
}


status_t
AX88772Device::ReadMACAddress(ether_address_t *address)
{
	if (fDeviceInfo.fType != DeviceInfo::AX88772B)
		return ASIXDevice::ReadMACAddress(address);

	// Auto-loaded default station address from internal ROM is
	// 00:00:00:00:00:00 such that an explicit access to EEPROM
	// is required to get real station address.
	for (size_t i = 0; i < sizeof(ether_address_t) / 2; i++) {
		size_t actual_length = 0;
		uint16 addr = 0;
		status_t result = gUSBModule->send_request(fDevice,
			USB_REQTYPE_VENDOR | USB_REQTYPE_DEVICE_IN, READ_SROM,
			EEPROM_772B_NODE_ID + i, 0, sizeof(addr), &addr, &actual_length);
		if (result != B_OK) {
			TRACE_ALWAYS("Error reading MAC[%d] address:%#010x\n", i, result);
			return result;
		}

		address->ebyte[i * 2 + 0] = (uint8)addr;
		address->ebyte[i * 2 + 1] = (uint8)(addr >> 8);
	}

	return B_OK;
}


status_t
AX88772Device::SetupDevice(bool deviceReplugged)
{
	status_t result = ASIXDevice::SetupDevice(deviceReplugged);
	if (result != B_OK) {
		return result;
	}

	result = fMII.Init(fDevice);

	switch (fDeviceInfo.fType) {
		case DeviceInfo::AX88772A:
			result = _SetupAX88772A();
			break;
		case DeviceInfo::AX88772B:
			result = _SetupAX88772B();
			break;
		default:
			result = _SetupAX88772();
			break;
	}

	if (result != B_OK)
		return result;

	result = fMII.SetupPHY();
	if (result != B_OK) {
		return result;
	}

	size_t actualLength = 0;
	result = gUSBModule->send_request(fDevice,
		USB_REQTYPE_VENDOR | USB_REQTYPE_DEVICE_OUT, WRITE_MEDIUM_MODE,
		MEDIUM_STATE_FD | MEDIUM_STATE_BIT2 | MEDIUM_STATE_RFC
		| MEDIUM_STATE_TFC | MEDIUM_STATE_RE | MEDIUM_STATE_PS_100,
		0, 0, 0, &actualLength);

	if (result != B_OK) {
		TRACE_ALWAYS("Error of setting medium mode: %#010x\n", result);
	}

	TRACE_RET(result);
	return result;
}


status_t
AX88772Device::_SetupAX88772()
{
	size_t actualLength = 0;
	// enable GPIO2 - magic from FreeBSD's if_axe
	uint16 GPIOs = GPIO_OO_2EN | GPIO_IO_2 | GPIO_RSE;
	status_t result = gUSBModule->send_request(fDevice,
		USB_REQTYPE_VENDOR | USB_REQTYPE_DEVICE_OUT, WRITE_GPIOS,
		GPIOs, 0, 0, 0, &actualLength);

	if (result != B_OK) {
		TRACE_ALWAYS("Error of wrinting GPIOs: %#010x\n", result);
		return result;
	}

	// select PHY
	bool useEmbeddedPHY = fMII.PHYID() == PHYIDEmbedded;
	uint16 selectPHY = useEmbeddedPHY
		? SW_PHY_SEL_STATUS_INT : SW_PHY_SEL_STATUS_EXT;

	result = gUSBModule->send_request(fDevice,
		USB_REQTYPE_VENDOR | USB_REQTYPE_DEVICE_OUT,
		WRITE_PHY_SEL, selectPHY, 0, 0, 0, &actualLength);
	snooze(10000);

	TRACE("Selecting %s PHY[%#02x].\n",
		useEmbeddedPHY ? "embedded" : "external", selectPHY);

	if (result != B_OK) {
		TRACE_ALWAYS("Error of selecting PHY:%#010x\n", result);
		return result;
	}

	struct SWReset {
		uint16 		reset;
		bigtime_t	delay;
	} resetCommands[] = {
	// EMBEDDED PHY
		// power down and reset state, pin reset state
		{ SW_RESET_CLR, 60000 },
		// power down/reset state, pin operating state
		{ SW_RESET_PRL | SW_RESET_IPPD, 150000 },
		// power up, reset
		{ SW_RESET_PRL, 0 },
		// power up, operating
		{ SW_RESET_PRL | SW_RESET_IPRL, 0 },
	// EXTERNAL PHY
		// power down/reset state, pin operating state
		{ SW_RESET_PRL | SW_RESET_IPPD, 0 }
	};

	size_t from = useEmbeddedPHY ? 0 : 4;
	size_t to   = useEmbeddedPHY ? 3 : 4;

	for (size_t i = from; i <= to; i++) {
		result = gUSBModule->send_request(fDevice,
			USB_REQTYPE_VENDOR | USB_REQTYPE_DEVICE_OUT, WRITE_SOFT_RESET,
			resetCommands[i].reset, 0, 0, 0, &actualLength);

		snooze(resetCommands[i].delay);

		if (result != B_OK) {
			TRACE_ALWAYS("Error of SW reset command %d:[%#04x]: %#010x\n",
				i, resetCommands[i].reset, result);
			return result;
		}
	}

	snooze(150000);

	return B_OK;
}


status_t
AX88772Device::_WakeupPHY()
{
	// select PHY
	bool useEmbeddedPHY = fMII.PHYID() == PHYIDEmbedded;
	uint16 selectPHY = useEmbeddedPHY
		? SW_PHY_SEL_STATUS_INT : SW_PHY_SEL_STATUS_EXT;

	selectPHY |= SW_PHY_SEL_STATUS_SS_MII | SW_PHY_SEL_STATUS_SS_ENB;

	size_t actualLength = 0;
	status_t result = gUSBModule->send_request(fDevice,
		USB_REQTYPE_VENDOR | USB_REQTYPE_DEVICE_OUT, WRITE_PHY_SEL,
		selectPHY, 0, 0, 0, &actualLength);
	snooze(31000);

	TRACE("Selecting %s PHY[%#02x].\n",
		useEmbeddedPHY ? "embedded" : "external", selectPHY);

	if (result != B_OK) {
		TRACE_ALWAYS("Error of selecting PHY:%#010x\n", result);
		return result;
	}

	struct SWReset {
		uint16 		reset;
		bigtime_t	delay;
	} resetCommands[] = {
		{ SW_RESET_IPRL | SW_RESET_IPPD, 250000 },
		{ SW_RESET_IPRL, 1000000 },
		{ SW_RESET_CLR, 31000 },
		{ SW_RESET_IPRL, 31000 }
	};

	for (size_t i = 0; i < B_COUNT_OF(resetCommands); i++) {
		result = gUSBModule->send_request(fDevice,
			USB_REQTYPE_VENDOR | USB_REQTYPE_DEVICE_OUT, WRITE_SOFT_RESET,
			resetCommands[i].reset, 0, 0, 0, &actualLength);

		snooze(resetCommands[i].delay);

		if (result != B_OK) {
			TRACE_ALWAYS("Error of SW reset command %d:[%#04x]: %#010x\n",
				i, resetCommands[i].reset, result);
			return result;
		}
	}

	return B_OK;
}


status_t
AX88772Device::_SetupAX88772A()
{
	// Reload EEPROM
	size_t actualLength = 0;
	status_t result = gUSBModule->send_request(fDevice,
		USB_REQTYPE_VENDOR | USB_REQTYPE_DEVICE_OUT, WRITE_GPIOS,
		GPIO_RSE, 0, 0, 0, &actualLength);

	if (result != B_OK) {
		TRACE_ALWAYS("Error of reloading EEPROM: %#010x\n", result);
		return result;
	}

	result = _WakeupPHY();
	if (result != B_OK)
		return result;

	fIPG[0] = 0x15;
	fIPG[1] = 0x16;
	fIPG[2] = 0x1A;

	return B_OK;
}


status_t
AX88772Device::_SetupAX88772B()
{
	// Reload EEPROM
	size_t actualLength = 0;
	status_t result = gUSBModule->send_request(fDevice,
		USB_REQTYPE_VENDOR | USB_REQTYPE_DEVICE_OUT, WRITE_GPIOS,
		GPIO_RSE, 0, 0, 0, &actualLength);

	if (result != B_OK) {
		TRACE_ALWAYS("Error of reloading EEPROM: %#010x\n", result);
		return result;
	}

	result = _WakeupPHY();
	if (result != B_OK)
		return result;

	fIPG[0] = 0x15;
	fIPG[1] = 0x16;
	fIPG[2] = 0x1A;

	return B_OK;
}


status_t
AX88772Device::StartDevice()
{
	size_t actualLength = 0;
	status_t result = gUSBModule->send_request(fDevice,
		USB_REQTYPE_VENDOR | USB_REQTYPE_DEVICE_OUT, WRITE_IPGS,
		0, 0, sizeof(fIPG), fIPG, &actualLength);

	if (result != B_OK) {
		TRACE_ALWAYS("Error of writing IPGs:%#010x\n", result);
		return result;
	}

	if (actualLength != sizeof(fIPG)) {
		TRACE_ALWAYS("Mismatch of written IPGs data. "
			"%d bytes of %d written.\n", actualLength, sizeof(fIPG));

	}

	uint16 rxcontrol = 0;

	// AX88772B uses different maximum frame burst configuration.
	if (fDeviceInfo.fType == DeviceInfo::AX88772B) {
		result = gUSBModule->send_request(fDevice,
			USB_REQTYPE_VENDOR | USB_REQTYPE_DEVICE_OUT, WRITE_RXCONTROL_CFG,
			AX88772B_MFBTable[AX88772B_MFB_2K].ByteCount,
			AX88772B_MFBTable[AX88772B_MFB_2K].Threshold, 0, 0, &actualLength);

		if (result != B_OK) {
			TRACE_ALWAYS("Error of writing frame burst:%#010x\n", result);
			return result;
		}
		rxcontrol = RXCTL_HDR_TYPE_1;
	} else {
		// TODO: FreeBSD documents this to speed up xfers, I don't
		// have the hardware to test however.
		// rxcontrol = RXCTL_USB_MFB_MAX;
	}

	rxcontrol |= RXCTL_START | RXCTL_BROADCAST;
	result = WriteRXControlRegister(rxcontrol);
	if (result != B_OK) {
		TRACE_ALWAYS("Error of writing %#04x RX Control:%#010x\n",
			rxcontrol, result);
	}

	TRACE_RET(result);
	return result;
}


status_t
AX88772Device::OnNotify(uint32 actualLength)
{
	if (actualLength < sizeof(AX88772_Notify)) {
		TRACE_ALWAYS("Data underrun error. %d of %d bytes received\n",
			actualLength, sizeof(AX88772_Notify));
		return B_BAD_DATA;
	}

	AX88772_Notify *notification = (AX88772_Notify *)fNotifyBuffer;

	if (notification->btA1 != 0xa1) {
		TRACE_ALWAYS("Notify magic byte is invalid: %#02x\n",
			notification->btA1);
	}

	uint phyIndex = 0;
	bool linkIsUp = fHasConnection;
	switch(fMII.ActivePHY()) {
		case PrimaryPHY:
			phyIndex = 1;
			linkIsUp = (notification->btBB & LINK_STATE_PPLS)
				== LINK_STATE_PPLS;
			break;
		case SecondaryPHY:
			phyIndex = 2;
			linkIsUp = (notification->btBB & LINK_STATE_SPLS)
				== LINK_STATE_SPLS;
			break;
		default:
		case CurrentPHY:
			TRACE_ALWAYS("Error: PHY is not initialized.\n");
			return B_NO_INIT;
	}

	bool linkStateChange = linkIsUp != fHasConnection;
	fHasConnection = linkIsUp;

	if (linkStateChange) {
		TRACE("Link state of PHY%d has been changed to '%s'\n",
			phyIndex, fHasConnection ? "up" : "down");
	}

	if (linkStateChange && fLinkStateChangeSem >= B_OK)
		release_sem_etc(fLinkStateChangeSem, 1, B_DO_NOT_RESCHEDULE);

	return B_OK;
}


status_t
AX88772Device::GetLinkState(ether_link_state *linkState)
{
	size_t actualLength = 0;
	uint16 mediumStatus = 0;
	status_t result = gUSBModule->send_request(fDevice,
		USB_REQTYPE_VENDOR | USB_REQTYPE_DEVICE_IN, READ_MEDIUM_STATUS,
		0, 0, sizeof(mediumStatus), &mediumStatus, &actualLength);

	if (result != B_OK) {
		TRACE_ALWAYS("Error of reading medium status:%#010x.\n", result);
		return result;
	}

	if (actualLength != sizeof(mediumStatus)) {
		TRACE_ALWAYS("Mismatch of reading medium status."
			"Read %d bytes instead of %d\n", actualLength,
			sizeof(mediumStatus));
	}

	TRACE_FLOW("Medium status is %#04x\n", mediumStatus);

	linkState->quality = 1000;

	linkState->media = IFM_ETHER | (fHasConnection ? IFM_ACTIVE : 0);
	linkState->media |= (mediumStatus & MEDIUM_STATE_FD)
		? IFM_FULL_DUPLEX : IFM_HALF_DUPLEX;

	linkState->speed = (mediumStatus & MEDIUM_STATE_PS_100)
		? 100000000 : 10000000;

	TRACE_FLOW("Medium state: %s, %lld MBit/s, %s duplex.\n",
		(linkState->media & IFM_ACTIVE) ? "active" : "inactive",
		linkState->speed / 1000000,
		(linkState->media & IFM_FULL_DUPLEX) ? "full" : "half");
	return B_OK;
}