/* $NetBSD: arcofi.c,v 1.4 2022/05/31 08:43:15 andvar 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 <sys/param.h>
#include <sys/systm.h>
#include <sys/conf.h>
#include <sys/device.h>
#include <sys/kernel.h>
#include <sys/proc.h>
#include <sys/mutex.h>
#include <sys/bus.h>
#include <sys/intr.h>
#include <sys/audioio.h>
#include <dev/audio/audio_if.h>
#include <dev/audio/mulaw.h>
#include <dev/ic/arcofivar.h>
#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 independently, 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);
}