1 | /* mac89x0.c: A Crystal Semiconductor CS89[02]0 driver for linux. */ |
2 | /* |
3 | Written 1996 by Russell Nelson, with reference to skeleton.c |
4 | written 1993-1994 by Donald Becker. |
5 | |
6 | This software may be used and distributed according to the terms |
7 | of the GNU General Public License, incorporated herein by reference. |
8 | |
9 | The author may be reached at nelson@crynwr.com, Crynwr |
10 | Software, 11 Grant St., Potsdam, NY 13676 |
11 | |
12 | Changelog: |
13 | |
14 | Mike Cruse : mcruse@cti-ltd.com |
15 | : Changes for Linux 2.0 compatibility. |
16 | : Added dev_id parameter in net_interrupt(), |
17 | : request_irq() and free_irq(). Just NULL for now. |
18 | |
19 | Mike Cruse : Added MOD_INC_USE_COUNT and MOD_DEC_USE_COUNT macros |
20 | : in net_open() and net_close() so kerneld would know |
21 | : that the module is in use and wouldn't eject the |
22 | : driver prematurely. |
23 | |
24 | Mike Cruse : Rewrote init_module() and cleanup_module using 8390.c |
25 | : as an example. Disabled autoprobing in init_module(), |
26 | : not a good thing to do to other devices while Linux |
27 | : is running from all accounts. |
28 | |
29 | Alan Cox : Removed 1.2 support, added 2.1 extra counters. |
30 | |
31 | David Huggins-Daines <dhd@debian.org> |
32 | |
33 | Split this off into mac89x0.c, and gutted it of all parts which are |
34 | not relevant to the existing CS8900 cards on the Macintosh |
35 | (i.e. basically the Daynaport CS and LC cards). To be precise: |
36 | |
37 | * Removed all the media-detection stuff, because these cards are |
38 | TP-only. |
39 | |
40 | * Lobotomized the ISA interrupt bogosity, because these cards use |
41 | a hardwired NuBus interrupt and a magic ISAIRQ value in the card. |
42 | |
43 | * Basically eliminated everything not relevant to getting the |
44 | cards minimally functioning on the Macintosh. |
45 | |
46 | I might add that these cards are badly designed even from the Mac |
47 | standpoint, in that Dayna, in their infinite wisdom, used NuBus slot |
48 | I/O space and NuBus interrupts for these cards, but neglected to |
49 | provide anything even remotely resembling a NuBus ROM. Therefore we |
50 | have to probe for them in a brain-damaged ISA-like fashion. |
51 | |
52 | Arnaldo Carvalho de Melo <acme@conectiva.com.br> - 11/01/2001 |
53 | check kmalloc and release the allocated memory on failure in |
54 | mac89x0_probe and in init_module |
55 | use local_irq_{save,restore}(flags) in net_get_stat, not just |
56 | local_irq_{dis,en}able() |
57 | */ |
58 | |
59 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
60 | |
61 | static const char version[] = |
62 | "cs89x0.c:v1.02 11/26/96 Russell Nelson <nelson@crynwr.com>\n" ; |
63 | |
64 | #include <linux/module.h> |
65 | |
66 | /* |
67 | Sources: |
68 | |
69 | Crynwr packet driver epktisa. |
70 | |
71 | Crystal Semiconductor data sheets. |
72 | |
73 | */ |
74 | |
75 | #include <linux/kernel.h> |
76 | #include <linux/types.h> |
77 | #include <linux/fcntl.h> |
78 | #include <linux/interrupt.h> |
79 | #include <linux/ioport.h> |
80 | #include <linux/in.h> |
81 | #include <linux/string.h> |
82 | #include <linux/nubus.h> |
83 | #include <linux/errno.h> |
84 | #include <linux/init.h> |
85 | #include <linux/netdevice.h> |
86 | #include <linux/platform_device.h> |
87 | #include <linux/etherdevice.h> |
88 | #include <linux/skbuff.h> |
89 | #include <linux/delay.h> |
90 | #include <linux/bitops.h> |
91 | #include <linux/gfp.h> |
92 | |
93 | #include <asm/io.h> |
94 | #include <asm/hwtest.h> |
95 | #include <asm/macints.h> |
96 | |
97 | #include "cs89x0.h" |
98 | |
99 | static int debug = -1; |
100 | module_param(debug, int, 0); |
101 | MODULE_PARM_DESC(debug, "debug message level" ); |
102 | |
103 | /* Information that need to be kept for each board. */ |
104 | struct net_local { |
105 | int msg_enable; |
106 | int chip_type; /* one of: CS8900, CS8920, CS8920M */ |
107 | char chip_revision; /* revision letter of the chip ('A'...) */ |
108 | int send_cmd; /* the propercommand used to send a packet. */ |
109 | int rx_mode; |
110 | int curr_rx_cfg; |
111 | int send_underrun; /* keep track of how many underruns in a row we get */ |
112 | }; |
113 | |
114 | /* Index to functions, as function prototypes. */ |
115 | static int net_open(struct net_device *dev); |
116 | static netdev_tx_t net_send_packet(struct sk_buff *skb, struct net_device *dev); |
117 | static irqreturn_t net_interrupt(int irq, void *dev_id); |
118 | static void set_multicast_list(struct net_device *dev); |
119 | static void net_rx(struct net_device *dev); |
120 | static int net_close(struct net_device *dev); |
121 | static struct net_device_stats *net_get_stats(struct net_device *dev); |
122 | static int set_mac_address(struct net_device *dev, void *addr); |
123 | |
124 | /* For reading/writing registers ISA-style */ |
125 | static inline int |
126 | readreg_io(struct net_device *dev, int portno) |
127 | { |
128 | nubus_writew(swab16(portno), dev->base_addr + ADD_PORT); |
129 | return swab16(nubus_readw(dev->base_addr + DATA_PORT)); |
130 | } |
131 | |
132 | static inline void |
133 | writereg_io(struct net_device *dev, int portno, int value) |
134 | { |
135 | nubus_writew(swab16(portno), dev->base_addr + ADD_PORT); |
136 | nubus_writew(swab16(value), dev->base_addr + DATA_PORT); |
137 | } |
138 | |
139 | /* These are for reading/writing registers in shared memory */ |
140 | static inline int |
141 | readreg(struct net_device *dev, int portno) |
142 | { |
143 | return swab16(nubus_readw(dev->mem_start + portno)); |
144 | } |
145 | |
146 | static inline void |
147 | writereg(struct net_device *dev, int portno, int value) |
148 | { |
149 | nubus_writew(swab16(value), dev->mem_start + portno); |
150 | } |
151 | |
152 | static const struct net_device_ops mac89x0_netdev_ops = { |
153 | .ndo_open = net_open, |
154 | .ndo_stop = net_close, |
155 | .ndo_start_xmit = net_send_packet, |
156 | .ndo_get_stats = net_get_stats, |
157 | .ndo_set_rx_mode = set_multicast_list, |
158 | .ndo_set_mac_address = set_mac_address, |
159 | .ndo_validate_addr = eth_validate_addr, |
160 | }; |
161 | |
162 | /* Probe for the CS8900 card in slot E. We won't bother looking |
163 | anywhere else until we have a really good reason to do so. */ |
164 | static int mac89x0_device_probe(struct platform_device *pdev) |
165 | { |
166 | struct net_device *dev; |
167 | struct net_local *lp; |
168 | int i, slot; |
169 | unsigned rev_type = 0; |
170 | unsigned long ioaddr; |
171 | unsigned short sig; |
172 | int err = -ENODEV; |
173 | struct nubus_rsrc *fres; |
174 | |
175 | dev = alloc_etherdev(sizeof(struct net_local)); |
176 | if (!dev) |
177 | return -ENOMEM; |
178 | |
179 | /* We might have to parameterize this later */ |
180 | slot = 0xE; |
181 | /* Get out now if there's a real NuBus card in slot E */ |
182 | for_each_func_rsrc(fres) |
183 | if (fres->board->slot == slot) |
184 | goto out; |
185 | |
186 | /* The pseudo-ISA bits always live at offset 0x300 (gee, |
187 | wonder why...) */ |
188 | ioaddr = (unsigned long) |
189 | nubus_slot_addr(slot) | (((slot&0xf) << 20) + DEFAULTIOBASE); |
190 | { |
191 | int card_present; |
192 | |
193 | card_present = (hwreg_present((void *)ioaddr + 4) && |
194 | hwreg_present((void *)ioaddr + DATA_PORT)); |
195 | if (!card_present) |
196 | goto out; |
197 | } |
198 | |
199 | nubus_writew(0, ioaddr + ADD_PORT); |
200 | sig = nubus_readw(ioaddr + DATA_PORT); |
201 | if (sig != swab16(CHIP_EISA_ID_SIG)) |
202 | goto out; |
203 | |
204 | SET_NETDEV_DEV(dev, &pdev->dev); |
205 | |
206 | /* Initialize the net_device structure. */ |
207 | lp = netdev_priv(dev); |
208 | |
209 | lp->msg_enable = netif_msg_init(debug_value: debug, default_msg_enable_bits: 0); |
210 | |
211 | /* Fill in the 'dev' fields. */ |
212 | dev->base_addr = ioaddr; |
213 | dev->mem_start = (unsigned long) |
214 | nubus_slot_addr(slot) | (((slot&0xf) << 20) + MMIOBASE); |
215 | dev->mem_end = dev->mem_start + 0x1000; |
216 | |
217 | /* Turn on shared memory */ |
218 | writereg_io(dev, PP_BusCTL, MEMORY_ON); |
219 | |
220 | /* get the chip type */ |
221 | rev_type = readreg(dev, PRODUCT_ID_ADD); |
222 | lp->chip_type = rev_type &~ REVISON_BITS; |
223 | lp->chip_revision = ((rev_type & REVISON_BITS) >> 8) + 'A'; |
224 | |
225 | /* Check the chip type and revision in order to set the correct send command |
226 | CS8920 revision C and CS8900 revision F can use the faster send. */ |
227 | lp->send_cmd = TX_AFTER_381; |
228 | if (lp->chip_type == CS8900 && lp->chip_revision >= 'F') |
229 | lp->send_cmd = TX_NOW; |
230 | if (lp->chip_type != CS8900 && lp->chip_revision >= 'C') |
231 | lp->send_cmd = TX_NOW; |
232 | |
233 | netif_dbg(lp, drv, dev, "%s" , version); |
234 | |
235 | pr_info("cs89%c0%s rev %c found at %#8lx\n" , |
236 | lp->chip_type == CS8900 ? '0' : '2', |
237 | lp->chip_type == CS8920M ? "M" : "" , |
238 | lp->chip_revision, dev->base_addr); |
239 | |
240 | /* Try to read the MAC address */ |
241 | if ((readreg(dev, PP_SelfST) & (EEPROM_PRESENT | EEPROM_OK)) == 0) { |
242 | pr_info("No EEPROM, giving up now.\n" ); |
243 | goto out1; |
244 | } else { |
245 | u8 addr[ETH_ALEN]; |
246 | |
247 | for (i = 0; i < ETH_ALEN; i += 2) { |
248 | /* Big-endian (why??!) */ |
249 | unsigned short s = readreg(dev, PP_IA + i); |
250 | addr[i] = s >> 8; |
251 | addr[i+1] = s & 0xff; |
252 | } |
253 | eth_hw_addr_set(dev, addr); |
254 | } |
255 | |
256 | dev->irq = SLOT2IRQ(slot); |
257 | |
258 | /* print the IRQ and ethernet address. */ |
259 | |
260 | pr_info("MAC %pM, IRQ %d\n" , dev->dev_addr, dev->irq); |
261 | |
262 | dev->netdev_ops = &mac89x0_netdev_ops; |
263 | |
264 | err = register_netdev(dev); |
265 | if (err) |
266 | goto out1; |
267 | |
268 | platform_set_drvdata(pdev, data: dev); |
269 | return 0; |
270 | out1: |
271 | nubus_writew(0, dev->base_addr + ADD_PORT); |
272 | out: |
273 | free_netdev(dev); |
274 | return err; |
275 | } |
276 | |
277 | /* Open/initialize the board. This is called (in the current kernel) |
278 | sometime after booting when the 'ifconfig' program is run. |
279 | |
280 | This routine should set everything up anew at each open, even |
281 | registers that "should" only need to be set once at boot, so that |
282 | there is non-reboot way to recover if something goes wrong. |
283 | */ |
284 | static int |
285 | net_open(struct net_device *dev) |
286 | { |
287 | struct net_local *lp = netdev_priv(dev); |
288 | int i; |
289 | |
290 | /* Disable the interrupt for now */ |
291 | writereg(dev, PP_BusCTL, value: readreg(dev, PP_BusCTL) & ~ENABLE_IRQ); |
292 | |
293 | /* Grab the interrupt */ |
294 | if (request_irq(irq: dev->irq, handler: net_interrupt, flags: 0, name: "cs89x0" , dev)) |
295 | return -EAGAIN; |
296 | |
297 | /* Set up the IRQ - Apparently magic */ |
298 | if (lp->chip_type == CS8900) |
299 | writereg(dev, PP_CS8900_ISAINT, value: 0); |
300 | else |
301 | writereg(dev, PP_CS8920_ISAINT, value: 0); |
302 | |
303 | /* set the Ethernet address */ |
304 | for (i=0; i < ETH_ALEN/2; i++) |
305 | writereg(dev, PP_IA+i*2, value: dev->dev_addr[i*2] | (dev->dev_addr[i*2+1] << 8)); |
306 | |
307 | /* Turn on both receive and transmit operations */ |
308 | writereg(dev, PP_LineCTL, value: readreg(dev, PP_LineCTL) | SERIAL_RX_ON | SERIAL_TX_ON); |
309 | |
310 | /* Receive only error free packets addressed to this card */ |
311 | lp->rx_mode = 0; |
312 | writereg(dev, PP_RxCTL, DEF_RX_ACCEPT); |
313 | |
314 | lp->curr_rx_cfg = RX_OK_ENBL | RX_CRC_ERROR_ENBL; |
315 | |
316 | writereg(dev, PP_RxCFG, value: lp->curr_rx_cfg); |
317 | |
318 | writereg(dev, PP_TxCFG, TX_LOST_CRS_ENBL | TX_SQE_ERROR_ENBL | TX_OK_ENBL | |
319 | TX_LATE_COL_ENBL | TX_JBR_ENBL | TX_ANY_COL_ENBL | TX_16_COL_ENBL); |
320 | |
321 | writereg(dev, PP_BufCFG, READY_FOR_TX_ENBL | RX_MISS_COUNT_OVRFLOW_ENBL | |
322 | TX_COL_COUNT_OVRFLOW_ENBL | TX_UNDERRUN_ENBL); |
323 | |
324 | /* now that we've got our act together, enable everything */ |
325 | writereg(dev, PP_BusCTL, value: readreg(dev, PP_BusCTL) | ENABLE_IRQ); |
326 | netif_start_queue(dev); |
327 | return 0; |
328 | } |
329 | |
330 | static netdev_tx_t |
331 | net_send_packet(struct sk_buff *skb, struct net_device *dev) |
332 | { |
333 | struct net_local *lp = netdev_priv(dev); |
334 | unsigned long flags; |
335 | |
336 | netif_dbg(lp, tx_queued, dev, "sent %d byte packet of type %x\n" , |
337 | skb->len, skb->data[ETH_ALEN + ETH_ALEN] << 8 | |
338 | skb->data[ETH_ALEN + ETH_ALEN + 1]); |
339 | |
340 | /* keep the upload from being interrupted, since we |
341 | ask the chip to start transmitting before the |
342 | whole packet has been completely uploaded. */ |
343 | local_irq_save(flags); |
344 | netif_stop_queue(dev); |
345 | |
346 | /* initiate a transmit sequence */ |
347 | writereg(dev, PP_TxCMD, value: lp->send_cmd); |
348 | writereg(dev, PP_TxLength, value: skb->len); |
349 | |
350 | /* Test to see if the chip has allocated memory for the packet */ |
351 | if ((readreg(dev, PP_BusST) & READY_FOR_TX_NOW) == 0) { |
352 | /* Gasp! It hasn't. But that shouldn't happen since |
353 | we're waiting for TxOk, so return 1 and requeue this packet. */ |
354 | local_irq_restore(flags); |
355 | return NETDEV_TX_BUSY; |
356 | } |
357 | |
358 | /* Write the contents of the packet */ |
359 | skb_copy_from_linear_data(skb, to: (void *)(dev->mem_start + PP_TxFrame), |
360 | len: skb->len+1); |
361 | |
362 | local_irq_restore(flags); |
363 | dev_kfree_skb (skb); |
364 | |
365 | return NETDEV_TX_OK; |
366 | } |
367 | |
368 | /* The typical workload of the driver: |
369 | Handle the network interface interrupts. */ |
370 | static irqreturn_t net_interrupt(int irq, void *dev_id) |
371 | { |
372 | struct net_device *dev = dev_id; |
373 | struct net_local *lp; |
374 | int ioaddr, status; |
375 | |
376 | ioaddr = dev->base_addr; |
377 | lp = netdev_priv(dev); |
378 | |
379 | /* we MUST read all the events out of the ISQ, otherwise we'll never |
380 | get interrupted again. As a consequence, we can't have any limit |
381 | on the number of times we loop in the interrupt handler. The |
382 | hardware guarantees that eventually we'll run out of events. Of |
383 | course, if you're on a slow machine, and packets are arriving |
384 | faster than you can read them off, you're screwed. Hasta la |
385 | vista, baby! */ |
386 | while ((status = swab16(nubus_readw(dev->base_addr + ISQ_PORT)))) { |
387 | netif_dbg(lp, intr, dev, "status=%04x\n" , status); |
388 | switch(status & ISQ_EVENT_MASK) { |
389 | case ISQ_RECEIVER_EVENT: |
390 | /* Got a packet(s). */ |
391 | net_rx(dev); |
392 | break; |
393 | case ISQ_TRANSMITTER_EVENT: |
394 | dev->stats.tx_packets++; |
395 | netif_wake_queue(dev); |
396 | if ((status & TX_OK) == 0) |
397 | dev->stats.tx_errors++; |
398 | if (status & TX_LOST_CRS) |
399 | dev->stats.tx_carrier_errors++; |
400 | if (status & TX_SQE_ERROR) |
401 | dev->stats.tx_heartbeat_errors++; |
402 | if (status & TX_LATE_COL) |
403 | dev->stats.tx_window_errors++; |
404 | if (status & TX_16_COL) |
405 | dev->stats.tx_aborted_errors++; |
406 | break; |
407 | case ISQ_BUFFER_EVENT: |
408 | if (status & READY_FOR_TX) { |
409 | /* we tried to transmit a packet earlier, |
410 | but inexplicably ran out of buffers. |
411 | That shouldn't happen since we only ever |
412 | load one packet. Shrug. Do the right |
413 | thing anyway. */ |
414 | netif_wake_queue(dev); |
415 | } |
416 | if (status & TX_UNDERRUN) { |
417 | netif_dbg(lp, tx_err, dev, "transmit underrun\n" ); |
418 | lp->send_underrun++; |
419 | if (lp->send_underrun == 3) lp->send_cmd = TX_AFTER_381; |
420 | else if (lp->send_underrun == 6) lp->send_cmd = TX_AFTER_ALL; |
421 | } |
422 | break; |
423 | case ISQ_RX_MISS_EVENT: |
424 | dev->stats.rx_missed_errors += (status >> 6); |
425 | break; |
426 | case ISQ_TX_COL_EVENT: |
427 | dev->stats.collisions += (status >> 6); |
428 | break; |
429 | } |
430 | } |
431 | return IRQ_HANDLED; |
432 | } |
433 | |
434 | /* We have a good packet(s), get it/them out of the buffers. */ |
435 | static void |
436 | net_rx(struct net_device *dev) |
437 | { |
438 | struct net_local *lp = netdev_priv(dev); |
439 | struct sk_buff *skb; |
440 | int status, length; |
441 | |
442 | status = readreg(dev, PP_RxStatus); |
443 | if ((status & RX_OK) == 0) { |
444 | dev->stats.rx_errors++; |
445 | if (status & RX_RUNT) |
446 | dev->stats.rx_length_errors++; |
447 | if (status & RX_EXTRA_DATA) |
448 | dev->stats.rx_length_errors++; |
449 | if ((status & RX_CRC_ERROR) && |
450 | !(status & (RX_EXTRA_DATA|RX_RUNT))) |
451 | /* per str 172 */ |
452 | dev->stats.rx_crc_errors++; |
453 | if (status & RX_DRIBBLE) |
454 | dev->stats.rx_frame_errors++; |
455 | return; |
456 | } |
457 | |
458 | length = readreg(dev, PP_RxLength); |
459 | /* Malloc up new buffer. */ |
460 | skb = alloc_skb(size: length, GFP_ATOMIC); |
461 | if (skb == NULL) { |
462 | dev->stats.rx_dropped++; |
463 | return; |
464 | } |
465 | skb_put(skb, len: length); |
466 | |
467 | skb_copy_to_linear_data(skb, from: (void *)(dev->mem_start + PP_RxFrame), |
468 | len: length); |
469 | |
470 | netif_dbg(lp, rx_status, dev, "received %d byte packet of type %x\n" , |
471 | length, skb->data[ETH_ALEN + ETH_ALEN] << 8 | |
472 | skb->data[ETH_ALEN + ETH_ALEN + 1]); |
473 | |
474 | skb->protocol=eth_type_trans(skb,dev); |
475 | netif_rx(skb); |
476 | dev->stats.rx_packets++; |
477 | dev->stats.rx_bytes += length; |
478 | } |
479 | |
480 | /* The inverse routine to net_open(). */ |
481 | static int |
482 | net_close(struct net_device *dev) |
483 | { |
484 | |
485 | writereg(dev, PP_RxCFG, value: 0); |
486 | writereg(dev, PP_TxCFG, value: 0); |
487 | writereg(dev, PP_BufCFG, value: 0); |
488 | writereg(dev, PP_BusCTL, value: 0); |
489 | |
490 | netif_stop_queue(dev); |
491 | |
492 | free_irq(dev->irq, dev); |
493 | |
494 | /* Update the statistics here. */ |
495 | |
496 | return 0; |
497 | |
498 | } |
499 | |
500 | /* Get the current statistics. This may be called with the card open or |
501 | closed. */ |
502 | static struct net_device_stats * |
503 | net_get_stats(struct net_device *dev) |
504 | { |
505 | unsigned long flags; |
506 | |
507 | local_irq_save(flags); |
508 | /* Update the statistics from the device registers. */ |
509 | dev->stats.rx_missed_errors += (readreg(dev, PP_RxMiss) >> 6); |
510 | dev->stats.collisions += (readreg(dev, PP_TxCol) >> 6); |
511 | local_irq_restore(flags); |
512 | |
513 | return &dev->stats; |
514 | } |
515 | |
516 | static void set_multicast_list(struct net_device *dev) |
517 | { |
518 | struct net_local *lp = netdev_priv(dev); |
519 | |
520 | if(dev->flags&IFF_PROMISC) |
521 | { |
522 | lp->rx_mode = RX_ALL_ACCEPT; |
523 | } else if ((dev->flags & IFF_ALLMULTI) || !netdev_mc_empty(dev)) { |
524 | /* The multicast-accept list is initialized to accept-all, and we |
525 | rely on higher-level filtering for now. */ |
526 | lp->rx_mode = RX_MULTCAST_ACCEPT; |
527 | } |
528 | else |
529 | lp->rx_mode = 0; |
530 | |
531 | writereg(dev, PP_RxCTL, DEF_RX_ACCEPT | lp->rx_mode); |
532 | |
533 | /* in promiscuous mode, we accept errored packets, so we have to enable interrupts on them also */ |
534 | writereg(dev, PP_RxCFG, value: lp->curr_rx_cfg | |
535 | (lp->rx_mode == RX_ALL_ACCEPT? (RX_CRC_ERROR_ENBL|RX_RUNT_ENBL|RX_EXTRA_DATA_ENBL) : 0)); |
536 | } |
537 | |
538 | |
539 | static int set_mac_address(struct net_device *dev, void *addr) |
540 | { |
541 | struct sockaddr *saddr = addr; |
542 | int i; |
543 | |
544 | if (!is_valid_ether_addr(addr: saddr->sa_data)) |
545 | return -EADDRNOTAVAIL; |
546 | |
547 | eth_hw_addr_set(dev, addr: saddr->sa_data); |
548 | netdev_info(dev, format: "Setting MAC address to %pM\n" , dev->dev_addr); |
549 | |
550 | /* set the Ethernet address */ |
551 | for (i=0; i < ETH_ALEN/2; i++) |
552 | writereg(dev, PP_IA+i*2, value: dev->dev_addr[i*2] | (dev->dev_addr[i*2+1] << 8)); |
553 | |
554 | return 0; |
555 | } |
556 | |
557 | MODULE_LICENSE("GPL" ); |
558 | |
559 | static void mac89x0_device_remove(struct platform_device *pdev) |
560 | { |
561 | struct net_device *dev = platform_get_drvdata(pdev); |
562 | |
563 | unregister_netdev(dev); |
564 | nubus_writew(0, dev->base_addr + ADD_PORT); |
565 | free_netdev(dev); |
566 | } |
567 | |
568 | static struct platform_driver mac89x0_platform_driver = { |
569 | .probe = mac89x0_device_probe, |
570 | .remove_new = mac89x0_device_remove, |
571 | .driver = { |
572 | .name = "mac89x0" , |
573 | }, |
574 | }; |
575 | |
576 | module_platform_driver(mac89x0_platform_driver); |
577 | |