|
@@ -152,7 +152,9 @@ enum {
|
|
XBASS_XOVER,
|
|
XBASS_XOVER,
|
|
EQ_PRESET_ENUM,
|
|
EQ_PRESET_ENUM,
|
|
SMART_VOLUME_ENUM,
|
|
SMART_VOLUME_ENUM,
|
|
- MIC_BOOST_ENUM
|
|
|
|
|
|
+ MIC_BOOST_ENUM,
|
|
|
|
+ AE5_HEADPHONE_GAIN_ENUM,
|
|
|
|
+ AE5_SOUND_FILTER_ENUM
|
|
#define EFFECTS_COUNT (EFFECT_END_NID - EFFECT_START_NID)
|
|
#define EFFECTS_COUNT (EFFECT_END_NID - EFFECT_START_NID)
|
|
};
|
|
};
|
|
|
|
|
|
@@ -689,6 +691,42 @@ static const struct ae5_ca0113_output_set ae5_ca0113_output_presets[] = {
|
|
}
|
|
}
|
|
};
|
|
};
|
|
|
|
|
|
|
|
+/* ae5 ca0113 command sequences to set headphone gain levels. */
|
|
|
|
+#define AE5_HEADPHONE_GAIN_PRESET_MAX_COMMANDS 4
|
|
|
|
+struct ae5_headphone_gain_set {
|
|
|
|
+ char *name;
|
|
|
|
+ unsigned int vals[AE5_HEADPHONE_GAIN_PRESET_MAX_COMMANDS];
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+static const struct ae5_headphone_gain_set ae5_headphone_gain_presets[] = {
|
|
|
|
+ { .name = "Low (16-31",
|
|
|
|
+ .vals = { 0xff, 0x2c, 0xf5, 0x32 }
|
|
|
|
+ },
|
|
|
|
+ { .name = "Medium (32-149",
|
|
|
|
+ .vals = { 0x38, 0xa8, 0x3e, 0x4c }
|
|
|
|
+ },
|
|
|
|
+ { .name = "High (150-600",
|
|
|
|
+ .vals = { 0xff, 0xff, 0xff, 0x7f }
|
|
|
|
+ }
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+struct ae5_filter_set {
|
|
|
|
+ char *name;
|
|
|
|
+ unsigned int val;
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+static const struct ae5_filter_set ae5_filter_presets[] = {
|
|
|
|
+ { .name = "Slow Roll Off",
|
|
|
|
+ .val = 0xa0
|
|
|
|
+ },
|
|
|
|
+ { .name = "Minimum Phase",
|
|
|
|
+ .val = 0xc0
|
|
|
|
+ },
|
|
|
|
+ { .name = "Fast Roll Off",
|
|
|
|
+ .val = 0x80
|
|
|
|
+ }
|
|
|
|
+};
|
|
|
|
+
|
|
enum hda_cmd_vendor_io {
|
|
enum hda_cmd_vendor_io {
|
|
/* for DspIO node */
|
|
/* for DspIO node */
|
|
VENDOR_DSPIO_SCP_WRITE_DATA_LOW = 0x000,
|
|
VENDOR_DSPIO_SCP_WRITE_DATA_LOW = 0x000,
|
|
@@ -990,6 +1028,9 @@ struct ca0132_spec {
|
|
long eq_preset_val;
|
|
long eq_preset_val;
|
|
unsigned int tlv[4];
|
|
unsigned int tlv[4];
|
|
struct hda_vmaster_mute_hook vmaster_mute;
|
|
struct hda_vmaster_mute_hook vmaster_mute;
|
|
|
|
+ /* AE-5 Control values */
|
|
|
|
+ unsigned char ae5_headphone_gain_val;
|
|
|
|
+ unsigned char ae5_filter_val;
|
|
|
|
|
|
|
|
|
|
struct hda_codec *codec;
|
|
struct hda_codec *codec;
|
|
@@ -3224,6 +3265,41 @@ static void ca0113_mmio_command_set(struct hda_codec *codec, unsigned int group,
|
|
readl(spec->mem_base + 0x210);
|
|
readl(spec->mem_base + 0x210);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+/*
|
|
|
|
+ * This second type of command is used for setting the sound filter type.
|
|
|
|
+ */
|
|
|
|
+static void ca0113_mmio_command_set_type2(struct hda_codec *codec,
|
|
|
|
+ unsigned int group, unsigned int target, unsigned int value)
|
|
|
|
+{
|
|
|
|
+ struct ca0132_spec *spec = codec->spec;
|
|
|
|
+ unsigned int write_val;
|
|
|
|
+
|
|
|
|
+ writel(0x0000007e, spec->mem_base + 0x210);
|
|
|
|
+ readl(spec->mem_base + 0x210);
|
|
|
|
+ writel(0x0000005a, spec->mem_base + 0x210);
|
|
|
|
+ readl(spec->mem_base + 0x210);
|
|
|
|
+ readl(spec->mem_base + 0x210);
|
|
|
|
+
|
|
|
|
+ writel(0x00800003, spec->mem_base + 0x20c);
|
|
|
|
+ writel(group, spec->mem_base + 0x804);
|
|
|
|
+
|
|
|
|
+ writel(0x00800005, spec->mem_base + 0x20c);
|
|
|
|
+ write_val = (target & 0xff);
|
|
|
|
+ write_val |= (value << 8);
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ writel(write_val, spec->mem_base + 0x204);
|
|
|
|
+ msleep(20);
|
|
|
|
+ readl(spec->mem_base + 0x860);
|
|
|
|
+ readl(spec->mem_base + 0x854);
|
|
|
|
+ readl(spec->mem_base + 0x840);
|
|
|
|
+
|
|
|
|
+ writel(0x00800004, spec->mem_base + 0x20c);
|
|
|
|
+ writel(0x00000000, spec->mem_base + 0x210);
|
|
|
|
+ readl(spec->mem_base + 0x210);
|
|
|
|
+ readl(spec->mem_base + 0x210);
|
|
|
|
+}
|
|
|
|
+
|
|
/*
|
|
/*
|
|
* Setup GPIO for the other variants of Core3D.
|
|
* Setup GPIO for the other variants of Core3D.
|
|
*/
|
|
*/
|
|
@@ -4048,6 +4124,8 @@ exit:
|
|
return err < 0 ? err : 0;
|
|
return err < 0 ? err : 0;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+static int ae5_headphone_gain_set(struct hda_codec *codec, long val);
|
|
|
|
+
|
|
static void ae5_mmio_select_out(struct hda_codec *codec)
|
|
static void ae5_mmio_select_out(struct hda_codec *codec)
|
|
{
|
|
{
|
|
struct ca0132_spec *spec = codec->spec;
|
|
struct ca0132_spec *spec = codec->spec;
|
|
@@ -4088,6 +4166,7 @@ static void ca0132_alt_select_out_quirk_handler(struct hda_codec *codec)
|
|
break;
|
|
break;
|
|
case QUIRK_AE5:
|
|
case QUIRK_AE5:
|
|
ae5_mmio_select_out(codec);
|
|
ae5_mmio_select_out(codec);
|
|
|
|
+ ae5_headphone_gain_set(codec, 2);
|
|
tmp = FLOAT_ZERO;
|
|
tmp = FLOAT_ZERO;
|
|
dspio_set_uint_param(codec, 0x96, 0x29, tmp);
|
|
dspio_set_uint_param(codec, 0x96, 0x29, tmp);
|
|
dspio_set_uint_param(codec, 0x96, 0x2a, tmp);
|
|
dspio_set_uint_param(codec, 0x96, 0x2a, tmp);
|
|
@@ -4114,6 +4193,8 @@ static void ca0132_alt_select_out_quirk_handler(struct hda_codec *codec)
|
|
break;
|
|
break;
|
|
case QUIRK_AE5:
|
|
case QUIRK_AE5:
|
|
ae5_mmio_select_out(codec);
|
|
ae5_mmio_select_out(codec);
|
|
|
|
+ ae5_headphone_gain_set(codec,
|
|
|
|
+ spec->ae5_headphone_gain_val);
|
|
tmp = FLOAT_ONE;
|
|
tmp = FLOAT_ONE;
|
|
dspio_set_uint_param(codec, 0x96, 0x29, tmp);
|
|
dspio_set_uint_param(codec, 0x96, 0x29, tmp);
|
|
dspio_set_uint_param(codec, 0x96, 0x2a, tmp);
|
|
dspio_set_uint_param(codec, 0x96, 0x2a, tmp);
|
|
@@ -4140,6 +4221,7 @@ static void ca0132_alt_select_out_quirk_handler(struct hda_codec *codec)
|
|
break;
|
|
break;
|
|
case QUIRK_AE5:
|
|
case QUIRK_AE5:
|
|
ae5_mmio_select_out(codec);
|
|
ae5_mmio_select_out(codec);
|
|
|
|
+ ae5_headphone_gain_set(codec, 2);
|
|
tmp = FLOAT_ZERO;
|
|
tmp = FLOAT_ZERO;
|
|
dspio_set_uint_param(codec, 0x96, 0x29, tmp);
|
|
dspio_set_uint_param(codec, 0x96, 0x29, tmp);
|
|
dspio_set_uint_param(codec, 0x96, 0x2a, tmp);
|
|
dspio_set_uint_param(codec, 0x96, 0x2a, tmp);
|
|
@@ -4876,6 +4958,16 @@ static int ca0132_alt_mic_boost_set(struct hda_codec *codec, long val)
|
|
return ret;
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+static int ae5_headphone_gain_set(struct hda_codec *codec, long val)
|
|
|
|
+{
|
|
|
|
+ unsigned int i;
|
|
|
|
+
|
|
|
|
+ for (i = 0; i < 4; i++)
|
|
|
|
+ ca0113_mmio_command_set(codec, 0x48, 0x11 + i,
|
|
|
|
+ ae5_headphone_gain_presets[val].vals[i]);
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+
|
|
static int ca0132_vnode_switch_set(struct snd_kcontrol *kcontrol,
|
|
static int ca0132_vnode_switch_set(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
{
|
|
@@ -5140,6 +5232,112 @@ static int ca0132_alt_mic_boost_put(struct snd_kcontrol *kcontrol,
|
|
return 1;
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+/*
|
|
|
|
+ * Sound BlasterX AE-5 Headphone Gain Controls.
|
|
|
|
+ */
|
|
|
|
+#define AE5_HEADPHONE_GAIN_MAX 3
|
|
|
|
+static int ae5_headphone_gain_info(struct snd_kcontrol *kcontrol,
|
|
|
|
+ struct snd_ctl_elem_info *uinfo)
|
|
|
|
+{
|
|
|
|
+ char *sfx = " Ohms)";
|
|
|
|
+ char namestr[SNDRV_CTL_ELEM_ID_NAME_MAXLEN];
|
|
|
|
+
|
|
|
|
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
|
|
|
|
+ uinfo->count = 1;
|
|
|
|
+ uinfo->value.enumerated.items = AE5_HEADPHONE_GAIN_MAX;
|
|
|
|
+ if (uinfo->value.enumerated.item >= AE5_HEADPHONE_GAIN_MAX)
|
|
|
|
+ uinfo->value.enumerated.item = AE5_HEADPHONE_GAIN_MAX - 1;
|
|
|
|
+ sprintf(namestr, "%s %s",
|
|
|
|
+ ae5_headphone_gain_presets[uinfo->value.enumerated.item].name,
|
|
|
|
+ sfx);
|
|
|
|
+ strcpy(uinfo->value.enumerated.name, namestr);
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int ae5_headphone_gain_get(struct snd_kcontrol *kcontrol,
|
|
|
|
+ struct snd_ctl_elem_value *ucontrol)
|
|
|
|
+{
|
|
|
|
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
|
|
|
|
+ struct ca0132_spec *spec = codec->spec;
|
|
|
|
+
|
|
|
|
+ ucontrol->value.enumerated.item[0] = spec->ae5_headphone_gain_val;
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int ae5_headphone_gain_put(struct snd_kcontrol *kcontrol,
|
|
|
|
+ struct snd_ctl_elem_value *ucontrol)
|
|
|
|
+{
|
|
|
|
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
|
|
|
|
+ struct ca0132_spec *spec = codec->spec;
|
|
|
|
+ int sel = ucontrol->value.enumerated.item[0];
|
|
|
|
+ unsigned int items = AE5_HEADPHONE_GAIN_MAX;
|
|
|
|
+
|
|
|
|
+ if (sel >= items)
|
|
|
|
+ return 0;
|
|
|
|
+
|
|
|
|
+ codec_dbg(codec, "ae5_headphone_gain: boost=%d\n",
|
|
|
|
+ sel);
|
|
|
|
+
|
|
|
|
+ spec->ae5_headphone_gain_val = sel;
|
|
|
|
+
|
|
|
|
+ if (spec->out_enum_val == HEADPHONE_OUT)
|
|
|
|
+ ae5_headphone_gain_set(codec, spec->ae5_headphone_gain_val);
|
|
|
|
+
|
|
|
|
+ return 1;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/*
|
|
|
|
+ * Sound BlasterX AE-5 sound filter enumerated control.
|
|
|
|
+ */
|
|
|
|
+#define AE5_SOUND_FILTER_MAX 3
|
|
|
|
+
|
|
|
|
+static int ae5_sound_filter_info(struct snd_kcontrol *kcontrol,
|
|
|
|
+ struct snd_ctl_elem_info *uinfo)
|
|
|
|
+{
|
|
|
|
+ char namestr[SNDRV_CTL_ELEM_ID_NAME_MAXLEN];
|
|
|
|
+
|
|
|
|
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
|
|
|
|
+ uinfo->count = 1;
|
|
|
|
+ uinfo->value.enumerated.items = AE5_SOUND_FILTER_MAX;
|
|
|
|
+ if (uinfo->value.enumerated.item >= AE5_SOUND_FILTER_MAX)
|
|
|
|
+ uinfo->value.enumerated.item = AE5_SOUND_FILTER_MAX - 1;
|
|
|
|
+ sprintf(namestr, "%s",
|
|
|
|
+ ae5_filter_presets[uinfo->value.enumerated.item].name);
|
|
|
|
+ strcpy(uinfo->value.enumerated.name, namestr);
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int ae5_sound_filter_get(struct snd_kcontrol *kcontrol,
|
|
|
|
+ struct snd_ctl_elem_value *ucontrol)
|
|
|
|
+{
|
|
|
|
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
|
|
|
|
+ struct ca0132_spec *spec = codec->spec;
|
|
|
|
+
|
|
|
|
+ ucontrol->value.enumerated.item[0] = spec->ae5_filter_val;
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int ae5_sound_filter_put(struct snd_kcontrol *kcontrol,
|
|
|
|
+ struct snd_ctl_elem_value *ucontrol)
|
|
|
|
+{
|
|
|
|
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
|
|
|
|
+ struct ca0132_spec *spec = codec->spec;
|
|
|
|
+ int sel = ucontrol->value.enumerated.item[0];
|
|
|
|
+ unsigned int items = AE5_SOUND_FILTER_MAX;
|
|
|
|
+
|
|
|
|
+ if (sel >= items)
|
|
|
|
+ return 0;
|
|
|
|
+
|
|
|
|
+ codec_dbg(codec, "ae5_sound_filter: %s\n",
|
|
|
|
+ ae5_filter_presets[sel].name);
|
|
|
|
+
|
|
|
|
+ spec->ae5_filter_val = sel;
|
|
|
|
+
|
|
|
|
+ ca0113_mmio_command_set_type2(codec, 0x48, 0x07,
|
|
|
|
+ ae5_filter_presets[sel].val);
|
|
|
|
+
|
|
|
|
+ return 1;
|
|
|
|
+}
|
|
|
|
|
|
/*
|
|
/*
|
|
* Input Select Control for alternative ca0132 codecs. This exists because
|
|
* Input Select Control for alternative ca0132 codecs. This exists because
|
|
@@ -5902,6 +6100,40 @@ static int ca0132_alt_add_mic_boost_enum(struct hda_codec *codec)
|
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+/*
|
|
|
|
+ * Add headphone gain enumerated control for the AE-5. This switches between
|
|
|
|
+ * three modes, low, medium, and high. When non-headphone outputs are selected,
|
|
|
|
+ * it is automatically set to high. This is the same behavior as Windows.
|
|
|
|
+ */
|
|
|
|
+static int ae5_add_headphone_gain_enum(struct hda_codec *codec)
|
|
|
|
+{
|
|
|
|
+ struct snd_kcontrol_new knew =
|
|
|
|
+ HDA_CODEC_MUTE_MONO("AE-5: Headphone Gain",
|
|
|
|
+ AE5_HEADPHONE_GAIN_ENUM, 1, 0, HDA_INPUT);
|
|
|
|
+ knew.info = ae5_headphone_gain_info;
|
|
|
|
+ knew.get = ae5_headphone_gain_get;
|
|
|
|
+ knew.put = ae5_headphone_gain_put;
|
|
|
|
+ return snd_hda_ctl_add(codec, AE5_HEADPHONE_GAIN_ENUM,
|
|
|
|
+ snd_ctl_new1(&knew, codec));
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/*
|
|
|
|
+ * Add sound filter enumerated control for the AE-5. This adds three different
|
|
|
|
+ * settings: Slow Roll Off, Minimum Phase, and Fast Roll Off. From what I've
|
|
|
|
+ * read into it, it changes the DAC's interpolation filter.
|
|
|
|
+ */
|
|
|
|
+static int ae5_add_sound_filter_enum(struct hda_codec *codec)
|
|
|
|
+{
|
|
|
|
+ struct snd_kcontrol_new knew =
|
|
|
|
+ HDA_CODEC_MUTE_MONO("AE-5: Sound Filter",
|
|
|
|
+ AE5_SOUND_FILTER_ENUM, 1, 0, HDA_INPUT);
|
|
|
|
+ knew.info = ae5_sound_filter_info;
|
|
|
|
+ knew.get = ae5_sound_filter_get;
|
|
|
|
+ knew.put = ae5_sound_filter_put;
|
|
|
|
+ return snd_hda_ctl_add(codec, AE5_SOUND_FILTER_ENUM,
|
|
|
|
+ snd_ctl_new1(&knew, codec));
|
|
|
|
+}
|
|
|
|
+
|
|
/*
|
|
/*
|
|
* Need to create slave controls for the alternate codecs that have surround
|
|
* Need to create slave controls for the alternate codecs that have surround
|
|
* capabilities.
|
|
* capabilities.
|
|
@@ -6122,6 +6354,11 @@ static int ca0132_build_controls(struct hda_codec *codec)
|
|
ca0132_alt_add_input_enum(codec);
|
|
ca0132_alt_add_input_enum(codec);
|
|
ca0132_alt_add_mic_boost_enum(codec);
|
|
ca0132_alt_add_mic_boost_enum(codec);
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ if (spec->quirk == QUIRK_AE5) {
|
|
|
|
+ ae5_add_headphone_gain_enum(codec);
|
|
|
|
+ ae5_add_sound_filter_enum(codec);
|
|
|
|
+ }
|
|
#ifdef ENABLE_TUNING_CONTROLS
|
|
#ifdef ENABLE_TUNING_CONTROLS
|
|
add_tuning_ctls(codec);
|
|
add_tuning_ctls(codec);
|
|
#endif
|
|
#endif
|