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