total_nodes = snd_hda_get_sub_nodes(codec, AC_NODE_ROOT, &nid);
for (i = 0; i < total_nodes; i++, nid++) {
- unsigned int func;
- func = snd_hda_param_read(codec, nid, AC_PAR_FUNCTION_TYPE);
- switch (func & 0xff) {
+ codec->function_id = snd_hda_param_read(codec, nid,
+ AC_PAR_FUNCTION_TYPE) & 0xff;
+ switch (codec->function_id) {
case AC_GRP_AUDIO_FUNCTION:
codec->afg = nid;
break;
kfree(codec);
}
+static void hda_set_power_state(struct hda_codec *codec, hda_nid_t fg,
+ unsigned int power_state);
+
/**
* snd_hda_codec_new - create a HDA codec
* @bus: the bus to assign
if (bus->modelname)
codec->modelname = kstrdup(bus->modelname, GFP_KERNEL);
+ /* power-up all before initialization */
+ hda_set_power_state(codec,
+ codec->afg ? codec->afg : codec->mfg,
+ AC_PWRST_D0);
+
if (do_init) {
err = snd_hda_codec_configure(codec);
if (err < 0)
/* FIXME: more better hash key? */
#define HDA_HASH_KEY(nid,dir,idx) (u32)((nid) + ((idx) << 16) + ((dir) << 24))
+#define HDA_HASH_PINCAP_KEY(nid) (u32)((nid) + (0x02 << 24))
#define INFO_AMP_CAPS (1<<0)
#define INFO_AMP_VOL(ch) (1 << (1 + (ch)))
}
EXPORT_SYMBOL_HDA(snd_hda_override_amp_caps);
+u32 snd_hda_query_pin_caps(struct hda_codec *codec, hda_nid_t nid)
+{
+ struct hda_amp_info *info;
+
+ info = get_alloc_amp_hash(codec, HDA_HASH_PINCAP_KEY(nid));
+ if (!info)
+ return 0;
+ if (!info->head.val) {
+ info->amp_caps = snd_hda_param_read(codec, nid, AC_PAR_PIN_CAP);
+ info->head.val |= INFO_AMP_CAPS;
+ }
+ return info->amp_caps;
+}
+EXPORT_SYMBOL_HDA(snd_hda_query_pin_caps);
+
/*
* read the current volume to info
* if the cache exists, read the cache value.
}
EXPORT_SYMBOL_HDA(snd_hda_ctl_add);
-#ifdef CONFIG_SND_HDA_RECONFIG
/* Clear all controls assigned to the given codec */
void snd_hda_ctls_clear(struct hda_codec *codec)
{
snd_array_free(&codec->mixers);
}
-void snd_hda_codec_reset(struct hda_codec *codec)
+/* pseudo device locking
+ * toggle card->shutdown to allow/disallow the device access (as a hack)
+ */
+static int hda_lock_devices(struct snd_card *card)
{
- int i;
+ spin_lock(&card->files_lock);
+ if (card->shutdown) {
+ spin_unlock(&card->files_lock);
+ return -EINVAL;
+ }
+ card->shutdown = 1;
+ spin_unlock(&card->files_lock);
+ return 0;
+}
+
+static void hda_unlock_devices(struct snd_card *card)
+{
+ spin_lock(&card->files_lock);
+ card->shutdown = 0;
+ spin_unlock(&card->files_lock);
+}
+
+int snd_hda_codec_reset(struct hda_codec *codec)
+{
+ struct snd_card *card = codec->bus->card;
+ int i, pcm;
+
+ if (hda_lock_devices(card) < 0)
+ return -EBUSY;
+ /* check whether the codec isn't used by any mixer or PCM streams */
+ if (!list_empty(&card->ctl_files)) {
+ hda_unlock_devices(card);
+ return -EBUSY;
+ }
+ for (pcm = 0; pcm < codec->num_pcms; pcm++) {
+ struct hda_pcm *cpcm = &codec->pcm_info[pcm];
+ if (!cpcm->pcm)
+ continue;
+ if (cpcm->pcm->streams[0].substream_opened ||
+ cpcm->pcm->streams[1].substream_opened) {
+ hda_unlock_devices(card);
+ return -EBUSY;
+ }
+ }
+
+ /* OK, let it free */
#ifdef CONFIG_SND_HDA_POWER_SAVE
cancel_delayed_work(&codec->power_work);
/* relase PCMs */
for (i = 0; i < codec->num_pcms; i++) {
if (codec->pcm_info[i].pcm) {
- snd_device_free(codec->bus->card,
- codec->pcm_info[i].pcm);
+ snd_device_free(card, codec->pcm_info[i].pcm);
clear_bit(codec->pcm_info[i].device,
codec->bus->pcm_dev_bits);
}
codec->num_pcms = 0;
codec->pcm_info = NULL;
codec->preset = NULL;
+ memset(&codec->patch_ops, 0, sizeof(codec->patch_ops));
+ codec->slave_dig_outs = NULL;
+ codec->spdif_status_reset = 0;
module_put(codec->owner);
codec->owner = NULL;
+
+ /* allow device access again */
+ hda_unlock_devices(card);
+ return 0;
}
-#endif /* CONFIG_SND_HDA_RECONFIG */
/* create a virtual master control and add slaves */
int snd_hda_add_vmaster(struct hda_codec *codec, char *name,
for (s = slaves; *s; s++) {
struct snd_kcontrol *sctl;
-
- sctl = snd_hda_find_mixer_ctl(codec, *s);
- if (!sctl) {
- snd_printdd("Cannot find slave %s, skipped\n", *s);
- continue;
+ int i = 0;
+ for (;;) {
+ sctl = _snd_hda_find_mixer_ctl(codec, *s, i);
+ if (!sctl) {
+ if (!i)
+ snd_printdd("Cannot find slave %s, "
+ "skipped\n", *s);
+ break;
+ }
+ err = snd_ctl_add_slave(kctl, sctl);
+ if (err < 0)
+ return err;
+ i++;
}
- err = snd_ctl_add_slave(kctl, sctl);
- if (err < 0)
- return err;
}
return 0;
}
err = bus->ops.command(bus, res);
if (!err) {
struct hda_cache_head *c;
- u32 key = build_cmd_cache_key(nid, verb);
+ u32 key;
+ /* parm may contain the verb stuff for get/set amp */
+ verb = verb | (parm >> 8);
+ parm &= 0xff;
+ key = build_cmd_cache_key(nid, verb);
c = get_alloc_hash(&codec->cmd_cache, key);
if (c)
c->val = parm;
* don't power down the widget if it controls
* eapd and EAPD_BTLENABLE is set.
*/
- pincap = snd_hda_param_read(codec, nid,
- AC_PAR_PIN_CAP);
+ pincap = snd_hda_query_pin_caps(codec, nid);
if (pincap & AC_PINCAP_EAPD) {
int eapd = snd_hda_codec_read(codec,
nid, 0,
list_for_each_entry(codec, &bus->codec_list, list) {
int err = snd_hda_codec_build_controls(codec);
- if (err < 0)
- return err;
+ if (err < 0) {
+ printk(KERN_ERR "hda_codec: cannot build controls"
+ "for #%d (error %d)\n", codec->addr, err);
+ err = snd_hda_codec_reset(codec);
+ if (err < 0) {
+ printk(KERN_ERR
+ "hda_codec: cannot revert codec\n");
+ return err;
+ }
+ }
}
return 0;
}
int snd_hda_codec_build_controls(struct hda_codec *codec)
{
int err = 0;
- /* fake as if already powered-on */
- hda_keep_power_on(codec);
- /* then fire up */
- hda_set_power_state(codec,
- codec->afg ? codec->afg : codec->mfg,
- AC_PWRST_D0);
hda_exec_init_verbs(codec);
/* continue to initialize... */
if (codec->patch_ops.init)
err = codec->patch_ops.init(codec);
if (!err && codec->patch_ops.build_controls)
err = codec->patch_ops.build_controls(codec);
- snd_hda_power_down(codec);
if (err < 0)
return err;
return 0;
static 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;
+ unsigned int i, val, wcaps;
val = 0;
- if (nid != codec->afg &&
- (get_wcaps(codec, nid) & AC_WCAP_FORMAT_OVRD)) {
+ wcaps = get_wcaps(codec, nid);
+ if (nid != codec->afg && (wcaps & AC_WCAP_FORMAT_OVRD)) {
val = snd_hda_param_read(codec, nid, AC_PAR_PCM);
if (val == -1)
return -EIO;
if (val & (1 << i))
rates |= rate_bits[i].alsa_bits;
}
+ if (rates == 0) {
+ snd_printk(KERN_ERR "hda_codec: rates == 0 "
+ "(nid=0x%x, val=0x%x, ovrd=%i)\n",
+ nid, val,
+ (wcaps & AC_WCAP_FORMAT_OVRD) ? 1 : 0);
+ return -EIO;
+ }
*ratesp = rates;
}
if (formatsp || bpsp) {
u64 formats = 0;
- unsigned int bps;
- unsigned int wcaps;
+ unsigned int streams, bps;
- wcaps = get_wcaps(codec, nid);
streams = snd_hda_param_read(codec, nid, AC_PAR_STREAM);
if (streams == -1)
return -EIO;
formats |= SNDRV_PCM_FMTBIT_U8;
bps = 8;
}
+ if (formats == 0) {
+ snd_printk(KERN_ERR "hda_codec: formats == 0 "
+ "(nid=0x%x, val=0x%x, ovrd=%i, "
+ "streams=0x%x)\n",
+ nid, val,
+ (wcaps & AC_WCAP_FORMAT_OVRD) ? 1 : 0,
+ streams);
+ return -EIO;
+ }
if (formatsp)
*formatsp = formats;
if (bpsp)
static int set_pcm_default_values(struct hda_codec *codec,
struct hda_pcm_stream *info)
{
+ int err;
+
/* query support PCM information from the given NID */
if (info->nid && (!info->rates || !info->formats)) {
- snd_hda_query_supported_pcm(codec, info->nid,
+ err = snd_hda_query_supported_pcm(codec, info->nid,
info->rates ? NULL : &info->rates,
info->formats ? NULL : &info->formats,
info->maxbps ? NULL : &info->maxbps);
+ if (err < 0)
+ return err;
}
if (info->ops.open == NULL)
info->ops.open = hda_pcm_default_open_close;
for (i = 0; i < ARRAY_SIZE(audio_idx); i++) {
dev = audio_idx[i];
if (!test_bit(dev, bus->pcm_dev_bits))
- break;
+ goto ok;
}
- if (i >= ARRAY_SIZE(audio_idx)) {
- snd_printk(KERN_WARNING "Too many audio devices\n");
- return -EAGAIN;
- }
- break;
+ snd_printk(KERN_WARNING "Too many audio devices\n");
+ return -EAGAIN;
case HDA_PCM_TYPE_SPDIF:
case HDA_PCM_TYPE_HDMI:
case HDA_PCM_TYPE_MODEM:
snd_printk(KERN_WARNING "Invalid PCM type %d\n", type);
return -EINVAL;
}
+ ok:
set_bit(dev, bus->pcm_dev_bits);
return dev;
}
if (!codec->patch_ops.build_pcms)
return 0;
err = codec->patch_ops.build_pcms(codec);
- if (err < 0)
- return err;
+ if (err < 0) {
+ printk(KERN_ERR "hda_codec: cannot build PCMs"
+ "for #%d (error %d)\n", codec->addr, err);
+ err = snd_hda_codec_reset(codec);
+ if (err < 0) {
+ printk(KERN_ERR
+ "hda_codec: cannot revert codec\n");
+ return err;
+ }
+ }
}
for (pcm = 0; pcm < codec->num_pcms; pcm++) {
struct hda_pcm *cpcm = &codec->pcm_info[pcm];
if (!cpcm->pcm) {
dev = get_empty_pcm_device(codec->bus, cpcm->pcm_type);
if (dev < 0)
- return 0;
+ continue; /* no fatal error */
cpcm->device = dev;
err = snd_hda_attach_pcm(codec, cpcm);
- if (err < 0)
- return err;
+ if (err < 0) {
+ printk(KERN_ERR "hda_codec: cannot attach "
+ "PCM stream %d for codec #%d\n",
+ dev, codec->addr);
+ continue; /* no fatal error */
+ }
}
}
return 0;