92631e33d6
[ Upstream commit f474808acb3c4b30552d9c59b181244e0300d218 ]
A lot of places in the driver use onyx_read_register() without
checking the return value, and it's been working OK for ~10 years
or so, so probably never fails ... Rather than trying to check the
return value everywhere, which would be relatively intrusive, at
least make sure we don't use an uninitialized value.
Fixes: f3d9478b2c
("[ALSA] snd-aoa: add snd-aoa")
Reported-by: Stephen Rothwell <sfr@canb.auug.org.au>
Signed-off-by: Johannes Berg <johannes@sipsolutions.net>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
Signed-off-by: Sasha Levin <sashal@kernel.org>
1061 lines
27 KiB
C
1061 lines
27 KiB
C
/*
|
|
* Apple Onboard Audio driver for Onyx codec
|
|
*
|
|
* Copyright 2006 Johannes Berg <johannes@sipsolutions.net>
|
|
*
|
|
* GPL v2, can be found in COPYING.
|
|
*
|
|
*
|
|
* 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/slab.h>
|
|
MODULE_AUTHOR("Johannes Berg <johannes@sipsolutions.net>");
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_DESCRIPTION("pcm3052 (onyx) codec driver for snd-aoa");
|
|
|
|
#include "onyx.h"
|
|
#include "../aoa.h"
|
|
#include "../soundbus/soundbus.h"
|
|
|
|
|
|
#define PFX "snd-aoa-codec-onyx: "
|
|
|
|
struct onyx {
|
|
/* cache registers 65 to 80, they are write-only! */
|
|
u8 cache[16];
|
|
struct i2c_client *i2c;
|
|
struct aoa_codec codec;
|
|
u32 initialised:1,
|
|
spdif_locked:1,
|
|
analog_locked:1,
|
|
original_mute:2;
|
|
int open_count;
|
|
struct codec_info *codec_info;
|
|
|
|
/* mutex serializes concurrent access to the device
|
|
* and this structure.
|
|
*/
|
|
struct mutex mutex;
|
|
};
|
|
#define codec_to_onyx(c) container_of(c, struct onyx, codec)
|
|
|
|
/* both return 0 if all ok, else on error */
|
|
static int onyx_read_register(struct onyx *onyx, u8 reg, u8 *value)
|
|
{
|
|
s32 v;
|
|
|
|
if (reg != ONYX_REG_CONTROL) {
|
|
*value = onyx->cache[reg-FIRSTREGISTER];
|
|
return 0;
|
|
}
|
|
v = i2c_smbus_read_byte_data(onyx->i2c, reg);
|
|
if (v < 0) {
|
|
*value = 0;
|
|
return -1;
|
|
}
|
|
*value = (u8)v;
|
|
onyx->cache[ONYX_REG_CONTROL-FIRSTREGISTER] = *value;
|
|
return 0;
|
|
}
|
|
|
|
static int onyx_write_register(struct onyx *onyx, u8 reg, u8 value)
|
|
{
|
|
int result;
|
|
|
|
result = i2c_smbus_write_byte_data(onyx->i2c, reg, value);
|
|
if (!result)
|
|
onyx->cache[reg-FIRSTREGISTER] = value;
|
|
return result;
|
|
}
|
|
|
|
/* alsa stuff */
|
|
|
|
static int onyx_dev_register(struct snd_device *dev)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static struct snd_device_ops ops = {
|
|
.dev_register = onyx_dev_register,
|
|
};
|
|
|
|
/* this is necessary because most alsa mixer programs
|
|
* can't properly handle the negative range */
|
|
#define VOLUME_RANGE_SHIFT 128
|
|
|
|
static int onyx_snd_vol_info(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_info *uinfo)
|
|
{
|
|
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
|
|
uinfo->count = 2;
|
|
uinfo->value.integer.min = -128 + VOLUME_RANGE_SHIFT;
|
|
uinfo->value.integer.max = -1 + VOLUME_RANGE_SHIFT;
|
|
return 0;
|
|
}
|
|
|
|
static int onyx_snd_vol_get(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct onyx *onyx = snd_kcontrol_chip(kcontrol);
|
|
s8 l, r;
|
|
|
|
mutex_lock(&onyx->mutex);
|
|
onyx_read_register(onyx, ONYX_REG_DAC_ATTEN_LEFT, &l);
|
|
onyx_read_register(onyx, ONYX_REG_DAC_ATTEN_RIGHT, &r);
|
|
mutex_unlock(&onyx->mutex);
|
|
|
|
ucontrol->value.integer.value[0] = l + VOLUME_RANGE_SHIFT;
|
|
ucontrol->value.integer.value[1] = r + VOLUME_RANGE_SHIFT;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int onyx_snd_vol_put(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct onyx *onyx = snd_kcontrol_chip(kcontrol);
|
|
s8 l, r;
|
|
|
|
if (ucontrol->value.integer.value[0] < -128 + VOLUME_RANGE_SHIFT ||
|
|
ucontrol->value.integer.value[0] > -1 + VOLUME_RANGE_SHIFT)
|
|
return -EINVAL;
|
|
if (ucontrol->value.integer.value[1] < -128 + VOLUME_RANGE_SHIFT ||
|
|
ucontrol->value.integer.value[1] > -1 + VOLUME_RANGE_SHIFT)
|
|
return -EINVAL;
|
|
|
|
mutex_lock(&onyx->mutex);
|
|
onyx_read_register(onyx, ONYX_REG_DAC_ATTEN_LEFT, &l);
|
|
onyx_read_register(onyx, ONYX_REG_DAC_ATTEN_RIGHT, &r);
|
|
|
|
if (l + VOLUME_RANGE_SHIFT == ucontrol->value.integer.value[0] &&
|
|
r + VOLUME_RANGE_SHIFT == ucontrol->value.integer.value[1]) {
|
|
mutex_unlock(&onyx->mutex);
|
|
return 0;
|
|
}
|
|
|
|
onyx_write_register(onyx, ONYX_REG_DAC_ATTEN_LEFT,
|
|
ucontrol->value.integer.value[0]
|
|
- VOLUME_RANGE_SHIFT);
|
|
onyx_write_register(onyx, ONYX_REG_DAC_ATTEN_RIGHT,
|
|
ucontrol->value.integer.value[1]
|
|
- VOLUME_RANGE_SHIFT);
|
|
mutex_unlock(&onyx->mutex);
|
|
|
|
return 1;
|
|
}
|
|
|
|
static const struct snd_kcontrol_new volume_control = {
|
|
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
|
.name = "Master Playback Volume",
|
|
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
|
|
.info = onyx_snd_vol_info,
|
|
.get = onyx_snd_vol_get,
|
|
.put = onyx_snd_vol_put,
|
|
};
|
|
|
|
/* 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)
|
|
|
|
static int onyx_snd_inputgain_info(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_info *uinfo)
|
|
{
|
|
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
|
|
uinfo->count = 1;
|
|
uinfo->value.integer.min = 3 + INPUTGAIN_RANGE_SHIFT;
|
|
uinfo->value.integer.max = 28 + INPUTGAIN_RANGE_SHIFT;
|
|
return 0;
|
|
}
|
|
|
|
static int onyx_snd_inputgain_get(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct onyx *onyx = snd_kcontrol_chip(kcontrol);
|
|
u8 ig;
|
|
|
|
mutex_lock(&onyx->mutex);
|
|
onyx_read_register(onyx, ONYX_REG_ADC_CONTROL, &ig);
|
|
mutex_unlock(&onyx->mutex);
|
|
|
|
ucontrol->value.integer.value[0] =
|
|
(ig & ONYX_ADC_PGA_GAIN_MASK) + INPUTGAIN_RANGE_SHIFT;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int onyx_snd_inputgain_put(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct onyx *onyx = snd_kcontrol_chip(kcontrol);
|
|
u8 v, n;
|
|
|
|
if (ucontrol->value.integer.value[0] < 3 + INPUTGAIN_RANGE_SHIFT ||
|
|
ucontrol->value.integer.value[0] > 28 + INPUTGAIN_RANGE_SHIFT)
|
|
return -EINVAL;
|
|
mutex_lock(&onyx->mutex);
|
|
onyx_read_register(onyx, ONYX_REG_ADC_CONTROL, &v);
|
|
n = v;
|
|
n &= ~ONYX_ADC_PGA_GAIN_MASK;
|
|
n |= (ucontrol->value.integer.value[0] - INPUTGAIN_RANGE_SHIFT)
|
|
& ONYX_ADC_PGA_GAIN_MASK;
|
|
onyx_write_register(onyx, ONYX_REG_ADC_CONTROL, n);
|
|
mutex_unlock(&onyx->mutex);
|
|
|
|
return n != v;
|
|
}
|
|
|
|
static const struct snd_kcontrol_new inputgain_control = {
|
|
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
|
.name = "Master Capture Volume",
|
|
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
|
|
.info = onyx_snd_inputgain_info,
|
|
.get = onyx_snd_inputgain_get,
|
|
.put = onyx_snd_inputgain_put,
|
|
};
|
|
|
|
static int onyx_snd_capture_source_info(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_info *uinfo)
|
|
{
|
|
static const char * const texts[] = { "Line-In", "Microphone" };
|
|
|
|
return snd_ctl_enum_info(uinfo, 1, 2, texts);
|
|
}
|
|
|
|
static int onyx_snd_capture_source_get(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct onyx *onyx = snd_kcontrol_chip(kcontrol);
|
|
s8 v;
|
|
|
|
mutex_lock(&onyx->mutex);
|
|
onyx_read_register(onyx, ONYX_REG_ADC_CONTROL, &v);
|
|
mutex_unlock(&onyx->mutex);
|
|
|
|
ucontrol->value.enumerated.item[0] = !!(v&ONYX_ADC_INPUT_MIC);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void onyx_set_capture_source(struct onyx *onyx, int mic)
|
|
{
|
|
s8 v;
|
|
|
|
mutex_lock(&onyx->mutex);
|
|
onyx_read_register(onyx, ONYX_REG_ADC_CONTROL, &v);
|
|
v &= ~ONYX_ADC_INPUT_MIC;
|
|
if (mic)
|
|
v |= ONYX_ADC_INPUT_MIC;
|
|
onyx_write_register(onyx, ONYX_REG_ADC_CONTROL, v);
|
|
mutex_unlock(&onyx->mutex);
|
|
}
|
|
|
|
static int onyx_snd_capture_source_put(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
if (ucontrol->value.enumerated.item[0] > 1)
|
|
return -EINVAL;
|
|
onyx_set_capture_source(snd_kcontrol_chip(kcontrol),
|
|
ucontrol->value.enumerated.item[0]);
|
|
return 1;
|
|
}
|
|
|
|
static const struct 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,
|
|
};
|
|
|
|
#define onyx_snd_mute_info snd_ctl_boolean_stereo_info
|
|
|
|
static int onyx_snd_mute_get(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct onyx *onyx = snd_kcontrol_chip(kcontrol);
|
|
u8 c;
|
|
|
|
mutex_lock(&onyx->mutex);
|
|
onyx_read_register(onyx, ONYX_REG_DAC_CONTROL, &c);
|
|
mutex_unlock(&onyx->mutex);
|
|
|
|
ucontrol->value.integer.value[0] = !(c & ONYX_MUTE_LEFT);
|
|
ucontrol->value.integer.value[1] = !(c & ONYX_MUTE_RIGHT);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int 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);
|
|
|
|
out_unlock:
|
|
mutex_unlock(&onyx->mutex);
|
|
|
|
return !err ? (v != c) : err;
|
|
}
|
|
|
|
static const struct snd_kcontrol_new mute_control = {
|
|
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
|
.name = "Master Playback Switch",
|
|
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
|
|
.info = onyx_snd_mute_info,
|
|
.get = onyx_snd_mute_get,
|
|
.put = onyx_snd_mute_put,
|
|
};
|
|
|
|
|
|
#define onyx_snd_single_bit_info snd_ctl_boolean_mono_info
|
|
|
|
#define FLAG_POLARITY_INVERT 1
|
|
#define FLAG_SPDIFLOCK 2
|
|
|
|
static int onyx_snd_single_bit_get(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct onyx *onyx = snd_kcontrol_chip(kcontrol);
|
|
u8 c;
|
|
long int pv = kcontrol->private_value;
|
|
u8 polarity = (pv >> 16) & FLAG_POLARITY_INVERT;
|
|
u8 address = (pv >> 8) & 0xff;
|
|
u8 mask = pv & 0xff;
|
|
|
|
mutex_lock(&onyx->mutex);
|
|
onyx_read_register(onyx, address, &c);
|
|
mutex_unlock(&onyx->mutex);
|
|
|
|
ucontrol->value.integer.value[0] = !!(c & mask) ^ polarity;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int onyx_snd_single_bit_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;
|
|
long int pv = kcontrol->private_value;
|
|
u8 polarity = (pv >> 16) & FLAG_POLARITY_INVERT;
|
|
u8 spdiflock = (pv >> 16) & FLAG_SPDIFLOCK;
|
|
u8 address = (pv >> 8) & 0xff;
|
|
u8 mask = pv & 0xff;
|
|
|
|
mutex_lock(&onyx->mutex);
|
|
if (spdiflock && onyx->spdif_locked) {
|
|
/* even if alsamixer doesn't care.. */
|
|
err = -EBUSY;
|
|
goto out_unlock;
|
|
}
|
|
onyx_read_register(onyx, address, &v);
|
|
c = v;
|
|
c &= ~(mask);
|
|
if (!!ucontrol->value.integer.value[0] ^ polarity)
|
|
c |= mask;
|
|
err = onyx_write_register(onyx, address, c);
|
|
|
|
out_unlock:
|
|
mutex_unlock(&onyx->mutex);
|
|
|
|
return !err ? (v != c) : err;
|
|
}
|
|
|
|
#define SINGLE_BIT(n, type, description, address, mask, flags) \
|
|
static struct snd_kcontrol_new n##_control = { \
|
|
.iface = SNDRV_CTL_ELEM_IFACE_##type, \
|
|
.name = description, \
|
|
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE, \
|
|
.info = onyx_snd_single_bit_info, \
|
|
.get = onyx_snd_single_bit_get, \
|
|
.put = onyx_snd_single_bit_put, \
|
|
.private_value = (flags << 16) | (address << 8) | mask \
|
|
}
|
|
|
|
SINGLE_BIT(spdif,
|
|
MIXER,
|
|
SNDRV_CTL_NAME_IEC958("", PLAYBACK, SWITCH),
|
|
ONYX_REG_DIG_INFO4,
|
|
ONYX_SPDIF_ENABLE,
|
|
FLAG_SPDIFLOCK);
|
|
SINGLE_BIT(ovr1,
|
|
MIXER,
|
|
"Oversampling Rate",
|
|
ONYX_REG_DAC_CONTROL,
|
|
ONYX_OVR1,
|
|
0);
|
|
SINGLE_BIT(flt0,
|
|
MIXER,
|
|
"Fast Digital Filter Rolloff",
|
|
ONYX_REG_DAC_FILTER,
|
|
ONYX_ROLLOFF_FAST,
|
|
FLAG_POLARITY_INVERT);
|
|
SINGLE_BIT(hpf,
|
|
MIXER,
|
|
"Highpass Filter",
|
|
ONYX_REG_ADC_HPF_BYPASS,
|
|
ONYX_HPF_DISABLE,
|
|
FLAG_POLARITY_INVERT);
|
|
SINGLE_BIT(dm12,
|
|
MIXER,
|
|
"Digital De-Emphasis",
|
|
ONYX_REG_DAC_DEEMPH,
|
|
ONYX_DIGDEEMPH_CTRL,
|
|
0);
|
|
|
|
static int onyx_spdif_info(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_info *uinfo)
|
|
{
|
|
uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
|
|
uinfo->count = 1;
|
|
return 0;
|
|
}
|
|
|
|
static int onyx_spdif_mask_get(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
/* datasheet page 30, all others are 0 */
|
|
ucontrol->value.iec958.status[0] = 0x3e;
|
|
ucontrol->value.iec958.status[1] = 0xff;
|
|
|
|
ucontrol->value.iec958.status[3] = 0x3f;
|
|
ucontrol->value.iec958.status[4] = 0x0f;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct snd_kcontrol_new onyx_spdif_mask = {
|
|
.access = SNDRV_CTL_ELEM_ACCESS_READ,
|
|
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
|
|
.name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,CON_MASK),
|
|
.info = onyx_spdif_info,
|
|
.get = onyx_spdif_mask_get,
|
|
};
|
|
|
|
static int onyx_spdif_get(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct onyx *onyx = snd_kcontrol_chip(kcontrol);
|
|
u8 v;
|
|
|
|
mutex_lock(&onyx->mutex);
|
|
onyx_read_register(onyx, ONYX_REG_DIG_INFO1, &v);
|
|
ucontrol->value.iec958.status[0] = v & 0x3e;
|
|
|
|
onyx_read_register(onyx, ONYX_REG_DIG_INFO2, &v);
|
|
ucontrol->value.iec958.status[1] = v;
|
|
|
|
onyx_read_register(onyx, ONYX_REG_DIG_INFO3, &v);
|
|
ucontrol->value.iec958.status[3] = v & 0x3f;
|
|
|
|
onyx_read_register(onyx, ONYX_REG_DIG_INFO4, &v);
|
|
ucontrol->value.iec958.status[4] = v & 0x0f;
|
|
mutex_unlock(&onyx->mutex);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int onyx_spdif_put(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct onyx *onyx = snd_kcontrol_chip(kcontrol);
|
|
u8 v;
|
|
|
|
mutex_lock(&onyx->mutex);
|
|
onyx_read_register(onyx, ONYX_REG_DIG_INFO1, &v);
|
|
v = (v & ~0x3e) | (ucontrol->value.iec958.status[0] & 0x3e);
|
|
onyx_write_register(onyx, ONYX_REG_DIG_INFO1, v);
|
|
|
|
v = ucontrol->value.iec958.status[1];
|
|
onyx_write_register(onyx, ONYX_REG_DIG_INFO2, v);
|
|
|
|
onyx_read_register(onyx, ONYX_REG_DIG_INFO3, &v);
|
|
v = (v & ~0x3f) | (ucontrol->value.iec958.status[3] & 0x3f);
|
|
onyx_write_register(onyx, ONYX_REG_DIG_INFO3, v);
|
|
|
|
onyx_read_register(onyx, ONYX_REG_DIG_INFO4, &v);
|
|
v = (v & ~0x0f) | (ucontrol->value.iec958.status[4] & 0x0f);
|
|
onyx_write_register(onyx, ONYX_REG_DIG_INFO4, v);
|
|
mutex_unlock(&onyx->mutex);
|
|
|
|
return 1;
|
|
}
|
|
|
|
static const struct snd_kcontrol_new onyx_spdif_ctrl = {
|
|
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
|
|
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
|
|
.name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,DEFAULT),
|
|
.info = onyx_spdif_info,
|
|
.get = onyx_spdif_get,
|
|
.put = onyx_spdif_put,
|
|
};
|
|
|
|
/* our registers */
|
|
|
|
static u8 register_map[] = {
|
|
ONYX_REG_DAC_ATTEN_LEFT,
|
|
ONYX_REG_DAC_ATTEN_RIGHT,
|
|
ONYX_REG_CONTROL,
|
|
ONYX_REG_DAC_CONTROL,
|
|
ONYX_REG_DAC_DEEMPH,
|
|
ONYX_REG_DAC_FILTER,
|
|
ONYX_REG_DAC_OUTPHASE,
|
|
ONYX_REG_ADC_CONTROL,
|
|
ONYX_REG_ADC_HPF_BYPASS,
|
|
ONYX_REG_DIG_INFO1,
|
|
ONYX_REG_DIG_INFO2,
|
|
ONYX_REG_DIG_INFO3,
|
|
ONYX_REG_DIG_INFO4
|
|
};
|
|
|
|
static 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 */
|
|
static int 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;
|
|
}
|
|
|
|
static struct 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
|
|
{}
|
|
};
|
|
|
|
static int onyx_usable(struct codec_info_item *cii,
|
|
struct transfer_info *ti,
|
|
struct transfer_info *out)
|
|
{
|
|
u8 v;
|
|
struct onyx *onyx = cii->codec_data;
|
|
int spdif_enabled, analog_enabled;
|
|
|
|
mutex_lock(&onyx->mutex);
|
|
onyx_read_register(onyx, ONYX_REG_DIG_INFO4, &v);
|
|
spdif_enabled = !!(v & ONYX_SPDIF_ENABLE);
|
|
onyx_read_register(onyx, ONYX_REG_DAC_CONTROL, &v);
|
|
analog_enabled =
|
|
(v & (ONYX_MUTE_RIGHT|ONYX_MUTE_LEFT))
|
|
!= (ONYX_MUTE_RIGHT|ONYX_MUTE_LEFT);
|
|
mutex_unlock(&onyx->mutex);
|
|
|
|
switch (ti->tag) {
|
|
case 0: return 1;
|
|
case 1: return analog_enabled;
|
|
case 2: return spdif_enabled;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static int onyx_prepare(struct codec_info_item *cii,
|
|
struct bus_info *bi,
|
|
struct snd_pcm_substream *substream)
|
|
{
|
|
u8 v;
|
|
struct onyx *onyx = cii->codec_data;
|
|
int err = -EBUSY;
|
|
|
|
mutex_lock(&onyx->mutex);
|
|
|
|
#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;
|
|
}
|
|
|
|
out_unlock:
|
|
mutex_unlock(&onyx->mutex);
|
|
|
|
return err;
|
|
}
|
|
|
|
static int onyx_open(struct codec_info_item *cii,
|
|
struct snd_pcm_substream *substream)
|
|
{
|
|
struct onyx *onyx = cii->codec_data;
|
|
|
|
mutex_lock(&onyx->mutex);
|
|
onyx->open_count++;
|
|
mutex_unlock(&onyx->mutex);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int onyx_close(struct codec_info_item *cii,
|
|
struct snd_pcm_substream *substream)
|
|
{
|
|
struct onyx *onyx = cii->codec_data;
|
|
|
|
mutex_lock(&onyx->mutex);
|
|
onyx->open_count--;
|
|
if (!onyx->open_count)
|
|
onyx->spdif_locked = onyx->analog_locked = 0;
|
|
mutex_unlock(&onyx->mutex);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int onyx_switch_clock(struct codec_info_item *cii,
|
|
enum clock_switch what)
|
|
{
|
|
struct onyx *onyx = cii->codec_data;
|
|
|
|
mutex_lock(&onyx->mutex);
|
|
/* this *MUST* be more elaborate later... */
|
|
switch (what) {
|
|
case CLOCK_SWITCH_PREPARE_SLAVE:
|
|
onyx->codec.gpio->methods->all_amps_off(onyx->codec.gpio);
|
|
break;
|
|
case CLOCK_SWITCH_SLAVE:
|
|
onyx->codec.gpio->methods->all_amps_restore(onyx->codec.gpio);
|
|
break;
|
|
default: /* silence warning */
|
|
break;
|
|
}
|
|
mutex_unlock(&onyx->mutex);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_PM
|
|
|
|
static int onyx_suspend(struct codec_info_item *cii, pm_message_t state)
|
|
{
|
|
struct onyx *onyx = cii->codec_data;
|
|
u8 v;
|
|
int err = -ENXIO;
|
|
|
|
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);
|
|
|
|
return err;
|
|
}
|
|
|
|
static int onyx_resume(struct codec_info_item *cii)
|
|
{
|
|
struct onyx *onyx = cii->codec_data;
|
|
u8 v;
|
|
int err = -ENXIO;
|
|
|
|
mutex_lock(&onyx->mutex);
|
|
|
|
/* reset codec */
|
|
onyx->codec.gpio->methods->set_hw_reset(onyx->codec.gpio, 0);
|
|
msleep(1);
|
|
onyx->codec.gpio->methods->set_hw_reset(onyx->codec.gpio, 1);
|
|
msleep(1);
|
|
onyx->codec.gpio->methods->set_hw_reset(onyx->codec.gpio, 0);
|
|
msleep(1);
|
|
|
|
/* 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);
|
|
|
|
return err;
|
|
}
|
|
|
|
#endif /* CONFIG_PM */
|
|
|
|
static struct codec_info onyx_codec_info = {
|
|
.transfers = onyx_transfers,
|
|
.sysclock_factor = 256,
|
|
.bus_factor = 64,
|
|
.owner = THIS_MODULE,
|
|
.usable = onyx_usable,
|
|
.prepare = onyx_prepare,
|
|
.open = onyx_open,
|
|
.close = onyx_close,
|
|
.switch_clock = onyx_switch_clock,
|
|
#ifdef CONFIG_PM
|
|
.suspend = onyx_suspend,
|
|
.resume = onyx_resume,
|
|
#endif
|
|
};
|
|
|
|
static int onyx_init_codec(struct aoa_codec *codec)
|
|
{
|
|
struct onyx *onyx = codec_to_onyx(codec);
|
|
struct snd_kcontrol *ctl;
|
|
struct codec_info *ci = &onyx_codec_info;
|
|
u8 v;
|
|
int err;
|
|
|
|
if (!onyx->codec.gpio || !onyx->codec.gpio->methods) {
|
|
printk(KERN_ERR PFX "gpios not assigned!!\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
onyx->codec.gpio->methods->set_hw_reset(onyx->codec.gpio, 0);
|
|
msleep(1);
|
|
onyx->codec.gpio->methods->set_hw_reset(onyx->codec.gpio, 1);
|
|
msleep(1);
|
|
onyx->codec.gpio->methods->set_hw_reset(onyx->codec.gpio, 0);
|
|
msleep(1);
|
|
|
|
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);
|
|
else if (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");
|
|
|
|
return 0;
|
|
error:
|
|
onyx->codec.soundbus_dev->detach_codec(onyx->codec.soundbus_dev, onyx);
|
|
snd_device_free(aoa_get_card(), onyx);
|
|
return err;
|
|
}
|
|
|
|
static void onyx_exit_codec(struct aoa_codec *codec)
|
|
{
|
|
struct onyx *onyx = codec_to_onyx(codec);
|
|
|
|
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);
|
|
}
|
|
|
|
static int onyx_i2c_probe(struct i2c_client *client,
|
|
const struct i2c_device_id *id)
|
|
{
|
|
struct device_node *node = client->dev.of_node;
|
|
struct onyx *onyx;
|
|
u8 dummy;
|
|
|
|
onyx = kzalloc(sizeof(struct onyx), GFP_KERNEL);
|
|
|
|
if (!onyx)
|
|
return -ENOMEM;
|
|
|
|
mutex_init(&onyx->mutex);
|
|
onyx->i2c = client;
|
|
i2c_set_clientdata(client, 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;
|
|
}
|
|
|
|
strlcpy(onyx->codec.name, "onyx", MAX_CODEC_NAME_LEN);
|
|
onyx->codec.owner = THIS_MODULE;
|
|
onyx->codec.init = onyx_init_codec;
|
|
onyx->codec.exit = onyx_exit_codec;
|
|
onyx->codec.node = of_node_get(node);
|
|
|
|
if (aoa_codec_register(&onyx->codec)) {
|
|
goto fail;
|
|
}
|
|
printk(KERN_DEBUG PFX "created and attached onyx instance\n");
|
|
return 0;
|
|
fail:
|
|
kfree(onyx);
|
|
return -ENODEV;
|
|
}
|
|
|
|
static int onyx_i2c_remove(struct i2c_client *client)
|
|
{
|
|
struct onyx *onyx = i2c_get_clientdata(client);
|
|
|
|
aoa_codec_unregister(&onyx->codec);
|
|
of_node_put(onyx->codec.node);
|
|
kfree(onyx->codec_info);
|
|
kfree(onyx);
|
|
return 0;
|
|
}
|
|
|
|
static const struct i2c_device_id onyx_i2c_id[] = {
|
|
{ "MAC,pcm3052", 0 },
|
|
{ }
|
|
};
|
|
MODULE_DEVICE_TABLE(i2c,onyx_i2c_id);
|
|
|
|
static struct i2c_driver onyx_driver = {
|
|
.driver = {
|
|
.name = "aoa_codec_onyx",
|
|
},
|
|
.probe = onyx_i2c_probe,
|
|
.remove = onyx_i2c_remove,
|
|
.id_table = onyx_i2c_id,
|
|
};
|
|
|
|
module_i2c_driver(onyx_driver);
|