* amp access functions
*/
-#define HDA_HASH_KEY(nid,dir,idx) (u32)((nid) + (idx) * 32 + (dir) * 64)
+/* FIXME: more better hash key? */
+#define HDA_HASH_KEY(nid,dir,idx) (u32)((nid) + ((idx) << 16) + ((dir) << 24))
#define INFO_AMP_CAPS (1<<0)
-#define INFO_AMP_VOL (1<<1)
+#define INFO_AMP_VOL(ch) (1 << (1 + (ch)))
/* initialize the hash table */
static void init_amp_hash(struct hda_codec *codec)
/*
* read the current volume to info
- * if the cache exists, read from the cache.
+ * if the cache exists, read the cache value.
*/
-static void get_vol_mute(struct hda_codec *codec, struct hda_amp_info *info,
+static unsigned int get_vol_mute(struct hda_codec *codec, struct hda_amp_info *info,
hda_nid_t nid, int ch, int direction, int index)
{
u32 val, parm;
- if (info->status & (INFO_AMP_VOL << ch))
- return;
+ if (info->status & INFO_AMP_VOL(ch))
+ return info->vol[ch];
parm = ch ? AC_AMP_GET_RIGHT : AC_AMP_GET_LEFT;
parm |= direction == HDA_OUTPUT ? AC_AMP_GET_OUTPUT : AC_AMP_GET_INPUT;
parm |= index;
val = snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_AMP_GAIN_MUTE, parm);
info->vol[ch] = val & 0xff;
- info->status |= INFO_AMP_VOL << ch;
+ info->status |= INFO_AMP_VOL(ch);
+ return info->vol[ch];
}
/*
- * write the current volume in info to the h/w
+ * write the current volume in info to the h/w and update the cache
*/
-static void put_vol_mute(struct hda_codec *codec,
+static void put_vol_mute(struct hda_codec *codec, struct hda_amp_info *info,
hda_nid_t nid, int ch, int direction, int index, int val)
{
u32 parm;
parm |= index << AC_AMP_SET_INDEX_SHIFT;
parm |= val;
snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_AMP_GAIN_MUTE, parm);
+ info->vol[ch] = val;
}
/*
- * read/write AMP value. The volume is between 0 to 0x7f, 0x80 = mute bit.
+ * read AMP value. The volume is between 0 to 0x7f, 0x80 = mute bit.
*/
static int snd_hda_codec_amp_read(struct hda_codec *codec, hda_nid_t nid, int ch, int direction, int index)
{
struct hda_amp_info *info = get_alloc_amp_hash(codec, HDA_HASH_KEY(nid, direction, index));
if (! info)
return 0;
- get_vol_mute(codec, info, nid, ch, direction, index);
- return info->vol[ch];
+ return get_vol_mute(codec, info, nid, ch, direction, index);
}
-static int snd_hda_codec_amp_write(struct hda_codec *codec, hda_nid_t nid, int ch, int direction, int idx, int val)
+/*
+ * update the AMP value, mask = bit mask to set, val = the value
+ */
+static int snd_hda_codec_amp_update(struct hda_codec *codec, hda_nid_t nid, int ch, int direction, int idx, int mask, int val)
{
struct hda_amp_info *info = get_alloc_amp_hash(codec, HDA_HASH_KEY(nid, direction, idx));
+
if (! info)
return 0;
- get_vol_mute(codec, info, nid, ch, direction, idx);
+ val &= mask;
+ val |= get_vol_mute(codec, info, nid, ch, direction, idx) & ~mask;
if (info->vol[ch] == val && ! codec->in_resume)
return 0;
- put_vol_mute(codec, nid, ch, direction, idx, val);
- info->vol[ch] = val;
+ put_vol_mute(codec, info, nid, ch, direction, idx, val);
return 1;
}
int chs = get_amp_channels(kcontrol);
int dir = get_amp_direction(kcontrol);
int idx = get_amp_index(kcontrol);
- int val;
long *valp = ucontrol->value.integer.value;
int change = 0;
- if (chs & 1) {
- val = *valp & 0x7f;
- val |= snd_hda_codec_amp_read(codec, nid, 0, dir, idx) & 0x80;
- change = snd_hda_codec_amp_write(codec, nid, 0, dir, idx, val);
- valp++;
- }
- if (chs & 2) {
- val = *valp & 0x7f;
- val |= snd_hda_codec_amp_read(codec, nid, 1, dir, idx) & 0x80;
- change |= snd_hda_codec_amp_write(codec, nid, 1, dir, idx, val);
- }
+ if (chs & 1)
+ change = snd_hda_codec_amp_update(codec, nid, 0, dir, idx,
+ 0x7f, *valp);
+ if (chs & 2)
+ change |= snd_hda_codec_amp_update(codec, nid, 1, dir, idx,
+ 0x7f, valp[1]);
return change;
}
int chs = get_amp_channels(kcontrol);
int dir = get_amp_direction(kcontrol);
int idx = get_amp_index(kcontrol);
- int val;
long *valp = ucontrol->value.integer.value;
int change = 0;
- if (chs & 1) {
- val = snd_hda_codec_amp_read(codec, nid, 0, dir, idx) & 0x7f;
- val |= *valp ? 0 : 0x80;
- change = snd_hda_codec_amp_write(codec, nid, 0, dir, idx, val);
- valp++;
- }
- if (chs & 2) {
- val = snd_hda_codec_amp_read(codec, nid, 1, dir, idx) & 0x7f;
- val |= *valp ? 0 : 0x80;
- change = snd_hda_codec_amp_write(codec, nid, 1, dir, idx, val);
- }
+ if (chs & 1)
+ change = snd_hda_codec_amp_update(codec, nid, 0, dir, idx,
+ 0x80, *valp ? 0 : 0x80);
+ if (chs & 2)
+ change |= snd_hda_codec_amp_update(codec, nid, 1, dir, idx,
+ 0x80, valp[1] ? 0 : 0x80);
return change;
}
*
* If no entries are matching, the function returns a negative value.
*/
-int snd_hda_check_board_config(struct hda_codec *codec, struct hda_board_config *tbl)
+int snd_hda_check_board_config(struct hda_codec *codec, const struct hda_board_config *tbl)
{
- struct hda_board_config *c;
+ const struct hda_board_config *c;
if (codec->bus->modelname) {
for (c = tbl; c->modelname || c->pci_subvendor; c++) {
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 == subsystem_device)
+ (! c->pci_subdevice /* all match */||
+ (c->pci_subdevice == subsystem_device)))
return c->config;
}
}
snd_hda_codec_setup_stream(codec, mout->hp_nid, stream_tag, 0, format);
/* surrounds */
for (i = 1; i < mout->num_dacs; i++) {
- if (i == HDA_REAR && chs == 2) /* copy front to rear */
- snd_hda_codec_setup_stream(codec, nids[i], stream_tag, 0, format);
- else if (chs >= (i + 1) * 2) /* independent out */
+ if (chs >= (i + 1) * 2) /* independent out */
snd_hda_codec_setup_stream(codec, nids[i], stream_tag, i * 2,
format);
+ else /* copy front */
+ snd_hda_codec_setup_stream(codec, nids[i], stream_tag, 0,
+ format);
}
return 0;
}
return 0;
}
+/*
+ * Helper for automatic ping configuration
+ */
+/* parse all pin widgets and store the useful pin nids to cfg */
+int snd_hda_parse_pin_def_config(struct hda_codec *codec, struct auto_pin_cfg *cfg)
+{
+ hda_nid_t nid, nid_start;
+ int i, j, nodes;
+ short seq, sequences[4], assoc_line_out;
+
+ memset(cfg, 0, sizeof(*cfg));
+
+ memset(sequences, 0, sizeof(sequences));
+ assoc_line_out = 0;
+
+ 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);
+ unsigned int wid_type = (wid_caps & AC_WCAP_TYPE) >> AC_WCAP_TYPE_SHIFT;
+ unsigned int def_conf;
+ short assoc, loc;
+
+ /* read all default configuration for pin complex */
+ if (wid_type != AC_WID_PIN)
+ continue;
+ def_conf = snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_CONFIG_DEFAULT, 0);
+ if (get_defcfg_connect(def_conf) == AC_JACK_PORT_NONE)
+ continue;
+ loc = get_defcfg_location(def_conf);
+ switch (get_defcfg_device(def_conf)) {
+ case AC_JACK_LINE_OUT:
+ case AC_JACK_SPEAKER:
+ seq = get_defcfg_sequence(def_conf);
+ assoc = get_defcfg_association(def_conf);
+ if (! assoc)
+ continue;
+ if (! assoc_line_out)
+ assoc_line_out = assoc;
+ else if (assoc_line_out != assoc)
+ continue;
+ if (cfg->line_outs >= ARRAY_SIZE(cfg->line_out_pins))
+ continue;
+ cfg->line_out_pins[cfg->line_outs] = nid;
+ sequences[cfg->line_outs] = seq;
+ cfg->line_outs++;
+ break;
+ case AC_JACK_HP_OUT:
+ cfg->hp_pin = nid;
+ break;
+ case AC_JACK_MIC_IN:
+ if (loc == AC_JACK_LOC_FRONT)
+ cfg->input_pins[AUTO_PIN_FRONT_MIC] = nid;
+ else
+ cfg->input_pins[AUTO_PIN_MIC] = nid;
+ break;
+ case AC_JACK_LINE_IN:
+ if (loc == AC_JACK_LOC_FRONT)
+ cfg->input_pins[AUTO_PIN_FRONT_LINE] = nid;
+ else
+ cfg->input_pins[AUTO_PIN_LINE] = nid;
+ break;
+ case AC_JACK_CD:
+ cfg->input_pins[AUTO_PIN_CD] = nid;
+ break;
+ case AC_JACK_AUX:
+ cfg->input_pins[AUTO_PIN_AUX] = nid;
+ break;
+ case AC_JACK_SPDIF_OUT:
+ cfg->dig_out_pin = nid;
+ break;
+ case AC_JACK_SPDIF_IN:
+ cfg->dig_in_pin = nid;
+ break;
+ }
+ }
+
+ /* sort by sequence */
+ for (i = 0; i < cfg->line_outs; i++)
+ for (j = i + 1; j < cfg->line_outs; j++)
+ if (sequences[i] > sequences[j]) {
+ seq = sequences[i];
+ sequences[i] = sequences[j];
+ sequences[j] = seq;
+ nid = cfg->line_out_pins[i];
+ cfg->line_out_pins[i] = cfg->line_out_pins[j];
+ cfg->line_out_pins[j] = nid;
+ }
+
+ /* Swap surround and CLFE: the association order is front/CLFE/surr/back */
+ if (cfg->line_outs >= 3) {
+ nid = cfg->line_out_pins[1];
+ cfg->line_out_pins[1] = cfg->line_out_pins[2];
+ cfg->line_out_pins[2] = nid;
+ }
+
+ return 0;
+}
+
#ifdef CONFIG_PM
/*
* power management