Commit 54d17403 authored by Takashi Iwai's avatar Takashi Iwai Committed by Jaroslav Kysela

[ALSA] hda-codec - Fix connection list parsing

Modules: HDA Codec driver,HDA generic driver

- Fix connection list parsing (with ranged flag).
- Increase the max number of connections
- Introduce widget capabilities cache
- Power up/down widgets at init, suspend and resume
Signed-off-by: default avatarTakashi Iwai <tiwai@suse.de>
parent 1b98ea47
...@@ -155,8 +155,9 @@ int snd_hda_get_connections(struct hda_codec *codec, hda_nid_t nid, ...@@ -155,8 +155,9 @@ int snd_hda_get_connections(struct hda_codec *codec, hda_nid_t nid,
hda_nid_t *conn_list, int max_conns) hda_nid_t *conn_list, int max_conns)
{ {
unsigned int parm; unsigned int parm;
int i, j, conn_len, num_tupples, conns; int i, conn_len, conns;
unsigned int shift, num_elems, mask; unsigned int shift, num_elems, mask;
hda_nid_t prev_nid;
snd_assert(conn_list && max_conns > 0, return -EINVAL); snd_assert(conn_list && max_conns > 0, return -EINVAL);
...@@ -171,7 +172,6 @@ int snd_hda_get_connections(struct hda_codec *codec, hda_nid_t nid, ...@@ -171,7 +172,6 @@ int snd_hda_get_connections(struct hda_codec *codec, hda_nid_t nid,
num_elems = 4; num_elems = 4;
} }
conn_len = parm & AC_CLIST_LENGTH; conn_len = parm & AC_CLIST_LENGTH;
num_tupples = num_elems / 2;
mask = (1 << (shift-1)) - 1; mask = (1 << (shift-1)) - 1;
if (! conn_len) if (! conn_len)
...@@ -186,40 +186,38 @@ int snd_hda_get_connections(struct hda_codec *codec, hda_nid_t nid, ...@@ -186,40 +186,38 @@ int snd_hda_get_connections(struct hda_codec *codec, hda_nid_t nid,
/* multi connection */ /* multi connection */
conns = 0; conns = 0;
for (i = 0; i < conn_len; i += num_elems) { prev_nid = 0;
parm = snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_CONNECT_LIST, i); for (i = 0; i < conn_len; i++) {
for (j = 0; j < num_tupples; j++) { int range_val;
int range_val; hda_nid_t val, n;
hda_nid_t val1, val2, n;
range_val = parm & (1 << (shift-1)); /* ranges */ if (i % num_elems == 0)
val1 = parm & mask; parm = snd_hda_codec_read(codec, nid, 0,
parm >>= shift; AC_VERB_GET_CONNECT_LIST, i);
val2 = parm & mask; range_val = !! (parm & (1 << (shift-1))); /* ranges */
parm >>= shift; val = parm & mask;
if (range_val) { parm >>= shift;
/* ranges between val1 and val2 */ if (range_val) {
if (val1 > val2) { /* ranges between the previous and this one */
snd_printk(KERN_WARNING "hda_codec: invalid dep_range_val %x:%x\n", val1, val2); if (! prev_nid || prev_nid >= val) {
continue; snd_printk(KERN_WARNING "hda_codec: invalid dep_range_val %x:%x\n", prev_nid, val);
} continue;
for (n = val1; n <= val2; n++) { }
if (conns >= max_conns) for (n = prev_nid + 1; n <= val; n++) {
return -EINVAL; if (conns >= max_conns) {
conn_list[conns++] = n; snd_printk(KERN_ERR "Too many connections\n");
}
} else {
if (! val1)
break;
if (conns >= max_conns)
return -EINVAL;
conn_list[conns++] = val1;
if (! val2)
break;
if (conns >= max_conns)
return -EINVAL; return -EINVAL;
conn_list[conns++] = val2; }
conn_list[conns++] = n;
} }
} else {
if (conns >= max_conns) {
snd_printk(KERN_ERR "Too many connections\n");
return -EINVAL;
}
conn_list[conns++] = val;
} }
prev_nid = val;
} }
return conns; return conns;
} }
...@@ -455,6 +453,27 @@ static void setup_fg_nodes(struct hda_codec *codec) ...@@ -455,6 +453,27 @@ static void setup_fg_nodes(struct hda_codec *codec)
} }
} }
/*
* 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;
}
/* /*
* codec destructor * codec destructor
*/ */
...@@ -467,6 +486,7 @@ static void snd_hda_codec_free(struct hda_codec *codec) ...@@ -467,6 +486,7 @@ static void snd_hda_codec_free(struct hda_codec *codec)
if (codec->patch_ops.free) if (codec->patch_ops.free)
codec->patch_ops.free(codec); codec->patch_ops.free(codec);
kfree(codec->amp_info); kfree(codec->amp_info);
kfree(codec->wcaps);
kfree(codec); kfree(codec);
} }
...@@ -520,6 +540,12 @@ int snd_hda_codec_new(struct hda_bus *bus, unsigned int codec_addr, ...@@ -520,6 +540,12 @@ int snd_hda_codec_new(struct hda_bus *bus, unsigned int codec_addr,
return -ENODEV; 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) { if (! codec->subsystem_id) {
hda_nid_t nid = codec->afg ? codec->afg : codec->mfg; hda_nid_t nid = codec->afg ? codec->afg : codec->mfg;
codec->subsystem_id = snd_hda_codec_read(codec, nid, 0, codec->subsystem_id = snd_hda_codec_read(codec, nid, 0,
...@@ -647,7 +673,7 @@ static u32 query_amp_caps(struct hda_codec *codec, hda_nid_t nid, int direction) ...@@ -647,7 +673,7 @@ static u32 query_amp_caps(struct hda_codec *codec, hda_nid_t nid, int direction)
if (! info) if (! info)
return 0; return 0;
if (! (info->status & INFO_AMP_CAPS)) { if (! (info->status & INFO_AMP_CAPS)) {
if (!(snd_hda_param_read(codec, nid, AC_PAR_AUDIO_WIDGET_CAP) & AC_WCAP_AMP_OVRD)) if (! (get_wcaps(codec, nid) & AC_WCAP_AMP_OVRD))
nid = codec->afg; nid = codec->afg;
info->amp_caps = snd_hda_param_read(codec, nid, direction == HDA_OUTPUT ? info->amp_caps = snd_hda_param_read(codec, nid, direction == HDA_OUTPUT ?
AC_PAR_AMP_OUT_CAP : AC_PAR_AMP_IN_CAP); AC_PAR_AMP_OUT_CAP : AC_PAR_AMP_IN_CAP);
...@@ -1195,6 +1221,31 @@ int snd_hda_create_spdif_in_ctls(struct hda_codec *codec, hda_nid_t nid) ...@@ -1195,6 +1221,31 @@ int snd_hda_create_spdif_in_ctls(struct hda_codec *codec, hda_nid_t nid)
} }
/*
* set power state of the codec
*/
static void hda_set_power_state(struct hda_codec *codec, hda_nid_t fg,
unsigned int power_state)
{
hda_nid_t nid, nid_start;
int nodes;
snd_hda_codec_write(codec, fg, 0, AC_VERB_SET_POWER_STATE,
power_state);
nodes = snd_hda_get_sub_nodes(codec, fg, &nid_start);
for (nid = nid_start; nid < nodes + nid_start; nid++) {
if (get_wcaps(codec, nid) & AC_WCAP_POWER)
snd_hda_codec_write(codec, nid, 0,
AC_VERB_SET_POWER_STATE,
power_state);
}
if (power_state == AC_PWRST_D0)
msleep(10);
}
/** /**
* snd_hda_build_controls - build mixer controls * snd_hda_build_controls - build mixer controls
* @bus: the BUS * @bus: the BUS
...@@ -1222,6 +1273,9 @@ int snd_hda_build_controls(struct hda_bus *bus) ...@@ -1222,6 +1273,9 @@ int snd_hda_build_controls(struct hda_bus *bus)
list_for_each(p, &bus->codec_list) { list_for_each(p, &bus->codec_list) {
struct hda_codec *codec = list_entry(p, struct hda_codec, list); struct hda_codec *codec = list_entry(p, struct hda_codec, list);
int err; int err;
hda_set_power_state(codec,
codec->afg ? codec->afg : codec->mfg,
AC_PWRST_D0);
if (! codec->patch_ops.init) if (! codec->patch_ops.init)
continue; continue;
err = codec->patch_ops.init(codec); err = codec->patch_ops.init(codec);
...@@ -1340,7 +1394,7 @@ int snd_hda_query_supported_pcm(struct hda_codec *codec, hda_nid_t nid, ...@@ -1340,7 +1394,7 @@ int snd_hda_query_supported_pcm(struct hda_codec *codec, hda_nid_t nid,
val = 0; val = 0;
if (nid != codec->afg && if (nid != codec->afg &&
snd_hda_param_read(codec, nid, AC_PAR_AUDIO_WIDGET_CAP) & AC_WCAP_FORMAT_OVRD) { (get_wcaps(codec, nid) & AC_WCAP_FORMAT_OVRD)) {
val = snd_hda_param_read(codec, nid, AC_PAR_PCM); val = snd_hda_param_read(codec, nid, AC_PAR_PCM);
if (val == -1) if (val == -1)
return -EIO; return -EIO;
...@@ -1362,7 +1416,7 @@ int snd_hda_query_supported_pcm(struct hda_codec *codec, hda_nid_t nid, ...@@ -1362,7 +1416,7 @@ int snd_hda_query_supported_pcm(struct hda_codec *codec, hda_nid_t nid,
unsigned int bps; unsigned int bps;
unsigned int wcaps; unsigned int wcaps;
wcaps = snd_hda_param_read(codec, nid, AC_PAR_AUDIO_WIDGET_CAP); wcaps = get_wcaps(codec, nid);
streams = snd_hda_param_read(codec, nid, AC_PAR_STREAM); streams = snd_hda_param_read(codec, nid, AC_PAR_STREAM);
if (streams == -1) if (streams == -1)
return -EIO; return -EIO;
...@@ -1432,7 +1486,7 @@ int snd_hda_is_supported_format(struct hda_codec *codec, hda_nid_t nid, ...@@ -1432,7 +1486,7 @@ int snd_hda_is_supported_format(struct hda_codec *codec, hda_nid_t nid,
unsigned int val = 0, rate, stream; unsigned int val = 0, rate, stream;
if (nid != codec->afg && if (nid != codec->afg &&
snd_hda_param_read(codec, nid, AC_PAR_AUDIO_WIDGET_CAP) & AC_WCAP_FORMAT_OVRD) { (get_wcaps(codec, nid) & AC_WCAP_FORMAT_OVRD)) {
val = snd_hda_param_read(codec, nid, AC_PAR_PCM); val = snd_hda_param_read(codec, nid, AC_PAR_PCM);
if (val == -1) if (val == -1)
return 0; return 0;
...@@ -1658,9 +1712,21 @@ int snd_hda_add_new_ctls(struct hda_codec *codec, struct snd_kcontrol_new *knew) ...@@ -1658,9 +1712,21 @@ int snd_hda_add_new_ctls(struct hda_codec *codec, struct snd_kcontrol_new *knew)
int err; int err;
for (; knew->name; knew++) { for (; knew->name; knew++) {
err = snd_ctl_add(codec->bus->card, snd_ctl_new1(knew, codec)); struct snd_kcontrol *kctl;
if (err < 0) kctl = snd_ctl_new1(knew, codec);
return err; 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; return 0;
} }
...@@ -1874,8 +1940,7 @@ int snd_hda_parse_pin_def_config(struct hda_codec *codec, struct auto_pin_cfg *c ...@@ -1874,8 +1940,7 @@ int snd_hda_parse_pin_def_config(struct hda_codec *codec, struct auto_pin_cfg *c
nodes = snd_hda_get_sub_nodes(codec, codec->afg, &nid_start); nodes = snd_hda_get_sub_nodes(codec, codec->afg, &nid_start);
for (nid = nid_start; nid < nodes + nid_start; nid++) { for (nid = nid_start; nid < nodes + nid_start; nid++) {
unsigned int wid_caps = snd_hda_param_read(codec, nid, unsigned int wid_caps = get_wcaps(codec, nid);
AC_PAR_AUDIO_WIDGET_CAP);
unsigned int wid_type = (wid_caps & AC_WCAP_TYPE) >> AC_WCAP_TYPE_SHIFT; unsigned int wid_type = (wid_caps & AC_WCAP_TYPE) >> AC_WCAP_TYPE_SHIFT;
unsigned int def_conf; unsigned int def_conf;
short assoc, loc; short assoc, loc;
...@@ -1993,6 +2058,9 @@ int snd_hda_suspend(struct hda_bus *bus, pm_message_t state) ...@@ -1993,6 +2058,9 @@ int snd_hda_suspend(struct hda_bus *bus, pm_message_t state)
struct hda_codec *codec = list_entry(p, struct hda_codec, list); struct hda_codec *codec = list_entry(p, struct hda_codec, list);
if (codec->patch_ops.suspend) if (codec->patch_ops.suspend)
codec->patch_ops.suspend(codec, state); codec->patch_ops.suspend(codec, state);
hda_set_power_state(codec,
codec->afg ? codec->afg : codec->mfg,
AC_PWRST_D3);
} }
return 0; return 0;
} }
...@@ -2010,6 +2078,9 @@ int snd_hda_resume(struct hda_bus *bus) ...@@ -2010,6 +2078,9 @@ int snd_hda_resume(struct hda_bus *bus)
list_for_each(p, &bus->codec_list) { list_for_each(p, &bus->codec_list) {
struct hda_codec *codec = list_entry(p, struct hda_codec, list); struct hda_codec *codec = list_entry(p, struct hda_codec, list);
hda_set_power_state(codec,
codec->afg ? codec->afg : codec->mfg,
AC_PWRST_D0);
if (codec->patch_ops.resume) if (codec->patch_ops.resume)
codec->patch_ops.resume(codec); codec->patch_ops.resume(codec);
} }
......
...@@ -214,6 +214,12 @@ enum { ...@@ -214,6 +214,12 @@ enum {
#define AC_PWRST_D2SUP (1<<2) #define AC_PWRST_D2SUP (1<<2)
#define AC_PWRST_D3SUP (1<<3) #define AC_PWRST_D3SUP (1<<3)
/* Power state values */
#define AC_PWRST_D0 0x00
#define AC_PWRST_D1 0x01
#define AC_PWRST_D2 0x02
#define AC_PWRST_D3 0x03
/* Processing capabilies */ /* Processing capabilies */
#define AC_PCAP_BENIGN (1<<0) #define AC_PCAP_BENIGN (1<<0)
#define AC_PCAP_NUM_COEF (0xff<<8) #define AC_PCAP_NUM_COEF (0xff<<8)
...@@ -376,7 +382,7 @@ enum { ...@@ -376,7 +382,7 @@ enum {
}; };
/* max. connections to a widget */ /* max. connections to a widget */
#define HDA_MAX_CONNECTIONS 16 #define HDA_MAX_CONNECTIONS 32
/* max. codec address */ /* max. codec address */
#define HDA_MAX_CODEC_ADDRESS 0x0f #define HDA_MAX_CODEC_ADDRESS 0x0f
...@@ -542,6 +548,11 @@ struct hda_codec { ...@@ -542,6 +548,11 @@ struct hda_codec {
/* codec specific info */ /* codec specific info */
void *spec; void *spec;
/* widget capabilities cache */
unsigned int num_nodes;
hda_nid_t start_nid;
u32 *wcaps;
/* hash for amp access */ /* hash for amp access */
u16 amp_hash[32]; u16 amp_hash[32];
int num_amp_entries; int num_amp_entries;
......
...@@ -87,7 +87,7 @@ int snd_hda_create_spdif_in_ctls(struct hda_codec *codec, hda_nid_t nid); ...@@ -87,7 +87,7 @@ int snd_hda_create_spdif_in_ctls(struct hda_codec *codec, hda_nid_t nid);
/* /*
* input MUX helper * input MUX helper
*/ */
#define HDA_MAX_NUM_INPUTS 8 #define HDA_MAX_NUM_INPUTS 16
struct hda_input_mux_item { struct hda_input_mux_item {
const char *label; const char *label;
unsigned int index; unsigned int index;
...@@ -243,4 +243,16 @@ int snd_hda_parse_pin_def_config(struct hda_codec *codec, struct auto_pin_cfg *c ...@@ -243,4 +243,16 @@ int snd_hda_parse_pin_def_config(struct hda_codec *codec, struct auto_pin_cfg *c
#define PIN_HP 0xc0 #define PIN_HP 0xc0
#define PIN_HP_AMP 0x80 #define PIN_HP_AMP 0x80
/*
* get widget capabilities
*/
static inline u32 get_wcaps(struct hda_codec *codec, hda_nid_t nid)
{
if (nid < codec->start_nid ||
nid >= codec->start_nid + codec->num_nodes)
return snd_hda_param_read(codec, nid, AC_PAR_AUDIO_WIDGET_CAP);
return codec->wcaps[nid - codec->start_nid];
}
#endif /* __SOUND_HDA_LOCAL_H */ #endif /* __SOUND_HDA_LOCAL_H */
...@@ -2091,8 +2091,7 @@ static int patch_alc880(struct hda_codec *codec) ...@@ -2091,8 +2091,7 @@ static int patch_alc880(struct hda_codec *codec)
if (! spec->adc_nids && spec->input_mux) { if (! spec->adc_nids && spec->input_mux) {
/* check whether NID 0x07 is valid */ /* check whether NID 0x07 is valid */
unsigned int wcap = snd_hda_param_read(codec, alc880_adc_nids[0], unsigned int wcap = get_wcaps(codec, alc880_adc_nids[0]);
AC_PAR_AUDIO_WIDGET_CAP);
wcap = (wcap & AC_WCAP_TYPE) >> AC_WCAP_TYPE_SHIFT; /* get type */ wcap = (wcap & AC_WCAP_TYPE) >> AC_WCAP_TYPE_SHIFT; /* get type */
if (wcap != AC_WID_AUD_IN) { if (wcap != AC_WID_AUD_IN) {
spec->adc_nids = alc880_adc_nids_alt; spec->adc_nids = alc880_adc_nids_alt;
......
...@@ -624,7 +624,7 @@ static int stac92xx_auto_create_hp_ctls(struct hda_codec *codec, struct auto_pin ...@@ -624,7 +624,7 @@ static int stac92xx_auto_create_hp_ctls(struct hda_codec *codec, struct auto_pin
if (! pin) if (! pin)
return 0; return 0;
wid_caps = snd_hda_param_read(codec, pin, AC_PAR_AUDIO_WIDGET_CAP); wid_caps = get_wcaps(codec, pin);
if (wid_caps & AC_WCAP_UNSOL_CAP) if (wid_caps & AC_WCAP_UNSOL_CAP)
/* Enable unsolicited responses on the HP widget */ /* Enable unsolicited responses on the HP widget */
snd_hda_codec_write(codec, pin, 0, snd_hda_codec_write(codec, pin, 0,
...@@ -786,33 +786,10 @@ static int stac9200_parse_auto_config(struct hda_codec *codec) ...@@ -786,33 +786,10 @@ static int stac9200_parse_auto_config(struct hda_codec *codec)
return 1; return 1;
} }
static int stac92xx_init_pstate(struct hda_codec *codec)
{
hda_nid_t nid, nid_start;
int nodes;
snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_POWER_STATE, 0x00);
nodes = snd_hda_get_sub_nodes(codec, codec->afg, &nid_start);
for (nid = nid_start; nid < nodes + nid_start; nid++) {
unsigned int wid_caps = snd_hda_param_read(codec, nid,
AC_PAR_AUDIO_WIDGET_CAP);
if (wid_caps & AC_WCAP_POWER)
snd_hda_codec_write(codec, nid, 0,
AC_VERB_SET_POWER_STATE, 0x00);
}
mdelay(100);
return 0;
}
static int stac92xx_init(struct hda_codec *codec) static int stac92xx_init(struct hda_codec *codec)
{ {
struct sigmatel_spec *spec = codec->spec; struct sigmatel_spec *spec = codec->spec;
stac92xx_init_pstate(codec);
snd_hda_sequence_write(codec, spec->init); snd_hda_sequence_write(codec, spec->init);
stac92xx_auto_init_multi_out(codec); stac92xx_auto_init_multi_out(codec);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment