/*
* 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.
*/
/* 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 */
/* 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).
*/
/*
* 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;
}
/*
* 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++);
/* 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;