mirror of https://github.com/Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1058 lines
27 KiB
1058 lines
27 KiB
// SPDX-License-Identifier: GPL-2.0-only |
|
/* |
|
* Apple Onboard Audio driver for Onyx codec |
|
* |
|
* Copyright 2006 Johannes Berg <[email protected]> |
|
* |
|
* 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 <[email protected]>"); |
|
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 const 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 const 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 const 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 const 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; |
|
} |
|
|
|
strscpy(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);
|
|
|