|  | /* | 
|  | * 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) | 
|  | 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 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 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) | 
|  | { | 
|  | 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 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 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 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 | 
|  | }; | 
|  |  | 
|  | 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_LOWLEVEL, 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", | 
|  | .owner = THIS_MODULE, | 
|  | }, | 
|  | .probe = onyx_i2c_probe, | 
|  | .remove = onyx_i2c_remove, | 
|  | .id_table = onyx_i2c_id, | 
|  | }; | 
|  |  | 
|  | module_i2c_driver(onyx_driver); |