📄 snd-aoa-codec-onyx.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>MODULE_AUTHOR("Johannes Berg <johannes@sipsolutions.net>");MODULE_LICENSE("GPL");MODULE_DESCRIPTION("pcm3052 (onyx) codec driver for snd-aoa");#include "snd-aoa-codec-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) 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 128static 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; 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 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; 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 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 char *texts[] = { "Line-In", "Microphone" }; uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; uinfo->count = 1; uinfo->value.enumerated.items = 2; if (uinfo->value.enumerated.item > 1) uinfo->value.enumerated.item = 1; strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]); return 0;}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){ onyx_set_capture_source(snd_kcontrol_chip(kcontrol), ucontrol->value.enumerated.item[0]); return 1;}static 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_infostatic 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 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 2static 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 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 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};
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -