⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 snd-aoa-codec-onyx.c

📁 linux 内核源代码
💻 C
📖 第 1 页 / 共 2 页
字号:
/* * 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 + -