📄 hda_codec.c
字号:
* read widget caps for each widget and store in cache */static int read_widget_caps(struct hda_codec *codec, hda_nid_t fg_node){ int i; hda_nid_t nid; codec->num_nodes = snd_hda_get_sub_nodes(codec, fg_node, &codec->start_nid); codec->wcaps = kmalloc(codec->num_nodes * 4, GFP_KERNEL); if (!codec->wcaps) return -ENOMEM; nid = codec->start_nid; for (i = 0; i < codec->num_nodes; i++, nid++) codec->wcaps[i] = snd_hda_param_read(codec, nid, AC_PAR_AUDIO_WIDGET_CAP); return 0;}static void init_hda_cache(struct hda_cache_rec *cache, unsigned int record_size);static void free_hda_cache(struct hda_cache_rec *cache);/* * codec destructor */static void snd_hda_codec_free(struct hda_codec *codec){ if (!codec) return;#ifdef CONFIG_SND_HDA_POWER_SAVE cancel_delayed_work(&codec->power_work); flush_scheduled_work();#endif list_del(&codec->list); codec->bus->caddr_tbl[codec->addr] = NULL; if (codec->patch_ops.free) codec->patch_ops.free(codec); free_hda_cache(&codec->amp_cache); free_hda_cache(&codec->cmd_cache); kfree(codec->wcaps); kfree(codec);}/** * snd_hda_codec_new - create a HDA codec * @bus: the bus to assign * @codec_addr: the codec address * @codecp: the pointer to store the generated codec * * Returns 0 if successful, or a negative error code. */int __devinit snd_hda_codec_new(struct hda_bus *bus, unsigned int codec_addr, struct hda_codec **codecp){ struct hda_codec *codec; char component[13]; int err; snd_assert(bus, return -EINVAL); snd_assert(codec_addr <= HDA_MAX_CODEC_ADDRESS, return -EINVAL); if (bus->caddr_tbl[codec_addr]) { snd_printk(KERN_ERR "hda_codec: " "address 0x%x is already occupied\n", codec_addr); return -EBUSY; } codec = kzalloc(sizeof(*codec), GFP_KERNEL); if (codec == NULL) { snd_printk(KERN_ERR "can't allocate struct hda_codec\n"); return -ENOMEM; } codec->bus = bus; codec->addr = codec_addr; mutex_init(&codec->spdif_mutex); init_hda_cache(&codec->amp_cache, sizeof(struct hda_amp_info)); init_hda_cache(&codec->cmd_cache, sizeof(struct hda_cache_head));#ifdef CONFIG_SND_HDA_POWER_SAVE INIT_DELAYED_WORK(&codec->power_work, hda_power_work); /* snd_hda_codec_new() marks the codec as power-up, and leave it as is. * the caller has to power down appropriatley after initialization * phase. */ hda_keep_power_on(codec);#endif list_add_tail(&codec->list, &bus->codec_list); bus->caddr_tbl[codec_addr] = codec; codec->vendor_id = snd_hda_param_read(codec, AC_NODE_ROOT, AC_PAR_VENDOR_ID); if (codec->vendor_id == -1) /* read again, hopefully the access method was corrected * in the last read... */ codec->vendor_id = snd_hda_param_read(codec, AC_NODE_ROOT, AC_PAR_VENDOR_ID); codec->subsystem_id = snd_hda_param_read(codec, AC_NODE_ROOT, AC_PAR_SUBSYSTEM_ID); codec->revision_id = snd_hda_param_read(codec, AC_NODE_ROOT, AC_PAR_REV_ID); setup_fg_nodes(codec); if (!codec->afg && !codec->mfg) { snd_printdd("hda_codec: no AFG or MFG node found\n"); snd_hda_codec_free(codec); return -ENODEV; } if (read_widget_caps(codec, codec->afg ? codec->afg : codec->mfg) < 0) { snd_printk(KERN_ERR "hda_codec: cannot malloc\n"); snd_hda_codec_free(codec); return -ENOMEM; } if (!codec->subsystem_id) { hda_nid_t nid = codec->afg ? codec->afg : codec->mfg; codec->subsystem_id = snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_SUBSYSTEM_ID, 0); } codec->preset = find_codec_preset(codec); /* audio codec should override the mixer name */ if (codec->afg || !*bus->card->mixername) snd_hda_get_codec_name(codec, bus->card->mixername, sizeof(bus->card->mixername)); if (is_generic_config(codec)) { err = snd_hda_parse_generic_codec(codec); goto patched; } if (codec->preset && codec->preset->patch) { err = codec->preset->patch(codec); goto patched; } /* call the default parser */ err = snd_hda_parse_generic_codec(codec); if (err < 0) printk(KERN_ERR "hda-codec: No codec parser is available\n"); patched: if (err < 0) { snd_hda_codec_free(codec); return err; } if (codec->patch_ops.unsol_event) init_unsol_queue(bus); snd_hda_codec_proc_new(codec);#ifdef CONFIG_SND_HDA_HWDEP snd_hda_create_hwdep(codec);#endif sprintf(component, "HDA:%08x", codec->vendor_id); snd_component_add(codec->bus->card, component); if (codecp) *codecp = codec; return 0;}/** * snd_hda_codec_setup_stream - set up the codec for streaming * @codec: the CODEC to set up * @nid: the NID to set up * @stream_tag: stream tag to pass, it's between 0x1 and 0xf. * @channel_id: channel id to pass, zero based. * @format: stream format. */void snd_hda_codec_setup_stream(struct hda_codec *codec, hda_nid_t nid, u32 stream_tag, int channel_id, int format){ if (!nid) return; snd_printdd("hda_codec_setup_stream: " "NID=0x%x, stream=0x%x, channel=%d, format=0x%x\n", nid, stream_tag, channel_id, format); snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_CHANNEL_STREAMID, (stream_tag << 4) | channel_id); msleep(1); snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_STREAM_FORMAT, format);}/* * amp access functions *//* FIXME: more better hash key? */#define HDA_HASH_KEY(nid,dir,idx) (u32)((nid) + ((idx) << 16) + ((dir) << 24))#define INFO_AMP_CAPS (1<<0)#define INFO_AMP_VOL(ch) (1 << (1 + (ch)))/* initialize the hash table */static void __devinit init_hda_cache(struct hda_cache_rec *cache, unsigned int record_size){ memset(cache, 0, sizeof(*cache)); memset(cache->hash, 0xff, sizeof(cache->hash)); cache->record_size = record_size;}static void free_hda_cache(struct hda_cache_rec *cache){ kfree(cache->buffer);}/* query the hash. allocate an entry if not found. */static struct hda_cache_head *get_alloc_hash(struct hda_cache_rec *cache, u32 key){ u16 idx = key % (u16)ARRAY_SIZE(cache->hash); u16 cur = cache->hash[idx]; struct hda_cache_head *info; while (cur != 0xffff) { info = (struct hda_cache_head *)(cache->buffer + cur * cache->record_size); if (info->key == key) return info; cur = info->next; } /* add a new hash entry */ if (cache->num_entries >= cache->size) { /* reallocate the array */ unsigned int new_size = cache->size + 64; void *new_buffer; new_buffer = kcalloc(new_size, cache->record_size, GFP_KERNEL); if (!new_buffer) { snd_printk(KERN_ERR "hda_codec: " "can't malloc amp_info\n"); return NULL; } if (cache->buffer) { memcpy(new_buffer, cache->buffer, cache->size * cache->record_size); kfree(cache->buffer); } cache->size = new_size; cache->buffer = new_buffer; } cur = cache->num_entries++; info = (struct hda_cache_head *)(cache->buffer + cur * cache->record_size); info->key = key; info->val = 0; info->next = cache->hash[idx]; cache->hash[idx] = cur; return info;}/* query and allocate an amp hash entry */static inline struct hda_amp_info *get_alloc_amp_hash(struct hda_codec *codec, u32 key){ return (struct hda_amp_info *)get_alloc_hash(&codec->amp_cache, key);}/* * query AMP capabilities for the given widget and direction */static u32 query_amp_caps(struct hda_codec *codec, hda_nid_t nid, int direction){ struct hda_amp_info *info; info = get_alloc_amp_hash(codec, HDA_HASH_KEY(nid, direction, 0)); if (!info) return 0; if (!(info->head.val & INFO_AMP_CAPS)) { if (!(get_wcaps(codec, nid) & AC_WCAP_AMP_OVRD)) nid = codec->afg; info->amp_caps = snd_hda_param_read(codec, nid, direction == HDA_OUTPUT ? AC_PAR_AMP_OUT_CAP : AC_PAR_AMP_IN_CAP); if (info->amp_caps) info->head.val |= INFO_AMP_CAPS; } return info->amp_caps;}int snd_hda_override_amp_caps(struct hda_codec *codec, hda_nid_t nid, int dir, unsigned int caps){ struct hda_amp_info *info; info = get_alloc_amp_hash(codec, HDA_HASH_KEY(nid, dir, 0)); if (!info) return -EINVAL; info->amp_caps = caps; info->head.val |= INFO_AMP_CAPS; return 0;}/* * read the current volume to info * if the cache exists, read the cache value. */static unsigned int get_vol_mute(struct hda_codec *codec, struct hda_amp_info *info, hda_nid_t nid, int ch, int direction, int index){ u32 val, parm; if (info->head.val & INFO_AMP_VOL(ch)) return info->vol[ch]; parm = ch ? AC_AMP_GET_RIGHT : AC_AMP_GET_LEFT; parm |= direction == HDA_OUTPUT ? AC_AMP_GET_OUTPUT : AC_AMP_GET_INPUT; parm |= index; val = snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_AMP_GAIN_MUTE, parm); info->vol[ch] = val & 0xff; info->head.val |= INFO_AMP_VOL(ch); return info->vol[ch];}/* * write the current volume in info to the h/w and update the cache */static void put_vol_mute(struct hda_codec *codec, struct hda_amp_info *info, hda_nid_t nid, int ch, int direction, int index, int val){ u32 parm; parm = ch ? AC_AMP_SET_RIGHT : AC_AMP_SET_LEFT; parm |= direction == HDA_OUTPUT ? AC_AMP_SET_OUTPUT : AC_AMP_SET_INPUT; parm |= index << AC_AMP_SET_INDEX_SHIFT; parm |= val; snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_AMP_GAIN_MUTE, parm); info->vol[ch] = val;}/* * read AMP value. The volume is between 0 to 0x7f, 0x80 = mute bit. */int snd_hda_codec_amp_read(struct hda_codec *codec, hda_nid_t nid, int ch, int direction, int index){ struct hda_amp_info *info; info = get_alloc_amp_hash(codec, HDA_HASH_KEY(nid, direction, index)); if (!info) return 0; return get_vol_mute(codec, info, nid, ch, direction, index);}/* * update the AMP value, mask = bit mask to set, val = the value */int snd_hda_codec_amp_update(struct hda_codec *codec, hda_nid_t nid, int ch, int direction, int idx, int mask, int val){ struct hda_amp_info *info; info = get_alloc_amp_hash(codec, HDA_HASH_KEY(nid, direction, idx)); if (!info) return 0; val &= mask; val |= get_vol_mute(codec, info, nid, ch, direction, idx) & ~mask; if (info->vol[ch] == val) return 0; put_vol_mute(codec, info, nid, ch, direction, idx, val); return 1;}/* * update the AMP stereo with the same mask and value */int snd_hda_codec_amp_stereo(struct hda_codec *codec, hda_nid_t nid, int direction, int idx, int mask, int val){ int ch, ret = 0; for (ch = 0; ch < 2; ch++) ret |= snd_hda_codec_amp_update(codec, nid, ch, direction, idx, mask, val); return ret;}#ifdef SND_HDA_NEEDS_RESUME/* resume the all amp commands from the cache */void snd_hda_codec_resume_amp(struct hda_codec *codec){ struct hda_amp_info *buffer = codec->amp_cache.buffer; int i; for (i = 0; i < codec->amp_cache.size; i++, buffer++) { u32 key = buffer->head.key; hda_nid_t nid; unsigned int idx, dir, ch; if (!key) continue; nid = key & 0xff; idx = (key >> 16) & 0xff; dir = (key >> 24) & 0xff; for (ch = 0; ch < 2; ch++) { if (!(buffer->head.val & INFO_AMP_VOL(ch))) continue; put_vol_mute(codec, buffer, nid, ch, dir, idx, buffer->vol[ch]); } }}#endif /* SND_HDA_NEEDS_RESUME *//* * AMP control callbacks *//* retrieve parameters from private_value */#define get_amp_nid(kc) ((kc)->private_value & 0xffff)#define get_amp_channels(kc) (((kc)->private_value >> 16) & 0x3)#define get_amp_direction(kc) (((kc)->private_value >> 18) & 0x1)#define get_amp_index(kc) (((kc)->private_value >> 19) & 0xf)/* volume */int snd_hda_mixer_amp_volume_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo){ struct hda_codec *codec = snd_kcontrol_chip(kcontrol); u16 nid = get_amp_nid(kcontrol); u8 chs = get_amp_channels(kcontrol); int dir = get_amp_direction(kcontrol); u32 caps; caps = query_amp_caps(codec, nid, dir); /* num steps */ caps = (caps & AC_AMPCAP_NUM_STEPS) >> AC_AMPCAP_NUM_STEPS_SHIFT; if (!caps) { printk(KERN_WARNING "hda_codec: " "num_steps = 0 for NID=0x%x\n", nid); return -EINVAL; } uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; uinfo->count = chs == 3 ? 2 : 1; uinfo->value.integer.min = 0; uinfo->value.integer.max = caps; return 0;}int snd_hda_mixer_amp_volume_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol){ struct hda_codec *codec = snd_kcontrol_chip(kcontrol); hda_nid_t nid = get_amp_nid(kcontrol); int chs = get_amp_channels(kcontrol); int dir = get_amp_direction(kcontrol); int idx = get_amp_index(kcontrol); long *valp = ucontrol->value.integer.value; if (chs & 1) *valp++ = snd_hda_codec_amp_read(codec, nid, 0, dir, idx) & HDA_AMP_VOLMASK; if (chs & 2) *valp = snd_hda_codec_amp_read(codec, nid, 1, dir, idx) & HDA_AMP_VOLMASK; return 0;}int snd_hda_mixer_amp_volume_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol){ struct hda_codec *codec = snd_kcontrol_chip(kcontrol); hda_nid_t nid = get_amp_nid(kcontrol); int chs = get_amp_channels(kcontrol); int dir = get_amp_direction(kcontrol); int idx = get_amp_index(kcontrol); long *valp = ucontrol->value.integer.value; int change = 0; snd_hda_power_up(codec); if (chs & 1) { change = snd_hda_codec_amp_update(codec, nid, 0, dir, idx, 0x7f, *valp); valp++; } if (chs & 2) change |= snd_hda_codec_amp_update(codec, nid, 1, dir, idx, 0x7f, *valp); snd_hda_power_down(codec); return change;}int snd_hda_mixer_amp_tlv(struct snd_kcontrol *kcontrol, int op_flag, unsigned int size, unsigned int __user *_tlv){ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -