shithub: riscv

ref: 29b1f55e7390043efef033578083694fb85dbe99
dir: /sys/src/9/pc/etherrt2860.c/

View raw version
/* 
 * Ralink RT2860 driver
 *
 * Written without any documentation but Damien Bergaminis
 * OpenBSD ral(4) driver sources. Requires ralink firmware
 * to be present in /lib/firmware/ral-rt2860 on attach.
 */

#include "u.h"
#include "../port/lib.h"
#include "mem.h"
#include "dat.h"
#include "fns.h"
#include "io.h"
#include "../port/pci.h"
#include "../port/error.h"
#include "../port/netif.h"
#include "../port/etherif.h"
#include "../port/wifi.h"

enum {
	/* PCI registers */
	PciCfg = 0x0000,
		PciCfgUsb = (1 << 17),
		PciCfgPci = (1 << 16),
	PciEectrl = 0x0004,
		EectrlC = (1 << 0),
		EectrlS = (1 << 1),
		EectrlD = (1 << 2),
		EectrlShiftD = 2,
		EectrlQ = (1 << 3),
		EectrlShiftQ = 3,
	PciMcuctrl = 0x0008,
	PciSysctrl = 0x000c,
	PcieJtag = 0x0010,

	Rt3090AuxCtrl = 0x010c,

	Rt3070Opt14 = 0x0114,
};

enum {
	/* SCH/DMA registers */
	IntStatus = 0x0200,
		/* flags for registers IntStatus/IntMask */
		TxCoherent = (1 << 17),
		RxCoherent = (1 << 16),
		MacInt4 = (1 << 15),
		MacInt3 = (1 << 14),
		MacInt2 = (1 << 13),
		MacInt1 = (1 << 12),
		MacInt0 = (1 << 11),
		TxRxCoherent = (1 << 10),
		McuCmdInt = (1 << 9),
		TxDoneInt5 = (1 << 8),
		TxDoneInt4 = (1 << 7),
		TxDoneInt3 = (1 << 6),
		TxDoneInt2 = (1 << 5),
		TxDoneInt1 = (1 << 4),
		TxDoneInt0 = (1 << 3),
		RxDoneInt = (1 << 2),
		TxDlyInt = (1 << 1),
		RxDlyInt = (1 << 0),
	IntMask = 0x0204,
	WpdmaGloCfg = 0x0208,
		HdrSegLenShift = 8,
		BigEndian = (1 << 7),
		TxWbDdone = (1 << 6),
		WpdmaBtSizeShift = 4,
		WpdmaBtSize16 = 0,
		WpdmaBtSize32 = 1,
		WpdmaBtSize64 = 2,
		WpdmaBtSize128 = 3,
		RxDmaBusy = (1 << 3),
		RxDmaEn = (1 << 2),
		TxDmaBusy = (1 << 1),
		TxDmaEn = (1 << 0),
	WpdmaRstIdx = 0x020c,
	DelayIntCfg = 0x0210,
		TxdlyIntEn = (1 << 31),
		TxmaxPintShift = 24,
		TxmaxPtimeShift = 16,
		RxdlyIntEn = (1 << 15),
		RxmaxPintShift = 8,
		RxmaxPtimeShift = 0,
	WmmAifsnCfg = 0x0214,
	WmmCwminCfg = 0x0218,
	WmmCwmaxCfg = 0x021c,
	WmmTxop0Cfg = 0x0220,
	WmmTxop1Cfg = 0x0224,
	GpioCtrl = 0x0228,
		GpioDShift = 8,
		GpioOShift = 0,
	McuCmdReg = 0x022c,
#define TxBasePtr(qid) (0x0230 + (qid) * 16)
#define TxMaxCnt(qid) (0x0234 + (qid) * 16)
#define TxCtxIdx(qid) (0x0238 + (qid) * 16)
#define TxDtxIdx(qid) (0x023c + (qid) * 16)
	RxBasePtr = 0x0290,
	RxMaxCnt = 0x0294,
	RxCalcIdx = 0x0298,
	FsDrxIdx = 0x029c,
	UsbDmaCfg = 0x02a0 /* RT2870 only */,
		UsbTxBusy = (1 << 31),
		UsbRxBusy = (1 << 30),
		UsbEpoutVldShift = 24,
		UsbTxEn = (1 << 23),
		UsbRxEn = (1 << 22),
		UsbRxAggEn = (1 << 21),
		UsbTxopHalt = (1 << 20),
		UsbTxClear = (1 << 19),
		UsbPhyWdEn = (1 << 16),
		UsbPhyManRst = (1 << 15),
#define UsbRxAggLmt(x) ((x) << 8) /* in unit of 1KB */
#define UsbRxAggTo(x) ((x) & 0xff) /* in unit of 33ns */
	UsCycCnt = 0x02a4,
		TestEn = (1 << 24),
		TestSelShift = 16,
		BtModeEn = (1 << 8),
		UsCycCntShift = 0,
};

enum {
	/* PBF registers */
	SysCtrl = 0x0400,
		HstPmSel = (1 << 16),
		CapMode = (1 << 14),
		PmeOen = (1 << 13),
		Clkselect = (1 << 12),
		PbfClkEn = (1 << 11),
		MacClkEn = (1 << 10),
		DmaClkEn = (1 << 9),
		McuReady = (1 << 7),
		AsyReset = (1 << 4),
		PbfReset = (1 << 3),
		MacReset = (1 << 2),
		DmaReset = (1 << 1),
		McuReset = (1 << 0),
	HostCmd = 0x0404,
		McuCmdSleep = 0x30,
		McuCmdWakeup = 0x31,
		McuCmdLeds = 0x50,
			LedRadio = (1 << 13),
			LedLink2ghz = (1 << 14),
			LedLink5ghz = (1 << 15),
		McuCmdLedRssi = 0x51,
		McuCmdLed1 = 0x52,
		McuCmdLed2 = 0x53,
		McuCmdLed3 = 0x54,
		McuCmdRfreset = 0x72,
		McuCmdAntsel = 0x73,
		McuCmdBbp = 0x80,
		McuCmdPslevel = 0x83,
	PbfCfg = 0x0408,
		Tx1qNumShift = 21,
		Tx2qNumShift = 16,
		Null0Mode = (1 << 15),
		Null1Mode = (1 << 14),
		RxDropMode = (1 << 13),
		Tx0qManual = (1 << 12),
		Tx1qManual = (1 << 11),
		Tx2qManual = (1 << 10),
		Rx0qManual = (1 << 9),
		HccaEn = (1 << 8),
		Tx0qEn = (1 << 4),
		Tx1qEn = (1 << 3),
		Tx2qEn = (1 << 2),
		Rx0qEn = (1 << 1),
	MaxPcnt = 0x040c,
	BufCtrl = 0x0410,
#define WriteTxq(qid) (1 << (11 - (qid)))
		Null0Kick = (1 << 7),
		Null1Kick = (1 << 6),
		BufReset = (1 << 5),
#define ReadTxq(qid) = (1 << (3 - (qid))
		ReadRx0q = (1 << 0),
	McuIntSta = 0x0414,
		/* flags for registers McuIntSta/McuIntEna */
		McuMacInt8 = (1 << 24),
		McuMacInt7 = (1 << 23),
		McuMacInt6 = (1 << 22),
		McuMacInt4 = (1 << 20),
		McuMacInt3 = (1 << 19),
		McuMacInt2 = (1 << 18),
		McuMacInt1 = (1 << 17),
		McuMacInt0 = (1 << 16),
		Dtx0Int = (1 << 11),
		Dtx1Int = (1 << 10),
		Dtx2Int = (1 << 9),
		Drx0Int = (1 << 8),
		HcmdInt = (1 << 7),
		N0txInt = (1 << 6),
		N1txInt = (1 << 5),
		BcntxInt = (1 << 4),
		Mtx0Int = (1 << 3),
		Mtx1Int = (1 << 2),
		Mtx2Int = (1 << 1),
		Mrx0Int = (1 << 0),
	McuIntEna = 0x0418,
#define TxqIo(qid) (0x041c + (qid) * 4)
	Rx0qIo = 0x0424,
	BcnOffset0 = 0x042c,
	BcnOffset1 = 0x0430,
	TxrxqSta = 0x0434,
	TxrxqPcnt = 0x0438,
		Rx0qPcntMask = 0xff000000,
		Tx2qPcntMask = 0x00ff0000,
		Tx1qPcntMask = 0x0000ff00,
		Tx0qPcntMask = 0x000000ff,
	PbfDbg = 0x043c,
	CapCtrl = 0x0440,
		CapAdcFeq = (1 << 31),
		CapStart = (1 << 30),
		ManTrig = (1 << 29),
		TrigOffsetShift = 16,
		StartAddrShift = 0,
};

enum {
	/* RT3070 registers */
	Rt3070RfCsrCfg = 0x0500,
		Rt3070RfKick = (1 << 17),
		Rt3070RfWrite = (1 << 16),
	Rt3070EfuseCtrl = 0x0580,
		Rt3070SelEfuse = (1 << 31),
		Rt3070EfsromKick = (1 << 30),
		Rt3070EfsromAinMask = 0x03ff0000,
		Rt3070EfsromAinShift = 16,
		Rt3070EfsromModeMask = 0x000000c0,
		Rt3070EfuseAoutMask = 0x0000003f,
	Rt3070EfuseData0 = 0x0590,
	Rt3070EfuseData1 = 0x0594,
	Rt3070EfuseData2 = 0x0598,
	Rt3070EfuseData3 = 0x059c,
	Rt3090OscCtrl = 0x05a4,
	Rt3070LdoCfg0 = 0x05d4,
	Rt3070GpioSwitch = 0x05dc,
};

enum {
	/* MAC registers */
	AsicVerId = 0x1000,
	MacSysCtrl = 0x1004,
		RxTsEn = (1 << 7),
		WlanHaltEn = (1 << 6),
		PbfLoopEn = (1 << 5),
		ContTxTest = (1 << 4),
		MacRxEn = (1 << 3),
		MacTxEn = (1 << 2),
		BbpHrst = (1 << 1),
		MacSrst = (1 << 0),
	MacAddrDw0 = 0x1008,
	MacAddrDw1 = 0x100c,
	MacBssidDw0 = 0x1010,
	MacBssidDw1 = 0x1014,
		MultiBcnNumShift = 18,
		MultiBssidModeShift = 16,
	MaxLenCfg = 0x1018,
		MinMpduLenShift = 16,
		MaxPsduLenShift = 12,
		MaxPsduLen8k = 0,
		MaxPsduLen16k = 1,
		MaxPsduLen32k = 2,
		MaxPsduLen64k = 3,
		MaxMpduLenShift = 0,
	BbpCsrCfg = 0x101c,
		BbpRwParallel = (1 << 19),
		BbpParDur1125 = (1 << 18),
		BbpCsrKick = (1 << 17),
		BbpCsrRead = (1 << 16),
		BbpAddrShift = 8,
		BbpDataShift = 0,
	RfCsrCfg0 = 0x1020,
		RfRegCtrl = (1 << 31),
		RfLeSel1 = (1 << 30),
		RfLeStby = (1 << 29),
		RfRegWidthShift = 24,
		RfReg0Shift = 0,
	RfCsrCfg1 = 0x1024,
		RfDur5 = (1 << 24),
		RfReg1Shift = 0,
	RfCsrCfg2 = 0x1028,
	LedCfg = 0x102c,
		LedPol = (1 << 30),
		YLedModeShift = 28,
		GLedModeShift = 26,
		RLedModeShift = 24,
		LedModeOff = 0,
		LedModeBlinkTx = 1,
		LedModeSlowBlink = 2,
		LedModeOn = 3,
		SlowBlkTimeShift = 16,
		LedOffTimeShift = 8,
		LedOnTimeShift = 0,
};

enum {
	/* undocumented registers */
	Debug = 0x10f4,
};

enum {
	/* MAC Timing control registers */
	XifsTimeCfg = 0x1100,
		BbRxendEn = (1 << 29),
		EifsTimeShift = 20,
		OfdmXifsTimeShift = 16,
		OfdmSifsTimeShift = 8,
		CckSifsTimeShift = 0,
	BkoffSlotCfg = 0x1104,
		CcDelayTimeShift = 8,
		SlotTime = 0,
	NavTimeCfg = 0x1108,
		NavUpd = (1 << 31),
		NavUpdValShift = 16,
		NavClrEn = (1 << 15),
		NavTimerShift = 0,
	ChTimeCfg = 0x110c,
		EifsAsChBusy = (1 << 4),
		NavAsChBusy = (1 << 3),
		RxAsChBusy = (1 << 2),
		TxAsChBusy = (1 << 1),
		ChStaTimerEn = (1 << 0),
	PbfLifeTimer = 0x1110,
	BcnTimeCfg = 0x1114,
		TsfInsCompShift = 24,
		BcnTxEn = (1 << 20),
		TbttTimerEn = (1 << 19),
		TsfSyncModeShift = 17,
		TsfSyncModeDis = 0,
		TsfSyncModeSta = 1,
		TsfSyncModeIbss = 2,
		TsfSyncModeHostap = 3,
		TsfTimerEn = (1 << 16),
		BcnIntvalShift = 0,
	TbttSyncCfg = 0x1118,
		BcnCwminShift = 20,
		BcnAifsnShift = 16,
		BcnExpWinShift = 8,
		TbttAdjustShift = 0,
	TsfTimerDw0 = 0x111c,
	TsfTimerDw1 = 0x1120,
	TbttTimer = 0x1124,
	IntTimerCfg = 0x1128,
		GpTimerShift = 16,
		PreTbttTimerShift = 0,
	IntTimerEn = 0x112c,
		GpTimerEn = (1 << 1),
		PreTbttIntEn = (1 << 0),
	ChIdleTime = 0x1130,
};

enum {
	/* MAC Power Save configuration registers */
	MacStatusReg = 0x1200,
		RxStatusBusy = (1 << 1),
		TxStatusBusy = (1 << 0),
	PwrPinCfg = 0x1204,
		IoAddaPd = (1 << 3),
		IoPllPd = (1 << 2),
		IoRaPe = (1 << 1),
		IoRfPe = (1 << 0),
	AutoWakeupCfg = 0x1208,
		AutoWakeupEn = (1 << 15),
		SleepTbttNumShift = 8,
		WakeupLeadTimeShift = 0,
};

enum {
	/* MAC TX configuration registers */
#define EdcaAcCfg(aci) (0x1300 + (aci) * 4)
	EdcaTidAcMap = 0x1310,
#define TxPwrCfg(ridx) (0x1314 + (ridx) * 4)
	TxPinCfg = 0x1328,
		Rt3593LnaPeG2Pol = (1 << 31),
		Rt3593LnaPeA2Pol = (1 << 30),
		Rt3593LnaPeG2En = (1 << 29),
		Rt3593LnaPeA2En = (1 << 28),
		Rt3593LnaPe2En = (Rt3593LnaPeA2En | Rt3593LnaPeG2En),
		Rt3593PaPeG2Pol = (1 << 27),
		Rt3593PaPeA2Pol = (1 << 26),
		Rt3593PaPeG2En = (1 << 25),
		Rt3593PaPeA2En = (1 << 24),
		TrswPol = (1 << 19),
		TrswEn = (1 << 18),
		RftrPol = (1 << 17),
		RftrEn = (1 << 16),
		LnaPeG1Pol = (1 << 15),
		LnaPeA1Pol = (1 << 14),
		LnaPeG0Pol = (1 << 13),
		LnaPeA0Pol = (1 << 12),
		LnaPeG1En = (1 << 11),
		LnaPeA1En = (1 << 10),
		LnaPe1En = (LnaPeA1En | LnaPeG1En),
		LnaPeG0En = (1 << 9),
		LnaPeA0En = (1 << 8),
		LnaPe0En = (LnaPeA0En | LnaPeG0En),
		PaPeG1Pol = (1 << 7),
		PaPeA1Pol = (1 << 6),
		PaPeG0Pol = (1 << 5),
		PaPeA0Pol = (1 << 4),
		PaPeG1En = (1 << 3),
		PaPeA1En = (1 << 2),
		PaPeG0En = (1 << 1),
		PaPeA0En = (1 << 0),
	TxBandCfg = 0x132c,
		Tx5gBandSelN = (1 << 2),
		Tx5gBandSelP = (1 << 1),
		TxBandSel = (1 << 0),
	TxSwCfg0 = 0x1330,
		DlyRftrEnShift = 24,
		DlyTrswEnShift = 16,
		DlyPapeEnShift = 8,
		DlyTxpeEnShift = 0,
	TxSwCfg1 = 0x1334,
		DlyRftrDisShift = 16,
		DlyTrswDisShift = 8,
		DlyPapeDisShift = 0,
	TxSwCfg2 = 0x1338,
		DlyLnaEnShift = 24,
		DlyLnaDisShift = 16,
		DlyDacEnShift = 8,
		DlyDacDisShift = 0,
	TxopThresCfg = 0x133c,
		TxopRemThresShift = 24,
		CfEndThresShift = 16,
		RdgInThres = 8,
		RdgOutThres = 0,
	TxopCtrlCfg = 0x1340,
		ExtCwMinShift = 16,
		ExtCcaDlyShift = 8,
		ExtCcaEn = (1 << 7),
		LsigTxopEn = (1 << 6),
		TxopTrunEnMimops = (1 << 4),
		TxopTrunEnTxop = (1 << 3),
		TxopTrunEnRate = (1 << 2),
		TxopTrunEnAc = (1 << 1),
		TxopTrunEnTimeout = (1 << 0),
	TxRtsCfg = 0x1344,
		RtsFbkEn = (1 << 24),
		RtsThresShift = 8,
		RtsRtyLimitShift = 0,
	TxTimeoutCfg = 0x1348,
		TxopTimeoutShift = 16,
		RxAckTimeoutShift = 8,
		MpduLifeTimeShift = 4,
	TxRtyCfg = 0x134c,
		TxAutofbEn = (1 << 30),
		AggRtyModeTimer = (1 << 29),
		NagRtyModeTimer = (1 << 28),
		LongRtyThresShift = 16,
		LongRtyLimitShift = 8,
		ShortRtyLimitShift = 0,
	TxLinkCfg = 0x1350,
		RemoteMfsShift = 24,
		RemoteMfbShift = 16,
		TxCfackEn = (1 << 12),
		TxRdgEn = (1 << 11),
		TxMrqEn = (1 << 10),
		RemoteUmfsEn = (1 << 9),
		TxMfbEn = (1 << 8),
		RemoteMfbLtShift = 0,
	HtFbkCfg0 = 0x1354,
	HtFbkCfg1 = 0x1358,
	LgFbkCfg0 = 0x135c,
	LgFbkCfg1 = 0x1360,
	CckProtCfg = 0x1364,
		/* possible flags for registers *ProtCfg */
		RtsthEn = (1 << 26),
		TxopAllowGf40 = (1 << 25),
		TxopAllowGf20 = (1 << 24),
		TxopAllowMm40 = (1 << 23),
		TxopAllowMm20 = (1 << 22),
		TxopAllowOfdm = (1 << 21),
		TxopAllowCck = (1 << 20),
		TxopAllowAll = (0x3f << 20),
		ProtNavShort = (1 << 18),
		ProtNavLong = (2 << 18),
		ProtCtrlRtsCts = (1 << 16),
		ProtCtrlCts = (2 << 16),
	OfdmProtCfg = 0x1368,
	Mm20ProtCfg = 0x136c,
	Mm40ProtCfg = 0x1370,
	Gf20ProtCfg = 0x1374,
	Gf40ProtCfg = 0x1378,
	ExpCtsTime = 0x137c,
		/* possible flags for registers EXP_{CTS,ACK}_TIME */
		ExpOfdmTimeShift = 16,
		ExpCckTimeShift = 0,
	ExpAckTime = 0x1380,
};

enum {
	/* MAC RX configuration registers */
	RxFiltrCfg = 0x1400,
		DropCtrlRsv = (1 << 16),
		DropBar = (1 << 15),
		DropBa = (1 << 14),
		DropPspoll = (1 << 13),
		DropRts = (1 << 12),
		DropCts = (1 << 11),
		DropAck = (1 << 10),
		DropCfend = (1 << 9),
		DropCfack = (1 << 8),
		DropDupl = (1 << 7),
		DropBc = (1 << 6),
		DropMc = (1 << 5),
		DropVerErr = (1 << 4),
		DropNotMybss = (1 << 3),
		DropUcNome = (1 << 2),
		DropPhyErr = (1 << 1),
		DropCrcErr = (1 << 0),
	AutoRspCfg = 0x1404,
		CtrlPwrBit = (1 << 7),
		BacAckPolicy = (1 << 6),
		CckShortEn = (1 << 4),
		Cts40mRefEn = (1 << 3),
		Cts40mModeEn = (1 << 2),
		BacAckpolicyEn = (1 << 1),
		AutoRspEn = (1 << 0),
	LegacyBasicRate = 0x1408,
	HtBasicRate = 0x140c,
	HtCtrlCfg = 0x1410,
	SifsCostCfg = 0x1414,
		OfdmSifsCostShift = 8,
		CckSifsCostShift = 0,
	RxParserCfg = 0x1418,
};

enum {
	/* MAC Security configuration registers */
	TxSecCnt0 = 0x1500,
	RxSecCnt0 = 0x1504,
	CcmpFcMute = 0x1508,
};

enum {
	/* MAC HCCA/PSMP configuration registers */
	TxopHldrAddr0 = 0x1600,
	TxopHldrAddr1 = 0x1604,
	TxopHldrEt = 0x1608,
		TxopEtm1En = (1 << 25),
		TxopEtm0En = (1 << 24),
		TxopEtmThresShift = 16,
		TxopEtoEn = (1 << 8),
		TxopEtoThresShift = 1,
		PerRxRstEn = (1 << 0),
	QosCfpollRaDw0 = 0x160c,
	QosCfpollA1Dw1 = 0x1610,
	QosCfpollQc = 0x1614,
};

enum {
	/* MAC Statistics Counters */
	RxStaCnt0 = 0x1700,
	RxStaCnt1 = 0x1704,
	RxStaCnt2 = 0x1708,
	TxStaCnt0 = 0x170c,
	TxStaCnt1 = 0x1710,
	TxStaCnt2 = 0x1714,
	TxStatFifo = 0x1718,
		TxqMcsShift = 16,
		TxqWcidShift = 8,
		TxqAckreq = (1 << 7),
		TxqAgg = (1 << 6),
		TxqOk = (1 << 5),
		TxqPidShift = 1,
		TxqVld = (1 << 0),
};

/* RX WCID search table */
#define WcidEntry(wcid) (0x1800 + (wcid) * 8)

enum {
	FwBase = 0x2000,
	Rt2870FwBase = 0x3000,
};

/* Pair-wise key table */
#define Pkey(wcid) (0x4000 + (wcid) * 32)

/* IV/EIV table */
#define Iveiv(wcid) (0x6000 + (wcid) * 8)

/* WCID attribute table */
#define WcidAttr(wcid) (0x6800 + (wcid) * 4)

/* possible flags for register WCID_ATTR */
enum {
	ModeNosec = 0,
	ModeWep40 = 1,
	ModeWep104 = 2,
	ModeTkip = 3,
	ModeAesCcmp = 4,
	ModeCkip40 = 5,
	ModeCkip104 = 6,
	ModeCkip128 = 7,
	RxPkeyEn = (1 << 0),
};

/* Shared Key Table */
#define Skey(vap, kidx) (0x6c00 + (vap) * 128 + (kidx) * 32)

/* Shared Key Mode */
enum {
	SkeyMode07 = 0x7000,
	SkeyMode815 = 0x7004,
	SkeyMode1623 = 0x7008,
	SkeyMode2431 = 0x700c,
};

enum {
	/* Shared Memory between MCU and host */
	H2mMailbox = 0x7010,
		H2mBusy = (1 << 24),
		TokenNoIntr = 0xff,
	H2mMailboxCid = 0x7014,
	H2mMailboxStatus = 0x701c,
	H2mBbpagent = 0x7028,
#define BcnBase(vap) (0x7800 + (vap) * 512)
};

/* 
 *	RT2860 TX descriptor
 *	--------------------
 *	u32int	sdp0 		Segment Data Pointer 0
 *	u16int	sdl1 		Segment Data Length 1
 *	u16int	sdl0		Segment Data Length 0
 *	u32int	sdp1		Segment Data Pointer 1
 *	u8int 	reserved[3]
 *	u8int 	flags
 */

enum {
	/* sdl1 flags */
	TxBurst = (1 << 15),
	TxLs1 = (1 << 14) /* SDP1 is the last segment */,
	/* sdl0 flags */
	TxDdone = (1 << 15),
	TxLs0 = (1 << 14) /* SDP0 is the last segment */,
	/* flags */
	TxQselShift = 1,
	TxQselMgmt = (0 << 1),
	TxQselHcca = (1 << 1),
	TxQselEdca = (2 << 1),
	TxWiv = (1 << 0),
};

/* 
 *	TX Wireless Information
 *	-----------------------
 *	u8int	flags
 *	u8int	txop
 *	u16int	phy
 *	u8int	xflags
 *	u8int	wcid 	Wireless Client ID
 *	u16int	len
 *	u32int	iv
 *	u32int	eiv
 */

enum {
	/* flags */
	TxMpduDsityShift = 5,
	TxAmpdu = (1 << 4),
	TxTs = (1 << 3),
	TxCfack = (1 << 2),
	TxMmps = (1 << 1),
	TxFrag = (1 << 0),
	/* txop */
	TxTxopHt = 0,
	TxTxopPifs = 1,
	TxTxopSifs = 2,
	TxTxopBackoff = 3,
	/* phy */
	PhyMode = 0xc000,
	PhyCck = (0 << 14),
	PhyOfdm = (1 << 14),
	PhyHt = (2 << 14),
	PhyHtGf = (3 << 14),
	PhySgi = (1 << 8),
	PhyBw40 = (1 << 7),
	PhyMcs = 0x7f,
	PhyShpre = (1 << 3),
	/* xflags */
	TxBawinsizeShift = 2,
	TxNseq = (1 << 1),
	TxAck = (1 << 0),
	/* len */
	TxPidShift = 12,
};

/* 
 *	RT2860 RX descriptor
 *	--------------------
 *	u32int	sdp0
 *	u16int	sdl1 	unused
 *	u16int  sdl0
 *	u32int	sdp1	unused
 *	u32int	flags
 */

enum {
	/* sdl flags */
	RxDdone = (1 << 15),
	RxLs0 = (1 << 14),
	/* flags */
	RxDec = (1 << 16),
	RxAmpdu = (1 << 15),
	RxL2pad = (1 << 14),
	RxRssi = (1 << 13),
	RxHtc = (1 << 12),
	RxAmsdu = (1 << 11),
	RxMicerr = (1 << 10),
	RxIcverr = (1 << 9),
	RxCrcerr = (1 << 8),
	RxMybss = (1 << 7),
	RxBc = (1 << 6),
	RxMc = (1 << 5),
	RxUc2me = (1 << 4),
	RxFrag = (1 << 3),
	RxNull = (1 << 2),
	RxData = (1 << 1),
	RxBa = (1 << 0),
};

/* 
 *	RX Wireless Information
 *	-----------------------
 *	u8int	wcid
 *	u8int	keyidx
 *	u16int	len
 *	u16int	seq
 *	u16int	phy
 *	u8int	rssi[3]
 *	u8int	reserved1
 *	u8int	snr[2]
 *	u16int	reserved2
 */

enum {
	/* keyidx flags */
	RxUdfShift = 5,
	RxBssIdxShift = 2,
	/* len flags */
	RxTidShift = 12,
};

enum {
	WIFIHDRSIZE = 2+2+3*6+2,
	Rdscsize = 16,
	Tdscsize = 16,
	Rbufsize = 4096,
	Tbufsize = 4096,
	Rxwisize = 16,
	Txwisize = 16,
	/* first DMA segment contains TXWI + 802.11 header + 32-bit padding */
	TxwiDmaSz = Txwisize + WIFIHDRSIZE + 2
};

/* RF registers */
enum {
	Rf1 = 0,
	Rf2 = 2,
	Rf3 = 1,
	Rf4 = 3,
};

enum {
	Rf2820 = 1 /* 2T3R */,
	Rf2850 = 2 /* dual-band 2T3R */,
	Rf2720 = 3 /* 1T2R */,
	Rf2750 = 4 /* dual-band 1T2R */,
	Rf3020 = 5 /* 1T1R */,
	Rf2020 = 6 /* b/g */,
	Rf3021 = 7 /* 1T2R */,
	Rf3022 = 8 /* 2T2R */,
 	Rf3052 = 9 /* dual-band 2T2R */,
	Rf3320 = 11 /* 1T1R */,
	Rf3053 = 13 /* dual-band 3T3R */,
};

enum {	
	Rt3070RfBlock = (1 << 0),
	Rt3070Rx0Pd = (1 << 2),
	Rt3070Tx0Pd = (1 << 3),
	Rt3070Rx1Pd = (1 << 4),
	Rt3070Tx1Pd = (1 << 5),
	Rt3070Rx2Pd = (1 << 6),
	Rt3070Tx2Pd = (1 << 7),
	Rt3070Tune = (1 << 0),
	Rt3070TxLo2 = (1 << 3),
	Rt3070TxLo1 = (1 << 3),
	Rt3070RxLo1 = (1 << 3),
	Rt3070RxLo2 = (1 << 3),
	Rt3070RxCtb = (1 << 7),
	Rt3070BbLoopback = (1 << 0),

	Rt3593Vco = (1 << 0),
	Rt3593Rescal = (1 << 7),
	Rt3593Vcocal = (1 << 7),
	Rt3593VcoIc = (1 << 6),
	Rt3593LdoPllVcMask = 0x0e,
	Rt3593LdoRfVcMask = 0xe0,
	Rt3593CpIcMask = 0xe0,
	Rt3593CpIcShift = 5,
	Rt3593RxCtb = (1 << 5)
};

static const char* rfnames[] = {
	[Rf2820] "RT2820",
	[Rf2850] "RT2850",
	[Rf2720] "RT2720",
	[Rf2750] "RT2750",
	[Rf3020] "RT3020",
	[Rf2020] "RT2020",
	[Rf3021] "RT3021",
	[Rf3022] "RT3022",
	[Rf3052] "RT3052",
	[Rf3320] "RT3320",
	[Rf3053] "RT3053",
};

enum {
	/* USB commands, RT2870 only */
	Rt2870Reset = 1,
	Rt2870Write2 = 2,
	Rt2870WriteRegion1 = 6,
	Rt2870ReadRegion1 = 7,
	Rt2870EepromRead = 9,
};

enum {
	EepromDelay = 1 /* minimum hold time (microsecond) */,

	EepromVersion = 0x01,
	EepromMac01 = 0x02,
	EepromMac23 = 0x03,
	EepromMac45 = 0x04,
	EepromPciePslevel = 0x11,
	EepromRev = 0x12,
	EepromAntenna = 0x1a,
	EepromConfig = 0x1b,
	EepromCountry = 0x1c,
	EepromFreqLeds = 0x1d,
	EepromLed1 = 0x1e,
	EepromLed2 = 0x1f,
	EepromLed3 = 0x20,
	EepromLna = 0x22,
	EepromRssi12ghz = 0x23,
	EepromRssi22ghz = 0x24,
	EepromRssi15ghz = 0x25,
	EepromRssi25ghz = 0x26,
	EepromDeltapwr = 0x28,
	EepromPwr2ghzBase1 = 0x29,
	EepromPwr2ghzBase2 = 0x30,
	EepromTssi12ghz = 0x37,
	EepromTssi22ghz = 0x38,
	EepromTssi32ghz = 0x39,
	EepromTssi42ghz = 0x3a,
	EepromTssi52ghz = 0x3b,
	EepromPwr5ghzBase1 = 0x3c,
	EepromPwr5ghzBase2 = 0x53,
	EepromTssi15ghz = 0x6a,
	EepromTssi25ghz = 0x6b,
	EepromTssi35ghz = 0x6c,
	EepromTssi45ghz = 0x6d,
	EepromTssi55ghz = 0x6e,
	EepromRpwr = 0x6f,
	EepromBbpBase = 0x78,
	Rt3071EepromRfBase = 0x82,
};

enum {
	RidxCck1 = 0,
	RidxCck11 = 3,
	RidxOfdm6 = 4,
	RidxMax = 11,
};

/* ring and pool count */
enum {
	Nrx = 128,
	Ntx = 64,
	Ntxpool = Ntx * 2
};

typedef struct FWImage FWImage;
typedef struct TXQ TXQ;
typedef struct RXQ RXQ;
typedef struct Pool Pool;

typedef struct Ctlr Ctlr;

struct FWImage {
	uint  size;
	uchar *data;
};

struct TXQ
{
	uint	n; /* next */
	uint	i; /* current */
	Block	**b;
	u32int	*d; /* descriptors */

	Rendez;
	QLock;
};

struct RXQ
{
	uint	i;
	Block	**b;
	u32int	*p;
};

struct Pool
{
	uint i; /* current */
	uchar *p; /* txwi */
};

struct Ctlr {
	Lock;
	QLock;

	Ctlr *link;
	uvlong port;
	Pcidev *pdev;
	Wifi *wifi;

	u16int mac_ver;
	u16int mac_rev;
	u8int rf_rev;
	u8int freq;
	u8int ntxchains;
	u8int nrxchains;
	u8int pslevel;
	s8int txpow1[54];
	s8int txpow2[54];
	s8int rssi_2ghz[3];
	s8int rssi_5ghz[3];
	u8int lna[4];
	u8int rf24_20mhz;
	u8int rf24_40mhz;
	u8int patch_dac;
	u8int rfswitch;
	u8int ext_2ghz_lna;
	u8int ext_5ghz_lna;
	u8int calib_2ghz;
	u8int calib_5ghz;
	u8int txmixgain_2ghz;
	u8int txmixgain_5ghz;
	u8int tssi_2ghz[9];
	u8int tssi_5ghz[9];
	u8int step_2ghz;
	u8int step_5ghz;
	uint mgtqid;

	struct {
		u8int	reg;
		u8int	val;
	} bbp[8], rf[10];
	u8int leds;
	u16int led[3];
	u32int txpow20mhz[5];
	u32int txpow40mhz_2ghz[5];
	u32int txpow40mhz_5ghz[5];

	int flags;
	int power;
	int active;
	int broken;
	int attached;

	u32int *nic;

	/* assigned node ids in hardware node table or -1 if unassigned */
	int bcastnodeid;
	int bssnodeid;
	u8int wcid;
	/* current receiver settings */
	uchar bssid[Eaddrlen];
	int channel;
	int prom;
	int aid;

	RXQ rx;
	TXQ tx[6];
	Pool pool;

	FWImage *fw;
};

/* controller flags */
enum {
	AdvancedPs = 1 << 0,
	ConnPciE   = 1 << 1,
};

static const struct rt2860_rate {
	u8int		rate;
	u8int		mcs;
	/*enum		ieee80211_phytype phy;*/
	u8int		ctl_ridx;
	u16int		sp_ack_dur;
	u16int		lp_ack_dur;
} rt2860_rates[] = {
	{   2, 0,/* IEEE80211_T_DS,*/   0, 314, 314 },
	{   4, 1,/* IEEE80211_T_DS,*/   1, 258, 162 },
	{  11, 2,/* IEEE80211_T_DS,*/   2, 223, 127 },
	{  22, 3,/* IEEE80211_T_DS,*/   3, 213, 117 },
	{  12, 0,/* IEEE80211_T_OFDM,*/ 4,  60,  60 },
	{  18, 1,/* IEEE80211_T_OFDM,*/ 4,  52,  52 },
	{  24, 2,/* IEEE80211_T_OFDM,*/ 6,  48,  48 },
	{  36, 3,/* IEEE80211_T_OFDM,*/ 6,  44,  44 },
	{  48, 4,/* IEEE80211_T_OFDM,*/ 8,  44,  44 },
	{  72, 5,/* IEEE80211_T_OFDM,*/ 8,  40,  40 },
	{  96, 6,/* IEEE80211_T_OFDM,*/ 8,  40,  40 },
	{ 108, 7,/* IEEE80211_T_OFDM,*/ 8,  40,  40 }
};

/*
 * Default values for MAC registers; values taken from the reference driver.
 */
static const struct {
	u32int	reg;
	u32int	val;
} rt2860_def_mac[] = {
	{ BcnOffset0,	 	 0xf8f0e8e0 }, 
	{ LegacyBasicRate,	 0x0000013f }, 
	{ HtBasicRate,		 0x00008003 }, 
	{ MacSysCtrl,	 	 0x00000000 }, 
	{ BkoffSlotCfg,		 0x00000209 }, 
	{ TxSwCfg0,			 0x00000000 }, 
	{ TxSwCfg1,			 0x00080606 }, 
	{ TxLinkCfg,		 0x00001020 }, 
	{ TxTimeoutCfg,		 0x000a2090 }, 
	{ LedCfg,			 0x7f031e46 }, 
	{ WmmAifsnCfg,		 0x00002273 }, 
	{ WmmCwminCfg,		 0x00002344 }, 
	{ WmmCwmaxCfg,		 0x000034aa }, 
	{ MaxPcnt,			 0x1f3fbf9f }, 
	{ TxRtyCfg,			 0x47d01f0f }, 
	{ AutoRspCfg,		 0x00000013 }, 
	{ CckProtCfg,		 0x05740003 }, 
	{ OfdmProtCfg,		 0x05740003 }, 
	{ Gf20ProtCfg,		 0x01744004 }, 
	{ Gf40ProtCfg,		 0x03f44084 }, 
	{ Mm20ProtCfg,		 0x01744004 }, 
	{ Mm40ProtCfg,		 0x03f54084 }, 
	{ TxopCtrlCfg,		 0x0000583f }, 
	{ TxopHldrEt,		 0x00000002 }, 
	{ TxRtsCfg,			 0x00092b20 }, 
	{ ExpAckTime,		 0x002400ca }, 
	{ XifsTimeCfg,		 0x33a41010 }, 
	{ PwrPinCfg,		 0x00000003 },
};

/*
 * Default values for BBP registers; values taken from the reference driver.
 */
static const struct {
	u8int	reg;
	u8int	val;
} rt2860_def_bbp[] = {
	{  65, 0x2c },	
	{  66, 0x38 },	
	{  69, 0x12 },	
	{  70, 0x0a },	
	{  73, 0x10 },	
	{  81, 0x37 },	
	{  82, 0x62 },	
	{  83, 0x6a },	
	{  84, 0x99 },	
	{  86, 0x00 },	
	{  91, 0x04 },	
	{  92, 0x00 },	
	{ 103, 0x00 },	
	{ 105, 0x05 },	
	{ 106, 0x35 },
};

/*
 * Default settings for RF registers; values derived from the reference driver.
 */
static const struct rfprog {
	u8int		chan;
	u32int		r1, r2, r3, r4;
} rt2860_rf2850[] = {
	{   1, 0x100bb3, 0x1301e1, 0x05a014, 0x001402 },	
	{   2, 0x100bb3, 0x1301e1, 0x05a014, 0x001407 },	
	{   3, 0x100bb3, 0x1301e2, 0x05a014, 0x001402 },	
	{   4, 0x100bb3, 0x1301e2, 0x05a014, 0x001407 },	
	{   5, 0x100bb3, 0x1301e3, 0x05a014, 0x001402 },	
	{   6, 0x100bb3, 0x1301e3, 0x05a014, 0x001407 },	
	{   7, 0x100bb3, 0x1301e4, 0x05a014, 0x001402 },	
	{   8, 0x100bb3, 0x1301e4, 0x05a014, 0x001407 },	
	{   9, 0x100bb3, 0x1301e5, 0x05a014, 0x001402 },	
	{  10, 0x100bb3, 0x1301e5, 0x05a014, 0x001407 },	
	{  11, 0x100bb3, 0x1301e6, 0x05a014, 0x001402 },	
	{  12, 0x100bb3, 0x1301e6, 0x05a014, 0x001407 },	
	{  13, 0x100bb3, 0x1301e7, 0x05a014, 0x001402 },	
	{  14, 0x100bb3, 0x1301e8, 0x05a014, 0x001404 },	
	{  36, 0x100bb3, 0x130266, 0x056014, 0x001408 },	
	{  38, 0x100bb3, 0x130267, 0x056014, 0x001404 },	
	{  40, 0x100bb2, 0x1301a0, 0x056014, 0x001400 },	
	{  44, 0x100bb2, 0x1301a0, 0x056014, 0x001408 },	
	{  46, 0x100bb2, 0x1301a1, 0x056014, 0x001402 },	
	{  48, 0x100bb2, 0x1301a1, 0x056014, 0x001406 },	
	{  52, 0x100bb2, 0x1301a2, 0x056014, 0x001404 },	
	{  54, 0x100bb2, 0x1301a2, 0x056014, 0x001408 },	
	{  56, 0x100bb2, 0x1301a3, 0x056014, 0x001402 },	
	{  60, 0x100bb2, 0x1301a4, 0x056014, 0x001400 },	
	{  62, 0x100bb2, 0x1301a4, 0x056014, 0x001404 },	
	{  64, 0x100bb2, 0x1301a4, 0x056014, 0x001408 },	
	{ 100, 0x100bb2, 0x1301ac, 0x05e014, 0x001400 },	
	{ 102, 0x100bb2, 0x1701ac, 0x15e014, 0x001404 },	
	{ 104, 0x100bb2, 0x1701ac, 0x15e014, 0x001408 },	
	{ 108, 0x100bb3, 0x17028c, 0x15e014, 0x001404 },	
	{ 110, 0x100bb3, 0x13028d, 0x05e014, 0x001400 },	
	{ 112, 0x100bb3, 0x13028d, 0x05e014, 0x001406 },	
	{ 116, 0x100bb3, 0x13028e, 0x05e014, 0x001408 },	
	{ 118, 0x100bb3, 0x13028f, 0x05e014, 0x001404 },	
	{ 120, 0x100bb1, 0x1300e0, 0x05e014, 0x001400 },	
	{ 124, 0x100bb1, 0x1300e0, 0x05e014, 0x001404 },	
	{ 126, 0x100bb1, 0x1300e0, 0x05e014, 0x001406 },	
	{ 128, 0x100bb1, 0x1300e0, 0x05e014, 0x001408 },	
	{ 132, 0x100bb1, 0x1300e1, 0x05e014, 0x001402 },	
	{ 134, 0x100bb1, 0x1300e1, 0x05e014, 0x001404 },	
	{ 136, 0x100bb1, 0x1300e1, 0x05e014, 0x001406 },	
	{ 140, 0x100bb1, 0x1300e2, 0x05e014, 0x001400 },	
	{ 149, 0x100bb1, 0x1300e2, 0x05e014, 0x001409 },	
	{ 151, 0x100bb1, 0x1300e3, 0x05e014, 0x001401 },	
	{ 153, 0x100bb1, 0x1300e3, 0x05e014, 0x001403 },	
	{ 157, 0x100bb1, 0x1300e3, 0x05e014, 0x001407 },	
	{ 159, 0x100bb1, 0x1300e3, 0x05e014, 0x001409 },	
	{ 161, 0x100bb1, 0x1300e4, 0x05e014, 0x001401 },	
	{ 165, 0x100bb1, 0x1300e4, 0x05e014, 0x001405 },	
	{ 167, 0x100bb1, 0x1300f4, 0x05e014, 0x001407 },	
	{ 169, 0x100bb1, 0x1300f4, 0x05e014, 0x001409 },	
	{ 171, 0x100bb1, 0x1300f5, 0x05e014, 0x001401 },	
	{ 173, 0x100bb1, 0x1300f5, 0x05e014, 0x001403 },
};

struct {
	u8int n;
	u8int r;
	u8int k;
} rt3090_freqs[] = {
	{ 0xf1, 2,  2 },
	{ 0xf1, 2,  7 },
	{ 0xf2, 2,  2 },
	{ 0xf2, 2,  7 },
	{ 0xf3, 2,  2 },
	{ 0xf3, 2,  7 },
	{ 0xf4, 2,  2 },
	{ 0xf4, 2,  7 },
	{ 0xf5, 2,  2 },
	{ 0xf5, 2,  7 },
	{ 0xf6, 2,  2 },
	{ 0xf6, 2,  7 },
	{ 0xf7, 2,  2 },
	{ 0xf8, 2,  4 },
	{ 0x56, 0,  4 },
	{ 0x56, 0,  6 },
	{ 0x56, 0,  8 },
	{ 0x57, 0,  0 },
	{ 0x57, 0,  2 },
	{ 0x57, 0,  4 },
	{ 0x57, 0,  8 },
	{ 0x57, 0, 10 },
	{ 0x58, 0,  0 },
	{ 0x58, 0,  4 },
	{ 0x58, 0,  6 },
	{ 0x58, 0,  8 },
	{ 0x5b, 0,  8 },
	{ 0x5b, 0, 10 },
	{ 0x5c, 0,  0 },
	{ 0x5c, 0,  4 },
	{ 0x5c, 0,  6 },
	{ 0x5c, 0,  8 },
	{ 0x5d, 0,  0 },
	{ 0x5d, 0,  2 },
	{ 0x5d, 0,  4 },
	{ 0x5d, 0,  8 },
	{ 0x5d, 0, 10 },
	{ 0x5e, 0,  0 },
	{ 0x5e, 0,  4 },
	{ 0x5e, 0,  6 },
	{ 0x5e, 0,  8 },
	{ 0x5f, 0,  0 },
	{ 0x5f, 0,  9 },
	{ 0x5f, 0, 11 },
	{ 0x60, 0,  1 },
	{ 0x60, 0,  5 },
	{ 0x60, 0,  7 },
	{ 0x60, 0,  9 },
	{ 0x61, 0,  1 },
	{ 0x61, 0,  3 },
	{ 0x61, 0,  5 },
	{ 0x61, 0,  7 },
	{ 0x61, 0,  9 }
};

static const struct {
	u8int reg;
	u8int val;
} rt3090_def_rf[] = {
	{  4, 0x40 },
	{  5, 0x03 },
	{  6, 0x02 },
	{  7, 0x70 },
	{  9, 0x0f },
	{ 10, 0x41 },
	{ 11, 0x21 },
	{ 12, 0x7b },
	{ 14, 0x90 },
	{ 15, 0x58 },
	{ 16, 0xb3 },
	{ 17, 0x92 },
	{ 18, 0x2c },
	{ 19, 0x02 },
	{ 20, 0xba },
	{ 21, 0xdb },
	{ 24, 0x16 },
	{ 25, 0x01 },
	{ 29, 0x1f }
};

/* vendors */
enum {
	Ralink = 0x1814,
	Awt = 0x1a3b,
};
/* products */
enum {
	RalinkRT2890 = 0x0681,
	RalinkRT2790 = 0x0781,
	RalinkRT3090 = 0x3090,
	AwtRT2890 = 0x1059,
};

#define csr32r(c, r)	(*((c)->nic+((r)/4)))
#define csr32w(c, r, v)	(*((c)->nic+((r)/4)) = (v))

static int rbplant(Ctlr*, int);
static void setchan(Ctlr*, uint);
static void rt3090setchan(Ctlr*, uint);
static void selchangroup(Ctlr*, int);
static void setleds(Ctlr*, u16int);

static uint
get16(uchar *p){
	return *((u16int*)p);
}
static uint
get32(uchar *p){
	return *((u32int*)p);
}
static void
put32(uchar *p, uint v){
	*((u32int*)p) = v;
}
static void
put16(uchar *p, uint v){
	*((u16int*)p) = v;
};
static void
memwrite(Ctlr *ctlr, u32int off, uchar *data, uint size){
	memmove((uchar*)ctlr->nic + off, data, size);
}
static void
setregion(Ctlr *ctlr, u32int off, uint val, uint size){
	memset((uchar*)ctlr->nic + off, val, size);
}

static long
rt2860ctl(Ether *edev, void *buf, long n)
{
	Ctlr *ctlr;

	ctlr = edev->ctlr;
	if(ctlr->wifi)
		return wifictl(ctlr->wifi, buf, n);
	return 0;
}

static long
rt2860ifstat(Ether *edev, void *buf, long n, ulong off)
{
	Ctlr *ctlr;

	ctlr = edev->ctlr;
	if(ctlr->wifi)
		return wifistat(ctlr->wifi, buf, n, off);
	return 0;
}

static void
setoptions(Ether *edev)
{
	Ctlr *ctlr;
	int i;

	ctlr = edev->ctlr;
	for(i = 0; i < edev->nopt; i++)
		wificfg(ctlr->wifi, edev->opt[i]);
}

static void
rxon(Ether *edev, Wnode *bss)
{
	u32int tmp;
	Ctlr *ctlr;
	int cap;

	ctlr = edev->ctlr;

	if(bss != nil){
		cap = bss->cap;
		ctlr->channel = bss->channel;
		memmove(ctlr->bssid, bss->bssid, Eaddrlen);
		ctlr->aid = bss->aid;
		if(ctlr->aid != 0){
			if(ctlr->wifi->debug)
				print("new assoc!");
			ctlr->bssnodeid = -1;
		}else
			ctlr->bcastnodeid = -1;
	}else{
		cap = 0;
		memmove(ctlr->bssid, edev->bcast, Eaddrlen);
		ctlr->aid = 0;
		ctlr->bcastnodeid = -1;
		ctlr->bssnodeid = -1;
	}
	if(ctlr->aid != 0)
		setleds(ctlr, LedRadio | LedLink2ghz);
	else
		setleds(ctlr, LedRadio);

	if(ctlr->wifi->debug)
		print("#l%d: rxon: bssid %E, aid %x, channel %d wcid %d\n",
			edev->ctlrno, ctlr->bssid, ctlr->aid, ctlr->channel, ctlr->wcid);

	/* Set channel */
	if(ctlr->mac_ver >= 0x3071)
		rt3090setchan(ctlr, ctlr->channel);
	else
		setchan(ctlr, ctlr->channel);
	selchangroup(ctlr, 0);
	microdelay(1000);

	/* enable mrr(?) */
#define CCK(mcs)	(mcs)
#define OFDM(mcs)	(1 << 3 | (mcs))
	csr32w(ctlr, LgFbkCfg0,
	    OFDM(6) << 28 |	/* 54->48 */
	    OFDM(5) << 24 |	/* 48->36 */
	    OFDM(4) << 20 |	/* 36->24 */
	    OFDM(3) << 16 |	/* 24->18 */
	    OFDM(2) << 12 |	/* 18->12 */
	    OFDM(1) <<  8 |	/* 12-> 9 */
	    OFDM(0) <<  4 |	/*  9-> 6 */
	    OFDM(0));		/*  6-> 6 */

	csr32w(ctlr, LgFbkCfg1,
	    CCK(2) << 12 |	/* 11->5.5 */
	    CCK(1) <<  8 |	/* 5.5-> 2 */
	    CCK(0) <<  4 |	/*   2-> 1 */
	    CCK(0));		/*   1-> 1 */
#undef OFDM
#undef CCK
	/* update slot */
	tmp = csr32r(ctlr, BkoffSlotCfg);
	tmp &= ~0xff;
	tmp |= (cap & (1<<10)) ? 9 : 20;
	csr32w(ctlr, BkoffSlotCfg, tmp);
	
	/* set TX preamble */
	tmp = csr32r(ctlr, AutoRspCfg);
	tmp &= ~CckShortEn;
	if(cap & (1<<5)) tmp |= CckShortEn;	
	csr32w(ctlr, AutoRspCfg, tmp);

	/* set basic rates */
	csr32w(ctlr, LegacyBasicRate, 0x003); /* 11B */

	/* Set BSSID */
	csr32w(ctlr, MacBssidDw0,
	    ctlr->bssid[0] | ctlr->bssid[1] << 8 | ctlr->bssid[2] << 16 | ctlr->bssid[3] << 24);
	csr32w(ctlr, MacBssidDw1,
	    ctlr->bssid[4] | ctlr->bssid[5] << 8);

	if(ctlr->bcastnodeid == -1){
		ctlr->bcastnodeid = 0xff;
		memwrite(ctlr, WcidEntry(ctlr->bcastnodeid), edev->bcast, Eaddrlen);
	}
	if(ctlr->bssnodeid == -1 && bss != nil && ctlr->aid != 0){
		ctlr->bssnodeid = 0;
		memwrite(ctlr, WcidEntry(ctlr->bssnodeid), ctlr->bssid, Eaddrlen);
	}
}

static void
rt2860promiscuous(void *arg, int on)
{
	Ether *edev;
	Ctlr *ctlr;

	edev = arg;
	ctlr = edev->ctlr;
	if(ctlr->attached == 0)
		return;
	qlock(ctlr);
	ctlr->prom = on;
	rxon(edev, ctlr->wifi->bss);
	qunlock(ctlr);
}

static void
rt2860multicast(void *, uchar*, int)
{
}

static FWImage*
readfirmware(void){
	static char name[] = "ral-rt2860";
	uchar dirbuf[sizeof(Dir)+100], *data;
	char buf[128];
	FWImage *fw;
	int n, r;
	Chan *c;
	Dir d;

	if(!iseve())
		error(Eperm);
	if(!waserror()){
		snprint(buf, sizeof buf, "/boot/%s", name);
		c = namec(buf, Aopen, OREAD, 0);
		poperror();
	}else{
		snprint(buf, sizeof buf, "/lib/firmware/%s", name);
		c = namec(buf, Aopen, OREAD, 0);
	}
	if(waserror()){
		cclose(c);
		nexterror();
	}
	n = devtab[c->type]->stat(c, dirbuf, sizeof dirbuf);
	if(n <= 0)
		error("can't stat firmware");
	convM2D(dirbuf, n, &d, nil);
	fw = malloc(sizeof(*fw));
	fw->size = d.length;
	data = fw->data = smalloc(d.length);
	if(waserror()){
		free(fw);
		nexterror();
	}
	r = 0;
	while(r < d.length){
		n = devtab[c->type]->read(c, data+r, d.length-r, (vlong)r);
		if(n <= 0)
			break;
		r += n;
	}
	poperror();
	poperror();
	cclose(c);
	return fw;
}

static char*
boot(Ctlr *ctlr)
{
	int ntries;

	/* set "host program ram write selection" bit */
	csr32w(ctlr, SysCtrl, HstPmSel);
	/* write microcode image */
	memwrite(ctlr, FwBase, ctlr->fw->data, ctlr->fw->size);
	/* kick microcontroller unit */
	csr32w(ctlr, SysCtrl, 0);
	coherence();
	csr32w(ctlr, SysCtrl, McuReset);

	csr32w(ctlr, H2mBbpagent, 0);
	csr32w(ctlr, H2mMailbox, 0);

	/* wait until microcontroller is ready */
	coherence();
	for(ntries = 0; ntries < 1000; ntries++){
		if(csr32r(ctlr, SysCtrl) & McuReady)
			break;
		microdelay(1000);
	}
	if(ntries == 1000)
		return "timeout waiting for MCU to initialize";
	return 0;
}

/*
 * Send a command to the 8051 microcontroller unit.
 */
static int
mcucmd(Ctlr *ctlr, u8int cmd, u16int arg, int wait)
{
	int slot, ntries;
	u32int tmp;
	u8int cid;

	SET(slot);
	for(ntries = 0; ntries < 100; ntries++){
		if(!(csr32r(ctlr, H2mMailbox) & H2mBusy))
			break;
		microdelay(2);
	}
	if(ntries == 100)
		return -1;

	cid = wait ? cmd : TokenNoIntr;
	csr32w(ctlr, H2mMailbox, H2mBusy | cid << 16 | arg);
	coherence();
	csr32w(ctlr, HostCmd, cmd);

	if(!wait)
		return 0;
	/* wait for the command to complete */
	for(ntries = 0; ntries < 200; ntries++){
		tmp = csr32r(ctlr, H2mMailboxCid);
		/* find the command slot */
		for(slot = 0; slot < 4; slot++, tmp >>= 8)
			if((tmp & 0xff) == cid)
				break;
		if(slot < 4)
			break;
		microdelay(100);
	}
	if(ntries == 200){
		/* clear command and status */
		csr32w(ctlr, H2mMailboxStatus, 0xffffffff);
		csr32w(ctlr, H2mMailboxCid, 0xffffffff);
		return -1;
	}
	/* get command status (1 means success) */
	tmp = csr32r(ctlr, H2mMailboxStatus);
	tmp = (tmp >> (slot * 8)) & 0xff;
	/* clear command and status */
	csr32w(ctlr, H2mMailboxStatus, 0xffffffff);
	csr32w(ctlr, H2mMailboxCid, 0xffffffff);
	return (tmp == 1) ? 0 : -1;
}


/*
 * Reading and writing from/to the BBP is different from RT2560 and RT2661.
 * We access the BBP through the 8051 microcontroller unit which means that
 * the microcode must be loaded first.
 */
static void
bbpwrite(Ctlr *ctlr, u8int reg, u8int val)
{
	int ntries;

	for(ntries = 0; ntries < 100; ntries++){
		if(!(csr32r(ctlr, H2mBbpagent) & BbpCsrKick))
			break;
		microdelay(1);
	}
	if(ntries == 100){
		print("could not write to BBP through MCU\n");
		return;
	}

	csr32w(ctlr, H2mBbpagent, BbpRwParallel |
	    BbpCsrKick | reg << 8 | val);
	coherence();

	mcucmd(ctlr, McuCmdBbp, 0, 0);
	microdelay(1000);
}

static u8int
bbpread(Ctlr *ctlr, u8int reg)
{
	u32int val;
	int ntries;

	for(ntries = 0; ntries < 100; ntries++){
		if(!(csr32r(ctlr, H2mBbpagent) & BbpCsrKick))
			break;
		microdelay(1);
	}
	if(ntries == 100){
		print("could not read from BBP through MCU");
		return 0;
	}

	csr32w(ctlr, H2mBbpagent, BbpRwParallel |
	    BbpCsrKick | BbpCsrRead | reg << 8);
	coherence();

	mcucmd(ctlr, McuCmdBbp, 0, 0);
	microdelay(1000);

	for(ntries = 0; ntries < 100; ntries++){
		val = csr32r(ctlr, H2mBbpagent);
		if(!(val & BbpCsrKick))
			return val & 0xff;
		microdelay(1);
	}
	print("could not read from BBP through MCU\n");

	return 0;
}

static char*
bbpinit(Ctlr *ctlr)
{
	int i, ntries;
	char *err;

	/* wait for BBP to wake up */
	for(ntries = 0; ntries < 20; ntries++){
		u8int bbp0 = bbpread(ctlr, 0);
		if(bbp0 != 0 && bbp0 != 0xff)
			break;
	}
	if(ntries == 20){
		err = "timeout waiting for BBP to wake up";
		return err;
	}

	/* initialize BBP registers to default values */
	for(i = 0; i < nelem(rt2860_def_bbp); i++){
		bbpwrite(ctlr, rt2860_def_bbp[i].reg,
		    rt2860_def_bbp[i].val);
	}

	/* fix BBP84 for RT2860E */
	if(ctlr->mac_ver == 0x2860 && ctlr->mac_rev != 0x0101)
		bbpwrite(ctlr, 84, 0x19);

	if(ctlr->mac_ver >= 0x3071){
		bbpwrite(ctlr, 79, 0x13);
		bbpwrite(ctlr, 80, 0x05);
		bbpwrite(ctlr, 81, 0x33);
	}else if(ctlr->mac_ver == 0x2860 && ctlr->mac_rev == 0x0100){
		bbpwrite(ctlr, 69, 0x16);
		bbpwrite(ctlr, 73, 0x12);
	}

	return nil;

}

static void
setleds(Ctlr *ctlr, u16int which)
{
	mcucmd(ctlr, McuCmdLeds,
	    which | (ctlr->leds & 0x7f), 0);
}

static char*
txrxon(Ctlr *ctlr)
{
	u32int tmp;
	int ntries;
	char *err;

	SET(tmp);
	/* enable Tx/Rx DMA engine */
	csr32w(ctlr, MacSysCtrl, MacTxEn);
	coherence();
	for(ntries = 0; ntries < 200; ntries++){
		tmp = csr32r(ctlr, WpdmaGloCfg);
		if((tmp & (TxDmaBusy | RxDmaBusy)) == 0)
			break;
		microdelay(1000);
	}
	if(ntries == 200){
		err = "timeout waiting for DMA engine";
		return err;
	}

	microdelay(50);

	tmp |= RxDmaEn | TxDmaEn |
	    WpdmaBtSize64 << WpdmaBtSizeShift;
	csr32w(ctlr, WpdmaGloCfg, tmp);

	/* set Rx filter */
	tmp = DropCrcErr | DropPhyErr;
	if(!ctlr->prom){
		tmp |= DropUcNome | DropDupl |
		    DropCts | DropBa | DropAck |
		    DropVerErr | DropCtrlRsv |
		    DropCfack | DropCfend;
		tmp |= DropRts | DropPspoll;
	} 
	csr32w(ctlr, RxFiltrCfg, tmp);

	csr32w(ctlr, MacSysCtrl, MacRxEn | MacTxEn);

	return 0;
}

/*
 * Write to one of the 4 programmable 24-bit RF registers.
 */
static void
rfwrite(Ctlr *ctlr, u8int reg, u32int val)
{
	u32int tmp;
	int ntries;

	for(ntries = 0; ntries < 100; ntries++){
		if(!(csr32r(ctlr, RfCsrCfg0) & RfRegCtrl))
			break;
		microdelay(1);
	}
	if(ntries == 100){
		print("could not write to RF\n");
		return;
	}

	/* RF registers are 24-bit on the RT2860 */
	tmp = RfRegCtrl | 24 << RfRegWidthShift |
	    (val & 0x3fffff) << 2 | (reg & 3);
	csr32w(ctlr, RfCsrCfg0, tmp);
}

u8int
rt3090rfread(Ctlr *ctlr, u8int reg)
{
	u32int tmp;
	int ntries;

	for(ntries = 0; ntries < 100; ntries++){
		if(!(csr32r(ctlr, Rt3070RfCsrCfg) & Rt3070RfKick))
			break;
		microdelay(1);
	}
	if(ntries == 100){
		print("could not read RF register\n");
		return 0xff;
	}
	tmp = Rt3070RfKick | reg << 8;
	csr32w(ctlr, Rt3070RfCsrCfg, tmp);

	for(ntries = 0; ntries < 100; ntries++){
		tmp = csr32r(ctlr, Rt3070RfCsrCfg);
		if(!(tmp & Rt3070RfKick))
			break;
		microdelay(1);
	}
	if(ntries == 100){
		print("could not read RF register\n");
		return 0xff;
	}
	return tmp & 0xff;
}

static void
rt3090rfwrite(Ctlr *ctlr, u8int reg, u8int val)
{
	u32int tmp;
	int ntries;

	for(ntries = 0; ntries < 10; ntries++){
		if(!(csr32r(ctlr, Rt3070RfCsrCfg) & Rt3070RfKick))
			break;
		microdelay(10);
	}
	if(ntries == 10){
		print("could not write to RF\n");
		return;
	}

	tmp = Rt3070RfWrite | Rt3070RfKick | reg << 8 | val;
	csr32w(ctlr, Rt3070RfCsrCfg, tmp);
}

static void
selchangroup(Ctlr *ctlr, int group)
{
	u32int tmp;
	u8int agc;

	bbpwrite(ctlr, 62, 0x37 - ctlr->lna[group]);
	bbpwrite(ctlr, 63, 0x37 - ctlr->lna[group]);
	bbpwrite(ctlr, 64, 0x37 - ctlr->lna[group]);
	bbpwrite(ctlr, 86, 0x00);

	if(group == 0){
		if(ctlr->ext_2ghz_lna){
			bbpwrite(ctlr, 82, 0x62);
			bbpwrite(ctlr, 75, 0x46);
		}else{
			bbpwrite(ctlr, 82, 0x84);
			bbpwrite(ctlr, 75, 0x50);
		}
	}else{
		if(ctlr->ext_5ghz_lna){
			bbpwrite(ctlr, 82, 0xf2);
			bbpwrite(ctlr, 75, 0x46);
		}else{
			bbpwrite(ctlr, 82, 0xf2);
			bbpwrite(ctlr, 75, 0x50);
		}
	}

	tmp = csr32r(ctlr, TxBandCfg);
	tmp &= ~(Tx5gBandSelN | Tx5gBandSelP);
	tmp |= (group == 0) ? Tx5gBandSelN : Tx5gBandSelP;
	csr32w(ctlr, TxBandCfg, tmp);

	/* enable appropriate Power Amplifiers and Low Noise Amplifiers */
	tmp = RftrEn | TrswEn | LnaPe0En;
	if(ctlr->nrxchains > 1)
		tmp |= LnaPe1En;
	if(ctlr->mac_ver == 0x3593 && ctlr->nrxchains > 2)
		tmp |= Rt3593LnaPe2En;
	if(group == 0){	/* 2GHz */
		tmp |= PaPeG0En;
		if(ctlr->ntxchains > 1)
			tmp |= PaPeG1En;
		if(ctlr->mac_ver == 0x3593 && ctlr->ntxchains > 2)
			tmp |= Rt3593PaPeG2En;
	}else{		/* 5GHz */
		tmp |= PaPeA0En;
		if(ctlr->ntxchains > 1)
			tmp |= PaPeA1En;
		if(ctlr->mac_ver == 0x3593 && ctlr->ntxchains > 2)
			tmp |= Rt3593PaPeA2En;
	}
	csr32w(ctlr, TxPinCfg, tmp);

	if(ctlr->mac_ver == 0x3593){
		tmp = csr32r(ctlr, GpioCtrl);
		if(ctlr->flags & ConnPciE){
			tmp &= ~0x01010000;
			if(group == 0)
				tmp |= 0x00010000;
		}else{
			tmp &= ~0x00008080;
			if(group == 0)
				tmp |= 0x00000080;
		}
		tmp = (tmp & ~0x00001000) | 0x00000010;
		csr32w(ctlr, GpioCtrl, tmp);
	}

	/* set initial AGC value */
	if(group == 0){	/* 2GHz band */
		if(ctlr->mac_ver >= 0x3071)
			agc = 0x1c + ctlr->lna[0] * 2;
		else
			agc = 0x2e + ctlr->lna[0];
	}else{		/* 5GHz band */
		agc = 0x32 + (ctlr->lna[group] * 5) / 3;
	}
	bbpwrite(ctlr, 66, agc);

	microdelay(1000);

}

static void
setchan(Ctlr *ctlr, uint chan)
{
	const struct rfprog *rfprog = rt2860_rf2850;
	u32int r2, r3, r4;
	s8int txpow1, txpow2;
	uint i;

	/* find the settings for this channel (we know it exists) */
	for(i = 0; rfprog[i].chan != chan; i++);

	r2 = rfprog[i].r2;
	if(ctlr->ntxchains == 1)
		r2 |= 1 << 12;		/* 1T: disable Tx chain 2 */
	if(ctlr->nrxchains == 1)
		r2 |= 1 << 15 | 1 << 4;	/* 1R: disable Rx chains 2 & 3 */
	else if(ctlr->nrxchains == 2)
		r2 |= 1 << 4;		/* 2R: disable Rx chain 3 */

	/* use Tx power values from EEPROM */
	txpow1 = ctlr->txpow1[i];
	txpow2 = ctlr->txpow2[i];
	if(chan > 14){
		if(txpow1 >= 0)
			txpow1 = txpow1 << 1 | 1;
		else
			txpow1 = (7 + txpow1) << 1;
		if(txpow2 >= 0)
			txpow2 = txpow2 << 1 | 1;
		else
			txpow2 = (7 + txpow2) << 1;
	}
	r3 = rfprog[i].r3 | txpow1 << 7;
	r4 = rfprog[i].r4 | ctlr->freq << 13 | txpow2 << 4;

	rfwrite(ctlr, Rf1, rfprog[i].r1);
	rfwrite(ctlr, Rf2, r2);
	rfwrite(ctlr, Rf3, r3);
	rfwrite(ctlr, Rf4, r4);

	microdelay(200);

	rfwrite(ctlr, Rf1, rfprog[i].r1);
	rfwrite(ctlr, Rf2, r2);
	rfwrite(ctlr, Rf3, r3 | 1);
	rfwrite(ctlr, Rf4, r4);

	microdelay(200);

	rfwrite(ctlr, Rf1, rfprog[i].r1);
	rfwrite(ctlr, Rf2, r2);
	rfwrite(ctlr, Rf3, r3);
	rfwrite(ctlr, Rf4, r4);
}

static void
rt3090setchan(Ctlr *ctlr, uint chan)
{
	s8int txpow1, txpow2;
	u8int rf;
	int i;

	assert(chan >= 1 && chan <= 14);	/* RT3090 is 2GHz only */

	/* find the settings for this channel (we know it exists) */
	for(i = 0; rt2860_rf2850[i].chan != chan; i++);

	/* use Tx power values from EEPROM */
	txpow1 = ctlr->txpow1[i];
	txpow2 = ctlr->txpow2[i];

	rt3090rfwrite(ctlr, 2, rt3090_freqs[i].n);
	rf = rt3090rfread(ctlr, 3);
	rf = (rf & ~0x0f) | rt3090_freqs[i].k;
	rt3090rfwrite(ctlr, 3, rf);
	rf = rt3090rfread(ctlr, 6);
	rf = (rf & ~0x03) | rt3090_freqs[i].r;
	rt3090rfwrite(ctlr, 6, rf);

	/* set Tx0 power */
	rf = rt3090rfread(ctlr, 12);
	rf = (rf & ~0x1f) | txpow1;
	rt3090rfwrite(ctlr, 12, rf);

	/* set Tx1 power */
	rf = rt3090rfread(ctlr, 13);
	rf = (rf & ~0x1f) | txpow2;
	rt3090rfwrite(ctlr, 13, rf);

	rf = rt3090rfread(ctlr, 1);
	rf &= ~0xfc;
	if(ctlr->ntxchains == 1)
		rf |= Rt3070Tx1Pd | Rt3070Tx2Pd;
	else if(ctlr->ntxchains == 2)
		rf |= Rt3070Tx2Pd;
	if(ctlr->nrxchains == 1)
		rf |= Rt3070Rx1Pd | Rt3070Rx2Pd;
	else if(ctlr->nrxchains == 2)
		rf |= Rt3070Rx2Pd;
	rt3090rfwrite(ctlr, 1, rf);

	/* set RF offset */
	rf = rt3090rfread(ctlr, 23);
	rf = (rf & ~0x7f) | ctlr->freq;
	rt3090rfwrite(ctlr, 23, rf);

	/* program RF filter */
	rf = rt3090rfread(ctlr, 24);	/* Tx */
	rf = (rf & ~0x3f) | ctlr->rf24_20mhz;
	rt3090rfwrite(ctlr, 24, rf);
	rf = rt3090rfread(ctlr, 31);	/* Rx */
	rf = (rf & ~0x3f) | ctlr->rf24_20mhz;
	rt3090rfwrite(ctlr, 31, rf);

	/* enable RF tuning */
	rf = rt3090rfread(ctlr, 7);
	rt3090rfwrite(ctlr, 7, rf | Rt3070Tune);
}

static int
rt3090filtercalib(Ctlr *ctlr, u8int init, u8int target, u8int *val)
{
	u8int rf22, rf24;
	u8int bbp55_pb, bbp55_sb, delta;
	int ntries;

	/* program filter */
	rf24 = rt3090rfread(ctlr, 24);
	rf24 = (rf24 & 0xc0) | init;	/* initial filter value */
	rt3090rfwrite(ctlr, 24, rf24);

	/* enable baseband loopback mode */
	rf22 = rt3090rfread(ctlr, 22);
	rt3090rfwrite(ctlr, 22, rf22 | Rt3070BbLoopback);

	/* set power and frequency of passband test tone */
	bbpwrite(ctlr, 24, 0x00);
	for(ntries = 0, bbp55_pb = 0; ntries < 100; ntries++){
		/* transmit test tone */
		bbpwrite(ctlr, 25, 0x90);
		microdelay(1000);
		/* read received power */
		bbp55_pb = bbpread(ctlr, 55);
		if(bbp55_pb != 0)
			break;
	}
	if(ntries == 100)
		return -1;

	/* set power and frequency of stopband test tone */
	bbpwrite(ctlr, 24, 0x06);
	for(ntries = 0; ntries < 100; ntries++){
		/* transmit test tone */
		bbpwrite(ctlr, 25, 0x90);
		microdelay(1000);
		/* read received power */
		bbp55_sb = bbpread(ctlr, 55);

		delta = bbp55_pb - bbp55_sb;
		if(delta > target)
			break;

		/* reprogram filter */
		rf24++;
		rt3090rfwrite(ctlr, 24, rf24);
	}
	if(ntries < 100){
		if(rf24 != init)
			rf24--;	/* backtrack */
		*val = rf24;
		rt3090rfwrite(ctlr, 24, rf24);
	}

	/* restore initial state */
	bbpwrite(ctlr, 24, 0x00);

	/* disable baseband loopback mode */
	rf22 = rt3090rfread(ctlr, 22);
	rt3090rfwrite(ctlr, 22, rf22 & ~Rt3070BbLoopback);

	return 0;
}

static void
rt3090setrxantenna(Ctlr *ctlr, int aux)
{
	u32int tmp;

	if(aux){
		tmp = csr32r(ctlr, PciEectrl);
		csr32w(ctlr, PciEectrl, tmp & ~EectrlC);
		tmp = csr32r(ctlr, GpioCtrl);
		csr32w(ctlr, GpioCtrl, (tmp & ~0x0808) | 0x08);
	}else{
		tmp = csr32r(ctlr, PciEectrl);
		csr32w(ctlr, PciEectrl, tmp | EectrlC);
		tmp = csr32r(ctlr, GpioCtrl);
		csr32w(ctlr, GpioCtrl, tmp & ~0x0808);
	}
}

static int
rt3090rfinit(Ctlr *ctlr)
{
	u32int tmp;
	u8int rf, bbp;
	int i;

	rf = rt3090rfread(ctlr, 30);
	/* toggle RF R30 bit 7 */
	rt3090rfwrite(ctlr, 30, rf | 0x80);
	microdelay(1000);
	rt3090rfwrite(ctlr, 30, rf & ~0x80);

	tmp = csr32r(ctlr, Rt3070LdoCfg0);
	tmp &= ~0x1f000000;
	if(ctlr->patch_dac && ctlr->mac_rev < 0x0211)
		tmp |= 0x0d000000;	/* 1.35V */
	else
		tmp |= 0x01000000;	/* 1.2V */
	csr32w(ctlr, Rt3070LdoCfg0, tmp);

	/* patch LNA_PE_G1 */
	tmp = csr32r(ctlr, Rt3070GpioSwitch);
	csr32w(ctlr, Rt3070GpioSwitch, tmp & ~0x20);

	/* initialize RF registers to default value */
	for(i = 0; i < nelem(rt3090_def_rf); i++){
		rt3090rfwrite(ctlr, rt3090_def_rf[i].reg,
		    rt3090_def_rf[i].val);
	}

	/* select 20MHz bandwidth */
	rt3090rfwrite(ctlr, 31, 0x14);

	rf = rt3090rfread(ctlr, 6);
	rt3090rfwrite(ctlr, 6, rf | 0x40);

	if(ctlr->mac_ver != 0x3593){
		/* calibrate filter for 20MHz bandwidth */
		ctlr->rf24_20mhz = 0x1f;	/* default value */
		rt3090filtercalib(ctlr, 0x07, 0x16, &ctlr->rf24_20mhz);

		/* select 40MHz bandwidth */
		bbp = bbpread(ctlr, 4);
		bbpwrite(ctlr, 4, (bbp & ~0x08) | 0x10);
		rf = rt3090rfread(ctlr, 31);
		rt3090rfwrite(ctlr, 31, rf | 0x20);

		/* calibrate filter for 40MHz bandwidth */
		ctlr->rf24_40mhz = 0x2f;	/* default value */
		rt3090filtercalib(ctlr, 0x27, 0x19, &ctlr->rf24_40mhz);

		/* go back to 20MHz bandwidth */
		bbp = bbpread(ctlr, 4);
		bbpwrite(ctlr, 4, bbp & ~0x18);
	}
	if(ctlr->mac_rev < 0x0211)
		rt3090rfwrite(ctlr, 27, 0x03);

	tmp = csr32r(ctlr, Rt3070Opt14);
	csr32w(ctlr, Rt3070Opt14, tmp | 1);

	if(ctlr->rf_rev == Rf3020)
		rt3090setrxantenna(ctlr, 0);

	bbp = bbpread(ctlr, 138);
	if(ctlr->mac_ver == 0x3593){
		if(ctlr->ntxchains == 1)
			bbp |= 0x60;	/* turn off DAC1 and DAC2 */
		else if(ctlr->ntxchains == 2)
			bbp |= 0x40;	/* turn off DAC2 */
		if(ctlr->nrxchains == 1)
			bbp &= ~0x06;	/* turn off ADC1 and ADC2 */
		else if(ctlr->nrxchains == 2)
			bbp &= ~0x04;	/* turn off ADC2 */
	}else{
		if(ctlr->ntxchains == 1)
			bbp |= 0x20;	/* turn off DAC1 */
		if(ctlr->nrxchains == 1)
			bbp &= ~0x02;	/* turn off ADC1 */
	}
	bbpwrite(ctlr, 138, bbp);

	rf = rt3090rfread(ctlr, 1);
	rf &= ~(Rt3070Rx0Pd | Rt3070Tx0Pd);
	rf |= Rt3070RfBlock | Rt3070Rx1Pd | Rt3070Tx1Pd;
	rt3090rfwrite(ctlr, 1, rf);

	rf = rt3090rfread(ctlr, 15);
	rt3090rfwrite(ctlr, 15, rf & ~Rt3070TxLo2);

	rf = rt3090rfread(ctlr, 17);
	rf &= ~Rt3070TxLo1;
	if(ctlr->mac_rev >= 0x0211 && !ctlr->ext_2ghz_lna)
		rf |= 0x20;	/* fix for long range Rx issue */
	if(ctlr->txmixgain_2ghz >= 2)
		rf = (rf & ~0x7) | ctlr->txmixgain_2ghz;
	rt3090rfwrite(ctlr, 17, rf);

	rf = rt3090rfread(ctlr, 20);
	rt3090rfwrite(ctlr, 20, rf & ~Rt3070RxLo1);

	rf = rt3090rfread(ctlr, 21);
	rt3090rfwrite(ctlr, 21, rf & ~Rt3070RxLo2);

	return 0;
}

static void
rt3090rfwakeup(Ctlr *ctlr)
{
	u32int tmp;
	u8int rf;

	if(ctlr->mac_ver == 0x3593){
		/* enable VCO */
		rf = rt3090rfread(ctlr, 1);
		rt3090rfwrite(ctlr, 1, rf | Rt3593Vco);

		/* initiate VCO calibration */
		rf = rt3090rfread(ctlr, 3);
		rt3090rfwrite(ctlr, 3, rf | Rt3593Vcocal);

		/* enable VCO bias current control */
		rf = rt3090rfread(ctlr, 6);
		rt3090rfwrite(ctlr, 6, rf | Rt3593VcoIc);

		/* initiate res calibration */
		rf = rt3090rfread(ctlr, 2);
		rt3090rfwrite(ctlr, 2, rf | Rt3593Rescal);

		/* set reference current control to 0.33 mA */
		rf = rt3090rfread(ctlr, 22);
		rf &= ~Rt3593CpIcMask;
		rf |= 1 << Rt3593CpIcShift;
		rt3090rfwrite(ctlr, 22, rf);

		/* enable RX CTB */
		rf = rt3090rfread(ctlr, 46);
		rt3090rfwrite(ctlr, 46, rf | Rt3593RxCtb);

		rf = rt3090rfread(ctlr, 20);
		rf &= ~(Rt3593LdoRfVcMask | Rt3593LdoPllVcMask);
		rt3090rfwrite(ctlr, 20, rf);
	}else{
		/* enable RF block */
		rf = rt3090rfread(ctlr, 1);
		rt3090rfwrite(ctlr, 1, rf | Rt3070RfBlock);

		/* enable VCO bias current control */
		rf = rt3090rfread(ctlr, 7);
		rt3090rfwrite(ctlr, 7, rf | 0x30);

		rf = rt3090rfread(ctlr, 9);
		rt3090rfwrite(ctlr, 9, rf | 0x0e);

		/* enable RX CTB */
		rf = rt3090rfread(ctlr, 21);
		rt3090rfwrite(ctlr, 21, rf | Rt3070RxCtb);

		/* fix Tx to Rx IQ glitch by raising RF voltage */
		rf = rt3090rfread(ctlr, 27);
		rf &= ~0x77;
		if(ctlr->mac_rev < 0x0211)
			rf |= 0x03;
		rt3090rfwrite(ctlr, 27, rf);
	}
	if(ctlr->patch_dac && ctlr->mac_rev < 0x0211){
		tmp = csr32r(ctlr, Rt3070LdoCfg0);
		tmp = (tmp & ~0x1f000000) | 0x0d000000;
		csr32w(ctlr, Rt3070LdoCfg0, tmp);
	}
}

static void
rt3090rfsetup(Ctlr *ctlr)
{
	u8int bbp;
	int i;

	if(ctlr->mac_rev >= 0x0211){
		/* enable DC filter */
		bbpwrite(ctlr, 103, 0xc0);

		/* improve power consumption */
		bbp = bbpread(ctlr, 31);
		bbpwrite(ctlr, 31, bbp & ~0x03);
	}

	csr32w(ctlr, TxSwCfg1, 0);
	if(ctlr->mac_rev < 0x0211){
		csr32w(ctlr, TxSwCfg2,
		    ctlr->patch_dac ? 0x2c : 0x0f);
	}else
		csr32w(ctlr, TxSwCfg2, 0);

	/* initialize RF registers from ROM */
	for(i = 0; i < 10; i++){
		if(ctlr->rf[i].reg == 0 || ctlr->rf[i].reg == 0xff)
			continue;
		rt3090rfwrite(ctlr, ctlr->rf[i].reg, ctlr->rf[i].val);
	}
}

static void
updateprot(Ctlr *ctlr)
{
	u32int tmp;

	tmp = RtsthEn | ProtNavShort | TxopAllowAll;
	/* setup protection frame rate (MCS code) */
	tmp |= /*(ic->ic_curmode == IEEE80211_MODE_11A) ?
	    rt2860_rates[RT2860_RIDX_OFDM6].mcs :*/
	    rt2860_rates[RidxCck11].mcs;

	/* CCK frames don't require protection */
	csr32w(ctlr, CckProtCfg, tmp);
/* XXX
	if(ic->ic_flags & IEEE80211_F_USEPROT){
		if(ic->ic_protmode == IEEE80211_PROT_RTSCTS)
			tmp |= ProtCtrlRtsCts;
		else if(ic->ic_protmode == IEEE80211_PROT_CTSONLY)
			tmp |= ProtCtrlCts;
	}
	csr32w(ctlr, OfdmProtCfg, tmp); */
}

static char*
rt2860start(Ether *edev)
{
	u32int tmp;
	u8int bbp1, bbp3;
	int i, qid, ridx, ntries;
	char *err;
	Ctlr *ctlr;

	ctlr = edev->ctlr;
	csr32w(ctlr, PwrPinCfg, IoRaPe);

	/* disable DMA */
	tmp = csr32r(ctlr, WpdmaGloCfg);
	tmp &= 0xff0;
	csr32w(ctlr, WpdmaGloCfg, tmp);

	/* PBF hardware reset */
	csr32w(ctlr, SysCtrl, 0xe1f);
	coherence();
	csr32w(ctlr, SysCtrl, 0xe00);

	if((err = boot(ctlr)) != nil){
		/*XXX: rt2860stop(ifp, 1);*/
		return err;
	}
	/* set MAC address */
	csr32w(ctlr, MacAddrDw0,
	    edev->ea[0] | edev->ea[1] << 8 | edev->ea[2] << 16 | edev->ea[3] << 24);
	csr32w(ctlr, MacAddrDw1,
	    edev->ea[4] | edev->ea[5] << 8 | 0xff << 16);

	/* init Tx power for all Tx rates (from EEPROM) */
	for(ridx = 0; ridx < 5; ridx++){
		if(ctlr->txpow20mhz[ridx] == 0xffffffff)
			continue;
		csr32w(ctlr, TxPwrCfg(ridx), ctlr->txpow20mhz[ridx]);
	}

	for(ntries = 0; ntries < 100; ntries++){
		tmp = csr32r(ctlr, WpdmaGloCfg);
		if((tmp & (TxDmaBusy | RxDmaBusy)) == 0)
			break;
		microdelay(1000);
	}
	if(ntries == 100){
		err = "timeout waiting for DMA engine";
		/*rt2860_stop(ifp, 1);*/
		return err;
	}
	tmp &= 0xff0;
	csr32w(ctlr, WpdmaGloCfg, tmp);

	/* reset Rx ring and all 6 Tx rings */
	csr32w(ctlr, WpdmaRstIdx, 0x1003f);

	/* PBF hardware reset */
	csr32w(ctlr, SysCtrl, 0xe1f);
	coherence();
	csr32w(ctlr, SysCtrl, 0xe00);

	csr32w(ctlr, PwrPinCfg, IoRaPe | IoRfPe);

	csr32w(ctlr, MacSysCtrl, BbpHrst | MacSrst);
	coherence();
	csr32w(ctlr, MacSysCtrl, 0);

	for(i = 0; i < nelem(rt2860_def_mac); i++)
		csr32w(ctlr, rt2860_def_mac[i].reg, rt2860_def_mac[i].val);
	if(ctlr->mac_ver >= 0x3071){
		/* set delay of PA_PE assertion to 1us (unit of 0.25us) */
		csr32w(ctlr, TxSwCfg0,
		    4 << DlyPapeEnShift);
	}

	if(!(csr32r(ctlr, PciCfg) & PciCfgPci)){
		ctlr->flags |= ConnPciE;
		/* PCIe has different clock cycle count than PCI */
		tmp = csr32r(ctlr, UsCycCnt);
		tmp = (tmp & ~0xff) | 0x7d;
		csr32w(ctlr, UsCycCnt, tmp);
	}

	/* wait while MAC is busy */
	for(ntries = 0; ntries < 100; ntries++){
		if(!(csr32r(ctlr, MacStatusReg) &
		    (RxStatusBusy | TxStatusBusy)))
			break;
		microdelay(1000);
	}
	if(ntries == 100){
		err = "timeout waiting for MAC";
		/*rt2860_stop(ifp, 1);*/
		return err;
	}

	/* clear Host to MCU mailbox */
	csr32w(ctlr, H2mBbpagent, 0);
	csr32w(ctlr, H2mMailbox, 0);

	mcucmd(ctlr, McuCmdRfreset, 0, 0);
	microdelay(1000);

	if((err = bbpinit(ctlr)) != nil){
		/*rt2860_stop(ifp, 1);*/
		return err;
	}
	/* clear RX WCID search table */
	setregion(ctlr, WcidEntry(0), 0, 512);
	/* clear pairwise key table */
	setregion(ctlr, Pkey(0), 0, 2048);
	/* clear IV/EIV table */
	setregion(ctlr, Iveiv(0), 0, 512);
	/* clear WCID attribute table */
	setregion(ctlr, WcidAttr(0), 0, 256);
	/* clear shared key table */
	setregion(ctlr, Skey(0, 0), 0, 8 * 32);
	/* clear shared key mode */
	setregion(ctlr, SkeyMode07, 0, 4);

	/* init Tx rings (4 EDCAs + HCCA + Mgt) */
	for(qid = 0; qid < 6; qid++){
		csr32w(ctlr, TxBasePtr(qid), PCIWADDR(ctlr->tx[qid].d));
		csr32w(ctlr, TxMaxCnt(qid), Ntx);
		csr32w(ctlr, TxCtxIdx(qid), 0);
	}

	/* init Rx ring */
	csr32w(ctlr, RxBasePtr, PCIWADDR(ctlr->rx.p));
	csr32w(ctlr, RxMaxCnt, Nrx);
	csr32w(ctlr, RxCalcIdx, Nrx - 1);

	/* setup maximum buffer sizes */
	csr32w(ctlr, MaxLenCfg, 1 << 12 |
	    (Rbufsize - Rxwisize - 2));

	for(ntries = 0; ntries < 100; ntries++){
		tmp = csr32r(ctlr, WpdmaGloCfg);
		if((tmp & (TxDmaBusy | RxDmaBusy)) == 0)
			break;
		microdelay(1000);
	}
	if(ntries == 100){
		err = "timeout waiting for DMA engine";
		/*rt2860_stop(ifp, 1);*/
		return err;
	}
	tmp &= 0xff0;
	csr32w(ctlr, WpdmaGloCfg, tmp);

	/* disable interrupts mitigation */
	csr32w(ctlr, DelayIntCfg, 0);

	/* write vendor-specific BBP values (from EEPROM) */
	for(i = 0; i < 8; i++){
		if(ctlr->bbp[i].reg == 0 || ctlr->bbp[i].reg == 0xff)
			continue;
		bbpwrite(ctlr, ctlr->bbp[i].reg, ctlr->bbp[i].val);
	}

	/* select Main antenna for 1T1R devices */
	if(ctlr->rf_rev == Rf2020 ||
	    ctlr->rf_rev == Rf3020 ||
	    ctlr->rf_rev == Rf3320)
		rt3090setrxantenna(ctlr, 0);

	/* send LEDs operating mode to microcontroller */
	mcucmd(ctlr, McuCmdLed1, ctlr->led[0], 0);
	mcucmd(ctlr, McuCmdLed2, ctlr->led[1], 0);
	mcucmd(ctlr, McuCmdLed3, ctlr->led[2], 0);

	if(ctlr->mac_ver >= 0x3071)
		rt3090rfinit(ctlr);

	mcucmd(ctlr, McuCmdSleep, 0x02ff, 1);
	mcucmd(ctlr, McuCmdWakeup, 0, 1);

	if(ctlr->mac_ver >= 0x3071)
		rt3090rfwakeup(ctlr);

	/* disable non-existing Rx chains */
	bbp3 = bbpread(ctlr, 3);
	bbp3 &= ~(1 << 3 | 1 << 4);
	if(ctlr->nrxchains == 2)
		bbp3 |= 1 << 3;
	else if(ctlr->nrxchains == 3)
		bbp3 |= 1 << 4;
	bbpwrite(ctlr, 3, bbp3);

	/* disable non-existing Tx chains */
	bbp1 = bbpread(ctlr, 1);
	if(ctlr->ntxchains == 1)
		bbp1 = (bbp1 & ~(1 << 3 | 1 << 4));
	else if(ctlr->mac_ver == 0x3593 && ctlr->ntxchains == 2)
		bbp1 = (bbp1 & ~(1 << 4)) | 1 << 3;
	else if(ctlr->mac_ver == 0x3593 && ctlr->ntxchains == 3)
		bbp1 = (bbp1 & ~(1 << 3)) | 1 << 4;
	bbpwrite(ctlr, 1, bbp1);

	if(ctlr->mac_ver >= 0x3071)
		rt3090rfsetup(ctlr);

	/* select default channel */
	if(ctlr->mac_ver >= 0x3071)
		rt3090setchan(ctlr, 3);
	else
		setchan(ctlr, 3);

	/* reset RF from MCU */
	mcucmd(ctlr, McuCmdRfreset, 0, 0);

	/* set RTS threshold */
	tmp = csr32r(ctlr, TxRtsCfg);
	tmp &= ~0xffff00;
	tmp |= 1 /* ic->ic_rtsthreshold */ << 8;
	csr32w(ctlr, TxRtsCfg, tmp);

	/* setup initial protection mode */
	updateprot(ctlr);

	/* turn radio LED on */
	setleds(ctlr, LedRadio);

	/* enable Tx/Rx DMA engine */
	if((err = txrxon(ctlr)) != 0){
		/*rt2860_stop(ifp, 1);*/
		return err;
	}

	/* clear pending interrupts */
	csr32w(ctlr, IntStatus, 0xffffffff);
	/* enable interrupts */
	csr32w(ctlr, IntMask, 0x3fffc);

	if(ctlr->flags & AdvancedPs)
		mcucmd(ctlr, McuCmdPslevel, ctlr->pslevel, 0);
	return nil;
}

/*
 * Add `delta' (signed) to each 4-bit sub-word of a 32-bit word.
 * Used to adjust per-rate Tx power registers.
 */
static u32int
b4inc(u32int b32, s8int delta)
{
	s8int i, b4;

	for(i = 0; i < 8; i++){
		b4 = b32 & 0xf;
		b4 += delta;
		if(b4 < 0)
			b4 = 0;
		else if(b4 > 0xf)
			b4 = 0xf;
		b32 = b32 >> 4 | b4 << 28;
	}
	return b32;
}

static void
transmit(Wifi *wifi, Wnode *wn, Block *b)
{
	Ether *edev;
	Ctlr *ctlr;
	Wifipkt *w;
	u8int mcs, qid;
	int ridx, /*ctl_ridx,*/ hdrlen;
	uchar *p;
	int nodeid;
	Block *outb;
	TXQ *tx;
	Pool *pool;

	edev = wifi->ether;
	ctlr = edev->ctlr;

	qlock(ctlr);
	if(ctlr->attached == 0 || ctlr->broken){
		qunlock(ctlr);
		freeb(b);
		return;
	}
	if((wn->channel != ctlr->channel)
	|| (!ctlr->prom && (wn->aid != ctlr->aid || memcmp(wn->bssid, ctlr->bssid, Eaddrlen) != 0)))
		rxon(edev, wn);

	if(b == nil){
		/* association note has no data to transmit */
		qunlock(ctlr);
		return;
	}

	pool = &ctlr->pool;
	qid = 0; /* for now */
	ridx = 0; 
	tx = &ctlr->tx[qid];

	nodeid = ctlr->bcastnodeid;
	w = (Wifipkt*)b->rp;
	hdrlen = wifihdrlen(w);

	p = pool->p + pool->i * TxwiDmaSz;
	if((w->a1[0] & 1) == 0){
		*(p+4) = TxAck; /* xflags */

		if(BLEN(b) > 512-4)
			*(p+1) = TxTxopBackoff; /* txop */ 

		if((w->fc[0] & 0x0c) == 0x08 &&	ctlr->bssnodeid != -1){
			nodeid = ctlr->bssnodeid;
			ridx = 2; /* BUG: hardcode 11Mbit */
		}
	}

	/*ctl_ridx = rt2860_rates[ridx].ctl_ridx;*/
	mcs = rt2860_rates[ridx].mcs;

	/* setup TX Wireless Information */
	*p = 0; /* flags */
	*(p+2) = PhyCck | mcs; /* phy */
	/* let HW generate seq numbers */
	*(p+4) |= TxNseq; /* xflags */
	put16(p + 6, BLEN(b) | (((mcs+1) & 0xf) << TxPidShift) ); /* length */

/*	put16((uchar*)&w->dur[0], rt2860_rates[ctl_ridx].lp_ack_dur); */

	*(p+5) = nodeid; /* wcid */

 	/* copy packet header */
	memmove(p + Txwisize, b->rp, hdrlen);
	
	/* setup tx descriptor */
	/* first segment is TXWI + 802.11 header */
	p = (uchar*)tx->d + Tdscsize * tx->i;
	put32(p, PCIWADDR(pool->p + pool->i * TxwiDmaSz)); /* sdp0 */
	put16(p + 6, Txwisize + hdrlen); /* sdl0 */
	*(p + 15) = TxQselEdca; /* flags */

	/* allocate output buffer */
	b->rp += hdrlen;
	tx->b[tx->i] = outb = iallocb(BLEN(b) + 256);
	if(outb == nil){
		print("outb = nil\n");
		return;
	}
	outb->rp = (uchar*)ROUND((uintptr)outb->base, 256);
	memset(outb->rp, 0, BLEN(b));
	memmove(outb->rp, b->rp, BLEN(b));
	outb->wp = outb->rp + BLEN(b);
	freeb(b);

	/* setup payload segments */
	put32(p + 8, PCIWADDR(outb->rp)); /* sdp1 */
	put16(p + 4, BLEN(outb) | TxLs1); /* sdl1 */

	p = pool->p + pool->i * TxwiDmaSz;
	w = (Wifipkt*)(p + Txwisize);
	if(ctlr->wifi->debug){
		print("transmit: %E->%E,%E nodeid=%x txq[%d]=%d size=%zd\n", w->a2, w->a1, w->a3, nodeid, qid, ctlr->tx[qid].i, BLEN(outb));
	}

	tx->i = (tx->i + 1) % Ntx;
	pool->i = (pool->i + 1) % Ntxpool;

	coherence();

	/* kick Tx */
	csr32w(ctlr, TxCtxIdx(qid), ctlr->tx[qid].i);

	qunlock(ctlr);
	return;
}

static void
rt2860attach(Ether *edev)
{
	FWImage *fw;
	Ctlr *ctlr;
	char *err;

	ctlr = edev->ctlr;
	eqlock(ctlr);
	if(waserror()){
		print("#l%d: %s\n", edev->ctlrno, up->errstr);
		/*if(ctlr->power)
			poweroff(ctlr);*/
		qunlock(ctlr);
		nexterror();
	}
	if(ctlr->attached == 0){
		if(ctlr->wifi == nil)
			ctlr->wifi = wifiattach(edev, transmit);

		if(ctlr->fw == nil){
			fw = readfirmware();
			ctlr->fw = fw;
		}
		if((err = rt2860start(edev)) != nil){
			error(err);
		} 

		ctlr->bcastnodeid = -1;
		ctlr->bssnodeid = -1;
		ctlr->channel = 1;
		ctlr->aid = 0;

		setoptions(edev);

		ctlr->attached = 1;
	}
	qunlock(ctlr);
	poperror();
}

static void
receive(Ctlr *ctlr)
{
	u32int hw;
	RXQ *rx;
	Block *b;
	uchar *d;

	rx = &ctlr->rx;
	if(rx->b == nil){
		print("rx->b == nil!");
		return;
	}
	hw = csr32r(ctlr, FsDrxIdx) & 0xfff;
	while(rx->i != hw){
		u16int sdl0, len;
		u32int flags;
		uchar *p;
		Wifipkt *w;
		int hdrlen;

		p = (uchar*)rx->p + Rdscsize * rx->i;
		sdl0 = get16(p + 4 /* sdp0 */ + 2 /* sdl1 */);
		if(!(sdl0 & RxDdone)){
			print("rxd ddone bit not set\n");
			break; /* should not happen */
		}
		flags = get32(p + 12);
		if(flags & (RxCrcerr | RxIcverr)){
		/*	print("crc | icv err\n"); */
			goto skip; 
		}

		b = rx->b[rx->i];
		if(b == nil){
			print("no buf\n");
			goto skip;
		}	
		d = b->rp;
		if(ctlr->wifi == nil)
			goto skip;
		if(rbplant(ctlr, rx->i) < 0){
			print("can't plant");
			goto skip;
		}
		ctlr->wcid = *b->rp;
		len = get16(b->rp + 2 /* wcid, keyidx */) & 0xfff;
		b->rp = d + Rxwisize;
		b->wp = b->rp + len;
		w = (Wifipkt*)b->rp;
		hdrlen = wifihdrlen(w);
		/* HW may insert 2 padding bytes after 802.11 header */
		if(flags & RxL2pad){
			memmove(b->rp + 2, b->rp, hdrlen);
			b->rp += 2;
		}
		w = (Wifipkt*)b->rp;
		if(ctlr->wifi->debug)
			print("receive: %E->%E,%E wcid 0x%x \n", w->a2, w->a1, w->a3, ctlr->wcid);
		wifiiq(ctlr->wifi, b);
skip:
		put16(p + 4 /* sdp0 */ + 2 /* sdl1 */, sdl0 & ~RxDdone);
		rx->i = (rx->i + 1) % Nrx;
	}
	coherence();
	/* tell HW what we have processed */
	csr32w(ctlr, RxCalcIdx, (rx->i - 1) % Nrx);
}

static void
stats(Ctlr *ctlr)
{
	u32int stat;
	u8int wcid;

	while((stat = csr32r(ctlr, TxStatFifo)) & TxqVld){
		wcid = (stat >> TxqWcidShift) & 0xff;
		/* if no ACK was requested, no feedback is available */
		if(!(stat & TxqAckreq) || wcid == 0xff){
			continue;
		}
	}
}

static void
rt2860tx(Ctlr *ctlr, u8int q)
{
	u32int hw;
	TXQ *tx;

	stats(ctlr);
	tx = &ctlr->tx[q];
	hw = csr32r(ctlr, TxDtxIdx(q));
	while(tx->n != hw){
		uchar *p = (uchar*)tx->d + Rdscsize * tx->n;
		u16int sdl0;

		if(tx->b[tx->n]){
			freeb(tx->b[tx->n]);
			tx->b[tx->n] = nil;
		}
		sdl0 = get16(p + 4 /* sdp0 */ + 2 /* sdl1 */);
		if(!(sdl0 & TxDdone)){
			print("txd ddone bit not set\n");
			break; /* should not happen */
		}
		memset((uchar*)ctlr->pool.p + TxwiDmaSz * tx->n, 0, TxwiDmaSz);
		memset((uchar*)tx->d + Tdscsize * tx->n, 0, Tdscsize);
		// put16(p + 4 /* sdp0 */ + 2 /* sdl1 */, sdl0 & ~TxDdone);
		tx->n = (tx->n + 1) % Ntx;
	}
	coherence();
}

static void
rt2860interrupt(Ureg*, void *arg)
{
	u32int r;
	Ether *edev;
	Ctlr *ctlr;
	int debug;

	edev = arg;
	ctlr = edev->ctlr;
	ilock(ctlr);

	debug = ctlr->wifi->debug;

	r = csr32r(ctlr, IntStatus);
	if(r == 0xffffffff){
		iunlock(ctlr);
		return;
	}
	if(r == 0){
		iunlock(ctlr);
		return;
	}

	/* acknowledge interrupts */
	csr32w(ctlr, IntStatus, r);

	if(r & TxRxCoherent){
		u32int tmp;
		/* DMA finds data coherent event when checking the DDONE bit */
		if(debug)
			print("txrx coherent intr\n");

		/* restart DMA engine */
		tmp = csr32r(ctlr, WpdmaGloCfg);
		tmp &= ~(TxWbDdone | RxDmaEn | TxDmaEn);
		csr32w(ctlr, WpdmaGloCfg, tmp);

		txrxon(ctlr);
	}
	if(r & MacInt2)
		stats(ctlr);
	
	if(r & TxDoneInt5)
		rt2860tx(ctlr, 5);

	if(r & RxDoneInt)
		receive(ctlr);

	if(r & TxDoneInt4)
		rt2860tx(ctlr, 4);

	if(r & TxDoneInt3)
		rt2860tx(ctlr, 3);

	if(r & TxDoneInt2)
		rt2860tx(ctlr, 2);

	if(r & TxDoneInt1)
		rt2860tx(ctlr, 1);

	if(r & TxDoneInt0)
		rt2860tx(ctlr, 0);

	iunlock(ctlr);
}

static void
eepromctl(Ctlr *ctlr, u32int val)
{
	csr32w(ctlr, PciEectrl, val);
	coherence();
	microdelay(EepromDelay);	
}

/*
 * Read 16 bits at address 'addr' from the serial EEPROM (either 93C46,
 * 93C66 or 93C86).
 */
static u16int
eeread2(Ctlr *ctlr, u16int addr)
{
	u32int tmp;
	u16int val;
	int n;

	/* clock C once before the first command */
	eepromctl(ctlr, 0);

	eepromctl(ctlr, EectrlS);
	eepromctl(ctlr, EectrlS | EectrlC);
	eepromctl(ctlr, EectrlS);

	/* write start bit (1) */
	eepromctl(ctlr, EectrlS | EectrlD);
	eepromctl(ctlr, EectrlS | EectrlD | EectrlC);

	/* write READ opcode (10) */
	eepromctl(ctlr, EectrlS | EectrlD);
	eepromctl(ctlr, EectrlS | EectrlD | EectrlC);
	eepromctl(ctlr, EectrlS);
	eepromctl(ctlr, EectrlS | EectrlC);

	/* write address (A5-A0 or A7-A0) */
	n = ((csr32r(ctlr, PciEectrl) & 0x30) == 0) ? 5 : 7;
	for(; n >= 0; n--){
		eepromctl(ctlr, EectrlS |
		    (((addr >> n) & 1) << EectrlShiftD));
		eepromctl(ctlr, EectrlS |
		    (((addr >> n) & 1) << EectrlShiftD) | EectrlC);
	}

	eepromctl(ctlr, EectrlS);

	/* read data Q15-Q0 */
	val = 0;
	for(n = 15; n >= 0; n--){
		eepromctl(ctlr, EectrlS | EectrlC);
		tmp = csr32r(ctlr, PciEectrl);
		val |= ((tmp & EectrlQ) >> EectrlShiftQ) << n;
		eepromctl(ctlr, EectrlS);
	}

	eepromctl(ctlr, 0);

	/* clear Chip Select and clock C */
	eepromctl(ctlr, EectrlS);
	eepromctl(ctlr, 0);
	eepromctl(ctlr, EectrlC);

	return val;
}

/* Read 16-bit from eFUSE ROM (>=RT3071 only.) */
static u16int
efuseread2(Ctlr *ctlr, u16int addr)
{
	u32int tmp;
	u16int reg;
	int ntries;

	addr *= 2;
	/*-
	 * Read one 16-byte block into registers EFUSE_DATA[0-3]:
	 * DATA0: F E D C
	 * DATA1: B A 9 8
	 * DATA2: 7 6 5 4
	 * DATA3: 3 2 1 0
	 */
	tmp = csr32r(ctlr, Rt3070EfuseCtrl);
	tmp &= ~(Rt3070EfsromModeMask | Rt3070EfsromAinMask);
	tmp |= (addr & ~0xf) << Rt3070EfsromAinShift | Rt3070EfsromKick;
	csr32w(ctlr, Rt3070EfuseCtrl, tmp);
	for(ntries = 0; ntries < 500; ntries++){
		tmp = csr32r(ctlr, Rt3070EfuseCtrl);
		if(!(tmp & Rt3070EfsromKick))
			break;
		microdelay(2);
	}
	if(ntries == 500)
		return 0xffff;

	if((tmp & Rt3070EfuseAoutMask) == Rt3070EfuseAoutMask)
		return 0xffff;	/* address not found */

	/* determine to which 32-bit register our 16-bit word belongs */
	reg = Rt3070EfuseData3 - (addr & 0xc);
	tmp = csr32r(ctlr, reg);

	return (addr & 2) ? tmp >> 16 : tmp & 0xffff;
}

static char*
eepromread(Ether *edev)
{
	s8int delta_2ghz, delta_5ghz;
	u32int tmp;
	u16int val;
	int ridx, ant, i;
	u16int (*rom_read)(Ctlr*, u16int);
	Ctlr *ctlr;

	enum { DefLna =	10 };

	ctlr = edev->ctlr;
	/* check whether the ROM is eFUSE ROM or EEPROM */
	rom_read = eeread2;
	if(ctlr->mac_ver >= 0x3071){
		tmp = csr32r(ctlr, Rt3070EfuseCtrl);
		if(tmp & Rt3070SelEfuse)
			rom_read = efuseread2;
	}

	/* read MAC address */
	val = rom_read(ctlr, EepromMac01);
	edev->ea[0] = val & 0xff;
	edev->ea[1] = val >> 8;
	val = rom_read(ctlr, EepromMac23);
	edev->ea[2] = val & 0xff;
	edev->ea[3] = val >> 8;
	val = rom_read(ctlr, EepromMac45);
	edev->ea[4] = val & 0xff;
	edev->ea[5] = val >> 8;

	/* read vendor BBP settings */
	for(i = 0; i < 8; i++){
		val = rom_read(ctlr, EepromBbpBase + i);
		ctlr->bbp[i].val = val & 0xff;
		ctlr->bbp[i].reg = val >> 8;
	}

	if(ctlr->mac_ver >= 0x3071){
		/* read vendor RF settings */
		for(i = 0; i < 10; i++){
			val = rom_read(ctlr, Rt3071EepromRfBase + i);
			ctlr->rf[i].val = val & 0xff;
			ctlr->rf[i].reg = val >> 8;
		}
	}

	/* read RF frequency offset from EEPROM */
	val = rom_read(ctlr, EepromFreqLeds);
	ctlr->freq = ((val & 0xff) != 0xff) ? val & 0xff : 0;
	if((val >> 8) != 0xff){
		/* read LEDs operating mode */
		ctlr->leds = val >> 8;
		ctlr->led[0] = rom_read(ctlr, EepromLed1);
		ctlr->led[1] = rom_read(ctlr, EepromLed2);
		ctlr->led[2] = rom_read(ctlr, EepromLed3);
	}else{
		/* broken EEPROM, use default settings */
		ctlr->leds = 0x01;
		ctlr->led[0] = 0x5555;
		ctlr->led[1] = 0x2221;
		ctlr->led[2] = 0xa9f8;
	}
	/* read RF information */
	val = rom_read(ctlr, EepromAntenna);
	if(val == 0xffff){
		if(ctlr->mac_ver == 0x3593){
			/* default to RF3053 3T3R */
			ctlr->rf_rev = Rf3053;
			ctlr->ntxchains = 3;
			ctlr->nrxchains = 3;
		}else if(ctlr->mac_ver >= 0x3071){
			/* default to RF3020 1T1R */
			ctlr->rf_rev = Rf3020;
			ctlr->ntxchains = 1;
			ctlr->nrxchains = 1;
		}else{
			/* default to RF2820 1T2R */
			ctlr->rf_rev = Rf2820;
			ctlr->ntxchains = 1;
			ctlr->nrxchains = 2;
		}
	}else{
		ctlr->rf_rev = (val >> 8) & 0xf;
		ctlr->ntxchains = (val >> 4) & 0xf;
		ctlr->nrxchains = val & 0xf;
	}

	/* check if RF supports automatic Tx access gain control */
	val = rom_read(ctlr, EepromConfig);
	/* check if driver should patch the DAC issue */
	if((val >> 8) != 0xff)
		ctlr->patch_dac = (val >> 15) & 1;
	if((val & 0xff) != 0xff){
		ctlr->ext_5ghz_lna = (val >> 3) & 1;
		ctlr->ext_2ghz_lna = (val >> 2) & 1;
		/* check if RF supports automatic Tx access gain control */
		ctlr->calib_2ghz = ctlr->calib_5ghz = 0; /* XXX (val >> 1) & 1 */;
		/* check if we have a hardware radio switch */
		ctlr->rfswitch = val & 1;
	}
	if(ctlr->flags & AdvancedPs){
		/* read PCIe power save level */
		val = rom_read(ctlr, EepromPciePslevel);
		if((val & 0xff) != 0xff){
			ctlr->pslevel = val & 0x3;
			val = rom_read(ctlr, EepromRev);
			if((val & 0xff80) != 0x9280)
				ctlr->pslevel = MIN(ctlr->pslevel, 1);
		}
	}

	/* read power settings for 2GHz channels */
	for(i = 0; i < 14; i += 2){
		val = rom_read(ctlr,
		    EepromPwr2ghzBase1 + i / 2);
		ctlr->txpow1[i + 0] = (s8int)(val & 0xff);
		ctlr->txpow1[i + 1] = (s8int)(val >> 8);

		val = rom_read(ctlr,
		    EepromPwr2ghzBase2 + i / 2);
		ctlr->txpow2[i + 0] = (s8int)(val & 0xff);
		ctlr->txpow2[i + 1] = (s8int)(val >> 8);
	}
	/* fix broken Tx power entries */

	for(i = 0; i < 14; i++){
		if(ctlr->txpow1[i] < 0 || ctlr->txpow1[i] > 31)
			ctlr->txpow1[i] = 5;
		if(ctlr->txpow2[i] < 0 || ctlr->txpow2[i] > 31)
			ctlr->txpow2[i] = 5;
	}
	/* read power settings for 5GHz channels */
	for(i = 0; i < 40; i += 2){
		val = rom_read(ctlr,
		    EepromPwr5ghzBase1 + i / 2);
		ctlr->txpow1[i + 14] = (s8int)(val & 0xff);
		ctlr->txpow1[i + 15] = (s8int)(val >> 8);

		val = rom_read(ctlr,
		    EepromPwr5ghzBase2 + i / 2);
		ctlr->txpow2[i + 14] = (s8int)(val & 0xff);
		ctlr->txpow2[i + 15] = (s8int)(val >> 8);
	}

	/* fix broken Tx power entries */
	for(i = 0; i < 40; i++){
		if(ctlr->txpow1[14 + i] < -7 || ctlr->txpow1[14 + i] > 15)
			ctlr->txpow1[14 + i] = 5;
		if(ctlr->txpow2[14 + i] < -7 || ctlr->txpow2[14 + i] > 15)
			ctlr->txpow2[14 + i] = 5;
	}

	/* read Tx power compensation for each Tx rate */
	val = rom_read(ctlr, EepromDeltapwr);
	delta_2ghz = delta_5ghz = 0;
	if((val & 0xff) != 0xff && (val & 0x80)){
		delta_2ghz = val & 0xf;
		if(!(val & 0x40))	/* negative number */
			delta_2ghz = -delta_2ghz;
	}
	val >>= 8;
	if((val & 0xff) != 0xff && (val & 0x80)){
		delta_5ghz = val & 0xf;
		if(!(val & 0x40))	/* negative number */
			delta_5ghz = -delta_5ghz;
	}

	for(ridx = 0; ridx < 5; ridx++){
		u32int reg;

		val = rom_read(ctlr, EepromRpwr + ridx * 2);
		reg = val;
		val = rom_read(ctlr, EepromRpwr + ridx * 2 + 1);
		reg |= (u32int)val << 16;

		ctlr->txpow20mhz[ridx] = reg;
		ctlr->txpow40mhz_2ghz[ridx] = b4inc(reg, delta_2ghz);
		ctlr->txpow40mhz_5ghz[ridx] = b4inc(reg, delta_5ghz);
	}

	/* read factory-calibrated samples for temperature compensation */
	val = rom_read(ctlr, EepromTssi12ghz);
	ctlr->tssi_2ghz[0] = val & 0xff;	/* [-4] */
	ctlr->tssi_2ghz[1] = val >> 8;	/* [-3] */
	val = rom_read(ctlr, EepromTssi22ghz);
	ctlr->tssi_2ghz[2] = val & 0xff;	/* [-2] */
	ctlr->tssi_2ghz[3] = val >> 8;	/* [-1] */
	val = rom_read(ctlr, EepromTssi32ghz);
	ctlr->tssi_2ghz[4] = val & 0xff;	/* [+0] */
	ctlr->tssi_2ghz[5] = val >> 8;	/* [+1] */
	val = rom_read(ctlr, EepromTssi42ghz);
	ctlr->tssi_2ghz[6] = val & 0xff;	/* [+2] */
	ctlr->tssi_2ghz[7] = val >> 8;	/* [+3] */
	val = rom_read(ctlr, EepromTssi52ghz);
	ctlr->tssi_2ghz[8] = val & 0xff;	/* [+4] */
	ctlr->step_2ghz = val >> 8;
	/* check that ref value is correct, otherwise disable calibration */
	if(ctlr->tssi_2ghz[4] == 0xff)
		ctlr->calib_2ghz = 0;

	val = rom_read(ctlr, EepromTssi15ghz);
	ctlr->tssi_5ghz[0] = val & 0xff;	/* [-4] */
	ctlr->tssi_5ghz[1] = val >> 8;	/* [-3] */
	val = rom_read(ctlr, EepromTssi25ghz);
	ctlr->tssi_5ghz[2] = val & 0xff;	/* [-2] */
	ctlr->tssi_5ghz[3] = val >> 8;	/* [-1] */
	val = rom_read(ctlr, EepromTssi35ghz);
	ctlr->tssi_5ghz[4] = val & 0xff;	/* [+0] */
	ctlr->tssi_5ghz[5] = val >> 8;	/* [+1] */
	val = rom_read(ctlr, EepromTssi45ghz);
	ctlr->tssi_5ghz[6] = val & 0xff;	/* [+2] */
	ctlr->tssi_5ghz[7] = val >> 8;	/* [+3] */
	val = rom_read(ctlr, EepromTssi55ghz);
	ctlr->tssi_5ghz[8] = val & 0xff;	/* [+4] */
	ctlr->step_5ghz = val >> 8;
	/* check that ref value is correct, otherwise disable calibration */
	if(ctlr->tssi_5ghz[4] == 0xff)
		ctlr->calib_5ghz = 0;

	/* read RSSI offsets and LNA gains from EEPROM */
	val = rom_read(ctlr, EepromRssi12ghz);
	ctlr->rssi_2ghz[0] = val & 0xff;	/* Ant A */
	ctlr->rssi_2ghz[1] = val >> 8;	/* Ant B */
	val = rom_read(ctlr, EepromRssi22ghz);
	if(ctlr->mac_ver >= 0x3071){
		/*
		 * On RT3090 chips (limited to 2 Rx chains), this ROM
		 * field contains the Tx mixer gain for the 2GHz band.
		 */
		if((val & 0xff) != 0xff)
			ctlr->txmixgain_2ghz = val & 0x7;
	}else
		ctlr->rssi_2ghz[2] = val & 0xff;	/* Ant C */
	ctlr->lna[2] = val >> 8;		/* channel group 2 */

	val = rom_read(ctlr, EepromRssi15ghz);
	ctlr->rssi_5ghz[0] = val & 0xff;	/* Ant A */
	ctlr->rssi_5ghz[1] = val >> 8;	/* Ant B */
	val = rom_read(ctlr, EepromRssi25ghz);
	ctlr->rssi_5ghz[2] = val & 0xff;	/* Ant C */
	ctlr->lna[3] = val >> 8;		/* channel group 3 */

	val = rom_read(ctlr, EepromLna);
	if(ctlr->mac_ver >= 0x3071)
		ctlr->lna[0] = DefLna;
	else				/* channel group 0 */
		ctlr->lna[0] = val & 0xff;
	ctlr->lna[1] = val >> 8;		/* channel group 1 */

	/* fix broken 5GHz LNA entries */
	if(ctlr->lna[2] == 0 || ctlr->lna[2] == 0xff){
		ctlr->lna[2] = ctlr->lna[1];
	}
	if(ctlr->lna[3] == 0 || ctlr->lna[3] == 0xff){
		ctlr->lna[3] = ctlr->lna[1];
	}

	/* fix broken RSSI offset entries */
	for(ant = 0; ant < 3; ant++){
		if(ctlr->rssi_2ghz[ant] < -10 || ctlr->rssi_2ghz[ant] > 10){
			ctlr->rssi_2ghz[ant] = 0;
		}
		if(ctlr->rssi_5ghz[ant] < -10 || ctlr->rssi_5ghz[ant] > 10){
			ctlr->rssi_5ghz[ant] = 0;
		}
	}

	return 0;
}

static const char *
getrfname(u8int rev)
{
	if((rev == 0) || (rev >= nelem(rfnames)))
		return "unknown";
	if(rfnames[rev][0] == '\0')
		return "unknown";
	return rfnames[rev];
}

static int
rbplant(Ctlr *ctlr, int i)
{
	Block *b;
	uchar *p;

	b = iallocb(Rbufsize + 256);
	if(b == nil)
		return -1;
	b->rp = b->wp = (uchar*)ROUND((uintptr)b->base, 256);
	memset(b->rp, 0, Rbufsize);
	ctlr->rx.b[i] = b;
	p = (uchar*)&ctlr->rx.p[i * 4]; /* sdp0 */
	memset(p, 0, Rdscsize);
	put32(p, PCIWADDR(b->rp)); 
	p = (uchar*)&ctlr->rx.p[i * 4 + 1]; /* sdl0 */
	p += 2; /* sdl1 */
	put16(p, Rbufsize);;
	
	return 0;
}

static char*
allocrx(Ctlr *ctlr, RXQ *rx)
{
	int i;

	if(rx->b == nil)
		rx->b = malloc(sizeof(Block*) * Nrx);
	if(rx->p == nil) /* Rx descriptors */
		rx->p = mallocalign(Nrx * Rdscsize, 16, 0, 0);
	if(rx->b == nil || rx->p == nil)
		return "no memory for rx ring";
	memset(rx->p, 0, Nrx * Rdscsize);
	for(i=0; i<Nrx; i++){
		if(rx->b[i] != nil){
			freeb(rx->b[i]);
			rx->b[i] = nil;
		}
		if(rbplant(ctlr, i) < 0)
			return "no memory for rx descriptors";
	}
	rx->i = 0;
	return nil;
}

static void 
freerx(Ctlr *, RXQ *rx)
{
	int i;

	for(i = 0; i < Nrx; i++){
		if(rx->b[i] != nil){
			freeb(rx->b[i]);
			rx->b[i] = nil;
		}
	}
	free(rx->b);
	free(rx->p);
	rx->p = nil;
	rx->b = nil;
	rx->i = 0;
}

static char*
alloctx(Ctlr *, TXQ *tx)
{
	if(tx->b == nil)
		tx->b = malloc(sizeof(Block*) * Ntx);
	if(tx->d == nil) /* Tx descriptors */
		tx->d = mallocalign(Ntx * Tdscsize, 16, 0, 0);
	if(tx->b == nil || tx->d == nil)
		return "no memory for tx ring";
	memset(tx->d, 0, Ntx * Tdscsize);
	memset(tx->b, 0, Ntx * sizeof(Block*));
	tx->i = 0;
	return nil;
}

static void
freetx(Ctlr *, TXQ *tx)
{
	free(tx->b);
	free(tx->d);
	tx->d = nil;
	tx->i = 0;
}

static char*
alloctxpool(Ctlr *ctlr)
{
	Pool *pool;

	pool = &ctlr->pool;
	if(pool->p == nil)
		pool->p = mallocalign(Ntxpool * TxwiDmaSz, 4096, 0, 0);
	if(pool->p == nil)
		return "no memory for pool";
	memset(pool->p, 0, Ntxpool * TxwiDmaSz);
	pool->i = 0;
	return 0;
}

static char*
initring(Ctlr *ctlr)
{
	int qid;
	char *err;

	/*
	 * Allocate Tx (4 EDCAs + HCCA + Mgt) and Rx rings.
	 */
	for(qid = 0; qid < 6; qid++){
		if((err = alloctx(ctlr, &ctlr->tx[qid])) != nil)
			goto fail1;
	}
	if((err = allocrx(ctlr, &ctlr->rx)) != nil)
		goto fail1;

	if((err = alloctxpool(ctlr)) != nil)
		goto fail2;
	/* mgmt ring is broken on RT2860C, use EDCA AC VO ring instead */

	ctlr->mgtqid = (ctlr->mac_ver == 0x2860 && ctlr->mac_rev == 0x0100) ?
	    3 : 5;

		return nil;
fail2:	freerx(ctlr, &ctlr->rx);
		return err;
fail1:	while(--qid >= 0)
			freetx(ctlr, &ctlr->tx[qid]);
		return err;
}

static int
rt2860init(Ether *edev)
{
	Ctlr *ctlr;
	int ntries;
	char *err;
	u32int tmp;

	SET(tmp);
	ctlr = edev->ctlr;
	/* wait for NIC to initialize */
	for(ntries = 0; ntries < 100; ntries++){
		tmp = csr32r(ctlr, AsicVerId);
		if(tmp != 0 && tmp != 0xffffffff)
			break;
		microdelay(10);
	}
	if(ntries == 100){
		print("timeout waiting for NIC to initialize");
		return -1;
	}
	ctlr->mac_ver = tmp >> 16;
	ctlr->mac_rev = tmp & 0xffff;

	if(ctlr->mac_ver != 0x2860){
		switch(ctlr->pdev->did){
			default:
					break;
			case RalinkRT2890:
			case RalinkRT2790:
			case RalinkRT3090:
			case AwtRT2890:
					ctlr->flags = AdvancedPs;
					break;
		}
	}
	/* retrieve RF rev. no and various other things from EEPROM */
	eepromread(edev);

	print("MAC/BBP RT%X (rev 0x%04X), RF %s (MIMO %dT%dR)\n",
	    ctlr->mac_ver, ctlr->mac_rev,
	    getrfname(ctlr->rf_rev), ctlr->ntxchains, ctlr->nrxchains);
	if((err = initring(ctlr)) != nil){
		print("error: %s", err);
		return -1;
	}

	return 0;
}

static Ctlr *rt2860head, *rt2860tail;

static void
rt2860pci(void)
{
	Pcidev *pdev;
	
	pdev = nil;
	while(pdev = pcimatch(pdev, 0, 0)){
		Ctlr *ctlr;
		void *mem;
		
		if(pdev->ccrb != 2 || pdev->ccru != 0x80)
			continue;
		if(pdev->vid != 0x1814) /* Ralink */
			continue;
		if(pdev->mem[0].bar & 1)
			continue;

		switch(pdev->did){
		default:
			continue;
		case RalinkRT2790:
		case RalinkRT3090:
			break;
		}

		ctlr = malloc(sizeof(Ctlr));
		if(ctlr == nil){
			print("rt2860: unable to alloc Ctlr\n");
			continue;
		}
		ctlr->port = pdev->mem[0].bar & ~0xF;
		mem = vmap(ctlr->port, pdev->mem[0].size);
		if(mem == nil){
			print("rt2860: can't map %llux\n", ctlr->port);
			free(ctlr);
			continue;
		}
		ctlr->nic = mem;
		ctlr->pdev = pdev;

		if(rt2860head != nil)
			rt2860tail->link = ctlr;
		else
			rt2860head = ctlr;
		rt2860tail = ctlr;
	}
}

static int
rt2860pnp(Ether* edev)
{
	Ctlr *ctlr;
	
	if(rt2860head == nil)
		rt2860pci();
again:
	for(ctlr = rt2860head; ctlr != nil; ctlr = ctlr->link){
		if(ctlr->active)
			continue;
		if(edev->port == 0 || edev->port == ctlr->port){
			ctlr->active = 1;
			break;
		}
	}

	if(ctlr == nil)
		return -1;

	pcienable(ctlr->pdev);
	pcisetbme(ctlr->pdev);

	edev->ctlr = ctlr;
	edev->port = ctlr->port;
	edev->irq = ctlr->pdev->intl;
	edev->tbdf = ctlr->pdev->tbdf;
	edev->arg = edev;
	edev->attach = rt2860attach;
	edev->ifstat = rt2860ifstat;
	edev->ctl = rt2860ctl;
	edev->promiscuous = rt2860promiscuous;
	edev->multicast = rt2860multicast;
	edev->mbps = 10;

	if(rt2860init(edev) < 0){
		edev->ctlr = nil;
		goto again;
	}

	intrenable(edev->irq, rt2860interrupt, edev, edev->tbdf, edev->name);

	return 0;
}

void
etherrt2860link(void)
{
	addethercard("rt2860", rt2860pnp);
}