aboutsummaryrefslogtreecommitdiffstats
path: root/gpxe/src/drivers/net/epic100.c
blob: aaa85f80811d6f339fe684db669b272b37fbdb8d (plain)
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
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
/* epic100.c: A SMC 83c170 EPIC/100 fast ethernet driver for Etherboot */

FILE_LICENCE ( GPL2_OR_LATER );

/* 05/06/2003	timlegge	Fixed relocation and implemented Multicast */
#define LINUX_OUT_MACROS

#include "etherboot.h"
#include <gpxe/pci.h>
#include <gpxe/ethernet.h>
#include "nic.h"
#include "console.h"
#include "epic100.h"

/* Condensed operations for readability */
#define virt_to_le32desc(addr)	cpu_to_le32(virt_to_bus(addr))
#define le32desc_to_virt(addr)	bus_to_virt(le32_to_cpu(addr))

#define TX_RING_SIZE	2	/* use at least 2 buffers for TX */
#define RX_RING_SIZE	2

#define PKT_BUF_SZ	1536	/* Size of each temporary Tx/Rx buffer.*/

/*
#define DEBUG_RX
#define DEBUG_TX
#define DEBUG_EEPROM
*/

#define EPIC_DEBUG 0	/* debug level */

/* The EPIC100 Rx and Tx buffer descriptors. */
struct epic_rx_desc {
    unsigned long status;
    unsigned long bufaddr;
    unsigned long buflength;
    unsigned long next;
};
/* description of the tx descriptors control bits commonly used */
#define TD_STDFLAGS	TD_LASTDESC

struct epic_tx_desc {
    unsigned long status;
    unsigned long bufaddr;
    unsigned long buflength;
    unsigned long  next;
};

#define delay(nanosec)   do { int _i = 3; while (--_i > 0) \
                                     { __SLOW_DOWN_IO; }} while (0)

static void	epic100_open(void);
static void	epic100_init_ring(void);
static void	epic100_disable(struct nic *nic);
static int	epic100_poll(struct nic *nic, int retrieve);
static void	epic100_transmit(struct nic *nic, const char *destaddr,
				 unsigned int type, unsigned int len, const char *data);
#ifdef	DEBUG_EEPROM
static int	read_eeprom(int location);
#endif
static int	mii_read(int phy_id, int location);
static void     epic100_irq(struct nic *nic, irq_action_t action);

static struct nic_operations epic100_operations;

static int	ioaddr;

static int	command;
static int	intstat;
static int	intmask;
static int	genctl ;
static int	eectl  ;
static int	test   ;
static int	mmctl  ;
static int	mmdata ;
static int	lan0   ;
static int	mc0    ;
static int	rxcon  ;
static int	txcon  ;
static int	prcdar ;
static int	ptcdar ;
static int	eththr ;

static unsigned int	cur_rx, cur_tx;		/* The next free ring entry */
#ifdef	DEBUG_EEPROM
static unsigned short	eeprom[64];
#endif
static signed char	phys[4];		/* MII device addresses. */
struct {
	struct epic_rx_desc	rx_ring[RX_RING_SIZE]
	__attribute__ ((aligned(4)));
	struct epic_tx_desc	tx_ring[TX_RING_SIZE]
	__attribute__ ((aligned(4)));
	unsigned char	 	rx_packet[PKT_BUF_SZ * RX_RING_SIZE];
	unsigned char		tx_packet[PKT_BUF_SZ * TX_RING_SIZE];
} epic100_bufs __shared;
#define rx_ring epic100_bufs.rx_ring
#define tx_ring epic100_bufs.tx_ring
#define rx_packet epic100_bufs.rx_packet
#define tx_packet epic100_bufs.tx_packet

/***********************************************************************/
/*                    Externally visible functions                     */
/***********************************************************************/


static int
epic100_probe ( struct nic *nic, struct pci_device *pci ) {

    int i;
    unsigned short* ap;
    unsigned int phy, phy_idx;

    if (pci->ioaddr == 0)
	return 0;

    /* Ideally we would detect all network cards in slot order.  That would
       be best done a central PCI probe dispatch, which wouldn't work
       well with the current structure.  So instead we detect just the
       Epic cards in slot order. */

    ioaddr = pci->ioaddr;

    nic->irqno  = 0;
    nic->ioaddr = pci->ioaddr & ~3;

    /* compute all used static epic100 registers address */
    command = ioaddr + COMMAND;		/* Control Register */
    intstat = ioaddr + INTSTAT;		/* Interrupt Status */
    intmask = ioaddr + INTMASK;		/* Interrupt Mask */
    genctl  = ioaddr + GENCTL;		/* General Control */
    eectl   = ioaddr + EECTL;		/* EEPROM Control  */
    test    = ioaddr + TEST;		/* Test register (clocks) */
    mmctl   = ioaddr + MMCTL;		/* MII Management Interface Control */
    mmdata  = ioaddr + MMDATA;		/* MII Management Interface Data */
    lan0    = ioaddr + LAN0;		/* MAC address. (0x40-0x48) */
    mc0     = ioaddr + MC0; 		/* Multicast Control */
    rxcon   = ioaddr + RXCON;		/* Receive Control */
    txcon   = ioaddr + TXCON;		/* Transmit Control */
    prcdar  = ioaddr + PRCDAR;		/* PCI Receive Current Descr Address */
    ptcdar  = ioaddr + PTCDAR;		/* PCI Transmit Current Descr Address */
    eththr  = ioaddr + ETHTHR;		/* Early Transmit Threshold */

    /* Reset the chip & bring it out of low-power mode. */
    outl(GC_SOFT_RESET, genctl);

    /* Disable ALL interrupts by setting the interrupt mask. */
    outl(INTR_DISABLE, intmask);

    /*
     * set the internal clocks:
     * Application Note 7.15 says:
     *    In order to set the CLOCK TEST bit in the TEST register,
     *	  perform the following:
     *
     *        Write 0x0008 to the test register at least sixteen
     *        consecutive times.
     *
     * The CLOCK TEST bit is Write-Only. Writing it several times
     * consecutively insures a successful write to the bit...
     */

    for (i = 0; i < 16; i++) {
	outl(0x00000008, test);
    }

#ifdef	DEBUG_EEPROM
{
    unsigned short sum = 0;
    unsigned short value;
    for (i = 0; i < 64; i++) {
	value = read_eeprom(i);
	eeprom[i] = value;
	sum += value;
    }
}

#if	(EPIC_DEBUG > 1)
    printf("EEPROM contents\n");
    for (i = 0; i < 64; i++) {
	printf(" %hhX%s", eeprom[i], i % 16 == 15 ? "\n" : "");
    }
#endif
#endif

    /* This could also be read from the EEPROM. */
    ap = (unsigned short*)nic->node_addr;
    for (i = 0; i < 3; i++)
	*ap++ = inw(lan0 + i*4);

    DBG ( " I/O %4.4x %s ", ioaddr, eth_ntoa ( nic->node_addr ) );

    /* Find the connected MII xcvrs. */
    for (phy = 0, phy_idx = 0; phy < 32 && phy_idx < sizeof(phys); phy++) {
	int mii_status = mii_read(phy, 0);

	if (mii_status != 0xffff  && mii_status != 0x0000) {
	    phys[phy_idx++] = phy;
#if	(EPIC_DEBUG > 1)
	    printf("MII transceiver found at address %d.\n", phy);
#endif
	}
    }
    if (phy_idx == 0) {
#if	(EPIC_DEBUG > 1)
	printf("***WARNING***: No MII transceiver found!\n");
#endif
	/* Use the known PHY address of the EPII. */
	phys[0] = 3;
    }

    epic100_open();
    nic->nic_op	= &epic100_operations;

    return 1;
}

static void set_rx_mode(void)
{
	unsigned char mc_filter[8];
	int i;
	memset(mc_filter, 0xff, sizeof(mc_filter));
	outl(0x0C, rxcon);
	for(i = 0; i < 4; i++)
		outw(((unsigned short *)mc_filter)[i], mc0 + i*4);
	return;
}
	
   static void
epic100_open(void)
{
    int mii_reg5;
    int full_duplex = 0;
    unsigned long tmp;

    epic100_init_ring();

    /* Pull the chip out of low-power mode, and set for PCI read multiple. */
    outl(GC_RX_FIFO_THR_64 | GC_MRC_READ_MULT | GC_ONE_COPY, genctl);

    outl(TX_FIFO_THRESH, eththr);

    tmp = TC_EARLY_TX_ENABLE | TX_SLOT_TIME;

    mii_reg5 = mii_read(phys[0], 5);
    if (mii_reg5 != 0xffff && (mii_reg5 & 0x0100)) {
	full_duplex = 1;
	printf(" full-duplex mode");
	tmp |= TC_LM_FULL_DPX;
    } else
	tmp |= TC_LM_NORMAL;

    outl(tmp, txcon);

    /* Give adress of RX and TX ring to the chip */
    outl(virt_to_le32desc(&rx_ring), prcdar);
    outl(virt_to_le32desc(&tx_ring), ptcdar);

    /* Start the chip's Rx process: receive unicast and broadcast */
    set_rx_mode();
    outl(CR_START_RX | CR_QUEUE_RX, command);

    putchar('\n');
}

/* Initialize the Rx and Tx rings. */
    static void
epic100_init_ring(void)
{
    int i;

    cur_rx = cur_tx = 0;

    for (i = 0; i < RX_RING_SIZE; i++) {
	rx_ring[i].status    = cpu_to_le32(RRING_OWN);	/* Owned by Epic chip */
	rx_ring[i].buflength = cpu_to_le32(PKT_BUF_SZ);
	rx_ring[i].bufaddr   = virt_to_bus(&rx_packet[i * PKT_BUF_SZ]);
	rx_ring[i].next      = virt_to_le32desc(&rx_ring[i + 1]) ;
    }
    /* Mark the last entry as wrapping the ring. */
    rx_ring[i-1].next = virt_to_le32desc(&rx_ring[0]);

    /*
     *The Tx buffer descriptor is filled in as needed,
     * but we do need to clear the ownership bit.
     */

    for (i = 0; i < TX_RING_SIZE; i++) {
	tx_ring[i].status  = 0x0000;			/* Owned by CPU */
    	tx_ring[i].buflength = 0x0000 | cpu_to_le32(TD_STDFLAGS << 16);
	tx_ring[i].bufaddr = virt_to_bus(&tx_packet[i * PKT_BUF_SZ]);
	tx_ring[i].next    = virt_to_le32desc(&tx_ring[i + 1]);
    }
	tx_ring[i-1].next    = virt_to_le32desc(&tx_ring[0]);
}

/* function: epic100_transmit
 * This transmits a packet.
 *
 * Arguments: char d[6]:          destination ethernet address.
 *            unsigned short t:   ethernet protocol type.
 *            unsigned short s:   size of the data-part of the packet.
 *            char *p:            the data for the packet.
 * returns:   void.
 */
    static void
epic100_transmit(struct nic *nic, const char *destaddr, unsigned int type,
		 unsigned int len, const char *data)
{
    unsigned short nstype;
    unsigned char *txp;
    int entry;
    unsigned long ct;

    /* Calculate the next Tx descriptor entry. */
    entry = cur_tx % TX_RING_SIZE;

    if ((tx_ring[entry].status & TRING_OWN) == TRING_OWN) {
	printf("eth_transmit: Unable to transmit. status=%4.4lx. Resetting...\n",
	       tx_ring[entry].status);

	epic100_open();
	return;
    }

    txp = tx_packet + (entry * PKT_BUF_SZ);

    memcpy(txp, destaddr, ETH_ALEN);
    memcpy(txp + ETH_ALEN, nic->node_addr, ETH_ALEN);
    nstype = htons(type);
    memcpy(txp + 12, (char*)&nstype, 2);
    memcpy(txp + ETH_HLEN, data, len);

    len += ETH_HLEN;
	len &= 0x0FFF;
	while(len < ETH_ZLEN)
		txp[len++] = '\0';
    /*
     * Caution: the write order is important here,
     * set the base address with the "ownership"
     * bits last.
     */
   
    tx_ring[entry].buflength |= cpu_to_le32(len);
    tx_ring[entry].status = cpu_to_le32(len << 16) |
	    cpu_to_le32(TRING_OWN);	/* Pass ownership to the chip. */

    cur_tx++;

    /* Trigger an immediate transmit demand. */
    outl(CR_QUEUE_TX, command);

    ct = currticks();
    /* timeout 10 ms for transmit */
    while ((le32_to_cpu(tx_ring[entry].status) & (TRING_OWN)) &&
		ct + 10*1000 < currticks())
	/* Wait */;

    if ((le32_to_cpu(tx_ring[entry].status) & TRING_OWN) != 0)
	printf("Oops, transmitter timeout, status=%4.4lX\n",
	    tx_ring[entry].status);
}

/* function: epic100_poll / eth_poll
 * This receives a packet from the network.
 *
 * Arguments: none
 *
 * returns:   1 if a packet was received.
 *            0 if no pacet was received.
 * side effects:
 *            returns the packet in the array nic->packet.
 *            returns the length of the packet in nic->packetlen.
 */

    static int
epic100_poll(struct nic *nic, int retrieve)
{
    int entry;
    int retcode;
    int status;
    entry = cur_rx % RX_RING_SIZE;

    if ((rx_ring[entry].status & cpu_to_le32(RRING_OWN)) == RRING_OWN)
	return (0);

    if ( ! retrieve ) return 1;

    status = le32_to_cpu(rx_ring[entry].status);
    /* We own the next entry, it's a new packet. Send it up. */

#if	(EPIC_DEBUG > 4)
    printf("epic_poll: entry %d status %hX\n", entry, status);
#endif

    cur_rx++;
    if (status & 0x2000) {
	printf("epic_poll: Giant packet\n");
	retcode = 0;
    } else if (status & 0x0006) {
	/* Rx Frame errors are counted in hardware. */
	printf("epic_poll: Frame received with errors\n");
	retcode = 0;
    } else {
	/* Omit the four octet CRC from the length. */
	nic->packetlen = le32_to_cpu((rx_ring[entry].buflength))- 4;
	memcpy(nic->packet, &rx_packet[entry * PKT_BUF_SZ], nic->packetlen);
	retcode = 1;
    }

    /* Clear all error sources. */
    outl(status & INTR_CLEARERRS, intstat);

    /* Give the descriptor back to the chip */
    rx_ring[entry].status = RRING_OWN;

    /* Restart Receiver */
    outl(CR_START_RX | CR_QUEUE_RX, command); 

    return retcode;
}


static void epic100_disable ( struct nic *nic __unused ) {
	/* Soft reset the chip. */
	outl(GC_SOFT_RESET, genctl);
}

static void epic100_irq(struct nic *nic __unused, irq_action_t action __unused)
{
  switch ( action ) {
  case DISABLE :
    break;
  case ENABLE :
    break;
  case FORCE :
    break;
  }
}

#ifdef	DEBUG_EEPROM
/* Serial EEPROM section. */

/*  EEPROM_Ctrl bits. */
#define EE_SHIFT_CLK	0x04	/* EEPROM shift clock. */
#define EE_CS		0x02	/* EEPROM chip select. */
#define EE_DATA_WRITE	0x08	/* EEPROM chip data in. */
#define EE_WRITE_0	0x01
#define EE_WRITE_1	0x09
#define EE_DATA_READ	0x10	/* EEPROM chip data out. */
#define EE_ENB		(0x0001 | EE_CS)

/* The EEPROM commands include the alway-set leading bit. */
#define EE_WRITE_CMD	(5 << 6)
#define EE_READ_CMD	(6 << 6)
#define EE_ERASE_CMD	(7 << 6)

#define eeprom_delay(n)	delay(n)

    static int
read_eeprom(int location)
{
    int i;
    int retval = 0;
    int read_cmd = location | EE_READ_CMD;

    outl(EE_ENB & ~EE_CS, eectl);
    outl(EE_ENB, eectl);

    /* Shift the read command bits out. */
    for (i = 10; i >= 0; i--) {
	short dataval = (read_cmd & (1 << i)) ? EE_DATA_WRITE : 0;
	outl(EE_ENB | dataval, eectl);
	eeprom_delay(100);
	outl(EE_ENB | dataval | EE_SHIFT_CLK, eectl);
	eeprom_delay(150);
	outl(EE_ENB | dataval, eectl);	/* Finish EEPROM a clock tick. */
	eeprom_delay(250);
    }
    outl(EE_ENB, eectl);

    for (i = 16; i > 0; i--) {
	outl(EE_ENB | EE_SHIFT_CLK, eectl);
	eeprom_delay(100);
	retval = (retval << 1) | ((inl(eectl) & EE_DATA_READ) ? 1 : 0);
	outl(EE_ENB, eectl);
	eeprom_delay(100);
    }

    /* Terminate the EEPROM access. */
    outl(EE_ENB & ~EE_CS, eectl);
    return retval;
}
#endif


#define MII_READOP	1
#define MII_WRITEOP	2

    static int
mii_read(int phy_id, int location)
{
    int i;

    outl((phy_id << 9) | (location << 4) | MII_READOP, mmctl);
    /* Typical operation takes < 50 ticks. */

    for (i = 4000; i > 0; i--)
	if ((inl(mmctl) & MII_READOP) == 0)
	    break;
    return inw(mmdata);
}

static struct nic_operations epic100_operations = {
	.connect	= dummy_connect,
	.poll		= epic100_poll,
	.transmit	= epic100_transmit,
	.irq		= epic100_irq,

};

static struct pci_device_id epic100_nics[] = {
PCI_ROM(0x10b8, 0x0005, "epic100",    "SMC EtherPowerII", 0),		/* SMC 83c170 EPIC/100 */
PCI_ROM(0x10b8, 0x0006, "smc-83c175", "SMC EPIC/C 83c175", 0),
};

PCI_DRIVER ( epic100_driver, epic100_nics, PCI_NO_CLASS );

DRIVER ( "EPIC100", nic_driver, pci_driver, epic100_driver,
	 epic100_probe, epic100_disable );

/*
 * Local variables:
 *  c-basic-offset: 8
 *  c-indent-level: 8
 *  tab-width: 8
 * End:
 */