// SPDX-License-Identifier: GPL-2.0-only /* * Apple Onboard Audio driver for Onyx codec * * Copyright 2006 Johannes Berg <johannes@sipsolutions.net> * * This is a driver for the pcm3052 codec chip (codenamed Onyx) * that is present in newer Apple hardware (with digital output). * * The Onyx codec has the following connections (listed by the bit * to be used in aoa_codec.connected): * 0: analog output * 1: digital output * 2: line input * 3: microphone input * Note that even though I know of no machine that has for example * the digital output connected but not the analog, I have handled * all the different cases in the code so that this driver may serve * as a good example of what to do. * * NOTE: This driver assumes that there's at most one chip to be * used with one alsa card, in form of creating all kinds * of mixer elements without regard for their existence. * But snd-aoa assumes that there's at most one card, so * this means you can only have one onyx on a system. This * should probably be fixed by changing the assumption of * having just a single card on a system, and making the * 'card' pointer accessible to anyone who needs it instead * of hiding it in the aoa_snd_* functions... */ #include <linux/delay.h> #include <linux/module.h> #include <linux/slab.h> MODULE_AUTHOR("Johannes Berg <johannes@sipsolutions.net>"); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("pcm3052 (onyx) codec driver for snd-aoa"); #include "onyx.h" #include "../aoa.h" #include "../soundbus/soundbus.h" #define PFX "snd-aoa-codec-onyx: " struct onyx { /* cache registers 65 to 80, they are write-only! */ u8 cache[16]; struct i2c_client *i2c; struct aoa_codec codec; u32 initialised:1, spdif_locked:1, analog_locked:1, original_mute:2; int open_count; struct codec_info *codec_info; /* mutex serializes concurrent access to the device * and this structure. */ struct mutex mutex; }; #define codec_to_onyx(c) container_of(c, struct onyx, codec) /* both return 0 if all ok, else on error */ static int onyx_read_register(struct onyx *onyx, u8 reg, u8 *value) { s32 v; if (reg != ONYX_REG_CONTROL) { *value = onyx->cache[reg-FIRSTREGISTER]; return 0; } v = i2c_smbus_read_byte_data(onyx->i2c, reg); if (v < 0) { *value = 0; return -1; } *value = (u8)v; onyx->cache[ONYX_REG_CONTROL-FIRSTREGISTER] = *value; return 0; } static int onyx_write_register(struct onyx *onyx, u8 reg, u8 value) { int result; result = i2c_smbus_write_byte_data(onyx->i2c, reg, value); if (!result) onyx->cache[reg-FIRSTREGISTER] = value; return result; } /* alsa stuff */ static int onyx_dev_register(struct snd_device *dev) { return 0; } static 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) { 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 void 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); } 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);