📄 snd-aoa-codec-tas.c
字号:
/* * Apple Onboard Audio driver for tas codec * * Copyright 2006 Johannes Berg <johannes@sipsolutions.net> * * GPL v2, can be found in COPYING. * * Open questions: * - How to distinguish between 3004 and versions? * * FIXMEs: * - This codec driver doesn't honour the 'connected' * property of the aoa_codec struct, hence if * it is used in machines where not everything is * connected it will display wrong mixer elements. * - Driver assumes that the microphone is always * monaureal and connected to the right channel of * the input. This should also be a codec-dependent * flag, maybe the codec should have 3 different * bits for the three different possibilities how * it can be hooked up... * But as long as I don't see any hardware hooked * up that way... * - As Apple notes in their code, the tas3004 seems * to delay the right channel by one sample. You can * see this when for example recording stereo in * audacity, or recording the tas output via cable * on another machine (use a sinus generator or so). * I tried programming the BiQuads but couldn't * make the delay work, maybe someone can read the * datasheet and fix it. The relevant Apple comment * is in AppleTAS3004Audio.cpp lines 1637 ff. Note * that their comment describing how they program * the filters sucks... * * Other things: * - this should actually register *two* aoa_codec * structs since it has two inputs. Then it must * use the prepare callback to forbid running the * secondary output on a different clock. * Also, whatever bus knows how to do this must * provide two soundbus_dev devices and the fabric * must be able to link them correctly. * * I don't even know if Apple ever uses the second * port on the tas3004 though, I don't think their * i2s controllers can even do it. OTOH, they all * derive the clocks from common clocks, so it * might just be possible. The framework allows the * codec to refine the transfer_info items in the * usable callback, so we can simply remove the * rates the second instance is not using when it * actually is in use. * Maybe we'll need to make the sound busses have * a 'clock group id' value so the codec can * determine if the two outputs can be driven at * the same time. But that is likely overkill, up * to the fabric to not link them up incorrectly, * and up to the hardware designer to not wire * them up in some weird unusable way. */#include <stddef.h>#include <linux/i2c.h>#include <asm/pmac_low_i2c.h>#include <asm/prom.h>#include <linux/delay.h>#include <linux/module.h>#include <linux/mutex.h>MODULE_AUTHOR("Johannes Berg <johannes@sipsolutions.net>");MODULE_LICENSE("GPL");MODULE_DESCRIPTION("tas codec driver for snd-aoa");#include "snd-aoa-codec-tas.h"#include "snd-aoa-codec-tas-gain-table.h"#include "snd-aoa-codec-tas-basstreble.h"#include "../aoa.h"#include "../soundbus/soundbus.h"#define PFX "snd-aoa-codec-tas: "struct tas { struct aoa_codec codec; struct i2c_client i2c; u32 mute_l:1, mute_r:1 , controls_created:1 , drc_enabled:1, hw_enabled:1; u8 cached_volume_l, cached_volume_r; u8 mixer_l[3], mixer_r[3]; u8 bass, treble; u8 acr; int drc_range; /* protects hardware access against concurrency from * userspace when hitting controls and during * codec init/suspend/resume */ struct mutex mtx;};static int tas_reset_init(struct tas *tas);static struct tas *codec_to_tas(struct aoa_codec *codec){ return container_of(codec, struct tas, codec);}static inline int tas_write_reg(struct tas *tas, u8 reg, u8 len, u8 *data){ if (len == 1) return i2c_smbus_write_byte_data(&tas->i2c, reg, *data); else return i2c_smbus_write_i2c_block_data(&tas->i2c, reg, len, data);}static void tas3004_set_drc(struct tas *tas){ unsigned char val[6]; if (tas->drc_enabled) val[0] = 0x50; /* 3:1 above threshold */ else val[0] = 0x51; /* disabled */ val[1] = 0x02; /* 1:1 below threshold */ if (tas->drc_range > 0xef) val[2] = 0xef; else if (tas->drc_range < 0) val[2] = 0x00; else val[2] = tas->drc_range; val[3] = 0xb0; val[4] = 0x60; val[5] = 0xa0; tas_write_reg(tas, TAS_REG_DRC, 6, val);}static void tas_set_treble(struct tas *tas){ u8 tmp; tmp = tas3004_treble(tas->treble); tas_write_reg(tas, TAS_REG_TREBLE, 1, &tmp);}static void tas_set_bass(struct tas *tas){ u8 tmp; tmp = tas3004_bass(tas->bass); tas_write_reg(tas, TAS_REG_BASS, 1, &tmp);}static void tas_set_volume(struct tas *tas){ u8 block[6]; int tmp; u8 left, right; left = tas->cached_volume_l; right = tas->cached_volume_r; if (left > 177) left = 177; if (right > 177) right = 177; if (tas->mute_l) left = 0; if (tas->mute_r) right = 0; /* analysing the volume and mixer tables shows * that they are similar enough when we shift * the mixer table down by 4 bits. The error * is miniscule, in just one item the error * is 1, at a value of 0x07f17b (mixer table * value is 0x07f17a) */ tmp = tas_gaintable[left]; block[0] = tmp>>20; block[1] = tmp>>12; block[2] = tmp>>4; tmp = tas_gaintable[right]; block[3] = tmp>>20; block[4] = tmp>>12; block[5] = tmp>>4; tas_write_reg(tas, TAS_REG_VOL, 6, block);}static void tas_set_mixer(struct tas *tas){ u8 block[9]; int tmp, i; u8 val; for (i=0;i<3;i++) { val = tas->mixer_l[i]; if (val > 177) val = 177; tmp = tas_gaintable[val]; block[3*i+0] = tmp>>16; block[3*i+1] = tmp>>8; block[3*i+2] = tmp; } tas_write_reg(tas, TAS_REG_LMIX, 9, block); for (i=0;i<3;i++) { val = tas->mixer_r[i]; if (val > 177) val = 177; tmp = tas_gaintable[val]; block[3*i+0] = tmp>>16; block[3*i+1] = tmp>>8; block[3*i+2] = tmp; } tas_write_reg(tas, TAS_REG_RMIX, 9, block);}/* alsa stuff */static int tas_dev_register(struct snd_device *dev){ return 0;}static struct snd_device_ops ops = { .dev_register = tas_dev_register,};static int tas_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 = 0; uinfo->value.integer.max = 177; return 0;}static int tas_snd_vol_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol){ struct tas *tas = snd_kcontrol_chip(kcontrol); mutex_lock(&tas->mtx); ucontrol->value.integer.value[0] = tas->cached_volume_l; ucontrol->value.integer.value[1] = tas->cached_volume_r; mutex_unlock(&tas->mtx); return 0;}static int tas_snd_vol_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol){ struct tas *tas = snd_kcontrol_chip(kcontrol); mutex_lock(&tas->mtx); if (tas->cached_volume_l == ucontrol->value.integer.value[0] && tas->cached_volume_r == ucontrol->value.integer.value[1]) { mutex_unlock(&tas->mtx); return 0; } tas->cached_volume_l = ucontrol->value.integer.value[0]; tas->cached_volume_r = ucontrol->value.integer.value[1]; if (tas->hw_enabled) tas_set_volume(tas); mutex_unlock(&tas->mtx); 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 = tas_snd_vol_info, .get = tas_snd_vol_get, .put = tas_snd_vol_put,};#define tas_snd_mute_info snd_ctl_boolean_stereo_infostatic int tas_snd_mute_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol){ struct tas *tas = snd_kcontrol_chip(kcontrol); mutex_lock(&tas->mtx); ucontrol->value.integer.value[0] = !tas->mute_l; ucontrol->value.integer.value[1] = !tas->mute_r; mutex_unlock(&tas->mtx); return 0;}static int tas_snd_mute_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol){ struct tas *tas = snd_kcontrol_chip(kcontrol); mutex_lock(&tas->mtx); if (tas->mute_l == !ucontrol->value.integer.value[0] && tas->mute_r == !ucontrol->value.integer.value[1]) { mutex_unlock(&tas->mtx); return 0; } tas->mute_l = !ucontrol->value.integer.value[0]; tas->mute_r = !ucontrol->value.integer.value[1]; if (tas->hw_enabled) tas_set_volume(tas); mutex_unlock(&tas->mtx); return 1;}static struct snd_kcontrol_new mute_control = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Master Playback Switch", .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, .info = tas_snd_mute_info, .get = tas_snd_mute_get, .put = tas_snd_mute_put,};static int tas_snd_mixer_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 = 0; uinfo->value.integer.max = 177; return 0;}static int tas_snd_mixer_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol){ struct tas *tas = snd_kcontrol_chip(kcontrol); int idx = kcontrol->private_value; mutex_lock(&tas->mtx); ucontrol->value.integer.value[0] = tas->mixer_l[idx]; ucontrol->value.integer.value[1] = tas->mixer_r[idx]; mutex_unlock(&tas->mtx); return 0;}static int tas_snd_mixer_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol){ struct tas *tas = snd_kcontrol_chip(kcontrol); int idx = kcontrol->private_value; mutex_lock(&tas->mtx); if (tas->mixer_l[idx] == ucontrol->value.integer.value[0] && tas->mixer_r[idx] == ucontrol->value.integer.value[1]) { mutex_unlock(&tas->mtx); return 0; } tas->mixer_l[idx] = ucontrol->value.integer.value[0]; tas->mixer_r[idx] = ucontrol->value.integer.value[1]; if (tas->hw_enabled) tas_set_mixer(tas); mutex_unlock(&tas->mtx); return 1;}#define MIXER_CONTROL(n,descr,idx) \static struct snd_kcontrol_new n##_control = { \ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ .name = descr " Playback Volume", \ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, \ .info = tas_snd_mixer_info, \ .get = tas_snd_mixer_get, \ .put = tas_snd_mixer_put, \ .private_value = idx, \}MIXER_CONTROL(pcm1, "PCM", 0);MIXER_CONTROL(monitor, "Monitor", 2);static int tas_snd_drc_range_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 = 0; uinfo->value.integer.max = TAS3004_DRC_MAX; return 0;}static int tas_snd_drc_range_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol){ struct tas *tas = snd_kcontrol_chip(kcontrol); mutex_lock(&tas->mtx); ucontrol->value.integer.value[0] = tas->drc_range; mutex_unlock(&tas->mtx); return 0;}static int tas_snd_drc_range_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol){ struct tas *tas = snd_kcontrol_chip(kcontrol); mutex_lock(&tas->mtx); if (tas->drc_range == ucontrol->value.integer.value[0]) { mutex_unlock(&tas->mtx); return 0; } tas->drc_range = ucontrol->value.integer.value[0]; if (tas->hw_enabled) tas3004_set_drc(tas); mutex_unlock(&tas->mtx); return 1;}static struct snd_kcontrol_new drc_range_control = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "DRC Range", .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, .info = tas_snd_drc_range_info, .get = tas_snd_drc_range_get, .put = tas_snd_drc_range_put,};#define tas_snd_drc_switch_info snd_ctl_boolean_mono_infostatic int tas_snd_drc_switch_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol){ struct tas *tas = snd_kcontrol_chip(kcontrol); mutex_lock(&tas->mtx); ucontrol->value.integer.value[0] = tas->drc_enabled; mutex_unlock(&tas->mtx); return 0;}static int tas_snd_drc_switch_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol){ struct tas *tas = snd_kcontrol_chip(kcontrol); mutex_lock(&tas->mtx); if (tas->drc_enabled == ucontrol->value.integer.value[0]) { mutex_unlock(&tas->mtx); return 0; } tas->drc_enabled = ucontrol->value.integer.value[0]; if (tas->hw_enabled) tas3004_set_drc(tas); mutex_unlock(&tas->mtx); return 1;}static struct snd_kcontrol_new drc_switch_control = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "DRC Range Switch", .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, .info = tas_snd_drc_switch_info, .get = tas_snd_drc_switch_get, .put = tas_snd_drc_switch_put,};static int tas_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 tas_snd_capture_source_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol){ struct tas *tas = snd_kcontrol_chip(kcontrol); mutex_lock(&tas->mtx); ucontrol->value.enumerated.item[0] = !!(tas->acr & TAS_ACR_INPUT_B); mutex_unlock(&tas->mtx); return 0;}static int tas_snd_capture_source_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol){ struct tas *tas = snd_kcontrol_chip(kcontrol); int oldacr; mutex_lock(&tas->mtx); oldacr = tas->acr;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -