| // 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/of.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" }, |
| { } |
| }; |
| 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); |