xref: /minix3/minix/drivers/net/atl2/atl2.c (revision f7df02e7476731c31f12548e38bcadbaf0233f6a)
1433d6423SLionel Sambuc /* Attansic/Atheros L2 FastEthernet driver, by D.C. van Moolenbroek */
21539606dSDavid van Moolenbroek /*
31539606dSDavid van Moolenbroek  * No documentation is available for this card.  The FreeBSD driver is based
4433d6423SLionel Sambuc  * heavily on the official Linux driver; this driver is based heavily on both.
5433d6423SLionel Sambuc  */
6433d6423SLionel Sambuc 
7433d6423SLionel Sambuc #include <minix/drivers.h>
8433d6423SLionel Sambuc #include <minix/netdriver.h>
9433d6423SLionel Sambuc 
10433d6423SLionel Sambuc #include <machine/pci.h>
11b80fc5beSDavid van Moolenbroek #include <sys/mman.h>
12433d6423SLionel Sambuc #include <assert.h>
13433d6423SLionel Sambuc 
14433d6423SLionel Sambuc #include "atl2.h"
15433d6423SLionel Sambuc 
16433d6423SLionel Sambuc #define VERBOSE		0	/* Verbose debugging output */
17433d6423SLionel Sambuc 
18433d6423SLionel Sambuc #if VERBOSE
19433d6423SLionel Sambuc #define ATL2_DEBUG(x) printf x
20433d6423SLionel Sambuc #else
21433d6423SLionel Sambuc #define ATL2_DEBUG(x)
22433d6423SLionel Sambuc #endif
23433d6423SLionel Sambuc 
24433d6423SLionel Sambuc typedef struct {
251539606dSDavid van Moolenbroek 	uint32_t hdr;
261539606dSDavid van Moolenbroek 	uint32_t vtag;
271539606dSDavid van Moolenbroek 	uint8_t data[ATL2_RXD_SIZE - sizeof(uint32_t) * 2];
28433d6423SLionel Sambuc } rxd_t;
29433d6423SLionel Sambuc 
30433d6423SLionel Sambuc static struct {
31433d6423SLionel Sambuc 	int devind;		/* PCI device index */
32433d6423SLionel Sambuc 	int irq;		/* IRQ number */
33433d6423SLionel Sambuc 	int hook_id;		/* IRQ hook ID */
34b80fc5beSDavid van Moolenbroek 	uint8_t *base;		/* base address of memory-mapped registers */
351539606dSDavid van Moolenbroek 	uint32_t size;		/* size of memory-mapped area */
361539606dSDavid van Moolenbroek 	uint32_t hwaddr[2];	/* MAC address, in register representation */
37433d6423SLionel Sambuc 
381539606dSDavid van Moolenbroek 	uint8_t *txd_base;	/* local address of TxD ring buffer base */
391539606dSDavid van Moolenbroek 	uint32_t *txs_base;	/* local address of TxS ring buffer base */
401539606dSDavid van Moolenbroek 	uint8_t *rxd_base_u;	/* unaligned base address of RxD ring buffer */
41433d6423SLionel Sambuc 	rxd_t *rxd_base; 	/* local address of RxD ring buffer base */
42433d6423SLionel Sambuc 
43433d6423SLionel Sambuc 	int rxd_align;		/* alignment offset of RxD ring buffer */
44433d6423SLionel Sambuc 
45433d6423SLionel Sambuc 	vir_bytes txd_phys;	/* physical address of TxD ring buffer */
46433d6423SLionel Sambuc 	vir_bytes txs_phys;	/* physical address of TxS ring buffer */
47433d6423SLionel Sambuc 	vir_bytes rxd_phys;	/* physical address of RxD ring buffer */
48433d6423SLionel Sambuc 
49433d6423SLionel Sambuc 	int txd_tail;		/* tail index into TxD, in bytes */
50433d6423SLionel Sambuc 	int txd_num;		/* head-tail offset into TxD, in bytes */
51433d6423SLionel Sambuc 	int txs_tail;		/* tail index into TxS, in elements */
52433d6423SLionel Sambuc 	int txs_num;		/* head-tail offset into TxS, in elements */
53433d6423SLionel Sambuc 	int rxd_tail;		/* tail index into RxD, in elements */
54433d6423SLionel Sambuc 
55b80fc5beSDavid van Moolenbroek 	int rx_avail;		/* is there a packet available for receipt? */
56433d6423SLionel Sambuc } state;
57433d6423SLionel Sambuc 
581539606dSDavid van Moolenbroek #define ATL2_READ_U8(off) (*(volatile uint8_t *)(state.base + (off)))
591539606dSDavid van Moolenbroek #define ATL2_READ_U16(off) (*(volatile uint16_t *)(state.base + (off)))
601539606dSDavid van Moolenbroek #define ATL2_READ_U32(off) (*(volatile uint32_t *)(state.base + (off)))
611539606dSDavid van Moolenbroek #define ATL2_WRITE_U8(off, val) \
621539606dSDavid van Moolenbroek 	*(volatile uint8_t *)(state.base + (off)) = (val)
631539606dSDavid van Moolenbroek #define ATL2_WRITE_U16(off, val) \
641539606dSDavid van Moolenbroek 	*(volatile uint16_t *)(state.base + (off)) = (val)
651539606dSDavid van Moolenbroek #define ATL2_WRITE_U32(off, val) \
661539606dSDavid van Moolenbroek 	*(volatile uint32_t *)(state.base + (off)) = (val)
67433d6423SLionel Sambuc 
68433d6423SLionel Sambuc #define ATL2_ALIGN_32(n) (((n) + 3) & ~3)
69433d6423SLionel Sambuc 
70*f7df02e7SDavid van Moolenbroek static int atl2_init(unsigned int, netdriver_addr_t *, uint32_t *,
71*f7df02e7SDavid van Moolenbroek 	unsigned int *);
72b80fc5beSDavid van Moolenbroek static void atl2_stop(void);
73*f7df02e7SDavid van Moolenbroek static void atl2_set_mode(unsigned int, const netdriver_addr_t *,
74*f7df02e7SDavid van Moolenbroek 	unsigned int);
75*f7df02e7SDavid van Moolenbroek static int atl2_send(struct netdriver_data *, size_t);
76*f7df02e7SDavid van Moolenbroek static ssize_t atl2_recv(struct netdriver_data *, size_t);
77b80fc5beSDavid van Moolenbroek static void atl2_intr(unsigned int mask);
78b80fc5beSDavid van Moolenbroek 
79b80fc5beSDavid van Moolenbroek static const struct netdriver atl2_table = {
80*f7df02e7SDavid van Moolenbroek 	.ndr_name	= "lii",
81b80fc5beSDavid van Moolenbroek 	.ndr_init	= atl2_init,
82b80fc5beSDavid van Moolenbroek 	.ndr_stop	= atl2_stop,
83*f7df02e7SDavid van Moolenbroek 	.ndr_set_mode	= atl2_set_mode,
84b80fc5beSDavid van Moolenbroek 	.ndr_recv	= atl2_recv,
85b80fc5beSDavid van Moolenbroek 	.ndr_send	= atl2_send,
86b80fc5beSDavid van Moolenbroek 	.ndr_intr	= atl2_intr,
87b80fc5beSDavid van Moolenbroek };
88433d6423SLionel Sambuc 
891539606dSDavid van Moolenbroek /*
901539606dSDavid van Moolenbroek  * Read a value from the VPD register area.
91433d6423SLionel Sambuc  */
921539606dSDavid van Moolenbroek static int
atl2_read_vpd(int index,uint32_t * res)931539606dSDavid van Moolenbroek atl2_read_vpd(int index, uint32_t * res)
941539606dSDavid van Moolenbroek {
951539606dSDavid van Moolenbroek 	uint32_t off, val;
96433d6423SLionel Sambuc 	int i;
97433d6423SLionel Sambuc 
98433d6423SLionel Sambuc 	ATL2_WRITE_U32(ATL2_VPD_DATA_REG, 0);
99433d6423SLionel Sambuc 
1001539606dSDavid van Moolenbroek 	off = ATL2_VPD_REGBASE + index * sizeof(uint32_t);
101433d6423SLionel Sambuc 
102433d6423SLionel Sambuc 	ATL2_WRITE_U32(ATL2_VPD_CAP_REG,
103433d6423SLionel Sambuc 	    (off << ATL2_VPD_CAP_ADDR_SHIFT) & ATL2_VPD_CAP_ADDR_MASK);
104433d6423SLionel Sambuc 
105433d6423SLionel Sambuc 	for (i = 0; i < ATL2_VPD_NTRIES; i++) {
106433d6423SLionel Sambuc 		micro_delay(ATL2_VPD_DELAY);
107433d6423SLionel Sambuc 
108433d6423SLionel Sambuc 		val = ATL2_READ_U32(ATL2_VPD_CAP_REG);
109433d6423SLionel Sambuc 		if (val & ATL2_VPD_CAP_DONE)
110433d6423SLionel Sambuc 			break;
111433d6423SLionel Sambuc 	}
112433d6423SLionel Sambuc 
113433d6423SLionel Sambuc 	if (i == ATL2_VPD_NTRIES) {
114*f7df02e7SDavid van Moolenbroek 		printf("%s: timeout reading EEPROM register %d\n",
115*f7df02e7SDavid van Moolenbroek 		    netdriver_name(), index);
116433d6423SLionel Sambuc 		return FALSE;
117433d6423SLionel Sambuc 	}
118433d6423SLionel Sambuc 
119433d6423SLionel Sambuc 	*res = ATL2_READ_U32(ATL2_VPD_DATA_REG);
120433d6423SLionel Sambuc 	return TRUE;
121433d6423SLionel Sambuc }
122433d6423SLionel Sambuc 
1231539606dSDavid van Moolenbroek /*
1241539606dSDavid van Moolenbroek  * Read the MAC address from the EEPROM, using the Vital Product Data register
1251539606dSDavid van Moolenbroek  * interface.
126433d6423SLionel Sambuc  */
1271539606dSDavid van Moolenbroek static int
atl2_get_vpd_hwaddr(void)1281539606dSDavid van Moolenbroek atl2_get_vpd_hwaddr(void)
1291539606dSDavid van Moolenbroek {
1301539606dSDavid van Moolenbroek 	uint32_t key, val;
131433d6423SLionel Sambuc 	int i, n, found[2];
132433d6423SLionel Sambuc 
133433d6423SLionel Sambuc 	/* No idea, copied from FreeBSD which copied it from Linux. */
134433d6423SLionel Sambuc 	val = ATL2_READ_U32(ATL2_SPICTL_REG);
135433d6423SLionel Sambuc 	if (val & ATL2_SPICTL_VPD_EN) {
136433d6423SLionel Sambuc 		val &= ~ATL2_SPICTL_VPD_EN;
137433d6423SLionel Sambuc 		ATL2_WRITE_U32(ATL2_SPICTL_REG, val);
138433d6423SLionel Sambuc 	}
139433d6423SLionel Sambuc 
140433d6423SLionel Sambuc 	/* Is VPD supported? */
141433d6423SLionel Sambuc #ifdef PCI_CAP_VPD	/* FIXME: just a guess at the future name */
142433d6423SLionel Sambuc 	if (!pci_find_cap(state.devind, PCI_CAP_VPD, &n))
143433d6423SLionel Sambuc 		return FALSE;
144433d6423SLionel Sambuc #endif
145433d6423SLionel Sambuc 
1461539606dSDavid van Moolenbroek 	/*
1471539606dSDavid van Moolenbroek 	 * Read out the set of key/value pairs.  Look for the two parts that
148433d6423SLionel Sambuc 	 * make up the MAC address.
149433d6423SLionel Sambuc 	 */
150433d6423SLionel Sambuc 	found[0] = found[1] = FALSE;
151433d6423SLionel Sambuc 	for (i = 0; i < ATL2_VPD_NREGS; i += 2) {
152433d6423SLionel Sambuc 		if (!atl2_read_vpd(i, &key))
153433d6423SLionel Sambuc 			break;
154433d6423SLionel Sambuc 
155433d6423SLionel Sambuc 		if ((key & ATL2_VPD_SIG_MASK) != ATL2_VPD_SIG)
156433d6423SLionel Sambuc 			break;
157433d6423SLionel Sambuc 
158433d6423SLionel Sambuc 		key >>= ATL2_VPD_REG_SHIFT;
159433d6423SLionel Sambuc 
160433d6423SLionel Sambuc 		if (key != ATL2_HWADDR0_REG && key != ATL2_HWADDR1_REG)
161433d6423SLionel Sambuc 			continue;
162433d6423SLionel Sambuc 
163433d6423SLionel Sambuc 		if (!atl2_read_vpd(i + 1, &val))
164433d6423SLionel Sambuc 			break;
165433d6423SLionel Sambuc 
166433d6423SLionel Sambuc 		n = (key == ATL2_HWADDR1_REG);
167433d6423SLionel Sambuc 		state.hwaddr[n] = val;
168433d6423SLionel Sambuc 		found[n] = TRUE;
169433d6423SLionel Sambuc 
170433d6423SLionel Sambuc 		if (found[1 - n]) break;
171433d6423SLionel Sambuc 	}
172433d6423SLionel Sambuc 
173433d6423SLionel Sambuc 	return found[0] && found[1];
174433d6423SLionel Sambuc }
175433d6423SLionel Sambuc 
1761539606dSDavid van Moolenbroek /*
1771539606dSDavid van Moolenbroek  * Get the MAC address of the card.  First try the EEPROM; if that fails, just
1781539606dSDavid van Moolenbroek  * use whatever the card was already set to.
179433d6423SLionel Sambuc  */
1801539606dSDavid van Moolenbroek static void
atl2_get_hwaddr(netdriver_addr_t * addr)181*f7df02e7SDavid van Moolenbroek atl2_get_hwaddr(netdriver_addr_t * addr)
1821539606dSDavid van Moolenbroek {
183433d6423SLionel Sambuc 
184433d6423SLionel Sambuc 	if (!atl2_get_vpd_hwaddr()) {
185*f7df02e7SDavid van Moolenbroek 		printf("%s: unable to read from VPD\n", netdriver_name());
186433d6423SLionel Sambuc 
187433d6423SLionel Sambuc 		state.hwaddr[0] = ATL2_READ_U32(ATL2_HWADDR0_REG);
188433d6423SLionel Sambuc 		state.hwaddr[1] = ATL2_READ_U32(ATL2_HWADDR1_REG) & 0xffff;
189433d6423SLionel Sambuc 	}
190433d6423SLionel Sambuc 
191*f7df02e7SDavid van Moolenbroek 	ATL2_DEBUG(("%s: MAC address %04x%08x\n",
192*f7df02e7SDavid van Moolenbroek 	    netdriver_name(), state.hwaddr[1], state.hwaddr[0]));
193b80fc5beSDavid van Moolenbroek 
194*f7df02e7SDavid van Moolenbroek 	addr->na_addr[0] = state.hwaddr[1] >> 8;
195*f7df02e7SDavid van Moolenbroek 	addr->na_addr[1] = state.hwaddr[1] & 0xff;
196*f7df02e7SDavid van Moolenbroek 	addr->na_addr[2] = state.hwaddr[0] >> 24;
197*f7df02e7SDavid van Moolenbroek 	addr->na_addr[3] = (state.hwaddr[0] >> 16) & 0xff;
198*f7df02e7SDavid van Moolenbroek 	addr->na_addr[4] = (state.hwaddr[0] >> 8) & 0xff;
199*f7df02e7SDavid van Moolenbroek 	addr->na_addr[5] = state.hwaddr[0] & 0xff;
200433d6423SLionel Sambuc }
201433d6423SLionel Sambuc 
202*f7df02e7SDavid van Moolenbroek #if 0 /* TODO: link status */
2031539606dSDavid van Moolenbroek /*
2041539606dSDavid van Moolenbroek  * Read a MII PHY register using MDIO.
205433d6423SLionel Sambuc  */
2061539606dSDavid van Moolenbroek static int
2071539606dSDavid van Moolenbroek atl2_read_mdio(int addr, uint16_t * res)
2081539606dSDavid van Moolenbroek {
2091539606dSDavid van Moolenbroek 	uint32_t rval;
210433d6423SLionel Sambuc 	int i;
211433d6423SLionel Sambuc 
212433d6423SLionel Sambuc 	rval = ((addr << ATL2_MDIO_ADDR_SHIFT) & ATL2_MDIO_ADDR_MASK) |
213433d6423SLionel Sambuc 	    ATL2_MDIO_START | ATL2_MDIO_READ | ATL2_MDIO_SUP_PREAMBLE |
214433d6423SLionel Sambuc 	    ATL2_MDIO_CLK_25_4;
215433d6423SLionel Sambuc 
216433d6423SLionel Sambuc 	ATL2_WRITE_U32(ATL2_MDIO_REG, rval);
217433d6423SLionel Sambuc 
218433d6423SLionel Sambuc 	for (i = 0; i < ATL2_MDIO_NTRIES; i++) {
219433d6423SLionel Sambuc 		micro_delay(ATL2_MDIO_DELAY);
220433d6423SLionel Sambuc 
221433d6423SLionel Sambuc 		rval = ATL2_READ_U32(ATL2_MDIO_REG);
222433d6423SLionel Sambuc 
223433d6423SLionel Sambuc 		if (!(rval & (ATL2_MDIO_START | ATL2_MDIO_BUSY)))
224433d6423SLionel Sambuc 			break;
225433d6423SLionel Sambuc 	}
226433d6423SLionel Sambuc 
227433d6423SLionel Sambuc 	if (i == ATL2_MDIO_NTRIES) return FALSE;
228433d6423SLionel Sambuc 
2291539606dSDavid van Moolenbroek 	*res = (uint16_t)(rval & ATL2_MDIO_DATA_MASK);
230433d6423SLionel Sambuc 	return TRUE;
231433d6423SLionel Sambuc }
232*f7df02e7SDavid van Moolenbroek #endif
233433d6423SLionel Sambuc 
2341539606dSDavid van Moolenbroek /*
2351539606dSDavid van Moolenbroek  * Allocate DMA ring buffers.
236433d6423SLionel Sambuc  */
2371539606dSDavid van Moolenbroek static int
atl2_alloc_dma(void)2381539606dSDavid van Moolenbroek atl2_alloc_dma(void)
2391539606dSDavid van Moolenbroek {
240433d6423SLionel Sambuc 
2411539606dSDavid van Moolenbroek 	state.txd_base = alloc_contig(ATL2_TXD_BUFSIZE, AC_ALIGN4K,
2421539606dSDavid van Moolenbroek 	    &state.txd_phys);
2431539606dSDavid van Moolenbroek 	state.txs_base = alloc_contig(ATL2_TXS_COUNT * sizeof(uint32_t),
244433d6423SLionel Sambuc 	    AC_ALIGN4K, &state.txs_phys);
245433d6423SLionel Sambuc 
2461539606dSDavid van Moolenbroek 	/*
2471539606dSDavid van Moolenbroek 	 * The data buffer in each RxD descriptor must be 128-byte aligned.
248433d6423SLionel Sambuc 	 * The two Tx buffers merely require a 4-byte start alignment.
249433d6423SLionel Sambuc 	 */
250433d6423SLionel Sambuc 	state.rxd_align = 128 - offsetof(rxd_t, data);
2511539606dSDavid van Moolenbroek 	state.rxd_base_u = alloc_contig(state.rxd_align +
2521539606dSDavid van Moolenbroek 	    ATL2_RXD_COUNT * ATL2_RXD_SIZE, AC_ALIGN4K, &state.rxd_phys);
253433d6423SLionel Sambuc 
254433d6423SLionel Sambuc 	/* Unlike mmap, alloc_contig returns NULL on failure. */
255433d6423SLionel Sambuc 	if (!state.txd_base || !state.txs_base || !state.rxd_base_u)
256433d6423SLionel Sambuc 		return ENOMEM;
257433d6423SLionel Sambuc 
258433d6423SLionel Sambuc 	state.rxd_base = (rxd_t *)(state.rxd_base_u + state.rxd_align);
259433d6423SLionel Sambuc 	state.rxd_phys += state.rxd_align;
260433d6423SLionel Sambuc 
261433d6423SLionel Sambuc 	/* Zero out just in case. */
262433d6423SLionel Sambuc 	memset(state.txd_base, 0, ATL2_TXD_BUFSIZE);
2631539606dSDavid van Moolenbroek 	memset(state.txs_base, 0, ATL2_TXS_COUNT * sizeof(uint32_t));
264433d6423SLionel Sambuc 	memset(state.rxd_base, 0, ATL2_RXD_COUNT * ATL2_RXD_SIZE);
265433d6423SLionel Sambuc 
266433d6423SLionel Sambuc 	return OK;
267433d6423SLionel Sambuc }
268433d6423SLionel Sambuc 
2691539606dSDavid van Moolenbroek /*
2701539606dSDavid van Moolenbroek  * Stop the device.
271433d6423SLionel Sambuc  */
272b80fc5beSDavid van Moolenbroek static void
atl2_stop(void)2731539606dSDavid van Moolenbroek atl2_stop(void)
2741539606dSDavid van Moolenbroek {
2751539606dSDavid van Moolenbroek 	uint32_t val;
276433d6423SLionel Sambuc 	int i;
277433d6423SLionel Sambuc 
278433d6423SLionel Sambuc 	/* Clear and disable interrupts. */
279433d6423SLionel Sambuc 	ATL2_WRITE_U32(ATL2_IMR_REG, 0);
280433d6423SLionel Sambuc 	ATL2_WRITE_U32(ATL2_ISR_REG, 0xffffffff);
281433d6423SLionel Sambuc 
282433d6423SLionel Sambuc 	/* Stop Rx/Tx MACs. */
283433d6423SLionel Sambuc 	val = ATL2_READ_U32(ATL2_MAC_REG);
284433d6423SLionel Sambuc 	if (val & (ATL2_MAC_RX_EN | ATL2_MAC_TX_EN)) {
285433d6423SLionel Sambuc 		val &= ~(ATL2_MAC_RX_EN | ATL2_MAC_TX_EN);
286433d6423SLionel Sambuc 		ATL2_WRITE_U32(ATL2_MAC_REG, val);
287433d6423SLionel Sambuc 	}
288433d6423SLionel Sambuc 
289433d6423SLionel Sambuc 	ATL2_WRITE_U8(ATL2_DMAWRITE_REG, 0);
290433d6423SLionel Sambuc 	ATL2_WRITE_U8(ATL2_DMAREAD_REG, 0);
291433d6423SLionel Sambuc 
292433d6423SLionel Sambuc 	/* Wait until everything is idle. */
293433d6423SLionel Sambuc 	for (i = 0; i < ATL2_IDLE_NTRIES; i++) {
294433d6423SLionel Sambuc 		if (ATL2_READ_U32(ATL2_IDLE_REG) == 0)
295433d6423SLionel Sambuc 			break;
296433d6423SLionel Sambuc 
297433d6423SLionel Sambuc 		micro_delay(ATL2_IDLE_DELAY);
298433d6423SLionel Sambuc 	}
299433d6423SLionel Sambuc 
300b80fc5beSDavid van Moolenbroek 	assert(i < ATL2_IDLE_NTRIES);
301433d6423SLionel Sambuc }
302433d6423SLionel Sambuc 
3031539606dSDavid van Moolenbroek /*
3041539606dSDavid van Moolenbroek  * Reset the device to a known good state.
305433d6423SLionel Sambuc  */
3061539606dSDavid van Moolenbroek static int
atl2_reset(void)3071539606dSDavid van Moolenbroek atl2_reset(void)
3081539606dSDavid van Moolenbroek {
3091539606dSDavid van Moolenbroek 	uint32_t val;
310433d6423SLionel Sambuc 	int i;
311433d6423SLionel Sambuc 
312433d6423SLionel Sambuc 	/* Issue a soft reset, and wait for the device to respond. */
313433d6423SLionel Sambuc 	ATL2_WRITE_U32(ATL2_MASTER_REG, ATL2_MASTER_SOFT_RESET);
314433d6423SLionel Sambuc 
315433d6423SLionel Sambuc 	for (i = 0; i < ATL2_RESET_NTRIES; i++) {
316433d6423SLionel Sambuc 		val = ATL2_READ_U32(ATL2_MASTER_REG);
317433d6423SLionel Sambuc 		if (!(val & ATL2_MASTER_SOFT_RESET))
318433d6423SLionel Sambuc 			break;
319433d6423SLionel Sambuc 
320433d6423SLionel Sambuc 		micro_delay(ATL2_RESET_DELAY);
321433d6423SLionel Sambuc 	}
322433d6423SLionel Sambuc 
323433d6423SLionel Sambuc 	if (i == ATL2_RESET_NTRIES)
324433d6423SLionel Sambuc 		return FALSE;
325433d6423SLionel Sambuc 
326433d6423SLionel Sambuc 	/* Wait until everything is idle. */
327433d6423SLionel Sambuc 	for (i = 0; i < ATL2_IDLE_NTRIES; i++) {
328433d6423SLionel Sambuc 		if (ATL2_READ_U32(ATL2_IDLE_REG) == 0)
329433d6423SLionel Sambuc 			break;
330433d6423SLionel Sambuc 
331433d6423SLionel Sambuc 		micro_delay(ATL2_IDLE_DELAY);
332433d6423SLionel Sambuc 	}
333433d6423SLionel Sambuc 
334433d6423SLionel Sambuc 	return (i < ATL2_IDLE_NTRIES);
335433d6423SLionel Sambuc }
336433d6423SLionel Sambuc 
3371539606dSDavid van Moolenbroek /*
3381539606dSDavid van Moolenbroek  * Reconfigure the device's promiscuity, multicast, and broadcast mode
339433d6423SLionel Sambuc  * settings.
340433d6423SLionel Sambuc  */
3411539606dSDavid van Moolenbroek static void
atl2_set_mode(unsigned int mode,const netdriver_addr_t * mcast_list __unused,unsigned int mcast_count __unused)342*f7df02e7SDavid van Moolenbroek atl2_set_mode(unsigned int mode, const netdriver_addr_t * mcast_list __unused,
343*f7df02e7SDavid van Moolenbroek 	unsigned int mcast_count __unused)
3441539606dSDavid van Moolenbroek {
3451539606dSDavid van Moolenbroek 	uint32_t val;
346433d6423SLionel Sambuc 
347433d6423SLionel Sambuc 	val = ATL2_READ_U32(ATL2_MAC_REG);
348433d6423SLionel Sambuc 	val &= ~(ATL2_MAC_PROMISC_EN | ATL2_MAC_MCAST_EN | ATL2_MAC_BCAST_EN);
349433d6423SLionel Sambuc 
350*f7df02e7SDavid van Moolenbroek 	if (mode & NDEV_MODE_PROMISC)
351433d6423SLionel Sambuc 		val |= ATL2_MAC_PROMISC_EN;
352*f7df02e7SDavid van Moolenbroek 	if (mode & (NDEV_MODE_MCAST_LIST | NDEV_MODE_MCAST_ALL))
353433d6423SLionel Sambuc 		val |= ATL2_MAC_MCAST_EN;
354*f7df02e7SDavid van Moolenbroek 	if (mode & NDEV_MODE_BCAST)
355433d6423SLionel Sambuc 		val |= ATL2_MAC_BCAST_EN;
356433d6423SLionel Sambuc 
357433d6423SLionel Sambuc 	ATL2_WRITE_U32(ATL2_MAC_REG, val);
358433d6423SLionel Sambuc }
359433d6423SLionel Sambuc 
3601539606dSDavid van Moolenbroek /*
3611539606dSDavid van Moolenbroek  * Set up the device for normal operation.
362433d6423SLionel Sambuc  */
3631539606dSDavid van Moolenbroek static int
atl2_setup(void)3641539606dSDavid van Moolenbroek atl2_setup(void)
3651539606dSDavid van Moolenbroek {
3661539606dSDavid van Moolenbroek 	uint32_t val;
367433d6423SLionel Sambuc 
368433d6423SLionel Sambuc 	atl2_stop();
369433d6423SLionel Sambuc 
370433d6423SLionel Sambuc 	if (!atl2_reset())
371433d6423SLionel Sambuc 		return FALSE;
372433d6423SLionel Sambuc 
3731539606dSDavid van Moolenbroek 	/* Initialize PCIe module.  Magic. */
374433d6423SLionel Sambuc 	ATL2_WRITE_U32(ATL2_LTSSM_TESTMODE_REG, ATL2_LTSSM_TESTMODE_DEFAULT);
375433d6423SLionel Sambuc 	ATL2_WRITE_U32(ATL2_DLL_TX_CTRL_REG, ATL2_DLL_TX_CTRL_DEFAULT);
376433d6423SLionel Sambuc 
377433d6423SLionel Sambuc 	/* Enable PHY. */
378433d6423SLionel Sambuc 	ATL2_WRITE_U32(ATL2_PHY_ENABLE_REG, ATL2_PHY_ENABLE);
379433d6423SLionel Sambuc 	micro_delay(1000);
380433d6423SLionel Sambuc 
381433d6423SLionel Sambuc 	/* Clear and disable interrupts. */
382433d6423SLionel Sambuc 	ATL2_WRITE_U32(ATL2_ISR_REG, 0xffffffff);
383433d6423SLionel Sambuc 
384433d6423SLionel Sambuc 	/* Set the MAC address. */
385433d6423SLionel Sambuc 	ATL2_WRITE_U32(ATL2_HWADDR0_REG, state.hwaddr[0]);
386433d6423SLionel Sambuc 	ATL2_WRITE_U32(ATL2_HWADDR1_REG, state.hwaddr[1]);
387433d6423SLionel Sambuc 
388433d6423SLionel Sambuc 	/* Initialize ring buffer addresses and sizes. */
389433d6423SLionel Sambuc 	ATL2_WRITE_U32(ATL2_DESC_ADDR_HI_REG, 0);	/* no 64 bit */
390433d6423SLionel Sambuc 	ATL2_WRITE_U32(ATL2_TXD_ADDR_LO_REG, state.txd_phys);
391433d6423SLionel Sambuc 	ATL2_WRITE_U32(ATL2_TXS_ADDR_LO_REG, state.txs_phys);
392433d6423SLionel Sambuc 	ATL2_WRITE_U32(ATL2_RXD_ADDR_LO_REG, state.rxd_phys);
393433d6423SLionel Sambuc 
394433d6423SLionel Sambuc 	ATL2_WRITE_U16(ATL2_RXD_COUNT_REG, ATL2_RXD_COUNT);
3951539606dSDavid van Moolenbroek 	ATL2_WRITE_U16(ATL2_TXD_BUFSIZE_REG,
3961539606dSDavid van Moolenbroek 	    ATL2_TXD_BUFSIZE / sizeof(uint32_t));
397433d6423SLionel Sambuc 	ATL2_WRITE_U16(ATL2_TXS_COUNT_REG, ATL2_TXS_COUNT);
398433d6423SLionel Sambuc 
399433d6423SLionel Sambuc 	/* A whole lot of other initialization copied from Linux/FreeBSD. */
400433d6423SLionel Sambuc 	ATL2_WRITE_U32(ATL2_IFG_REG, ATL2_IFG_DEFAULT);
401433d6423SLionel Sambuc 
402433d6423SLionel Sambuc 	ATL2_WRITE_U32(ATL2_HDPX_REG, ATL2_HDPX_DEFAULT);
403433d6423SLionel Sambuc 
404433d6423SLionel Sambuc 	ATL2_WRITE_U16(ATL2_IMT_REG, ATL2_IMT_DEFAULT);
405433d6423SLionel Sambuc 	val = ATL2_READ_U32(ATL2_MASTER_REG);
406433d6423SLionel Sambuc 	ATL2_WRITE_U32(ATL2_MASTER_REG, val | ATL2_MASTER_IMT_EN);
407433d6423SLionel Sambuc 
408433d6423SLionel Sambuc 	ATL2_WRITE_U16(ATL2_ICT_REG, ATL2_ICT_DEFAULT);
409433d6423SLionel Sambuc 
410433d6423SLionel Sambuc 	ATL2_WRITE_U32(ATL2_CUT_THRESH_REG, ATL2_CUT_THRESH_DEFAULT);
411433d6423SLionel Sambuc 
412433d6423SLionel Sambuc 	ATL2_WRITE_U16(ATL2_FLOW_THRESH_HI_REG, (ATL2_RXD_COUNT / 8) * 7);
413433d6423SLionel Sambuc 	ATL2_WRITE_U16(ATL2_FLOW_THRESH_LO_REG, ATL2_RXD_COUNT / 12);
414433d6423SLionel Sambuc 
415433d6423SLionel Sambuc 	/* Set MTU. */
416433d6423SLionel Sambuc 	ATL2_WRITE_U16(ATL2_MTU_REG, ATL2_MTU_DEFAULT);
417433d6423SLionel Sambuc 
418433d6423SLionel Sambuc 	/* Reset descriptors, and enable DMA. */
419433d6423SLionel Sambuc 	state.txd_tail = state.txs_tail = state.rxd_tail = 0;
420433d6423SLionel Sambuc 	state.txd_num = state.txs_num = 0;
421b80fc5beSDavid van Moolenbroek 	state.rx_avail = FALSE;
422433d6423SLionel Sambuc 	ATL2_WRITE_U16(ATL2_TXD_IDX_REG, 0);
423433d6423SLionel Sambuc 	ATL2_WRITE_U16(ATL2_RXD_IDX_REG, 0);
424433d6423SLionel Sambuc 
425433d6423SLionel Sambuc 	ATL2_WRITE_U8(ATL2_DMAREAD_REG, ATL2_DMAREAD_EN);
426433d6423SLionel Sambuc 	ATL2_WRITE_U8(ATL2_DMAWRITE_REG, ATL2_DMAWRITE_EN);
427433d6423SLionel Sambuc 
428433d6423SLionel Sambuc 	/* Did everything go alright? */
429433d6423SLionel Sambuc 	val = ATL2_READ_U32(ATL2_ISR_REG);
430433d6423SLionel Sambuc 	if (val & ATL2_ISR_PHY_LINKDOWN) {
431*f7df02e7SDavid van Moolenbroek 		printf("%s: initialization failed\n", netdriver_name());
432433d6423SLionel Sambuc 		return FALSE;
433433d6423SLionel Sambuc 	}
434433d6423SLionel Sambuc 
435433d6423SLionel Sambuc 	/* Clear interrupt status. */
436433d6423SLionel Sambuc 	ATL2_WRITE_U32(ATL2_ISR_REG, 0x3fffffff);
437433d6423SLionel Sambuc 	ATL2_WRITE_U32(ATL2_ISR_REG, 0);
438433d6423SLionel Sambuc 
439433d6423SLionel Sambuc 	/* Enable interrupts. */
440433d6423SLionel Sambuc 	ATL2_WRITE_U32(ATL2_IMR_REG, ATL2_IMR_DEFAULT);
441433d6423SLionel Sambuc 
442433d6423SLionel Sambuc 	/* Configure MAC. */
443433d6423SLionel Sambuc 	ATL2_WRITE_U32(ATL2_MAC_REG, ATL2_MAC_DEFAULT);
444433d6423SLionel Sambuc 
445*f7df02e7SDavid van Moolenbroek 	/* TODO: multicast lists. */
446433d6423SLionel Sambuc 	ATL2_WRITE_U32(ATL2_MHT0_REG, 0xffffffff);
447433d6423SLionel Sambuc 	ATL2_WRITE_U32(ATL2_MHT1_REG, 0xffffffff);
448433d6423SLionel Sambuc 
449433d6423SLionel Sambuc 	/* Enable Tx/Rx. */
450433d6423SLionel Sambuc 	val = ATL2_READ_U32(ATL2_MAC_REG);
451433d6423SLionel Sambuc 	ATL2_WRITE_U32(ATL2_MAC_REG, val | ATL2_MAC_TX_EN | ATL2_MAC_RX_EN);
452433d6423SLionel Sambuc 
453433d6423SLionel Sambuc 	return TRUE;
454433d6423SLionel Sambuc }
455433d6423SLionel Sambuc 
4561539606dSDavid van Moolenbroek /*
4571539606dSDavid van Moolenbroek  * Find a matching PCI device.
458433d6423SLionel Sambuc  */
4591539606dSDavid van Moolenbroek static int
atl2_probe(int skip)4601539606dSDavid van Moolenbroek atl2_probe(int skip)
4611539606dSDavid van Moolenbroek {
4621539606dSDavid van Moolenbroek 	uint16_t vid, did;
463433d6423SLionel Sambuc #if VERBOSE
464*f7df02e7SDavid van Moolenbroek 	const char *dname;
465433d6423SLionel Sambuc #endif
466433d6423SLionel Sambuc 	int r, devind;
467433d6423SLionel Sambuc 
468433d6423SLionel Sambuc 	pci_init();
469433d6423SLionel Sambuc 
470433d6423SLionel Sambuc 	r = pci_first_dev(&devind, &vid, &did);
471433d6423SLionel Sambuc 	if (r <= 0)
472433d6423SLionel Sambuc 		return -1;
473433d6423SLionel Sambuc 
474433d6423SLionel Sambuc 	while (skip--) {
475433d6423SLionel Sambuc 		r = pci_next_dev(&devind, &vid, &did);
476433d6423SLionel Sambuc 		if (r <= 0)
477433d6423SLionel Sambuc 			return -1;
478433d6423SLionel Sambuc 	}
479433d6423SLionel Sambuc 
480433d6423SLionel Sambuc #if VERBOSE
481433d6423SLionel Sambuc 	dname = pci_dev_name(vid, did);
482*f7df02e7SDavid van Moolenbroek 	ATL2_DEBUG(("%s: found %s (%x/%x) at %s\n", netdriver_name(),
4831539606dSDavid van Moolenbroek 	    dname ? dname : "<unknown>", vid, did, pci_slot_name(devind)));
484433d6423SLionel Sambuc #endif
485433d6423SLionel Sambuc 
486433d6423SLionel Sambuc 	pci_reserve(devind);
487433d6423SLionel Sambuc 
488433d6423SLionel Sambuc 	return devind;
489433d6423SLionel Sambuc }
490433d6423SLionel Sambuc 
4911539606dSDavid van Moolenbroek /*
4921539606dSDavid van Moolenbroek  * Initialize the device.
493433d6423SLionel Sambuc  */
4941539606dSDavid van Moolenbroek static void
atl2_init_hw(int devind,netdriver_addr_t * addr)495*f7df02e7SDavid van Moolenbroek atl2_init_hw(int devind, netdriver_addr_t * addr)
4961539606dSDavid van Moolenbroek {
4971539606dSDavid van Moolenbroek 	uint32_t bar;
498433d6423SLionel Sambuc 	int r, flag;
499433d6423SLionel Sambuc 
500433d6423SLionel Sambuc 	/* Initialize global state. */
501433d6423SLionel Sambuc 	state.devind = devind;
502433d6423SLionel Sambuc 
503433d6423SLionel Sambuc 	if ((r = pci_get_bar(devind, PCI_BAR, &bar, &state.size, &flag)) != OK)
504433d6423SLionel Sambuc 		panic("unable to retrieve bar: %d", r);
505433d6423SLionel Sambuc 
506433d6423SLionel Sambuc 	if (state.size < ATL2_MIN_MMAP_SIZE || flag)
507433d6423SLionel Sambuc 		panic("invalid register bar");
508433d6423SLionel Sambuc 
509433d6423SLionel Sambuc 	state.base = vm_map_phys(SELF, (void *)bar, state.size);
510433d6423SLionel Sambuc 	if (state.base == MAP_FAILED)
511433d6423SLionel Sambuc 		panic("unable to map in registers");
512433d6423SLionel Sambuc 
513433d6423SLionel Sambuc 	if ((r = atl2_alloc_dma()) != OK)
514433d6423SLionel Sambuc 		panic("unable to allocate DMA buffers: %d", r);
515433d6423SLionel Sambuc 
516433d6423SLionel Sambuc 	state.irq = pci_attr_r8(devind, PCI_ILR);
517433d6423SLionel Sambuc 	state.hook_id = 0;
518433d6423SLionel Sambuc 
519433d6423SLionel Sambuc 	if ((r = sys_irqsetpolicy(state.irq, 0, &state.hook_id)) != OK)
520433d6423SLionel Sambuc 		panic("unable to register IRQ: %d", r);
521433d6423SLionel Sambuc 
522433d6423SLionel Sambuc 	if (!atl2_reset())
523433d6423SLionel Sambuc 		panic("unable to reset hardware");
524433d6423SLionel Sambuc 
525433d6423SLionel Sambuc 	if ((r = sys_irqenable(&state.hook_id)) != OK)
526433d6423SLionel Sambuc 		panic("unable to enable IRQ: %d", r);
527433d6423SLionel Sambuc 
528b80fc5beSDavid van Moolenbroek 	atl2_get_hwaddr(addr);
529433d6423SLionel Sambuc 
530433d6423SLionel Sambuc 	atl2_setup();
531433d6423SLionel Sambuc }
532433d6423SLionel Sambuc 
5331539606dSDavid van Moolenbroek /*
5341539606dSDavid van Moolenbroek  * Update statistics for packet transmission.
535433d6423SLionel Sambuc  */
5361539606dSDavid van Moolenbroek static void
atl2_tx_stat(uint32_t stat)5371539606dSDavid van Moolenbroek atl2_tx_stat(uint32_t stat)
5381539606dSDavid van Moolenbroek {
539433d6423SLionel Sambuc 
540433d6423SLionel Sambuc 	if (stat & ATL2_TXS_SUCCESS)
541*f7df02e7SDavid van Moolenbroek 		return;
542433d6423SLionel Sambuc 
543*f7df02e7SDavid van Moolenbroek 	if (stat & (ATL2_TXS_SINGLECOL | ATL2_TXS_MULTICOL | ATL2_TXS_LATECOL))
544*f7df02e7SDavid van Moolenbroek 		netdriver_stat_coll(1);
545*f7df02e7SDavid van Moolenbroek 	else
546*f7df02e7SDavid van Moolenbroek 		netdriver_stat_oerror(1);
547433d6423SLionel Sambuc }
548433d6423SLionel Sambuc 
5491539606dSDavid van Moolenbroek /*
5501539606dSDavid van Moolenbroek  * Update statistics for packet receipt.
551433d6423SLionel Sambuc  */
5521539606dSDavid van Moolenbroek static void
atl2_rx_stat(uint32_t stat)5531539606dSDavid van Moolenbroek atl2_rx_stat(uint32_t stat)
5541539606dSDavid van Moolenbroek {
555433d6423SLionel Sambuc 
556*f7df02e7SDavid van Moolenbroek 	if (!(stat & ATL2_RXD_SUCCESS))
557*f7df02e7SDavid van Moolenbroek 		netdriver_stat_ierror(1);
558433d6423SLionel Sambuc }
559433d6423SLionel Sambuc 
5601539606dSDavid van Moolenbroek /*
5611539606dSDavid van Moolenbroek  * Advance the TxD/TxS tails by as many sent packets as found.
562433d6423SLionel Sambuc  */
5631539606dSDavid van Moolenbroek static int
atl2_tx_advance(void)5641539606dSDavid van Moolenbroek atl2_tx_advance(void)
5651539606dSDavid van Moolenbroek {
5661539606dSDavid van Moolenbroek 	uint32_t stat, size, dsize;
567433d6423SLionel Sambuc 	int advanced;
568433d6423SLionel Sambuc 
569433d6423SLionel Sambuc 	advanced = FALSE;
570433d6423SLionel Sambuc 
571433d6423SLionel Sambuc 	while (state.txs_num > 0) {
572433d6423SLionel Sambuc 		/* Has the tail packet been processed by the driver? */
573433d6423SLionel Sambuc 		stat = state.txs_base[state.txs_tail];
574433d6423SLionel Sambuc 
575433d6423SLionel Sambuc 		if (!(stat & ATL2_TXS_UPDATE))
576433d6423SLionel Sambuc 			break;
577433d6423SLionel Sambuc 
5781539606dSDavid van Moolenbroek 		/*
5791539606dSDavid van Moolenbroek 		 * The packet size from the status must match the packet size
580433d6423SLionel Sambuc 		 * we put in.  If they don't, there's not much we can do..
581433d6423SLionel Sambuc 		 */
582433d6423SLionel Sambuc 		size = stat & ATL2_TXS_SIZE_MASK;
583433d6423SLionel Sambuc 
5841539606dSDavid van Moolenbroek 		assert((uint32_t)state.txd_tail <=
5851539606dSDavid van Moolenbroek 		    ATL2_TXD_BUFSIZE - sizeof(uint32_t));
586b80fc5beSDavid van Moolenbroek 		dsize =
587b80fc5beSDavid van Moolenbroek 		    *(volatile uint32_t *)(state.txd_base + state.txd_tail);
588433d6423SLionel Sambuc 		if (size != dsize)
589*f7df02e7SDavid van Moolenbroek 			printf("%s: TxD/TxS size mismatch (%x vs %x)\n",
590*f7df02e7SDavid van Moolenbroek 			    netdriver_name(), size, dsize);
591433d6423SLionel Sambuc 
592433d6423SLionel Sambuc 		/* Advance tails accordingly. */
5931539606dSDavid van Moolenbroek 		size = sizeof(uint32_t) + ATL2_ALIGN_32(dsize);
5941539606dSDavid van Moolenbroek 		assert((uint32_t)state.txd_num >= size);
595433d6423SLionel Sambuc 		state.txd_tail = (state.txd_tail + size) % ATL2_TXD_BUFSIZE;
596433d6423SLionel Sambuc 		state.txd_num -= size;
597433d6423SLionel Sambuc 
598433d6423SLionel Sambuc 		state.txs_tail = (state.txs_tail + 1) % ATL2_TXS_COUNT;
599433d6423SLionel Sambuc 		state.txs_num--;
600433d6423SLionel Sambuc 
6011539606dSDavid van Moolenbroek 		if (stat & ATL2_TXS_SUCCESS)
602*f7df02e7SDavid van Moolenbroek 			ATL2_DEBUG(("%s: successfully sent packet\n",
603*f7df02e7SDavid van Moolenbroek 			    netdriver_name()));
6041539606dSDavid van Moolenbroek 		else
605*f7df02e7SDavid van Moolenbroek 			ATL2_DEBUG(("%s: failed to send packet\n",
606*f7df02e7SDavid van Moolenbroek 			    netdriver_name()));
607433d6423SLionel Sambuc 
608433d6423SLionel Sambuc 		/* Update statistics. */
609433d6423SLionel Sambuc 		atl2_tx_stat(stat);
610433d6423SLionel Sambuc 
611433d6423SLionel Sambuc 		advanced = TRUE;
612433d6423SLionel Sambuc 	}
613433d6423SLionel Sambuc 
614433d6423SLionel Sambuc 	return advanced;
615433d6423SLionel Sambuc }
616433d6423SLionel Sambuc 
6171539606dSDavid van Moolenbroek /*
6181539606dSDavid van Moolenbroek  * Advance the RxD tail by as many failed receipts as possible, and see if
6191539606dSDavid van Moolenbroek  * there is an actual packet left to receive.  If 'next' is set, the packet at
6201539606dSDavid van Moolenbroek  * the current tail has been processed.
621433d6423SLionel Sambuc  */
6221539606dSDavid van Moolenbroek static void
atl2_rx_advance(int next)6231539606dSDavid van Moolenbroek atl2_rx_advance(int next)
6241539606dSDavid van Moolenbroek {
625433d6423SLionel Sambuc 	int update_tail;
626433d6423SLionel Sambuc 	rxd_t *rxd;
627b80fc5beSDavid van Moolenbroek 	uint32_t hdr;
628b80fc5beSDavid van Moolenbroek 	size_t size;
629433d6423SLionel Sambuc 
630433d6423SLionel Sambuc 	update_tail = FALSE;
631433d6423SLionel Sambuc 
632433d6423SLionel Sambuc 	if (next) {
633433d6423SLionel Sambuc 		state.rxd_tail = (state.rxd_tail + 1) % ATL2_RXD_COUNT;
634433d6423SLionel Sambuc 		update_tail = TRUE;
635433d6423SLionel Sambuc 
636*f7df02e7SDavid van Moolenbroek 		ATL2_DEBUG(("%s: successfully received packet\n",
637*f7df02e7SDavid van Moolenbroek 		    netdriver_name()));
638433d6423SLionel Sambuc 
639b80fc5beSDavid van Moolenbroek 		state.rx_avail = FALSE;
640433d6423SLionel Sambuc 	}
641433d6423SLionel Sambuc 
642b80fc5beSDavid van Moolenbroek 	assert(!state.rx_avail);
643433d6423SLionel Sambuc 
644433d6423SLionel Sambuc 	for (;;) {
645433d6423SLionel Sambuc 		/* Check the RxD tail for updates. */
646433d6423SLionel Sambuc 		rxd = &state.rxd_base[state.rxd_tail];
647433d6423SLionel Sambuc 
648433d6423SLionel Sambuc 		hdr = rxd->hdr;
649433d6423SLionel Sambuc 
650433d6423SLionel Sambuc 		if (!(hdr & ATL2_RXD_UPDATE))
651433d6423SLionel Sambuc 			break;
652433d6423SLionel Sambuc 
6531539606dSDavid van Moolenbroek 		rxd->hdr = hdr & ~ATL2_RXD_UPDATE;
654433d6423SLionel Sambuc 
655433d6423SLionel Sambuc 		/* Update statistics. */
656433d6423SLionel Sambuc 		atl2_rx_stat(hdr);
657433d6423SLionel Sambuc 
6581539606dSDavid van Moolenbroek 		/*
6591539606dSDavid van Moolenbroek 		 * Stop at the first successful receipt.  The packet will be
660433d6423SLionel Sambuc 		 * picked up by Inet later.
661433d6423SLionel Sambuc 		 */
662433d6423SLionel Sambuc 		size = hdr & ATL2_RXD_SIZE_MASK;
663433d6423SLionel Sambuc 
664b80fc5beSDavid van Moolenbroek 		if ((hdr & ATL2_RXD_SUCCESS) &&
665*f7df02e7SDavid van Moolenbroek 		    size >= NDEV_ETH_PACKET_MIN + NDEV_ETH_PACKET_CRC) {
666*f7df02e7SDavid van Moolenbroek 			ATL2_DEBUG(("%s: packet available, size %zu\n",
667*f7df02e7SDavid van Moolenbroek 			    netdriver_name(), size));
668433d6423SLionel Sambuc 
669b80fc5beSDavid van Moolenbroek 			state.rx_avail = TRUE;
670433d6423SLionel Sambuc 			break;
671433d6423SLionel Sambuc 		}
672433d6423SLionel Sambuc 
673*f7df02e7SDavid van Moolenbroek 		ATL2_DEBUG(("%s: packet receipt failed\n", netdriver_name()));
674433d6423SLionel Sambuc 
675433d6423SLionel Sambuc 		/* Advance tail. */
676433d6423SLionel Sambuc 		state.rxd_tail = (state.rxd_tail + 1) % ATL2_RXD_COUNT;
677433d6423SLionel Sambuc 		update_tail = TRUE;
678433d6423SLionel Sambuc 	}
679433d6423SLionel Sambuc 
680433d6423SLionel Sambuc 	/* If new RxD descriptors are now up for reuse, tell the device. */
681433d6423SLionel Sambuc 	if (update_tail) {
682433d6423SLionel Sambuc 		__insn_barrier();
683433d6423SLionel Sambuc 
684433d6423SLionel Sambuc 		ATL2_WRITE_U32(ATL2_RXD_IDX_REG, state.rxd_tail);
685433d6423SLionel Sambuc 	}
686433d6423SLionel Sambuc }
687433d6423SLionel Sambuc 
6881539606dSDavid van Moolenbroek /*
689b80fc5beSDavid van Moolenbroek  * Receive a packet.
690433d6423SLionel Sambuc  */
691b80fc5beSDavid van Moolenbroek static ssize_t
atl2_recv(struct netdriver_data * data,size_t max)692b80fc5beSDavid van Moolenbroek atl2_recv(struct netdriver_data * data, size_t max)
6931539606dSDavid van Moolenbroek {
694433d6423SLionel Sambuc 	rxd_t *rxd;
695b80fc5beSDavid van Moolenbroek 	size_t size;
696433d6423SLionel Sambuc 
697433d6423SLionel Sambuc 	/* Are there any packets available at all? */
698b80fc5beSDavid van Moolenbroek 	if (!state.rx_avail)
699b80fc5beSDavid van Moolenbroek 		return SUSPEND;
700433d6423SLionel Sambuc 
701433d6423SLionel Sambuc 	/* Get the first available packet's size.  Cut off the CRC. */
702433d6423SLionel Sambuc 	rxd = &state.rxd_base[state.rxd_tail];
703433d6423SLionel Sambuc 
704b80fc5beSDavid van Moolenbroek 	size = rxd->hdr & ATL2_RXD_SIZE_MASK;
705*f7df02e7SDavid van Moolenbroek 	size -= NDEV_ETH_PACKET_CRC;
706433d6423SLionel Sambuc 
707*f7df02e7SDavid van Moolenbroek 	ATL2_DEBUG(("%s: receiving packet with length %zu\n",
708*f7df02e7SDavid van Moolenbroek 	    netdriver_name(), size));
709b80fc5beSDavid van Moolenbroek 
710b80fc5beSDavid van Moolenbroek 	/* Truncate large packets. */
711b80fc5beSDavid van Moolenbroek 	if (size > max)
712b80fc5beSDavid van Moolenbroek 		size = max;
713433d6423SLionel Sambuc 
714433d6423SLionel Sambuc 	/* Copy out the packet. */
715b80fc5beSDavid van Moolenbroek 		netdriver_copyout(data, 0, rxd->data, size);
716433d6423SLionel Sambuc 
717433d6423SLionel Sambuc 	/* We are done with this packet.  Move on to the next. */
718433d6423SLionel Sambuc 	atl2_rx_advance(TRUE /*next*/);
719433d6423SLionel Sambuc 
720b80fc5beSDavid van Moolenbroek 	return size;
721433d6423SLionel Sambuc }
722433d6423SLionel Sambuc 
7231539606dSDavid van Moolenbroek /*
724b80fc5beSDavid van Moolenbroek  * Send a packet.
725433d6423SLionel Sambuc  */
726b80fc5beSDavid van Moolenbroek static int
atl2_send(struct netdriver_data * data,size_t size)727b80fc5beSDavid van Moolenbroek atl2_send(struct netdriver_data * data, size_t size)
7281539606dSDavid van Moolenbroek {
729b80fc5beSDavid van Moolenbroek 	size_t pos, chunk;
7301539606dSDavid van Moolenbroek 	uint8_t *sizep;
731433d6423SLionel Sambuc 
7321539606dSDavid van Moolenbroek 	/*
733b80fc5beSDavid van Moolenbroek 	 * If the packet won't fit, bail out.  Keep at least some space between
734b80fc5beSDavid van Moolenbroek 	 * TxD head and tail, as it is not clear whether the device deals well
735b80fc5beSDavid van Moolenbroek 	 * with the case that they collide.
736433d6423SLionel Sambuc 	 */
737433d6423SLionel Sambuc 	if (state.txs_num >= ATL2_TXS_COUNT)
738b80fc5beSDavid van Moolenbroek 		return SUSPEND;
739433d6423SLionel Sambuc 
740b80fc5beSDavid van Moolenbroek 	if (state.txd_num + sizeof(uint32_t) + ATL2_ALIGN_32(size) >=
741b80fc5beSDavid van Moolenbroek 	    ATL2_TXD_BUFSIZE)
742b80fc5beSDavid van Moolenbroek 		return SUSPEND;
743b80fc5beSDavid van Moolenbroek 
744b80fc5beSDavid van Moolenbroek 	/* Copy in the packet. */
745433d6423SLionel Sambuc 	pos = (state.txd_tail + state.txd_num +
7461539606dSDavid van Moolenbroek 	    sizeof(uint32_t)) % ATL2_TXD_BUFSIZE;
747b80fc5beSDavid van Moolenbroek 	chunk = ATL2_TXD_BUFSIZE - pos;
748b80fc5beSDavid van Moolenbroek 	if (size > chunk) {
749b80fc5beSDavid van Moolenbroek 		netdriver_copyin(data, 0, state.txd_base + pos, chunk);
750b80fc5beSDavid van Moolenbroek 		netdriver_copyin(data, chunk, state.txd_base, size - chunk);
751b80fc5beSDavid van Moolenbroek 	} else
752b80fc5beSDavid van Moolenbroek 		netdriver_copyin(data, 0, state.txd_base + pos, size);
753433d6423SLionel Sambuc 
754433d6423SLionel Sambuc 	/* Write the length to the DWORD right before the packet. */
755433d6423SLionel Sambuc 	sizep = state.txd_base +
756433d6423SLionel Sambuc 	    (state.txd_tail + state.txd_num) % ATL2_TXD_BUFSIZE;
757b80fc5beSDavid van Moolenbroek 	*(volatile uint32_t *)sizep = size;
758433d6423SLionel Sambuc 
759433d6423SLionel Sambuc 	/* Update the TxD head. */
760b80fc5beSDavid van Moolenbroek 	state.txd_num += sizeof(uint32_t) + ATL2_ALIGN_32(size);
761b80fc5beSDavid van Moolenbroek 	pos = ATL2_ALIGN_32(pos + size) % ATL2_TXD_BUFSIZE;
762433d6423SLionel Sambuc 	assert((int)pos ==
763433d6423SLionel Sambuc 	    (state.txd_tail + state.txd_num) % ATL2_TXD_BUFSIZE);
764433d6423SLionel Sambuc 
765433d6423SLionel Sambuc 	/* Initialize and update the TxS head. */
766433d6423SLionel Sambuc 	state.txs_base[(state.txs_tail + state.txs_num) % ATL2_TXS_COUNT] = 0;
767433d6423SLionel Sambuc 	state.txs_num++;
768433d6423SLionel Sambuc 
769433d6423SLionel Sambuc 	/* Tell the device about our new position. */
770433d6423SLionel Sambuc 	__insn_barrier();
771433d6423SLionel Sambuc 
7721539606dSDavid van Moolenbroek 	ATL2_WRITE_U32(ATL2_TXD_IDX_REG, pos / sizeof(uint32_t));
773433d6423SLionel Sambuc 
774b80fc5beSDavid van Moolenbroek 	return OK;
775433d6423SLionel Sambuc }
776433d6423SLionel Sambuc 
7771539606dSDavid van Moolenbroek /*
7781539606dSDavid van Moolenbroek  * Process an interrupt.
779433d6423SLionel Sambuc  */
7801539606dSDavid van Moolenbroek static void
atl2_intr(unsigned int __unused mask)781b80fc5beSDavid van Moolenbroek atl2_intr(unsigned int __unused mask)
7821539606dSDavid van Moolenbroek {
7831539606dSDavid van Moolenbroek 	uint32_t val;
784b80fc5beSDavid van Moolenbroek 	int r, try_send, try_recv;
785433d6423SLionel Sambuc 
786433d6423SLionel Sambuc 	/* Clear and disable interrupts. */
787433d6423SLionel Sambuc 	val = ATL2_READ_U32(ATL2_ISR_REG);
788433d6423SLionel Sambuc 
789433d6423SLionel Sambuc 	ATL2_WRITE_U32(ATL2_ISR_REG, val | ATL2_ISR_DISABLE);
790433d6423SLionel Sambuc 
791*f7df02e7SDavid van Moolenbroek 	ATL2_DEBUG(("%s: interrupt (0x%08x)\n", netdriver_name(), val));
792433d6423SLionel Sambuc 
793433d6423SLionel Sambuc 	/* If an error occurred, reset the card. */
794433d6423SLionel Sambuc 	if (val & (ATL2_ISR_DMAR_TIMEOUT | ATL2_ISR_DMAW_TIMEOUT |
7951539606dSDavid van Moolenbroek 	    ATL2_ISR_PHY_LINKDOWN))
796433d6423SLionel Sambuc 		atl2_setup();
797433d6423SLionel Sambuc 
798b80fc5beSDavid van Moolenbroek 	try_send = try_recv = FALSE;
799433d6423SLionel Sambuc 
800433d6423SLionel Sambuc 	/* Process sent data, and possibly send pending data. */
801433d6423SLionel Sambuc 	if (val & ATL2_ISR_TX_EVENT) {
802433d6423SLionel Sambuc 		if (atl2_tx_advance())
803b80fc5beSDavid van Moolenbroek 			try_send = TRUE;
804433d6423SLionel Sambuc 	}
805433d6423SLionel Sambuc 
806433d6423SLionel Sambuc 	/* Receive new data, and possible satisfy a pending receive request. */
807433d6423SLionel Sambuc 	if (val & ATL2_ISR_RX_EVENT) {
808b80fc5beSDavid van Moolenbroek 		if (!state.rx_avail) {
809433d6423SLionel Sambuc 			atl2_rx_advance(FALSE /*next*/);
810433d6423SLionel Sambuc 
811b80fc5beSDavid van Moolenbroek 			try_recv = TRUE;
812433d6423SLionel Sambuc 		}
813433d6423SLionel Sambuc 	}
814433d6423SLionel Sambuc 
815433d6423SLionel Sambuc 	/* Reenable interrupts. */
816433d6423SLionel Sambuc 	ATL2_WRITE_U32(ATL2_ISR_REG, 0);
817433d6423SLionel Sambuc 
818433d6423SLionel Sambuc 	if ((r = sys_irqenable(&state.hook_id)) != OK)
819433d6423SLionel Sambuc 		panic("unable to enable IRQ: %d", r);
820433d6423SLionel Sambuc 
821b80fc5beSDavid van Moolenbroek 	/* Attempt to satisfy pending send and receive requests. */
822b80fc5beSDavid van Moolenbroek 	if (try_send)
823b80fc5beSDavid van Moolenbroek 		netdriver_send();
824b80fc5beSDavid van Moolenbroek 	if (try_recv)
825b80fc5beSDavid van Moolenbroek 		netdriver_recv();
826433d6423SLionel Sambuc }
827433d6423SLionel Sambuc 
828*f7df02e7SDavid van Moolenbroek #if 0 /* TODO: link status (using part of this code) */
8291539606dSDavid van Moolenbroek /*
8301539606dSDavid van Moolenbroek  * Dump link status.
831433d6423SLionel Sambuc  */
8321539606dSDavid van Moolenbroek static void
8331539606dSDavid van Moolenbroek atl2_dump_link(void)
8341539606dSDavid van Moolenbroek {
8351539606dSDavid van Moolenbroek 	uint16_t val;
836433d6423SLionel Sambuc 	int link_up;
837433d6423SLionel Sambuc 
838433d6423SLionel Sambuc 	/* The link status bit is latched.  Read the status register twice. */
839433d6423SLionel Sambuc 	atl2_read_mdio(ATL2_MII_BMSR, &val);
840433d6423SLionel Sambuc 	if (!atl2_read_mdio(ATL2_MII_BMSR, &val)) return;
841433d6423SLionel Sambuc 
842433d6423SLionel Sambuc 	link_up = val & ATL2_MII_BMSR_LSTATUS;
843433d6423SLionel Sambuc 	printf("link status:     %4s\t", link_up ? "up" : "down");
844433d6423SLionel Sambuc 
845433d6423SLionel Sambuc 	if (!link_up) return;
846433d6423SLionel Sambuc 
847433d6423SLionel Sambuc 	if (!atl2_read_mdio(ATL2_MII_PSSR, &val)) return;
848433d6423SLionel Sambuc 
849433d6423SLionel Sambuc 	if (!(val & ATL2_MII_PSSR_RESOLVED)) {
850433d6423SLionel Sambuc 		printf("(not resolved)\n");
851433d6423SLionel Sambuc 
852433d6423SLionel Sambuc 		return;
853433d6423SLionel Sambuc 	}
854433d6423SLionel Sambuc 
855433d6423SLionel Sambuc 	switch (val & ATL2_MII_PSSR_SPEED) {
856433d6423SLionel Sambuc 	case ATL2_MII_PSSR_10: printf("(10Mbps "); break;
857433d6423SLionel Sambuc 	case ATL2_MII_PSSR_100: printf("(100Mbps "); break;
858433d6423SLionel Sambuc 	case ATL2_MII_PSSR_1000: printf("(1000Mbps "); break;
859433d6423SLionel Sambuc 	default: printf("(unknown, ");
860433d6423SLionel Sambuc 	}
861433d6423SLionel Sambuc 
862433d6423SLionel Sambuc 	printf("%s duplex)", (val & ATL2_MII_PSSR_DUPLEX) ? "full" : "half");
863433d6423SLionel Sambuc }
864b80fc5beSDavid van Moolenbroek #endif
865b80fc5beSDavid van Moolenbroek 
866b80fc5beSDavid van Moolenbroek /*
8671539606dSDavid van Moolenbroek  * Initialize the atl2 driver.
868433d6423SLionel Sambuc  */
8691539606dSDavid van Moolenbroek static int
atl2_init(unsigned int instance,netdriver_addr_t * addr,uint32_t * caps,unsigned int * ticks __unused)870*f7df02e7SDavid van Moolenbroek atl2_init(unsigned int instance, netdriver_addr_t * addr, uint32_t * caps,
871*f7df02e7SDavid van Moolenbroek 	unsigned int * ticks __unused)
8721539606dSDavid van Moolenbroek {
873b80fc5beSDavid van Moolenbroek 	int devind;
874433d6423SLionel Sambuc 
875b80fc5beSDavid van Moolenbroek 	memset(&state, 0, sizeof(state));
876433d6423SLionel Sambuc 
877433d6423SLionel Sambuc 	/* Try to find a recognized device. */
878433d6423SLionel Sambuc 	devind = atl2_probe(instance);
879433d6423SLionel Sambuc 
880433d6423SLionel Sambuc 	if (devind < 0)
881b80fc5beSDavid van Moolenbroek 		return ENXIO;
882433d6423SLionel Sambuc 
883433d6423SLionel Sambuc 	/* Initialize the device. */
884b80fc5beSDavid van Moolenbroek 	atl2_init_hw(devind, addr);
885433d6423SLionel Sambuc 
886*f7df02e7SDavid van Moolenbroek 	*caps = NDEV_CAP_MCAST | NDEV_CAP_BCAST;
8871539606dSDavid van Moolenbroek 	return OK;
888433d6423SLionel Sambuc }
889433d6423SLionel Sambuc 
890b80fc5beSDavid van Moolenbroek #if 0
8911539606dSDavid van Moolenbroek /*
892b80fc5beSDavid van Moolenbroek  * Deallocate resources as proof of concept.  Currently unused.
893433d6423SLionel Sambuc  */
8941539606dSDavid van Moolenbroek static void
895b80fc5beSDavid van Moolenbroek atl2_cleanup(void)
8961539606dSDavid van Moolenbroek {
897433d6423SLionel Sambuc 	int r;
898433d6423SLionel Sambuc 
899433d6423SLionel Sambuc 	if ((r = sys_irqrmpolicy(&state.hook_id)) != OK)
900433d6423SLionel Sambuc 		panic("unable to deregister IRQ: %d", r);
901433d6423SLionel Sambuc 
902433d6423SLionel Sambuc 	free_contig(state.txd_base, ATL2_TXD_BUFSIZE);
9031539606dSDavid van Moolenbroek 	free_contig(state.txs_base, ATL2_TXS_COUNT * sizeof(uint32_t));
904433d6423SLionel Sambuc 	free_contig(state.rxd_base_u,
905433d6423SLionel Sambuc 	    state.rxd_align + ATL2_RXD_COUNT * ATL2_RXD_SIZE);
906433d6423SLionel Sambuc 
907433d6423SLionel Sambuc 	vm_unmap_phys(SELF, (void *)state.base, state.size);
908433d6423SLionel Sambuc 
909433d6423SLionel Sambuc 	/* We cannot free the PCI device at this time. */
910433d6423SLionel Sambuc }
911b80fc5beSDavid van Moolenbroek #endif
912433d6423SLionel Sambuc 
9131539606dSDavid van Moolenbroek /*
9141539606dSDavid van Moolenbroek  * The ATL2 ethernet driver.
915433d6423SLionel Sambuc  */
9161539606dSDavid van Moolenbroek int
main(int argc,char ** argv)9171539606dSDavid van Moolenbroek main(int argc, char ** argv)
9181539606dSDavid van Moolenbroek {
919433d6423SLionel Sambuc 
920433d6423SLionel Sambuc 	env_setargs(argc, argv);
921433d6423SLionel Sambuc 
922b80fc5beSDavid van Moolenbroek 	netdriver_task(&atl2_table);
923433d6423SLionel Sambuc 
924b80fc5beSDavid van Moolenbroek 	return EXIT_SUCCESS;
925433d6423SLionel Sambuc }
926