/* $NetBSD: arcofi.c,v 1.3 2019/06/08 08:02:38 isaki Exp $ */ /* $OpenBSD: arcofi.c,v 1.6 2013/05/15 08:29:24 ratchov Exp $ */ /* * Copyright (c) 2011 Miodrag Vallat. * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * Driver for the HP ``Audio1'' device, which is a FIFO layer around a * Siemens PSB 2160 ``ARCOFI'' phone quality audio chip. * * It is known to exist in two flavours: on-board the HP9000/425e as a DIO * device, an on-board the HP9000/{705,710,745,747} as a GIO device. * * The FIFO logic buffers up to 128 bytes. When using 8 bit samples and * the logic set to interrupt every half FIFO, the device will interrupt * 125 times per second. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ioconf.h" #if 0 #define ARCOFI_DEBUG #endif /* * Siemens PSB2160 registers */ /* CMDR */ #define CMDR_AD 0x80 /* SP1/PS2 address convention */ #define CMDR_READ 0x40 #define CMDR_WRITE 0x00 #define CMDR_PU 0x20 /* Power Up */ #define CMDR_RCS 0x10 /* Receive and transmit in CH B2 */ #define CMDR_MASK 0x0f /* command length data */ #define SOP_0 0x00 /* 5 CR4 CR3 CR2 CR1 */ #define COP_1 0x01 /* 5 t1_hi t1_lo f1_hi f1_lo */ #define COP_2 0x02 /* 3 gr1 gr2 */ #define COP_3 0x03 /* 3 t2_hi t2_lo f2_hi f2_lo */ #define SOP_4 0x04 /* 2 CR1 */ #define SOP_5 0x05 /* 2 CR2 */ #define SOP_6 0x06 /* 2 CR3 */ #define SOP_7 0x07 /* 2 CR4 */ #define COP_8 0x08 /* 3 dtmf_hi dtmf_lo */ #define COP_9 0x09 /* 5 gz a3 a2 a1 */ #define COP_A 0x0a /* 9 fx1 to fx8 */ #define COP_B 0x0b /* 3 gx1 gx2 */ #define COP_C 0x0c /* 9 fr1 to fr 8 */ #define COP_D 0x0d /* 5 fr9 fr10 fx9 fx10 */ #define COP_E 0x0e /* 5 t3_hi t3_lo f3_hi f3_lo */ /* CR1 */ #define CR1_GR 0x80 /* GR gain loaded from CRAM vs 0dB */ #define CR1_GZ 0x40 /* Z gain loaded from CRAM vs -18dB */ #define CR1_FX 0x20 /* X filter loaded from CRAM vs 0dB flat */ #define CR1_FR 0x10 /* R filter loaded from CRAM vs 0dB flat */ #define CR1_GX 0x08 /* GX gain loaded from CRAM vs 0dB */ #define CR1_T_MASK 0x07 /* test mode */ #define CR1_DLP 0x07 /* digital loopback via PCM registers */ #define CR1_DLM 0x06 /* D/A output looped back to A/D input */ #define CR1_DLS 0x05 /* digital loopback via converter registers */ #define CR1_IDR 0x04 /* data RAM initialization */ #define CR1_BYP 0x03 /* bypass analog frontend */ #define CR1_ALM 0x02 /* analog loopback via MUX */ #define CR1_ALS 0x01 /* analog loopback via converter registers */ /* CR2 */ #define CR2_SD 0x80 /* SD pin set to input vs output */ #define CR2_SC 0x40 /* SC pin set to input vs output */ #define CR2_SB 0x20 /* SB pin set to input vs output */ #define CR2_SA 0x10 /* SA pin set to input vs output */ #define CR2_ELS 0x08 /* non-input S pins tristate SIP vs sending 0 */ #define CR2_AM 0x04 /* only one device on the SLD bus */ #define CR2_TR 0x02 /* three party conferencing */ #define CR2_EFC 0x01 /* enable feature control */ /* CR3 */ #define CR3_MIC_G_MASK 0xe0 /* MIC input analog gain */ #define CR3_MIC_X_INPUT 0xe0 /* MIC disabled, X input 15.1 dB */ #define CR3_MIC_G_17 0xc0 /* 17 dB */ #define CR3_MIC_G_22 0xa0 /* 22 dB */ #define CR3_MIC_G_28 0x80 /* 28 dB */ #define CR3_MIC_G_34 0x60 /* 34 dB */ #define CR3_MIC_G_40 0x40 /* 40 dB */ #define CR3_MIC_G_46 0x20 /* 46 dB */ #define CR3_MIC_G_52 0x00 /* 52 dB (reset default) */ #define CR3_AFEC_MASK 0x1c #define CR3_AFEC_MUTE 0x18 /* mute: Hout */ #define CR3_AFEC_HFS 0x14 /* hands free: FHM, LS out */ #define CR3_AFEC_LH3 0x10 /* loud hearing 3: MIC, H out, LS out */ #define CR3_AFEC_LH2 0x0c /* loud hearing 2: MIC, LS out */ #define CR3_AFEC_LH1 0x08 /* loud hearing 1: LS out */ #define CR3_AFEC_RDY 0x04 /* ready: MIC, H out */ #define CR3_AFEC_POR 0x00 /* power on reset: all off */ #define CR3_OPMODE_MASK 0x03 #define CR3_OPMODE_LINEAR 0x02 /* linear (16 bit) */ #define CR3_OPMODE_MIXED 0x01 /* mixed */ #define CR3_OPMODE_NORMAL 0x00 /* normal (A/u-Law) */ /* CR4 */ #define CR4_DHF 0x80 /* TX digital high frequency enable */ #define CR4_DTMF 0x40 /* DTMF generator enable */ #define CR4_TG 0x20 /* tone ring enable */ #define CR4_BT 0x10 /* beat tone generator enable */ #define CR4_TM 0x08 /* incoming voice enable */ #define CR4_BM 0x04 /* beat mode (3 tone vs 2 tone) */ #define CR4_PM 0x02 /* tone sent to piezo vs loudspeaker */ #define CR4_ULAW 0x01 /* u-Law vs A-Law */ /* * Glue logic registers * Note the register values here are symbolic, as actual addresses * depend upon the particular bus the device is connected to. */ #define ARCOFI_ID 0 /* id (r) and reset (w) register */ #define ARCOFI_CSR 1 /* status and control register */ #define CSR_INTR_ENABLE 0x80 #define CSR_INTR_REQUEST 0x40 /* unacknowledged interrupt */ /* 0x20 and 0x10 used in DIO flavours, to provide IPL */ #define CSR_WIDTH_16 0x08 /* 16-bit samples */ #define CSR_CTRL_FIFO_ENABLE 0x04 /* connect FIFO to CMDR */ #define CSR_DATA_FIFO_ENABLE 0x01 /* connect FIFO to DU/DD */ #define ARCOFI_FIFO_IR 2 /* FIFO interrupt register */ #define FIFO_IR_ENABLE(ev) ((ev) << 4) #define FIFO_IR_EVENT(ev) (ev) #define FIFO_IR_OUT_EMPTY 0x08 #define FIFO_IR_CTRL_EMPTY 0x04 #define FIFO_IR_OUT_HALF_EMPTY 0x02 #define FIFO_IR_IN_HALF_EMPTY 0x01 #define ARCOFI_FIFO_SR 3 /* FIFO status register (ro) */ #define FIFO_SR_CTRL_FULL 0x20 #define FIFO_SR_CTRL_EMPTY 0x10 #define FIFO_SR_OUT_FULL 0x08 #define FIFO_SR_OUT_EMPTY 0x04 #define FIFO_SR_IN_FULL 0x02 #define FIFO_SR_IN_EMPTY 0x01 #define ARCOFI_FIFO_DATA 4 /* data FIFO port */ #define ARCOFI_FIFO_CTRL 5 /* control FIFO port (wo) */ #define ARCOFI_FIFO_SIZE 128 #ifdef hp300 /* XXX */ #define arcofi_read(sc, r) \ bus_space_read_1((sc)->sc_iot, (sc)->sc_ioh, (r)) #define arcofi_write(sc, r, v) \ bus_space_write_1((sc)->sc_iot, (sc)->sc_ioh, (r), (v)) #else #define arcofi_read(sc, r) \ bus_space_read_1((sc)->sc_iot, (sc)->sc_ioh, (sc)->sc_reg[(r)]) #define arcofi_write(sc, r, v) \ bus_space_write_1((sc)->sc_iot, (sc)->sc_ioh, (sc)->sc_reg[(r)], (v)) #endif static int arcofi_cmd(struct arcofi_softc *, uint8_t, const uint8_t *); static int arcofi_cr3_to_portmask(uint, int); static int arcofi_gain_to_mi(uint); static uint arcofi_mi_to_gain(int); static uint arcofi_portmask_to_cr3(int); static int arcofi_recv_data(struct arcofi_softc *); static int arcofi_xmit_data(struct arcofi_softc *); static int arcofi_open(void *, int); static int arcofi_query_format(void *, audio_format_query_t *); static int arcofi_set_format(void *, int, const audio_params_t *, const audio_params_t *, audio_filter_reg_t *, audio_filter_reg_t *); static int arcofi_round_blocksize(void *, int, int, const audio_params_t *); static int arcofi_commit_settings(void *); static int arcofi_start_output(void *, void *, int, void (*)(void *), void *); static int arcofi_start_input(void *, void *, int, void (*)(void *), void *); static int arcofi_halt_output(void *); static int arcofi_halt_input(void *); static int arcofi_getdev(void *, struct audio_device *); static int arcofi_set_port(void *, mixer_ctrl_t *); static int arcofi_get_port(void *, mixer_ctrl_t *); static int arcofi_query_devinfo(void *, mixer_devinfo_t *); static int arcofi_get_props(void *); static void arcofi_get_locks(void *, kmutex_t **, kmutex_t **); static const struct audio_hw_if arcofi_hw_if = { .open = arcofi_open, .query_format = arcofi_query_format, .set_format = arcofi_set_format, .round_blocksize = arcofi_round_blocksize, .commit_settings = arcofi_commit_settings, .start_output = arcofi_start_output, .start_input = arcofi_start_input, .halt_output = arcofi_halt_output, .halt_input = arcofi_halt_input, .speaker_ctl = NULL, .getdev = arcofi_getdev, .set_port = arcofi_set_port, .get_port = arcofi_get_port, .query_devinfo = arcofi_query_devinfo, .allocm = NULL, .freem = NULL, .round_buffersize = NULL, .get_props = arcofi_get_props, .trigger_output = NULL, .trigger_input = NULL, .dev_ioctl = NULL, .get_locks = arcofi_get_locks, }; #define ARCOFI_FORMAT(prio, enc, prec) \ { \ .mode = AUMODE_PLAY | AUMODE_RECORD, \ .priority = (prio), \ .encoding = (enc), \ .validbits = (prec), \ .precision = (prec), \ .channels = 1, \ .channel_mask = AUFMT_MONAURAL, \ .frequency_type = 1, \ .frequency = { 8000 }, \ } static const struct audio_format arcofi_formats[] = { /* * 8-bit u-Law and A-Law are native. */ ARCOFI_FORMAT(1, AUDIO_ENCODING_ULAW, 8), ARCOFI_FORMAT(0, AUDIO_ENCODING_ALAW, 8), /* * 16-bit slinear big-endian is native. * But it's hard to use due to hardware restrictions. */ ARCOFI_FORMAT(0, AUDIO_ENCODING_SLINEAR_BE, 16), }; #define ARCOFI_NFORMATS __arraycount(arcofi_formats) /* mixer items */ #define ARCOFI_PORT_AUDIO_IN_VOLUME 0 /* line in volume (GR) */ #define ARCOFI_PORT_AUDIO_OUT_VOLUME 1 /* line out volume (GX) */ #define ARCOFI_PORT_AUDIO_SPKR_VOLUME 2 /* speaker volume (GX) */ #define ARCOFI_PORT_AUDIO_IN_MUTE 3 /* line in mute (MIC) */ #define ARCOFI_PORT_AUDIO_OUT_MUTE 4 /* line out mute (H out) */ #define ARCOFI_PORT_AUDIO_SPKR_MUTE 5 /* line in mute (LS out) */ /* mixer classes */ #define ARCOFI_CLASS_INPUT 6 #define ARCOFI_CLASS_OUTPUT 7 /* * Gain programming formulae are a complete mystery to me, and of course * no two chips are compatible - not even the PSB 2163 and PSB 2165 * later ARCOFI chips, from the same manufacturer as the PSB 2160! * * Of course, the PSB 2160 datasheet does not give any set of values. * The following table is taken from the HP-UX audio driver (audio_shared.o * private_audio_gain_tab). */ #define NEGATIVE_GAINS 60 #define POSITIVE_GAINS 14 static const uint16_t arcofi_gains[1 + NEGATIVE_GAINS + 1 + POSITIVE_GAINS] = { /* minus infinity */ 0x0988, 0xf8b8, 0xf8b8, 0xf8b8, 0xf8b8, 0x099f, 0x099f, 0x099f, 0x099f, 0x09af, 0x09af, 0x09af, 0x09cf, 0x09cf, 0x09cf, 0xf8a9, 0xf83a, 0xf83a, 0xf82b, 0xf82d, 0xf8a3, 0xf8b2, 0xf8a1, 0xe8aa, 0xe84b, 0xe89e, 0xe8d3, 0xe891, 0xe8b1, 0xd8aa, 0xd8cb, 0xd8a6, 0xd8b3, 0xd842, 0xd8b1, 0xc8aa, 0xc8bb, 0xc888, 0xc853, 0xc852, 0xc8b1, 0xb8aa, 0xb8ab, 0xb896, 0xb892, 0xb842, 0xb8b1, 0xa8aa, 0xa8bb, 0x199f, 0x195b, 0x29c1, 0x2923, 0x29aa, 0x392b, 0xf998, 0xb988, 0x1aac, 0x3aa1, 0xbaa1, 0xbb88, /* 0 */ 0x8888, 0xd388, 0x5288, 0xb1a1, 0x31a1, 0x1192, 0x11d0, 0x30c0, 0x2050, 0x1021, 0x1020, 0x1000, 0x0001, 0x0010, 0x0000 }; static int arcofi_open(void *v, int flags) { struct arcofi_softc *sc __diagused = (struct arcofi_softc *)v; KASSERT(sc->sc_mode == 0); return 0; } static int arcofi_query_format(void *v, audio_format_query_t *afp) { return audio_query_format(arcofi_formats, ARCOFI_NFORMATS, afp); } static int arcofi_set_format(void *handle, int setmode, const audio_params_t *play, const audio_params_t *rec, audio_filter_reg_t *pfil, audio_filter_reg_t *rfil) { struct arcofi_softc *sc; sc = handle; if ((setmode & AUMODE_PLAY)) { switch (play->encoding) { case AUDIO_ENCODING_ULAW: pfil->codec = audio_internal_to_mulaw; break; case AUDIO_ENCODING_ALAW: pfil->codec = audio_internal_to_alaw; break; } } if ((setmode & AUMODE_RECORD)) { switch (rec->encoding) { case AUDIO_ENCODING_ULAW: rfil->codec = audio_mulaw_to_internal; break; case AUDIO_ENCODING_ALAW: rfil->codec = audio_alaw_to_internal; break; } } /* *play and *rec are identical because !AUDIO_PROP_INDEPENDENT */ if (play->precision == 8) { if (play->encoding == AUDIO_ENCODING_ULAW) sc->sc_shadow.cr4 |= CR4_ULAW; else sc->sc_shadow.cr4 &= ~CR4_ULAW; sc->sc_shadow.cr3 = (sc->sc_shadow.cr3 & ~CR3_OPMODE_MASK) | CR3_OPMODE_NORMAL; } else { sc->sc_shadow.cr3 = (sc->sc_shadow.cr3 & ~CR3_OPMODE_MASK) | CR3_OPMODE_LINEAR; } return 0; } static int arcofi_round_blocksize(void *handle, int block, int mode, const audio_params_t *param) { /* * Round the size up to a multiple of half the FIFO, to favour * smooth interrupt operation. */ return roundup(block, ARCOFI_FIFO_SIZE / 2); } static int arcofi_commit_settings(void *v) { struct arcofi_softc *sc = (struct arcofi_softc *)v; int rc; uint8_t cmd[2], csr, ocsr; #ifdef ARCOFI_DEBUG printf("%s: %s, gr %04x gx %04x cr3 %02x cr4 %02x mute %d\n", device_xname(sc->sc_dev), __func__, arcofi_gains[sc->sc_shadow.gr_idx], arcofi_gains[sc->sc_shadow.gx_idx], sc->sc_shadow.cr3, sc->sc_shadow.cr4, sc->sc_shadow.output_mute); #endif if (memcmp(&sc->sc_active, &sc->sc_shadow, sizeof(sc->sc_active)) == 0) return 0; mutex_spin_enter(&sc->sc_intr_lock); if (sc->sc_active.gr_idx != sc->sc_shadow.gr_idx) { cmd[0] = arcofi_gains[sc->sc_shadow.gr_idx] >> 8; cmd[1] = arcofi_gains[sc->sc_shadow.gr_idx]; if ((rc = arcofi_cmd(sc, COP_2, cmd)) != 0) goto error; sc->sc_active.gr_idx = sc->sc_shadow.gr_idx; } if (sc->sc_active.gx_idx != sc->sc_shadow.gx_idx || sc->sc_active.output_mute != sc->sc_shadow.output_mute) { if (sc->sc_shadow.output_mute) { cmd[0] = arcofi_gains[0] >> 8; cmd[1] = arcofi_gains[0]; } else { cmd[0] = arcofi_gains[sc->sc_shadow.gx_idx] >> 8; cmd[1] = arcofi_gains[sc->sc_shadow.gx_idx]; } if ((rc = arcofi_cmd(sc, COP_B, cmd)) != 0) goto error; sc->sc_active.gx_idx = sc->sc_shadow.gx_idx; sc->sc_active.output_mute = sc->sc_shadow.output_mute; } if (sc->sc_active.cr3 != sc->sc_shadow.cr3) { cmd[0] = sc->sc_shadow.cr3; if ((rc = arcofi_cmd(sc, SOP_6, cmd)) != 0) goto error; sc->sc_active.cr3 = sc->sc_shadow.cr3; ocsr = arcofi_read(sc, ARCOFI_CSR); if ((sc->sc_active.cr3 & CR3_OPMODE_MASK) != CR3_OPMODE_NORMAL) csr = ocsr | CSR_WIDTH_16; else csr = ocsr & ~CSR_WIDTH_16; if (csr != ocsr) arcofi_write(sc, ARCOFI_CSR, csr); } if (sc->sc_active.cr4 != sc->sc_shadow.cr4) { cmd[0] = sc->sc_shadow.cr4; if ((rc = arcofi_cmd(sc, SOP_7, cmd)) != 0) goto error; sc->sc_active.cr4 = sc->sc_shadow.cr4; } rc = 0; error: mutex_spin_exit(&sc->sc_intr_lock); return rc; } /* * Take it out of the queue as much as possible. */ static int arcofi_recv_data(struct arcofi_softc *sc) { uint8_t *cur; uint8_t *past; cur = sc->sc_recv.buf; past = sc->sc_recv.past; while (cur != past && (arcofi_read(sc, ARCOFI_FIFO_SR) & FIFO_SR_IN_EMPTY) == 0) { *cur++ = arcofi_read(sc, ARCOFI_FIFO_DATA); } sc->sc_recv.buf = cur; return past - cur; } /* * Fill the queue as much as possible. */ static int arcofi_xmit_data(struct arcofi_softc *sc) { uint8_t *cur; uint8_t *past; cur = sc->sc_xmit.buf; past = sc->sc_xmit.past; while (cur != past && (arcofi_read(sc, ARCOFI_FIFO_SR) & FIFO_SR_OUT_FULL) == 0) { arcofi_write(sc, ARCOFI_FIFO_DATA, *cur++); } sc->sc_xmit.buf = cur; return past - cur; } static int arcofi_start_input(void *v, void *rbuf, int rsz, void (*cb)(void *), void *cbarg) { struct arcofi_softc *sc = (struct arcofi_softc *)v; #ifdef ARCOFI_DEBUG printf("%s: %s, mode %d\n", device_xname(sc->sc_dev), __func__, sc->sc_mode); #endif /* enable data FIFO if becoming active */ if (sc->sc_mode == 0) arcofi_write(sc, ARCOFI_CSR, arcofi_read(sc, ARCOFI_CSR) | CSR_DATA_FIFO_ENABLE); sc->sc_mode |= AUMODE_RECORD; sc->sc_recv.buf = (uint8_t *)rbuf; sc->sc_recv.past = (uint8_t *)rbuf + rsz; sc->sc_recv.cb = cb; sc->sc_recv.cbarg = cbarg; /* enable input FIFO interrupts */ arcofi_write(sc, ARCOFI_FIFO_IR, arcofi_read(sc, ARCOFI_FIFO_IR) | FIFO_IR_ENABLE(FIFO_IR_IN_HALF_EMPTY)); return 0; } static int arcofi_start_output(void *v, void *wbuf, int wsz, void (*cb)(void *), void *cbarg) { struct arcofi_softc *sc = (struct arcofi_softc *)v; #ifdef ARCOFI_DEBUG printf("%s: %s, mode %d\n", device_xname(sc->sc_dev), __func__, sc->sc_mode); #endif /* enable data FIFO if becoming active */ if (sc->sc_mode == 0) arcofi_write(sc, ARCOFI_CSR, arcofi_read(sc, ARCOFI_CSR) | CSR_DATA_FIFO_ENABLE); sc->sc_mode |= AUMODE_PLAY; sc->sc_xmit.buf = (uint8_t *)wbuf; sc->sc_xmit.past = (uint8_t *)wbuf + wsz; sc->sc_xmit.cb = cb; sc->sc_xmit.cbarg = cbarg; /* Fill FIFO */ arcofi_xmit_data(sc); /* enable output FIFO interrupts */ arcofi_write(sc, ARCOFI_FIFO_IR, arcofi_read(sc, ARCOFI_FIFO_IR) | FIFO_IR_ENABLE(FIFO_IR_OUT_HALF_EMPTY)); return 0; } static int arcofi_halt_input(void *v) { struct arcofi_softc *sc = (struct arcofi_softc *)v; #ifdef ARCOFI_DEBUG printf("%s: %s, mode %d\n", device_xname(sc->sc_dev), __func__, sc->sc_mode); #endif /* disable input FIFO interrupts */ arcofi_write(sc, ARCOFI_FIFO_IR, arcofi_read(sc, ARCOFI_FIFO_IR) & ~FIFO_IR_ENABLE(FIFO_IR_IN_HALF_EMPTY)); /* disable data FIFO if becoming idle */ sc->sc_mode &= ~AUMODE_RECORD; if (sc->sc_mode == 0) arcofi_write(sc, ARCOFI_CSR, arcofi_read(sc, ARCOFI_CSR) & ~CSR_DATA_FIFO_ENABLE); return 0; } static int arcofi_halt_output(void *v) { struct arcofi_softc *sc = (struct arcofi_softc *)v; #ifdef ARCOFI_DEBUG printf("%s: %s, mode %d\n", device_xname(sc->sc_dev), __func__, sc->sc_mode); #endif /* disable output FIFO interrupts */ arcofi_write(sc, ARCOFI_FIFO_IR, arcofi_read(sc, ARCOFI_FIFO_IR) & ~FIFO_IR_ENABLE(FIFO_IR_OUT_HALF_EMPTY)); /* disable data FIFO if becoming idle */ sc->sc_mode &= ~AUMODE_PLAY; if (sc->sc_mode == 0) arcofi_write(sc, ARCOFI_CSR, arcofi_read(sc, ARCOFI_CSR) & ~CSR_DATA_FIFO_ENABLE); return 0; } static int arcofi_getdev(void *v, struct audio_device *ad) { struct arcofi_softc *sc = (struct arcofi_softc *)v; *ad = sc->sc_audio_device; return 0; } /* * Convert gain table index to AUDIO_MIN_GAIN..AUDIO_MAX_GAIN scale. */ static int arcofi_gain_to_mi(uint idx) { if (idx == 0) return AUDIO_MIN_GAIN; if (idx == __arraycount(arcofi_gains) - 1) return AUDIO_MAX_GAIN; return ((idx - 1) * (AUDIO_MAX_GAIN - AUDIO_MIN_GAIN)) / (__arraycount(arcofi_gains) - 1) + AUDIO_MIN_GAIN + 1; } /* * Convert AUDIO_MIN_GAIN..AUDIO_MAX_GAIN scale to gain table index. */ static uint arcofi_mi_to_gain(int lvl) { if (lvl <= AUDIO_MIN_GAIN) return 0; if (lvl >= AUDIO_MAX_GAIN) return __arraycount(arcofi_gains) - 1; return ((lvl - AUDIO_MIN_GAIN - 1) * (__arraycount(arcofi_gains) - 1)) / (AUDIO_MAX_GAIN - AUDIO_MIN_GAIN); } /* * The following routines rely upon this... */ #if (AUDIO_SPEAKER == AUDIO_LINE_IN) || (AUDIO_LINE_OUT == AUDIO_LINE_IN) || \ (AUDIO_SPEAKER == AUDIO_LINE_OUT) #error Please rework the cr3 handling logic. #endif /* * The mapping between the available inputs and outputs, and CR3, is as * follows: * - the `line in' connector is the `MIC' input. * - the `line out' connector is the `H out' (heaphones) output. * - the internal `speaker' is the `LS out' (loudspeaker) output. * * Each of these can be enabled or disabled indepently, except for * MIC enabled with H out and LS out disabled, which is not allowed * by the chip (and makes no sense for a chip which was intended to * be used in phones, not voice recorders); we cheat by keeping one * output source enabled, but with the output gain forced to minus * infinity to mute it. * * The truth table is thus: * * MIC LS out H out AFEC * off off off POR * off off on MUTE * off on off LH1 * off on on LH3, X input enabled * on off off RDY, GX forced to minus infinity * on off on RDY * on on off LH2 * on on on LH3 */ /* * Convert logical port enable settings to a valid CR3 value. */ static uint arcofi_portmask_to_cr3(int mask) { switch (mask) { default: case 0: return CR3_MIC_G_17 | CR3_AFEC_POR; case AUDIO_LINE_OUT: return CR3_MIC_G_17 | CR3_AFEC_MUTE; case AUDIO_SPEAKER: return CR3_MIC_G_17 | CR3_AFEC_LH1; case AUDIO_SPEAKER | AUDIO_LINE_OUT: return CR3_MIC_X_INPUT | CR3_AFEC_LH3; case AUDIO_LINE_IN: /* since we can't do this, just... */ /* FALLTHROUGH */ case AUDIO_LINE_IN | AUDIO_LINE_OUT: return CR3_MIC_G_17 | CR3_AFEC_RDY; case AUDIO_LINE_IN | AUDIO_SPEAKER: return CR3_MIC_G_17 | CR3_AFEC_LH2; case AUDIO_LINE_IN | AUDIO_SPEAKER | AUDIO_LINE_OUT: return CR3_MIC_G_17 | CR3_AFEC_LH3; } } /* * Convert CR3 to an enabled ports mask. */ static int arcofi_cr3_to_portmask(uint cr3, int output_mute) { switch (cr3 & CR3_AFEC_MASK) { default: case CR3_AFEC_POR: return 0; case CR3_AFEC_RDY: return output_mute ? AUDIO_LINE_IN : AUDIO_LINE_IN | AUDIO_LINE_OUT; case CR3_AFEC_HFS: case CR3_AFEC_LH1: return AUDIO_SPEAKER; case CR3_AFEC_LH2: return AUDIO_LINE_IN | AUDIO_SPEAKER; case CR3_AFEC_LH3: if ((cr3 & CR3_MIC_G_MASK) == CR3_MIC_X_INPUT) return AUDIO_SPEAKER | AUDIO_LINE_OUT; else return AUDIO_LINE_IN | AUDIO_SPEAKER | AUDIO_LINE_OUT; case CR3_AFEC_MUTE: return AUDIO_LINE_OUT; } } static int arcofi_set_port(void *v, mixer_ctrl_t *mc) { struct arcofi_softc *sc = (struct arcofi_softc *)v; int portmask = 0; #ifdef ARCOFI_DEBUG printf("%s: %s, mode %d\n", device_xname(sc->sc_dev), __func__, sc->sc_mode); #endif /* check for proper type */ switch (mc->dev) { /* volume settings */ case ARCOFI_PORT_AUDIO_IN_VOLUME: case ARCOFI_PORT_AUDIO_OUT_VOLUME: case ARCOFI_PORT_AUDIO_SPKR_VOLUME: if (mc->un.value.num_channels != 1) return EINVAL; break; /* mute settings */ case ARCOFI_PORT_AUDIO_IN_MUTE: case ARCOFI_PORT_AUDIO_OUT_MUTE: case ARCOFI_PORT_AUDIO_SPKR_MUTE: if (mc->type != AUDIO_MIXER_ENUM) return EINVAL; portmask = arcofi_cr3_to_portmask(sc->sc_shadow.cr3, sc->sc_shadow.output_mute); #ifdef ARCOFI_DEBUG printf("%s: %s cr3 %02x -> mask %02x\n", device_xname(sc->sc_dev), __func__, sc->sc_shadow.cr3, portmask); #endif break; default: return EINVAL; } switch (mc->dev) { /* volume settings */ case ARCOFI_PORT_AUDIO_IN_VOLUME: sc->sc_shadow.gr_idx = arcofi_mi_to_gain(mc->un.value.level[AUDIO_MIXER_LEVEL_MONO]); return 0; case ARCOFI_PORT_AUDIO_OUT_VOLUME: case ARCOFI_PORT_AUDIO_SPKR_VOLUME: sc->sc_shadow.gx_idx = arcofi_mi_to_gain(mc->un.value.level[AUDIO_MIXER_LEVEL_MONO]); return 0; /* mute settings */ case ARCOFI_PORT_AUDIO_IN_MUTE: if (mc->un.ord) portmask &= ~AUDIO_LINE_IN; else portmask |= AUDIO_LINE_IN; break; case ARCOFI_PORT_AUDIO_OUT_MUTE: if (mc->un.ord) portmask &= ~AUDIO_LINE_OUT; else portmask |= AUDIO_LINE_OUT; break; case ARCOFI_PORT_AUDIO_SPKR_MUTE: if (mc->un.ord) portmask &= ~AUDIO_SPEAKER; else portmask |= AUDIO_SPEAKER; break; } sc->sc_shadow.cr3 = (sc->sc_shadow.cr3 & CR3_OPMODE_MASK) | arcofi_portmask_to_cr3(portmask); sc->sc_shadow.output_mute = (portmask == AUDIO_LINE_IN); #ifdef ARCOFI_DEBUG printf("%s: %s mask %02x -> cr3 %02x m %d\n", device_xname(sc->sc_dev), __func__, portmask, sc->sc_shadow.cr3, sc->sc_shadow.output_mute); #endif return 0; } static int arcofi_get_port(void *v, mixer_ctrl_t *mc) { struct arcofi_softc *sc = (struct arcofi_softc *)v; int portmask = 0; #ifdef ARCOFI_DEBUG printf("%s: %s, mode %d\n", device_xname(sc->sc_dev), __func__, sc->sc_mode); #endif /* check for proper type */ switch (mc->dev) { /* volume settings */ case ARCOFI_PORT_AUDIO_IN_VOLUME: case ARCOFI_PORT_AUDIO_OUT_VOLUME: case ARCOFI_PORT_AUDIO_SPKR_VOLUME: if (mc->un.value.num_channels != 1) return EINVAL; break; /* mute settings */ case ARCOFI_PORT_AUDIO_IN_MUTE: case ARCOFI_PORT_AUDIO_OUT_MUTE: case ARCOFI_PORT_AUDIO_SPKR_MUTE: if (mc->type != AUDIO_MIXER_ENUM) return EINVAL; portmask = arcofi_cr3_to_portmask(sc->sc_shadow.cr3, sc->sc_shadow.output_mute); #ifdef ARCOFI_DEBUG printf("%s: %s cr3 %02x -> mask %02x\n", device_xname(sc->sc_dev), __func__, sc->sc_shadow.cr3, portmask); #endif break; default: return EINVAL; } switch (mc->dev) { /* volume settings */ case ARCOFI_PORT_AUDIO_IN_VOLUME: mc->un.value.level[AUDIO_MIXER_LEVEL_MONO] = arcofi_gain_to_mi(sc->sc_shadow.gr_idx); break; case ARCOFI_PORT_AUDIO_OUT_VOLUME: case ARCOFI_PORT_AUDIO_SPKR_VOLUME: mc->un.value.level[AUDIO_MIXER_LEVEL_MONO] = arcofi_gain_to_mi(sc->sc_shadow.gx_idx); break; /* mute settings */ case ARCOFI_PORT_AUDIO_IN_MUTE: mc->un.ord = portmask & AUDIO_LINE_IN ? 0 : 1; break; case ARCOFI_PORT_AUDIO_OUT_MUTE: mc->un.ord = portmask & AUDIO_LINE_OUT ? 0 : 1; break; case ARCOFI_PORT_AUDIO_SPKR_MUTE: mc->un.ord = portmask & AUDIO_SPEAKER ? 0 : 1; break; } return 0; } static int arcofi_query_devinfo(void *v, mixer_devinfo_t *md) { switch (md->index) { default: return ENXIO; /* items */ case ARCOFI_PORT_AUDIO_IN_VOLUME: md->type = AUDIO_MIXER_VALUE; md->mixer_class = ARCOFI_CLASS_INPUT; md->prev = AUDIO_MIXER_LAST; md->next = ARCOFI_PORT_AUDIO_IN_MUTE; strlcpy(md->label.name, AudioNline, sizeof md->label.name); goto mono_volume; case ARCOFI_PORT_AUDIO_OUT_VOLUME: md->type = AUDIO_MIXER_VALUE; md->mixer_class = ARCOFI_CLASS_OUTPUT; md->prev = AUDIO_MIXER_LAST; md->next = ARCOFI_PORT_AUDIO_OUT_MUTE; strlcpy(md->label.name, AudioNline, sizeof md->label.name); goto mono_volume; case ARCOFI_PORT_AUDIO_SPKR_VOLUME: md->type = AUDIO_MIXER_VALUE; md->mixer_class = ARCOFI_CLASS_OUTPUT; md->prev = AUDIO_MIXER_LAST; md->next = ARCOFI_PORT_AUDIO_SPKR_MUTE; strlcpy(md->label.name, AudioNspeaker, sizeof md->label.name); /* goto mono_volume; */ mono_volume: md->un.v.num_channels = 1; strlcpy(md->un.v.units.name, AudioNvolume, sizeof md->un.v.units.name); break; case ARCOFI_PORT_AUDIO_IN_MUTE: md->type = AUDIO_MIXER_ENUM; md->mixer_class = ARCOFI_CLASS_INPUT; md->prev = ARCOFI_PORT_AUDIO_IN_VOLUME; md->next = AUDIO_MIXER_LAST; goto mute; case ARCOFI_PORT_AUDIO_OUT_MUTE: md->type = AUDIO_MIXER_ENUM; md->mixer_class = ARCOFI_CLASS_OUTPUT; md->prev = ARCOFI_PORT_AUDIO_OUT_VOLUME; md->next = AUDIO_MIXER_LAST; goto mute; case ARCOFI_PORT_AUDIO_SPKR_MUTE: md->type = AUDIO_MIXER_ENUM; md->mixer_class = ARCOFI_CLASS_OUTPUT; md->prev = ARCOFI_PORT_AUDIO_SPKR_VOLUME; md->next = AUDIO_MIXER_LAST; /* goto mute; */ mute: strlcpy(md->label.name, AudioNmute, sizeof md->label.name); md->un.e.num_mem = 2; strlcpy(md->un.e.member[0].label.name, AudioNoff, sizeof md->un.e.member[0].label.name); md->un.e.member[0].ord = 0; strlcpy(md->un.e.member[1].label.name, AudioNon, sizeof md->un.e.member[1].label.name); md->un.e.member[1].ord = 1; break; /* classes */ case ARCOFI_CLASS_INPUT: md->type = AUDIO_MIXER_CLASS; md->mixer_class = ARCOFI_CLASS_INPUT; md->prev = AUDIO_MIXER_LAST; md->next = AUDIO_MIXER_LAST; strlcpy(md->label.name, AudioCinputs, sizeof md->label.name); break; case ARCOFI_CLASS_OUTPUT: md->type = AUDIO_MIXER_CLASS; md->mixer_class = ARCOFI_CLASS_OUTPUT; md->prev = AUDIO_MIXER_LAST; md->next = AUDIO_MIXER_LAST; strlcpy(md->label.name, AudioCoutputs, sizeof md->label.name); break; } return 0; } static int arcofi_get_props(void *v) { return AUDIO_PROP_PLAYBACK | AUDIO_PROP_CAPTURE; } static void arcofi_get_locks(void *v, kmutex_t **intr, kmutex_t **thread) { struct arcofi_softc *sc = (struct arcofi_softc *)v; *intr = &sc->sc_intr_lock; *thread = &sc->sc_lock; } int arcofi_hwintr(void *v) { struct arcofi_softc *sc = (struct arcofi_softc *)v; uint8_t csr, fir; int rc = 0; csr = arcofi_read(sc, ARCOFI_CSR); if ((csr & CSR_INTR_REQUEST) == 0) return 0; fir = arcofi_read(sc, ARCOFI_FIFO_IR); /* receive */ if ((sc->sc_mode & AUMODE_RECORD) && (fir & FIFO_IR_EVENT(FIFO_IR_IN_HALF_EMPTY))) { rc = 1; if (arcofi_recv_data(sc) == 0) { /* disable further interrupts */ arcofi_write(sc, ARCOFI_FIFO_IR, arcofi_read(sc, ARCOFI_FIFO_IR) & ~FIFO_IR_ENABLE(FIFO_IR_IN_HALF_EMPTY)); /* callback */ sc->sc_recv.cb(sc->sc_recv.cbarg); } } /* xmit */ if ((sc->sc_mode & AUMODE_PLAY) && (fir & FIFO_IR_EVENT(FIFO_IR_OUT_HALF_EMPTY))) { rc = 1; if (arcofi_xmit_data(sc) == 0) { /* disable further interrupts */ arcofi_write(sc, ARCOFI_FIFO_IR, arcofi_read(sc, ARCOFI_FIFO_IR) & ~FIFO_IR_ENABLE(FIFO_IR_OUT_HALF_EMPTY)); /* callback */ sc->sc_xmit.cb(sc->sc_xmit.cbarg); } } #ifdef ARCOFI_DEBUG if (rc == 0) printf("%s: unclaimed interrupt, csr %02x fir %02x fsr %02x\n", device_xname(sc->sc_dev), csr, fir, arcofi_read(sc, ARCOFI_FIFO_SR)); #endif return rc; } static int arcofi_cmd(struct arcofi_softc *sc, uint8_t cmd, const uint8_t *data) { size_t len; uint8_t csr; int cnt; static const uint8_t cmdlen[] = { [SOP_0] = 4, [COP_1] = 4, [COP_2] = 2, [COP_3] = 2, [SOP_4] = 1, [SOP_5] = 1, [SOP_6] = 1, [SOP_7] = 1, [COP_8] = 2, [COP_9] = 4, [COP_A] = 8, [COP_B] = 2, [COP_C] = 8, [COP_D] = 4, [COP_E] = 4 }; /* * Compute command length. */ if (cmd >= __arraycount(cmdlen)) return EINVAL; len = cmdlen[cmd]; KASSERT(cold || mutex_owned(&sc->sc_intr_lock)); /* * Disable all FIFO processing. */ csr = arcofi_read(sc, ARCOFI_CSR); arcofi_write(sc, ARCOFI_CSR, csr & ~(CSR_DATA_FIFO_ENABLE | CSR_CTRL_FIFO_ENABLE)); /* * Fill the FIFO with the command bytes. */ arcofi_write(sc, ARCOFI_FIFO_CTRL, CMDR_PU | CMDR_WRITE | cmd); for (; len != 0; len--) arcofi_write(sc, ARCOFI_FIFO_CTRL, *data++); /* * Enable command processing. */ arcofi_write(sc, ARCOFI_CSR, (csr & ~CSR_DATA_FIFO_ENABLE) | CSR_CTRL_FIFO_ENABLE); /* * Wait for the command FIFO to be empty. */ cnt = 100; while ((arcofi_read(sc, ARCOFI_FIFO_SR) & FIFO_SR_CTRL_EMPTY) == 0) { if (cnt-- == 0) { return EBUSY; } delay(10); } arcofi_write(sc, ARCOFI_CSR, csr); return 0; } void arcofi_attach(struct arcofi_softc *sc, const char *versionstr) { device_t self; int rc; uint8_t op, cmd[4]; self = sc->sc_dev; mutex_init(&sc->sc_lock, MUTEX_DEFAULT, IPL_NONE); mutex_init(&sc->sc_intr_lock, MUTEX_DEFAULT, IPL_AUDIO); /* * Reset logic. */ arcofi_write(sc, ARCOFI_ID, 0); delay(100000); arcofi_write(sc, ARCOFI_CSR, 0); /* * Initialize the chip to default settings (8 bit, u-Law). */ sc->sc_active.cr3 = arcofi_portmask_to_cr3(AUDIO_SPEAKER) | CR3_OPMODE_NORMAL; sc->sc_active.cr4 = CR4_TM | CR4_ULAW; sc->sc_active.gr_idx = sc->sc_active.gx_idx = 1 + NEGATIVE_GAINS; sc->sc_active.output_mute = 0; memcpy(&sc->sc_shadow, &sc->sc_active, sizeof(sc->sc_active)); /* clear CRAM */ op = SOP_4; cmd[0] = CR1_IDR; if ((rc = arcofi_cmd(sc, op, cmd)) != 0) goto error; delay(1000); /* set gain values before enabling them in CR1 */ op = COP_2; cmd[0] = arcofi_gains[sc->sc_active.gr_idx] >> 8; cmd[1] = arcofi_gains[sc->sc_active.gr_idx]; if ((rc = arcofi_cmd(sc, op, cmd)) != 0) goto error; /* same value for gx... */ op = COP_B; if ((rc = arcofi_cmd(sc, op, cmd)) != 0) goto error; /* set all CR registers at once */ op = SOP_0; cmd[0] = sc->sc_active.cr4; cmd[1] = sc->sc_active.cr3; cmd[2] = CR2_SD | CR2_SC | CR2_SB | CR2_SA | CR2_ELS | CR2_AM | CR2_EFC; cmd[3] = CR1_GR | CR1_GX; if ((rc = arcofi_cmd(sc, op, cmd)) != 0) goto error; arcofi_write(sc, ARCOFI_FIFO_IR, 0); arcofi_write(sc, ARCOFI_CSR, CSR_INTR_ENABLE); strlcpy(sc->sc_audio_device.name, arcofi_cd.cd_name, sizeof(sc->sc_audio_device.name)); strlcpy(sc->sc_audio_device.version, versionstr, sizeof(sc->sc_audio_device.version)); strlcpy(sc->sc_audio_device.config, device_xname(self), sizeof(sc->sc_audio_device.config)); audio_attach_mi(&arcofi_hw_if, sc, self); return; error: arcofi_write(sc, ARCOFI_ID, 0); aprint_error("%s: %02x command failed, error %d\n", __func__, op, rc); mutex_destroy(&sc->sc_intr_lock); mutex_destroy(&sc->sc_lock); }