summaryrefslogtreecommitdiff
path: root/sound/pci/hda/hda_codec.c
diff options
context:
space:
mode:
Diffstat (limited to 'sound/pci/hda/hda_codec.c')
-rw-r--r--sound/pci/hda/hda_codec.c270
1 files changed, 223 insertions, 47 deletions
diff --git a/sound/pci/hda/hda_codec.c b/sound/pci/hda/hda_codec.c
index ba2098d20ccc..14829210ef0b 100644
--- a/sound/pci/hda/hda_codec.c
+++ b/sound/pci/hda/hda_codec.c
@@ -396,15 +396,18 @@ int snd_hda_get_connections(struct hda_codec *codec, hda_nid_t nid,
}
for (n = prev_nid + 1; n <= val; n++) {
if (conns >= max_conns) {
- snd_printk(KERN_ERR
- "Too many connections\n");
+ snd_printk(KERN_ERR "hda_codec: "
+ "Too many connections %d for NID 0x%x\n",
+ conns, nid);
return -EINVAL;
}
conn_list[conns++] = n;
}
} else {
if (conns >= max_conns) {
- snd_printk(KERN_ERR "Too many connections\n");
+ snd_printk(KERN_ERR "hda_codec: "
+ "Too many connections %d for NID 0x%x\n",
+ conns, nid);
return -EINVAL;
}
conn_list[conns++] = val;
@@ -586,6 +589,7 @@ int /*__devinit*/ snd_hda_bus_new(struct snd_card *card,
bus->ops = temp->ops;
mutex_init(&bus->cmd_mutex);
+ mutex_init(&bus->prepare_mutex);
INIT_LIST_HEAD(&bus->codec_list);
snprintf(bus->workq_name, sizeof(bus->workq_name),
@@ -730,15 +734,17 @@ static void /*__devinit*/ setup_fg_nodes(struct hda_codec *codec)
total_nodes = snd_hda_get_sub_nodes(codec, AC_NODE_ROOT, &nid);
for (i = 0; i < total_nodes; i++, nid++) {
function_id = snd_hda_param_read(codec, nid,
- AC_PAR_FUNCTION_TYPE) & 0xff;
- switch (function_id) {
+ AC_PAR_FUNCTION_TYPE);
+ switch (function_id & 0xff) {
case AC_GRP_AUDIO_FUNCTION:
codec->afg = nid;
- codec->function_id = function_id;
+ codec->afg_function_id = function_id & 0xff;
+ codec->afg_unsol = (function_id >> 8) & 1;
break;
case AC_GRP_MODEM_FUNCTION:
codec->mfg = nid;
- codec->function_id = function_id;
+ codec->mfg_function_id = function_id & 0xff;
+ codec->mfg_unsol = (function_id >> 8) & 1;
break;
default:
break;
@@ -966,6 +972,36 @@ static void restore_init_pincfgs(struct hda_codec *codec)
}
/*
+ * audio-converter setup caches
+ */
+struct hda_cvt_setup {
+ hda_nid_t nid;
+ u8 stream_tag;
+ u8 channel_id;
+ u16 format_id;
+ unsigned char active; /* cvt is currently used */
+ unsigned char dirty; /* setups should be cleared */
+};
+
+/* get or create a cache entry for the given audio converter NID */
+static struct hda_cvt_setup *
+get_hda_cvt_setup(struct hda_codec *codec, hda_nid_t nid)
+{
+ struct hda_cvt_setup *p;
+ int i;
+
+ for (i = 0; i < codec->cvt_setups.used; i++) {
+ p = snd_array_elem(&codec->cvt_setups, i);
+ if (p->nid == nid)
+ return p;
+ }
+ p = snd_array_new(&codec->cvt_setups);
+ if (p)
+ p->nid = nid;
+ return p;
+}
+
+/*
* codec destructor
*/
static void snd_hda_codec_free(struct hda_codec *codec)
@@ -1039,6 +1075,7 @@ int /*__devinit*/ snd_hda_codec_new(struct hda_bus *bus,
snd_array_init(&codec->nids, sizeof(struct hda_nid_item), 32);
snd_array_init(&codec->init_pins, sizeof(struct hda_pincfg), 16);
snd_array_init(&codec->driver_pins, sizeof(struct hda_pincfg), 16);
+ snd_array_init(&codec->cvt_setups, sizeof(struct hda_cvt_setup), 8);
if (codec->bus->modelname) {
codec->modelname = kstrdup(codec->bus->modelname, GFP_KERNEL);
if (!codec->modelname) {
@@ -1176,37 +1213,126 @@ void snd_hda_codec_setup_stream(struct hda_codec *codec, hda_nid_t nid,
u32 stream_tag,
int channel_id, int format)
{
+ struct hda_codec *c;
+ struct hda_cvt_setup *p;
+ unsigned int oldval, newval;
+ int i;
+
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);
+ p = get_hda_cvt_setup(codec, nid);
+ if (!p)
+ return;
+ /* update the stream-id if changed */
+ if (p->stream_tag != stream_tag || p->channel_id != channel_id) {
+ oldval = snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_CONV, 0);
+ newval = (stream_tag << 4) | channel_id;
+ if (oldval != newval)
+ snd_hda_codec_write(codec, nid, 0,
+ AC_VERB_SET_CHANNEL_STREAMID,
+ newval);
+ p->stream_tag = stream_tag;
+ p->channel_id = channel_id;
+ }
+ /* update the format-id if changed */
+ if (p->format_id != format) {
+ oldval = snd_hda_codec_read(codec, nid, 0,
+ AC_VERB_GET_STREAM_FORMAT, 0);
+ if (oldval != format) {
+ msleep(1);
+ snd_hda_codec_write(codec, nid, 0,
+ AC_VERB_SET_STREAM_FORMAT,
+ format);
+ }
+ p->format_id = format;
+ }
+ p->active = 1;
+ p->dirty = 0;
+
+ /* make other inactive cvts with the same stream-tag dirty */
+ list_for_each_entry(c, &codec->bus->codec_list, list) {
+ for (i = 0; i < c->cvt_setups.used; i++) {
+ p = snd_array_elem(&c->cvt_setups, i);
+ if (!p->active && p->stream_tag == stream_tag)
+ p->dirty = 1;
+ }
+ }
}
EXPORT_SYMBOL_HDA(snd_hda_codec_setup_stream);
+static void really_cleanup_stream(struct hda_codec *codec,
+ struct hda_cvt_setup *q);
+
/**
- * snd_hda_codec_cleanup_stream - clean up the codec for closing
+ * __snd_hda_codec_cleanup_stream - clean up the codec for closing
* @codec: the CODEC to clean up
* @nid: the NID to clean up
+ * @do_now: really clean up the stream instead of clearing the active flag
*/
-void snd_hda_codec_cleanup_stream(struct hda_codec *codec, hda_nid_t nid)
+void __snd_hda_codec_cleanup_stream(struct hda_codec *codec, hda_nid_t nid,
+ int do_now)
{
+ struct hda_cvt_setup *p;
+
if (!nid)
return;
snd_printdd("hda_codec_cleanup_stream: NID=0x%x\n", nid);
+ p = get_hda_cvt_setup(codec, nid);
+ if (p) {
+ /* here we just clear the active flag when do_now isn't set;
+ * actual clean-ups will be done later in
+ * purify_inactive_streams() called from snd_hda_codec_prpapre()
+ */
+ if (do_now)
+ really_cleanup_stream(codec, p);
+ else
+ p->active = 0;
+ }
+}
+EXPORT_SYMBOL_HDA(__snd_hda_codec_cleanup_stream);
+
+static void really_cleanup_stream(struct hda_codec *codec,
+ struct hda_cvt_setup *q)
+{
+ hda_nid_t nid = q->nid;
snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_CHANNEL_STREAMID, 0);
-#if 0 /* keep the format */
- msleep(1);
snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_STREAM_FORMAT, 0);
-#endif
+ memset(q, 0, sizeof(*q));
+ q->nid = nid;
+}
+
+/* clean up the all conflicting obsolete streams */
+static void purify_inactive_streams(struct hda_codec *codec)
+{
+ struct hda_codec *c;
+ int i;
+
+ list_for_each_entry(c, &codec->bus->codec_list, list) {
+ for (i = 0; i < c->cvt_setups.used; i++) {
+ struct hda_cvt_setup *p;
+ p = snd_array_elem(&c->cvt_setups, i);
+ if (p->dirty)
+ really_cleanup_stream(c, p);
+ }
+ }
+}
+
+/* clean up all streams; called from suspend */
+static void hda_cleanup_all_streams(struct hda_codec *codec)
+{
+ int i;
+
+ for (i = 0; i < codec->cvt_setups.used; i++) {
+ struct hda_cvt_setup *p = snd_array_elem(&codec->cvt_setups, i);
+ if (p->stream_tag)
+ really_cleanup_stream(codec, p);
+ }
}
-EXPORT_SYMBOL_HDA(snd_hda_codec_cleanup_stream);
/*
* amp access functions
@@ -1565,6 +1691,17 @@ void snd_hda_codec_resume_amp(struct hda_codec *codec)
EXPORT_SYMBOL_HDA(snd_hda_codec_resume_amp);
#endif /* SND_HDA_NEEDS_RESUME */
+static u32 get_amp_max_value(struct hda_codec *codec, hda_nid_t nid, int dir,
+ unsigned int ofs)
+{
+ u32 caps = query_amp_caps(codec, nid, dir);
+ /* get num steps */
+ caps = (caps & AC_AMPCAP_NUM_STEPS) >> AC_AMPCAP_NUM_STEPS_SHIFT;
+ if (ofs < caps)
+ caps -= ofs;
+ return caps;
+}
+
/**
* snd_hda_mixer_amp_volume_info - Info callback for a standard AMP mixer
*
@@ -1579,23 +1716,17 @@ int snd_hda_mixer_amp_volume_info(struct snd_kcontrol *kcontrol,
u8 chs = get_amp_channels(kcontrol);
int dir = get_amp_direction(kcontrol);
unsigned int ofs = get_amp_offset(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) {
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = chs == 3 ? 2 : 1;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = get_amp_max_value(codec, nid, dir, ofs);
+ if (!uinfo->value.integer.max) {
printk(KERN_WARNING "hda_codec: "
"num_steps = 0 for NID=0x%x (ctl = %s)\n", nid,
kcontrol->id.name);
return -EINVAL;
}
- if (ofs < caps)
- caps -= ofs;
- 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;
}
EXPORT_SYMBOL_HDA(snd_hda_mixer_amp_volume_info);
@@ -1620,8 +1751,14 @@ update_amp_value(struct hda_codec *codec, hda_nid_t nid,
int ch, int dir, int idx, unsigned int ofs,
unsigned int val)
{
+ unsigned int maxval;
+
if (val > 0)
val += ofs;
+ /* ofs = 0: raw max value */
+ maxval = get_amp_max_value(codec, nid, dir, 0);
+ if (val > maxval)
+ val = maxval;
return snd_hda_codec_amp_update(codec, nid, ch, dir, idx,
HDA_AMP_VOLMASK, val);
}
@@ -2912,6 +3049,7 @@ static void hda_call_codec_suspend(struct hda_codec *codec)
{
if (codec->patch_ops.suspend)
codec->patch_ops.suspend(codec, PMSG_SUSPEND);
+ hda_cleanup_all_streams(codec);
hda_set_power_state(codec,
codec->afg ? codec->afg : codec->mfg,
AC_PWRST_D3);
@@ -2999,26 +3137,31 @@ struct hda_rate_tbl {
unsigned int hda_fmt;
};
+/* rate = base * mult / div */
+#define HDA_RATE(base, mult, div) \
+ (AC_FMT_BASE_##base##K | (((mult) - 1) << AC_FMT_MULT_SHIFT) | \
+ (((div) - 1) << AC_FMT_DIV_SHIFT))
+
static struct hda_rate_tbl rate_bits[] = {
/* rate in Hz, ALSA rate bitmask, HDA format value */
/* autodetected value used in snd_hda_query_supported_pcm */
- { 8000, SNDRV_PCM_RATE_8000, 0x0500 }, /* 1/6 x 48 */
- { 11025, SNDRV_PCM_RATE_11025, 0x4300 }, /* 1/4 x 44 */
- { 16000, SNDRV_PCM_RATE_16000, 0x0200 }, /* 1/3 x 48 */
- { 22050, SNDRV_PCM_RATE_22050, 0x4100 }, /* 1/2 x 44 */
- { 32000, SNDRV_PCM_RATE_32000, 0x0a00 }, /* 2/3 x 48 */
- { 44100, SNDRV_PCM_RATE_44100, 0x4000 }, /* 44 */
- { 48000, SNDRV_PCM_RATE_48000, 0x0000 }, /* 48 */
- { 88200, SNDRV_PCM_RATE_88200, 0x4800 }, /* 2 x 44 */
- { 96000, SNDRV_PCM_RATE_96000, 0x0800 }, /* 2 x 48 */
- { 176400, SNDRV_PCM_RATE_176400, 0x5800 },/* 4 x 44 */
- { 192000, SNDRV_PCM_RATE_192000, 0x1800 }, /* 4 x 48 */
+ { 8000, SNDRV_PCM_RATE_8000, HDA_RATE(48, 1, 6) },
+ { 11025, SNDRV_PCM_RATE_11025, HDA_RATE(44, 1, 4) },
+ { 16000, SNDRV_PCM_RATE_16000, HDA_RATE(48, 1, 3) },
+ { 22050, SNDRV_PCM_RATE_22050, HDA_RATE(44, 1, 2) },
+ { 32000, SNDRV_PCM_RATE_32000, HDA_RATE(48, 2, 3) },
+ { 44100, SNDRV_PCM_RATE_44100, HDA_RATE(44, 1, 1) },
+ { 48000, SNDRV_PCM_RATE_48000, HDA_RATE(48, 1, 1) },
+ { 88200, SNDRV_PCM_RATE_88200, HDA_RATE(44, 2, 1) },
+ { 96000, SNDRV_PCM_RATE_96000, HDA_RATE(48, 2, 1) },
+ { 176400, SNDRV_PCM_RATE_176400, HDA_RATE(44, 4, 1) },
+ { 192000, SNDRV_PCM_RATE_192000, HDA_RATE(48, 4, 1) },
#define AC_PAR_PCM_RATE_BITS 11
/* up to bits 10, 384kHZ isn't supported properly */
/* not autodetected value */
- { 9600, SNDRV_PCM_RATE_KNOT, 0x0400 }, /* 1/5 x 48 */
+ { 9600, SNDRV_PCM_RATE_KNOT, HDA_RATE(48, 1, 5) },
{ 0 } /* terminator */
};
@@ -3037,7 +3180,8 @@ static struct hda_rate_tbl rate_bits[] = {
unsigned int snd_hda_calc_stream_format(unsigned int rate,
unsigned int channels,
unsigned int format,
- unsigned int maxbps)
+ unsigned int maxbps,
+ unsigned short spdif_ctls)
{
int i;
unsigned int val = 0;
@@ -3060,20 +3204,20 @@ unsigned int snd_hda_calc_stream_format(unsigned int rate,
switch (snd_pcm_format_width(format)) {
case 8:
- val |= 0x00;
+ val |= AC_FMT_BITS_8;
break;
case 16:
- val |= 0x10;
+ val |= AC_FMT_BITS_16;
break;
case 20:
case 24:
case 32:
if (maxbps >= 32 || format == SNDRV_PCM_FORMAT_FLOAT_LE)
- val |= 0x40;
+ val |= AC_FMT_BITS_32;
else if (maxbps >= 24)
- val |= 0x30;
+ val |= AC_FMT_BITS_24;
else
- val |= 0x20;
+ val |= AC_FMT_BITS_20;
break;
default:
snd_printdd("invalid format width %d\n",
@@ -3081,6 +3225,9 @@ unsigned int snd_hda_calc_stream_format(unsigned int rate,
return 0;
}
+ if (spdif_ctls & AC_DIG1_NONAUDIO)
+ val |= AC_FMT_TYPE_NON_PCM;
+
return val;
}
EXPORT_SYMBOL_HDA(snd_hda_calc_stream_format);
@@ -3352,6 +3499,35 @@ static int set_pcm_default_values(struct hda_codec *codec,
return 0;
}
+/*
+ * codec prepare/cleanup entries
+ */
+int snd_hda_codec_prepare(struct hda_codec *codec,
+ struct hda_pcm_stream *hinfo,
+ unsigned int stream,
+ unsigned int format,
+ struct snd_pcm_substream *substream)
+{
+ int ret;
+ mutex_lock(&codec->bus->prepare_mutex);
+ ret = hinfo->ops.prepare(hinfo, codec, stream, format, substream);
+ if (ret >= 0)
+ purify_inactive_streams(codec);
+ mutex_unlock(&codec->bus->prepare_mutex);
+ return ret;
+}
+EXPORT_SYMBOL_HDA(snd_hda_codec_prepare);
+
+void snd_hda_codec_cleanup(struct hda_codec *codec,
+ struct hda_pcm_stream *hinfo,
+ struct snd_pcm_substream *substream)
+{
+ mutex_lock(&codec->bus->prepare_mutex);
+ hinfo->ops.cleanup(hinfo, codec, substream);
+ mutex_unlock(&codec->bus->prepare_mutex);
+}
+EXPORT_SYMBOL_HDA(snd_hda_codec_cleanup);
+
/* global */
const char *snd_hda_pcm_type_name[HDA_PCM_NTYPES] = {
"Audio", "SPDIF", "HDMI", "Modem"
@@ -4360,7 +4536,7 @@ int snd_hda_parse_pin_def_config(struct hda_codec *codec,
cfg->hp_outs--;
memmove(cfg->hp_pins + i, cfg->hp_pins + i + 1,
sizeof(cfg->hp_pins[0]) * (cfg->hp_outs - i));
- memmove(sequences_hp + i - 1, sequences_hp + i,
+ memmove(sequences_hp + i, sequences_hp + i + 1,
sizeof(sequences_hp[0]) * (cfg->hp_outs - i));
}
}