ALSA: hda/ca0132: Add DSP Volume set and New mixers for SBZ + R3Di
authorConnor McAdams <conmanx360@gmail.com>
Tue, 8 May 2018 17:20:11 +0000 (13:20 -0400)
committerTakashi Iwai <tiwai@suse.de>
Sun, 13 May 2018 07:29:31 +0000 (09:29 +0200)
Adds lookup table for floating point decibel volume, and new functions
to allow for setting the decibel level on the DSP.

Signed-off-by: Connor McAdams <conmanx360@gmail.com>
Reviewed-by: Takashi Sakamoto <o-takashi@sakamocchi.jp>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
sound/pci/hda/patch_ca0132.c

index 60e8a0ce530ebd260b624e5e6f1e47a7e8cbb96e..394e604c37877f80fd0f4542152ac897a730b31c 100644 (file)
@@ -541,6 +541,31 @@ static const struct ca0132_alt_out_set alt_out_presets[] = {
        }
 };
 
+/*
+ * DSP volume setting structs. Req 1 is left volume, req 2 is right volume,
+ * and I don't know what the third req is, but it's always zero. I assume it's
+ * some sort of update or set command to tell the DSP there's new volume info.
+ */
+#define DSP_VOL_OUT 0
+#define DSP_VOL_IN  1
+
+struct ct_dsp_volume_ctl {
+       hda_nid_t vnid;
+       int mid; /* module ID*/
+       unsigned int reqs[3]; /* scp req ID */
+};
+
+static struct ct_dsp_volume_ctl ca0132_alt_vol_ctls[] = {
+       { .vnid = VNID_SPK,
+         .mid = 0x32,
+         .reqs = {3, 4, 2}
+       },
+       { .vnid = VNID_MIC,
+         .mid = 0x37,
+         .reqs = {2, 3, 1}
+       }
+};
+
 enum hda_cmd_vendor_io {
        /* for DspIO node */
        VENDOR_DSPIO_SCP_WRITE_DATA_LOW      = 0x000,
@@ -3252,6 +3277,24 @@ static unsigned int ca0132_capture_pcm_delay(struct hda_pcm_stream *info,
          .tlv = { .c = ca0132_volume_tlv }, \
          .private_value = HDA_COMPOSE_AMP_VAL(nid, channel, 0, dir) }
 
+/*
+ * Creates a mixer control that uses defaults of HDA_CODEC_VOL except for the
+ * volume put, which is used for setting the DSP volume. This was done because
+ * the ca0132 functions were taking too much time and causing lag.
+ */
+#define CA0132_ALT_CODEC_VOL_MONO(xname, nid, channel, dir) \
+       { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+         .name = xname, \
+         .subdevice = HDA_SUBDEV_AMP_FLAG, \
+         .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | \
+                       SNDRV_CTL_ELEM_ACCESS_TLV_READ | \
+                       SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK, \
+         .info = snd_hda_mixer_amp_volume_info, \
+         .get = snd_hda_mixer_amp_volume_get, \
+         .put = ca0132_alt_volume_put, \
+         .tlv = { .c = snd_hda_mixer_amp_tlv }, \
+         .private_value = HDA_COMPOSE_AMP_VAL(nid, channel, 0, dir) }
+
 #define CA0132_CODEC_MUTE_MONO(xname, nid, channel, dir) \
        { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
          .name = xname, \
@@ -3264,9 +3307,40 @@ static unsigned int ca0132_capture_pcm_delay(struct hda_pcm_stream *info,
 /* stereo */
 #define CA0132_CODEC_VOL(xname, nid, dir) \
        CA0132_CODEC_VOL_MONO(xname, nid, 3, dir)
+#define CA0132_ALT_CODEC_VOL(xname, nid, dir) \
+       CA0132_ALT_CODEC_VOL_MONO(xname, nid, 3, dir)
 #define CA0132_CODEC_MUTE(xname, nid, dir) \
        CA0132_CODEC_MUTE_MONO(xname, nid, 3, dir)
 
+/* lookup tables */
+/*
+ * Lookup table with decibel values for the DSP. When volume is changed in
+ * Windows, the DSP is also sent the dB value in floating point. In Windows,
+ * these values have decimal points, probably because the Windows driver
+ * actually uses floating point. We can't here, so I made a lookup table of
+ * values -90 to 9. -90 is the lowest decibel value for both the ADC's and the
+ * DAC's, and 9 is the maximum.
+ */
+static const unsigned int float_vol_db_lookup[] = {
+0xC2B40000, 0xC2B20000, 0xC2B00000, 0xC2AE0000, 0xC2AC0000, 0xC2AA0000,
+0xC2A80000, 0xC2A60000, 0xC2A40000, 0xC2A20000, 0xC2A00000, 0xC29E0000,
+0xC29C0000, 0xC29A0000, 0xC2980000, 0xC2960000, 0xC2940000, 0xC2920000,
+0xC2900000, 0xC28E0000, 0xC28C0000, 0xC28A0000, 0xC2880000, 0xC2860000,
+0xC2840000, 0xC2820000, 0xC2800000, 0xC27C0000, 0xC2780000, 0xC2740000,
+0xC2700000, 0xC26C0000, 0xC2680000, 0xC2640000, 0xC2600000, 0xC25C0000,
+0xC2580000, 0xC2540000, 0xC2500000, 0xC24C0000, 0xC2480000, 0xC2440000,
+0xC2400000, 0xC23C0000, 0xC2380000, 0xC2340000, 0xC2300000, 0xC22C0000,
+0xC2280000, 0xC2240000, 0xC2200000, 0xC21C0000, 0xC2180000, 0xC2140000,
+0xC2100000, 0xC20C0000, 0xC2080000, 0xC2040000, 0xC2000000, 0xC1F80000,
+0xC1F00000, 0xC1E80000, 0xC1E00000, 0xC1D80000, 0xC1D00000, 0xC1C80000,
+0xC1C00000, 0xC1B80000, 0xC1B00000, 0xC1A80000, 0xC1A00000, 0xC1980000,
+0xC1900000, 0xC1880000, 0xC1800000, 0xC1700000, 0xC1600000, 0xC1500000,
+0xC1400000, 0xC1300000, 0xC1200000, 0xC1100000, 0xC1000000, 0xC0E00000,
+0xC0C00000, 0xC0A00000, 0xC0800000, 0xC0400000, 0xC0000000, 0xBF800000,
+0x00000000, 0x3F800000, 0x40000000, 0x40400000, 0x40800000, 0x40A00000,
+0x40C00000, 0x40E00000, 0x41000000, 0x41100000
+};
+
 /* The following are for tuning of products */
 #ifdef ENABLE_TUNING_CONTROLS
 
@@ -4633,6 +4707,41 @@ exit:
 /*
  * Volume related
  */
+/*
+ * Sets the internal DSP decibel level to match the DAC for output, and the
+ * ADC for input. Currently only the SBZ sets dsp capture volume level, and
+ * all alternative codecs set DSP playback volume.
+ */
+static void ca0132_alt_dsp_volume_put(struct hda_codec *codec, hda_nid_t nid)
+{
+       struct ca0132_spec *spec = codec->spec;
+       unsigned int dsp_dir;
+       unsigned int lookup_val;
+
+       if (nid == VNID_SPK)
+               dsp_dir = DSP_VOL_OUT;
+       else
+               dsp_dir = DSP_VOL_IN;
+
+       lookup_val = spec->vnode_lvol[nid - VNODE_START_NID];
+
+       dspio_set_uint_param(codec,
+               ca0132_alt_vol_ctls[dsp_dir].mid,
+               ca0132_alt_vol_ctls[dsp_dir].reqs[0],
+               float_vol_db_lookup[lookup_val]);
+
+       lookup_val = spec->vnode_rvol[nid - VNODE_START_NID];
+
+       dspio_set_uint_param(codec,
+               ca0132_alt_vol_ctls[dsp_dir].mid,
+               ca0132_alt_vol_ctls[dsp_dir].reqs[1],
+               float_vol_db_lookup[lookup_val]);
+
+       dspio_set_uint_param(codec,
+               ca0132_alt_vol_ctls[dsp_dir].mid,
+               ca0132_alt_vol_ctls[dsp_dir].reqs[2], FLOAT_ZERO);
+}
+
 static int ca0132_volume_info(struct snd_kcontrol *kcontrol,
                              struct snd_ctl_elem_info *uinfo)
 {
@@ -4734,6 +4843,51 @@ static int ca0132_volume_put(struct snd_kcontrol *kcontrol,
        return changed;
 }
 
+/*
+ * This function is the same as the one above, because using an if statement
+ * inside of the above volume control for the DSP volume would cause too much
+ * lag. This is a lot more smooth.
+ */
+static int ca0132_alt_volume_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;
+       hda_nid_t nid = get_amp_nid(kcontrol);
+       int ch = get_amp_channels(kcontrol);
+       long *valp = ucontrol->value.integer.value;
+       hda_nid_t vnid = 0;
+       int changed = 1;
+
+       switch (nid) {
+       case 0x02:
+               vnid = VNID_SPK;
+               break;
+       case 0x07:
+               vnid = VNID_MIC;
+               break;
+       }
+
+       /* store the left and right volume */
+       if (ch & 1) {
+               spec->vnode_lvol[vnid - VNODE_START_NID] = *valp;
+               valp++;
+       }
+       if (ch & 2) {
+               spec->vnode_rvol[vnid - VNODE_START_NID] = *valp;
+               valp++;
+       }
+
+       snd_hda_power_up(codec);
+       ca0132_alt_dsp_volume_put(codec, vnid);
+       mutex_lock(&codec->control_mutex);
+       changed = snd_hda_mixer_amp_volume_put(kcontrol, ucontrol);
+       mutex_unlock(&codec->control_mutex);
+       snd_hda_power_down(codec);
+
+       return changed;
+}
+
 static int ca0132_volume_tlv(struct snd_kcontrol *kcontrol, int op_flag,
                             unsigned int size, unsigned int __user *tlv)
 {
@@ -4853,6 +5007,39 @@ static struct snd_kcontrol_new ca0132_mixer[] = {
        { } /* end */
 };
 
+/*
+ * SBZ specific control mixer. Removes auto-detect for mic, and adds surround
+ * controls. Also sets both the Front Playback and Capture Volume controls to
+ * alt so they set the DSP's decibel level.
+ */
+static struct snd_kcontrol_new sbz_mixer[] = {
+       CA0132_ALT_CODEC_VOL("Front Playback Volume", 0x02, HDA_OUTPUT),
+       CA0132_CODEC_MUTE("Front Playback Switch", VNID_SPK, HDA_OUTPUT),
+       CA0132_ALT_CODEC_VOL("Capture Volume", 0x07, HDA_INPUT),
+       CA0132_CODEC_MUTE("Capture Switch", VNID_MIC, HDA_INPUT),
+       HDA_CODEC_VOLUME("What U Hear Capture Volume", 0x0a, 0, HDA_INPUT),
+       HDA_CODEC_MUTE("What U Hear Capture Switch", 0x0a, 0, HDA_INPUT),
+       CA0132_CODEC_MUTE_MONO("HP/Speaker Auto Detect Playback Switch",
+                               VNID_HP_ASEL, 1, HDA_OUTPUT),
+       { } /* end */
+};
+
+/*
+ * Same as the Sound Blaster Z, except doesn't use the alt volume for capture
+ * because it doesn't set decibel levels for the DSP for capture.
+ */
+static struct snd_kcontrol_new r3di_mixer[] = {
+       CA0132_ALT_CODEC_VOL("Front Playback Volume", 0x02, HDA_OUTPUT),
+       CA0132_CODEC_MUTE("Front Playback Switch", VNID_SPK, HDA_OUTPUT),
+       CA0132_CODEC_VOL("Capture Volume", VNID_MIC, HDA_INPUT),
+       CA0132_CODEC_MUTE("Capture Switch", VNID_MIC, HDA_INPUT),
+       HDA_CODEC_VOLUME("What U Hear Capture Volume", 0x0a, 0, HDA_INPUT),
+       HDA_CODEC_MUTE("What U Hear Capture Switch", 0x0a, 0, HDA_INPUT),
+       CA0132_CODEC_MUTE_MONO("HP/Speaker Auto Detect Playback Switch",
+                               VNID_HP_ASEL, 1, HDA_OUTPUT),
+       { } /* end */
+};
+
 static int ca0132_build_controls(struct hda_codec *codec)
 {
        struct ca0132_spec *spec = codec->spec;
@@ -6566,7 +6753,21 @@ static int patch_ca0132(struct hda_codec *codec)
 
        spec->dsp_state = DSP_DOWNLOAD_INIT;
        spec->num_mixers = 1;
-       spec->mixers[0] = ca0132_mixer;
+
+       /* Set which mixers each quirk uses. */
+       switch (spec->quirk) {
+       case QUIRK_SBZ:
+               spec->mixers[0] = sbz_mixer;
+               snd_hda_codec_set_name(codec, "Sound Blaster Z");
+               break;
+       case QUIRK_R3DI:
+               spec->mixers[0] = r3di_mixer;
+               snd_hda_codec_set_name(codec, "Recon3Di");
+               break;
+       default:
+               spec->mixers[0] = ca0132_mixer;
+               break;
+       }
 
        /* Setup whether or not to use alt functions */
        switch (spec->quirk) {