/* $OpenBSD: nec86hw.c,v 1.5 2017/03/11 12:15:35 ratchov Exp $ */ /* $NecBSD: nec86hw.c,v 1.13 1998/03/14 07:04:54 kmatsuda Exp $ */ /* $NetBSD$ */ /* * [NetBSD for NEC PC-98 series] * Copyright (c) 1996, 1997, 1998 * NetBSD/pc98 porting staff. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ /* * nec86hw.c * * NEC PC-9801-86 SoundBoard PCM driver for NetBSD/pc98. * Written by NAGAO Tadaaki, Feb 10, 1996. * * Modified by N. Honda, Mar 7, 1998 */ /* * TODO: * - Add PC-9801-73 support. * - Fake the mixer device with electric volumes. */ #include #include #include #include #include #include #include #include #include #include #include #if 0 #include #endif #include #include #include #ifdef AUDIO_DEBUG extern void Dprintf(const char *, ...); #define DPRINTF(x) if (nec86hwdebug) printf x #define DPRINTF2(l, x) if (nec86hwdebug >= l) printf x int nec86hwdebug = 3; #else /* !AUDIO_DEBUG */ #define DPRINTF(x) #define DPRINTF2(l, x) #endif /* AUDIO_DEBUG */ #ifndef VOLUME_DELAY /* * XXX - Delaytime in microsecond after an access * to the volume I/O port. */ #define VOLUME_DELAY 10 #endif /* !VOLUME_DELAY */ /* * Sampling rates supported by the hardware. */ static int nec86hw_rate_table[NEC86HW_NRATE_TYPE][NEC86_NRATE] = { /* NEC PC-9801-86 or its full compatibles */ { 44100, 33075, 22050, 16538, 11025, 8269, 5513, 4134 }, /* Some earlier versions of Q-Vision's WaveMaster */ { 44100, 33075, 22050, 16000, 11025, 8000, 5513, 4000 }, }; static struct audio_params nec86hw_audio_default = {44100, AUDIO_ENCODING_SLINEAR_LE, 16, 2, 1, 2}; int nec86hw_set_output_block(struct nec86hw_softc *, int); int nec86hw_set_input_block(struct nec86hw_softc *, int); /* * Function tables. */ static struct nec86hw_functable_entry nec86hw_functable[] = { /* precision, channels, output function, input function (without resampling), output function, input function (with resampling) */ { 8, 1, nec86fifo_output_mono_8_direct, nec86fifo_input_mono_8_direct }, { 16, 1, nec86fifo_output_mono_16_direct, nec86fifo_input_mono_16_direct }, { 8, 2, nec86fifo_output_stereo_8_direct, nec86fifo_input_stereo_8_direct }, { 16, 2, nec86fifo_output_stereo_16_direct, nec86fifo_input_stereo_16_direct }, }; #define NFUNCTABLEENTRY (sizeof(nec86hw_functable) / sizeof(nec86hw_functable[0])) /* * Attach hardware to driver, attach hardware driver to audio * pseudo-device driver. */ void nec86hw_attach(struct nec86hw_softc *sc) { bus_space_tag_t iot = sc->sc_iot; bus_space_handle_t ioh = sc->sc_ioh; u_int8_t data; /* Set default encoding. */ sc->func_fifo_output = nec86fifo_output_mono_8_direct; sc->func_fifo_input = nec86fifo_input_mono_8_direct; (void) nec86hw_set_params(sc, AUMODE_RECORD, 0, &nec86hw_audio_default, &nec86hw_audio_default); (void) nec86hw_set_params(sc, AUMODE_PLAY, 0, &nec86hw_audio_default, &nec86hw_audio_default); /* Set default ports. */ (void) nec86hw_set_in_port(sc, NEC86HW_INPUT_MIXER); (void) nec86hw_set_out_port(sc, NEC86HW_OUTPUT_MIXER); /* Set default gains. */ nec86hw_set_volume(sc, NEC86_VOLUME_PORT_OPNAD, NEC86_MAXVOL); nec86hw_set_volume(sc, NEC86_VOLUME_PORT_OPNAI, NEC86_MAXVOL); nec86hw_set_volume(sc, NEC86_VOLUME_PORT_LINED, NEC86_MAXVOL); nec86hw_set_volume(sc, NEC86_VOLUME_PORT_LINEI, NEC86_MAXVOL); nec86hw_set_volume(sc, NEC86_VOLUME_PORT_PCMD, NEC86_MAXVOL); /* Internal Speaker ON */ nec86hw_speaker_ctl(sc, SPKR_ON); /* Set miscellanous stuffs. */ data = bus_space_read_1(iot, ioh, NEC86_CTRL); data &= NEC86_CTRL_MASK_PAN | NEC86_CTRL_MASK_PORT; data |= NEC86_CTRL_PAN_L | NEC86_CTRL_PAN_R; data |= NEC86_CTRL_PORT_STD; bus_space_write_1(iot, ioh, NEC86_CTRL, data); } /* * Various routines to interface to higher level audio driver. */ int nec86hw_open(void *arg, int flags) { struct nec86hw_softc *sc = arg; DPRINTF(("nec86hw_open: sc=%p\n", sc)); if (sc->sc_open != 0 || nec86hw_reset(sc) != 0) return ENXIO; sc->sc_open = 1; sc->sc_intr = NULL; sc->sc_arg = NULL; sc->conv_acc = 0; sc->conv_last0 = 0; sc->conv_last0_l = 0; sc->conv_last0_r = 0; sc->conv_last1 = 0; sc->conv_last1_l = 0; sc->conv_last1_r = 0; /* * Leave most things including sampling format and rate as they were. * (See audio_open() in audio.c) */ DPRINTF(("nec86hw_open: opened\n")); return 0; } void nec86hw_close(void *addr) { register struct nec86hw_softc *sc = (struct nec86hw_softc *) addr; DPRINTF(("nec86hw_close: sc=%p\n", sc)); sc->sc_open = 0; sc->sc_intr = NULL; (void) nec86hw_reset(sc); DPRINTF(("nec86hw_close: closed\n")); } int nec86hw_set_params(void *addr, int mode, int usemode, struct audio_params *p, struct audio_params *r) { register struct nec86hw_softc *sc = (struct nec86hw_softc *) addr; int rate_type = NEC86HW_RATE_TYPE(sc->sc_cfgflags); if ((p->channels != 1) && (p->channels != 2)) return EINVAL; if (p->precision == 8) p->encoding = AUDIO_ENCODING_ULINEAR_LE; else { p->precision = 16; p->encoding = AUDIO_ENCODING_SLINEAR_LE; } sc->channels = p->channels; sc->precision = p->precision; sc->encoding = p->encoding; sc->hw_orate_bits = nec86hw_rate_bits(sc, p->sample_rate); sc->sc_orate = p->sample_rate = sc->hw_orate = nec86hw_rate_table[rate_type][sc->hw_orate_bits]; sc->hw_irate_bits = nec86hw_rate_bits(sc, r->sample_rate); sc->sc_irate = r->sample_rate = sc->hw_irate = nec86hw_rate_table[rate_type][sc->hw_irate_bits]; return 0; } int nec86hw_round_blocksize(void *addr, int blk) { u_int base = NEC86_INTRBLK_UNIT; if (blk < NEC86_INTRBLK_UNIT * 2) return NEC86_INTRBLK_UNIT * 2; for ( ; base <= blk; base *= 2) ; return base / 2; } int nec86hw_set_out_port(void *addr, int port) { register struct nec86hw_softc *sc = (struct nec86hw_softc *) addr; DPRINTF(("nec86hw_set_out_port:\n")); if (port != NEC86HW_OUTPUT_MIXER) return EINVAL; sc->out_port = port; return 0; } int nec86hw_get_out_port(void *addr) { register struct nec86hw_softc *sc = (struct nec86hw_softc *) addr; DPRINTF(("nec86hw_get_out_port:\n")); return sc->out_port; } int nec86hw_set_in_port(void *addr, int port) { register struct nec86hw_softc *sc = (struct nec86hw_softc *) addr; DPRINTF(("nec86hw_set_in_port:\n")); if (port != NEC86HW_INPUT_MIXER) return EINVAL; sc->in_port = port; return 0; } int nec86hw_get_in_port(void *addr) { register struct nec86hw_softc *sc = (struct nec86hw_softc *) addr; DPRINTF(("nec86hw_get_in_port:\n")); return sc->in_port; } int nec86hw_commit_settings(void *addr) { register struct nec86hw_softc *sc = (struct nec86hw_softc *) addr; int i; /* * Determine which function should be used to write to/read from * the FIFO ring buffer. */ for (i = 0; i < NFUNCTABLEENTRY; i++) { if ((nec86hw_functable[i].precision == sc->precision) && (nec86hw_functable[i].channels == sc->channels)) break; } if (i >= NFUNCTABLEENTRY) { /* ??? - This should never happen. */ return EINVAL; } sc->func_fifo_output = nec86hw_functable[i].func_fifo_output_direct; sc->func_fifo_input = nec86hw_functable[i].func_fifo_input_direct; return 0; } int nec86hw_setfd(void *addr, int flag) { DPRINTF(("nec86hw_setfd:\n")); /* Can't do full-duplex */ return ENOTTY; } int nec86hw_mixer_set_port(void *addr, mixer_ctrl_t *cp) { DPRINTF(("nec86hw_mixer_set_port:\n")); /* not yet implemented */ return ENXIO; } int nec86hw_mixer_get_port(void *addr, mixer_ctrl_t *cp) { DPRINTF(("nec86hw_mixer_get_port:\n")); /* not yet implemented */ return ENXIO; } int nec86hw_mixer_query_devinfo(void *addr, mixer_devinfo_t *dip) { DPRINTF(("nec86hw_mixer_query_devinfo:\n")); /* not yet implemented */ return ENXIO; } int nec86hw_set_output_block(struct nec86hw_softc *sc, int cc) { int bpf, hw_blocksize, watermark; bpf = (sc->channels * sc->precision) / NBBY; /* bytes per frame */ sc->pdma_count = cc / bpf; /* Size of the block */ hw_blocksize = sc->pdma_count * (sc->precision / NBBY * 2); /* How many chunks the block should be divided into. */ sc->pdma_nchunk = ((hw_blocksize * (WATERMARK_MAX_RATIO + WATERMARK_RATIO_OUT)) + (NEC86_BUFFSIZE * WATERMARK_MAX_RATIO - 1)) / (NEC86_BUFFSIZE * WATERMARK_MAX_RATIO); /* Calculate the watermark. */ watermark = (hw_blocksize * WATERMARK_RATIO_OUT) / (sc->pdma_nchunk * WATERMARK_MAX_RATIO); sc->pdma_watermark = nec86hw_round_watermark(watermark); /* * If the formula (*1) does not hold, watermark may be less * than the minimum watermark (NEC86_INTRBLK_UNIT) and then * nec86hw_round_watermark() returns that minimum value. * In this case, the ring buffer on the hardware will potentially * overflow. * To avoid such a case, here calculate pdma_nchunk again. * * (*1) NEC86_BUFFSIZE / NEC86_INTRBLK_UNIT * >= WATERMARK_MAX_RATIO / WATERMARK_RATIO_OUT + 1 */ if (hw_blocksize + sc->pdma_watermark > NEC86_BUFFSIZE) { sc->pdma_nchunk = (hw_blocksize + (NEC86_BUFFSIZE - sc->pdma_watermark - 1)) / (NEC86_BUFFSIZE - sc->pdma_watermark); } DPRINTF2(2, ("nec86hw_pdma_output: cc=%d count=%d hw_blocksize=%d " "watermark=%d nchunk=%d ptr=%p\n", cc, sc->pdma_count, hw_blocksize, sc->pdma_watermark, sc->pdma_nchunk, sc->pdma_ptr)); return 0; } int nec86hw_set_input_block(struct nec86hw_softc *sc, int cc) { int bpf, hw_blocksize, watermark, maxwatermark; bpf = (sc->channels * sc->precision) / NBBY; /* bytes per frame */ sc->pdma_count = cc / bpf; /* Maximum watermark. */ watermark = (NEC86_BUFFSIZE * WATERMARK_RATIO_IN) / WATERMARK_MAX_RATIO; maxwatermark = nec86hw_round_watermark(watermark); /* Size of the block */ hw_blocksize = sc->pdma_count * (sc->precision / NBBY * 2); /* How many chunks the block should be divided into. */ sc->pdma_nchunk = (hw_blocksize + (maxwatermark - 1)) / maxwatermark; /* Calculate the watermark. */ watermark = (hw_blocksize / sc->pdma_nchunk) + (NEC86_INTRBLK_UNIT - 1); sc->pdma_watermark = nec86hw_round_watermark(watermark); DPRINTF2(2, ("nec86hw_pdma_input: cc=%d count=%d hw_blocksize=%d " "watermark=%d nchunk=%d ptr=%p\n", cc, sc->pdma_count, hw_blocksize, sc->pdma_watermark, sc->pdma_nchunk, sc->pdma_ptr)); return 0; } int nec86hw_pdma_init_output(void *addr, void *buf, int cc) { struct nec86hw_softc *sc = (struct nec86hw_softc *) addr; nec86hw_halt_pdma(addr); nec86hw_reset_fifo(sc); nec86hw_set_mode_playing(sc); nec86hw_set_rate_real(sc, sc->hw_orate_bits); nec86hw_set_precision_real(sc, sc->precision); nec86hw_set_output_block(sc, cc); nec86hw_reset_fifo(sc); nec86hw_enable_fifointr(sc); nec86hw_set_watermark(sc, sc->pdma_watermark); nec86hw_disable_fifointr(sc); return 0; } int nec86hw_pdma_init_input(void *addr, void *buf, int cc) { struct nec86hw_softc *sc = (struct nec86hw_softc *) addr; nec86hw_halt_pdma(addr); nec86hw_reset_fifo(sc); nec86hw_set_mode_recording(sc); nec86hw_set_rate_real(sc, sc->hw_irate_bits); nec86hw_set_precision_real(sc, sc->precision); nec86hw_set_input_block(sc, cc); nec86hw_reset_fifo(sc); nec86hw_enable_fifointr(sc); nec86hw_set_watermark(sc, sc->pdma_watermark); nec86hw_disable_fifointr(sc); return 0; } int nec86hw_pdma_output(void *addr, void *p, int cc, void (*intr)(void *), void *arg) { struct nec86hw_softc *sc = (struct nec86hw_softc *) addr; int bpf; bpf = (sc->channels * sc->precision) / NBBY; /* bytes per frame */ if ((cc % bpf) != 0) { DPRINTF(("nec86hw_pdma_output: odd bytes\n")); return EIO; } sc->sc_intr = intr; sc->sc_arg = arg; /* * We divide the data block into some relatively small chunks if the * block is so large that the ring buffer on the hardware would * overflow. * In this case, we send the first chunk now and the rest in the * interrupt handler. */ /* Set up for pseudo-DMA. */ sc->pdma_ptr = (u_char *) p; sc->pdma_padded = 0; sc->pdma_mode = PDMA_MODE_OUTPUT; if (sc->pdma_count != cc / bpf) nec86hw_set_output_block(sc, cc); if (!sc->intr_busy) { nec86hw_set_precision_real(sc, sc->precision); nec86hw_set_rate_real(sc, sc->hw_orate_bits); nec86hw_set_mode_playing(sc); nec86hw_reset_fifo(sc); } /* * Send the first chunk. The rest will be sent in the interrupt * handler nec86hw_intr(), if any. */ nec86hw_disable_fifointr(sc); nec86hw_output_chunk(sc); nec86hw_enable_fifointr(sc); if (!sc->intr_busy) { /* Now start playing. */ nec86hw_start_fifo(sc); sc->intr_busy = 1; } nec86hw_set_watermark(sc, sc->pdma_watermark); return 0; } int nec86hw_pdma_input(void *addr, void *p, int cc, void (*intr)(void *), void *arg) { struct nec86hw_softc *sc = (struct nec86hw_softc *) addr; int bpf; bpf = (sc->channels * sc->precision) / NBBY; /* bytes per frame */ if ((cc % bpf) != 0) { DPRINTF(("nec86hw_pdma_input: odd bytes\n")); return EIO; } sc->sc_intr = intr; sc->sc_arg = arg; /* Set up for pseudo-DMA. */ sc->pdma_ptr = (u_int8_t *) p; sc->pdma_padded = 0; /* Never padded in recording, though. */ sc->pdma_mode = PDMA_MODE_INPUT; if (sc->pdma_count != cc / bpf) nec86hw_set_input_block(sc, cc); if (!sc->intr_busy) { nec86hw_set_precision_real(sc, sc->precision); nec86hw_set_rate_real(sc, sc->hw_irate_bits); nec86hw_set_mode_recording(sc); nec86hw_reset_fifo(sc); /* Now start recording. */ nec86hw_enable_fifointr(sc); nec86hw_start_fifo(sc); sc->intr_busy = 1; } nec86hw_set_watermark(sc, sc->pdma_watermark); return 0; } int nec86hw_halt_pdma(void *addr) { register struct nec86hw_softc *sc = (struct nec86hw_softc *) addr; DPRINTF(("nec86hw_halt_pdma: sc=%p\n", sc)); nec86hw_stop_fifo(sc); nec86hw_disable_fifointr(sc); sc->intr_busy = 0; return 0; } int nec86hw_cont_pdma(void *addr) { register struct nec86hw_softc *sc = (struct nec86hw_softc *) addr; DPRINTF(("nec86hw_cont_pdma: sc=%p\n", sc)); nec86hw_enable_fifointr(sc); nec86hw_start_fifo(sc); sc->intr_busy = 1; return 0; } int nec86hw_speaker_ctl(void *addr, int onoff) { register struct nec86hw_softc *sc = (struct nec86hw_softc *) addr; bus_space_tag_t iot = sc->sc_iot; bus_space_handle_t ioh = sc->sc_ioh; DPRINTF(("nec86hw_speaker_ctl:\n")); switch (onoff) { case SPKR_ON: bus_space_write_1(iot, ioh, NEC86_VOLUME, 0x0d1); delay(VOLUME_DELAY); break; case SPKR_OFF: bus_space_write_1(iot, ioh, NEC86_VOLUME, 0x0d0); delay(VOLUME_DELAY); break; default: return EINVAL; } return 0; } u_int8_t nec86hw_rate_bits(struct nec86hw_softc *sc, u_long sr) { int i; u_long rval, hr, min; int rate_type = NEC86HW_RATE_TYPE(sc->sc_cfgflags); /* Look for the minimum hardware rate equal to or more than sr. */ min = 0; rval = 0; for (i = 0; i < NEC86_NRATE; i++) { hr = nec86hw_rate_table[rate_type][i]; if ((hr >= sr) && ((min == 0) || (min > hr))) { min = hr; rval = (u_int8_t) i; } } return rval; } int nec86hw_round_watermark(int wm) { wm = (wm / NEC86_INTRBLK_UNIT) * NEC86_INTRBLK_UNIT; if (wm < NEC86_INTRBLK_UNIT) wm = NEC86_INTRBLK_UNIT; return wm; } /* * Lower-level routines. */ int nec86hw_reset(struct nec86hw_softc *sc) { nec86hw_stop_fifo(sc); nec86hw_disable_fifointr(sc); nec86hw_clear_intrflg(sc); nec86hw_reset_fifo(sc); sc->intr_busy = 0; sc->pdma_mode = PDMA_MODE_NONE; if (nec86hw_seeif_intrflg(sc)) return -1; /* The hardware vanished? */ return 0; } /* * Set the mode for playing/recording. */ void nec86hw_set_mode_playing(struct nec86hw_softc *sc) { bus_space_tag_t iot = sc->sc_iot; bus_space_handle_t ioh = sc->sc_ioh; u_int8_t data; data = bus_space_read_1(iot, ioh, NEC86_FIFOCTL); data &= ~NEC86_FIFOCTL_RECMODE; bus_space_write_1(iot, ioh, NEC86_FIFOCTL, data); } void nec86hw_set_mode_recording(struct nec86hw_softc *sc) { bus_space_tag_t iot = sc->sc_iot; bus_space_handle_t ioh = sc->sc_ioh; u_int8_t data; data = bus_space_read_1(iot, ioh, NEC86_FIFOCTL); data |= NEC86_FIFOCTL_RECMODE; bus_space_write_1(iot, ioh, NEC86_FIFOCTL, data); } /* * Set the electric volumes. */ void nec86hw_set_volume(struct nec86hw_softc *sc, int port, u_int8_t vol) { bus_space_tag_t iot = sc->sc_iot; bus_space_handle_t ioh = sc->sc_ioh; bus_space_write_1(iot, ioh, NEC86_VOLUME, NEC86_VOL_TO_BITS(port, vol)); delay(VOLUME_DELAY); } /* * Control the FIFO ring buffer on the board. */ void nec86hw_start_fifo(struct nec86hw_softc *sc) { bus_space_tag_t iot = sc->sc_iot; bus_space_handle_t ioh = sc->sc_ioh; u_int8_t data; /* Start playing/recording. */ data = bus_space_read_1(iot, ioh, NEC86_FIFOCTL); data |= NEC86_FIFOCTL_RUN; bus_space_write_1(iot, ioh, NEC86_FIFOCTL, data); } void nec86hw_stop_fifo(struct nec86hw_softc *sc) { bus_space_tag_t iot = sc->sc_iot; bus_space_handle_t ioh = sc->sc_ioh; u_int8_t data; /* Stop playing/recording. */ data = bus_space_read_1(iot, ioh, NEC86_FIFOCTL); data &= ~NEC86_FIFOCTL_RUN; bus_space_write_1(iot, ioh, NEC86_FIFOCTL, data); } void nec86hw_enable_fifointr(struct nec86hw_softc *sc) { bus_space_tag_t iot = sc->sc_iot; bus_space_handle_t ioh = sc->sc_ioh; u_int8_t data; data = bus_space_read_1(iot, ioh, NEC86_FIFOCTL); data |= NEC86_FIFOCTL_ENBLINTR; bus_space_write_1(iot, ioh, NEC86_FIFOCTL, data); } void nec86hw_disable_fifointr(struct nec86hw_softc *sc) { bus_space_tag_t iot = sc->sc_iot; bus_space_handle_t ioh = sc->sc_ioh; u_int8_t data; data = bus_space_read_1(iot, ioh, NEC86_FIFOCTL); data &= ~NEC86_FIFOCTL_ENBLINTR; bus_space_write_1(iot, ioh, NEC86_FIFOCTL, data); } int nec86hw_seeif_intrflg(struct nec86hw_softc *sc) { bus_space_tag_t iot = sc->sc_iot; bus_space_handle_t ioh = sc->sc_ioh; u_int8_t data; data = bus_space_read_1(iot, ioh, NEC86_FIFOCTL); return (data & NEC86_FIFOCTL_INTRFLG); } void nec86hw_clear_intrflg(struct nec86hw_softc *sc) { bus_space_tag_t iot = sc->sc_iot; bus_space_handle_t ioh = sc->sc_ioh; u_int8_t data; data = bus_space_read_1(iot, ioh, NEC86_FIFOCTL); data &= ~NEC86_FIFOCTL_INTRFLG; bus_space_write_1(iot, ioh, NEC86_FIFOCTL, data); data |= NEC86_FIFOCTL_INTRFLG; bus_space_write_1(iot, ioh, NEC86_FIFOCTL, data); } void nec86hw_reset_fifo(struct nec86hw_softc *sc) { bus_space_tag_t iot = sc->sc_iot; bus_space_handle_t ioh = sc->sc_ioh; u_int8_t data; data = bus_space_read_1(iot, ioh, NEC86_FIFOCTL); data |= NEC86_FIFOCTL_INIT; bus_space_write_1(iot, ioh, NEC86_FIFOCTL, data); data &= ~NEC86_FIFOCTL_INIT; bus_space_write_1(iot, ioh, NEC86_FIFOCTL, data); } void nec86hw_set_watermark(struct nec86hw_softc *sc, int wm) { bus_space_tag_t iot = sc->sc_iot; bus_space_handle_t ioh = sc->sc_ioh; /* * Must be called after nec86hw_start_fifo() and * nec86hw_enable_fifointr() are both called. */ #ifdef DIAGNOSTIC if ((wm < NEC86_INTRBLK_UNIT) || (wm > NEC86_BUFFSIZE) || ((wm % NEC86_INTRBLK_UNIT) != 0)) printf("nec86hw_set_watermark: invalid watermark %d\n", wm); #endif /* DIAGNOSTIC */ /* * The interrupt occurs when the number of bytes in the FIFO ring * buffer exceeds this watarmark. */ bus_space_write_1(iot, ioh, NEC86_FIFOINTRBLK, (wm / NEC86_INTRBLK_UNIT) - 1); } void nec86hw_set_precision_real(struct nec86hw_softc *sc, u_int prec) { bus_space_tag_t iot = sc->sc_iot; bus_space_handle_t ioh = sc->sc_ioh; u_int8_t data; data = bus_space_read_1(iot, ioh, NEC86_CTRL); data &= ~NEC86_CTRL_8BITS; if (prec == 8) data |= NEC86_CTRL_8BITS; bus_space_write_1(iot, ioh, NEC86_CTRL, data); } void nec86hw_set_rate_real(struct nec86hw_softc *sc, u_int8_t bits) { bus_space_tag_t iot = sc->sc_iot; bus_space_handle_t ioh = sc->sc_ioh; u_int8_t data; data = bus_space_read_1(iot, ioh, NEC86_FIFOCTL); data &= ~NEC86_FIFOCTL_MASK_RATE; data |= bits & NEC86_FIFOCTL_MASK_RATE; bus_space_write_1(iot, ioh, NEC86_FIFOCTL, data); } /* * Write data to the FIFO ring buffer on the board. */ void nec86hw_output_chunk(struct nec86hw_softc *sc) { int cc, nbyte; if (sc->pdma_nchunk > 0) { /* Update chunksize and then send the chunk to the board. */ /* chunksize in frames */ cc = sc->pdma_count / sc->pdma_nchunk; nbyte = (*sc->func_fifo_output)(sc, cc); DPRINTF2(3, ("nec86hw_output_chunk: sc->pdma_count=%d " "sc->pdma_nchunk=%d cc=%d nbyte=%d ptr=%p\n", sc->pdma_count, sc->pdma_nchunk, cc, nbyte, sc->pdma_ptr)); sc->pdma_nchunk--; sc->pdma_count -= cc; sc->pdma_ptr += nbyte; } else { /* ??? - This should never happen. */ nbyte = 0; DPRINTF(("nec86hw_output_chunk: sc->pdma_nchunk=%d\n", sc->pdma_nchunk)); } /* * If size of the sent chunk is not enough, then pad out the buffer * with zero's. */ if (nbyte <= sc->pdma_watermark) { nec86fifo_padding(sc, sc->pdma_watermark); sc->pdma_padded = 1; DPRINTF(("nec86hw_output_chunk: write padding zero's\n")); } } /* * Routines to write data directly. */ int nec86fifo_output_mono_8_direct(struct nec86hw_softc *sc, int cc) { bus_space_tag_t iot = sc->sc_iot; bus_space_handle_t ioh = sc->sc_ioh; u_int8_t *p = sc->pdma_ptr; int i; register u_int8_t d; for (i = 0; i < cc; i++) { d = *p++; d ^= 0x80; /* unsigned -> signed */ /* Fake monoral playing by duplicating a sample. */ bus_space_write_1(iot, ioh, NEC86_FIFODATA, d); bus_space_write_1(iot, ioh, NEC86_FIFODATA, d); } return cc * 2; } int nec86fifo_output_mono_16_direct(struct nec86hw_softc *sc, int cc) { bus_space_tag_t iot = sc->sc_iot; bus_space_handle_t ioh = sc->sc_ioh; u_int8_t *p = sc->pdma_ptr; int i; for (i = 0; i < cc; i++) { /* Fake monoral playing by duplicating a sample. */ #if BYTE_ORDER == BIG_ENDIAN bus_space_write_1(iot, ioh, NEC86_FIFODATA, *p); bus_space_write_1(iot, ioh, NEC86_FIFODATA, *(p + 1)); bus_space_write_1(iot, ioh, NEC86_FIFODATA, *p); bus_space_write_1(iot, ioh, NEC86_FIFODATA, *(p + 1)); #else /* little endian -> big endian */ bus_space_write_1(iot, ioh, NEC86_FIFODATA, *(p + 1)); bus_space_write_1(iot, ioh, NEC86_FIFODATA, *p); bus_space_write_1(iot, ioh, NEC86_FIFODATA, *(p + 1)); bus_space_write_1(iot, ioh, NEC86_FIFODATA, *p); #endif p += 2; } return cc * 4; } int nec86fifo_output_stereo_8_direct(struct nec86hw_softc *sc, int cc) { bus_space_tag_t iot = sc->sc_iot; bus_space_handle_t ioh = sc->sc_ioh; u_int8_t *p = sc->pdma_ptr; int i; for (i = 0; i < cc; i++) { /* unsigned -> signed (L) */ bus_space_write_1(iot, ioh, NEC86_FIFODATA, (*p++) ^ 0x80); /* unsigned -> signed (R) */ bus_space_write_1(iot, ioh, NEC86_FIFODATA, (*p++) ^ 0x80); } return cc * 2; } int nec86fifo_output_stereo_16_direct(struct nec86hw_softc *sc, int cc) { bus_space_tag_t iot = sc->sc_iot; bus_space_handle_t ioh = sc->sc_ioh; u_int8_t *p = sc->pdma_ptr; int i; for (i = 0; i < cc; i++) { #if BYTE_ORDER == BIG_ENDIAN /* (L) */ bus_space_write_1(iot, ioh, NEC86_FIFODATA, *p); bus_space_write_1(iot, ioh, NEC86_FIFODATA, *(p + 1)); p += 2; /* (R) */ bus_space_write_1(iot, ioh, NEC86_FIFODATA, *p); bus_space_write_1(iot, ioh, NEC86_FIFODATA, *(p + 1)); p += 2; #else /* little endian -> big endian */ /* (L) */ bus_space_write_1(iot, ioh, NEC86_FIFODATA, *(p + 1)); bus_space_write_1(iot, ioh, NEC86_FIFODATA, *p); p += 2; /* (R) */ bus_space_write_1(iot, ioh, NEC86_FIFODATA, *(p + 1)); bus_space_write_1(iot, ioh, NEC86_FIFODATA, *p); p += 2; #endif } return cc * 4; } /* * Routines to write data with resampling. (linear interpolation) */ int nec86fifo_output_mono_8_resamp(struct nec86hw_softc *sc, int cc) { bus_space_tag_t iot = sc->sc_iot; bus_space_handle_t ioh = sc->sc_ioh; u_int8_t *p = sc->pdma_ptr; int i; register int rval; register u_int8_t d; register u_int8_t d0, d1; register u_long acc, orate, hw_orate; rval = 0; orate = sc->sc_orate; hw_orate = sc->hw_orate; acc = sc->conv_acc; d0 = (u_int8_t) sc->conv_last0; d1 = (u_int8_t) sc->conv_last1; for (i = 0; i < cc; i++) { d0 = d1; d1 = *p++; while (acc <= hw_orate) { /* Linear interpolation. */ d = ((d0 * (hw_orate - acc)) + (d1 * acc)) / hw_orate; /* unsigned -> signed */ d ^= 0x80; /* Fake monoral playing by duplicating a sample. */ bus_space_write_1(iot, ioh, NEC86_FIFODATA, d); bus_space_write_1(iot, ioh, NEC86_FIFODATA, d); acc += orate; rval += 2; } acc -= hw_orate; } sc->conv_acc = acc; sc->conv_last0 = (u_short) d0; sc->conv_last1 = (u_short) d1; return rval; } int nec86fifo_output_mono_16_resamp(struct nec86hw_softc *sc, int cc) { bus_space_tag_t iot = sc->sc_iot; bus_space_handle_t ioh = sc->sc_ioh; u_int8_t *p = sc->pdma_ptr; int i; register int rval; register u_short d, d0, d1; register u_long acc, orate, hw_orate; rval = 0; orate = sc->sc_orate; hw_orate = sc->hw_orate; acc = sc->conv_acc; d0 = sc->conv_last0; d1 = sc->conv_last1; for (i = 0; i < cc; i++) { d0 = d1; /* little endian signed -> unsigned */ d1 = (*p | (*(p + 1) << 8)) ^ 0x8000; p += 2; while (acc <= hw_orate) { /* Linear interpolation. */ d = ((d0 * (hw_orate - acc)) + (d1 * acc)) / hw_orate; /* unsigned -> signed */ d ^= 0x8000; /* Fake monoral playing by duplicating a sample. */ bus_space_write_1(iot, ioh, NEC86_FIFODATA, (d >> 8) & 0xff); /* -> big endian */ bus_space_write_1(iot, ioh, NEC86_FIFODATA, d & 0xff); bus_space_write_1(iot, ioh, NEC86_FIFODATA, (d >> 8) & 0xff); bus_space_write_1(iot, ioh, NEC86_FIFODATA, d & 0xff); acc += orate; rval += 4; } acc -= hw_orate; } sc->conv_acc = acc; sc->conv_last0 = d0; sc->conv_last1 = d1; return rval; } /* * Read data from the FIFO ring buffer on the board. */ void nec86hw_input_chunk(struct nec86hw_softc *sc) { int cc, bpf; bpf = (sc->channels * sc->precision) / NBBY; if (sc->pdma_nchunk > 0) { /* Update chunksize and then receive the chunk from the board. */ /* chunksize in frames */ cc = sc->pdma_count / sc->pdma_nchunk; (*sc->func_fifo_input)(sc, cc); DPRINTF2(3, ("nec86hw_input_chunk: sc->pdma_count=%d " "sc->pdma_nchunk=%d cc=%d ptr=%p\n", sc->pdma_count, sc->pdma_nchunk, cc, sc->pdma_ptr)); sc->pdma_nchunk--; sc->pdma_count -= cc; sc->pdma_ptr += (cc * bpf); } else { /* ??? - This should never happen. */ cc = 0; DPRINTF(("nec86hw_input_chunk: ??? sc->pdma_nchunk=%d ???", sc->pdma_nchunk)); } } /* * Routines to read data directly. */ void nec86fifo_input_mono_8_direct(struct nec86hw_softc *sc, int cc) { bus_space_tag_t iot = sc->sc_iot; bus_space_handle_t ioh = sc->sc_ioh; u_int8_t *p = sc->pdma_ptr; int i; register u_int8_t d_l, d_r; for (i = 0; i < cc; i++) { /* signed -> unsigned (L) */ d_l = bus_space_read_1(iot, ioh, NEC86_FIFODATA) ^ 0x80; /* signed -> unsigned (R) */ d_r = bus_space_read_1(iot, ioh, NEC86_FIFODATA) ^ 0x80; /* Fake monoral recording by taking arithmetical mean. */ *p++ = (d_l + d_r) / 2; } } void nec86fifo_input_mono_16_direct(struct nec86hw_softc *sc, int cc) { bus_space_tag_t iot = sc->sc_iot; bus_space_handle_t ioh = sc->sc_ioh; u_int8_t *p = sc->pdma_ptr; int i; register u_short d, d_l, d_r; for (i = 0; i < cc; i++) { /* big endian signed -> unsigned (L) */ d_l = (bus_space_read_1(iot, ioh, NEC86_FIFODATA) ^ 0x80) << 8; d_l |= bus_space_read_1(iot, ioh, NEC86_FIFODATA); /* big endian signed -> unsigned (R) */ d_r = (bus_space_read_1(iot, ioh, NEC86_FIFODATA) ^ 0x80) << 8; d_r |= bus_space_read_1(iot, ioh, NEC86_FIFODATA); /* Fake monoral recording by taking arithmetical mean. */ d = (d_l + d_r) / 2; #if BYTE_ORDER == BIG_ENDIAN /* -> big endian signed */ *p++ = ((d >> 8) & 0xff) ^ 0x80; *p++ = d & 0xff; #else /* -> little endian signed */ *p++ = d & 0xff; *p++ = ((d >> 8) & 0xff) ^ 0x80; #endif } } void nec86fifo_input_stereo_8_direct(struct nec86hw_softc *sc, int cc) { bus_space_tag_t iot = sc->sc_iot; bus_space_handle_t ioh = sc->sc_ioh; u_int8_t *p = sc->pdma_ptr; int i; for (i = 0; i < cc; i++) { /* signed -> unsigned (L) */ *p++ = bus_space_read_1(iot, ioh, NEC86_FIFODATA) ^ 0x80; /* signed -> unsigned (R) */ *p++ = bus_space_read_1(iot, ioh, NEC86_FIFODATA) ^ 0x80; } } void nec86fifo_input_stereo_16_direct(struct nec86hw_softc *sc, int cc) { bus_space_tag_t iot = sc->sc_iot; bus_space_handle_t ioh = sc->sc_ioh; u_int8_t *p = sc->pdma_ptr; int i; for (i = 0; i < cc; i++) { #if BYTE_ORDER == BIG_ENDIAN *p = bus_space_read_1(iot, ioh, NEC86_FIFODATA);/* (L) */ *(p + 1) = bus_space_read_1(iot, ioh, NEC86_FIFODATA); p += 2; *p = bus_space_read_1(iot, ioh, NEC86_FIFODATA);/* (R) */ *(p + 1) = bus_space_read_1(iot, ioh, NEC86_FIFODATA); p += 2; #else /* big endian -> little endian */ *(p + 1) = bus_space_read_1(iot, ioh, NEC86_FIFODATA);/* (L) */ *p = bus_space_read_1(iot, ioh, NEC86_FIFODATA); p += 2; *(p + 1) = bus_space_read_1(iot, ioh, NEC86_FIFODATA);/* (R) */ *p = bus_space_read_1(iot, ioh, NEC86_FIFODATA); p += 2; #endif } } /* * Write padding zero's to the FIFO ring buffer on the board. */ void nec86fifo_padding(struct nec86hw_softc *sc, int cc) { bus_space_tag_t iot = sc->sc_iot; bus_space_handle_t ioh = sc->sc_ioh; register int i; DPRINTF2(2, ("nec86fifo_padding: %d\n", cc)); for (i = 0; i < cc; i++) bus_space_write_1(iot, ioh, NEC86_FIFODATA, 0); } /* * Interrupt handler. */ int nec86hw_intr(void *arg) { struct nec86hw_softc *sc = (struct nec86hw_softc *) arg; if (!nec86hw_seeif_intrflg(sc)) { /* Seems to be an FM sound interrupt. */ DPRINTF(("nec86hw_intr: ??? FM sound interrupt ???\n")); return 0; } mtx_enter(&audio_lock); nec86hw_clear_intrflg(sc); switch(sc->pdma_mode) { case PDMA_MODE_OUTPUT: if (sc->pdma_padded) { /* Clear the padding zero's. */ nec86hw_reset_fifo(sc); sc->pdma_padded = 0; DPRINTF(("nec86hw_intr: clear padding zero's\n")); } if (sc->pdma_count > 0) { /* Send the next chunk. */ nec86hw_disable_fifointr(sc); nec86hw_output_chunk(sc); nec86hw_enable_fifointr(sc); } else (*sc->sc_intr)(sc->sc_arg); break; case PDMA_MODE_INPUT: if (sc->pdma_count > 0) { /* Receive the next chunk. */ nec86hw_disable_fifointr(sc); nec86hw_input_chunk(sc); nec86hw_enable_fifointr(sc); } if (sc->pdma_count <= 0) (*sc->sc_intr)(sc->sc_arg); break; default: /* This should never happen. */ nec86hw_stop_fifo(sc); nec86hw_disable_fifointr(sc); sc->intr_busy = 0; DPRINTF(("nec86hw_intr: ??? unexpected interrupt ???\n")); mtx_leave(&audio_lock); return 0; } mtx_leave(&audio_lock); return 1; } int nec86_get_props(void *addr) { return 0; }