xref: /minix/minix/drivers/net/3c90x/3c90x.c (revision f7df02e7)
175e18fe4SDavid van Moolenbroek /* 3Com 3C90xB/C EtherLink driver, by D.C. van Moolenbroek */
275e18fe4SDavid van Moolenbroek 
375e18fe4SDavid van Moolenbroek #include <minix/drivers.h>
475e18fe4SDavid van Moolenbroek #include <minix/netdriver.h>
575e18fe4SDavid van Moolenbroek 
675e18fe4SDavid van Moolenbroek #include <machine/pci.h>
775e18fe4SDavid van Moolenbroek #include <sys/mman.h>
875e18fe4SDavid van Moolenbroek #include <assert.h>
975e18fe4SDavid van Moolenbroek 
1075e18fe4SDavid van Moolenbroek #include "3c90x.h"
1175e18fe4SDavid van Moolenbroek 
1275e18fe4SDavid van Moolenbroek #define VERBOSE		0	/* verbose debugging output */
1375e18fe4SDavid van Moolenbroek 
1475e18fe4SDavid van Moolenbroek #if VERBOSE
1575e18fe4SDavid van Moolenbroek #define XLBC_DEBUG(x)	printf x
1675e18fe4SDavid van Moolenbroek #else
1775e18fe4SDavid van Moolenbroek #define XLBC_DEBUG(x)
1875e18fe4SDavid van Moolenbroek #endif
1975e18fe4SDavid van Moolenbroek 
2075e18fe4SDavid van Moolenbroek static struct {
2175e18fe4SDavid van Moolenbroek 	int hook_id;		/* IRQ hook ID */
2275e18fe4SDavid van Moolenbroek 	uint8_t *base;		/* base address of memory-mapped registers */
2375e18fe4SDavid van Moolenbroek 	uint32_t size;		/* size of memory-mapped register area */
2475e18fe4SDavid van Moolenbroek 	uint16_t window;	/* currently active register window */
2575e18fe4SDavid van Moolenbroek 	uint16_t filter;	/* packet receipt filter flags */
2675e18fe4SDavid van Moolenbroek 
2775e18fe4SDavid van Moolenbroek 	xlbc_pd_t *dpd_base;	/* TX descriptor array, virtual address */
2875e18fe4SDavid van Moolenbroek 	phys_bytes dpd_phys;	/* TX descriptor array, physical address */
2975e18fe4SDavid van Moolenbroek 	uint8_t *txb_base;	/* transmission buffer, virtual address */
3075e18fe4SDavid van Moolenbroek 	phys_bytes txb_phys;	/* transmission buffer, physical address */
3175e18fe4SDavid van Moolenbroek 	xlbc_pd_t *upd_base;	/* RX descriptor array, virtual address */
3275e18fe4SDavid van Moolenbroek 	phys_bytes upd_phys;	/* RX descriptor array, physical address */
3375e18fe4SDavid van Moolenbroek 	uint8_t *rxb_base;	/* receipt buffers, virtual address */
3475e18fe4SDavid van Moolenbroek 	phys_bytes rxb_phys;	/* receipt buffers, physical address */
3575e18fe4SDavid van Moolenbroek 
3675e18fe4SDavid van Moolenbroek 	unsigned int dpd_tail;	/* index of tail TX descriptor */
3775e18fe4SDavid van Moolenbroek 	unsigned int dpd_used;	/* number of in-use TX descriptors */
3875e18fe4SDavid van Moolenbroek 	size_t txb_tail;	/* index of tail TX byte in buffer */
3975e18fe4SDavid van Moolenbroek 	size_t txb_used;	/* number of in-use TX buffer bytes */
4075e18fe4SDavid van Moolenbroek 	unsigned int upd_head;	/* index of head RX descriptor */
4175e18fe4SDavid van Moolenbroek } state;
4275e18fe4SDavid van Moolenbroek 
4375e18fe4SDavid van Moolenbroek enum xlbc_link_type {
4475e18fe4SDavid van Moolenbroek 	XLBC_LINK_DOWN,
4575e18fe4SDavid van Moolenbroek 	XLBC_LINK_UP,
4675e18fe4SDavid van Moolenbroek 	XLBC_LINK_UP_T_HD,
4775e18fe4SDavid van Moolenbroek 	XLBC_LINK_UP_T_FD,
4875e18fe4SDavid van Moolenbroek 	XLBC_LINK_UP_TX_HD,
4975e18fe4SDavid van Moolenbroek 	XLBC_LINK_UP_TX_FD
5075e18fe4SDavid van Moolenbroek };
5175e18fe4SDavid van Moolenbroek 
5275e18fe4SDavid van Moolenbroek #define XLBC_READ_8(off)	(*(volatile uint8_t *)(state.base + (off)))
5375e18fe4SDavid van Moolenbroek #define XLBC_READ_16(off)	(*(volatile uint16_t *)(state.base + (off)))
5475e18fe4SDavid van Moolenbroek #define XLBC_READ_32(off)	(*(volatile uint32_t *)(state.base + (off)))
5575e18fe4SDavid van Moolenbroek #define XLBC_WRITE_8(off, val)	\
5675e18fe4SDavid van Moolenbroek 	(*(volatile uint8_t *)(state.base + (off)) = (val))
5775e18fe4SDavid van Moolenbroek #define XLBC_WRITE_16(off, val)	\
5875e18fe4SDavid van Moolenbroek 	(*(volatile uint16_t *)(state.base + (off)) = (val))
5975e18fe4SDavid van Moolenbroek #define XLBC_WRITE_32(off, val)	\
6075e18fe4SDavid van Moolenbroek 	(*(volatile uint32_t *)(state.base + (off)) = (val))
6175e18fe4SDavid van Moolenbroek 
62*f7df02e7SDavid van Moolenbroek static int xlbc_init(unsigned int, netdriver_addr_t *, uint32_t *,
63*f7df02e7SDavid van Moolenbroek 	unsigned int *);
6475e18fe4SDavid van Moolenbroek static void xlbc_stop(void);
65*f7df02e7SDavid van Moolenbroek static void xlbc_set_mode(unsigned int, const netdriver_addr_t *,
66*f7df02e7SDavid van Moolenbroek 	unsigned int);
67*f7df02e7SDavid van Moolenbroek static ssize_t xlbc_recv(struct netdriver_data *, size_t);
68*f7df02e7SDavid van Moolenbroek static int xlbc_send(struct netdriver_data *, size_t);
69*f7df02e7SDavid van Moolenbroek static void xlbc_intr(unsigned int);
70*f7df02e7SDavid van Moolenbroek static void xlbc_tick(void);
7175e18fe4SDavid van Moolenbroek 
7275e18fe4SDavid van Moolenbroek static const struct netdriver xlbc_table = {
73*f7df02e7SDavid van Moolenbroek 	.ndr_name	= "xl",
7475e18fe4SDavid van Moolenbroek 	.ndr_init	= xlbc_init,
7575e18fe4SDavid van Moolenbroek 	.ndr_stop	= xlbc_stop,
76*f7df02e7SDavid van Moolenbroek 	.ndr_set_mode	= xlbc_set_mode,
7775e18fe4SDavid van Moolenbroek 	.ndr_recv	= xlbc_recv,
7875e18fe4SDavid van Moolenbroek 	.ndr_send	= xlbc_send,
7975e18fe4SDavid van Moolenbroek 	.ndr_intr	= xlbc_intr,
80*f7df02e7SDavid van Moolenbroek 	.ndr_tick	= xlbc_tick
8175e18fe4SDavid van Moolenbroek };
8275e18fe4SDavid van Moolenbroek 
8375e18fe4SDavid van Moolenbroek /*
8475e18fe4SDavid van Moolenbroek  * Find a matching PCI device.
8575e18fe4SDavid van Moolenbroek  */
8675e18fe4SDavid van Moolenbroek static int
xlbc_probe(unsigned int skip)8775e18fe4SDavid van Moolenbroek xlbc_probe(unsigned int skip)
8875e18fe4SDavid van Moolenbroek {
8975e18fe4SDavid van Moolenbroek 	uint16_t vid, did;
9075e18fe4SDavid van Moolenbroek 	int devind;
9175e18fe4SDavid van Moolenbroek #if VERBOSE
9275e18fe4SDavid van Moolenbroek 	const char *dname;
9375e18fe4SDavid van Moolenbroek #endif
9475e18fe4SDavid van Moolenbroek 
9575e18fe4SDavid van Moolenbroek 	pci_init();
9675e18fe4SDavid van Moolenbroek 
9775e18fe4SDavid van Moolenbroek 	if (pci_first_dev(&devind, &vid, &did) <= 0)
9875e18fe4SDavid van Moolenbroek 		return -1;
9975e18fe4SDavid van Moolenbroek 
10075e18fe4SDavid van Moolenbroek 	while (skip--) {
10175e18fe4SDavid van Moolenbroek 		if (pci_next_dev(&devind, &vid, &did) <= 0)
10275e18fe4SDavid van Moolenbroek 			return -1;
10375e18fe4SDavid van Moolenbroek 	}
10475e18fe4SDavid van Moolenbroek 
10575e18fe4SDavid van Moolenbroek #if VERBOSE
10675e18fe4SDavid van Moolenbroek 	dname = pci_dev_name(vid, did);
107*f7df02e7SDavid van Moolenbroek 	XLBC_DEBUG(("%s: found %s (%04x:%04x) at %s\n", netdriver_name(),
10875e18fe4SDavid van Moolenbroek 		dname ? dname : "<unknown>", vid, did, pci_slot_name(devind)));
10975e18fe4SDavid van Moolenbroek #endif
11075e18fe4SDavid van Moolenbroek 
11175e18fe4SDavid van Moolenbroek 	pci_reserve(devind);
11275e18fe4SDavid van Moolenbroek 
11375e18fe4SDavid van Moolenbroek 	return devind;
11475e18fe4SDavid van Moolenbroek }
11575e18fe4SDavid van Moolenbroek 
11675e18fe4SDavid van Moolenbroek /*
11775e18fe4SDavid van Moolenbroek  * Issue a command to the command register.
11875e18fe4SDavid van Moolenbroek  */
11975e18fe4SDavid van Moolenbroek static void
xlbc_issue_cmd(uint16_t cmd)12075e18fe4SDavid van Moolenbroek xlbc_issue_cmd(uint16_t cmd)
12175e18fe4SDavid van Moolenbroek {
12275e18fe4SDavid van Moolenbroek 
12375e18fe4SDavid van Moolenbroek 	assert(!(XLBC_READ_16(XLBC_STATUS_REG) & XLBC_STATUS_IN_PROGRESS));
12475e18fe4SDavid van Moolenbroek 
12575e18fe4SDavid van Moolenbroek 	XLBC_WRITE_16(XLBC_CMD_REG, cmd);
12675e18fe4SDavid van Moolenbroek }
12775e18fe4SDavid van Moolenbroek 
12875e18fe4SDavid van Moolenbroek /*
12975e18fe4SDavid van Moolenbroek  * Wait for a command to be acknowledged.  Return TRUE iff the command
13075e18fe4SDavid van Moolenbroek  * completed within the timeout period.
13175e18fe4SDavid van Moolenbroek  */
13275e18fe4SDavid van Moolenbroek static int
xlbc_wait_cmd(void)13375e18fe4SDavid van Moolenbroek xlbc_wait_cmd(void)
13475e18fe4SDavid van Moolenbroek {
13575e18fe4SDavid van Moolenbroek 	spin_t spin;
13675e18fe4SDavid van Moolenbroek 
13775e18fe4SDavid van Moolenbroek 	/*
13875e18fe4SDavid van Moolenbroek 	 * The documentation implies that a timeout of 1ms is an upper bound
13975e18fe4SDavid van Moolenbroek 	 * for all commands.
14075e18fe4SDavid van Moolenbroek 	 */
14175e18fe4SDavid van Moolenbroek 	SPIN_FOR(&spin, XLBC_CMD_TIMEOUT) {
14275e18fe4SDavid van Moolenbroek 		if (!(XLBC_READ_16(XLBC_STATUS_REG) & XLBC_STATUS_IN_PROGRESS))
14375e18fe4SDavid van Moolenbroek 			return TRUE;
14475e18fe4SDavid van Moolenbroek 	}
14575e18fe4SDavid van Moolenbroek 
14675e18fe4SDavid van Moolenbroek 	return FALSE;
14775e18fe4SDavid van Moolenbroek }
14875e18fe4SDavid van Moolenbroek 
14975e18fe4SDavid van Moolenbroek /*
15075e18fe4SDavid van Moolenbroek  * Reset the device to its initial state.  Return TRUE iff successful.
15175e18fe4SDavid van Moolenbroek  */
15275e18fe4SDavid van Moolenbroek static int
xlbc_reset(void)15375e18fe4SDavid van Moolenbroek xlbc_reset(void)
15475e18fe4SDavid van Moolenbroek {
15575e18fe4SDavid van Moolenbroek 
15675e18fe4SDavid van Moolenbroek 	(void)xlbc_wait_cmd();
15775e18fe4SDavid van Moolenbroek 
15875e18fe4SDavid van Moolenbroek 	xlbc_issue_cmd(XLBC_CMD_GLOBAL_RESET);
15975e18fe4SDavid van Moolenbroek 
16075e18fe4SDavid van Moolenbroek 	/*
16175e18fe4SDavid van Moolenbroek 	 * It appears that the "command in progress" bit may be cleared before
16275e18fe4SDavid van Moolenbroek 	 * the reset has completed, resulting in strange behavior afterwards.
16375e18fe4SDavid van Moolenbroek 	 * Thus, we wait for the maximum reset time (1ms) regardless first, and
16475e18fe4SDavid van Moolenbroek 	 * only then start checking the command-in-progress bit.
16575e18fe4SDavid van Moolenbroek 	 */
16675e18fe4SDavid van Moolenbroek 	micro_delay(XLBC_RESET_DELAY);
16775e18fe4SDavid van Moolenbroek 
16875e18fe4SDavid van Moolenbroek 	if (!xlbc_wait_cmd())
16975e18fe4SDavid van Moolenbroek 		return FALSE;
17075e18fe4SDavid van Moolenbroek 
17175e18fe4SDavid van Moolenbroek 	state.window = 0;
17275e18fe4SDavid van Moolenbroek 
17375e18fe4SDavid van Moolenbroek 	return TRUE;
17475e18fe4SDavid van Moolenbroek }
17575e18fe4SDavid van Moolenbroek 
17675e18fe4SDavid van Moolenbroek /*
17775e18fe4SDavid van Moolenbroek  * Select a register window.
17875e18fe4SDavid van Moolenbroek  */
17975e18fe4SDavid van Moolenbroek static void
xlbc_select_window(unsigned int window)18075e18fe4SDavid van Moolenbroek xlbc_select_window(unsigned int window)
18175e18fe4SDavid van Moolenbroek {
18275e18fe4SDavid van Moolenbroek 
18375e18fe4SDavid van Moolenbroek 	if (state.window == window)
18475e18fe4SDavid van Moolenbroek 		return;
18575e18fe4SDavid van Moolenbroek 
18675e18fe4SDavid van Moolenbroek 	xlbc_issue_cmd(XLBC_CMD_SELECT_WINDOW | window);
18775e18fe4SDavid van Moolenbroek 
18875e18fe4SDavid van Moolenbroek 	state.window = window;
18975e18fe4SDavid van Moolenbroek }
19075e18fe4SDavid van Moolenbroek 
19175e18fe4SDavid van Moolenbroek /*
19275e18fe4SDavid van Moolenbroek  * Read a word from the EEPROM.  On failure, return a value with all bits set.
19375e18fe4SDavid van Moolenbroek  */
19475e18fe4SDavid van Moolenbroek static uint16_t
xlbc_read_eeprom(unsigned int word)19575e18fe4SDavid van Moolenbroek xlbc_read_eeprom(unsigned int word)
19675e18fe4SDavid van Moolenbroek {
19775e18fe4SDavid van Moolenbroek 	spin_t spin;
19875e18fe4SDavid van Moolenbroek 
19975e18fe4SDavid van Moolenbroek 	/* The B revision supports 64 EEPROM words only. */
20075e18fe4SDavid van Moolenbroek 	assert(!(word & ~XLBC_EEPROM_CMD_ADDR));
20175e18fe4SDavid van Moolenbroek 
20275e18fe4SDavid van Moolenbroek 	xlbc_select_window(XLBC_EEPROM_WINDOW);
20375e18fe4SDavid van Moolenbroek 
20475e18fe4SDavid van Moolenbroek 	assert(!(XLBC_READ_16(XLBC_EEPROM_CMD_REG) & XLBC_EEPROM_CMD_BUSY));
20575e18fe4SDavid van Moolenbroek 
20675e18fe4SDavid van Moolenbroek 	XLBC_WRITE_16(XLBC_EEPROM_CMD_REG, XLBC_EEPROM_CMD_READ | word);
20775e18fe4SDavid van Moolenbroek 
20875e18fe4SDavid van Moolenbroek 	/* The documented maximum delay for reads is 162us. */
20975e18fe4SDavid van Moolenbroek 	SPIN_FOR(&spin, XLBC_EEPROM_TIMEOUT) {
21075e18fe4SDavid van Moolenbroek 		if (!(XLBC_READ_16(XLBC_EEPROM_CMD_REG) &
21175e18fe4SDavid van Moolenbroek 		    XLBC_EEPROM_CMD_BUSY))
21275e18fe4SDavid van Moolenbroek 			return XLBC_READ_16(XLBC_EEPROM_DATA_REG);
21375e18fe4SDavid van Moolenbroek 	}
21475e18fe4SDavid van Moolenbroek 
21575e18fe4SDavid van Moolenbroek 	return (uint16_t)-1;
21675e18fe4SDavid van Moolenbroek }
21775e18fe4SDavid van Moolenbroek 
21875e18fe4SDavid van Moolenbroek /*
21975e18fe4SDavid van Moolenbroek  * Obtain the preconfigured hardware address of the device.
22075e18fe4SDavid van Moolenbroek  */
22175e18fe4SDavid van Moolenbroek static void
xlbc_get_hwaddr(netdriver_addr_t * addr)222*f7df02e7SDavid van Moolenbroek xlbc_get_hwaddr(netdriver_addr_t * addr)
22375e18fe4SDavid van Moolenbroek {
22475e18fe4SDavid van Moolenbroek 	uint16_t word[3];
22575e18fe4SDavid van Moolenbroek 
22675e18fe4SDavid van Moolenbroek 	/* TODO: allow overriding through environment variables */
22775e18fe4SDavid van Moolenbroek 
22875e18fe4SDavid van Moolenbroek 	word[0] = xlbc_read_eeprom(XLBC_EEPROM_WORD_OEM_ADDR0);
22975e18fe4SDavid van Moolenbroek 	word[1] = xlbc_read_eeprom(XLBC_EEPROM_WORD_OEM_ADDR1);
23075e18fe4SDavid van Moolenbroek 	word[2] = xlbc_read_eeprom(XLBC_EEPROM_WORD_OEM_ADDR2);
23175e18fe4SDavid van Moolenbroek 
232*f7df02e7SDavid van Moolenbroek 	addr->na_addr[0] = word[0] >> 8;
233*f7df02e7SDavid van Moolenbroek 	addr->na_addr[1] = word[0] & 0xff;
234*f7df02e7SDavid van Moolenbroek 	addr->na_addr[2] = word[1] >> 8;
235*f7df02e7SDavid van Moolenbroek 	addr->na_addr[3] = word[1] & 0xff;
236*f7df02e7SDavid van Moolenbroek 	addr->na_addr[4] = word[2] >> 8;
237*f7df02e7SDavid van Moolenbroek 	addr->na_addr[5] = word[2] & 0xff;
23875e18fe4SDavid van Moolenbroek 
23975e18fe4SDavid van Moolenbroek 	XLBC_DEBUG(("%s: MAC address %02x:%02x:%02x:%02x:%02x:%02x\n",
240*f7df02e7SDavid van Moolenbroek 	    netdriver_name(),
241*f7df02e7SDavid van Moolenbroek 	    addr->na_addr[0], addr->na_addr[1], addr->na_addr[2],
242*f7df02e7SDavid van Moolenbroek 	    addr->na_addr[3], addr->na_addr[4], addr->na_addr[5]));
24375e18fe4SDavid van Moolenbroek }
24475e18fe4SDavid van Moolenbroek 
24575e18fe4SDavid van Moolenbroek /*
24675e18fe4SDavid van Moolenbroek  * Configure the device to use the given hardware address.
24775e18fe4SDavid van Moolenbroek  */
24875e18fe4SDavid van Moolenbroek static void
xlbc_set_hwaddr(netdriver_addr_t * addr)249*f7df02e7SDavid van Moolenbroek xlbc_set_hwaddr(netdriver_addr_t * addr)
25075e18fe4SDavid van Moolenbroek {
25175e18fe4SDavid van Moolenbroek 
25275e18fe4SDavid van Moolenbroek 	xlbc_select_window(XLBC_STATION_WINDOW);
25375e18fe4SDavid van Moolenbroek 
25475e18fe4SDavid van Moolenbroek 	/* Set station address. */
25575e18fe4SDavid van Moolenbroek 	XLBC_WRITE_16(XLBC_STATION_ADDR0_REG,
256*f7df02e7SDavid van Moolenbroek 	    addr->na_addr[0] | (addr->na_addr[1] << 8));
25775e18fe4SDavid van Moolenbroek 	XLBC_WRITE_16(XLBC_STATION_ADDR1_REG,
258*f7df02e7SDavid van Moolenbroek 	    addr->na_addr[2] | (addr->na_addr[3] << 8));
25975e18fe4SDavid van Moolenbroek 	XLBC_WRITE_16(XLBC_STATION_ADDR2_REG,
260*f7df02e7SDavid van Moolenbroek 	    addr->na_addr[4] | (addr->na_addr[5] << 8));
26175e18fe4SDavid van Moolenbroek 
26275e18fe4SDavid van Moolenbroek 	/* Set station mask. */
26375e18fe4SDavid van Moolenbroek 	XLBC_WRITE_16(XLBC_STATION_MASK0_REG, 0);
26475e18fe4SDavid van Moolenbroek 	XLBC_WRITE_16(XLBC_STATION_MASK1_REG, 0);
26575e18fe4SDavid van Moolenbroek 	XLBC_WRITE_16(XLBC_STATION_MASK2_REG, 0);
26675e18fe4SDavid van Moolenbroek }
26775e18fe4SDavid van Moolenbroek 
26875e18fe4SDavid van Moolenbroek /*
26975e18fe4SDavid van Moolenbroek  * Perform one-time initialization of various settings.
27075e18fe4SDavid van Moolenbroek  */
27175e18fe4SDavid van Moolenbroek static void
xlbc_init_once(void)27275e18fe4SDavid van Moolenbroek xlbc_init_once(void)
27375e18fe4SDavid van Moolenbroek {
27475e18fe4SDavid van Moolenbroek 	uint16_t word;
27575e18fe4SDavid van Moolenbroek 	uint32_t dword;
27675e18fe4SDavid van Moolenbroek 
27775e18fe4SDavid van Moolenbroek 	/*
27875e18fe4SDavid van Moolenbroek 	 * Verify the presence of a 10BASE-T or 100BASE-TX port.  Those are the
27975e18fe4SDavid van Moolenbroek 	 * only port types that are supported and have been tested so far.
28075e18fe4SDavid van Moolenbroek 	 */
28175e18fe4SDavid van Moolenbroek 	xlbc_select_window(XLBC_MEDIA_OPT_WINDOW);
28275e18fe4SDavid van Moolenbroek 
28375e18fe4SDavid van Moolenbroek 	word = XLBC_READ_16(XLBC_MEDIA_OPT_REG);
28475e18fe4SDavid van Moolenbroek 	if (!(word & (XLBC_MEDIA_OPT_BASE_TX | XLBC_MEDIA_OPT_10_BT)))
28575e18fe4SDavid van Moolenbroek 		panic("no 100BASE-TX or 10BASE-T port on device");
28675e18fe4SDavid van Moolenbroek 
28775e18fe4SDavid van Moolenbroek 	/* Initialize the device's internal configuration. */
28875e18fe4SDavid van Moolenbroek 	xlbc_select_window(XLBC_CONFIG_WINDOW);
28975e18fe4SDavid van Moolenbroek 
29075e18fe4SDavid van Moolenbroek 	word = XLBC_READ_16(XLBC_CONFIG_WORD1_REG);
29175e18fe4SDavid van Moolenbroek 	word = (word & ~XLBC_CONFIG_XCVR_MASK) | XLBC_CONFIG_XCVR_AUTO;
29275e18fe4SDavid van Moolenbroek 	XLBC_WRITE_16(XLBC_CONFIG_WORD1_REG, word);
29375e18fe4SDavid van Moolenbroek 
29475e18fe4SDavid van Moolenbroek 	/* Disable alternate upload and download sequences. */
29575e18fe4SDavid van Moolenbroek 	dword = XLBC_READ_32(XLBC_DMA_CTRL_REG);
29675e18fe4SDavid van Moolenbroek 	dword |= XLBC_DMA_CTRL_UP_NOALT | XLBC_DMA_CTRL_DN_NOALT;
29775e18fe4SDavid van Moolenbroek 	XLBC_WRITE_32(XLBC_DMA_CTRL_REG, dword);
29875e18fe4SDavid van Moolenbroek 
29975e18fe4SDavid van Moolenbroek 	/* Specify in which status events we are interested. */
30075e18fe4SDavid van Moolenbroek 	xlbc_issue_cmd(XLBC_CMD_IND_ENABLE | XLBC_STATUS_MASK);
30175e18fe4SDavid van Moolenbroek 
30275e18fe4SDavid van Moolenbroek 	/* Enable statistics, including support for counters' upper bits. */
30375e18fe4SDavid van Moolenbroek 	xlbc_select_window(XLBC_NET_DIAG_WINDOW);
30475e18fe4SDavid van Moolenbroek 
30575e18fe4SDavid van Moolenbroek 	word = XLBC_READ_16(XLBC_NET_DIAG_REG);
30675e18fe4SDavid van Moolenbroek 	XLBC_WRITE_16(XLBC_NET_DIAG_REG, word | XLBC_NET_DIAG_UPPER);
30775e18fe4SDavid van Moolenbroek 
30875e18fe4SDavid van Moolenbroek 	xlbc_issue_cmd(XLBC_CMD_STATS_ENABLE);
30975e18fe4SDavid van Moolenbroek }
31075e18fe4SDavid van Moolenbroek 
31175e18fe4SDavid van Moolenbroek /*
31275e18fe4SDavid van Moolenbroek  * Allocate memory for DMA.
31375e18fe4SDavid van Moolenbroek  */
31475e18fe4SDavid van Moolenbroek static void
xlbc_alloc_dma(void)31575e18fe4SDavid van Moolenbroek xlbc_alloc_dma(void)
31675e18fe4SDavid van Moolenbroek {
31775e18fe4SDavid van Moolenbroek 
31875e18fe4SDavid van Moolenbroek 	/* Packet descriptors require 8-byte alignment. */
31975e18fe4SDavid van Moolenbroek 	assert(!(sizeof(xlbc_pd_t) % 8));
32075e18fe4SDavid van Moolenbroek 
32175e18fe4SDavid van Moolenbroek 	/*
32275e18fe4SDavid van Moolenbroek 	 * For packet transmission, we use one single circular buffer in which
32375e18fe4SDavid van Moolenbroek 	 * we store packet data.  We do not split packets in two when the
32475e18fe4SDavid van Moolenbroek 	 * buffer wraps; instead we waste the trailing bytes and move on to the
32575e18fe4SDavid van Moolenbroek 	 * start of the buffer.  This allows us to use a single fragment for
32675e18fe4SDavid van Moolenbroek 	 * each transmitted packet, thus keeping the descriptors small (16
32775e18fe4SDavid van Moolenbroek 	 * bytes).  The descriptors themselves are allocated as a separate
32875e18fe4SDavid van Moolenbroek 	 * array.  There is obviously room for improvement here, but the
32975e18fe4SDavid van Moolenbroek 	 * approach should be good enough.
33075e18fe4SDavid van Moolenbroek 	 */
33175e18fe4SDavid van Moolenbroek 	state.dpd_base = alloc_contig(XLBC_DPD_COUNT * sizeof(xlbc_pd_t),
33275e18fe4SDavid van Moolenbroek 	    AC_ALIGN4K, &state.dpd_phys);
33375e18fe4SDavid van Moolenbroek 	state.txb_base = alloc_contig(XLBC_TXB_SIZE, 0, &state.txb_phys);
33475e18fe4SDavid van Moolenbroek 
33575e18fe4SDavid van Moolenbroek 	if (state.dpd_base == NULL || state.txb_base == NULL)
33675e18fe4SDavid van Moolenbroek 		panic("unable to allocate memory for packet transmission");
33775e18fe4SDavid van Moolenbroek 
33875e18fe4SDavid van Moolenbroek 	/*
33975e18fe4SDavid van Moolenbroek 	 * For packet receipt, we have a number of pairs of buffers and
34075e18fe4SDavid van Moolenbroek 	 * corresponding descriptors.  Each buffer is large enough to contain
34175e18fe4SDavid van Moolenbroek 	 * an entire packet.  We avoid wasting memory by allocating the buffers
34275e18fe4SDavid van Moolenbroek 	 * in one go, at the cost of requiring a large contiguous area.  The
34375e18fe4SDavid van Moolenbroek 	 * descriptors are allocated as a separate array, thus matching the
34475e18fe4SDavid van Moolenbroek 	 * scheme for transmission in terms of allocation strategy.  Here, too,
34575e18fe4SDavid van Moolenbroek 	 * there is clear room for improvement at the cost of extra complexity.
34675e18fe4SDavid van Moolenbroek 	 */
34775e18fe4SDavid van Moolenbroek 	state.upd_base = alloc_contig(XLBC_UPD_COUNT * sizeof(xlbc_pd_t),
34875e18fe4SDavid van Moolenbroek 	    AC_ALIGN4K, &state.upd_phys);
34975e18fe4SDavid van Moolenbroek 	state.rxb_base = alloc_contig(XLBC_UPD_COUNT * XLBC_MAX_PKT_LEN, 0,
35075e18fe4SDavid van Moolenbroek 	    &state.rxb_phys);
35175e18fe4SDavid van Moolenbroek 
35275e18fe4SDavid van Moolenbroek 	if (state.upd_base == NULL || state.rxb_base == NULL)
35375e18fe4SDavid van Moolenbroek 		panic("unable to allocate memory for packet receipt");
35475e18fe4SDavid van Moolenbroek }
35575e18fe4SDavid van Moolenbroek 
35675e18fe4SDavid van Moolenbroek /*
35775e18fe4SDavid van Moolenbroek  * Reset the transmitter.
35875e18fe4SDavid van Moolenbroek  */
35975e18fe4SDavid van Moolenbroek static void
xlbc_reset_tx(void)36075e18fe4SDavid van Moolenbroek xlbc_reset_tx(void)
36175e18fe4SDavid van Moolenbroek {
36275e18fe4SDavid van Moolenbroek 
36375e18fe4SDavid van Moolenbroek 	xlbc_issue_cmd(XLBC_CMD_TX_RESET);
36475e18fe4SDavid van Moolenbroek 	if (!xlbc_wait_cmd())
36575e18fe4SDavid van Moolenbroek 		panic("timeout trying to reset transmitter");
36675e18fe4SDavid van Moolenbroek 
36775e18fe4SDavid van Moolenbroek 	state.dpd_tail = 0;
36875e18fe4SDavid van Moolenbroek 	state.dpd_used = 0;
36975e18fe4SDavid van Moolenbroek 	state.txb_tail = 0;
37075e18fe4SDavid van Moolenbroek 	state.txb_used = 0;
37175e18fe4SDavid van Moolenbroek 
37275e18fe4SDavid van Moolenbroek 	xlbc_issue_cmd(XLBC_CMD_TX_ENABLE);
37375e18fe4SDavid van Moolenbroek }
37475e18fe4SDavid van Moolenbroek 
37575e18fe4SDavid van Moolenbroek /*
37675e18fe4SDavid van Moolenbroek  * Reset the receiver.
37775e18fe4SDavid van Moolenbroek  */
37875e18fe4SDavid van Moolenbroek static void
xlbc_reset_rx(void)37975e18fe4SDavid van Moolenbroek xlbc_reset_rx(void)
38075e18fe4SDavid van Moolenbroek {
38175e18fe4SDavid van Moolenbroek 	unsigned int i;
38275e18fe4SDavid van Moolenbroek 
38375e18fe4SDavid van Moolenbroek 	xlbc_issue_cmd(XLBC_CMD_RX_RESET);
38475e18fe4SDavid van Moolenbroek 	if (!xlbc_wait_cmd())
38575e18fe4SDavid van Moolenbroek 		panic("timeout trying to reset receiver");
38675e18fe4SDavid van Moolenbroek 
38775e18fe4SDavid van Moolenbroek 	xlbc_issue_cmd(XLBC_CMD_SET_FILTER | state.filter);
38875e18fe4SDavid van Moolenbroek 
38975e18fe4SDavid van Moolenbroek 	for (i = 0; i < XLBC_UPD_COUNT; i++) {
39075e18fe4SDavid van Moolenbroek 		state.upd_base[i].next = state.upd_phys +
39175e18fe4SDavid van Moolenbroek 		    ((i + 1) % XLBC_UPD_COUNT) * sizeof(xlbc_pd_t);
39275e18fe4SDavid van Moolenbroek 		state.upd_base[i].flags = 0;
39375e18fe4SDavid van Moolenbroek 		state.upd_base[i].addr = state.rxb_phys + i * XLBC_MAX_PKT_LEN;
39475e18fe4SDavid van Moolenbroek 		state.upd_base[i].len = XLBC_LEN_LAST | XLBC_MAX_PKT_LEN;
39575e18fe4SDavid van Moolenbroek 	}
39675e18fe4SDavid van Moolenbroek 
39775e18fe4SDavid van Moolenbroek 	XLBC_WRITE_32(XLBC_UP_LIST_PTR_REG, state.upd_phys);
39875e18fe4SDavid van Moolenbroek 
39975e18fe4SDavid van Moolenbroek 	state.upd_head = 0;
40075e18fe4SDavid van Moolenbroek 
40175e18fe4SDavid van Moolenbroek 	__insn_barrier();
40275e18fe4SDavid van Moolenbroek 
40375e18fe4SDavid van Moolenbroek 	xlbc_issue_cmd(XLBC_CMD_RX_ENABLE);
40475e18fe4SDavid van Moolenbroek }
40575e18fe4SDavid van Moolenbroek 
40675e18fe4SDavid van Moolenbroek /*
40775e18fe4SDavid van Moolenbroek  * Execute a MII read, write, or Z cycle.  Stop the clock, wait, start the
40875e18fe4SDavid van Moolenbroek  * clock, optionally change direction and/or data bits, and wait again.
40975e18fe4SDavid van Moolenbroek  */
41075e18fe4SDavid van Moolenbroek static uint16_t
xlbc_mii_cycle(uint16_t val,uint16_t mask,uint16_t bits)41175e18fe4SDavid van Moolenbroek xlbc_mii_cycle(uint16_t val, uint16_t mask, uint16_t bits)
41275e18fe4SDavid van Moolenbroek {
41375e18fe4SDavid van Moolenbroek 
41475e18fe4SDavid van Moolenbroek 	val &= ~XLBC_PHYS_MGMT_CLK;
41575e18fe4SDavid van Moolenbroek 	XLBC_WRITE_16(XLBC_PHYS_MGMT_REG, val);
41675e18fe4SDavid van Moolenbroek 
41775e18fe4SDavid van Moolenbroek 	/* All the delays should be 200ns minimum. */
41875e18fe4SDavid van Moolenbroek 	micro_delay(XLBC_MII_DELAY);
41975e18fe4SDavid van Moolenbroek 
42075e18fe4SDavid van Moolenbroek 	/* The clock must be enabled separately from other bit updates. */
42175e18fe4SDavid van Moolenbroek 	val |= XLBC_PHYS_MGMT_CLK;
42275e18fe4SDavid van Moolenbroek 	XLBC_WRITE_16(XLBC_PHYS_MGMT_REG, val);
42375e18fe4SDavid van Moolenbroek 
42475e18fe4SDavid van Moolenbroek 	if (mask != 0) {
42575e18fe4SDavid van Moolenbroek 		val = (val & ~mask) | bits;
42675e18fe4SDavid van Moolenbroek 		XLBC_WRITE_16(XLBC_PHYS_MGMT_REG, val);
42775e18fe4SDavid van Moolenbroek 	}
42875e18fe4SDavid van Moolenbroek 
42975e18fe4SDavid van Moolenbroek 	micro_delay(XLBC_MII_DELAY);
43075e18fe4SDavid van Moolenbroek 
43175e18fe4SDavid van Moolenbroek 	return val;
43275e18fe4SDavid van Moolenbroek }
43375e18fe4SDavid van Moolenbroek 
43475e18fe4SDavid van Moolenbroek /*
43575e18fe4SDavid van Moolenbroek  * Read a MII register.
43675e18fe4SDavid van Moolenbroek  */
43775e18fe4SDavid van Moolenbroek static uint16_t
xlbc_mii_read(uint16_t phy,uint16_t reg)43875e18fe4SDavid van Moolenbroek xlbc_mii_read(uint16_t phy, uint16_t reg)
43975e18fe4SDavid van Moolenbroek {
44075e18fe4SDavid van Moolenbroek 	uint32_t dword;
44175e18fe4SDavid van Moolenbroek 	uint16_t val;
44275e18fe4SDavid van Moolenbroek 	int i;
44375e18fe4SDavid van Moolenbroek 
44475e18fe4SDavid van Moolenbroek 	xlbc_select_window(XLBC_PHYS_MGMT_WINDOW);
44575e18fe4SDavid van Moolenbroek 
44675e18fe4SDavid van Moolenbroek 	/* Set the direction to write. */
44775e18fe4SDavid van Moolenbroek 	val = XLBC_READ_16(XLBC_PHYS_MGMT_REG) | XLBC_PHYS_MGMT_DIR;
44875e18fe4SDavid van Moolenbroek 
44975e18fe4SDavid van Moolenbroek 	XLBC_WRITE_16(XLBC_PHYS_MGMT_REG, val);
45075e18fe4SDavid van Moolenbroek 
45175e18fe4SDavid van Moolenbroek 	/* Execute write cycles to submit the preamble: PR=1..1 (32 bits) */
45275e18fe4SDavid van Moolenbroek 	for (i = 0; i < 32; i++)
45375e18fe4SDavid van Moolenbroek 		val = xlbc_mii_cycle(val, XLBC_PHYS_MGMT_DATA,
45475e18fe4SDavid van Moolenbroek 		    XLBC_PHYS_MGMT_DATA);
45575e18fe4SDavid van Moolenbroek 
45675e18fe4SDavid van Moolenbroek 	/* Execute write cycles to submit the rest of the read frame. */
45775e18fe4SDavid van Moolenbroek 	/* ST=01 OP=10 PHYAD=aaaaa REGAD=rrrrr */
45875e18fe4SDavid van Moolenbroek 	dword = 0x1800 | (phy << 5) | reg;
45975e18fe4SDavid van Moolenbroek 
46075e18fe4SDavid van Moolenbroek 	for (i = 13; i >= 0; i--)
46175e18fe4SDavid van Moolenbroek 		val = xlbc_mii_cycle(val, XLBC_PHYS_MGMT_DATA,
46275e18fe4SDavid van Moolenbroek 		    ((dword >> i) & 1) ? XLBC_PHYS_MGMT_DATA : 0);
46375e18fe4SDavid van Moolenbroek 
46475e18fe4SDavid van Moolenbroek 	/* Execute a Z cycle to set the direction to read. */
46575e18fe4SDavid van Moolenbroek 	val = xlbc_mii_cycle(val, XLBC_PHYS_MGMT_DIR, 0);
46675e18fe4SDavid van Moolenbroek 
46775e18fe4SDavid van Moolenbroek 	dword = 0;
46875e18fe4SDavid van Moolenbroek 
46975e18fe4SDavid van Moolenbroek 	/* Receive one status bit and 16 actual data bits. */
47075e18fe4SDavid van Moolenbroek 	for (i = 16; i >= 0; i--) {
47175e18fe4SDavid van Moolenbroek 		(void)xlbc_mii_cycle(val, 0, 0);
47275e18fe4SDavid van Moolenbroek 
47375e18fe4SDavid van Moolenbroek 		val = XLBC_READ_16(XLBC_PHYS_MGMT_REG);
47475e18fe4SDavid van Moolenbroek 
47575e18fe4SDavid van Moolenbroek 		dword = (dword << 1) | !!(val & XLBC_PHYS_MGMT_DATA);
47675e18fe4SDavid van Moolenbroek 
47775e18fe4SDavid van Moolenbroek 		micro_delay(XLBC_MII_DELAY);
47875e18fe4SDavid van Moolenbroek 	}
47975e18fe4SDavid van Moolenbroek 
48075e18fe4SDavid van Moolenbroek 	/* Execute a Z cycle to terminate the read frame. */
48175e18fe4SDavid van Moolenbroek 	(void)xlbc_mii_cycle(val, 0, 0);
48275e18fe4SDavid van Moolenbroek 
48375e18fe4SDavid van Moolenbroek 	/* If the status bit was set, the results are invalid. */
48475e18fe4SDavid van Moolenbroek 	if (dword & 0x10000)
48575e18fe4SDavid van Moolenbroek 		dword = 0xffff;
48675e18fe4SDavid van Moolenbroek 
48775e18fe4SDavid van Moolenbroek 	return (uint16_t)dword;
48875e18fe4SDavid van Moolenbroek }
48975e18fe4SDavid van Moolenbroek 
49075e18fe4SDavid van Moolenbroek /*
49175e18fe4SDavid van Moolenbroek  * Write a MII register.
49275e18fe4SDavid van Moolenbroek  */
49375e18fe4SDavid van Moolenbroek static void
xlbc_mii_write(uint16_t phy,uint16_t reg,uint16_t data)49475e18fe4SDavid van Moolenbroek xlbc_mii_write(uint16_t phy, uint16_t reg, uint16_t data)
49575e18fe4SDavid van Moolenbroek {
49675e18fe4SDavid van Moolenbroek 	uint32_t dword;
49775e18fe4SDavid van Moolenbroek 	uint16_t val;
49875e18fe4SDavid van Moolenbroek 	int i;
49975e18fe4SDavid van Moolenbroek 
50075e18fe4SDavid van Moolenbroek 	xlbc_select_window(XLBC_PHYS_MGMT_WINDOW);
50175e18fe4SDavid van Moolenbroek 
50275e18fe4SDavid van Moolenbroek 	/* Set the direction to write. */
50375e18fe4SDavid van Moolenbroek 	val = XLBC_READ_16(XLBC_PHYS_MGMT_REG) | XLBC_PHYS_MGMT_DIR;
50475e18fe4SDavid van Moolenbroek 
50575e18fe4SDavid van Moolenbroek 	XLBC_WRITE_16(XLBC_PHYS_MGMT_REG, val);
50675e18fe4SDavid van Moolenbroek 
50775e18fe4SDavid van Moolenbroek 	/* Execute write cycles to submit the preamble: PR=1..1 (32 bits) */
50875e18fe4SDavid van Moolenbroek 	for (i = 0; i < 32; i++)
50975e18fe4SDavid van Moolenbroek 		val = xlbc_mii_cycle(val, XLBC_PHYS_MGMT_DATA,
51075e18fe4SDavid van Moolenbroek 		    XLBC_PHYS_MGMT_DATA);
51175e18fe4SDavid van Moolenbroek 
51275e18fe4SDavid van Moolenbroek 	/* Execute write cycles to submit the rest of the read frame. */
51375e18fe4SDavid van Moolenbroek 	/* ST=01 OP=01 PHYAD=aaaaa REGAD=rrrrr TA=10 DATA=d..d (16 bits) */
51475e18fe4SDavid van Moolenbroek 	dword = 0x50020000 | (phy << 23) | (reg << 18) | data;
51575e18fe4SDavid van Moolenbroek 
51675e18fe4SDavid van Moolenbroek 	for (i = 31; i >= 0; i--)
51775e18fe4SDavid van Moolenbroek 		val = xlbc_mii_cycle(val, XLBC_PHYS_MGMT_DATA,
51875e18fe4SDavid van Moolenbroek 		    ((dword >> i) & 1) ? XLBC_PHYS_MGMT_DATA : 0);
51975e18fe4SDavid van Moolenbroek 
52075e18fe4SDavid van Moolenbroek 	/* Execute a Z cycle to terminate the write frame. */
52175e18fe4SDavid van Moolenbroek 	(void)xlbc_mii_cycle(val, 0, 0);
52275e18fe4SDavid van Moolenbroek }
52375e18fe4SDavid van Moolenbroek 
52475e18fe4SDavid van Moolenbroek /*
52575e18fe4SDavid van Moolenbroek  * Return a human-readable description for the given link type.
52675e18fe4SDavid van Moolenbroek  */
527*f7df02e7SDavid van Moolenbroek #if VERBOSE
52875e18fe4SDavid van Moolenbroek static const char *
xlbc_get_link_name(enum xlbc_link_type link_type)52975e18fe4SDavid van Moolenbroek xlbc_get_link_name(enum xlbc_link_type link_type)
53075e18fe4SDavid van Moolenbroek {
53175e18fe4SDavid van Moolenbroek 
53275e18fe4SDavid van Moolenbroek 	switch (link_type) {
53375e18fe4SDavid van Moolenbroek 	case XLBC_LINK_DOWN:		return "down";
53475e18fe4SDavid van Moolenbroek 	case XLBC_LINK_UP:		return "up";
53575e18fe4SDavid van Moolenbroek 	case XLBC_LINK_UP_T_HD:		return "up (10Mbps, half duplex)";
53675e18fe4SDavid van Moolenbroek 	case XLBC_LINK_UP_T_FD:		return "up (10Mbps, full duplex)";
53775e18fe4SDavid van Moolenbroek 	case XLBC_LINK_UP_TX_HD:	return "up (100Mbps, half duplex)";
53875e18fe4SDavid van Moolenbroek 	case XLBC_LINK_UP_TX_FD:	return "up (100Mbps, full duplex)";
53975e18fe4SDavid van Moolenbroek 	default:			return "(unknown)";
54075e18fe4SDavid van Moolenbroek 	}
54175e18fe4SDavid van Moolenbroek }
542*f7df02e7SDavid van Moolenbroek #endif /* VERBOSE */
54375e18fe4SDavid van Moolenbroek 
54475e18fe4SDavid van Moolenbroek /*
54575e18fe4SDavid van Moolenbroek  * Determine the current link status, and return the resulting link type.
54675e18fe4SDavid van Moolenbroek  */
54775e18fe4SDavid van Moolenbroek static enum xlbc_link_type
xlbc_get_link_type(void)54875e18fe4SDavid van Moolenbroek xlbc_get_link_type(void)
54975e18fe4SDavid van Moolenbroek {
55075e18fe4SDavid van Moolenbroek 	uint16_t status, control, mask;
55175e18fe4SDavid van Moolenbroek 
55275e18fe4SDavid van Moolenbroek 	xlbc_select_window(XLBC_MEDIA_STS_WINDOW);
55375e18fe4SDavid van Moolenbroek 
55475e18fe4SDavid van Moolenbroek 	if (!(XLBC_READ_16(XLBC_MEDIA_STS_REG) & XLBC_MEDIA_STS_LINK_DET))
55575e18fe4SDavid van Moolenbroek 		return XLBC_LINK_DOWN;
55675e18fe4SDavid van Moolenbroek 
55775e18fe4SDavid van Moolenbroek 	status = xlbc_mii_read(XLBC_PHY_ADDR, XLBC_MII_STATUS);
55875e18fe4SDavid van Moolenbroek 	if (!(status & XLBC_MII_STATUS_EXTCAP))
55975e18fe4SDavid van Moolenbroek 		return XLBC_LINK_UP;
56075e18fe4SDavid van Moolenbroek 	if (!(status & XLBC_MII_STATUS_AUTONEG))
56175e18fe4SDavid van Moolenbroek 		return XLBC_LINK_UP;
56275e18fe4SDavid van Moolenbroek 
56375e18fe4SDavid van Moolenbroek 	/* Wait for auto-negotiation to complete first. */
56475e18fe4SDavid van Moolenbroek 	if (!(status & XLBC_MII_STATUS_COMPLETE)) {
56575e18fe4SDavid van Moolenbroek 		control = xlbc_mii_read(XLBC_PHY_ADDR, XLBC_MII_CONTROL);
56675e18fe4SDavid van Moolenbroek 		control |= XLBC_MII_CONTROL_AUTONEG;
56775e18fe4SDavid van Moolenbroek 		xlbc_mii_write(XLBC_PHY_ADDR, XLBC_MII_CONTROL, control);
56875e18fe4SDavid van Moolenbroek 
56975e18fe4SDavid van Moolenbroek 		SPIN_UNTIL(xlbc_mii_read(XLBC_PHY_ADDR, XLBC_MII_STATUS) &
57075e18fe4SDavid van Moolenbroek 		    XLBC_MII_STATUS_COMPLETE, XLBC_AUTONEG_TIMEOUT);
57175e18fe4SDavid van Moolenbroek 
57275e18fe4SDavid van Moolenbroek 		status = xlbc_mii_read(XLBC_PHY_ADDR, XLBC_MII_STATUS);
57375e18fe4SDavid van Moolenbroek 		if (!(status & XLBC_MII_STATUS_COMPLETE))
57475e18fe4SDavid van Moolenbroek 			return XLBC_LINK_UP;
57575e18fe4SDavid van Moolenbroek 	}
57675e18fe4SDavid van Moolenbroek 
57775e18fe4SDavid van Moolenbroek 	/* The highest bit set in both registers is the selected link type. */
57875e18fe4SDavid van Moolenbroek 	mask = xlbc_mii_read(XLBC_PHY_ADDR, XLBC_MII_AUTONEG_ADV) &
57975e18fe4SDavid van Moolenbroek 	    xlbc_mii_read(XLBC_PHY_ADDR, XLBC_MII_LP_ABILITY);
58075e18fe4SDavid van Moolenbroek 
58175e18fe4SDavid van Moolenbroek 	if (mask & XLBC_MII_LINK_TX_FD)
58275e18fe4SDavid van Moolenbroek 		return XLBC_LINK_UP_TX_FD;
58375e18fe4SDavid van Moolenbroek 	if (mask & XLBC_MII_LINK_TX_HD)
58475e18fe4SDavid van Moolenbroek 		return XLBC_LINK_UP_TX_HD;
58575e18fe4SDavid van Moolenbroek 	if (mask & XLBC_MII_LINK_T_FD)
58675e18fe4SDavid van Moolenbroek 		return XLBC_LINK_UP_T_FD;
58775e18fe4SDavid van Moolenbroek 	if (mask & XLBC_MII_LINK_T_HD)
58875e18fe4SDavid van Moolenbroek 		return XLBC_LINK_UP_T_HD;
58975e18fe4SDavid van Moolenbroek 
59075e18fe4SDavid van Moolenbroek 	return XLBC_LINK_UP;
59175e18fe4SDavid van Moolenbroek }
59275e18fe4SDavid van Moolenbroek 
59375e18fe4SDavid van Moolenbroek /*
59475e18fe4SDavid van Moolenbroek  * Set the duplex mode to full or half, based on the current link type.
59575e18fe4SDavid van Moolenbroek  */
59675e18fe4SDavid van Moolenbroek static void
xlbc_set_duplex(enum xlbc_link_type link)59775e18fe4SDavid van Moolenbroek xlbc_set_duplex(enum xlbc_link_type link)
59875e18fe4SDavid van Moolenbroek {
59975e18fe4SDavid van Moolenbroek 	uint16_t word;
60075e18fe4SDavid van Moolenbroek 	int duplex;
60175e18fe4SDavid van Moolenbroek 
60275e18fe4SDavid van Moolenbroek 	/*
60375e18fe4SDavid van Moolenbroek 	 * If the link is down, do not change modes.  In fact, the link may go
60475e18fe4SDavid van Moolenbroek 	 * down as a result of the reset that is part of changing the mode.
60575e18fe4SDavid van Moolenbroek 	 */
60675e18fe4SDavid van Moolenbroek 	if (link == XLBC_LINK_DOWN)
60775e18fe4SDavid van Moolenbroek 		return;
60875e18fe4SDavid van Moolenbroek 
60975e18fe4SDavid van Moolenbroek 	/* See if the desired duplex mode differs from the current mode. */
61075e18fe4SDavid van Moolenbroek 	duplex = (link == XLBC_LINK_UP_T_FD || link == XLBC_LINK_UP_TX_FD);
61175e18fe4SDavid van Moolenbroek 
61275e18fe4SDavid van Moolenbroek 	xlbc_select_window(XLBC_MAC_CTRL_WINDOW);
61375e18fe4SDavid van Moolenbroek 
61475e18fe4SDavid van Moolenbroek 	word = XLBC_READ_16(XLBC_MAC_CTRL_REG);
61575e18fe4SDavid van Moolenbroek 
61675e18fe4SDavid van Moolenbroek 	if (!!(word & XLBC_MAC_CTRL_ENA_FD) == duplex)
61775e18fe4SDavid van Moolenbroek 		return; /* already in the desired mode */
61875e18fe4SDavid van Moolenbroek 
61975e18fe4SDavid van Moolenbroek 	/*
62075e18fe4SDavid van Moolenbroek 	 * Change duplex mode.  Unfortunately, that also means we need to
62175e18fe4SDavid van Moolenbroek 	 * reset the RX and TX engines.  Fortunately, this should happen only
62275e18fe4SDavid van Moolenbroek 	 * on a link change, so we're probably not doing much extra damage.
62375e18fe4SDavid van Moolenbroek 	 * TODO: recovery for packets currently on the transmission queue.
62475e18fe4SDavid van Moolenbroek 	 */
625*f7df02e7SDavid van Moolenbroek 	XLBC_DEBUG(("%s: %s full-duplex mode\n", netdriver_name(),
62675e18fe4SDavid van Moolenbroek 	    duplex ? "setting" : "clearing"));
62775e18fe4SDavid van Moolenbroek 
62875e18fe4SDavid van Moolenbroek 	XLBC_WRITE_16(XLBC_MAC_CTRL_REG, word ^ XLBC_MAC_CTRL_ENA_FD);
62975e18fe4SDavid van Moolenbroek 
63075e18fe4SDavid van Moolenbroek 	xlbc_reset_rx();
63175e18fe4SDavid van Moolenbroek 
63275e18fe4SDavid van Moolenbroek 	xlbc_reset_tx();
63375e18fe4SDavid van Moolenbroek }
63475e18fe4SDavid van Moolenbroek 
63575e18fe4SDavid van Moolenbroek /*
63675e18fe4SDavid van Moolenbroek  * The link status has changed.
63775e18fe4SDavid van Moolenbroek  */
63875e18fe4SDavid van Moolenbroek static void
xlbc_link_event(void)63975e18fe4SDavid van Moolenbroek xlbc_link_event(void)
64075e18fe4SDavid van Moolenbroek {
64175e18fe4SDavid van Moolenbroek 	enum xlbc_link_type link_type;
64275e18fe4SDavid van Moolenbroek 
64375e18fe4SDavid van Moolenbroek 	/*
64475e18fe4SDavid van Moolenbroek 	 * The 3c90xB is documented to require a read from the internal
64575e18fe4SDavid van Moolenbroek 	 * auto-negotiation expansion MII register in order to clear the link
64675e18fe4SDavid van Moolenbroek 	 * event interrupt.  The 3c90xC resets the link event interrupt as part
64775e18fe4SDavid van Moolenbroek 	 * of automatic interrupt acknowledgment.
64875e18fe4SDavid van Moolenbroek 	 */
64975e18fe4SDavid van Moolenbroek 	(void)xlbc_mii_read(XLBC_PHY_ADDR, XLBC_MII_AUTONEG_EXP);
65075e18fe4SDavid van Moolenbroek 
65175e18fe4SDavid van Moolenbroek 	link_type = xlbc_get_link_type();
65275e18fe4SDavid van Moolenbroek 
65375e18fe4SDavid van Moolenbroek #if VERBOSE
654*f7df02e7SDavid van Moolenbroek 	XLBC_DEBUG(("%s: link %s\n", netdriver_name(),
65575e18fe4SDavid van Moolenbroek 	    xlbc_get_link_name(link_type)));
65675e18fe4SDavid van Moolenbroek #endif
65775e18fe4SDavid van Moolenbroek 
65875e18fe4SDavid van Moolenbroek 	xlbc_set_duplex(link_type);
65975e18fe4SDavid van Moolenbroek }
66075e18fe4SDavid van Moolenbroek 
66175e18fe4SDavid van Moolenbroek /*
66275e18fe4SDavid van Moolenbroek  * Initialize the device.
66375e18fe4SDavid van Moolenbroek  */
66475e18fe4SDavid van Moolenbroek static void
xlbc_init_hw(int devind,netdriver_addr_t * addr)665*f7df02e7SDavid van Moolenbroek xlbc_init_hw(int devind, netdriver_addr_t * addr)
66675e18fe4SDavid van Moolenbroek {
66775e18fe4SDavid van Moolenbroek 	uint32_t bar;
66875e18fe4SDavid van Moolenbroek 	uint16_t cr;
66975e18fe4SDavid van Moolenbroek 	int r, io, irq;
67075e18fe4SDavid van Moolenbroek 
67175e18fe4SDavid van Moolenbroek 	/* Map in the device's memory-mapped registers. */
67275e18fe4SDavid van Moolenbroek 	if ((r = pci_get_bar(devind, PCI_BAR_2, &bar, &state.size, &io)) != OK)
67375e18fe4SDavid van Moolenbroek 		panic("unable to retrieve bar: %d", r);
67475e18fe4SDavid van Moolenbroek 
67575e18fe4SDavid van Moolenbroek 	if (state.size < XLBC_MIN_REG_SIZE || io)
67675e18fe4SDavid van Moolenbroek 		panic("invalid register bar");
67775e18fe4SDavid van Moolenbroek 
67875e18fe4SDavid van Moolenbroek 	state.base = vm_map_phys(SELF, (void *)bar, state.size);
67975e18fe4SDavid van Moolenbroek 	if (state.base == MAP_FAILED)
68075e18fe4SDavid van Moolenbroek 		panic("unable to map in registers");
68175e18fe4SDavid van Moolenbroek 
68275e18fe4SDavid van Moolenbroek 	/* Reset the device to a known initial state. */
68375e18fe4SDavid van Moolenbroek 	if (!xlbc_reset())
68475e18fe4SDavid van Moolenbroek 		panic("unable to reset hardware");
68575e18fe4SDavid van Moolenbroek 
68675e18fe4SDavid van Moolenbroek 	/* Now that the device is reset, enable bus mastering if needed. */
68775e18fe4SDavid van Moolenbroek 	cr = pci_attr_r8(devind, PCI_CR);
68875e18fe4SDavid van Moolenbroek 	if (!(cr & PCI_CR_MAST_EN))
68975e18fe4SDavid van Moolenbroek 		pci_attr_w8(devind, PCI_CR, cr | PCI_CR_MAST_EN);
69075e18fe4SDavid van Moolenbroek 
69175e18fe4SDavid van Moolenbroek 	/* Obtain and apply the hardware address. */
69275e18fe4SDavid van Moolenbroek 	xlbc_get_hwaddr(addr);
69375e18fe4SDavid van Moolenbroek 
69475e18fe4SDavid van Moolenbroek 	xlbc_set_hwaddr(addr);
69575e18fe4SDavid van Moolenbroek 
69675e18fe4SDavid van Moolenbroek 	/* Perform various one-time initialization actions. */
69775e18fe4SDavid van Moolenbroek 	xlbc_init_once();
69875e18fe4SDavid van Moolenbroek 
69975e18fe4SDavid van Moolenbroek 	/* Allocate memory for DMA. */
70075e18fe4SDavid van Moolenbroek 	xlbc_alloc_dma();
70175e18fe4SDavid van Moolenbroek 
70275e18fe4SDavid van Moolenbroek 	/* Initialize the transmitter. */
70375e18fe4SDavid van Moolenbroek 	xlbc_reset_tx();
70475e18fe4SDavid van Moolenbroek 
70575e18fe4SDavid van Moolenbroek 	/* Initialize the receiver. */
70675e18fe4SDavid van Moolenbroek 	state.filter = XLBC_FILTER_STATION;
70775e18fe4SDavid van Moolenbroek 
70875e18fe4SDavid van Moolenbroek 	xlbc_reset_rx();
70975e18fe4SDavid van Moolenbroek 
71075e18fe4SDavid van Moolenbroek 	/* Enable interrupts. */
71175e18fe4SDavid van Moolenbroek 	irq = pci_attr_r8(devind, PCI_ILR);
71275e18fe4SDavid van Moolenbroek 	state.hook_id = 0;
71375e18fe4SDavid van Moolenbroek 
71475e18fe4SDavid van Moolenbroek 	if ((r = sys_irqsetpolicy(irq, 0, &state.hook_id)) != OK)
71575e18fe4SDavid van Moolenbroek 		panic("unable to register IRQ: %d", r);
71675e18fe4SDavid van Moolenbroek 
71775e18fe4SDavid van Moolenbroek 	if ((r = sys_irqenable(&state.hook_id)) != OK)
71875e18fe4SDavid van Moolenbroek 		panic("unable to enable IRQ: %d", r);
71975e18fe4SDavid van Moolenbroek 
72075e18fe4SDavid van Moolenbroek 	xlbc_issue_cmd(XLBC_CMD_INT_ENABLE | XLBC_STATUS_MASK);
72175e18fe4SDavid van Moolenbroek 
72275e18fe4SDavid van Moolenbroek 	/*
72375e18fe4SDavid van Moolenbroek 	 * We will probably get a link event anyway, but trigger one now in
72475e18fe4SDavid van Moolenbroek 	 * case that does not happen.  The main purpose of this call is to
72575e18fe4SDavid van Moolenbroek 	 * set the right duplex mode.
72675e18fe4SDavid van Moolenbroek 	 */
72775e18fe4SDavid van Moolenbroek 	xlbc_link_event();
72875e18fe4SDavid van Moolenbroek }
72975e18fe4SDavid van Moolenbroek 
73075e18fe4SDavid van Moolenbroek /*
73175e18fe4SDavid van Moolenbroek  * Initialize the 3c90x driver and device.
73275e18fe4SDavid van Moolenbroek  */
73375e18fe4SDavid van Moolenbroek static int
xlbc_init(unsigned int instance,netdriver_addr_t * addr,uint32_t * caps,unsigned int * ticks)734*f7df02e7SDavid van Moolenbroek xlbc_init(unsigned int instance, netdriver_addr_t * addr, uint32_t * caps,
735*f7df02e7SDavid van Moolenbroek 	unsigned int * ticks)
73675e18fe4SDavid van Moolenbroek {
73775e18fe4SDavid van Moolenbroek 	int devind;
73875e18fe4SDavid van Moolenbroek 
73975e18fe4SDavid van Moolenbroek 	memset(&state, 0, sizeof(state));
74075e18fe4SDavid van Moolenbroek 
74175e18fe4SDavid van Moolenbroek 	/* Try to find a recognized device. */
74275e18fe4SDavid van Moolenbroek 	if ((devind = xlbc_probe(instance)) < 0)
74375e18fe4SDavid van Moolenbroek 		return ENXIO;
74475e18fe4SDavid van Moolenbroek 
74575e18fe4SDavid van Moolenbroek 	/* Initialize the device. */
74675e18fe4SDavid van Moolenbroek 	xlbc_init_hw(devind, addr);
74775e18fe4SDavid van Moolenbroek 
748*f7df02e7SDavid van Moolenbroek 	*caps = NDEV_CAP_MCAST | NDEV_CAP_BCAST;
749*f7df02e7SDavid van Moolenbroek 	*ticks = sys_hz() / 10; /* update statistics 10x/sec */
75075e18fe4SDavid van Moolenbroek 	return OK;
75175e18fe4SDavid van Moolenbroek }
75275e18fe4SDavid van Moolenbroek 
75375e18fe4SDavid van Moolenbroek /*
75475e18fe4SDavid van Moolenbroek  * Stop the device.  The main purpose is to stop any ongoing and future DMA.
75575e18fe4SDavid van Moolenbroek  */
75675e18fe4SDavid van Moolenbroek static void
xlbc_stop(void)75775e18fe4SDavid van Moolenbroek xlbc_stop(void)
75875e18fe4SDavid van Moolenbroek {
75975e18fe4SDavid van Moolenbroek 
76075e18fe4SDavid van Moolenbroek 	/* A full reset ought to do it. */
76175e18fe4SDavid van Moolenbroek 	(void)xlbc_reset();
76275e18fe4SDavid van Moolenbroek }
76375e18fe4SDavid van Moolenbroek 
76475e18fe4SDavid van Moolenbroek /*
76575e18fe4SDavid van Moolenbroek  * Set packet receipt mode.
76675e18fe4SDavid van Moolenbroek  */
76775e18fe4SDavid van Moolenbroek static void
xlbc_set_mode(unsigned int mode,const netdriver_addr_t * mcast_list __unused,unsigned int mcast_count __unused)768*f7df02e7SDavid van Moolenbroek xlbc_set_mode(unsigned int mode, const netdriver_addr_t * mcast_list __unused,
769*f7df02e7SDavid van Moolenbroek 	unsigned int mcast_count __unused)
77075e18fe4SDavid van Moolenbroek {
77175e18fe4SDavid van Moolenbroek 
77275e18fe4SDavid van Moolenbroek 	state.filter = XLBC_FILTER_STATION;
77375e18fe4SDavid van Moolenbroek 
774*f7df02e7SDavid van Moolenbroek 	if (mode & (NDEV_MODE_MCAST_LIST | NDEV_MODE_MCAST_ALL))
77575e18fe4SDavid van Moolenbroek 		state.filter |= XLBC_FILTER_MULTI;
776*f7df02e7SDavid van Moolenbroek 	if (mode & NDEV_MODE_BCAST)
77775e18fe4SDavid van Moolenbroek 		state.filter |= XLBC_FILTER_BROAD;
778*f7df02e7SDavid van Moolenbroek 	if (mode & NDEV_MODE_PROMISC)
77975e18fe4SDavid van Moolenbroek 		state.filter |= XLBC_FILTER_PROMISC;
78075e18fe4SDavid van Moolenbroek 
78175e18fe4SDavid van Moolenbroek 	xlbc_issue_cmd(XLBC_CMD_SET_FILTER | state.filter);
78275e18fe4SDavid van Moolenbroek }
78375e18fe4SDavid van Moolenbroek 
78475e18fe4SDavid van Moolenbroek /*
78575e18fe4SDavid van Moolenbroek  * Try to receive a packet.
78675e18fe4SDavid van Moolenbroek  */
78775e18fe4SDavid van Moolenbroek static ssize_t
xlbc_recv(struct netdriver_data * data,size_t max)78875e18fe4SDavid van Moolenbroek xlbc_recv(struct netdriver_data * data, size_t max)
78975e18fe4SDavid van Moolenbroek {
79075e18fe4SDavid van Moolenbroek 	uint32_t flags;
79175e18fe4SDavid van Moolenbroek 	uint8_t *ptr;
79275e18fe4SDavid van Moolenbroek 	unsigned int head;
79375e18fe4SDavid van Moolenbroek 	size_t len;
79475e18fe4SDavid van Moolenbroek 
79575e18fe4SDavid van Moolenbroek 	head = state.upd_head;
79675e18fe4SDavid van Moolenbroek 	flags = *(volatile uint32_t *)&state.upd_base[head].flags;
79775e18fe4SDavid van Moolenbroek 
79875e18fe4SDavid van Moolenbroek 	/*
79975e18fe4SDavid van Moolenbroek 	 * The documentation implies, but does not state, that UP_COMPLETE is
80075e18fe4SDavid van Moolenbroek 	 * set whenever UP_ERROR is.  We rely exclusively on UP_COMPLETE.
80175e18fe4SDavid van Moolenbroek 	 */
80275e18fe4SDavid van Moolenbroek 	if (!(flags & XLBC_UP_COMPLETE))
80375e18fe4SDavid van Moolenbroek 		return SUSPEND;
80475e18fe4SDavid van Moolenbroek 
80575e18fe4SDavid van Moolenbroek 	if (flags & XLBC_UP_ERROR) {
806*f7df02e7SDavid van Moolenbroek 		XLBC_DEBUG(("%s: received error\n", netdriver_name()));
80775e18fe4SDavid van Moolenbroek 
808*f7df02e7SDavid van Moolenbroek 		netdriver_stat_ierror(1);
80975e18fe4SDavid van Moolenbroek 
81075e18fe4SDavid van Moolenbroek 		len = 0; /* immediately move on to the next descriptor */
81175e18fe4SDavid van Moolenbroek 	} else {
81275e18fe4SDavid van Moolenbroek 		len = flags & XLBC_UP_LEN;
81375e18fe4SDavid van Moolenbroek 
814*f7df02e7SDavid van Moolenbroek 		XLBC_DEBUG(("%s: received packet (size %zu)\n",
815*f7df02e7SDavid van Moolenbroek 		    netdriver_name(), len));
81675e18fe4SDavid van Moolenbroek 
81775e18fe4SDavid van Moolenbroek 		/* The device is supposed to not give us runt frames. */
81875e18fe4SDavid van Moolenbroek 		assert(len >= XLBC_MIN_PKT_LEN);
81975e18fe4SDavid van Moolenbroek 
82075e18fe4SDavid van Moolenbroek 		/* Truncate large packets. */
82175e18fe4SDavid van Moolenbroek 		if (flags & XLBC_UP_OVERFLOW)
82275e18fe4SDavid van Moolenbroek 			len = XLBC_MAX_PKT_LEN;
82375e18fe4SDavid van Moolenbroek 		if (len > max)
82475e18fe4SDavid van Moolenbroek 			len = max;
82575e18fe4SDavid van Moolenbroek 
82675e18fe4SDavid van Moolenbroek 		ptr = state.rxb_base + head * XLBC_MAX_PKT_LEN;
82775e18fe4SDavid van Moolenbroek 
82875e18fe4SDavid van Moolenbroek 		netdriver_copyout(data, 0, ptr, len);
82975e18fe4SDavid van Moolenbroek 	}
83075e18fe4SDavid van Moolenbroek 
83175e18fe4SDavid van Moolenbroek 	/* Mark the descriptor as ready for reuse. */
83275e18fe4SDavid van Moolenbroek 	*(volatile uint32_t *)&state.upd_base[head].flags = 0;
83375e18fe4SDavid van Moolenbroek 
83475e18fe4SDavid van Moolenbroek 	/*
83575e18fe4SDavid van Moolenbroek 	 * At this point, the receive engine may have stalled as a result of
83675e18fe4SDavid van Moolenbroek 	 * filling up all descriptors.  Now that we have a free descriptor, we
83775e18fe4SDavid van Moolenbroek 	 * can restart it.  As per the documentation, we unstall blindly.
83875e18fe4SDavid van Moolenbroek 	 */
83975e18fe4SDavid van Moolenbroek 	xlbc_issue_cmd(XLBC_CMD_UP_UNSTALL);
84075e18fe4SDavid van Moolenbroek 
84175e18fe4SDavid van Moolenbroek 	/* Advance to the next descriptor in our ring. */
84275e18fe4SDavid van Moolenbroek 	state.upd_head = (head + 1) % XLBC_UPD_COUNT;
84375e18fe4SDavid van Moolenbroek 
84475e18fe4SDavid van Moolenbroek 	return len;
84575e18fe4SDavid van Moolenbroek }
84675e18fe4SDavid van Moolenbroek 
84775e18fe4SDavid van Moolenbroek /*
84875e18fe4SDavid van Moolenbroek  * Return how much padding (if any) must be prepended to a packet of the given
84975e18fe4SDavid van Moolenbroek  * size so that it does not have to be split due to wrapping.  The given offset
85075e18fe4SDavid van Moolenbroek  * is the starting point of the packet; this may be beyond the transmission
85175e18fe4SDavid van Moolenbroek  * buffer size in the case that the current buffer contents already wrap.
85275e18fe4SDavid van Moolenbroek  */
85375e18fe4SDavid van Moolenbroek static size_t
xlbc_pad_tx(size_t off,size_t size)85475e18fe4SDavid van Moolenbroek xlbc_pad_tx(size_t off, size_t size)
85575e18fe4SDavid van Moolenbroek {
85675e18fe4SDavid van Moolenbroek 
85775e18fe4SDavid van Moolenbroek 	if (off < XLBC_TXB_SIZE && off + size >= XLBC_TXB_SIZE)
85875e18fe4SDavid van Moolenbroek 		return XLBC_TXB_SIZE - off;
85975e18fe4SDavid van Moolenbroek 	else
86075e18fe4SDavid van Moolenbroek 		return 0;
86175e18fe4SDavid van Moolenbroek }
86275e18fe4SDavid van Moolenbroek 
86375e18fe4SDavid van Moolenbroek /*
86475e18fe4SDavid van Moolenbroek  * Try to send a packet.
86575e18fe4SDavid van Moolenbroek  */
86675e18fe4SDavid van Moolenbroek static int
xlbc_send(struct netdriver_data * data,size_t size)86775e18fe4SDavid van Moolenbroek xlbc_send(struct netdriver_data * data, size_t size)
86875e18fe4SDavid van Moolenbroek {
86975e18fe4SDavid van Moolenbroek 	size_t used, off, left;
87075e18fe4SDavid van Moolenbroek 	unsigned int head, last;
87175e18fe4SDavid van Moolenbroek 	uint32_t phys;
87275e18fe4SDavid van Moolenbroek 
87375e18fe4SDavid van Moolenbroek 	/* We need a free transmission descriptor. */
87475e18fe4SDavid van Moolenbroek 	if (state.dpd_used == XLBC_DPD_COUNT)
87575e18fe4SDavid van Moolenbroek 		return SUSPEND;
87675e18fe4SDavid van Moolenbroek 
87775e18fe4SDavid van Moolenbroek 	/*
87875e18fe4SDavid van Moolenbroek 	 * See if we can fit the packet in the circular transmission buffer.
87975e18fe4SDavid van Moolenbroek 	 * The packet may not be broken up in two parts as the buffer wraps.
88075e18fe4SDavid van Moolenbroek 	 */
88175e18fe4SDavid van Moolenbroek 	used = state.txb_used;
88275e18fe4SDavid van Moolenbroek 	used += xlbc_pad_tx(state.txb_tail + used, size);
88375e18fe4SDavid van Moolenbroek 	left = XLBC_TXB_SIZE - used;
88475e18fe4SDavid van Moolenbroek 
88575e18fe4SDavid van Moolenbroek 	if (left < size)
88675e18fe4SDavid van Moolenbroek 		return SUSPEND;
88775e18fe4SDavid van Moolenbroek 
888*f7df02e7SDavid van Moolenbroek 	XLBC_DEBUG(("%s: transmitting packet (size %zu)\n",
889*f7df02e7SDavid van Moolenbroek 	    netdriver_name(), size));
89075e18fe4SDavid van Moolenbroek 
89175e18fe4SDavid van Moolenbroek 	/* Copy in the packet. */
89275e18fe4SDavid van Moolenbroek 	off = (state.txb_tail + used) % XLBC_TXB_SIZE;
89375e18fe4SDavid van Moolenbroek 
89475e18fe4SDavid van Moolenbroek 	netdriver_copyin(data, 0, &state.txb_base[off], size);
89575e18fe4SDavid van Moolenbroek 
89675e18fe4SDavid van Moolenbroek 	/* Set up a descriptor for the packet. */
89775e18fe4SDavid van Moolenbroek 	head = (state.dpd_tail + state.dpd_used) % XLBC_DPD_COUNT;
89875e18fe4SDavid van Moolenbroek 
89975e18fe4SDavid van Moolenbroek 	state.dpd_base[head].next = 0;
90075e18fe4SDavid van Moolenbroek 	state.dpd_base[head].flags = XLBC_DN_RNDUP_WORD | XLBC_DN_DN_INDICATE;
90175e18fe4SDavid van Moolenbroek 	state.dpd_base[head].addr = state.txb_phys + off;
90275e18fe4SDavid van Moolenbroek 	state.dpd_base[head].len = XLBC_LEN_LAST | size;
90375e18fe4SDavid van Moolenbroek 
90475e18fe4SDavid van Moolenbroek 	phys = state.dpd_phys + head * sizeof(xlbc_pd_t);
90575e18fe4SDavid van Moolenbroek 
90675e18fe4SDavid van Moolenbroek 	__insn_barrier();
90775e18fe4SDavid van Moolenbroek 
90875e18fe4SDavid van Moolenbroek 	/* We need to stall only if other packets were already pending. */
90975e18fe4SDavid van Moolenbroek 	if (XLBC_READ_32(XLBC_DN_LIST_PTR_REG) != 0) {
91075e18fe4SDavid van Moolenbroek 		assert(state.dpd_used > 0);
91175e18fe4SDavid van Moolenbroek 
91275e18fe4SDavid van Moolenbroek 		xlbc_issue_cmd(XLBC_CMD_DN_STALL);
91375e18fe4SDavid van Moolenbroek 		if (!xlbc_wait_cmd())
91475e18fe4SDavid van Moolenbroek 			panic("timeout trying to stall downloads");
91575e18fe4SDavid van Moolenbroek 
91675e18fe4SDavid van Moolenbroek 		last = (state.dpd_tail + state.dpd_used - 1) % XLBC_DPD_COUNT;
91775e18fe4SDavid van Moolenbroek 		state.dpd_base[last].next = phys;
91875e18fe4SDavid van Moolenbroek 		/* Group interrupts a bit.  This is a tradeoff. */
91975e18fe4SDavid van Moolenbroek 		state.dpd_base[last].flags &= ~XLBC_DN_DN_INDICATE;
92075e18fe4SDavid van Moolenbroek 
92175e18fe4SDavid van Moolenbroek 		if (XLBC_READ_32(XLBC_DN_LIST_PTR_REG) == 0)
92275e18fe4SDavid van Moolenbroek 			XLBC_WRITE_32(XLBC_DN_LIST_PTR_REG, phys);
92375e18fe4SDavid van Moolenbroek 
92475e18fe4SDavid van Moolenbroek 		xlbc_issue_cmd(XLBC_CMD_DN_UNSTALL);
92575e18fe4SDavid van Moolenbroek 	} else
92675e18fe4SDavid van Moolenbroek 		XLBC_WRITE_32(XLBC_DN_LIST_PTR_REG, phys);
92775e18fe4SDavid van Moolenbroek 
92875e18fe4SDavid van Moolenbroek 	/* Advance internal queue heads. */
92975e18fe4SDavid van Moolenbroek 	state.dpd_used++;
93075e18fe4SDavid van Moolenbroek 
93175e18fe4SDavid van Moolenbroek 	state.txb_used = used + size;
93275e18fe4SDavid van Moolenbroek 	assert(state.txb_used <= XLBC_TXB_SIZE);
93375e18fe4SDavid van Moolenbroek 
93475e18fe4SDavid van Moolenbroek 	return OK;
93575e18fe4SDavid van Moolenbroek }
93675e18fe4SDavid van Moolenbroek 
93775e18fe4SDavid van Moolenbroek /*
93875e18fe4SDavid van Moolenbroek  * One or more packets have been downloaded.  Free up the corresponding
93975e18fe4SDavid van Moolenbroek  * descriptors for later reuse.
94075e18fe4SDavid van Moolenbroek  */
94175e18fe4SDavid van Moolenbroek static void
xlbc_advance_tx(void)94275e18fe4SDavid van Moolenbroek xlbc_advance_tx(void)
94375e18fe4SDavid van Moolenbroek {
94475e18fe4SDavid van Moolenbroek 	uint32_t flags, len;
94575e18fe4SDavid van Moolenbroek 
94675e18fe4SDavid van Moolenbroek 	while (state.dpd_used > 0) {
94775e18fe4SDavid van Moolenbroek 		flags = *(volatile uint32_t *)
94875e18fe4SDavid van Moolenbroek 		    &state.dpd_base[state.dpd_tail].flags;
94975e18fe4SDavid van Moolenbroek 
95075e18fe4SDavid van Moolenbroek 		if (!(flags & XLBC_DN_DN_COMPLETE))
95175e18fe4SDavid van Moolenbroek 			break;
95275e18fe4SDavid van Moolenbroek 
953*f7df02e7SDavid van Moolenbroek 		XLBC_DEBUG(("%s: packet copied to transmitter\n",
954*f7df02e7SDavid van Moolenbroek 		    netdriver_name()));
95575e18fe4SDavid van Moolenbroek 
95675e18fe4SDavid van Moolenbroek 		len = state.dpd_base[state.dpd_tail].len & ~XLBC_LEN_LAST;
95775e18fe4SDavid van Moolenbroek 
95875e18fe4SDavid van Moolenbroek 		state.dpd_tail = (state.dpd_tail + 1) % XLBC_DPD_COUNT;
95975e18fe4SDavid van Moolenbroek 		state.dpd_used--;
96075e18fe4SDavid van Moolenbroek 
96175e18fe4SDavid van Moolenbroek 		len += xlbc_pad_tx(state.txb_tail, len);
96275e18fe4SDavid van Moolenbroek 		assert(state.txb_used >= len);
96375e18fe4SDavid van Moolenbroek 
96475e18fe4SDavid van Moolenbroek 		state.txb_tail = (state.txb_tail + len) % XLBC_TXB_SIZE;
96575e18fe4SDavid van Moolenbroek 		state.txb_used -= len;
96675e18fe4SDavid van Moolenbroek 	}
96775e18fe4SDavid van Moolenbroek }
96875e18fe4SDavid van Moolenbroek 
96975e18fe4SDavid van Moolenbroek /*
97075e18fe4SDavid van Moolenbroek  * A transmission error has occurred.  Restart, and if necessary even reset,
97175e18fe4SDavid van Moolenbroek  * the transmitter.
97275e18fe4SDavid van Moolenbroek  */
97375e18fe4SDavid van Moolenbroek static void
xlbc_recover_tx(void)97475e18fe4SDavid van Moolenbroek xlbc_recover_tx(void)
97575e18fe4SDavid van Moolenbroek {
97675e18fe4SDavid van Moolenbroek 	uint8_t status;
97775e18fe4SDavid van Moolenbroek 	int enable, reset;
97875e18fe4SDavid van Moolenbroek 
97975e18fe4SDavid van Moolenbroek 	enable = reset = FALSE;
98075e18fe4SDavid van Moolenbroek 
98175e18fe4SDavid van Moolenbroek 	while ((status = XLBC_READ_8(XLBC_TX_STATUS_REG)) &
98275e18fe4SDavid van Moolenbroek 	    XLBC_TX_STATUS_COMPLETE) {
983*f7df02e7SDavid van Moolenbroek 		XLBC_DEBUG(("%s: transmission error (0x%04x)\n",
984*f7df02e7SDavid van Moolenbroek 		    netdriver_name(), status));
98575e18fe4SDavid van Moolenbroek 
98675e18fe4SDavid van Moolenbroek 		/* This is an internal (non-packet) error status. */
98775e18fe4SDavid van Moolenbroek 		if (status & XLBC_TX_STATUS_OVERFLOW)
98875e18fe4SDavid van Moolenbroek 			enable = TRUE;
98975e18fe4SDavid van Moolenbroek 
99075e18fe4SDavid van Moolenbroek 		if (status & XLBC_TX_STATUS_MAX_COLL) {
991*f7df02e7SDavid van Moolenbroek 			netdriver_stat_coll(1);
99275e18fe4SDavid van Moolenbroek 			enable = TRUE;
99375e18fe4SDavid van Moolenbroek 		}
994*f7df02e7SDavid van Moolenbroek 		if (status &
995*f7df02e7SDavid van Moolenbroek 		    (XLBC_TX_STATUS_UNDERRUN | XLBC_TX_STATUS_JABBER)) {
996*f7df02e7SDavid van Moolenbroek 			netdriver_stat_oerror(1);
99775e18fe4SDavid van Moolenbroek 			reset = TRUE;
99875e18fe4SDavid van Moolenbroek 		}
99975e18fe4SDavid van Moolenbroek 
100075e18fe4SDavid van Moolenbroek 		XLBC_WRITE_8(XLBC_TX_STATUS_REG, status);
100175e18fe4SDavid van Moolenbroek 	}
100275e18fe4SDavid van Moolenbroek 
100375e18fe4SDavid van Moolenbroek 	if (reset) {
100475e18fe4SDavid van Moolenbroek 		/*
100575e18fe4SDavid van Moolenbroek 		 * Below is the documented Underrun Recovery procedure.  We use
100675e18fe4SDavid van Moolenbroek 		 * it for jabber errors as well, because there is no indication
100775e18fe4SDavid van Moolenbroek 		 * that another procedure should be followed for that case.
100875e18fe4SDavid van Moolenbroek 		 */
100975e18fe4SDavid van Moolenbroek 		xlbc_issue_cmd(XLBC_CMD_DN_STALL);
101075e18fe4SDavid van Moolenbroek 		if (!xlbc_wait_cmd())
101175e18fe4SDavid van Moolenbroek 			panic("download stall timeout during recovery");
101275e18fe4SDavid van Moolenbroek 
101375e18fe4SDavid van Moolenbroek 		SPIN_UNTIL(!(XLBC_READ_32(XLBC_DMA_CTRL_REG) &
101475e18fe4SDavid van Moolenbroek 		    XLBC_DMA_CTRL_DN_INPROG), XLBC_CMD_TIMEOUT);
101575e18fe4SDavid van Moolenbroek 
101675e18fe4SDavid van Moolenbroek 		xlbc_select_window(XLBC_MEDIA_STS_WINDOW);
101775e18fe4SDavid van Moolenbroek 
101875e18fe4SDavid van Moolenbroek 		SPIN_UNTIL(!(XLBC_READ_16(XLBC_MEDIA_STS_REG) &
101975e18fe4SDavid van Moolenbroek 		    XLBC_MEDIA_STS_TX_INPROG), XLBC_CMD_TIMEOUT);
102075e18fe4SDavid van Moolenbroek 
102175e18fe4SDavid van Moolenbroek 		xlbc_issue_cmd(XLBC_CMD_TX_RESET);
102275e18fe4SDavid van Moolenbroek 		if (!xlbc_wait_cmd())
102375e18fe4SDavid van Moolenbroek 			panic("transmitter reset timeout during recovery");
102475e18fe4SDavid van Moolenbroek 
102575e18fe4SDavid van Moolenbroek 		xlbc_issue_cmd(XLBC_CMD_TX_ENABLE);
102675e18fe4SDavid van Moolenbroek 
102775e18fe4SDavid van Moolenbroek 		XLBC_WRITE_32(XLBC_DN_LIST_PTR_REG,
102875e18fe4SDavid van Moolenbroek 		    state.dpd_phys + state.dpd_tail * sizeof(xlbc_pd_t));
102975e18fe4SDavid van Moolenbroek 
1030*f7df02e7SDavid van Moolenbroek 		XLBC_DEBUG(("%s: performed recovery\n", netdriver_name()));
103175e18fe4SDavid van Moolenbroek 	} else if (enable)
103275e18fe4SDavid van Moolenbroek 		xlbc_issue_cmd(XLBC_CMD_TX_ENABLE);
103375e18fe4SDavid van Moolenbroek }
103475e18fe4SDavid van Moolenbroek 
103575e18fe4SDavid van Moolenbroek /*
103675e18fe4SDavid van Moolenbroek  * Update statistics.  We read all registers, not just the ones we are
103775e18fe4SDavid van Moolenbroek  * interested in, so as to limit the number of useless statistics interrupts.
103875e18fe4SDavid van Moolenbroek  */
103975e18fe4SDavid van Moolenbroek static void
xlbc_update_stats(void)104075e18fe4SDavid van Moolenbroek xlbc_update_stats(void)
104175e18fe4SDavid van Moolenbroek {
104275e18fe4SDavid van Moolenbroek 
104375e18fe4SDavid van Moolenbroek 	xlbc_select_window(XLBC_STATS_WINDOW);
104475e18fe4SDavid van Moolenbroek 
1045*f7df02e7SDavid van Moolenbroek 	(void)XLBC_READ_8(XLBC_CARRIER_LOST_REG);
104675e18fe4SDavid van Moolenbroek 	(void)XLBC_READ_8(XLBC_SQE_ERR_REG);
1047*f7df02e7SDavid van Moolenbroek 	netdriver_stat_coll(XLBC_READ_8(XLBC_MULTI_COLL_REG));
1048*f7df02e7SDavid van Moolenbroek 	netdriver_stat_coll(XLBC_READ_8(XLBC_SINGLE_COLL_REG));
1049*f7df02e7SDavid van Moolenbroek 	netdriver_stat_coll(XLBC_READ_8(XLBC_LATE_COLL_REG));
1050*f7df02e7SDavid van Moolenbroek 	netdriver_stat_ierror(XLBC_READ_8(XLBC_RX_OVERRUNS_REG));
1051*f7df02e7SDavid van Moolenbroek 	(void)XLBC_READ_8(XLBC_FRAMES_DEFERRED_REG);
105275e18fe4SDavid van Moolenbroek 
1053*f7df02e7SDavid van Moolenbroek 	(void)XLBC_READ_8(XLBC_UPPER_FRAMES_REG);
1054*f7df02e7SDavid van Moolenbroek 	(void)XLBC_READ_8(XLBC_FRAMES_XMIT_OK_REG);
1055*f7df02e7SDavid van Moolenbroek 	(void)XLBC_READ_8(XLBC_FRAMES_RCVD_OK_REG);
105675e18fe4SDavid van Moolenbroek 
105775e18fe4SDavid van Moolenbroek 	(void)XLBC_READ_16(XLBC_BYTES_RCVD_OK_REG);
105875e18fe4SDavid van Moolenbroek 	(void)XLBC_READ_16(XLBC_BYTES_XMIT_OK_REG);
105975e18fe4SDavid van Moolenbroek 
106075e18fe4SDavid van Moolenbroek 	xlbc_select_window(XLBC_SSD_STATS_WINDOW);
106175e18fe4SDavid van Moolenbroek 
106275e18fe4SDavid van Moolenbroek 	(void)XLBC_READ_8(XLBC_BAD_SSD_REG);
106375e18fe4SDavid van Moolenbroek }
106475e18fe4SDavid van Moolenbroek 
106575e18fe4SDavid van Moolenbroek /*
106675e18fe4SDavid van Moolenbroek  * Process an interrupt.
106775e18fe4SDavid van Moolenbroek  */
106875e18fe4SDavid van Moolenbroek static void
xlbc_intr(unsigned int __unused mask)106975e18fe4SDavid van Moolenbroek xlbc_intr(unsigned int __unused mask)
107075e18fe4SDavid van Moolenbroek {
107175e18fe4SDavid van Moolenbroek 	uint32_t val;
107275e18fe4SDavid van Moolenbroek 	int r;
107375e18fe4SDavid van Moolenbroek 
107475e18fe4SDavid van Moolenbroek 	/*
107575e18fe4SDavid van Moolenbroek 	 * Get interrupt mask.  Acknowledge some interrupts, and disable all
107675e18fe4SDavid van Moolenbroek 	 * interrupts as automatic side effect.  The assumption is that any new
107775e18fe4SDavid van Moolenbroek 	 * events are stored as indications which are then translated into
107875e18fe4SDavid van Moolenbroek 	 * interrupts as soon as interrupts are reenabled, but this is not
107975e18fe4SDavid van Moolenbroek 	 * documented explicitly.
108075e18fe4SDavid van Moolenbroek 	 */
108175e18fe4SDavid van Moolenbroek 	val = XLBC_READ_16(XLBC_STATUS_AUTO_REG);
108275e18fe4SDavid van Moolenbroek 
1083*f7df02e7SDavid van Moolenbroek 	XLBC_DEBUG(("%s: interrupt (0x%04x)\n", netdriver_name(), val));
108475e18fe4SDavid van Moolenbroek 
108575e18fe4SDavid van Moolenbroek 	if (val & XLBC_STATUS_UP_COMPLETE)
108675e18fe4SDavid van Moolenbroek 		netdriver_recv();
108775e18fe4SDavid van Moolenbroek 
108875e18fe4SDavid van Moolenbroek 	if (val & (XLBC_STATUS_DN_COMPLETE | XLBC_STATUS_TX_COMPLETE))
108975e18fe4SDavid van Moolenbroek 		xlbc_advance_tx();
109075e18fe4SDavid van Moolenbroek 
109175e18fe4SDavid van Moolenbroek 	if (val & XLBC_STATUS_TX_COMPLETE)
109275e18fe4SDavid van Moolenbroek 		xlbc_recover_tx();
109375e18fe4SDavid van Moolenbroek 
109475e18fe4SDavid van Moolenbroek 	if (val & XLBC_STATUS_HOST_ERROR) {
109575e18fe4SDavid van Moolenbroek 		/*
109675e18fe4SDavid van Moolenbroek 		 * A catastrophic host error has occurred.  Reset both the
109775e18fe4SDavid van Moolenbroek 		 * transmitter and the receiver.  This should be enough to
109875e18fe4SDavid van Moolenbroek 		 * clear the host error, but may be overkill in the cases where
109975e18fe4SDavid van Moolenbroek 		 * the error direction (TX or RX) can be clearly identified.
110075e18fe4SDavid van Moolenbroek 		 * Since this entire condition is effectively untestable, we
110175e18fe4SDavid van Moolenbroek 		 * do not even try to be smart about it.
110275e18fe4SDavid van Moolenbroek 		 */
1103*f7df02e7SDavid van Moolenbroek 		XLBC_DEBUG(("%s: host error, performing reset\n",
1104*f7df02e7SDavid van Moolenbroek 		    netdriver_name()));
110575e18fe4SDavid van Moolenbroek 
110675e18fe4SDavid van Moolenbroek 		xlbc_reset_tx();
110775e18fe4SDavid van Moolenbroek 
110875e18fe4SDavid van Moolenbroek 		xlbc_reset_rx();
110975e18fe4SDavid van Moolenbroek 
111075e18fe4SDavid van Moolenbroek 		/* If this has not resolved the problem, restart the driver. */
111175e18fe4SDavid van Moolenbroek 		if (XLBC_READ_16(XLBC_STATUS_REG) & XLBC_STATUS_HOST_ERROR)
111275e18fe4SDavid van Moolenbroek 			panic("host error not cleared");
111375e18fe4SDavid van Moolenbroek 	}
111475e18fe4SDavid van Moolenbroek 
111575e18fe4SDavid van Moolenbroek 	if (val & XLBC_STATUS_UPDATE_STATS)
111675e18fe4SDavid van Moolenbroek 		xlbc_update_stats();
111775e18fe4SDavid van Moolenbroek 
111875e18fe4SDavid van Moolenbroek 	if (val & XLBC_STATUS_LINK_EVENT)
111975e18fe4SDavid van Moolenbroek 		xlbc_link_event();
112075e18fe4SDavid van Moolenbroek 
112175e18fe4SDavid van Moolenbroek 	/* See if we should try to send more packets. */
112275e18fe4SDavid van Moolenbroek 	if (val & (XLBC_STATUS_DN_COMPLETE | XLBC_STATUS_TX_COMPLETE |
112375e18fe4SDavid van Moolenbroek 	    XLBC_STATUS_HOST_ERROR))
112475e18fe4SDavid van Moolenbroek 		netdriver_send();
112575e18fe4SDavid van Moolenbroek 
112675e18fe4SDavid van Moolenbroek 	/* Reenable interrupts. */
112775e18fe4SDavid van Moolenbroek 	if ((r = sys_irqenable(&state.hook_id)) != OK)
112875e18fe4SDavid van Moolenbroek 		panic("unable to reenable IRQ: %d", r);
112975e18fe4SDavid van Moolenbroek 
113075e18fe4SDavid van Moolenbroek 	xlbc_issue_cmd(XLBC_CMD_INT_ENABLE | XLBC_STATUS_MASK);
113175e18fe4SDavid van Moolenbroek }
113275e18fe4SDavid van Moolenbroek 
113375e18fe4SDavid van Moolenbroek /*
1134*f7df02e7SDavid van Moolenbroek  * Do regular processing.
113575e18fe4SDavid van Moolenbroek  */
113675e18fe4SDavid van Moolenbroek static void
xlbc_tick(void)1137*f7df02e7SDavid van Moolenbroek xlbc_tick(void)
113875e18fe4SDavid van Moolenbroek {
113975e18fe4SDavid van Moolenbroek 
114075e18fe4SDavid van Moolenbroek 	xlbc_update_stats();
114175e18fe4SDavid van Moolenbroek }
114275e18fe4SDavid van Moolenbroek 
114375e18fe4SDavid van Moolenbroek /*
114475e18fe4SDavid van Moolenbroek  * The 3c90x ethernet driver.
114575e18fe4SDavid van Moolenbroek  */
114675e18fe4SDavid van Moolenbroek int
main(int argc,char ** argv)114775e18fe4SDavid van Moolenbroek main(int argc, char ** argv)
114875e18fe4SDavid van Moolenbroek {
114975e18fe4SDavid van Moolenbroek 
115075e18fe4SDavid van Moolenbroek 	env_setargs(argc, argv);
115175e18fe4SDavid van Moolenbroek 
115275e18fe4SDavid van Moolenbroek 	netdriver_task(&xlbc_table);
115375e18fe4SDavid van Moolenbroek 
115475e18fe4SDavid van Moolenbroek 	return EXIT_SUCCESS;
115575e18fe4SDavid van Moolenbroek }
1156