/* * Apple Onboard Audio driver for Onyx codec * * Copyright 2006 Johannes Berg * * 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 #include #include MODULE_AUTHOR("Johannes Berg "); 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; icache[register_map[i]-FIRSTREGISTER]; } for (i=0; iinitialised = 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_create(struct i2c_adapter *adapter, struct device_node *node, int addr) { struct i2c_board_info info; struct i2c_client *client; memset(&info, 0, sizeof(struct i2c_board_info)); strlcpy(info.type, "aoa_codec_onyx", I2C_NAME_SIZE); info.addr = addr; info.platform_data = node; client = i2c_new_device(adapter, &info); if (!client) return -ENODEV; /* * We know the driver is already loaded, so the device should be * already bound. If not it means binding failed, which suggests * the device doesn't really exist and should be deleted. * Ideally this would be replaced by better checks _before_ * instantiating the device. */ if (!client->driver) { i2c_unregister_device(client); return -ENODEV; } /* * Let i2c-core delete that device on driver removal. * This is safe because i2c-core holds the core_lock mutex for us. */ list_add_tail(&client->detected, &client->driver->clients); return 0; } static int onyx_i2c_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct device_node *node = client->dev.platform_data; 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: i2c_set_clientdata(client, NULL); kfree(onyx); return -ENODEV; } static int onyx_i2c_attach(struct i2c_adapter *adapter) { struct device_node *busnode, *dev = NULL; struct pmac_i2c_bus *bus; bus = pmac_i2c_adapter_to_bus(adapter); if (bus == NULL) return -ENODEV; busnode = pmac_i2c_get_bus_node(bus); while ((dev = of_get_next_child(busnode, dev)) != NULL) { if (of_device_is_compatible(dev, "pcm3052")) { const u32 *addr; printk(KERN_DEBUG PFX "found pcm3052\n"); addr = of_get_property(dev, "reg", NULL); if (!addr) return -ENODEV; return onyx_create(adapter, dev, (*addr)>>1); } } /* if that didn't work, try desperate mode for older * machines that have stuff missing from the device tree */ if (!of_device_is_compatible(busnode, "k2-i2c")) return -ENODEV; printk(KERN_DEBUG PFX "found k2-i2c, checking if onyx chip is on it\n"); /* probe both possible addresses for the onyx chip */ if (onyx_create(adapter, NULL, 0x46) == 0) return 0; return onyx_create(adapter, NULL, 0x47); } 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); if (onyx->codec_info) kfree(onyx->codec_info); kfree(onyx); return 0; } static const struct i2c_device_id onyx_i2c_id[] = { { "aoa_codec_onyx", 0 }, { } }; static struct i2c_driver onyx_driver = { .driver = { .name = "aoa_codec_onyx", .owner = THIS_MODULE, }, .attach_adapter = onyx_i2c_attach, .probe = onyx_i2c_probe, .remove = onyx_i2c_remove, .id_table = onyx_i2c_id, }; static int __init onyx_init(void) { return i2c_add_driver(&onyx_driver); } static void __exit onyx_exit(void) { i2c_del_driver(&onyx_driver); } module_init(onyx_init); module_exit(onyx_exit);