xref: /netbsd-src/sys/arch/arm/iomd/vidcaudio.c (revision 96230fab84e26a6435963032070e916a951a8b2e)
1 /*	$NetBSD: vidcaudio.c,v 1.46 2008/03/01 16:17:47 chris Exp $	*/
2 
3 /*
4  * Copyright (c) 1995 Melvin Tang-Richardson
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  * 3. All advertising materials mentioning features or use of this software
15  *    must display the following acknowledgement:
16  *	This product includes software developed by the RiscBSD team.
17  * 4. The name of the author may not be used to endorse or promote products
18  *    derived from this software without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
21  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
22  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
23  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
24  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
25  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
29  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30  */
31 
32 /*-
33  * Copyright (c) 2003 Ben Harris
34  * All rights reserved.
35  *
36  * Redistribution and use in source and binary forms, with or without
37  * modification, are permitted provided that the following conditions
38  * are met:
39  * 1. Redistributions of source code must retain the above copyright
40  *    notice, this list of conditions and the following disclaimer.
41  * 2. Redistributions in binary form must reproduce the above copyright
42  *    notice, this list of conditions and the following disclaimer in the
43  *    documentation and/or other materials provided with the distribution.
44  * 3. The name of the author may not be used to endorse or promote products
45  *    derived from this software without specific prior written permission.
46  *
47  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
48  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
49  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
50  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
51  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
52  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
53  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
54  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
55  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
56  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
57  */
58 
59 /*
60  * audio driver for the RiscPC 8/16 bit sound
61  *
62  * Interfaces with the NetBSD generic audio driver to provide SUN
63  * /dev/audio (partial) compatibility.
64  */
65 
66 #include <sys/param.h>	/* proc.h */
67 
68 __KERNEL_RCSID(0, "$NetBSD: vidcaudio.c,v 1.46 2008/03/01 16:17:47 chris Exp $");
69 
70 #include <sys/audioio.h>
71 #include <sys/conf.h>   /* autoconfig functions */
72 #include <sys/device.h> /* device calls */
73 #include <sys/errno.h>
74 #include <sys/malloc.h>
75 #include <sys/proc.h>	/* device calls */
76 #include <sys/systm.h>
77 
78 #include <uvm/uvm_extern.h>
79 
80 #include <dev/audio_if.h>
81 #include <dev/audiobellvar.h>
82 #include <dev/auconv.h>
83 #include <dev/mulaw.h>
84 
85 #include <machine/intr.h>
86 #include <machine/machdep.h>
87 #include <arm/arm32/katelib.h>
88 
89 #include <arm/iomd/vidcaudiovar.h>
90 #include <arm/iomd/iomdreg.h>
91 #include <arm/iomd/iomdvar.h>
92 #include <arm/iomd/vidc.h>
93 #include <arm/mainbus/mainbus.h>
94 
95 #include "pckbd.h"
96 #if NPCKBD > 0
97 #include <dev/pckbport/pckbdvar.h>
98 #endif
99 
100 extern int *vidc_base;
101 
102 #ifdef VIDCAUDIO_DEBUG
103 #define DPRINTF(x)	printf x
104 #else
105 #define DPRINTF(x)
106 #endif
107 
108 struct vidcaudio_softc {
109 	struct	device sc_dev;
110 
111 	irqhandler_t	sc_ih;
112 	int	sc_dma_intr;
113 
114 	int	sc_is16bit;
115 
116 	size_t	sc_pblksize;
117 	vm_offset_t	sc_poffset;
118 	vm_offset_t	sc_pbufsize;
119 	paddr_t	*sc_ppages;
120 	void	(*sc_pintr)(void *);
121 	void	*sc_parg;
122 	int	sc_pcountdown;
123 };
124 
125 static int  vidcaudio_probe(struct device *, struct cfdata *, void *);
126 static void vidcaudio_attach(struct device *, struct device *, void *);
127 static void vidcaudio_close(void *);
128 
129 static int vidcaudio_intr(void *);
130 static void vidcaudio_rate(int);
131 static void vidcaudio_ctrl(int);
132 static void vidcaudio_stereo(int, int);
133 static stream_filter_factory_t mulaw_to_vidc;
134 static stream_filter_factory_t mulaw_to_vidc_stereo;
135 static int mulaw_to_vidc_fetch_to(stream_fetcher_t *, audio_stream_t *, int);
136 static int mulaw_to_vidc_stereo_fetch_to(stream_fetcher_t *,
137     audio_stream_t *, int);
138 
139 CFATTACH_DECL(vidcaudio, sizeof(struct vidcaudio_softc),
140     vidcaudio_probe, vidcaudio_attach, NULL, NULL);
141 
142 static int    vidcaudio_query_encoding(void *, struct audio_encoding *);
143 static int    vidcaudio_set_params(void *, int, int, audio_params_t *,
144     audio_params_t *, stream_filter_list_t *, stream_filter_list_t *);
145 static int    vidcaudio_round_blocksize(void *, int, int, const audio_params_t *);
146 static int    vidcaudio_trigger_output(void *, void *, void *, int,
147     void (*)(void *), void *, const audio_params_t *);
148 static int    vidcaudio_trigger_input(void *, void *, void *, int,
149     void (*)(void *), void *, const audio_params_t *);
150 static int    vidcaudio_halt_output(void *);
151 static int    vidcaudio_halt_input(void *);
152 static int    vidcaudio_getdev(void *, struct audio_device *);
153 static int    vidcaudio_set_port(void *, mixer_ctrl_t *);
154 static int    vidcaudio_get_port(void *, mixer_ctrl_t *);
155 static int    vidcaudio_query_devinfo(void *, mixer_devinfo_t *);
156 static int    vidcaudio_get_props(void *);
157 
158 static struct audio_device vidcaudio_device = {
159 	"ARM VIDC",
160 	"",
161 	"vidcaudio"
162 };
163 
164 static const struct audio_hw_if vidcaudio_hw_if = {
165 	NULL,			/* open */
166 	vidcaudio_close,
167 	NULL,
168 	vidcaudio_query_encoding,
169 	vidcaudio_set_params,
170 	vidcaudio_round_blocksize,
171 	NULL,
172 	NULL,
173 	NULL,
174 	NULL,
175 	NULL,
176 	vidcaudio_halt_output,
177 	vidcaudio_halt_input,
178 	NULL,
179 	vidcaudio_getdev,
180 	NULL,
181 	vidcaudio_set_port,
182 	vidcaudio_get_port,
183 	vidcaudio_query_devinfo,
184 	NULL,
185 	NULL,
186 	NULL,
187 	NULL,
188 	vidcaudio_get_props,
189 	vidcaudio_trigger_output,
190 	vidcaudio_trigger_input,
191 	NULL,
192 };
193 
194 static int
195 vidcaudio_probe(struct device *parent, struct cfdata *cf, void *aux)
196 {
197 	int id;
198 
199 	id = IOMD_ID;
200 
201 	/* So far I only know about this IOMD */
202 	switch (id) {
203 	case RPC600_IOMD_ID:
204 	case ARM7500_IOC_ID:
205 	case ARM7500FE_IOC_ID:
206 		return 1;
207 	default:
208 		aprint_error("vidcaudio: Unknown IOMD id=%04x", id);
209 		return 0;
210 	}
211 }
212 
213 
214 static void
215 vidcaudio_attach(struct device *parent, struct device *self, void *aux)
216 {
217 	struct vidcaudio_softc *sc;
218 	struct device *beepdev;
219 
220 	sc  = (void *)self;
221 	switch (IOMD_ID) {
222 #ifndef EB7500ATX
223 	case RPC600_IOMD_ID:
224 		sc->sc_is16bit = (cmos_read(0xc4) >> 5) & 1;
225 		sc->sc_dma_intr = IRQ_DMASCH0;
226 		break;
227 #endif
228 	case ARM7500_IOC_ID:
229 	case ARM7500FE_IOC_ID:
230 		sc->sc_is16bit = true;
231 		sc->sc_dma_intr = IRQ_SDMA;
232 		break;
233 	default:
234 		aprint_error(": strange IOMD\n");
235 		return;
236 	}
237 
238 	if (sc->sc_is16bit)
239 		aprint_normal(": 16-bit external DAC\n");
240 	else
241 		aprint_normal(": 8-bit internal DAC\n");
242 
243 	/* Install the irq handler for the DMA interrupt */
244 	sc->sc_ih.ih_func = vidcaudio_intr;
245 	sc->sc_ih.ih_arg = sc;
246 	sc->sc_ih.ih_level = IPL_AUDIO;
247 	sc->sc_ih.ih_name = self->dv_xname;
248 
249 	if (irq_claim(sc->sc_dma_intr, &sc->sc_ih) != 0) {
250 		aprint_error("%s: couldn't claim IRQ %d\n",
251 		    self->dv_xname, sc->sc_dma_intr);
252 		return;
253 	}
254 
255 	disable_irq(sc->sc_dma_intr);
256 
257 	beepdev = audio_attach_mi(&vidcaudio_hw_if, sc, self);
258 #if NPCKBD > 0
259 	pckbd_hookup_bell(audiobell, beepdev);
260 #endif
261 }
262 
263 static void
264 vidcaudio_close(void *addr)
265 {
266 	struct vidcaudio_softc *sc;
267 
268 	DPRINTF(("DEBUG: vidcaudio_close called\n"));
269 	sc = addr;
270 	/*
271 	 * We do this here rather than in vidcaudio_halt_output()
272 	 * because the latter can be called from interrupt context
273 	 * (audio_pint()->audio_clear()->vidcaudio_halt_output()).
274 	 */
275 	if (sc->sc_ppages != NULL) {
276 		free(sc->sc_ppages, M_DEVBUF);
277 		sc->sc_ppages = NULL;
278 	}
279 }
280 
281 /*
282  * Interface to the generic audio driver
283  */
284 
285 static int
286 vidcaudio_query_encoding(void *addr, struct audio_encoding *fp)
287 {
288 	struct vidcaudio_softc *sc;
289 
290 	sc = addr;
291 	switch (fp->index) {
292 	case 0:
293 		strcpy(fp->name, AudioEmulaw);
294 		fp->encoding = AUDIO_ENCODING_ULAW;
295 		fp->precision = 8;
296 		fp->flags = AUDIO_ENCODINGFLAG_EMULATED;
297 		break;
298 
299 	case 1:
300 		if (sc->sc_is16bit) {
301 			strcpy(fp->name, AudioEslinear_le);
302 			fp->encoding = AUDIO_ENCODING_SLINEAR_LE;
303 			fp->precision = 16;
304 			fp->flags = 0;
305 			break;
306 		}
307 		/* FALLTHROUGH */
308 	default:
309 		return EINVAL;
310 	}
311 	return 0;
312 }
313 
314 #define MULAW_TO_VIDC(m) (~((m) << 1 | (m) >> 7))
315 
316 static stream_filter_t *
317 mulaw_to_vidc(struct audio_softc *sc, const audio_params_t *from,
318 	      const audio_params_t *to)
319 {
320 
321 	return auconv_nocontext_filter_factory(mulaw_to_vidc_fetch_to);
322 }
323 
324 static int
325 mulaw_to_vidc_fetch_to(stream_fetcher_t *self, audio_stream_t *dst, int max_used)
326 {
327 	stream_filter_t *this;
328 	int m, err;
329 
330 	this = (stream_filter_t *)self;
331 	if ((err = this->prev->fetch_to(this->prev, this->src, max_used)))
332 		return err;
333 	m = dst->end - dst->start;
334 	m = min(m, max_used);
335 	FILTER_LOOP_PROLOGUE(this->src, 1, dst, 1, m) {
336 		*d = MULAW_TO_VIDC(*s);
337 	} FILTER_LOOP_EPILOGUE(this->src, dst);
338 	return 0;
339 }
340 
341 static stream_filter_t *
342 mulaw_to_vidc_stereo(struct audio_softc *sc, const audio_params_t *from,
343 		     const audio_params_t *to)
344 {
345 
346 	return auconv_nocontext_filter_factory(mulaw_to_vidc_stereo_fetch_to);
347 }
348 
349 static int
350 mulaw_to_vidc_stereo_fetch_to(stream_fetcher_t *self, audio_stream_t *dst,
351 			      int max_used)
352 {
353 	stream_filter_t *this;
354 	int m, err;
355 
356 	this = (stream_filter_t *)self;
357 	max_used = (max_used + 1) & ~1;
358 	if ((err = this->prev->fetch_to(this->prev, this->src, max_used / 2)))
359 		return err;
360 	m = (dst->end - dst->start) & ~1;
361 	m = min(m, max_used);
362 	FILTER_LOOP_PROLOGUE(this->src, 1, dst, 2, m) {
363 		d[0] = d[1] = MULAW_TO_VIDC(*s);
364 	} FILTER_LOOP_EPILOGUE(this->src, dst);
365 	return 0;
366 }
367 
368 static int
369 vidcaudio_set_params(void *addr, int setmode, int usemode,
370     audio_params_t *p, audio_params_t *r,
371     stream_filter_list_t *pfil, stream_filter_list_t *rfil)
372 {
373 	audio_params_t hw;
374 	struct vidcaudio_softc *sc;
375 	int sample_period, ch;
376 
377 	if ((setmode & AUMODE_PLAY) == 0)
378 		return 0;
379 
380 	sc = addr;
381 	if (sc->sc_is16bit) {
382 		/* ARM7500ish, 16-bit, two-channel */
383 		hw = *p;
384 		if (p->encoding == AUDIO_ENCODING_ULAW && p->precision == 8) {
385 			hw.encoding = AUDIO_ENCODING_SLINEAR_LE;
386 			hw.precision = hw.validbits = 16;
387 			pfil->append(pfil, mulaw_to_linear16, &hw);
388 		} else if (p->encoding != AUDIO_ENCODING_SLINEAR_LE ||
389 		    p->precision != 16)
390 			return EINVAL;
391 		sample_period = 705600 / 4 / p->sample_rate;
392 		if (sample_period < 3) sample_period = 3;
393 		vidcaudio_rate(sample_period - 2);
394 		vidcaudio_ctrl(SCR_SERIAL);
395 		hw.sample_rate = 705600 / 4 / sample_period;
396 		hw.channels = 2;
397 		pfil->prepend(pfil, aurateconv, &hw);
398 	} else {
399 		/* VIDC20ish, u-law, 8-channel */
400 		if (p->encoding != AUDIO_ENCODING_ULAW || p->precision != 8)
401 			return EINVAL;
402 		/*
403 		 * We always use two hardware channels, because using
404 		 * one at 8kHz gives a nasty whining sound from the
405 		 * speaker.  The aurateconv mechanism doesn't support
406 		 * ulaw, so we do the channel duplication ourselves,
407 		 * and don't try to do rate conversion.
408 		 */
409 		sample_period = 1000000 / 2 / p->sample_rate;
410 		if (sample_period < 3) sample_period = 3;
411 		p->sample_rate = 1000000 / 2 / sample_period;
412 		hw = *p;
413 		hw.encoding = AUDIO_ENCODING_NONE;
414 		hw.precision = 8;
415 		vidcaudio_rate(sample_period - 2);
416 		vidcaudio_ctrl(SCR_SDAC | SCR_CLKSEL);
417 		if (p->channels == 1) {
418 			pfil->append(pfil, mulaw_to_vidc_stereo, &hw);
419 			for (ch = 0; ch < 8; ch++)
420 				vidcaudio_stereo(ch, SIR_CENTRE);
421 		} else {
422 			pfil->append(pfil, mulaw_to_vidc, &hw);
423 			for (ch = 0; ch < 8; ch += 2)
424 				vidcaudio_stereo(ch, SIR_LEFT_100);
425 			for (ch = 1; ch < 8; ch += 2)
426 				vidcaudio_stereo(ch, SIR_RIGHT_100);
427 		}
428 	}
429 	return 0;
430 }
431 
432 static int
433 vidcaudio_round_blocksize(void *addr, int wantblk,
434 			  int mode, const audio_params_t *param)
435 {
436 	int blk;
437 
438 	/*
439 	 * Find the smallest power of two that's larger than the
440 	 * requested block size, but don't allow < 32 (DMA burst is 16
441 	 * bytes, and single bursts are tricky) or > PAGE_SIZE (DMA is
442 	 * confined to a page).
443 	 */
444 
445 	for (blk = 32; blk < PAGE_SIZE; blk <<= 1)
446 		if (blk >= wantblk)
447 			return blk;
448 	return blk;
449 }
450 
451 static int
452 vidcaudio_trigger_output(void *addr, void *start, void *end, int blksize,
453     void (*intr)(void *), void *arg, const audio_params_t *params)
454 {
455 	struct vidcaudio_softc *sc;
456 	size_t npages, i;
457 
458 	DPRINTF(("vidcaudio_trigger_output %p-%p/0x%x\n",
459 	    start, end, blksize));
460 
461 	sc = addr;
462 	KASSERT(blksize == vidcaudio_round_blocksize(addr, blksize, 0, NULL));
463 	KASSERT((vaddr_t)start % blksize == 0);
464 
465 	sc->sc_pblksize = blksize;
466 	sc->sc_pbufsize = (char *)end - (char *)start;
467 	npages = sc->sc_pbufsize >> PGSHIFT;
468 	if (sc->sc_ppages != NULL)
469 		free(sc->sc_ppages, M_DEVBUF);
470 	sc->sc_ppages = malloc(npages * sizeof(paddr_t), M_DEVBUF, M_WAITOK);
471 	if (sc->sc_ppages == NULL) return ENOMEM;
472 	for (i = 0; i < npages; i++)
473 		if (!pmap_extract(pmap_kernel(),
474 		    (vaddr_t)start + i * PAGE_SIZE, &sc->sc_ppages[i]))
475 			return EIO;
476 	sc->sc_poffset = 0;
477 	sc->sc_pintr = intr;
478 	sc->sc_parg = arg;
479 
480 	IOMD_WRITE_WORD(IOMD_SD0CR, IOMD_DMACR_CLEAR | IOMD_DMACR_QUADWORD);
481 	IOMD_WRITE_WORD(IOMD_SD0CR, IOMD_DMACR_ENABLE | IOMD_DMACR_QUADWORD);
482 
483 	/*
484 	 * Queue up the first two blocks, but don't tell audio code
485 	 * we're finished with them yet.
486 	 */
487 	sc->sc_pcountdown = 2;
488 
489 	enable_irq(sc->sc_dma_intr);
490 
491 	return 0;
492 }
493 
494 static int
495 vidcaudio_trigger_input(void *addr, void *start, void *end, int blksize,
496     void (*intr)(void *), void *arg, const audio_params_t *params)
497 {
498 
499 	return ENODEV;
500 }
501 
502 static int
503 vidcaudio_halt_output(void *addr)
504 {
505 	struct vidcaudio_softc *sc;
506 
507 	DPRINTF(("vidcaudio_halt_output\n"));
508 	sc = addr;
509 	disable_irq(sc->sc_dma_intr);
510 	IOMD_WRITE_WORD(IOMD_SD0CR, IOMD_DMACR_CLEAR | IOMD_DMACR_QUADWORD);
511 	return 0;
512 }
513 
514 static int
515 vidcaudio_halt_input(void *addr)
516 {
517 
518 	return ENODEV;
519 }
520 
521 static int
522 vidcaudio_getdev(void *addr, struct audio_device *retp)
523 {
524 
525 	*retp = vidcaudio_device;
526 	return 0;
527 }
528 
529 
530 static int
531 vidcaudio_set_port(void *addr, mixer_ctrl_t *cp)
532 {
533 
534 	return EINVAL;
535 }
536 
537 static int
538 vidcaudio_get_port(void *addr, mixer_ctrl_t *cp)
539 {
540 
541 	return EINVAL;
542 }
543 
544 static int
545 vidcaudio_query_devinfo(void *addr, mixer_devinfo_t *dip)
546 {
547 
548 	return ENXIO;
549 }
550 
551 static int
552 vidcaudio_get_props(void *addr)
553 {
554 
555 	return 0;
556 }
557 
558 static void
559 vidcaudio_rate(int rate)
560 {
561 
562 	WriteWord(vidc_base, VIDC_SFR | rate);
563 }
564 
565 static void
566 vidcaudio_ctrl(int ctrl)
567 {
568 
569 	WriteWord(vidc_base, VIDC_SCR | ctrl);
570 }
571 
572 static void
573 vidcaudio_stereo(int channel, int position)
574 {
575 
576 	channel = channel << 24 | VIDC_SIR0;
577 	WriteWord(vidc_base, channel | position);
578 }
579 
580 static int
581 vidcaudio_intr(void *arg)
582 {
583 	struct vidcaudio_softc *sc;
584 	int status;
585 	paddr_t pnext, pend;
586 
587 	sc = arg;
588 	status = IOMD_READ_BYTE(IOMD_SD0ST);
589 	DPRINTF(("I[%x]", status));
590 	if ((status & IOMD_DMAST_INT) == 0)
591 		return 0;
592 
593 	pnext = sc->sc_ppages[sc->sc_poffset >> PGSHIFT] |
594 	    (sc->sc_poffset & PGOFSET);
595 	pend = (pnext + sc->sc_pblksize - 16) & IOMD_DMAEND_OFFSET;
596 
597 	switch (status &
598 	    (IOMD_DMAST_OVERRUN | IOMD_DMAST_INT | IOMD_DMAST_BANKB)) {
599 
600 	case (IOMD_DMAST_INT | IOMD_DMAST_BANKA):
601 	case (IOMD_DMAST_OVERRUN | IOMD_DMAST_INT | IOMD_DMAST_BANKB):
602 		DPRINTF(("B<0x%08lx,0x%03lx>", pnext, pend));
603 		IOMD_WRITE_WORD(IOMD_SD0CURB, pnext);
604 		IOMD_WRITE_WORD(IOMD_SD0ENDB, pend);
605 		break;
606 
607 	case (IOMD_DMAST_INT | IOMD_DMAST_BANKB):
608 	case (IOMD_DMAST_OVERRUN | IOMD_DMAST_INT | IOMD_DMAST_BANKA):
609 		DPRINTF(("A<0x%08lx,0x%03lx>", pnext, pend));
610 		IOMD_WRITE_WORD(IOMD_SD0CURA, pnext);
611 		IOMD_WRITE_WORD(IOMD_SD0ENDA, pend);
612 		break;
613 	}
614 
615 	sc->sc_poffset += sc->sc_pblksize;
616 	if (sc->sc_poffset >= sc->sc_pbufsize)
617 		sc->sc_poffset = 0;
618 
619 	if (sc->sc_pcountdown > 0)
620 		sc->sc_pcountdown--;
621 	else
622 		(*sc->sc_pintr)(sc->sc_parg);
623 
624 	return 1;
625 }
626