summaryrefslogtreecommitdiffstats
path: root/src/drivers/net/3c509.c
blob: bc21978f19b0bbdb79e1414eed8a81b42a6310f6 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
/*
 * Split out into 3c509.c and 3c5x9.c, to make it possible to build a
 * 3c529 module without including ISA, ISAPnP and EISA code.
 *
 */

#include "isa.h"
#include "io.h"
#include "timer.h"
#include "string.h"
#include "console.h"
#include "3c509.h"

/*
 * 3c509 cards have their own method of contention resolution; this
 * effectively defines another bus type similar to ISAPnP.  Even the
 * original ISA cards can be programatically mapped to any I/O address
 * in the range 0x200-0x3e0.
 * 
 * However, there is a small problem: once you've activated a card,
 * the only ways to deactivate it will also wipe its tag, meaning that
 * you won't be able to subsequently reactivate it without going
 * through the whole ID sequence again.  The solution we adopt is to
 * isolate and tag all cards at the start, and to immediately
 * re-isolate and re-tag a card after disabling it.
 *
 */

static uint16_t t509_id_port = 0;
static uint8_t t509_max_tag = 0;

/*
 * A location on a t509 bus
 *
 */
struct t509_loc {
	uint8_t tag;
};

/*
 * A physical t509 device
 *
 */
struct t509_device {
	uint16_t ioaddr;
	uint8_t tag;
};

/*
 * t509 utility functions
 *
 */

static inline void t509_set_id_port ( void ) {
	outb ( 0x00, t509_id_port );
}

static inline void t509_wait_for_id_sequence ( void ) {
	outb ( 0x00, t509_id_port );
}

static inline void t509_global_reset ( void ) {
	outb ( 0xc0, t509_id_port );
}

static inline void t509_reset_tag ( void ) {
	outb ( 0xd0, t509_id_port );
}

static inline void t509_set_tag ( uint8_t tag ) {
	outb ( 0xd0 | tag, t509_id_port );
}

static inline void t509_select_tag ( uint8_t tag ) {
	outb ( 0xd8 | tag, t509_id_port );
}

static inline void t509_activate ( uint16_t ioaddr ) {
	outb ( 0xe0 | ( ioaddr >> 4 ), t509_id_port );
}

static inline void t509_deactivate_and_reset_tag ( uint16_t ioaddr ) {
	outb ( GLOBAL_RESET, ioaddr + EP_COMMAND );
}

static inline void t509_load_eeprom_word ( uint8_t offset ) {
	outb ( 0x80 | offset, t509_id_port );
}

/*
 * Find a suitable ID port
 *
 */
static inline int t509_find_id_port ( void ) {

	for ( t509_id_port = EP_ID_PORT_START ;
	      t509_id_port < EP_ID_PORT_END ;
	      t509_id_port += EP_ID_PORT_INC ) {
		t509_set_id_port ();
		/* See if anything's listening */
		outb ( 0xff, t509_id_port );
		if ( inb ( t509_id_port ) & 0x01 ) {
			/* Found a suitable port */
			DBG ( "T509 using ID port at %hx\n", t509_id_port );
			return 1;
		}
	}
	/* No id port available */
	DBG ( "T509 found no available ID port\n" );
	return 0;
}

/*
 * Send ID sequence to the ID port
 *
 */
static void t509_send_id_sequence ( void ) {
	unsigned short lrs_state, i;

	t509_set_id_port ();
	/* Reset IDS on cards */
	t509_wait_for_id_sequence ();
	lrs_state = 0xff;
        for ( i = 0; i < 255; i++ ) {
                outb ( lrs_state, t509_id_port );
                lrs_state <<= 1;
                lrs_state = lrs_state & 0x100 ? lrs_state ^ 0xcf : lrs_state;
        }
}

/*
 * We get eeprom data from the id_port given an offset into the eeprom.
 * Basically; after the ID_sequence is sent to all of the cards; they enter
 * the ID_CMD state where they will accept command requests. 0x80-0xbf loads
 * the eeprom data.  We then read the port 16 times and with every read; the
 * cards check for contention (ie: if one card writes a 0 bit and another
 * writes a 1 bit then the host sees a 0. At the end of the cycle; each card
 * compares the data on the bus; if there is a difference then that card goes
 * into ID_WAIT state again). In the meantime; one bit of data is returned in
 * the AX register which is conveniently returned to us by inb().  Hence; we
 * read 16 times getting one bit of data with each read.
 */
static uint16_t t509_id_read_eeprom ( int offset ) {
	int i, data = 0;

	t509_load_eeprom_word ( offset );
	/* Do we really need this wait? Won't be noticeable anyway */
	udelay(10000);

	for ( i = 0; i < 16; i++ ) {
		data = ( data << 1 ) | ( inw ( t509_id_port ) & 1 );
	}
	return data;
}

/*
 * Isolate and tag all t509 cards
 *
 */
static void t509_isolate ( void ) {
	unsigned int i;
	uint16_t contend[3];

	/* Find a suitable ID port */
	if ( ! t509_find_id_port () )
		return;

	while ( 1 ) {

		/* All cards are in ID_WAIT state each time we go
		 * through this loop.
		 */

		/* Send the ID sequence */
		t509_send_id_sequence ();

		/* First time through, reset all tags.  On subsequent
		 * iterations, kill off any already-tagged cards
		 */
		if ( t509_max_tag == 0 ) {
			t509_reset_tag();
		} else {
			t509_select_tag(0);
		}
	
		/* Read the manufacturer ID, to see if there are any
		 * more cards
		 */
		if ( t509_id_read_eeprom ( EEPROM_MFG_ID ) != MFG_ID ) {
			DBG ( "T509 saw %s signs of life\n",
			      t509_max_tag ? "no further" : "no" );
			break;
		}

		/* Perform contention selection on the MAC address */
		for ( i = 0 ; i < 3 ; i++ ) {
			contend[i] = t509_id_read_eeprom ( i );
		}

		/* Only one device will still be left alive.  Tag it. */
		++t509_max_tag;
		DBG ( "T509 found card %hx%hx%hx, assigning tag %hhx\n",
		      contend[0], contend[1], contend[2], t509_max_tag );
		t509_set_tag ( t509_max_tag );

		/* Return all cards back to ID_WAIT state */
		t509_wait_for_id_sequence();
	}

	DBG ( "T509 found %d cards using ID port %hx\n",
	      t509_max_tag, t509_id_port );
	return;
}

/*
 * Increment a bus_loc structure to the next possible T509 location.
 * Leave the structure zeroed and return 0 if there are no more valid
 * locations.
 *
 */
static int t509_next_location ( struct bus_loc *bus_loc ) {
	struct t509_loc *t509_loc = ( struct t509_loc * ) bus_loc;
	
	/*
	 * Ensure that there is sufficient space in the shared bus
	 * structures for a struct t509_loc and a struct t509_dev,
	 * as mandated by bus.h.
	 *
	 */
	BUS_LOC_CHECK ( struct t509_loc );
	BUS_DEV_CHECK ( struct t509_device );

	return ( t509_loc->tag = ( ++t509_loc->tag & EP_TAG_MAX ) );
}

/*
 * Fill in parameters for a T509 device based on tag
 *
 * Return 1 if device present, 0 otherwise
 *
 */
static int t509_fill_device ( struct bus_dev *bus_dev,
			      struct bus_loc *bus_loc ) {
	struct t509_device *t509 = ( struct t509_device * ) bus_dev;
	struct t509_loc *t509_loc = ( struct t509_loc * ) bus_loc;
	uint16_t iobase;

	/* Copy tag to struct t509 */
	t509->tag = t509_loc->tag;

	/* Tag 0 is never valid, but may be passed in */
	if ( ! t509->tag )
		return 0;

	/* Perform isolation if it hasn't yet been done */
	if ( ! t509_id_port )
		t509_isolate();

	/* Check tag is in range */
	if ( t509->tag > t509_max_tag )
		return 0;

	/* Send the ID sequence */
	t509_send_id_sequence ();

	/* Select the specified tag */
	t509_select_tag ( t509->tag );

	/* Read the default I/O address */
	iobase = t509_id_read_eeprom ( EEPROM_ADDR_CFG );
	t509->ioaddr = 0x200 + ( ( iobase & 0x1f ) << 4 );

	/* Send card back to ID_WAIT */
	t509_wait_for_id_sequence();

	DBG ( "T509 found device %hhx, base %hx\n", t509->tag, t509->ioaddr );
	return 1;
}

/*
 * Test whether or not a driver is capable of driving the device.
 * This is a no-op for T509.
 *
 */
static int t509_check_driver ( struct bus_dev *bus_dev __unused,
			       struct device_driver *device_driver __unused ) {
	return 1;
}

/*
 * Describe a T509 device
 *
 */
static char * t509_describe ( struct bus_dev *bus_dev ) {
	struct t509_device *t509 = ( struct t509_device * ) bus_dev;
	static char t509_description[] = "T509 00";

	sprintf ( t509_description + 4, "%hhx", t509->tag );
	return t509_description;
}

/*
 * Name a T509 device
 *
 */
static const char * t509_name ( struct bus_dev *bus_dev __unused ) {
	return "T509";
}

/*
 * T509 bus operations table
 *
 */
struct bus_driver t509_driver __bus_driver = {
	.next_location	= t509_next_location,
	.fill_device	= t509_fill_device,
	.check_driver	= t509_check_driver,
	.describe	= t509_describe,
	.name		= t509_name,
};

/*
 * Activate a T509 device
 *
 * The device will be enabled at whatever ioaddr is specified in the
 * struct t509_device; there is no need to stick with the default
 * ioaddr read from the EEPROM.
 *
 */
static inline void activate_t509_device ( struct t509_device *t509 ) {
	t509_send_id_sequence ();
	t509_select_tag ( t509->tag );
	t509_activate ( t509->ioaddr );
	DBG ( "T509 activated device %hhx at ioaddr %hx\n",
	      t509->tag, t509->ioaddr );
}

/*
 * Deactivate a T509 device
 *
 * Disabling also clears the tag, so we immediately isolate and re-tag
 * this card.
 *
 */
static inline void deactivate_t509_device ( struct t509_device *t509 ) {
	t509_deactivate_and_reset_tag ( t509->ioaddr );
	udelay ( 1000 );
	t509_send_id_sequence ();
	t509_select_tag ( 0 );
	t509_set_tag ( t509->tag );
	t509_wait_for_id_sequence ();
	DBG ( "T509 deactivated device at %hx and re-tagged as %hhx\n",
	      t509->ioaddr, t509->tag );
}

/*
 * Fill in a nic structure
 *
 * Called only once, so inlined for efficiency
 *
 */
static inline void t509_fill_nic ( struct nic *nic,
				   struct t509_device *t509 ) {

	/* Fill in ioaddr and irqno */
	nic->ioaddr = t509->ioaddr;
	nic->irqno = 0;

	/* Fill in DHCP device ID structure */
	nic->dhcp_dev_id.bus_type = ISA_BUS_TYPE;
	nic->dhcp_dev_id.vendor_id = htons ( MFG_ID );
	nic->dhcp_dev_id.device_id = htons ( PROD_ID );
}

/*
 * The ISA probe function
 *
 */
static int el3_t509_probe ( struct nic *nic, struct t509_device *t509 ) {
	
	/* We could change t509->ioaddr if we wanted to */
	activate_t509_device ( t509 );
	t509_fill_nic ( nic, t509 );

	/* Hand off to generic t5x9 probe routine */
	return t5x9_probe ( nic, ISA_PROD_ID ( PROD_ID ), ISA_PROD_ID_MASK );
}

static void el3_t509_disable ( struct nic *nic, struct t509_device *t509 ) {
	t5x9_disable ( nic );
	deactivate_t509_device ( t509 );
}

static struct {} el3_t509_driver;

DRIVER ( "3c509", nic_driver, t509_driver, el3_t509_driver,
	 el3_t509_probe, el3_t509_disable );

ISA_ROM ( "3c509", "3c509" );