]> CyberLeo.Net >> Repos - FreeBSD/releng/10.0.git/blob - sys/dev/ed/if_ed_hpp.c
- Copy stable/10 (r259064) to releng/10.0 as part of the
[FreeBSD/releng/10.0.git] / sys / dev / ed / if_ed_hpp.c
1 /*-
2  * Copyright (c) 2005, M. Warner Losh
3  * All rights reserved.
4  * Copyright (c) 1995, David Greenman 
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice unmodified, this list of conditions, and the following
12  *    disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
21  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27  * SUCH DAMAGE.
28  */
29
30 #include <sys/cdefs.h>
31 __FBSDID("$FreeBSD$");
32
33 #include "opt_ed.h"
34
35 #ifdef ED_HPP
36
37 #include <sys/param.h>
38 #include <sys/systm.h>
39 #include <sys/sockio.h>
40 #include <sys/mbuf.h>
41 #include <sys/kernel.h>
42 #include <sys/socket.h>
43 #include <sys/syslog.h>
44
45 #include <sys/bus.h>
46
47 #include <machine/bus.h>
48 #include <sys/rman.h>
49 #include <machine/resource.h>
50
51 #include <net/ethernet.h>
52 #include <net/if.h>
53 #include <net/if_arp.h>
54 #include <net/if_dl.h>
55 #include <net/if_mib.h>
56 #include <net/if_media.h>
57
58 #include <net/bpf.h>
59
60 #include <dev/ed/if_edreg.h>
61 #include <dev/ed/if_edvar.h>
62
63 static void     ed_hpp_readmem(struct ed_softc *, bus_size_t, uint8_t *,
64                     uint16_t);
65 static void     ed_hpp_writemem(struct ed_softc *, uint8_t *, uint16_t,
66                     uint16_t);
67 static void     ed_hpp_set_physical_link(struct ed_softc *sc);
68 static u_short  ed_hpp_write_mbufs(struct ed_softc *, struct mbuf *,
69                     bus_size_t);
70
71 /*
72  * Interrupt conversion table for the HP PC LAN+
73  */
74 static uint16_t ed_hpp_intr_val[] = {
75         0,              /* 0 */
76         0,              /* 1 */
77         0,              /* 2 */
78         3,              /* 3 */
79         4,              /* 4 */
80         5,              /* 5 */
81         6,              /* 6 */
82         7,              /* 7 */
83         0,              /* 8 */
84         9,              /* 9 */
85         10,             /* 10 */
86         11,             /* 11 */
87         12,             /* 12 */
88         0,              /* 13 */
89         0,              /* 14 */
90         15              /* 15 */
91 };
92
93 #define ED_HPP_TEST_SIZE        16
94
95 /*
96  * Probe and vendor specific initialization for the HP PC Lan+ Cards.
97  * (HP Part nos: 27247B and 27252A).
98  *
99  * The card has an asic wrapper around a DS8390 core.  The asic handles 
100  * host accesses and offers both standard register IO and memory mapped 
101  * IO.  Memory mapped I/O allows better performance at the expense of greater
102  * chance of an incompatibility with existing ISA cards.
103  *
104  * The card has a few caveats: it isn't tolerant of byte wide accesses, only
105  * short (16 bit) or word (32 bit) accesses are allowed.  Some card revisions
106  * don't allow 32 bit accesses; these are indicated by a bit in the software
107  * ID register (see if_edreg.h).
108  * 
109  * Other caveats are: we should read the MAC address only when the card
110  * is inactive.
111  *
112  * For more information; please consult the CRYNWR packet driver.
113  *
114  * The AUI port is turned on using the "link2" option on the ifconfig 
115  * command line.
116  */
117 int
118 ed_probe_HP_pclanp(device_t dev, int port_rid, int flags)
119 {
120         struct ed_softc *sc = device_get_softc(dev);
121         int error;
122         int n;                          /* temp var */
123         int memsize;                    /* mem on board */
124         u_char checksum;                /* checksum of board address */
125         u_char irq;                     /* board configured IRQ */
126         uint8_t test_pattern[ED_HPP_TEST_SIZE]; /* read/write areas for */
127         uint8_t test_buffer[ED_HPP_TEST_SIZE];  /* probing card */
128         u_long conf_maddr, conf_msize, conf_irq, junk;
129
130         error = ed_alloc_port(dev, 0, ED_HPP_IO_PORTS);
131         if (error)
132                 return (error);
133
134         /* Fill in basic information */
135         sc->asic_offset = ED_HPP_ASIC_OFFSET;
136         sc->nic_offset  = ED_HPP_NIC_OFFSET;
137
138         sc->chip_type = ED_CHIP_TYPE_DP8390;
139         sc->isa16bit = 0;       /* the 8390 core needs to be in byte mode */
140
141         /* 
142          * Look for the HP PCLAN+ signature: "0x50,0x48,0x00,0x53" 
143          */
144         
145         if ((ed_asic_inb(sc, ED_HPP_ID) != 0x50) || 
146             (ed_asic_inb(sc, ED_HPP_ID + 1) != 0x48) ||
147             ((ed_asic_inb(sc, ED_HPP_ID + 2) & 0xF0) != 0) ||
148             (ed_asic_inb(sc, ED_HPP_ID + 3) != 0x53))
149                 return (ENXIO);
150
151         /* 
152          * Read the MAC address and verify checksum on the address.
153          */
154
155         ed_asic_outw(sc, ED_HPP_PAGING, ED_HPP_PAGE_MAC);
156         for (n  = 0, checksum = 0; n < ETHER_ADDR_LEN; n++)
157                 checksum += (sc->enaddr[n] = 
158                     ed_asic_inb(sc, ED_HPP_MAC_ADDR + n));
159         
160         checksum += ed_asic_inb(sc, ED_HPP_MAC_ADDR + ETHER_ADDR_LEN);
161
162         if (checksum != 0xFF)
163                 return (ENXIO);
164
165         /*
166          * Verify that the software model number is 0.
167          */
168         
169         ed_asic_outw(sc, ED_HPP_PAGING, ED_HPP_PAGE_ID);
170         if (((sc->hpp_id = ed_asic_inw(sc, ED_HPP_PAGE_4)) & 
171                 ED_HPP_ID_SOFT_MODEL_MASK) != 0x0000)
172                 return (ENXIO);
173
174         /*
175          * Read in and save the current options configured on card.
176          */
177
178         sc->hpp_options = ed_asic_inw(sc, ED_HPP_OPTION);
179
180         sc->hpp_options |= (ED_HPP_OPTION_NIC_RESET | 
181             ED_HPP_OPTION_CHIP_RESET | ED_HPP_OPTION_ENABLE_IRQ);
182
183         /* 
184          * Reset the chip.  This requires writing to the option register
185          * so take care to preserve the other bits.
186          */
187
188         ed_asic_outw(sc, ED_HPP_OPTION, 
189             (sc->hpp_options & ~(ED_HPP_OPTION_NIC_RESET | 
190             ED_HPP_OPTION_CHIP_RESET)));
191
192         DELAY(5000);    /* wait for chip reset to complete */
193
194         ed_asic_outw(sc, ED_HPP_OPTION,
195             (sc->hpp_options | (ED_HPP_OPTION_NIC_RESET |
196             ED_HPP_OPTION_CHIP_RESET |
197             ED_HPP_OPTION_ENABLE_IRQ)));
198
199         DELAY(5000);
200
201         if (!(ed_nic_inb(sc, ED_P0_ISR) & ED_ISR_RST))
202                 return (ENXIO); /* reset did not complete */
203
204         /*
205          * Read out configuration information.
206          */
207
208         ed_asic_outw(sc, ED_HPP_PAGING, ED_HPP_PAGE_HW);
209
210         irq = ed_asic_inb(sc, ED_HPP_HW_IRQ);
211
212         /*
213          * Check for impossible IRQ.
214          */
215
216         if (irq >= (sizeof(ed_hpp_intr_val) / sizeof(ed_hpp_intr_val[0])))
217                 return (ENXIO);
218
219         /* 
220          * If the kernel IRQ was specified with a '?' use the cards idea
221          * of the IRQ.  If the kernel IRQ was explicitly specified, it
222          * should match that of the hardware.
223          */
224         error = bus_get_resource(dev, SYS_RES_IRQ, 0, &conf_irq, &junk);
225         if (error)
226                 bus_set_resource(dev, SYS_RES_IRQ, 0, ed_hpp_intr_val[irq], 1);
227         else {
228                 if (conf_irq != ed_hpp_intr_val[irq])
229                         return (ENXIO);
230         }
231
232         /*
233          * Fill in softconfig info.
234          */
235
236         sc->vendor = ED_VENDOR_HP;
237         sc->type = ED_TYPE_HP_PCLANPLUS;
238         sc->type_str = "HP-PCLAN+";
239
240         sc->mem_shared = 0;     /* we DON'T have dual ported RAM */
241         sc->mem_start = 0;      /* we use offsets inside the card RAM */
242
243         sc->hpp_mem_start = NULL;/* no memory mapped I/O by default */
244
245         /*
246          * The board has 32KB of memory.  Is there a way to determine
247          * this programmatically?
248          */
249         
250         memsize = 32768;
251
252         /*
253          * Check if memory mapping of the I/O registers possible.
254          */
255         if (sc->hpp_options & ED_HPP_OPTION_MEM_ENABLE) {
256                 u_long mem_addr;
257
258                 /*
259                  * determine the memory address from the board.
260                  */
261                 
262                 ed_asic_outw(sc, ED_HPP_PAGING, ED_HPP_PAGE_HW);
263                 mem_addr = (ed_asic_inw(sc, ED_HPP_HW_MEM_MAP) << 8);
264
265                 /*
266                  * Check that the kernel specified start of memory and
267                  * hardware's idea of it match.
268                  */
269                 error = bus_get_resource(dev, SYS_RES_MEMORY, 0,
270                                          &conf_maddr, &conf_msize);
271                 if (error)
272                         return (error);
273                 
274                 if (mem_addr != conf_maddr)
275                         return (ENXIO);
276
277                 error = ed_alloc_memory(dev, 0, memsize);
278                 if (error)
279                         return (error);
280
281                 sc->hpp_mem_start = rman_get_virtual(sc->mem_res);
282         }
283
284         /*
285          * Fill in the rest of the soft config structure.
286          */
287
288         /*
289          * The transmit page index.
290          */
291
292         sc->tx_page_start = ED_HPP_TX_PAGE_OFFSET;
293
294         if (device_get_flags(dev) & ED_FLAGS_NO_MULTI_BUFFERING)
295                 sc->txb_cnt = 1;
296         else
297                 sc->txb_cnt = 2;
298
299         /*
300          * Memory description
301          */
302
303         sc->mem_size = memsize;
304         sc->mem_ring = sc->mem_start + 
305                 (sc->txb_cnt * ED_PAGE_SIZE * ED_TXBUF_SIZE);
306         sc->mem_end = sc->mem_start + sc->mem_size;
307
308         /*
309          * Receive area starts after the transmit area and 
310          * continues till the end of memory.
311          */
312
313         sc->rec_page_start = sc->tx_page_start + 
314                                 (sc->txb_cnt * ED_TXBUF_SIZE);
315         sc->rec_page_stop = (sc->mem_size / ED_PAGE_SIZE);
316
317
318         sc->cr_proto = 0;       /* value works */
319
320         /*
321          * Set the wrap registers for string I/O reads.
322          */
323
324         ed_asic_outw(sc, ED_HPP_PAGING, ED_HPP_PAGE_HW);
325         ed_asic_outw(sc, ED_HPP_HW_WRAP,
326             ((sc->rec_page_start / ED_PAGE_SIZE) |
327             (((sc->rec_page_stop / ED_PAGE_SIZE) - 1) << 8)));
328
329         /*
330          * Reset the register page to normal operation.
331          */
332
333         ed_asic_outw(sc, ED_HPP_PAGING, ED_HPP_PAGE_PERF);
334
335         /*
336          * Verify that we can read/write from adapter memory.
337          * Create test pattern.
338          */
339
340         for (n = 0; n < ED_HPP_TEST_SIZE; n++)
341                 test_pattern[n] = (n*n) ^ ~n;
342
343 #undef  ED_HPP_TEST_SIZE
344
345         /*
346          * Check that the memory is accessible thru the I/O ports.
347          * Write out the contents of "test_pattern", read back
348          * into "test_buffer" and compare the two for any
349          * mismatch.
350          */
351
352         for (n = 0; n < (32768 / ED_PAGE_SIZE); n ++) {
353                 ed_hpp_writemem(sc, test_pattern, (n * ED_PAGE_SIZE), 
354                                 sizeof(test_pattern));
355                 ed_hpp_readmem(sc, (n * ED_PAGE_SIZE), 
356                         test_buffer, sizeof(test_pattern));
357
358                 if (bcmp(test_pattern, test_buffer, 
359                         sizeof(test_pattern)))
360                         return (ENXIO);
361         }
362
363         sc->sc_mediachg = ed_hpp_set_physical_link;
364         sc->sc_write_mbufs = ed_hpp_write_mbufs;
365         sc->readmem = ed_hpp_readmem;
366         return (0);
367 }
368
369 /*
370  * HP PC Lan+ : Set the physical link to use AUI or TP/TL.
371  */
372
373 static void
374 ed_hpp_set_physical_link(struct ed_softc *sc)
375 {
376         struct ifnet *ifp = sc->ifp;
377         int lan_page;
378
379         ed_asic_outw(sc, ED_HPP_PAGING, ED_HPP_PAGE_LAN);
380         lan_page = ed_asic_inw(sc, ED_HPP_PAGE_0);
381
382         if (ifp->if_flags & IFF_LINK2) {
383                 /*
384                  * Use the AUI port.
385                  */
386
387                 lan_page |= ED_HPP_LAN_AUI;
388                 ed_asic_outw(sc, ED_HPP_PAGING, ED_HPP_PAGE_LAN);
389                 ed_asic_outw(sc, ED_HPP_PAGE_0, lan_page);
390         } else {
391                 /*
392                  * Use the ThinLan interface
393                  */
394
395                 lan_page &= ~ED_HPP_LAN_AUI;
396                 ed_asic_outw(sc, ED_HPP_PAGING, ED_HPP_PAGE_LAN);
397                 ed_asic_outw(sc, ED_HPP_PAGE_0, lan_page);
398         }
399
400         /*
401          * Wait for the lan card to re-initialize itself
402          */
403         DELAY(150000);  /* wait 150 ms */
404
405         /*
406          * Restore normal pages.
407          */
408         ed_asic_outw(sc, ED_HPP_PAGING, ED_HPP_PAGE_PERF);
409 }
410
411 /*
412  * Support routines to handle the HP PC Lan+ card.
413  */
414
415 /*
416  * HP PC Lan+: Read from NIC memory, using either PIO or memory mapped
417  * IO.
418  */
419
420 static void
421 ed_hpp_readmem(struct ed_softc *sc, bus_size_t src, uint8_t *dst,
422     uint16_t amount)
423 {
424         int use_32bit_access = !(sc->hpp_id & ED_HPP_ID_16_BIT_ACCESS);
425
426         /* Program the source address in RAM */
427         ed_asic_outw(sc, ED_HPP_PAGE_2, src);
428
429         /*
430          * The HP PC Lan+ card supports word reads as well as
431          * a memory mapped i/o port that is aliased to every 
432          * even address on the board.
433          */
434         if (sc->hpp_mem_start) {
435                 /* Enable memory mapped access.  */
436                 ed_asic_outw(sc, ED_HPP_OPTION, sc->hpp_options & 
437                         ~(ED_HPP_OPTION_MEM_DISABLE | 
438                           ED_HPP_OPTION_BOOT_ROM_ENB));
439
440                 if (use_32bit_access && (amount > 3)) {
441                         uint32_t *dl = (uint32_t *) dst;        
442                         volatile uint32_t *const sl = 
443                                 (uint32_t *) sc->hpp_mem_start;
444                         uint32_t *const fence = dl + (amount >> 2);
445                         
446                         /*
447                          * Copy out NIC data.  We could probably write this
448                          * as a `movsl'. The currently generated code is lousy.
449                          */
450                         while (dl < fence)
451                                 *dl++ = *sl;
452                 
453                         dst += (amount & ~3);
454                         amount &= 3;
455
456                 } 
457
458                 /* Finish off any words left, as a series of short reads */
459                 if (amount > 1) {
460                         u_short *d = (u_short *) dst;   
461                         volatile u_short *const s = 
462                                 (u_short *) sc->hpp_mem_start;
463                         u_short *const fence = d + (amount >> 1);
464                         
465                         /* Copy out NIC data.  */
466                         while (d < fence)
467                                 *d++ = *s;
468         
469                         dst += (amount & ~1);
470                         amount &= 1;
471                 }
472
473                 /*
474                  * read in a byte; however we need to always read 16 bits
475                  * at a time or the hardware gets into a funny state
476                  */
477
478                 if (amount == 1) {
479                         /* need to read in a short and copy LSB */
480                         volatile u_short *const s = 
481                                 (volatile u_short *) sc->hpp_mem_start;
482                         *dst = (*s) & 0xFF;     
483                 }
484
485                 /* Restore Boot ROM access.  */
486                 ed_asic_outw(sc, ED_HPP_OPTION, sc->hpp_options);
487         } else { 
488                 /* Read in data using the I/O port */
489                 if (use_32bit_access && (amount > 3)) {
490                         ed_asic_insl(sc, ED_HPP_PAGE_4, dst, amount >> 2);
491                         dst += (amount & ~3);
492                         amount &= 3;
493                 }
494                 if (amount > 1) {
495                         ed_asic_insw(sc, ED_HPP_PAGE_4, dst, amount >> 1);
496                         dst += (amount & ~1);
497                         amount &= 1;
498                 }
499                 if (amount == 1) { /* read in a short and keep the LSB */
500                         *dst = ed_asic_inw(sc, ED_HPP_PAGE_4) & 0xFF;
501                 }
502         }
503 }
504
505 /*
506  * HP PC Lan+: Write to NIC memory, using either PIO or memory mapped
507  * IO.
508  *      Only used in the probe routine to test the memory. 'len' must
509  *      be even.
510  */
511 static void
512 ed_hpp_writemem(struct ed_softc *sc, uint8_t *src, uint16_t dst, uint16_t len)
513 {
514         /* reset remote DMA complete flag */
515         ed_nic_outb(sc, ED_P0_ISR, ED_ISR_RDC);
516
517         /* program the write address in RAM */
518         ed_asic_outw(sc, ED_HPP_PAGE_0, dst);
519
520         if (sc->hpp_mem_start) {
521                 u_short *s = (u_short *) src;
522                 volatile u_short *d = (u_short *) sc->hpp_mem_start;
523                 u_short *const fence = s + (len >> 1);
524
525                 /*
526                  * Enable memory mapped access.
527                  */
528                 ed_asic_outw(sc, ED_HPP_OPTION, sc->hpp_options & 
529                         ~(ED_HPP_OPTION_MEM_DISABLE | 
530                           ED_HPP_OPTION_BOOT_ROM_ENB));
531
532                 /*
533                  * Copy to NIC memory.
534                  */
535                 while (s < fence)
536                         *d = *s++;
537
538                 /*
539                  * Restore Boot ROM access.
540                  */
541                 ed_asic_outw(sc, ED_HPP_OPTION, sc->hpp_options);
542         } else {
543                 /* write data using I/O writes */
544                 ed_asic_outsw(sc, ED_HPP_PAGE_4, src, len / 2);
545         }
546 }
547
548 /*
549  * Write to HP PC Lan+ NIC memory.  Access to the NIC can be by using 
550  * outsw() or via the memory mapped interface to the same register.
551  * Writes have to be in word units; byte accesses won't work and may cause
552  * the NIC to behave weirdly. Long word accesses are permitted if the ASIC
553  * allows it.
554  */
555
556 static u_short
557 ed_hpp_write_mbufs(struct ed_softc *sc, struct mbuf *m, bus_size_t dst)
558 {
559         int len, wantbyte;
560         unsigned short total_len;
561         unsigned char savebyte[2];
562         volatile u_short * const d = 
563                 (volatile u_short *) sc->hpp_mem_start;
564         int use_32bit_accesses = !(sc->hpp_id & ED_HPP_ID_16_BIT_ACCESS);
565
566         /* select page 0 registers */
567         ed_nic_outb(sc, ED_P0_CR, sc->cr_proto | ED_CR_STA);
568
569         /* reset remote DMA complete flag */
570         ed_nic_outb(sc, ED_P0_ISR, ED_ISR_RDC);
571
572         /* program the write address in RAM */
573         ed_asic_outw(sc, ED_HPP_PAGE_0, dst);
574
575         if (sc->hpp_mem_start)  /* enable memory mapped I/O */
576                 ed_asic_outw(sc, ED_HPP_OPTION, sc->hpp_options & 
577                         ~(ED_HPP_OPTION_MEM_DISABLE |
578                         ED_HPP_OPTION_BOOT_ROM_ENB));
579
580         wantbyte = 0;
581         total_len = 0;
582
583         if (sc->hpp_mem_start) {        /* Memory mapped I/O port */
584                 while (m) {
585                         total_len += (len = m->m_len);
586                         if (len) {
587                                 caddr_t data = mtod(m, caddr_t);
588                                 /* finish the last word of the previous mbuf */
589                                 if (wantbyte) {
590                                         savebyte[1] = *data;
591                                         *d = *((u_short *) savebyte);
592                                         data++; len--; wantbyte = 0;
593                                 }
594                                 /* output contiguous words */
595                                 if ((len > 3) && (use_32bit_accesses)) {
596                                         volatile uint32_t *const dl = 
597                                                 (volatile uint32_t *) d;
598                                         uint32_t *sl = (uint32_t *) data;
599                                         uint32_t *fence = sl + (len >> 2);
600
601                                         while (sl < fence)
602                                                 *dl = *sl++;
603
604                                         data += (len & ~3);
605                                         len &= 3;
606                                 }
607                                 /* finish off remain 16 bit writes */
608                                 if (len > 1) {
609                                         u_short *s = (u_short *) data;
610                                         u_short *fence = s + (len >> 1);
611
612                                         while (s < fence)
613                                                 *d = *s++;
614
615                                         data += (len & ~1); 
616                                         len &= 1;
617                                 }
618                                 /* save last byte if needed */
619                                 if ((wantbyte = (len == 1)) != 0)
620                                         savebyte[0] = *data;
621                         }
622                         m = m->m_next;  /* to next mbuf */
623                 }
624                 if (wantbyte) /* write last byte */
625                         *d = *((u_short *) savebyte);
626         } else {
627                 /* use programmed I/O */
628                 while (m) {
629                         total_len += (len = m->m_len);
630                         if (len) {
631                                 caddr_t data = mtod(m, caddr_t);
632                                 /* finish the last word of the previous mbuf */
633                                 if (wantbyte) {
634                                         savebyte[1] = *data;
635                                         ed_asic_outw(sc, ED_HPP_PAGE_4,
636                                                      *((u_short *)savebyte));
637                                         data++; 
638                                         len--; 
639                                         wantbyte = 0;
640                                 }
641                                 /* output contiguous words */
642                                 if ((len > 3) && use_32bit_accesses) {
643                                         ed_asic_outsl(sc, ED_HPP_PAGE_4,
644                                                       data, len >> 2);
645                                         data += (len & ~3);
646                                         len &= 3;
647                                 }
648                                 /* finish off remaining 16 bit accesses */
649                                 if (len > 1) {
650                                         ed_asic_outsw(sc, ED_HPP_PAGE_4,
651                                                       data, len >> 1);
652                                         data += (len & ~1);
653                                         len &= 1;
654                                 }
655                                 if ((wantbyte = (len == 1)) != 0)
656                                         savebyte[0] = *data;
657
658                         } /* if len != 0 */
659                         m = m->m_next;
660                 }
661                 if (wantbyte) /* spit last byte */
662                         ed_asic_outw(sc, ED_HPP_PAGE_4, *(u_short *)savebyte);
663
664         }
665
666         if (sc->hpp_mem_start)  /* turn off memory mapped i/o */
667                 ed_asic_outw(sc, ED_HPP_OPTION, sc->hpp_options);
668
669         return (total_len);
670 }
671
672 #endif /* ED_HPP */