// SPDX-License-Identifier: GPL-2.0-only /* * Apple Onboard Audio driver for Onyx codec * * Copyright 2006 Johannes Berg <johannes@sipsolutions.net> * * This is a driver for the pcm3052 codec chip (codenamed Onyx) * that is present in newer Apple hardware (with digital output). * * The Onyx codec has the following connections (listed by the bit * to be used in aoa_codec.connected): * 0: analog output * 1: digital output * 2: line input * 3: microphone input * Note that even though I know of no machine that has for example * the digital output connected but not the analog, I have handled * all the different cases in the code so that this driver may serve * as a good example of what to do. * * NOTE: This driver assumes that there's at most one chip to be * used with one alsa card, in form of creating all kinds * of mixer elements without regard for their existence. * But snd-aoa assumes that there's at most one card, so * this means you can only have one onyx on a system. This * should probably be fixed by changing the assumption of * having just a single card on a system, and making the * 'card' pointer accessible to anyone who needs it instead * of hiding it in the aoa_snd_* functions...
*/ #include <linux/delay.h> #include <linux/module.h> #include <linux/of.h> #include <linux/slab.h>
MODULE_AUTHOR("Johannes Berg ");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("pcm3052 (onyx) codec driver for snd-aoa");
/* like above, this is necessary because a lot * of alsa mixer programs don't handle ranges * that don't start at 0 properly.
* even alsamixer is one of them... */ #define INPUTGAIN_RANGE_SHIFT (-3)
staticconststruct snd_kcontrol_new capture_source_control = {
.iface = SNDRV_CTL_ELEM_IFACE_MIXER, /* If we name this 'Input Source', it properly shows up in * alsamixer as a selection, * but it's shown under the * 'Playback' category. * If I name it 'Capture Source', it shows up in strange * ways (two bools of which one can be selected at a * time) but at least it's shown in the 'Capture' * category. * I was told that this was due to backward compatibility, * but I don't understand then why the mangling is *not* * done when I name it "Input Source".....
*/
.name = "Capture Source",
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
.info = onyx_snd_capture_source_info,
.get = onyx_snd_capture_source_get,
.put = onyx_snd_capture_source_put,
};
staticint onyx_snd_mute_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{ struct onyx *onyx = snd_kcontrol_chip(kcontrol);
u8 v = 0, c = 0; int err = -EBUSY;
mutex_lock(&onyx->mutex); if (onyx->analog_locked) goto out_unlock;
onyx_read_register(onyx, ONYX_REG_DAC_CONTROL, &v);
c = v;
c &= ~(ONYX_MUTE_RIGHT | ONYX_MUTE_LEFT); if (!ucontrol->value.integer.value[0])
c |= ONYX_MUTE_LEFT; if (!ucontrol->value.integer.value[1])
c |= ONYX_MUTE_RIGHT;
err = onyx_write_register(onyx, ONYX_REG_DAC_CONTROL, c);
staticconst u8 initial_values[ARRAY_SIZE(register_map)] = {
0x80, 0x80, /* muted */
ONYX_MRST | ONYX_SRST, /* but handled specially! */
ONYX_MUTE_LEFT | ONYX_MUTE_RIGHT,
0, /* no deemphasis */
ONYX_DAC_FILTER_ALWAYS,
ONYX_OUTPHASE_INVERTED,
(-1 /*dB*/ + 8) & 0xF, /* line in selected, -1 dB gain*/
ONYX_ADC_HPF_ALWAYS,
(1<<2), /* pcm audio */
2, /* category: pcm coder */
0, /* sampling frequency 44.1 kHz, clock accuracy level II */
1 /* 24 bit depth */
};
/* reset registers of chip, either to initial or to previous values */ staticint onyx_register_init(struct onyx *onyx)
{ int i;
u8 val;
u8 regs[sizeof(initial_values)];
if (!onyx->initialised) {
memcpy(regs, initial_values, sizeof(initial_values)); if (onyx_read_register(onyx, ONYX_REG_CONTROL, &val)) return -1;
val &= ~ONYX_SILICONVERSION;
val |= initial_values[3];
regs[3] = val;
} else { for (i=0; i<sizeof(register_map); i++)
regs[i] = onyx->cache[register_map[i]-FIRSTREGISTER];
}
for (i=0; i<sizeof(register_map); i++) { if (onyx_write_register(onyx, register_map[i], regs[i])) return -1;
}
onyx->initialised = 1; return 0;
}
staticstruct transfer_info onyx_transfers[] = { /* this is first so we can skip it if no input is present... * No hardware exists with that, but it's here as an example
* of what to do :) */
{ /* analog input */
.formats = SNDRV_PCM_FMTBIT_S8 |
SNDRV_PCM_FMTBIT_S16_BE |
SNDRV_PCM_FMTBIT_S24_BE,
.rates = SNDRV_PCM_RATE_8000_96000,
.transfer_in = 1,
.must_be_clock_source = 0,
.tag = 0,
},
{ /* if analog and digital are currently off, anything should go,
* so this entry describes everything we can do... */
.formats = SNDRV_PCM_FMTBIT_S8 |
SNDRV_PCM_FMTBIT_S16_BE |
SNDRV_PCM_FMTBIT_S24_BE #ifdef SNDRV_PCM_FMTBIT_COMPRESSED_16BE
| SNDRV_PCM_FMTBIT_COMPRESSED_16BE #endif
,
.rates = SNDRV_PCM_RATE_8000_96000,
.tag = 0,
},
{ /* analog output */
.formats = SNDRV_PCM_FMTBIT_S8 |
SNDRV_PCM_FMTBIT_S16_BE |
SNDRV_PCM_FMTBIT_S24_BE,
.rates = SNDRV_PCM_RATE_8000_96000,
.transfer_in = 0,
.must_be_clock_source = 0,
.tag = 1,
},
{ /* digital pcm output, also possible for analog out */
.formats = SNDRV_PCM_FMTBIT_S8 |
SNDRV_PCM_FMTBIT_S16_BE |
SNDRV_PCM_FMTBIT_S24_BE,
.rates = SNDRV_PCM_RATE_32000 |
SNDRV_PCM_RATE_44100 |
SNDRV_PCM_RATE_48000,
.transfer_in = 0,
.must_be_clock_source = 0,
.tag = 2,
}, #ifdef SNDRV_PCM_FMTBIT_COMPRESSED_16BE /* Once alsa gets supports for this kind of thing we can add it... */
{ /* digital compressed output */
.formats = SNDRV_PCM_FMTBIT_COMPRESSED_16BE,
.rates = SNDRV_PCM_RATE_32000 |
SNDRV_PCM_RATE_44100 |
SNDRV_PCM_RATE_48000,
.tag = 2,
}, #endif
{}
};
#ifdef SNDRV_PCM_FMTBIT_COMPRESSED_16BE if (substream->runtime->format == SNDRV_PCM_FMTBIT_COMPRESSED_16BE) { /* mute and lock analog output */
onyx_read_register(onyx, ONYX_REG_DAC_CONTROL, &v); if (onyx_write_register(onyx,
ONYX_REG_DAC_CONTROL,
v | ONYX_MUTE_RIGHT | ONYX_MUTE_LEFT)) goto out_unlock;
onyx->analog_locked = 1;
err = 0; goto out_unlock;
} #endif switch (substream->runtime->rate) { case 32000: case 44100: case 48000: /* these rates are ok for all outputs */ /* FIXME: program spdif channel control bits here so that
* userspace doesn't have to if it only plays pcm! */
err = 0; goto out_unlock; default: /* got some rate that the digital output can't do,
* so disable and lock it */
onyx_read_register(cii->codec_data, ONYX_REG_DIG_INFO4, &v); if (onyx_write_register(onyx,
ONYX_REG_DIG_INFO4,
v & ~ONYX_SPDIF_ENABLE)) goto out_unlock;
onyx->spdif_locked = 1;
err = 0; goto out_unlock;
}
mutex_lock(&onyx->mutex); if (onyx_read_register(onyx, ONYX_REG_CONTROL, &v)) goto out_unlock;
onyx_write_register(onyx, ONYX_REG_CONTROL, v | ONYX_ADPSV | ONYX_DAPSV); /* Apple does a sleep here but the datasheet says to do it on resume */
err = 0;
out_unlock:
mutex_unlock(&onyx->mutex);
/* take codec out of suspend (if it still is after reset) */ if (onyx_read_register(onyx, ONYX_REG_CONTROL, &v)) goto out_unlock;
onyx_write_register(onyx, ONYX_REG_CONTROL, v & ~(ONYX_ADPSV | ONYX_DAPSV)); /* FIXME: should divide by sample rate, but 8k is the lowest we go */
msleep(2205000/8000); /* reset all values */
onyx_register_init(onyx);
err = 0;
out_unlock:
mutex_unlock(&onyx->mutex);
if (onyx_register_init(onyx)) {
printk(KERN_ERR PFX "failed to initialise onyx registers\n"); return -ENODEV;
}
if (aoa_snd_device_new(SNDRV_DEV_CODEC, onyx, &ops)) {
printk(KERN_ERR PFX "failed to create onyx snd device!\n"); return -ENODEV;
}
/* nothing connected? what a joke! */ if ((onyx->codec.connected & 0xF) == 0) return -ENOTCONN;
/* if no inputs are present... */ if ((onyx->codec.connected & 0xC) == 0) { if (!onyx->codec_info)
onyx->codec_info = kmalloc(sizeof(struct codec_info), GFP_KERNEL); if (!onyx->codec_info) return -ENOMEM;
ci = onyx->codec_info;
*ci = onyx_codec_info;
ci->transfers++;
}
/* if no outputs are present... */ if ((onyx->codec.connected & 3) == 0) { if (!onyx->codec_info)
onyx->codec_info = kmalloc(sizeof(struct codec_info), GFP_KERNEL); if (!onyx->codec_info) return -ENOMEM;
ci = onyx->codec_info; /* this is fine as there have to be inputs
* if we end up in this part of the code */
*ci = onyx_codec_info;
ci->transfers[1].formats = 0;
}
if (onyx->codec.soundbus_dev->attach_codec(onyx->codec.soundbus_dev,
aoa_get_card(),
ci, onyx)) {
printk(KERN_ERR PFX "error creating onyx pcm\n"); return -ENODEV;
} #define ADDCTL(n) \ do { \
ctl = snd_ctl_new1(&n, onyx); \ if (ctl) { \
ctl->id.device = \
onyx->codec.soundbus_dev->pcm->device; \
err = aoa_snd_ctl_add(ctl); \ if (err) \ goto error; \
} \
} while (0)
if (onyx->codec.soundbus_dev->pcm) { /* give the user appropriate controls
* depending on what inputs are connected */ if ((onyx->codec.connected & 0xC) == 0xC)
ADDCTL(capture_source_control); elseif (onyx->codec.connected & 4)
onyx_set_capture_source(onyx, 0); else
onyx_set_capture_source(onyx, 1); if (onyx->codec.connected & 0xC)
ADDCTL(inputgain_control);
/* depending on what output is connected,
* give the user appropriate controls */ if (onyx->codec.connected & 1) {
ADDCTL(volume_control);
ADDCTL(mute_control);
ADDCTL(ovr1_control);
ADDCTL(flt0_control);
ADDCTL(hpf_control);
ADDCTL(dm12_control); /* spdif control defaults to off */
} if (onyx->codec.connected & 2) {
ADDCTL(onyx_spdif_mask);
ADDCTL(onyx_spdif_ctrl);
} if ((onyx->codec.connected & 3) == 3)
ADDCTL(spdif_control); /* if only S/PDIF is connected, enable it unconditionally */ if ((onyx->codec.connected & 3) == 2) {
onyx_read_register(onyx, ONYX_REG_DIG_INFO4, &v);
v |= ONYX_SPDIF_ENABLE;
onyx_write_register(onyx, ONYX_REG_DIG_INFO4, v);
}
} #undef ADDCTL
printk(KERN_INFO PFX "attached to onyx codec via i2c\n");
if (!onyx->codec.soundbus_dev) {
printk(KERN_ERR PFX "onyx_exit_codec called without soundbus_dev!\n"); return;
}
onyx->codec.soundbus_dev->detach_codec(onyx->codec.soundbus_dev, onyx);
}
/* we try to read from register ONYX_REG_CONTROL
* to check if the codec is present */ if (onyx_read_register(onyx, ONYX_REG_CONTROL, &dummy) != 0) {
printk(KERN_ERR PFX "failed to read control register\n"); goto fail;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.