/* $NetBSD: aucc.c,v 1.46.2.2 2020/03/21 15:31:50 martin Exp $ */ /* * Copyright (c) 1999 Bernardo Innocenti * All rights reserved. * * Copyright (c) 1997 Stephan Thesing * 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. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by Stephan Thesing. * 4. 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. */ /* TODO: * * - channel allocation is wrong for 14bit mono * - perhaps use a calibration table for better 14bit output * - set 31 kHz AGA video mode to allow 44.1 kHz even if grfcc is missing * in the kernel * - 14bit output requires maximum volume */ #include "aucc.h" #if NAUCC > 0 #include __KERNEL_RCSID(0, "$NetBSD: aucc.c,v 1.46.2.2 2020/03/21 15:31:50 martin Exp $"); #include #include #include #include #include #include #include #include #include #include /* for AUDIO_MIN_FREQUENCY */ #include #include #include #include #include "opt_lev6_defer.h" #ifdef LEV6_DEFER #define AUCC_MAXINT 3 #define AUCC_ALLINTF (INTF_AUD0|INTF_AUD1|INTF_AUD2) #else #define AUCC_MAXINT 4 #define AUCC_ALLINTF (INTF_AUD0|INTF_AUD1|INTF_AUD2|INTF_AUD3) #endif /* this unconditionally; we may use AUD3 as slave channel with LEV6_DEFER */ #define AUCC_ALLDMAF (DMAF_AUD0|DMAF_AUD1|DMAF_AUD2|DMAF_AUD3) #ifdef AUDIO_DEBUG /*extern printf(const char *,...);*/ int auccdebug = 1; #define DPRINTF(x) if (auccdebug) printf x #else #define DPRINTF(x) #endif /* clock frequency.. */ extern int eclockfreq; /* hw audio ch */ extern struct audio_channel channel[4]; /* * Software state. */ struct aucc_softc { aucc_data_t sc_channel[4]; /* per channel freq, ... */ u_int sc_encoding; /* encoding AUDIO_ENCODING_.*/ int sc_channels; /* # of channels used */ int sc_precision; /* 8 or 16 bits */ int sc_14bit; /* 14bit output enabled */ int sc_intrcnt; /* interrupt count */ int sc_channelmask; /* which channels are used ? */ void (*sc_decodefunc)(u_char **, u_char *, int); /* pointer to format conversion routine */ kmutex_t sc_lock; kmutex_t sc_intr_lock; }; /* interrupt interfaces */ void aucc_inthdl(int); /* forward declarations */ static int init_aucc(struct aucc_softc *); static u_int freqtoper(u_int); static u_int pertofreq(u_int); /* autoconfiguration driver */ void auccattach(device_t, device_t, void *); int auccmatch(device_t, cfdata_t, void *); CFATTACH_DECL_NEW(aucc, sizeof(struct aucc_softc), auccmatch, auccattach, NULL, NULL); struct audio_device aucc_device = { "Amiga-audio", "2.0", "aucc" }; struct aucc_softc *aucc = NULL; /* * Define our interface to the higher level audio driver. */ int aucc_open(void *, int); void aucc_close(void *); int aucc_set_out_sr(void *, u_int); int aucc_query_format(void *, audio_format_query_t *); int aucc_round_blocksize(void *, int, int, const audio_params_t *); int aucc_commit_settings(void *); int aucc_start_output(void *, void *, int, void (*)(void *), void *); int aucc_start_input(void *, void *, int, void (*)(void *), void *); int aucc_halt_output(void *); int aucc_halt_input(void *); int aucc_getdev(void *, struct audio_device *); int aucc_set_port(void *, mixer_ctrl_t *); int aucc_get_port(void *, mixer_ctrl_t *); int aucc_query_devinfo(void *, mixer_devinfo_t *); void aucc_encode(int, int, int, int, u_char *, u_short **); int aucc_set_format(void *, int, const audio_params_t *, const audio_params_t *, audio_filter_reg_t *, audio_filter_reg_t *); int aucc_get_props(void *); void aucc_get_locks(void *, kmutex_t **, kmutex_t **); static void aucc_decode_slinear16_1ch(u_char **, u_char *, int); static void aucc_decode_slinear16_2ch(u_char **, u_char *, int); static void aucc_decode_slinear16_3ch(u_char **, u_char *, int); static void aucc_decode_slinear16_4ch(u_char **, u_char *, int); const struct audio_hw_if sa_hw_if = { .open = aucc_open, .close = aucc_close, .query_format = aucc_query_format, .set_format = aucc_set_format, .round_blocksize = aucc_round_blocksize, .commit_settings = aucc_commit_settings, .start_output = aucc_start_output, .start_input = aucc_start_input, .halt_output = aucc_halt_output, .halt_input = aucc_halt_input, .getdev = aucc_getdev, .set_port = aucc_set_port, .get_port = aucc_get_port, .query_devinfo = aucc_query_devinfo, .get_props = aucc_get_props, .get_locks = aucc_get_locks, }; /* * XXX *1 How lower limit of frequency should be? same as audio(4)? * XXX *2 Should avoid a magic number at the upper limit of frequency. * XXX *3 In fact, there is a number in this range that have minimal errors. * It would be better if there is a mechanism which such frequency * is prioritized. * XXX *4 3/4ch modes use 8bits, 1/2ch modes use 14bits, * so I imagined that 1/2ch modes are better. */ #define AUCC_FORMAT(prio, ch, chmask) \ { \ .mode = AUMODE_PLAY, \ .priority = (prio), \ .encoding = AUDIO_ENCODING_SLINEAR_BE, \ .validbits = 16, \ .precision = 16, \ .channels = (ch), \ .channel_mask = (chmask), \ .frequency_type = 0, \ .frequency = { AUDIO_MIN_FREQUENCY, 28867 }, \ } static const struct audio_format aucc_formats[] = { AUCC_FORMAT(1, 1, AUFMT_MONAURAL), AUCC_FORMAT(1, 2, AUFMT_STEREO), AUCC_FORMAT(0, 3, AUFMT_UNKNOWN_POSITION), AUCC_FORMAT(0, 4, AUFMT_UNKNOWN_POSITION), }; #define AUCC_NFORMATS __arraycount(aucc_formats) /* autoconfig routines */ int auccmatch(device_t parent, cfdata_t cf, void *aux) { static int aucc_matched = 0; if (!matchname((char *)aux, "aucc") || #ifdef DRACO is_draco() || #endif aucc_matched) return 0; aucc_matched = 1; return 1; } /* * Audio chip found. */ void auccattach(device_t parent, device_t self, void *args) { struct aucc_softc *sc; int i; sc = device_private(self); printf("\n"); if ((i=init_aucc(sc))) { printf("audio: no chipmem\n"); return; } audio_attach_mi(&sa_hw_if, sc, self); } static int init_aucc(struct aucc_softc *sc) { int i, err; err = 0; /* init values per channel */ for (i = 0; i < 4; i++) { sc->sc_channel[i].nd_freq = 8000; sc->sc_channel[i].nd_per = freqtoper(8000); sc->sc_channel[i].nd_busy = 0; sc->sc_channel[i].nd_dma = alloc_chipmem(AUDIO_BUF_SIZE*2); if (sc->sc_channel[i].nd_dma == NULL) err = 1; sc->sc_channel[i].nd_dmalength = 0; sc->sc_channel[i].nd_volume = 64; sc->sc_channel[i].nd_intr = NULL; sc->sc_channel[i].nd_intrdata = NULL; sc->sc_channel[i].nd_doublebuf = 0; DPRINTF(("DMA buffer for channel %d is %p\n", i, sc->sc_channel[i].nd_dma)); } if (err) { for (i = 0; i < 4; i++) if (sc->sc_channel[i].nd_dma) free_chipmem(sc->sc_channel[i].nd_dma); } sc->sc_channels = 1; sc->sc_channelmask = 0xf; sc->sc_precision = 16; sc->sc_14bit = 1; sc->sc_encoding = AUDIO_ENCODING_SLINEAR_BE; sc->sc_decodefunc = aucc_decode_slinear16_2ch; /* clear interrupts and DMA: */ custom.intena = AUCC_ALLINTF; custom.dmacon = AUCC_ALLDMAF; mutex_init(&sc->sc_lock, MUTEX_DEFAULT, IPL_NONE); mutex_init(&sc->sc_intr_lock, MUTEX_DEFAULT, IPL_SCHED); return err; } int aucc_open(void *addr, int flags) { struct aucc_softc *sc; int i; sc = addr; DPRINTF(("sa_open: unit %p\n",sc)); for (i = 0; i < AUCC_MAXINT; i++) { sc->sc_channel[i].nd_intr = NULL; sc->sc_channel[i].nd_intrdata = NULL; } aucc = sc; sc->sc_channelmask = 0xf; DPRINTF(("saopen: ok -> sc=%p\n",sc)); return 0; } void aucc_close(void *addr) { DPRINTF(("sa_close: closed.\n")); } int aucc_set_out_sr(void *addr, u_int sr) { struct aucc_softc *sc; u_long per; int i; sc = addr; per = freqtoper(sr); if (per > 0xffff) return EINVAL; sr = pertofreq(per); for (i = 0; i < 4; i++) { sc->sc_channel[i].nd_freq = sr; sc->sc_channel[i].nd_per = per; } return 0; } int aucc_query_format(void *addr, audio_format_query_t *afp) { return audio_query_format(aucc_formats, AUCC_NFORMATS, afp); } int aucc_set_format(void *addr, int setmode, const audio_params_t *p, const audio_params_t *r, audio_filter_reg_t *pfil, audio_filter_reg_t *rfil) { struct aucc_softc *sc; sc = addr; KASSERT((setmode & AUMODE_RECORD) == 0); #ifdef AUCCDEBUG printf("%s(setmode 0x%x," "enc %u bits %u, chn %u, sr %u)\n", setmode, p->encoding, p->precision, p->channels, p->sample_rate); #endif switch (p->channels) { case 1: sc->sc_decodefunc = aucc_decode_slinear16_1ch; break; case 2: sc->sc_decodefunc = aucc_decode_slinear16_2ch; break; case 3: sc->sc_decodefunc = aucc_decode_slinear16_3ch; break; case 4: sc->sc_decodefunc = aucc_decode_slinear16_4ch; break; default: return EINVAL; } sc->sc_encoding = p->encoding; sc->sc_precision = p->precision; sc->sc_14bit = ((p->precision == 16) && (p->channels <= 2)); sc->sc_channels = sc->sc_14bit ? (p->channels * 2) : p->channels; return aucc_set_out_sr(addr, p->sample_rate); } int aucc_round_blocksize(void *addr, int blk, int mode, const audio_params_t *param) { if (blk > AUDIO_BUF_SIZE) blk = AUDIO_BUF_SIZE; blk = rounddown(blk, param->channels * param->precision / NBBY); return blk; } int aucc_commit_settings(void *addr) { struct aucc_softc *sc; int i; DPRINTF(("sa_commit.\n")); sc = addr; for (i = 0; i < 4; i++) { custom.aud[i].vol = sc->sc_channel[i].nd_volume; custom.aud[i].per = sc->sc_channel[i].nd_per; } DPRINTF(("commit done\n")); return 0; } static int masks[4] = {1,3,7,15}; /* masks for n first channels */ static int masks2[4] = {1,2,4,8}; int aucc_start_output(void *addr, void *p, int cc, void (*intr)(void *), void *arg) { struct aucc_softc *sc; int mask; int i, j, k, len; u_char *dmap[4]; sc = addr; mask = sc->sc_channelmask; dmap[0] = dmap[1] = dmap[2] = dmap[3] = NULL; DPRINTF(("sa_start_output: cc=%d %p (%p)\n", cc, intr, arg)); if (sc->sc_channels > 1) mask &= masks[sc->sc_channels - 1]; /* we use first sc_channels channels */ if (mask == 0) /* active and used channels are disjoint */ return EINVAL; for (i = 0; i < 4; i++) { /* channels available ? */ if ((masks2[i] & mask) && (sc->sc_channel[i].nd_busy)) return EBUSY; /* channel is busy */ if (channel[i].isaudio == -1) return EBUSY; /* system uses them */ } /* enable interrupt on 1st channel */ for (i = j = 0; i < AUCC_MAXINT; i++) { if (masks2[i] & mask) { DPRINTF(("first channel is %d\n",i)); j = i; sc->sc_channel[i].nd_intr = intr; sc->sc_channel[i].nd_intrdata = arg; break; } } DPRINTF(("dmap is %p %p %p %p, mask=0x%x\n", dmap[0], dmap[1], dmap[2], dmap[3], mask)); /* disable ints, DMA for channels, until all parameters set */ /* XXX dont disable DMA! custom.dmacon=mask;*/ custom.intreq = mask << INTB_AUD0; custom.intena = mask << INTB_AUD0; /* copy data to DMA buffer */ if (sc->sc_channels == 1) { dmap[0] = dmap[1] = dmap[2] = dmap[3] = (u_char *)sc->sc_channel[j].nd_dma; } else { for (k = 0; k < 4; k++) { if (masks2[k+j] & mask) dmap[k] = (u_char *)sc->sc_channel[k+j].nd_dma; } } sc->sc_channel[j].nd_doublebuf ^= 1; if (sc->sc_channel[j].nd_doublebuf) { dmap[0] += AUDIO_BUF_SIZE; dmap[1] += AUDIO_BUF_SIZE; dmap[2] += AUDIO_BUF_SIZE; dmap[3] += AUDIO_BUF_SIZE; } /* * compute output length in bytes per channel. * divide by two only for 16bit->8bit conversion. */ len = cc / sc->sc_channels; if (!sc->sc_14bit && (sc->sc_precision == 16)) len /= 2; /* call audio decoding routine */ sc->sc_decodefunc (dmap, (u_char *)p, len); /* DMA buffers: we use same buffer 4 all channels * write DMA location and length */ for (i = k = 0; i < 4; i++) { if (masks2[i] & mask) { DPRINTF(("turning channel %d on\n",i)); /* sc->sc_channel[i].nd_busy=1; */ channel[i].isaudio = 1; channel[i].play_count = 1; channel[i].handler = NULL; custom.aud[i].per = sc->sc_channel[i].nd_per; if (sc->sc_14bit && (i > 1)) custom.aud[i].vol = 1; else custom.aud[i].vol = sc->sc_channel[i].nd_volume; custom.aud[i].lc = PREP_DMA_MEM(dmap[k++]); custom.aud[i].len = len / 2; sc->sc_channel[i].nd_mask = mask; DPRINTF(("per is %d, vol is %d, len is %d\n",\ sc->sc_channel[i].nd_per, sc->sc_channel[i].nd_volume, len)); } } channel[j].handler = aucc_inthdl; /* enable ints */ custom.intena = INTF_SETCLR | INTF_INTEN | (masks2[j] << INTB_AUD0); DPRINTF(("enabled ints: 0x%x\n", (masks2[j] << INTB_AUD0))); /* enable DMA */ custom.dmacon = DMAF_SETCLR | DMAF_MASTER | mask; DPRINTF(("enabled DMA, mask=0x%x\n",mask)); return 0; } /* ARGSUSED */ int aucc_start_input(void *addr, void *p, int cc, void (*intr)(void *), void *arg) { return ENXIO; /* no input */ } int aucc_halt_output(void *addr) { struct aucc_softc *sc; int i; /* XXX only halt, if input is also halted ?? */ sc = addr; /* stop DMA, etc */ custom.intena = AUCC_ALLINTF; custom.dmacon = AUCC_ALLDMAF; /* mark every busy unit idle */ for (i = 0; i < 4; i++) { sc->sc_channel[i].nd_busy = sc->sc_channel[i].nd_mask = 0; channel[i].isaudio = 0; channel[i].play_count = 0; } return 0; } int aucc_halt_input(void *addr) { /* no input */ return ENXIO; } int aucc_getdev(void *addr, struct audio_device *retp) { *retp = aucc_device; return 0; } int aucc_set_port(void *addr, mixer_ctrl_t *cp) { struct aucc_softc *sc; int i,j; DPRINTF(("aucc_set_port: port=%d", cp->dev)); sc = addr; switch (cp->type) { case AUDIO_MIXER_SET: if (cp->dev != AUCC_CHANNELS) return EINVAL; i = cp->un.mask; if ((i < 1) || (i > 15)) return EINVAL; sc->sc_channelmask = i; break; case AUDIO_MIXER_VALUE: i = cp->un.value.num_channels; if ((i < 1) || (i > 4)) return EINVAL; #ifdef __XXXwhatsthat if (cp->dev != AUCC_VOLUME) return EINVAL; #endif /* set volume for channel 0..i-1 */ /* evil workaround for xanim bug, IMO */ if ((sc->sc_channels == 1) && (i == 2)) { sc->sc_channel[0].nd_volume = sc->sc_channel[3].nd_volume = cp->un.value.level[0] >> 2; sc->sc_channel[1].nd_volume = sc->sc_channel[2].nd_volume = cp->un.value.level[1] >> 2; } else if (i > 1) { for (j = 0; j < i; j++) sc->sc_channel[j].nd_volume = cp->un.value.level[j] >> 2; } else if (sc->sc_channels > 1) for (j = 0; j < sc->sc_channels; j++) sc->sc_channel[j].nd_volume = cp->un.value.level[0] >> 2; else for (j = 0; j < 4; j++) sc->sc_channel[j].nd_volume = cp->un.value.level[0] >> 2; break; default: return EINVAL; break; } return 0; } int aucc_get_port(void *addr, mixer_ctrl_t *cp) { struct aucc_softc *sc; int i,j; DPRINTF(("aucc_get_port: port=%d", cp->dev)); sc = addr; switch (cp->type) { case AUDIO_MIXER_SET: if (cp->dev != AUCC_CHANNELS) return EINVAL; cp->un.mask = sc->sc_channelmask; break; case AUDIO_MIXER_VALUE: i = cp->un.value.num_channels; if ((i < 1) || (i > 4)) return EINVAL; for (j = 0; j < i; j++) cp->un.value.level[j] = (sc->sc_channel[j].nd_volume << 2) + (sc->sc_channel[j].nd_volume >> 4); break; default: return EINVAL; } return 0; } int aucc_get_props(void *addr) { return AUDIO_PROP_PLAYBACK; } void aucc_get_locks(void *opaque, kmutex_t **intr, kmutex_t **thread) { struct aucc_softc *sc = opaque; *intr = &sc->sc_intr_lock; *thread = &sc->sc_lock; } int aucc_query_devinfo(void *addr, register mixer_devinfo_t *dip) { int i; switch(dip->index) { case AUCC_CHANNELS: dip->type = AUDIO_MIXER_SET; dip->mixer_class = AUCC_OUTPUT_CLASS; dip->prev = dip->next = AUDIO_MIXER_LAST; #define setname(a) strlcpy(dip->label.name, (a), sizeof(dip->label.name)) setname(AudioNspeaker); for (i = 0; i < 16; i++) { snprintf(dip->un.s.member[i].label.name, sizeof(dip->un.s.member[i].label.name), "channelmask%d", i); dip->un.s.member[i].mask = i; } dip->un.s.num_mem = 16; break; case AUCC_VOLUME: dip->type = AUDIO_MIXER_VALUE; dip->mixer_class = AUCC_OUTPUT_CLASS; dip->prev = dip->next = AUDIO_MIXER_LAST; setname(AudioNmaster); dip->un.v.num_channels = 4; strcpy(dip->un.v.units.name, AudioNvolume); break; case AUCC_OUTPUT_CLASS: dip->type = AUDIO_MIXER_CLASS; dip->mixer_class = AUCC_OUTPUT_CLASS; dip->next = dip->prev = AUDIO_MIXER_LAST; setname(AudioCoutputs); break; default: return ENXIO; } DPRINTF(("AUDIO_MIXER_DEVINFO: name=%s\n", dip->label.name)); return 0; } /* audio int handler */ void aucc_inthdl(int ch) { int i; int mask; mutex_spin_enter(&aucc->sc_intr_lock); mask = aucc->sc_channel[ch].nd_mask; /* * for all channels in this maskgroup: * disable DMA, int * mark idle */ DPRINTF(("inthandler called, channel %d, mask 0x%x\n", ch, mask)); custom.intreq = mask << INTB_AUD0; /* clear request */ /* * XXX: maybe we can leave ints and/or DMA on, * if another sample has to be played? */ custom.intena = mask << INTB_AUD0; /* * XXX custom.dmacon=mask; NO!!! */ for (i = 0; i < 4; i++) { if (masks2[i] && mask) { DPRINTF(("marking channel %d idle\n",i)); aucc->sc_channel[i].nd_busy = 0; aucc->sc_channel[i].nd_mask = 0; channel[i].isaudio = channel[i].play_count = 0; } } /* call handler */ if (aucc->sc_channel[ch].nd_intr) { DPRINTF(("calling %p\n",aucc->sc_channel[ch].nd_intr)); (*(aucc->sc_channel[ch].nd_intr)) (aucc->sc_channel[ch].nd_intrdata); } else DPRINTF(("zero int handler\n")); mutex_spin_exit(&aucc->sc_intr_lock); DPRINTF(("ints done\n")); } /* transform frequency to period, adjust bounds */ static u_int freqtoper(u_int freq) { u_int per; per = eclockfreq * 5 / freq; if (per < 124) per = 124; /* must have at least 124 ticks between samples */ return per; } /* transform period to frequency */ static u_int pertofreq(u_int per) { return eclockfreq * 5 / per; } /* 14bit output */ static void aucc_decode_slinear16_1ch(u_char **dmap, u_char *p, int i) { u_char *ch0; u_char *ch3; ch0 = dmap[0]; ch3 = dmap[1]; /* XXX should be 3 */ while (i--) { *ch0++ = *p++; *ch3++ = *p++ >> 2; } } /* 14bit stereo output */ static void aucc_decode_slinear16_2ch(u_char **dmap, u_char *p, int i) { u_char *ch0; u_char *ch1; u_char *ch2; u_char *ch3; ch0 = dmap[0]; ch1 = dmap[1]; ch2 = dmap[2]; ch3 = dmap[3]; while (i--) { *ch0++ = *p++; *ch3++ = *p++ >> 2; *ch1++ = *p++; *ch2++ = *p++ >> 2; } } static void aucc_decode_slinear16_3ch(u_char **dmap, u_char *p, int i) { u_char *ch0; u_char *ch1; u_char *ch2; ch0 = dmap[0]; ch1 = dmap[1]; ch2 = dmap[2]; while (i--) { *ch0++ = *p++; p++; *ch1++ = *p++; p++; *ch2++ = *p++; p++; } } static void aucc_decode_slinear16_4ch(u_char **dmap, u_char *p, int i) { u_char *ch0; u_char *ch1; u_char *ch2; u_char *ch3; ch0 = dmap[0]; ch1 = dmap[1]; ch2 = dmap[2]; ch3 = dmap[3]; while (i--) { *ch0++ = *p++; p++; *ch1++ = *p++; p++; *ch2++ = *p++; p++; *ch3++ = *p++; p++; } } #endif /* NAUCC > 0 */