📄 hda_codec.c
字号:
else if (maxbps >= 24) val |= 0x30; else val |= 0x20; break; default: snd_printdd("invalid format width %d\n", snd_pcm_format_width(format)); return 0; } return val;}/** * snd_hda_query_supported_pcm - query the supported PCM rates and formats * @codec: the HDA codec * @nid: NID to query * @ratesp: the pointer to store the detected rate bitflags * @formatsp: the pointer to store the detected formats * @bpsp: the pointer to store the detected format widths * * Queries the supported PCM rates and formats. The NULL @ratesp, @formatsp * or @bsps argument is ignored. * * Returns 0 if successful, otherwise a negative error code. */int snd_hda_query_supported_pcm(struct hda_codec *codec, hda_nid_t nid, u32 *ratesp, u64 *formatsp, unsigned int *bpsp){ int i; unsigned int val, streams; val = 0; if (nid != codec->afg && (get_wcaps(codec, nid) & AC_WCAP_FORMAT_OVRD)) { val = snd_hda_param_read(codec, nid, AC_PAR_PCM); if (val == -1) return -EIO; } if (! val) val = snd_hda_param_read(codec, codec->afg, AC_PAR_PCM); if (ratesp) { u32 rates = 0; for (i = 0; rate_bits[i].hz; i++) { if (val & (1 << i)) rates |= rate_bits[i].alsa_bits; } *ratesp = rates; } if (formatsp || bpsp) { u64 formats = 0; unsigned int bps; unsigned int wcaps; wcaps = get_wcaps(codec, nid); streams = snd_hda_param_read(codec, nid, AC_PAR_STREAM); if (streams == -1) return -EIO; if (! streams) { streams = snd_hda_param_read(codec, codec->afg, AC_PAR_STREAM); if (streams == -1) return -EIO; } bps = 0; if (streams & AC_SUPFMT_PCM) { if (val & AC_SUPPCM_BITS_8) { formats |= SNDRV_PCM_FMTBIT_U8; bps = 8; } if (val & AC_SUPPCM_BITS_16) { formats |= SNDRV_PCM_FMTBIT_S16_LE; bps = 16; } if (wcaps & AC_WCAP_DIGITAL) { if (val & AC_SUPPCM_BITS_32) formats |= SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE; if (val & (AC_SUPPCM_BITS_20|AC_SUPPCM_BITS_24)) formats |= SNDRV_PCM_FMTBIT_S32_LE; if (val & AC_SUPPCM_BITS_24) bps = 24; else if (val & AC_SUPPCM_BITS_20) bps = 20; } else if (val & (AC_SUPPCM_BITS_20|AC_SUPPCM_BITS_24|AC_SUPPCM_BITS_32)) { formats |= SNDRV_PCM_FMTBIT_S32_LE; if (val & AC_SUPPCM_BITS_32) bps = 32; else if (val & AC_SUPPCM_BITS_20) bps = 20; else if (val & AC_SUPPCM_BITS_24) bps = 24; } } else if (streams == AC_SUPFMT_FLOAT32) { /* should be exclusive */ formats |= SNDRV_PCM_FMTBIT_FLOAT_LE; bps = 32; } else if (streams == AC_SUPFMT_AC3) { /* should be exclusive */ /* temporary hack: we have still no proper support * for the direct AC3 stream... */ formats |= SNDRV_PCM_FMTBIT_U8; bps = 8; } if (formatsp) *formatsp = formats; if (bpsp) *bpsp = bps; } return 0;}/** * snd_hda_is_supported_format - check whether the given node supports the format val * * Returns 1 if supported, 0 if not. */int snd_hda_is_supported_format(struct hda_codec *codec, hda_nid_t nid, unsigned int format){ int i; unsigned int val = 0, rate, stream; if (nid != codec->afg && (get_wcaps(codec, nid) & AC_WCAP_FORMAT_OVRD)) { val = snd_hda_param_read(codec, nid, AC_PAR_PCM); if (val == -1) return 0; } if (! val) { val = snd_hda_param_read(codec, codec->afg, AC_PAR_PCM); if (val == -1) return 0; } rate = format & 0xff00; for (i = 0; rate_bits[i].hz; i++) if (rate_bits[i].hda_fmt == rate) { if (val & (1 << i)) break; return 0; } if (! rate_bits[i].hz) return 0; stream = snd_hda_param_read(codec, nid, AC_PAR_STREAM); if (stream == -1) return 0; if (! stream && nid != codec->afg) stream = snd_hda_param_read(codec, codec->afg, AC_PAR_STREAM); if (! stream || stream == -1) return 0; if (stream & AC_SUPFMT_PCM) { switch (format & 0xf0) { case 0x00: if (! (val & AC_SUPPCM_BITS_8)) return 0; break; case 0x10: if (! (val & AC_SUPPCM_BITS_16)) return 0; break; case 0x20: if (! (val & AC_SUPPCM_BITS_20)) return 0; break; case 0x30: if (! (val & AC_SUPPCM_BITS_24)) return 0; break; case 0x40: if (! (val & AC_SUPPCM_BITS_32)) return 0; break; default: return 0; } } else { /* FIXME: check for float32 and AC3? */ } return 1;}/* * PCM stuff */static int hda_pcm_default_open_close(struct hda_pcm_stream *hinfo, struct hda_codec *codec, struct snd_pcm_substream *substream){ return 0;}static int hda_pcm_default_prepare(struct hda_pcm_stream *hinfo, struct hda_codec *codec, unsigned int stream_tag, unsigned int format, struct snd_pcm_substream *substream){ snd_hda_codec_setup_stream(codec, hinfo->nid, stream_tag, 0, format); return 0;}static int hda_pcm_default_cleanup(struct hda_pcm_stream *hinfo, struct hda_codec *codec, struct snd_pcm_substream *substream){ snd_hda_codec_setup_stream(codec, hinfo->nid, 0, 0, 0); return 0;}static int set_pcm_default_values(struct hda_codec *codec, struct hda_pcm_stream *info){ if (info->nid) { /* query support PCM information from the given NID */ if (! info->rates || ! info->formats) snd_hda_query_supported_pcm(codec, info->nid, info->rates ? NULL : &info->rates, info->formats ? NULL : &info->formats, info->maxbps ? NULL : &info->maxbps); } if (info->ops.open == NULL) info->ops.open = hda_pcm_default_open_close; if (info->ops.close == NULL) info->ops.close = hda_pcm_default_open_close; if (info->ops.prepare == NULL) { snd_assert(info->nid, return -EINVAL); info->ops.prepare = hda_pcm_default_prepare; } if (info->ops.cleanup == NULL) { snd_assert(info->nid, return -EINVAL); info->ops.cleanup = hda_pcm_default_cleanup; } return 0;}/** * snd_hda_build_pcms - build PCM information * @bus: the BUS * * Create PCM information for each codec included in the bus. * * The build_pcms codec patch is requested to set up codec->num_pcms and * codec->pcm_info properly. The array is referred by the top-level driver * to create its PCM instances. * The allocated codec->pcm_info should be released in codec->patch_ops.free * callback. * * At least, substreams, channels_min and channels_max must be filled for * each stream. substreams = 0 indicates that the stream doesn't exist. * When rates and/or formats are zero, the supported values are queried * from the given nid. The nid is used also by the default ops.prepare * and ops.cleanup callbacks. * * The driver needs to call ops.open in its open callback. Similarly, * ops.close is supposed to be called in the close callback. * ops.prepare should be called in the prepare or hw_params callback * with the proper parameters for set up. * ops.cleanup should be called in hw_free for clean up of streams. * * This function returns 0 if successfull, or a negative error code. */int snd_hda_build_pcms(struct hda_bus *bus){ struct list_head *p; list_for_each(p, &bus->codec_list) { struct hda_codec *codec = list_entry(p, struct hda_codec, list); unsigned int pcm, s; int err; if (! codec->patch_ops.build_pcms) continue; err = codec->patch_ops.build_pcms(codec); if (err < 0) return err; for (pcm = 0; pcm < codec->num_pcms; pcm++) { for (s = 0; s < 2; s++) { struct hda_pcm_stream *info; info = &codec->pcm_info[pcm].stream[s]; if (! info->substreams) continue; err = set_pcm_default_values(codec, info); if (err < 0) return err; } } } return 0;}/** * snd_hda_check_board_config - compare the current codec with the config table * @codec: the HDA codec * @tbl: configuration table, terminated by null entries * * Compares the modelname or PCI subsystem id of the current codec with the * given configuration table. If a matching entry is found, returns its * config value (supposed to be 0 or positive). * * If no entries are matching, the function returns a negative value. */int snd_hda_check_board_config(struct hda_codec *codec, const struct hda_board_config *tbl){ const struct hda_board_config *c; if (codec->bus->modelname) { for (c = tbl; c->modelname || c->pci_subvendor; c++) { if (c->modelname && ! strcmp(codec->bus->modelname, c->modelname)) { snd_printd(KERN_INFO "hda_codec: model '%s' is selected\n", c->modelname); return c->config; } } } if (codec->bus->pci) { u16 subsystem_vendor, subsystem_device; pci_read_config_word(codec->bus->pci, PCI_SUBSYSTEM_VENDOR_ID, &subsystem_vendor); pci_read_config_word(codec->bus->pci, PCI_SUBSYSTEM_ID, &subsystem_device); for (c = tbl; c->modelname || c->pci_subvendor; c++) { if (c->pci_subvendor == subsystem_vendor && (! c->pci_subdevice /* all match */|| (c->pci_subdevice == subsystem_device))) { snd_printdd(KERN_INFO "hda_codec: PCI %x:%x, codec config %d is selected\n", subsystem_vendor, subsystem_device, c->config); return c->config; } } } return -1;}/** * snd_hda_add_new_ctls - create controls from the array * @codec: the HDA codec * @knew: the array of struct snd_kcontrol_new * * This helper function creates and add new controls in the given array. * The array must be terminated with an empty entry as terminator. * * Returns 0 if successful, or a negative error code. */int snd_hda_add_new_ctls(struct hda_codec *codec, struct snd_kcontrol_new *knew){ int err; for (; knew->name; knew++) { struct snd_kcontrol *kctl; kctl = snd_ctl_new1(knew, codec); if (! kctl) return -ENOMEM; err = snd_ctl_add(codec->bus->card, kctl); if (err < 0) { if (! codec->addr) return err; kctl = snd_ctl_new1(knew, codec); if (! kctl) return -ENOMEM; kctl->id.device = codec->addr; if ((err = snd_ctl_add(codec->bus->card, kctl)) < 0) return err; } } return 0;}/* * Channel mode helper */int snd_hda_ch_mode_info(struct hda_codec *codec, struct snd_ctl_elem_info *uinfo, const struct hda_channel_mode *chmode, int num_chmodes){ uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; uinfo->count = 1; uinfo->value.enumerated.items = num_chmodes; if (uinfo->value.enumerated.item >= num_chmodes) uinfo->value.enumerated.item = num_chmodes - 1; sprintf(uinfo->value.enumerated.name, "%dch", chmode[uinfo->value.enumerated.item].channels); return 0;}int snd_hda_ch_mode_get(struct hda_codec *codec, struct snd_ctl_elem_value *ucontrol, const struct hda_channel_mode *chmode, int num_chmodes, int max_channels){ int i; for (i = 0; i < num_chmodes; i++) { if (max_channels == chmode[i].channels) { ucontrol->value.enumerated.item[0] = i; break; } } return 0;}int snd_hda_ch_mode_put(struct hda_codec *codec, struct snd_ctl_elem_value *ucontrol, const struct hda_channel_mode *chmode, int num_chmodes, int *max_channelsp){ unsigned int mode; mode = ucontrol->value.enumerated.item[0]; snd_assert(mode < num_chmodes, return -EINVAL); if (*max_channelsp == chmode[mode].channels && ! codec->in_resume) return 0; /* change the current channel setting */ *max_channelsp = chmode[mode].channels; if (chmode[mode].sequence) snd_hda_sequence_write(codec, chmode[mode].sequence); return 1;}/* * input MUX helper */int snd_hda_input_mux_info(const struct hda_input_mux *imux, struct snd_ctl_elem_info *uinfo){ unsigned int index; uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; uinfo->count = 1; uinfo->value.enumerated.items = imux->num_items; index = uinfo->value.enumerated.item; if (index >= imux->num_items) index = imux->num_items - 1; strcpy(uinfo->value.enumerated.name, imux->items[index].label); return 0;}int snd_hda_input_mux_put(struct hda_codec *codec, const struct hda_input_mux *imux, struct snd_ctl_elem_value *ucontrol, hda_nid_t nid, unsigned int *cur_val){ unsigned int idx; idx = ucontrol->value.enumerated.item[0]; if (idx >= imux->num_items) idx = imux->num_items - 1; if (*cur_val == idx && ! codec->in_resume) return 0; snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_CONNECT_SEL, imux->items[idx].index); *cur_val = idx; return 1;}/* * Multi-channel / digital-out PCM helper functions
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -