ALSA: hda - Allocate hda_pcm objects dynamically

So far, the hda_codec object kept the hda_pcm list in an array, and
the codec driver was expected to assign the array.  However, this
makes the object life cycle management harder, because the assigned
array is freed at the codec driver detach while it might be still
accessed by the opened streams.

In this patch, we allocate each hda_pcm object dynamically and manage
it as a linked list.  Each object has a kref refcount, and both the
codec driver binder and the PCM open/close touches it, so that the
object won't be freed while in use.

Signed-off-by: Takashi Iwai <tiwai@suse.de>
This commit is contained in:
Takashi Iwai 2015-02-27 17:43:19 +01:00
parent f4de8fe6cf
commit bbbc7e8502
10 changed files with 145 additions and 103 deletions

View File

@ -1116,6 +1116,60 @@ get_hda_cvt_setup(struct hda_codec *codec, hda_nid_t nid)
return p;
}
/*
* PCM device
*/
static void release_pcm(struct kref *kref)
{
struct hda_pcm *pcm = container_of(kref, struct hda_pcm, kref);
if (pcm->pcm)
snd_device_free(pcm->codec->card, pcm->pcm);
clear_bit(pcm->device, pcm->codec->bus->pcm_dev_bits);
kfree(pcm->name);
kfree(pcm);
}
void snd_hda_codec_pcm_put(struct hda_pcm *pcm)
{
kref_put(&pcm->kref, release_pcm);
}
EXPORT_SYMBOL_GPL(snd_hda_codec_pcm_put);
struct hda_pcm *snd_hda_codec_pcm_new(struct hda_codec *codec,
const char *fmt, ...)
{
struct hda_pcm *pcm;
va_list args;
va_start(args, fmt);
pcm = kzalloc(sizeof(*pcm), GFP_KERNEL);
if (!pcm)
return NULL;
pcm->codec = codec;
kref_init(&pcm->kref);
pcm->name = kvasprintf(GFP_KERNEL, fmt, args);
if (!pcm->name) {
kfree(pcm);
return NULL;
}
list_add_tail(&pcm->list, &codec->pcm_list_head);
return pcm;
}
EXPORT_SYMBOL_GPL(snd_hda_codec_pcm_new);
static void codec_release_pcms(struct hda_codec *codec)
{
struct hda_pcm *pcm, *n;
list_for_each_entry_safe(pcm, n, &codec->pcm_list_head, list) {
list_del_init(&pcm->list);
snd_hda_codec_pcm_put(pcm);
}
}
/*
* codec destructor
*/
@ -1124,6 +1178,7 @@ static void snd_hda_codec_free(struct hda_codec *codec)
if (!codec)
return;
cancel_delayed_work_sync(&codec->jackpoll_work);
codec_release_pcms(codec);
if (device_is_registered(hda_codec_dev(codec)))
device_del(hda_codec_dev(codec));
snd_hda_jack_tbl_clear(codec);
@ -1251,6 +1306,7 @@ int snd_hda_codec_new(struct hda_bus *bus, struct snd_card *card,
snd_array_init(&codec->jacktbl, sizeof(struct hda_jack_tbl), 16);
snd_array_init(&codec->verbs, sizeof(struct hda_verb *), 8);
INIT_LIST_HEAD(&codec->conn_list);
INIT_LIST_HEAD(&codec->pcm_list_head);
INIT_DELAYED_WORK(&codec->jackpoll_work, hda_jackpoll_work);
codec->depop_delay = -1;
@ -2370,9 +2426,8 @@ int snd_hda_lock_devices(struct hda_bus *bus)
goto err_clear;
list_for_each_entry(codec, &bus->codec_list, list) {
int pcm;
for (pcm = 0; pcm < codec->num_pcms; pcm++) {
struct hda_pcm *cpcm = &codec->pcm_info[pcm];
struct hda_pcm *cpcm;
list_for_each_entry(cpcm, &codec->pcm_list_head, list) {
if (!cpcm->pcm)
continue;
if (cpcm->pcm->streams[0].substream_opened ||
@ -2419,8 +2474,6 @@ EXPORT_SYMBOL_GPL(snd_hda_unlock_devices);
int snd_hda_codec_reset(struct hda_codec *codec)
{
struct hda_bus *bus = codec->bus;
struct snd_card *card = codec->card;
int i;
if (snd_hda_lock_devices(bus) < 0)
return -EBUSY;
@ -2429,14 +2482,7 @@ int snd_hda_codec_reset(struct hda_codec *codec)
cancel_delayed_work_sync(&codec->jackpoll_work);
flush_workqueue(bus->workq);
snd_hda_ctls_clear(codec);
/* release PCMs */
for (i = 0; i < codec->num_pcms; i++) {
if (codec->pcm_info[i].pcm) {
snd_device_free(card, codec->pcm_info[i].pcm);
clear_bit(codec->pcm_info[i].device,
bus->pcm_dev_bits);
}
}
codec_release_pcms(codec);
snd_hda_detach_beep_device(codec);
if (device_is_registered(hda_codec_dev(codec)))
device_del(hda_codec_dev(codec));
@ -2454,8 +2500,6 @@ int snd_hda_codec_reset(struct hda_codec *codec)
snd_array_free(&codec->cvt_setups);
snd_array_free(&codec->spdif_out);
snd_array_free(&codec->verbs);
codec->num_pcms = 0;
codec->pcm_info = NULL;
codec->preset = NULL;
codec->slave_dig_outs = NULL;
codec->spdif_status_reset = 0;
@ -3952,12 +3996,12 @@ static void hda_call_codec_resume(struct hda_codec *codec)
static int hda_codec_runtime_suspend(struct device *dev)
{
struct hda_codec *codec = dev_to_hda_codec(dev);
struct hda_pcm *pcm;
unsigned int state;
int i;
cancel_delayed_work_sync(&codec->jackpoll_work);
for (i = 0; i < codec->num_pcms; i++)
snd_pcm_suspend_all(codec->pcm_info[i].pcm);
list_for_each_entry(pcm, &codec->pcm_list_head, list)
snd_pcm_suspend_all(pcm->pcm);
state = hda_call_codec_suspend(codec);
if (codec->d3_stop_clk && codec->epss && (state & AC_PWRST_CLK_STOP_OK))
clear_bit(codec->addr, &codec->bus->codec_powered);
@ -4018,22 +4062,21 @@ EXPORT_SYMBOL_GPL(snd_hda_build_controls);
*/
static int add_std_chmaps(struct hda_codec *codec)
{
int i, str, err;
struct hda_pcm *pcm;
int str, err;
for (i = 0; i < codec->num_pcms; i++) {
list_for_each_entry(pcm, &codec->pcm_list_head, list) {
for (str = 0; str < 2; str++) {
struct snd_pcm *pcm = codec->pcm_info[i].pcm;
struct hda_pcm_stream *hinfo =
&codec->pcm_info[i].stream[str];
struct hda_pcm_stream *hinfo = &pcm->stream[str];
struct snd_pcm_chmap *chmap;
const struct snd_pcm_chmap_elem *elem;
if (codec->pcm_info[i].own_chmap)
if (pcm->own_chmap)
continue;
if (!pcm || !hinfo->substreams)
continue;
elem = hinfo->chmap ? hinfo->chmap : snd_pcm_std_chmaps;
err = snd_pcm_add_chmap_ctls(pcm, str, elem,
err = snd_pcm_add_chmap_ctls(pcm->pcm, str, elem,
hinfo->channels_max,
0, &chmap);
if (err < 0)
@ -4564,10 +4607,10 @@ static int get_empty_pcm_device(struct hda_bus *bus, unsigned int type)
/* call build_pcms ops of the given codec and set up the default parameters */
int snd_hda_codec_parse_pcms(struct hda_codec *codec)
{
unsigned int pcm;
struct hda_pcm *cpcm;
int err;
if (codec->num_pcms)
if (!list_empty(&codec->pcm_list_head))
return 0; /* already parsed */
if (!codec->patch_ops.build_pcms)
@ -4580,8 +4623,7 @@ int snd_hda_codec_parse_pcms(struct hda_codec *codec)
return err;
}
for (pcm = 0; pcm < codec->num_pcms; pcm++) {
struct hda_pcm *cpcm = &codec->pcm_info[pcm];
list_for_each_entry(cpcm, &codec->pcm_list_head, list) {
int stream;
for (stream = 0; stream < 2; stream++) {
@ -4589,8 +4631,6 @@ int snd_hda_codec_parse_pcms(struct hda_codec *codec)
if (!info->substreams)
continue;
if (snd_BUG_ON(!cpcm->name))
return -EINVAL;
err = set_pcm_default_values(codec, info);
if (err < 0) {
codec_warn(codec,
@ -4608,7 +4648,7 @@ int snd_hda_codec_parse_pcms(struct hda_codec *codec)
int snd_hda_codec_build_pcms(struct hda_codec *codec)
{
struct hda_bus *bus = codec->bus;
unsigned int pcm;
struct hda_pcm *cpcm;
int dev, err;
if (snd_BUG_ON(!bus->ops.attach_pcm))
@ -4621,9 +4661,7 @@ int snd_hda_codec_build_pcms(struct hda_codec *codec)
}
/* attach a new PCM streams */
for (pcm = 0; pcm < codec->num_pcms; pcm++) {
struct hda_pcm *cpcm = &codec->pcm_info[pcm];
list_for_each_entry(cpcm, &codec->pcm_list_head, list) {
if (cpcm->pcm)
continue; /* already attached */
if (!cpcm->stream[0].substreams && !cpcm->stream[1].substreams)
@ -4651,11 +4689,9 @@ int snd_hda_codec_build_pcms(struct hda_codec *codec)
*
* 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.
* The build_pcms codec patch is requested to create and assign new
* hda_pcm objects. The codec is responsible to call snd_hda_codec_pcm_new()
* and fills the fields. Later they are instantiated by this function.
*
* At least, substreams, channels_min and channels_max must be filled for
* each stream. substreams = 0 indicates that the stream doesn't exist.

View File

@ -21,6 +21,7 @@
#ifndef __SOUND_HDA_CODEC_H
#define __SOUND_HDA_CODEC_H
#include <linux/kref.h>
#include <sound/info.h>
#include <sound/control.h>
#include <sound/pcm.h>
@ -268,6 +269,10 @@ struct hda_pcm {
int device; /* device number to assign */
struct snd_pcm *pcm; /* assigned PCM instance */
bool own_chmap; /* codec driver provides own channel maps */
/* private: */
struct hda_codec *codec;
struct kref kref;
struct list_head list;
};
/* codec information */
@ -301,8 +306,7 @@ struct hda_codec {
struct hda_codec_ops patch_ops;
/* PCM to create, set by patch_ops.build_pcms callback */
unsigned int num_pcms;
struct hda_pcm *pcm_info;
struct list_head pcm_list_head;
/* codec specific info */
void *spec;
@ -521,6 +525,16 @@ int snd_hda_build_pcms(struct hda_bus *bus);
int snd_hda_codec_parse_pcms(struct hda_codec *codec);
int snd_hda_codec_build_pcms(struct hda_codec *codec);
__printf(2, 3)
struct hda_pcm *snd_hda_codec_pcm_new(struct hda_codec *codec,
const char *fmt, ...);
static inline void snd_hda_codec_pcm_get(struct hda_pcm *pcm)
{
kref_get(&pcm->kref);
}
void snd_hda_codec_pcm_put(struct hda_pcm *pcm);
int snd_hda_codec_prepare(struct hda_codec *codec,
struct hda_pcm_stream *hinfo,
unsigned int stream,

View File

@ -4644,7 +4644,7 @@ int snd_hda_gen_build_controls(struct hda_codec *codec)
err = snd_hda_create_dig_out_ctls(codec,
spec->multiout.dig_out_nid,
spec->multiout.dig_out_nid,
spec->pcm_rec[1].pcm_type);
spec->pcm_rec[1]->pcm_type);
if (err < 0)
return err;
if (!spec->no_analog) {
@ -5115,20 +5115,20 @@ static void fill_pcm_stream_name(char *str, size_t len, const char *sfx,
int snd_hda_gen_build_pcms(struct hda_codec *codec)
{
struct hda_gen_spec *spec = codec->spec;
struct hda_pcm *info = spec->pcm_rec;
struct hda_pcm *info;
const struct hda_pcm_stream *p;
bool have_multi_adcs;
codec->num_pcms = 1;
codec->pcm_info = info;
if (spec->no_analog)
goto skip_analog;
fill_pcm_stream_name(spec->stream_name_analog,
sizeof(spec->stream_name_analog),
" Analog", codec->chip_name);
info->name = spec->stream_name_analog;
info = snd_hda_codec_pcm_new(codec, "%s", spec->stream_name_analog);
if (!info)
return -ENOMEM;
spec->pcm_rec[0] = info;
if (spec->multiout.num_dacs > 0) {
p = spec->stream_analog_playback;
@ -5161,10 +5161,12 @@ int snd_hda_gen_build_pcms(struct hda_codec *codec)
fill_pcm_stream_name(spec->stream_name_digital,
sizeof(spec->stream_name_digital),
" Digital", codec->chip_name);
codec->num_pcms = 2;
info = snd_hda_codec_pcm_new(codec, "%s",
spec->stream_name_digital);
if (!info)
return -ENOMEM;
codec->slave_dig_outs = spec->multiout.slave_dig_outs;
info = spec->pcm_rec + 1;
info->name = spec->stream_name_digital;
spec->pcm_rec[1] = info;
if (spec->dig_out_type)
info->pcm_type = spec->dig_out_type;
else
@ -5198,9 +5200,11 @@ int snd_hda_gen_build_pcms(struct hda_codec *codec)
fill_pcm_stream_name(spec->stream_name_alt_analog,
sizeof(spec->stream_name_alt_analog),
" Alt Analog", codec->chip_name);
codec->num_pcms = 3;
info = spec->pcm_rec + 2;
info->name = spec->stream_name_alt_analog;
info = snd_hda_codec_pcm_new(codec, "%s",
spec->stream_name_alt_analog);
if (!info)
return -ENOMEM;
spec->pcm_rec[2] = info;
if (spec->alt_dac_nid) {
p = spec->stream_analog_alt_playback;
if (!p)

View File

@ -144,7 +144,7 @@ struct hda_gen_spec {
int const_channel_count; /* channel count for all */
/* PCM information */
struct hda_pcm pcm_rec[3]; /* used in build_pcms() */
struct hda_pcm *pcm_rec[3]; /* used in build_pcms() */
/* dynamic controls, init_verbs and input_mux */
struct auto_pin_cfg autocfg;

View File

@ -99,10 +99,10 @@ static void print_nid_array(struct snd_info_buffer *buffer,
static void print_nid_pcms(struct snd_info_buffer *buffer,
struct hda_codec *codec, hda_nid_t nid)
{
int pcm, type;
int type;
struct hda_pcm *cpcm;
for (pcm = 0; pcm < codec->num_pcms; pcm++) {
cpcm = &codec->pcm_info[pcm];
list_for_each_entry(cpcm, &codec->pcm_list_head, list) {
for (type = 0; type < 2; type++) {
if (cpcm->stream[type].nid != nid || cpcm->pcm == NULL)
continue;

View File

@ -719,7 +719,6 @@ struct ca0132_spec {
unsigned int num_inputs;
hda_nid_t shared_mic_nid;
hda_nid_t shared_out_nid;
struct hda_pcm pcm_rec[5]; /* PCM information */
/* chip access */
struct mutex chipio_mutex; /* chip access mutex */
@ -4036,12 +4035,11 @@ static struct hda_pcm_stream ca0132_pcm_digital_capture = {
static int ca0132_build_pcms(struct hda_codec *codec)
{
struct ca0132_spec *spec = codec->spec;
struct hda_pcm *info = spec->pcm_rec;
struct hda_pcm *info;
codec->pcm_info = info;
codec->num_pcms = 0;
info->name = "CA0132 Analog";
info = snd_hda_codec_pcm_new(codec, "CA0132 Analog");
if (!info)
return -ENOMEM;
info->stream[SNDRV_PCM_STREAM_PLAYBACK] = ca0132_pcm_analog_playback;
info->stream[SNDRV_PCM_STREAM_PLAYBACK].nid = spec->dacs[0];
info->stream[SNDRV_PCM_STREAM_PLAYBACK].channels_max =
@ -4049,27 +4047,27 @@ static int ca0132_build_pcms(struct hda_codec *codec)
info->stream[SNDRV_PCM_STREAM_CAPTURE] = ca0132_pcm_analog_capture;
info->stream[SNDRV_PCM_STREAM_CAPTURE].substreams = 1;
info->stream[SNDRV_PCM_STREAM_CAPTURE].nid = spec->adcs[0];
codec->num_pcms++;
info++;
info->name = "CA0132 Analog Mic-In2";
info = snd_hda_codec_pcm_new(codec, "CA0132 Analog Mic-In2");
if (!info)
return -ENOMEM;
info->stream[SNDRV_PCM_STREAM_CAPTURE] = ca0132_pcm_analog_capture;
info->stream[SNDRV_PCM_STREAM_CAPTURE].substreams = 1;
info->stream[SNDRV_PCM_STREAM_CAPTURE].nid = spec->adcs[1];
codec->num_pcms++;
info++;
info->name = "CA0132 What U Hear";
info = snd_hda_codec_pcm_new(codec, "CA0132 What U Hear");
if (!info)
return -ENOMEM;
info->stream[SNDRV_PCM_STREAM_CAPTURE] = ca0132_pcm_analog_capture;
info->stream[SNDRV_PCM_STREAM_CAPTURE].substreams = 1;
info->stream[SNDRV_PCM_STREAM_CAPTURE].nid = spec->adcs[2];
codec->num_pcms++;
if (!spec->dig_out && !spec->dig_in)
return 0;
info++;
info->name = "CA0132 Digital";
info = snd_hda_codec_pcm_new(codec, "CA0132 Digital");
if (!info)
return -ENOMEM;
info->pcm_type = HDA_PCM_TYPE_SPDIF;
if (spec->dig_out) {
info->stream[SNDRV_PCM_STREAM_PLAYBACK] =
@ -4081,7 +4079,6 @@ static int ca0132_build_pcms(struct hda_codec *codec)
ca0132_pcm_digital_capture;
info->stream[SNDRV_PCM_STREAM_CAPTURE].nid = spec->dig_in;
}
codec->num_pcms++;
return 0;
}

View File

@ -86,7 +86,6 @@ struct hdmi_spec_per_pin {
bool non_pcm;
bool chmap_set; /* channel-map override by ALSA API? */
unsigned char chmap[8]; /* ALSA API channel-map */
char pcm_name[8]; /* filled in build_pcm callbacks */
#ifdef CONFIG_PROC_FS
struct snd_info_entry *proc_entry;
#endif
@ -132,7 +131,7 @@ struct hdmi_spec {
int num_pins;
struct snd_array pins; /* struct hdmi_spec_per_pin */
struct snd_array pcm_rec; /* struct hda_pcm */
struct hda_pcm *pcm_rec[16];
unsigned int channels_max; /* max over all cvts */
struct hdmi_eld temp_eld;
@ -355,8 +354,7 @@ static struct cea_channel_speaker_allocation channel_allocations[] = {
((struct hdmi_spec_per_pin *)snd_array_elem(&spec->pins, idx))
#define get_cvt(spec, idx) \
((struct hdmi_spec_per_cvt *)snd_array_elem(&spec->cvts, idx))
#define get_pcm_rec(spec, idx) \
((struct hda_pcm *)snd_array_elem(&spec->pcm_rec, idx))
#define get_pcm_rec(spec, idx) ((spec)->pcm_rec[idx])
static int pin_nid_to_pin_index(struct hda_codec *codec, hda_nid_t pin_nid)
{
@ -2056,11 +2054,10 @@ static int generic_hdmi_build_pcms(struct hda_codec *codec)
struct hdmi_spec_per_pin *per_pin;
per_pin = get_pin(spec, pin_idx);
sprintf(per_pin->pcm_name, "HDMI %d", pin_idx);
info = snd_array_new(&spec->pcm_rec);
info = snd_hda_codec_pcm_new(codec, "HDMI %d", pin_idx);
if (!info)
return -ENOMEM;
info->name = per_pin->pcm_name;
spec->pcm_rec[pin_idx] = info;
info->pcm_type = HDA_PCM_TYPE_HDMI;
info->own_chmap = true;
@ -2070,9 +2067,6 @@ static int generic_hdmi_build_pcms(struct hda_codec *codec)
/* other pstr fields are set in open */
}
codec->num_pcms = spec->num_pins;
codec->pcm_info = spec->pcm_rec.list;
return 0;
}
@ -2125,13 +2119,15 @@ static int generic_hdmi_build_controls(struct hda_codec *codec)
/* add channel maps */
for (pin_idx = 0; pin_idx < spec->num_pins; pin_idx++) {
struct hda_pcm *pcm;
struct snd_pcm_chmap *chmap;
struct snd_kcontrol *kctl;
int i;
if (!codec->pcm_info[pin_idx].pcm)
pcm = spec->pcm_rec[pin_idx];
if (!pcm || !pcm->pcm)
break;
err = snd_pcm_add_chmap_ctls(codec->pcm_info[pin_idx].pcm,
err = snd_pcm_add_chmap_ctls(pcm->pcm,
SNDRV_PCM_STREAM_PLAYBACK,
NULL, 0, pin_idx, &chmap);
if (err < 0)
@ -2186,14 +2182,12 @@ static void hdmi_array_init(struct hdmi_spec *spec, int nums)
{
snd_array_init(&spec->pins, sizeof(struct hdmi_spec_per_pin), nums);
snd_array_init(&spec->cvts, sizeof(struct hdmi_spec_per_cvt), nums);
snd_array_init(&spec->pcm_rec, sizeof(struct hda_pcm), nums);
}
static void hdmi_array_free(struct hdmi_spec *spec)
{
snd_array_free(&spec->pins);
snd_array_free(&spec->cvts);
snd_array_free(&spec->pcm_rec);
}
static void generic_hdmi_free(struct hda_codec *codec)
@ -2381,11 +2375,10 @@ static int simple_playback_build_pcms(struct hda_codec *codec)
chans = get_wcaps(codec, per_cvt->cvt_nid);
chans = get_wcaps_channels(chans);
info = snd_array_new(&spec->pcm_rec);
info = snd_hda_codec_pcm_new(codec, "HDMI 0");
if (!info)
return -ENOMEM;
info->name = get_pin(spec, 0)->pcm_name;
sprintf(info->name, "HDMI 0");
spec->pcm_rec[0] = info;
info->pcm_type = HDA_PCM_TYPE_HDMI;
pstr = &info->stream[SNDRV_PCM_STREAM_PLAYBACK];
*pstr = spec->pcm_playback;
@ -2393,9 +2386,6 @@ static int simple_playback_build_pcms(struct hda_codec *codec)
if (pstr->channels_max <= 2 && chans && chans <= 16)
pstr->channels_max = chans;
codec->num_pcms = 1;
codec->pcm_info = info;
return 0;
}

View File

@ -5843,7 +5843,7 @@ static void alc_fixup_bass_chmap(struct hda_codec *codec,
{
if (action == HDA_FIXUP_ACT_BUILD) {
struct alc_spec *spec = codec->spec;
spec->gen.pcm_rec[0].stream[0].chmap = asus_pcm_2_1_chmaps;
spec->gen.pcm_rec[0]->stream[0].chmap = asus_pcm_2_1_chmaps;
}
}

View File

@ -83,7 +83,6 @@
struct si3054_spec {
unsigned international;
struct hda_pcm pcm;
};
@ -199,11 +198,11 @@ static const struct hda_pcm_stream si3054_pcm = {
static int si3054_build_pcms(struct hda_codec *codec)
{
struct si3054_spec *spec = codec->spec;
struct hda_pcm *info = &spec->pcm;
codec->num_pcms = 1;
codec->pcm_info = info;
info->name = "Si3054 Modem";
struct hda_pcm *info;
info = snd_hda_codec_pcm_new(codec, "Si3054 Modem");
if (!info)
return -ENOMEM;
info->stream[SNDRV_PCM_STREAM_PLAYBACK] = si3054_pcm;
info->stream[SNDRV_PCM_STREAM_CAPTURE] = si3054_pcm;
info->stream[SNDRV_PCM_STREAM_PLAYBACK].nid = codec->mfg;

View File

@ -683,8 +683,10 @@ static int vt1708_build_pcms(struct hda_codec *codec)
* 24bit samples are used. Until any workaround is found,
* disable the 24bit format, so far.
*/
for (i = 0; i < codec->num_pcms; i++) {
struct hda_pcm *info = &spec->gen.pcm_rec[i];
for (i = 0; i < ARRAY_SIZE(spec->gen.pcm_rec); i++) {
struct hda_pcm *info = spec->gen.pcm_rec[i];
if (!info)
continue;
if (!info->stream[SNDRV_PCM_STREAM_PLAYBACK].substreams ||
info->pcm_type != HDA_PCM_TYPE_AUDIO)
continue;