git: 9front

Download patch

ref: 04b8eab6060efc3c709136170d286126457805af
parent: f6e898f38f38ce84153ac0d7ed57e475f5c5a3e1
author: Alexander Polakov <plhk@sdf.org>
date: Tue Jul 9 12:16:28 EDT 2013

Add Ralink RT2860 wireless driver

--- /dev/null
+++ b/sys/src/9/pc/etherrt2860.c
@@ -1,0 +1,3019 @@
+/* 
+ * 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/error.h"
+#include "../port/netif.h"
+
+#include "etherif.h"
+#include "wifi.h"
+
+/* for consistency */
+typedef signed char s8int;
+
+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 */,
+};
+
+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;
+	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 port;
+	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 },
+};
+
+/* vendors */
+enum {
+	Ralink = 0x1814,
+	Awt = 0x1a3b,
+};
+/* products */
+enum {
+	RalinkRT2890 = 0x0681,
+	RalinkRT2790 = 0x0781,
+	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 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;
+	char buf[64];
+	int i;
+
+	ctlr = edev->ctlr;
+	for(i = 0; i < edev->nopt; i++){
+		if(strncmp(edev->opt[i], "essid=", 6) == 0){
+			snprint(buf, sizeof(buf), "essid %s", edev->opt[i]+6);
+			if(!waserror()){
+				wifictl(ctlr->wifi, buf, strlen(buf));
+				poperror();
+			}
+		}
+	}
+}
+
+static void
+rxon(Ether *edev, Wnode *bss)
+{
+	u32int tmp;
+	Ctlr *ctlr;
+
+	ctlr = edev->ctlr;
+
+	if(bss != nil){
+		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{
+		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 */
+	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 |= /* (ic->ic_flags & IEEE80211_F_SHSLOT) ? 9 : */ 20;
+	csr32w(ctlr, BkoffSlotCfg, tmp);
+	
+	/* set TX preamble */
+	tmp = csr32r(ctlr, AutoRspCfg);
+	tmp &= ~CckShortEn;
+/*	if (sc->sc_ic.ic_flags & IEEE80211_F_SHPREAMBLE)
+		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 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 char*
+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)
+		return "could not write to RF";
+
+	/* RF registers are 24-bit on the RT2860 */
+	tmp = RfRegCtrl | 24 << RfRegWidthShift |
+	    (val & 0x3fffff) << 2 | (reg & 3);
+	csr32w(ctlr, RfCsrCfg0, tmp);
+	return nil;
+}
+
+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
+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 */
+/* notyet
+	if (ctlr->rf_rev == Rf2020 ||
+	    ctlr->rf_rev == Rf3020 ||
+	    ctlr->rf_rev == Rf3320)
+		rt3090_set_rx_antenna(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);
+
+/* XXX: 30xx
+	if (ctlr->mac_ver >= 0x3071)
+		rt3090_rf_init(ctlr);
+*/
+
+	mcucmd(ctlr, McuCmdSleep, 0x02ff, 1);
+	mcucmd(ctlr, McuCmdWakeup, 0, 1);
+/* XXX: 30xx
+	if (ctlr->mac_ver >= 0x3071)
+		rt3090_rf_wakeup(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);
+/* XXX: 30xx
+	if (ctlr->mac_ver >= 0x3071)
+		rt3090_rf_setup(ctlr);
+*/
+	/* select default channel */
+	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=%ld\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 *, u16int)
+{
+	/* XXX: 30xx */
+	print("efuse rom not implemented\n");
+	return 0;
+}
+
+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 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;
+
+		switch(pdev->did){
+		default:
+			continue;
+		case 0x0781: /* RT2790 */
+			break;
+		}
+
+		pcisetbme(pdev);
+		pcisetpms(pdev, 0);
+
+		ctlr = malloc(sizeof(Ctlr));
+		if(ctlr == nil) {
+			print("rt2860: unable to alloc Ctlr\n");
+			continue;
+		}
+		ctlr->port = pdev->mem[0].bar & ~0x0F;
+		mem = vmap(pdev->mem[0].bar & ~0x0F, pdev->mem[0].size);
+		if(mem == nil) {
+			print("rt2860: can't map %8.8luX\n", pdev->mem[0].bar);
+			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;
+
+	edev->ctlr = ctlr;
+	edev->port = ctlr->port;
+	edev->irq = ctlr->pdev->intl;
+	edev->tbdf = ctlr->pdev->tbdf;
+	edev->arg = edev;
+	edev->interrupt = rt2860interrupt;
+	edev->attach = rt2860attach;
+	edev->ifstat = rt2860ifstat;
+	edev->ctl = rt2860ctl;
+	edev->promiscuous = rt2860promiscuous;
+	edev->multicast = nil;
+	edev->mbps = 10;
+
+	if(rt2860init(edev) < 0){
+		edev->ctlr = nil;
+		goto again;
+	}
+	return 0;
+}
+
+void
+etherrt2860link(void)
+{
+	addethercard("rt2860", rt2860pnp);
+}
--- a/sys/src/9/pc/mkfile
+++ b/sys/src/9/pc/mkfile
@@ -121,6 +121,7 @@
 uartaxp.$O:			uartaxp.i
 etherm10g.$O:			etherm10g2k.i etherm10g4k.i
 etheriwl.$O:			wifi.h
+etherrt2860.$O: 		wifi.h
 wifi.$O:			wifi.h
 
 init.h:D:		../port/initcode.c init9.c
--- a/sys/src/9/pc/pccpuf
+++ b/sys/src/9/pc/pccpuf
@@ -68,6 +68,7 @@
 	ethersmc	devi82365 cis
 	etherwavelan	wavelan devi82365 cis pci
 	etheriwl	pci wifi
+	etherrt2860	pci wifi
 	ethermedium
 	netdevmedium
 	loopbackmedium
--- a/sys/src/9/pc/pcf
+++ b/sys/src/9/pc/pcf
@@ -69,6 +69,7 @@
 	ethersmc	devi82365 cis
 	etherwavelan	wavelan devi82365 cis pci
 	etheriwl	pci wifi
+	etherrt2860	pci wifi
 	ethermedium
 	pcmciamodem
 	netdevmedium
--