1 | /*
|
---|
2 | * Etherboot - BOOTP/TFTP Bootstrap Program
|
---|
3 | *
|
---|
4 | * w89c840.c -- This file implements the winbond-840 driver for etherboot.
|
---|
5 | *
|
---|
6 | */
|
---|
7 |
|
---|
8 | /*
|
---|
9 | * Adapted by Igor V. Kovalenko
|
---|
10 | * -- <[email protected]>
|
---|
11 | * OR
|
---|
12 | * -- <[email protected]>
|
---|
13 | * Initial adaptaion stage, including testing, completed 23 August 2000.
|
---|
14 | */
|
---|
15 |
|
---|
16 | /*
|
---|
17 | * This program is free software; you can redistribute it and/or
|
---|
18 | * modify it under the terms of the GNU General Public License as
|
---|
19 | * published by the Free Software Foundation; either version 2, or (at
|
---|
20 | * your option) any later version.
|
---|
21 | *
|
---|
22 | * This program is distributed in the hope that it will be useful, but
|
---|
23 | * WITHOUT ANY WARRANTY; without even the implied warranty of
|
---|
24 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
---|
25 | * General Public License for more details.
|
---|
26 | *
|
---|
27 | * You should have received a copy of the GNU General Public License
|
---|
28 | * along with this program; if not, write to the Free Software
|
---|
29 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
---|
30 | */
|
---|
31 |
|
---|
32 | /*
|
---|
33 | * date version by what
|
---|
34 | * Written: Aug 20 2000 V0.10 iko Initial revision.
|
---|
35 | * changes: Aug 22 2000 V0.90 iko Works!
|
---|
36 | * Aug 23 2000 V0.91 iko Cleanup, posted to etherboot
|
---|
37 | * maintainer.
|
---|
38 | * Aug 26 2000 V0.92 iko Fixed Rx ring handling.
|
---|
39 | * First Linux Kernel (TM)
|
---|
40 | * successfully loaded using
|
---|
41 | * this driver.
|
---|
42 | * Jan 07 2001 V0.93 iko Transmitter timeouts are handled
|
---|
43 | * using timer2 routines. Proposed
|
---|
44 | * by Ken Yap to eliminate CPU speed
|
---|
45 | * dependency.
|
---|
46 | * Dec 12 2003 V0.94 timlegge Fixed issues in 5.2, removed
|
---|
47 | * interrupt usage, enabled
|
---|
48 | * multicast support
|
---|
49 | *
|
---|
50 | * This is the etherboot driver for cards based on Winbond W89c840F chip.
|
---|
51 | *
|
---|
52 | * It was written from skeleton source, with Donald Becker's winbond-840.c
|
---|
53 | * kernel driver as a guideline. Mostly the w89c840 related definitions
|
---|
54 | * and the lower level routines have been cut-and-pasted into this source.
|
---|
55 | *
|
---|
56 | * Frankly speaking, about 90% of the code was obtained using cut'n'paste
|
---|
57 | * sequence :) while the remainder appeared while brainstorming
|
---|
58 | * Linux Kernel 2.4.0-testX source code. Thanks, Donald and Linus!
|
---|
59 | *
|
---|
60 | * There was a demand for using this card in a rather large
|
---|
61 | * remote boot environment at MSKP OVTI Lab of
|
---|
62 | * Moscow Institute for Physics and Technology (MIPT) -- http://www.mipt.ru/
|
---|
63 | * so you may count that for motivation.
|
---|
64 | *
|
---|
65 | */
|
---|
66 |
|
---|
67 | /*
|
---|
68 | * If you want to see debugging output then define W89C840_DEBUG
|
---|
69 | */
|
---|
70 |
|
---|
71 | /*
|
---|
72 | #define W89C840_DEBUG
|
---|
73 | */
|
---|
74 |
|
---|
75 | /*
|
---|
76 | * Keep using IO_OPS for Etherboot driver!
|
---|
77 | */
|
---|
78 | #define USE_IO_OPS
|
---|
79 |
|
---|
80 | #include "etherboot.h"
|
---|
81 | #include "nic.h"
|
---|
82 | #include "pci.h"
|
---|
83 | #include "timer.h"
|
---|
84 |
|
---|
85 | static const char *w89c840_version = "driver Version 0.94 - December 12, 2003";
|
---|
86 |
|
---|
87 | /* Linux support functions */
|
---|
88 | #define virt_to_le32desc(addr) virt_to_bus(addr)
|
---|
89 | #define le32desc_to_virt(addr) bus_to_virt(addr)
|
---|
90 |
|
---|
91 | /*
|
---|
92 | #define cpu_to_le32(val) (val)
|
---|
93 | #define le32_to_cpu(val) (val)
|
---|
94 | */
|
---|
95 |
|
---|
96 | /* Operational parameters that are set at compile time. */
|
---|
97 |
|
---|
98 | /* Keep the ring sizes a power of two for compile efficiency.
|
---|
99 | The compiler will convert <unsigned>'%'<2^N> into a bit mask.
|
---|
100 | Making the Tx ring too large decreases the effectiveness of channel
|
---|
101 | bonding and packet priority.
|
---|
102 | There are no ill effects from too-large receive rings. */
|
---|
103 | #define TX_RING_SIZE 2
|
---|
104 | #define RX_RING_SIZE 2
|
---|
105 |
|
---|
106 | /* The presumed FIFO size for working around the Tx-FIFO-overflow bug.
|
---|
107 | To avoid overflowing we don't queue again until we have room for a
|
---|
108 | full-size packet.
|
---|
109 | */
|
---|
110 | #define TX_FIFO_SIZE (2048)
|
---|
111 | #define TX_BUG_FIFO_LIMIT (TX_FIFO_SIZE-1514-16)
|
---|
112 |
|
---|
113 | /* Operational parameters that usually are not changed. */
|
---|
114 | /* Time in jiffies before concluding the transmitter is hung. */
|
---|
115 | #define TX_TIMEOUT (10*TICKS_PER_MS)
|
---|
116 |
|
---|
117 | #define PKT_BUF_SZ 1536 /* Size of each temporary Rx buffer.*/
|
---|
118 |
|
---|
119 | /*
|
---|
120 | * Used to be this much CPU loops on Celeron@400 (?),
|
---|
121 | * now using real timer and TX_TIMEOUT!
|
---|
122 | * #define TX_LOOP_COUNT 10000000
|
---|
123 | */
|
---|
124 |
|
---|
125 | #if !defined(__OPTIMIZE__)
|
---|
126 | #warning You must compile this file with the correct options!
|
---|
127 | #warning See the last lines of the source file.
|
---|
128 | #error You must compile this driver with "-O".
|
---|
129 | #endif
|
---|
130 |
|
---|
131 | enum chip_capability_flags {CanHaveMII=1, HasBrokenTx=2};
|
---|
132 |
|
---|
133 | #ifdef USE_IO_OPS
|
---|
134 | #define W840_FLAGS (PCI_USES_IO | PCI_ADDR0 | PCI_USES_MASTER)
|
---|
135 | #else
|
---|
136 | #define W840_FLAGS (PCI_USES_MEM | PCI_ADDR1 | PCI_USES_MASTER)
|
---|
137 | #endif
|
---|
138 |
|
---|
139 | static u32 driver_flags = CanHaveMII | HasBrokenTx;
|
---|
140 |
|
---|
141 | /* This driver was written to use PCI memory space, however some x86 systems
|
---|
142 | work only with I/O space accesses. Pass -DUSE_IO_OPS to use PCI I/O space
|
---|
143 | accesses instead of memory space. */
|
---|
144 |
|
---|
145 | #ifdef USE_IO_OPS
|
---|
146 | #undef readb
|
---|
147 | #undef readw
|
---|
148 | #undef readl
|
---|
149 | #undef writeb
|
---|
150 | #undef writew
|
---|
151 | #undef writel
|
---|
152 | #define readb inb
|
---|
153 | #define readw inw
|
---|
154 | #define readl inl
|
---|
155 | #define writeb outb
|
---|
156 | #define writew outw
|
---|
157 | #define writel outl
|
---|
158 | #endif
|
---|
159 |
|
---|
160 | /* Offsets to the Command and Status Registers, "CSRs".
|
---|
161 | While similar to the Tulip, these registers are longword aligned.
|
---|
162 | Note: It's not useful to define symbolic names for every register bit in
|
---|
163 | the device. The name can only partially document the semantics and make
|
---|
164 | the driver longer and more difficult to read.
|
---|
165 | */
|
---|
166 | enum w840_offsets {
|
---|
167 | PCIBusCfg=0x00, TxStartDemand=0x04, RxStartDemand=0x08,
|
---|
168 | RxRingPtr=0x0C, TxRingPtr=0x10,
|
---|
169 | IntrStatus=0x14, NetworkConfig=0x18, IntrEnable=0x1C,
|
---|
170 | RxMissed=0x20, EECtrl=0x24, MIICtrl=0x24, BootRom=0x28, GPTimer=0x2C,
|
---|
171 | CurRxDescAddr=0x30, CurRxBufAddr=0x34, /* Debug use */
|
---|
172 | MulticastFilter0=0x38, MulticastFilter1=0x3C, StationAddr=0x40,
|
---|
173 | CurTxDescAddr=0x4C, CurTxBufAddr=0x50,
|
---|
174 | };
|
---|
175 |
|
---|
176 | /* Bits in the interrupt status/enable registers. */
|
---|
177 | /* The bits in the Intr Status/Enable registers, mostly interrupt sources. */
|
---|
178 | enum intr_status_bits {
|
---|
179 | NormalIntr=0x10000, AbnormalIntr=0x8000,
|
---|
180 | IntrPCIErr=0x2000, TimerInt=0x800,
|
---|
181 | IntrRxDied=0x100, RxNoBuf=0x80, IntrRxDone=0x40,
|
---|
182 | TxFIFOUnderflow=0x20, RxErrIntr=0x10,
|
---|
183 | TxIdle=0x04, IntrTxStopped=0x02, IntrTxDone=0x01,
|
---|
184 | };
|
---|
185 |
|
---|
186 | /* Bits in the NetworkConfig register. */
|
---|
187 | enum rx_mode_bits {
|
---|
188 | AcceptErr=0x80, AcceptRunt=0x40,
|
---|
189 | AcceptBroadcast=0x20, AcceptMulticast=0x10,
|
---|
190 | AcceptAllPhys=0x08, AcceptMyPhys=0x02,
|
---|
191 | };
|
---|
192 |
|
---|
193 | enum mii_reg_bits {
|
---|
194 | MDIO_ShiftClk=0x10000, MDIO_DataIn=0x80000, MDIO_DataOut=0x20000,
|
---|
195 | MDIO_EnbOutput=0x40000, MDIO_EnbIn = 0x00000,
|
---|
196 | };
|
---|
197 |
|
---|
198 | /* The Tulip Rx and Tx buffer descriptors. */
|
---|
199 | struct w840_rx_desc {
|
---|
200 | s32 status;
|
---|
201 | s32 length;
|
---|
202 | u32 buffer1;
|
---|
203 | u32 next_desc;
|
---|
204 | };
|
---|
205 |
|
---|
206 | struct w840_tx_desc {
|
---|
207 | s32 status;
|
---|
208 | s32 length;
|
---|
209 | u32 buffer1, buffer2; /* We use only buffer 1. */
|
---|
210 | };
|
---|
211 |
|
---|
212 | /* Bits in network_desc.status */
|
---|
213 | enum desc_status_bits {
|
---|
214 | DescOwn=0x80000000, DescEndRing=0x02000000, DescUseLink=0x01000000,
|
---|
215 | DescWholePkt=0x60000000, DescStartPkt=0x20000000, DescEndPkt=0x40000000,
|
---|
216 | DescIntr=0x80000000,
|
---|
217 | };
|
---|
218 | #define PRIV_ALIGN 15 /* Required alignment mask */
|
---|
219 | #define PRIV_ALIGN_BYTES 32
|
---|
220 |
|
---|
221 | static struct winbond_private
|
---|
222 | {
|
---|
223 | /* Descriptor rings first for alignment. */
|
---|
224 | struct w840_rx_desc rx_ring[RX_RING_SIZE];
|
---|
225 | struct w840_tx_desc tx_ring[TX_RING_SIZE];
|
---|
226 | struct net_device *next_module; /* Link for devices of this type. */
|
---|
227 | void *priv_addr; /* Unaligned address for kfree */
|
---|
228 | const char *product_name;
|
---|
229 | /* Frequently used values: keep some adjacent for cache effect. */
|
---|
230 | int chip_id, drv_flags;
|
---|
231 | struct pci_dev *pci_dev;
|
---|
232 | int csr6;
|
---|
233 | struct w840_rx_desc *rx_head_desc;
|
---|
234 | unsigned int cur_rx, dirty_rx; /* Producer/consumer ring indices */
|
---|
235 | unsigned int rx_buf_sz; /* Based on MTU+slack. */
|
---|
236 | unsigned int cur_tx, dirty_tx;
|
---|
237 | int tx_q_bytes;
|
---|
238 | unsigned int tx_full:1; /* The Tx queue is full. */
|
---|
239 | /* These values are keep track of the transceiver/media in use. */
|
---|
240 | unsigned int full_duplex:1; /* Full-duplex operation requested. */
|
---|
241 | unsigned int duplex_lock:1;
|
---|
242 | unsigned int medialock:1; /* Do not sense media. */
|
---|
243 | unsigned int default_port:4; /* Last dev->if_port value. */
|
---|
244 | /* MII transceiver section. */
|
---|
245 | int mii_cnt; /* MII device addresses. */
|
---|
246 | u16 advertising; /* NWay media advertisement */
|
---|
247 | unsigned char phys[2]; /* MII device addresses. */
|
---|
248 | } w840private __attribute__ ((aligned (PRIV_ALIGN_BYTES)));
|
---|
249 |
|
---|
250 | /* NIC specific static variables go here */
|
---|
251 |
|
---|
252 | static int ioaddr;
|
---|
253 | static unsigned short eeprom [0x40];
|
---|
254 | static char rx_packet[PKT_BUF_SZ * RX_RING_SIZE];
|
---|
255 | static char tx_packet[PKT_BUF_SZ * TX_RING_SIZE];
|
---|
256 |
|
---|
257 | static int eeprom_read(long ioaddr, int location);
|
---|
258 | static int mdio_read(int base_address, int phy_id, int location);
|
---|
259 | #if 0
|
---|
260 | static void mdio_write(int base_address, int phy_id, int location, int value);
|
---|
261 | #endif
|
---|
262 |
|
---|
263 | static void check_duplex(void);
|
---|
264 | static void set_rx_mode(void);
|
---|
265 | static void init_ring(void);
|
---|
266 |
|
---|
267 | #if defined(W89C840_DEBUG)
|
---|
268 | static void decode_interrupt(u32 intr_status)
|
---|
269 | {
|
---|
270 | printf("Interrupt status: ");
|
---|
271 |
|
---|
272 | #define TRACE_INTR(_intr_) \
|
---|
273 | if (intr_status & (_intr_)) { printf (" " #_intr_); }
|
---|
274 |
|
---|
275 | TRACE_INTR(NormalIntr);
|
---|
276 | TRACE_INTR(AbnormalIntr);
|
---|
277 | TRACE_INTR(IntrPCIErr);
|
---|
278 | TRACE_INTR(TimerInt);
|
---|
279 | TRACE_INTR(IntrRxDied);
|
---|
280 | TRACE_INTR(RxNoBuf);
|
---|
281 | TRACE_INTR(IntrRxDone);
|
---|
282 | TRACE_INTR(TxFIFOUnderflow);
|
---|
283 | TRACE_INTR(RxErrIntr);
|
---|
284 | TRACE_INTR(TxIdle);
|
---|
285 | TRACE_INTR(IntrTxStopped);
|
---|
286 | TRACE_INTR(IntrTxDone);
|
---|
287 |
|
---|
288 | printf("\n");
|
---|
289 | /*sleep(1);*/
|
---|
290 | }
|
---|
291 | #endif
|
---|
292 |
|
---|
293 | /**************************************************************************
|
---|
294 | w89c840_reset - Reset adapter
|
---|
295 | ***************************************************************************/
|
---|
296 | static void w89c840_reset(struct nic *nic)
|
---|
297 | {
|
---|
298 | int i;
|
---|
299 |
|
---|
300 | /* Reset the chip to erase previous misconfiguration.
|
---|
301 | No hold time required! */
|
---|
302 | writel(0x00000001, ioaddr + PCIBusCfg);
|
---|
303 |
|
---|
304 | init_ring();
|
---|
305 |
|
---|
306 | writel(virt_to_bus(w840private.rx_ring), ioaddr + RxRingPtr);
|
---|
307 | writel(virt_to_bus(w840private.tx_ring), ioaddr + TxRingPtr);
|
---|
308 |
|
---|
309 | for (i = 0; i < ETH_ALEN; i++)
|
---|
310 | writeb(nic->node_addr[i], ioaddr + StationAddr + i);
|
---|
311 |
|
---|
312 | /* Initialize other registers. */
|
---|
313 | /* Configure the PCI bus bursts and FIFO thresholds.
|
---|
314 | 486: Set 8 longword cache alignment, 8 longword burst.
|
---|
315 | 586: Set 16 longword cache alignment, no burst limit.
|
---|
316 | Cache alignment bits 15:14 Burst length 13:8
|
---|
317 | 0000 <not allowed> 0000 align to cache 0800 8 longwords
|
---|
318 | 4000 8 longwords 0100 1 longword 1000 16 longwords
|
---|
319 | 8000 16 longwords 0200 2 longwords 2000 32 longwords
|
---|
320 | C000 32 longwords 0400 4 longwords
|
---|
321 | Wait the specified 50 PCI cycles after a reset by initializing
|
---|
322 | Tx and Rx queues and the address filter list. */
|
---|
323 |
|
---|
324 | writel(0xE010, ioaddr + PCIBusCfg);
|
---|
325 |
|
---|
326 | writel(0, ioaddr + RxStartDemand);
|
---|
327 | w840private.csr6 = 0x20022002;
|
---|
328 | check_duplex();
|
---|
329 | set_rx_mode();
|
---|
330 |
|
---|
331 | /* Do not enable the interrupts Etherboot doesn't need them */
|
---|
332 | /*
|
---|
333 | writel(0x1A0F5, ioaddr + IntrStatus);
|
---|
334 | writel(0x1A0F5, ioaddr + IntrEnable);
|
---|
335 | */
|
---|
336 | #if defined(W89C840_DEBUG)
|
---|
337 | printf("winbond-840 : Done reset.\n");
|
---|
338 | #endif
|
---|
339 | }
|
---|
340 |
|
---|
341 | #if 0
|
---|
342 | static void handle_intr(u32 intr_stat)
|
---|
343 | {
|
---|
344 | if ((intr_stat & (NormalIntr|AbnormalIntr)) == 0) {
|
---|
345 | /* we are polling, do not return now */
|
---|
346 | /*return 0;*/
|
---|
347 | } else {
|
---|
348 | /* Acknowledge all of the current interrupt sources ASAP. */
|
---|
349 | writel(intr_stat & 0x001ffff, ioaddr + IntrStatus);
|
---|
350 | }
|
---|
351 |
|
---|
352 | if (intr_stat & AbnormalIntr) {
|
---|
353 | /* There was an abnormal interrupt */
|
---|
354 | printf("\n-=- Abnormal interrupt.\n");
|
---|
355 |
|
---|
356 | #if defined(W89C840_DEBUG)
|
---|
357 | decode_interrupt(intr_stat);
|
---|
358 | #endif
|
---|
359 |
|
---|
360 | if (intr_stat & RxNoBuf) {
|
---|
361 | /* There was an interrupt */
|
---|
362 | printf("-=- <=> No receive buffers available.\n");
|
---|
363 | writel(0, ioaddr + RxStartDemand);
|
---|
364 | }
|
---|
365 | }
|
---|
366 | }
|
---|
367 | #endif
|
---|
368 |
|
---|
369 | /**************************************************************************
|
---|
370 | w89c840_poll - Wait for a frame
|
---|
371 | ***************************************************************************/
|
---|
372 | static int w89c840_poll(struct nic *nic, int retrieve)
|
---|
373 | {
|
---|
374 | /* return true if there's an ethernet packet ready to read */
|
---|
375 | /* nic->packet should contain data on return */
|
---|
376 | /* nic->packetlen should contain length of data */
|
---|
377 | int packet_received = 0;
|
---|
378 |
|
---|
379 | #if defined(W89C840_DEBUG)
|
---|
380 | u32 intr_status = readl(ioaddr + IntrStatus);
|
---|
381 | #endif
|
---|
382 |
|
---|
383 | do {
|
---|
384 | /* Code from netdev_rx(dev) */
|
---|
385 |
|
---|
386 | int entry = w840private.cur_rx % RX_RING_SIZE;
|
---|
387 |
|
---|
388 | struct w840_rx_desc *desc = w840private.rx_head_desc;
|
---|
389 | s32 status = desc->status;
|
---|
390 |
|
---|
391 | if (status & DescOwn) {
|
---|
392 | /* DescOwn bit is still set, we should wait for RX to complete */
|
---|
393 | packet_received = 0;
|
---|
394 | break;
|
---|
395 | }
|
---|
396 |
|
---|
397 | if ( !retrieve ) {
|
---|
398 | packet_received = 1;
|
---|
399 | break;
|
---|
400 | }
|
---|
401 |
|
---|
402 | if ((status & 0x38008300) != 0x0300) {
|
---|
403 | if ((status & 0x38000300) != 0x0300) {
|
---|
404 | /* Ingore earlier buffers. */
|
---|
405 | if ((status & 0xffff) != 0x7fff) {
|
---|
406 | printf("winbond-840 : Oversized Ethernet frame spanned "
|
---|
407 | "multiple buffers, entry %d status %X !\n",
|
---|
408 | w840private.cur_rx, status);
|
---|
409 | }
|
---|
410 | } else if (status & 0x8000) {
|
---|
411 | /* There was a fatal error. */
|
---|
412 | #if defined(W89C840_DEBUG)
|
---|
413 | printf("winbond-840 : Receive error, Rx status %X :", status);
|
---|
414 | if (status & 0x0890) {
|
---|
415 | printf(" RXLEN_ERROR");
|
---|
416 | }
|
---|
417 | if (status & 0x004C) {
|
---|
418 | printf(", FRAME_ERROR");
|
---|
419 | }
|
---|
420 | if (status & 0x0002) {
|
---|
421 | printf(", CRC_ERROR");
|
---|
422 | }
|
---|
423 | printf("\n");
|
---|
424 | #endif
|
---|
425 |
|
---|
426 | /* Simpy do a reset now... */
|
---|
427 | w89c840_reset(nic);
|
---|
428 |
|
---|
429 | packet_received = 0;
|
---|
430 | break;
|
---|
431 | }
|
---|
432 | } else {
|
---|
433 | /* Omit the four octet CRC from the length. */
|
---|
434 | int pkt_len = ((status >> 16) & 0x7ff) - 4;
|
---|
435 |
|
---|
436 | #if defined(W89C840_DEBUG)
|
---|
437 | printf(" netdev_rx() normal Rx pkt ring %d length %d status %X\n", entry, pkt_len, status);
|
---|
438 | #endif
|
---|
439 |
|
---|
440 | nic->packetlen = pkt_len;
|
---|
441 |
|
---|
442 | /* Check if the packet is long enough to accept without copying
|
---|
443 | to a minimally-sized skbuff. */
|
---|
444 |
|
---|
445 | memcpy(nic->packet, le32desc_to_virt(w840private.rx_ring[entry].buffer1), pkt_len);
|
---|
446 | packet_received = 1;
|
---|
447 |
|
---|
448 | /* Release buffer to NIC */
|
---|
449 | w840private.rx_ring[entry].status = DescOwn;
|
---|
450 |
|
---|
451 | #if defined(W89C840_DEBUG)
|
---|
452 | /* You will want this info for the initial debug. */
|
---|
453 | printf(" Rx data %hhX:%hhX:%hhX:%hhX:%hhX:"
|
---|
454 | "%hhX %hhX:%hhX:%hhX:%hhX:%hhX:%hhX %hhX%hhX "
|
---|
455 | "%hhX.%hhX.%hhX.%hhX.\n",
|
---|
456 | nic->packet[0], nic->packet[1], nic->packet[2], nic->packet[3],
|
---|
457 | nic->packet[4], nic->packet[5], nic->packet[6], nic->packet[7],
|
---|
458 | nic->packet[8], nic->packet[9], nic->packet[10],
|
---|
459 | nic->packet[11], nic->packet[12], nic->packet[13],
|
---|
460 | nic->packet[14], nic->packet[15], nic->packet[16],
|
---|
461 | nic->packet[17]);
|
---|
462 | #endif
|
---|
463 |
|
---|
464 | }
|
---|
465 |
|
---|
466 | entry = (++w840private.cur_rx) % RX_RING_SIZE;
|
---|
467 | w840private.rx_head_desc = &w840private.rx_ring[entry];
|
---|
468 | } while (0);
|
---|
469 |
|
---|
470 | return packet_received;
|
---|
471 | }
|
---|
472 |
|
---|
473 | /**************************************************************************
|
---|
474 | w89c840_transmit - Transmit a frame
|
---|
475 | ***************************************************************************/
|
---|
476 |
|
---|
477 | static void w89c840_transmit(
|
---|
478 | struct nic *nic,
|
---|
479 | const char *d, /* Destination */
|
---|
480 | unsigned int t, /* Type */
|
---|
481 | unsigned int s, /* size */
|
---|
482 | const char *p) /* Packet */
|
---|
483 | {
|
---|
484 | /* send the packet to destination */
|
---|
485 | unsigned entry;
|
---|
486 | int transmit_status;
|
---|
487 |
|
---|
488 | /* Caution: the write order is important here, set the field
|
---|
489 | with the "ownership" bits last. */
|
---|
490 |
|
---|
491 | /* Fill in our transmit buffer */
|
---|
492 | entry = w840private.cur_tx % TX_RING_SIZE;
|
---|
493 |
|
---|
494 | memcpy (tx_packet, d, ETH_ALEN); /* dst */
|
---|
495 | memcpy (tx_packet + ETH_ALEN, nic->node_addr, ETH_ALEN);/* src */
|
---|
496 |
|
---|
497 | *((char *) tx_packet + 12) = t >> 8; /* type */
|
---|
498 | *((char *) tx_packet + 13) = t;
|
---|
499 |
|
---|
500 | memcpy (tx_packet + ETH_HLEN, p, s);
|
---|
501 | s += ETH_HLEN;
|
---|
502 |
|
---|
503 | while (s < ETH_ZLEN)
|
---|
504 | *((char *) tx_packet + ETH_HLEN + (s++)) = 0;
|
---|
505 |
|
---|
506 | w840private.tx_ring[entry].buffer1 = virt_to_le32desc(tx_packet);
|
---|
507 |
|
---|
508 | w840private.tx_ring[entry].length = (DescWholePkt | (u32) s);
|
---|
509 | if (entry >= TX_RING_SIZE-1) /* Wrap ring */
|
---|
510 | w840private.tx_ring[entry].length |= (DescIntr | DescEndRing);
|
---|
511 | w840private.tx_ring[entry].status = (DescOwn);
|
---|
512 | w840private.cur_tx++;
|
---|
513 |
|
---|
514 | w840private.tx_q_bytes = (u16) s;
|
---|
515 | writel(0, ioaddr + TxStartDemand);
|
---|
516 |
|
---|
517 | /* Work around horrible bug in the chip by marking the queue as full
|
---|
518 | when we do not have FIFO room for a maximum sized packet. */
|
---|
519 |
|
---|
520 | if ((w840private.drv_flags & HasBrokenTx) && w840private.tx_q_bytes > TX_BUG_FIFO_LIMIT) {
|
---|
521 | /* Actually this is left to help finding error tails later in debugging...
|
---|
522 | * See Linux kernel driver in winbond-840.c for details.
|
---|
523 | */
|
---|
524 | w840private.tx_full = 1;
|
---|
525 | }
|
---|
526 |
|
---|
527 | #if defined(W89C840_DEBUG)
|
---|
528 | printf("winbond-840 : Transmit frame # %d size %d queued in slot %d.\n", w840private.cur_tx, s, entry);
|
---|
529 | #endif
|
---|
530 |
|
---|
531 | /* Now wait for TX to complete. */
|
---|
532 | transmit_status = w840private.tx_ring[entry].status;
|
---|
533 |
|
---|
534 | load_timer2(TX_TIMEOUT);
|
---|
535 |
|
---|
536 | {
|
---|
537 | #if defined W89C840_DEBUG
|
---|
538 | u32 intr_stat = 0;
|
---|
539 | #endif
|
---|
540 | while (1) {
|
---|
541 |
|
---|
542 | #if defined(W89C840_DEBUG)
|
---|
543 | decode_interrupt(intr_stat);
|
---|
544 | #endif
|
---|
545 |
|
---|
546 | while ( (transmit_status & DescOwn) && timer2_running()) {
|
---|
547 |
|
---|
548 | transmit_status = w840private.tx_ring[entry].status;
|
---|
549 | }
|
---|
550 |
|
---|
551 | break;
|
---|
552 | }
|
---|
553 | }
|
---|
554 |
|
---|
555 | if ((transmit_status & DescOwn) == 0) {
|
---|
556 |
|
---|
557 | #if defined(W89C840_DEBUG)
|
---|
558 | printf("winbond-840 : transmission complete after wait loop iterations, status %X\n",
|
---|
559 | w840private.tx_ring[entry].status);
|
---|
560 | #endif
|
---|
561 |
|
---|
562 | return;
|
---|
563 | }
|
---|
564 |
|
---|
565 | /* Transmit timed out... */
|
---|
566 |
|
---|
567 | printf("winbond-840 : transmission TIMEOUT : status %X\n", w840private.tx_ring[entry].status);
|
---|
568 |
|
---|
569 | return;
|
---|
570 | }
|
---|
571 |
|
---|
572 | /**************************************************************************
|
---|
573 | w89c840_disable - Turn off ethernet interface
|
---|
574 | ***************************************************************************/
|
---|
575 | static void w89c840_disable(struct dev *dev)
|
---|
576 | {
|
---|
577 | struct nic *nic = (struct nic *)dev;
|
---|
578 | /* merge reset and disable */
|
---|
579 | w89c840_reset(nic);
|
---|
580 |
|
---|
581 | /* Don't know what to do to disable the board. Is this needed at all? */
|
---|
582 | /* Yes, a live NIC can corrupt the loaded memory later [Ken] */
|
---|
583 | /* Stop the chip's Tx and Rx processes. */
|
---|
584 | writel(w840private.csr6 &= ~0x20FA, ioaddr + NetworkConfig);
|
---|
585 | }
|
---|
586 |
|
---|
587 | /**************************************************************************
|
---|
588 | w89c840_irq - Enable, Disable, or Force interrupts
|
---|
589 | ***************************************************************************/
|
---|
590 | static void w89c840_irq(struct nic *nic __unused, irq_action_t action __unused)
|
---|
591 | {
|
---|
592 | switch ( action ) {
|
---|
593 | case DISABLE :
|
---|
594 | break;
|
---|
595 | case ENABLE :
|
---|
596 | break;
|
---|
597 | case FORCE :
|
---|
598 | break;
|
---|
599 | }
|
---|
600 | }
|
---|
601 |
|
---|
602 | /**************************************************************************
|
---|
603 | w89c840_probe - Look for an adapter, this routine's visible to the outside
|
---|
604 | ***************************************************************************/
|
---|
605 | static int w89c840_probe(struct dev *dev, struct pci_device *p)
|
---|
606 | {
|
---|
607 | struct nic *nic = (struct nic *)dev;
|
---|
608 | u16 sum = 0;
|
---|
609 | int i, j;
|
---|
610 | unsigned short value;
|
---|
611 |
|
---|
612 | if (p->ioaddr == 0)
|
---|
613 | return 0;
|
---|
614 |
|
---|
615 | ioaddr = p->ioaddr;
|
---|
616 | nic->ioaddr = p->ioaddr & ~3;
|
---|
617 | nic->irqno = 0;
|
---|
618 |
|
---|
619 |
|
---|
620 | #if defined(W89C840_DEBUG)
|
---|
621 | printf("winbond-840: PCI bus %hhX device function %hhX: I/O address: %hX\n", p->bus, p->devfn, ioaddr);
|
---|
622 | #endif
|
---|
623 |
|
---|
624 | ioaddr = ioaddr & ~3; /* Mask the bit that says "this is an io addr" */
|
---|
625 |
|
---|
626 | #define PCI_DEVICE_ID_WINBOND2_89C840 0x0840
|
---|
627 | #define PCI_DEVICE_ID_COMPEX_RL100ATX 0x2011
|
---|
628 |
|
---|
629 | /* From Matt Hortman <[email protected]> */
|
---|
630 | if (p->vendor == PCI_VENDOR_ID_WINBOND2
|
---|
631 | && p->dev_id == PCI_DEVICE_ID_WINBOND2_89C840) {
|
---|
632 |
|
---|
633 | /* detected "Winbond W89c840 Fast Ethernet PCI NIC" */
|
---|
634 |
|
---|
635 | } else if ( p->vendor == PCI_VENDOR_ID_COMPEX
|
---|
636 | && p->dev_id == PCI_DEVICE_ID_COMPEX_RL100ATX) {
|
---|
637 |
|
---|
638 | /* detected "Compex RL100ATX Fast Ethernet PCI NIC" */
|
---|
639 |
|
---|
640 | } else {
|
---|
641 | /* Gee, guess what? They missed again. */
|
---|
642 | printf("device ID : %X - is not a Compex RL100ATX NIC.\n", p->dev_id);
|
---|
643 | return 0;
|
---|
644 | }
|
---|
645 |
|
---|
646 | printf(" %s\n", w89c840_version);
|
---|
647 |
|
---|
648 | adjust_pci_device(p);
|
---|
649 |
|
---|
650 | /* Ok. Got one. Read the eeprom. */
|
---|
651 | for (j = 0, i = 0; i < 0x40; i++) {
|
---|
652 | value = eeprom_read(ioaddr, i);
|
---|
653 | eeprom[i] = value;
|
---|
654 | sum += value;
|
---|
655 | }
|
---|
656 |
|
---|
657 | for (i=0;i<ETH_ALEN;i++) {
|
---|
658 | nic->node_addr[i] = (eeprom[i/2] >> (8*(i&1))) & 0xff;
|
---|
659 | }
|
---|
660 | printf ("Ethernet addr: %!\n", nic->node_addr);
|
---|
661 |
|
---|
662 | #if defined(W89C840_DEBUG)
|
---|
663 | printf("winbond-840: EEPROM checksum %hX, got eeprom", sum);
|
---|
664 | #endif
|
---|
665 |
|
---|
666 | /* Reset the chip to erase previous misconfiguration.
|
---|
667 | No hold time required! */
|
---|
668 | writel(0x00000001, ioaddr + PCIBusCfg);
|
---|
669 |
|
---|
670 | if (driver_flags & CanHaveMII) {
|
---|
671 | int phy, phy_idx = 0;
|
---|
672 | for (phy = 1; phy < 32 && phy_idx < 4; phy++) {
|
---|
673 | int mii_status = mdio_read(ioaddr, phy, 1);
|
---|
674 | if (mii_status != 0xffff && mii_status != 0x0000) {
|
---|
675 | w840private.phys[phy_idx++] = phy;
|
---|
676 | w840private.advertising = mdio_read(ioaddr, phy, 4);
|
---|
677 |
|
---|
678 | #if defined(W89C840_DEBUG)
|
---|
679 | printf("winbond-840 : MII PHY found at address %d, status "
|
---|
680 | "%X advertising %hX.\n", phy, mii_status, w840private.advertising);
|
---|
681 | #endif
|
---|
682 |
|
---|
683 | }
|
---|
684 | }
|
---|
685 |
|
---|
686 | w840private.mii_cnt = phy_idx;
|
---|
687 |
|
---|
688 | if (phy_idx == 0) {
|
---|
689 | printf("winbond-840 : MII PHY not found -- this device may not operate correctly.\n");
|
---|
690 | }
|
---|
691 | }
|
---|
692 |
|
---|
693 | /* point to NIC specific routines */
|
---|
694 | dev->disable = w89c840_disable;
|
---|
695 | nic->poll = w89c840_poll;
|
---|
696 | nic->transmit = w89c840_transmit;
|
---|
697 | nic->irq = w89c840_irq;
|
---|
698 |
|
---|
699 | w89c840_reset(nic);
|
---|
700 |
|
---|
701 | return 1;
|
---|
702 | }
|
---|
703 |
|
---|
704 | /* Read the EEPROM and MII Management Data I/O (MDIO) interfaces. These are
|
---|
705 | often serial bit streams generated by the host processor.
|
---|
706 | The example below is for the common 93c46 EEPROM, 64 16 bit words. */
|
---|
707 |
|
---|
708 | /* Delay between EEPROM clock transitions.
|
---|
709 | No extra delay is needed with 33Mhz PCI, but future 66Mhz access may need
|
---|
710 | a delay. Note that pre-2.0.34 kernels had a cache-alignment bug that
|
---|
711 | made udelay() unreliable.
|
---|
712 | The old method of using an ISA access as a delay, __SLOW_DOWN_IO__, is
|
---|
713 | depricated.
|
---|
714 | */
|
---|
715 | #define eeprom_delay(ee_addr) readl(ee_addr)
|
---|
716 |
|
---|
717 | enum EEPROM_Ctrl_Bits {
|
---|
718 | EE_ShiftClk=0x02, EE_Write0=0x801, EE_Write1=0x805,
|
---|
719 | EE_ChipSelect=0x801, EE_DataIn=0x08,
|
---|
720 | };
|
---|
721 |
|
---|
722 | /* The EEPROM commands include the alway-set leading bit. */
|
---|
723 | enum EEPROM_Cmds {
|
---|
724 | EE_WriteCmd=(5 << 6), EE_ReadCmd=(6 << 6), EE_EraseCmd=(7 << 6),
|
---|
725 | };
|
---|
726 |
|
---|
727 | static int eeprom_read(long addr, int location)
|
---|
728 | {
|
---|
729 | int i;
|
---|
730 | int retval = 0;
|
---|
731 | int ee_addr = addr + EECtrl;
|
---|
732 | int read_cmd = location | EE_ReadCmd;
|
---|
733 | writel(EE_ChipSelect, ee_addr);
|
---|
734 |
|
---|
735 | /* Shift the read command bits out. */
|
---|
736 | for (i = 10; i >= 0; i--) {
|
---|
737 | short dataval = (read_cmd & (1 << i)) ? EE_Write1 : EE_Write0;
|
---|
738 | writel(dataval, ee_addr);
|
---|
739 | eeprom_delay(ee_addr);
|
---|
740 | writel(dataval | EE_ShiftClk, ee_addr);
|
---|
741 | eeprom_delay(ee_addr);
|
---|
742 | }
|
---|
743 | writel(EE_ChipSelect, ee_addr);
|
---|
744 |
|
---|
745 | for (i = 16; i > 0; i--) {
|
---|
746 | writel(EE_ChipSelect | EE_ShiftClk, ee_addr);
|
---|
747 | eeprom_delay(ee_addr);
|
---|
748 | retval = (retval << 1) | ((readl(ee_addr) & EE_DataIn) ? 1 : 0);
|
---|
749 | writel(EE_ChipSelect, ee_addr);
|
---|
750 | eeprom_delay(ee_addr);
|
---|
751 | }
|
---|
752 |
|
---|
753 | /* Terminate the EEPROM access. */
|
---|
754 | writel(0, ee_addr);
|
---|
755 | return retval;
|
---|
756 | }
|
---|
757 |
|
---|
758 | /* MII transceiver control section.
|
---|
759 | Read and write the MII registers using software-generated serial
|
---|
760 | MDIO protocol. See the MII specifications or DP83840A data sheet
|
---|
761 | for details.
|
---|
762 |
|
---|
763 | The maximum data clock rate is 2.5 Mhz. The minimum timing is usually
|
---|
764 | met by back-to-back 33Mhz PCI cycles. */
|
---|
765 | #define mdio_delay(mdio_addr) readl(mdio_addr)
|
---|
766 |
|
---|
767 | /* Set iff a MII transceiver on any interface requires mdio preamble.
|
---|
768 | This only set with older tranceivers, so the extra
|
---|
769 | code size of a per-interface flag is not worthwhile. */
|
---|
770 | static char mii_preamble_required = 1;
|
---|
771 |
|
---|
772 | #define MDIO_WRITE0 (MDIO_EnbOutput)
|
---|
773 | #define MDIO_WRITE1 (MDIO_DataOut | MDIO_EnbOutput)
|
---|
774 |
|
---|
775 | /* Generate the preamble required for initial synchronization and
|
---|
776 | a few older transceivers. */
|
---|
777 | static void mdio_sync(long mdio_addr)
|
---|
778 | {
|
---|
779 | int bits = 32;
|
---|
780 |
|
---|
781 | /* Establish sync by sending at least 32 logic ones. */
|
---|
782 | while (--bits >= 0) {
|
---|
783 | writel(MDIO_WRITE1, mdio_addr);
|
---|
784 | mdio_delay(mdio_addr);
|
---|
785 | writel(MDIO_WRITE1 | MDIO_ShiftClk, mdio_addr);
|
---|
786 | mdio_delay(mdio_addr);
|
---|
787 | }
|
---|
788 | }
|
---|
789 |
|
---|
790 | static int mdio_read(int base_address, int phy_id, int location)
|
---|
791 | {
|
---|
792 | long mdio_addr = base_address + MIICtrl;
|
---|
793 | int mii_cmd = (0xf6 << 10) | (phy_id << 5) | location;
|
---|
794 | int i, retval = 0;
|
---|
795 |
|
---|
796 | if (mii_preamble_required)
|
---|
797 | mdio_sync(mdio_addr);
|
---|
798 |
|
---|
799 | /* Shift the read command bits out. */
|
---|
800 | for (i = 15; i >= 0; i--) {
|
---|
801 | int dataval = (mii_cmd & (1 << i)) ? MDIO_WRITE1 : MDIO_WRITE0;
|
---|
802 |
|
---|
803 | writel(dataval, mdio_addr);
|
---|
804 | mdio_delay(mdio_addr);
|
---|
805 | writel(dataval | MDIO_ShiftClk, mdio_addr);
|
---|
806 | mdio_delay(mdio_addr);
|
---|
807 | }
|
---|
808 | /* Read the two transition, 16 data, and wire-idle bits. */
|
---|
809 | for (i = 20; i > 0; i--) {
|
---|
810 | writel(MDIO_EnbIn, mdio_addr);
|
---|
811 | mdio_delay(mdio_addr);
|
---|
812 | retval = (retval << 1) | ((readl(mdio_addr) & MDIO_DataIn) ? 1 : 0);
|
---|
813 | writel(MDIO_EnbIn | MDIO_ShiftClk, mdio_addr);
|
---|
814 | mdio_delay(mdio_addr);
|
---|
815 | }
|
---|
816 | return (retval>>1) & 0xffff;
|
---|
817 | }
|
---|
818 |
|
---|
819 | #if 0
|
---|
820 | static void mdio_write(int base_address, int phy_id, int location, int value)
|
---|
821 | {
|
---|
822 | long mdio_addr = base_address + MIICtrl;
|
---|
823 | int mii_cmd = (0x5002 << 16) | (phy_id << 23) | (location<<18) | value;
|
---|
824 | int i;
|
---|
825 |
|
---|
826 | if (location == 4 && phy_id == w840private.phys[0])
|
---|
827 | w840private.advertising = value;
|
---|
828 |
|
---|
829 | if (mii_preamble_required)
|
---|
830 | mdio_sync(mdio_addr);
|
---|
831 |
|
---|
832 | /* Shift the command bits out. */
|
---|
833 | for (i = 31; i >= 0; i--) {
|
---|
834 | int dataval = (mii_cmd & (1 << i)) ? MDIO_WRITE1 : MDIO_WRITE0;
|
---|
835 |
|
---|
836 | writel(dataval, mdio_addr);
|
---|
837 | mdio_delay(mdio_addr);
|
---|
838 | writel(dataval | MDIO_ShiftClk, mdio_addr);
|
---|
839 | mdio_delay(mdio_addr);
|
---|
840 | }
|
---|
841 | /* Clear out extra bits. */
|
---|
842 | for (i = 2; i > 0; i--) {
|
---|
843 | writel(MDIO_EnbIn, mdio_addr);
|
---|
844 | mdio_delay(mdio_addr);
|
---|
845 | writel(MDIO_EnbIn | MDIO_ShiftClk, mdio_addr);
|
---|
846 | mdio_delay(mdio_addr);
|
---|
847 | }
|
---|
848 | return;
|
---|
849 | }
|
---|
850 | #endif
|
---|
851 |
|
---|
852 | static void check_duplex(void)
|
---|
853 | {
|
---|
854 | int mii_reg5 = mdio_read(ioaddr, w840private.phys[0], 5);
|
---|
855 | int negotiated = mii_reg5 & w840private.advertising;
|
---|
856 | int duplex;
|
---|
857 |
|
---|
858 | if (w840private.duplex_lock || mii_reg5 == 0xffff)
|
---|
859 | return;
|
---|
860 |
|
---|
861 | duplex = (negotiated & 0x0100) || (negotiated & 0x01C0) == 0x0040;
|
---|
862 | if (w840private.full_duplex != duplex) {
|
---|
863 | w840private.full_duplex = duplex;
|
---|
864 |
|
---|
865 | #if defined(W89C840_DEBUG)
|
---|
866 | printf("winbond-840 : Setting %s-duplex based on MII # %d negotiated capability %X\n",
|
---|
867 | duplex ? "full" : "half", w840private.phys[0], negotiated);
|
---|
868 | #endif
|
---|
869 |
|
---|
870 | w840private.csr6 &= ~0x200;
|
---|
871 | w840private.csr6 |= duplex ? 0x200 : 0;
|
---|
872 | }
|
---|
873 | }
|
---|
874 |
|
---|
875 | static void set_rx_mode(void)
|
---|
876 | {
|
---|
877 | u32 mc_filter[2]; /* Multicast hash filter */
|
---|
878 | u32 rx_mode;
|
---|
879 |
|
---|
880 | /* Accept all multicasts from now on. */
|
---|
881 | memset(mc_filter, 0xff, sizeof(mc_filter));
|
---|
882 |
|
---|
883 | /*
|
---|
884 | * works OK with multicast enabled.
|
---|
885 | */
|
---|
886 |
|
---|
887 | rx_mode = AcceptBroadcast | AcceptMyPhys | AcceptMulticast;
|
---|
888 |
|
---|
889 | writel(mc_filter[0], ioaddr + MulticastFilter0);
|
---|
890 | writel(mc_filter[1], ioaddr + MulticastFilter1);
|
---|
891 | w840private.csr6 &= ~0x00F8;
|
---|
892 | w840private.csr6 |= rx_mode;
|
---|
893 | writel(w840private.csr6, ioaddr + NetworkConfig);
|
---|
894 |
|
---|
895 | #if defined(W89C840_DEBUG)
|
---|
896 | printf("winbond-840 : Done setting RX mode.\n");
|
---|
897 | #endif
|
---|
898 | }
|
---|
899 |
|
---|
900 | /* Initialize the Rx and Tx rings, along with various 'dev' bits. */
|
---|
901 | static void init_ring(void)
|
---|
902 | {
|
---|
903 | int i;
|
---|
904 | char * p;
|
---|
905 |
|
---|
906 | w840private.tx_full = 0;
|
---|
907 | w840private.tx_q_bytes = w840private.cur_rx = w840private.cur_tx = 0;
|
---|
908 | w840private.dirty_rx = w840private.dirty_tx = 0;
|
---|
909 |
|
---|
910 | w840private.rx_buf_sz = PKT_BUF_SZ;
|
---|
911 | w840private.rx_head_desc = &w840private.rx_ring[0];
|
---|
912 |
|
---|
913 | /* Initial all Rx descriptors. Fill in the Rx buffers. */
|
---|
914 |
|
---|
915 | p = &rx_packet[0];
|
---|
916 |
|
---|
917 | for (i = 0; i < RX_RING_SIZE; i++) {
|
---|
918 | w840private.rx_ring[i].length = w840private.rx_buf_sz;
|
---|
919 | w840private.rx_ring[i].status = 0;
|
---|
920 | w840private.rx_ring[i].next_desc = virt_to_le32desc(&w840private.rx_ring[i+1]);
|
---|
921 |
|
---|
922 | w840private.rx_ring[i].buffer1 = virt_to_le32desc(p + (PKT_BUF_SZ * i));
|
---|
923 | w840private.rx_ring[i].status = DescOwn | DescIntr;
|
---|
924 | }
|
---|
925 |
|
---|
926 | /* Mark the last entry as wrapping the ring. */
|
---|
927 | w840private.rx_ring[i-1].length |= DescEndRing;
|
---|
928 | w840private.rx_ring[i-1].next_desc = virt_to_le32desc(&w840private.rx_ring[0]);
|
---|
929 |
|
---|
930 | w840private.dirty_rx = (unsigned int)(i - RX_RING_SIZE);
|
---|
931 |
|
---|
932 | for (i = 0; i < TX_RING_SIZE; i++) {
|
---|
933 | w840private.tx_ring[i].status = 0;
|
---|
934 | }
|
---|
935 | return;
|
---|
936 | }
|
---|
937 |
|
---|
938 |
|
---|
939 | static struct pci_id w89c840_nics[] = {
|
---|
940 | PCI_ROM(0x1050, 0x0840, "winbond840", "Winbond W89C840F"),
|
---|
941 | PCI_ROM(0x11f6, 0x2011, "compexrl100atx", "Compex RL100ATX"),
|
---|
942 | };
|
---|
943 |
|
---|
944 | static struct pci_driver w89c840_driver __pci_driver = {
|
---|
945 | .type = NIC_DRIVER,
|
---|
946 | .name = "W89C840F",
|
---|
947 | .probe = w89c840_probe,
|
---|
948 | .ids = w89c840_nics,
|
---|
949 | .id_count = sizeof(w89c840_nics)/sizeof(w89c840_nics[0]),
|
---|
950 | .class = 0,
|
---|
951 | };
|
---|