1/**************************************************************************
2ETHERBOOT -  BOOTP/TFTP Bootstrap Program
3
4Author: Martin Renters.
5  Date: Mar 22 1995
6
7 This code is based heavily on David Greenman's if_ed.c driver and
8  Andres Vega Garcia's if_ep.c driver.
9
10 Copyright (C) 1993-1994, David Greenman, Martin Renters.
11 Copyright (C) 1993-1995, Andres Vega Garcia.
12 Copyright (C) 1995, Serge Babkin.
13  This software may be used, modified, copied, distributed, and sold, in
14  both source and binary form provided that the above copyright and these
15  terms are retained. Under no circumstances are the authors responsible for
16  the proper functioning of this software, nor do the authors assume any
17  responsibility for damages incurred with its use.
18
193c509 support added by Serge Babkin (babkin@hq.icb.chel.su)
20
21$Id$
22
23***************************************************************************/
24
25FILE_LICENCE ( BSD2 );
26
27/* #define EDEBUG */
28
29#include <gpxe/ethernet.h>
30#include "etherboot.h"
31#include "nic.h"
32#include <gpxe/isa.h>
33#include "3c509.h"
34
35static enum { none, bnc, utp } connector = none;	/* for 3C509 */
36
37/**************************************************************************
38ETH_RESET - Reset adapter
39***************************************************************************/
40void t5x9_disable ( struct nic *nic ) {
41	/* stop card */
42	outw(RX_DISABLE, nic->ioaddr + EP_COMMAND);
43	outw(RX_DISCARD_TOP_PACK, nic->ioaddr + EP_COMMAND);
44	while (inw(nic->ioaddr + EP_STATUS) & S_COMMAND_IN_PROGRESS)
45		;
46	outw(TX_DISABLE, nic->ioaddr + EP_COMMAND);
47	outw(STOP_TRANSCEIVER, nic->ioaddr + EP_COMMAND);
48	udelay(1000);
49	outw(RX_RESET, nic->ioaddr + EP_COMMAND);
50	outw(TX_RESET, nic->ioaddr + EP_COMMAND);
51	outw(C_INTR_LATCH, nic->ioaddr + EP_COMMAND);
52	outw(SET_RD_0_MASK, nic->ioaddr + EP_COMMAND);
53	outw(SET_INTR_MASK, nic->ioaddr + EP_COMMAND);
54	outw(SET_RX_FILTER, nic->ioaddr + EP_COMMAND);
55
56	/*
57	 * wait for reset to complete
58	 */
59	while (inw(nic->ioaddr + EP_STATUS) & S_COMMAND_IN_PROGRESS)
60		;
61
62	GO_WINDOW(nic->ioaddr,0);
63
64	/* Disable the card */
65	outw(0, nic->ioaddr + EP_W0_CONFIG_CTRL);
66
67	/* Configure IRQ to none */
68	outw(SET_IRQ(0), nic->ioaddr + EP_W0_RESOURCE_CFG);
69}
70
71static void t509_enable ( struct nic *nic ) {
72	int i;
73
74	/* Enable the card */
75	GO_WINDOW(nic->ioaddr,0);
76	outw(ENABLE_DRQ_IRQ, nic->ioaddr + EP_W0_CONFIG_CTRL);
77
78	GO_WINDOW(nic->ioaddr,2);
79
80	/* Reload the ether_addr. */
81	for (i = 0; i < ETH_ALEN; i++)
82		outb(nic->node_addr[i], nic->ioaddr + EP_W2_ADDR_0 + i);
83
84	outw(RX_RESET, nic->ioaddr + EP_COMMAND);
85	outw(TX_RESET, nic->ioaddr + EP_COMMAND);
86
87	/* Window 1 is operating window */
88	GO_WINDOW(nic->ioaddr,1);
89	for (i = 0; i < 31; i++)
90		inb(nic->ioaddr + EP_W1_TX_STATUS);
91
92	/* get rid of stray intr's */
93	outw(ACK_INTR | 0xff, nic->ioaddr + EP_COMMAND);
94
95	outw(SET_RD_0_MASK | S_5_INTS, nic->ioaddr + EP_COMMAND);
96
97	outw(SET_INTR_MASK, nic->ioaddr + EP_COMMAND);
98
99	outw(SET_RX_FILTER | FIL_GROUP | FIL_INDIVIDUAL | FIL_BRDCST,
100	     nic->ioaddr + EP_COMMAND);
101
102	/* configure BNC */
103	if (connector == bnc) {
104		outw(START_TRANSCEIVER, nic->ioaddr + EP_COMMAND);
105		udelay(1000);
106	}
107	/* configure UTP */
108	else if (connector == utp) {
109		GO_WINDOW(nic->ioaddr,4);
110		outw(ENABLE_UTP, nic->ioaddr + EP_W4_MEDIA_TYPE);
111		sleep(2);	/* Give time for media to negotiate */
112		GO_WINDOW(nic->ioaddr,1);
113	}
114
115	/* start transceiver and receiver */
116	outw(RX_ENABLE, nic->ioaddr + EP_COMMAND);
117	outw(TX_ENABLE, nic->ioaddr + EP_COMMAND);
118
119	/* set early threshold for minimal packet length */
120	outw(SET_RX_EARLY_THRESH | ETH_ZLEN, nic->ioaddr + EP_COMMAND);
121	outw(SET_TX_START_THRESH | 16, nic->ioaddr + EP_COMMAND);
122}
123
124static void t509_reset ( struct nic *nic ) {
125	t5x9_disable ( nic );
126	t509_enable ( nic );
127}
128
129/**************************************************************************
130ETH_TRANSMIT - Transmit a frame
131***************************************************************************/
132static char padmap[] = {
133	0, 3, 2, 1};
134
135static void t509_transmit(
136struct nic *nic,
137const char *d,			/* Destination */
138unsigned int t,			/* Type */
139unsigned int s,			/* size */
140const char *p)			/* Packet */
141{
142	register unsigned int len;
143	int pad;
144	int status;
145
146#ifdef	EDEBUG
147	printf("{l=%d,t=%hX}",s+ETH_HLEN,t);
148#endif
149
150	/* swap bytes of type */
151	t= htons(t);
152
153	len=s+ETH_HLEN; /* actual length of packet */
154	pad = padmap[len & 3];
155
156	/*
157	* The 3c509 automatically pads short packets to minimum ethernet length,
158	* but we drop packets that are too large. Perhaps we should truncate
159	* them instead?
160	*/
161	if (len + pad > ETH_FRAME_LEN) {
162		return;
163	}
164
165	/* drop acknowledgements */
166	while ((status=inb(nic->ioaddr + EP_W1_TX_STATUS)) & TXS_COMPLETE ) {
167		if (status & (TXS_UNDERRUN|TXS_MAX_COLLISION|TXS_STATUS_OVERFLOW)) {
168			outw(TX_RESET, nic->ioaddr + EP_COMMAND);
169			outw(TX_ENABLE, nic->ioaddr + EP_COMMAND);
170		}
171		outb(0x0, nic->ioaddr + EP_W1_TX_STATUS);
172	}
173
174	while (inw(nic->ioaddr + EP_W1_FREE_TX) < (unsigned short)len + pad + 4)
175		; /* no room in FIFO */
176
177	outw(len, nic->ioaddr + EP_W1_TX_PIO_WR_1);
178	outw(0x0, nic->ioaddr + EP_W1_TX_PIO_WR_1);	/* Second dword meaningless */
179
180	/* write packet */
181	outsw(nic->ioaddr + EP_W1_TX_PIO_WR_1, d, ETH_ALEN/2);
182	outsw(nic->ioaddr + EP_W1_TX_PIO_WR_1, nic->node_addr, ETH_ALEN/2);
183	outw(t, nic->ioaddr + EP_W1_TX_PIO_WR_1);
184	outsw(nic->ioaddr + EP_W1_TX_PIO_WR_1, p, s / 2);
185	if (s & 1)
186		outb(*(p+s - 1), nic->ioaddr + EP_W1_TX_PIO_WR_1);
187
188	while (pad--)
189		outb(0, nic->ioaddr + EP_W1_TX_PIO_WR_1);	/* Padding */
190
191	/* wait for Tx complete */
192	while((inw(nic->ioaddr + EP_STATUS) & S_COMMAND_IN_PROGRESS) != 0)
193		;
194}
195
196/**************************************************************************
197ETH_POLL - Wait for a frame
198***************************************************************************/
199static int t509_poll(struct nic *nic, int retrieve)
200{
201	/* common variables */
202	/* variables for 3C509 */
203	short status, cst;
204	register short rx_fifo;
205
206	cst=inw(nic->ioaddr + EP_STATUS);
207
208#ifdef	EDEBUG
209	if(cst & 0x1FFF)
210		printf("-%hX-",cst);
211#endif
212
213	if( (cst & S_RX_COMPLETE)==0 ) {
214		/* acknowledge  everything */
215		outw(ACK_INTR| (cst & S_5_INTS), nic->ioaddr + EP_COMMAND);
216		outw(C_INTR_LATCH, nic->ioaddr + EP_COMMAND);
217
218		return 0;
219	}
220
221	status = inw(nic->ioaddr + EP_W1_RX_STATUS);
222#ifdef	EDEBUG
223	printf("*%hX*",status);
224#endif
225
226	if (status & ERR_RX) {
227		outw(RX_DISCARD_TOP_PACK, nic->ioaddr + EP_COMMAND);
228		return 0;
229	}
230
231	rx_fifo = status & RX_BYTES_MASK;
232	if (rx_fifo==0)
233		return 0;
234
235	if ( ! retrieve ) return 1;
236
237		/* read packet */
238#ifdef	EDEBUG
239	printf("[l=%d",rx_fifo);
240#endif
241	insw(nic->ioaddr + EP_W1_RX_PIO_RD_1, nic->packet, rx_fifo / 2);
242	if(rx_fifo & 1)
243		nic->packet[rx_fifo-1]=inb(nic->ioaddr + EP_W1_RX_PIO_RD_1);
244	nic->packetlen=rx_fifo;
245
246	while(1) {
247		status = inw(nic->ioaddr + EP_W1_RX_STATUS);
248#ifdef	EDEBUG
249		printf("*%hX*",status);
250#endif
251		rx_fifo = status & RX_BYTES_MASK;
252		if(rx_fifo>0) {
253			insw(nic->ioaddr + EP_W1_RX_PIO_RD_1, nic->packet+nic->packetlen, rx_fifo / 2);
254			if(rx_fifo & 1)
255				nic->packet[nic->packetlen+rx_fifo-1]=inb(nic->ioaddr + EP_W1_RX_PIO_RD_1);
256			nic->packetlen+=rx_fifo;
257#ifdef	EDEBUG
258			printf("+%d",rx_fifo);
259#endif
260		}
261		if(( status & RX_INCOMPLETE )==0) {
262#ifdef	EDEBUG
263			printf("=%d",nic->packetlen);
264#endif
265			break;
266		}
267		udelay(1000);	/* if incomplete wait 1 ms */
268	}
269	/* acknowledge reception of packet */
270	outw(RX_DISCARD_TOP_PACK, nic->ioaddr + EP_COMMAND);
271	while (inw(nic->ioaddr + EP_STATUS) & S_COMMAND_IN_PROGRESS)
272		;
273#ifdef	EDEBUG
274{
275	unsigned short type = 0;	/* used by EDEBUG */
276	type = (nic->packet[12]<<8) | nic->packet[13];
277	if(nic->packet[0]+nic->packet[1]+nic->packet[2]+nic->packet[3]+nic->packet[4]+
278	    nic->packet[5] == 0xFF*ETH_ALEN)
279		printf(",t=%hX,b]",type);
280	else
281		printf(",t=%hX]",type);
282}
283#endif
284	return (1);
285}
286
287/**************************************************************************
288ETH_IRQ - interrupt handling
289***************************************************************************/
290static void t509_irq(struct nic *nic __unused, irq_action_t action __unused)
291{
292  switch ( action ) {
293  case DISABLE :
294    break;
295  case ENABLE :
296    break;
297  case FORCE :
298    break;
299  }
300}
301
302/*************************************************************************
303	3Com 509 - specific routines
304**************************************************************************/
305
306static int eeprom_rdy ( uint16_t ioaddr ) {
307	int i;
308
309	for (i = 0; is_eeprom_busy(ioaddr) && i < MAX_EEPROMBUSY; i++);
310	if (i >= MAX_EEPROMBUSY) {
311		/* printf("3c509: eeprom failed to come ready.\n"); */
312		/* memory in EPROM is tight */
313		/* printf("3c509: eeprom busy.\n"); */
314		return (0);
315	}
316	return (1);
317}
318
319/*
320 * get_e: gets a 16 bits word from the EEPROM.
321 */
322static int get_e ( uint16_t ioaddr, int offset ) {
323	GO_WINDOW(ioaddr,0);
324	if (!eeprom_rdy(ioaddr))
325		return (0xffff);
326	outw(EEPROM_CMD_RD | offset, ioaddr + EP_W0_EEPROM_COMMAND);
327	if (!eeprom_rdy(ioaddr))
328		return (0xffff);
329	return (inw(ioaddr + EP_W0_EEPROM_DATA));
330}
331
332static struct nic_operations t509_operations = {
333	.connect	= dummy_connect,
334	.poll		= t509_poll,
335	.transmit	= t509_transmit,
336	.irq		= t509_irq,
337};
338
339/**************************************************************************
340ETH_PROBE - Look for an adapter
341***************************************************************************/
342int t5x9_probe ( struct nic *nic,
343		 uint16_t prod_id_check, uint16_t prod_id_mask ) {
344	uint16_t prod_id;
345	int i,j;
346	unsigned short *p;
347
348	/* Check product ID */
349	prod_id = get_e ( nic->ioaddr, EEPROM_PROD_ID );
350	if ( ( prod_id & prod_id_mask ) != prod_id_check ) {
351		printf ( "EEPROM Product ID is incorrect (%hx & %hx != %hx)\n",
352			 prod_id, prod_id_mask, prod_id_check );
353		return 0;
354	}
355
356	/* test for presence of connectors */
357	GO_WINDOW(nic->ioaddr,0);
358	i = inw(nic->ioaddr + EP_W0_CONFIG_CTRL);
359	j = (inw(nic->ioaddr + EP_W0_ADDRESS_CFG) >> 14) & 0x3;
360
361	switch(j) {
362	case 0:
363		if (i & IS_UTP) {
364			printf("10baseT\n");
365			connector = utp;
366		} else {
367			printf("10baseT not present\n");
368			return 0;
369		}
370		break;
371	case 1:
372		if (i & IS_AUI) {
373			printf("10base5\n");
374		} else {
375			printf("10base5 not present\n");
376			return 0;
377		}
378		break;
379	case 3:
380		if (i & IS_BNC) {
381			printf("10base2\n");
382			connector = bnc;
383		} else {
384			printf("10base2 not present\n");
385			return 0;
386		}
387		break;
388	default:
389		printf("unknown connector\n");
390		return 0;
391	}
392
393	/*
394	* Read the station address from the eeprom
395	*/
396	p = (unsigned short *) nic->node_addr;
397	for (i = 0; i < ETH_ALEN / 2; i++) {
398		p[i] = htons(get_e(nic->ioaddr,i));
399		GO_WINDOW(nic->ioaddr,2);
400		outw(ntohs(p[i]), nic->ioaddr + EP_W2_ADDR_0 + (i * 2));
401	}
402
403	DBG ( "Ethernet Address: %s\n", eth_ntoa ( nic->node_addr ) );
404
405	t509_reset(nic);
406
407	nic->nic_op = &t509_operations;
408	return 1;
409
410}
411
412/*
413 * Local variables:
414 *  c-basic-offset: 8
415 * End:
416 */
417