diff mbox

[v2] ALSA: hda - Enable mic mute hotkey and LEDs for an HP machine

Message ID 1420642213-21458-1-git-send-email-david.henningsson@canonical.com (mailing list archive)
State Accepted
Commit 33f4acd3b214fee9662cbaf569a4876b8604f152
Headers show

Commit Message

David Henningsson Jan. 7, 2015, 2:50 p.m. UTC
On this machine, the mic mute hotkey is connected to GPIO2. We therefore
create an extra input device for this key.

Also enable LEDs connected to GPIO3 and GPIO4.

(Note: if this patch is backported to older kernels, where KEY_MIC_MUTE is
not available, change the KEY_MIC_MUTE to KEY_F20, which was previously used
for mic mute keys.)

BugLink: https://bugs.launchpad.net/bugs/1408295
Tested-by: Keng-Yu Lin <keng-yu.lin@canonical.com>
Signed-off-by: David Henningsson <david.henningsson@canonical.com>
---

Changes since v1 (which was by mistake not posted to alsa-devel)
 - fixes according to Takashi's review comments

 sound/pci/hda/patch_realtek.c | 88 +++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 88 insertions(+)

Comments

Takashi Iwai Jan. 7, 2015, 2:59 p.m. UTC | #1
At Wed,  7 Jan 2015 15:50:13 +0100,
David Henningsson wrote:
> 
> On this machine, the mic mute hotkey is connected to GPIO2. We therefore
> create an extra input device for this key.
> 
> Also enable LEDs connected to GPIO3 and GPIO4.
> 
> (Note: if this patch is backported to older kernels, where KEY_MIC_MUTE is
> not available, change the KEY_MIC_MUTE to KEY_F20, which was previously used
> for mic mute keys.)
> 
> BugLink: https://bugs.launchpad.net/bugs/1408295
> Tested-by: Keng-Yu Lin <keng-yu.lin@canonical.com>
> Signed-off-by: David Henningsson <david.henningsson@canonical.com>
> ---
> 
> Changes since v1 (which was by mistake not posted to alsa-devel)
>  - fixes according to Takashi's review comments

Thanks, applied now.


Takashi

> 
>  sound/pci/hda/patch_realtek.c | 88 +++++++++++++++++++++++++++++++++++++++++++
>  1 file changed, 88 insertions(+)
> 
> diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c
> index 65f1f4e..09d2131 100644
> --- a/sound/pci/hda/patch_realtek.c
> +++ b/sound/pci/hda/patch_realtek.c
> @@ -29,6 +29,7 @@
>  #include <linux/pci.h>
>  #include <linux/dmi.h>
>  #include <linux/module.h>
> +#include <linux/input.h>
>  #include <sound/core.h>
>  #include <sound/jack.h>
>  #include "hda_codec.h"
> @@ -120,6 +121,9 @@ struct alc_spec {
>  	hda_nid_t pll_nid;
>  	unsigned int pll_coef_idx, pll_coef_bit;
>  	unsigned int coef0;
> +#if IS_ENABLED(CONFIG_INPUT)
> +	struct input_dev *kb_dev;
> +#endif
>  };
>  
>  /*
> @@ -3472,6 +3476,84 @@ static void alc280_fixup_hp_gpio4(struct hda_codec *codec,
>  	}
>  }
>  
> +#if IS_ENABLED(CONFIG_INPUT)
> +static void gpio2_mic_hotkey_event(struct hda_codec *codec,
> +				   struct hda_jack_callback *event)
> +{
> +	struct alc_spec *spec = codec->spec;
> +
> +	/* GPIO2 just toggles on a keypress/keyrelease cycle. Therefore
> +	   send both key on and key off event for every interrupt. */
> +	input_report_key(spec->kb_dev, KEY_MICMUTE, 1);
> +	input_sync(spec->kb_dev);
> +	input_report_key(spec->kb_dev, KEY_MICMUTE, 0);
> +	input_sync(spec->kb_dev);
> +}
> +#endif
> +
> +static void alc280_fixup_hp_gpio2_mic_hotkey(struct hda_codec *codec,
> +					     const struct hda_fixup *fix, int action)
> +{
> +#if IS_ENABLED(CONFIG_INPUT)
> +	/* GPIO1 = set according to SKU external amp
> +	   GPIO2 = mic mute hotkey
> +	   GPIO3 = mute LED
> +	   GPIO4 = mic mute LED */
> +	static const struct hda_verb gpio_init[] = {
> +		{ 0x01, AC_VERB_SET_GPIO_MASK, 0x1e },
> +		{ 0x01, AC_VERB_SET_GPIO_DIRECTION, 0x1a },
> +		{ 0x01, AC_VERB_SET_GPIO_DATA, 0x02 },
> +		{}
> +	};
> +
> +	struct alc_spec *spec = codec->spec;
> +
> +	if (action == HDA_FIXUP_ACT_PRE_PROBE) {
> +		spec->kb_dev = input_allocate_device();
> +		if (!spec->kb_dev) {
> +			codec_err(codec, "Out of memory (input_allocate_device)\n");
> +			return;
> +		}
> +		spec->kb_dev->name = "Microphone Mute Button";
> +		spec->kb_dev->evbit[0] = BIT_MASK(EV_KEY);
> +		spec->kb_dev->keybit[BIT_WORD(KEY_MICMUTE)] = BIT_MASK(KEY_MICMUTE);
> +		if (input_register_device(spec->kb_dev)) {
> +			codec_err(codec, "input_register_device failed\n");
> +			input_free_device(spec->kb_dev);
> +			spec->kb_dev = NULL;
> +			return;
> +		}
> +
> +		snd_hda_add_verbs(codec, gpio_init);
> +		snd_hda_codec_write_cache(codec, codec->afg, 0,
> +					  AC_VERB_SET_GPIO_UNSOLICITED_RSP_MASK, 0x04);
> +		snd_hda_jack_detect_enable_callback(codec, codec->afg,
> +						    gpio2_mic_hotkey_event);
> +
> +		spec->gen.vmaster_mute.hook = alc_fixup_gpio_mute_hook;
> +		spec->gen.cap_sync_hook = alc_fixup_gpio_mic_mute_hook;
> +		spec->gpio_led = 0;
> +		spec->mute_led_polarity = 0;
> +		spec->gpio_mute_led_mask = 0x08;
> +		spec->gpio_mic_led_mask = 0x10;
> +		return;
> +	}
> +
> +	if (!spec->kb_dev)
> +		return;
> +
> +	switch (action) {
> +	case HDA_FIXUP_ACT_PROBE:
> +		spec->init_amp = ALC_INIT_DEFAULT;
> +		break;
> +	case HDA_FIXUP_ACT_FREE:
> +		input_unregister_device(spec->kb_dev);
> +		input_free_device(spec->kb_dev);
> +		spec->kb_dev = NULL;
> +	}
> +#endif
> +}
> +
>  static void alc269_fixup_hp_line1_mic1_led(struct hda_codec *codec,
>  				const struct hda_fixup *fix, int action)
>  {
> @@ -4341,6 +4423,7 @@ enum {
>  	ALC282_FIXUP_ASPIRE_V5_PINS,
>  	ALC280_FIXUP_HP_GPIO4,
>  	ALC286_FIXUP_HP_GPIO_LED,
> +	ALC280_FIXUP_HP_GPIO2_MIC_HOTKEY,
>  };
>  
>  static const struct hda_fixup alc269_fixups[] = {
> @@ -4814,6 +4897,10 @@ static const struct hda_fixup alc269_fixups[] = {
>  		.type = HDA_FIXUP_FUNC,
>  		.v.func = alc286_fixup_hp_gpio_led,
>  	},
> +	[ALC280_FIXUP_HP_GPIO2_MIC_HOTKEY] = {
> +		.type = HDA_FIXUP_FUNC,
> +		.v.func = alc280_fixup_hp_gpio2_mic_hotkey,
> +	},
>  };
>  
>  static const struct snd_pci_quirk alc269_fixup_tbl[] = {
> @@ -4843,6 +4930,7 @@ static const struct snd_pci_quirk alc269_fixup_tbl[] = {
>  	SND_PCI_QUIRK(0x103c, 0x1586, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC2),
>  	SND_PCI_QUIRK(0x103c, 0x18e6, "HP", ALC269_FIXUP_HP_GPIO_LED),
>  	SND_PCI_QUIRK(0x103c, 0x218b, "HP", ALC269_FIXUP_LIMIT_INT_MIC_BOOST_MUTE_LED),
> +	SND_PCI_QUIRK(0x103c, 0x225f, "HP", ALC280_FIXUP_HP_GPIO2_MIC_HOTKEY),
>  	/* ALC282 */
>  	SND_PCI_QUIRK(0x103c, 0x2210, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1),
>  	SND_PCI_QUIRK(0x103c, 0x2214, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1),
> -- 
> 1.9.1
>
diff mbox

Patch

diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c
index 65f1f4e..09d2131 100644
--- a/sound/pci/hda/patch_realtek.c
+++ b/sound/pci/hda/patch_realtek.c
@@ -29,6 +29,7 @@ 
 #include <linux/pci.h>
 #include <linux/dmi.h>
 #include <linux/module.h>
+#include <linux/input.h>
 #include <sound/core.h>
 #include <sound/jack.h>
 #include "hda_codec.h"
@@ -120,6 +121,9 @@  struct alc_spec {
 	hda_nid_t pll_nid;
 	unsigned int pll_coef_idx, pll_coef_bit;
 	unsigned int coef0;
+#if IS_ENABLED(CONFIG_INPUT)
+	struct input_dev *kb_dev;
+#endif
 };
 
 /*
@@ -3472,6 +3476,84 @@  static void alc280_fixup_hp_gpio4(struct hda_codec *codec,
 	}
 }
 
+#if IS_ENABLED(CONFIG_INPUT)
+static void gpio2_mic_hotkey_event(struct hda_codec *codec,
+				   struct hda_jack_callback *event)
+{
+	struct alc_spec *spec = codec->spec;
+
+	/* GPIO2 just toggles on a keypress/keyrelease cycle. Therefore
+	   send both key on and key off event for every interrupt. */
+	input_report_key(spec->kb_dev, KEY_MICMUTE, 1);
+	input_sync(spec->kb_dev);
+	input_report_key(spec->kb_dev, KEY_MICMUTE, 0);
+	input_sync(spec->kb_dev);
+}
+#endif
+
+static void alc280_fixup_hp_gpio2_mic_hotkey(struct hda_codec *codec,
+					     const struct hda_fixup *fix, int action)
+{
+#if IS_ENABLED(CONFIG_INPUT)
+	/* GPIO1 = set according to SKU external amp
+	   GPIO2 = mic mute hotkey
+	   GPIO3 = mute LED
+	   GPIO4 = mic mute LED */
+	static const struct hda_verb gpio_init[] = {
+		{ 0x01, AC_VERB_SET_GPIO_MASK, 0x1e },
+		{ 0x01, AC_VERB_SET_GPIO_DIRECTION, 0x1a },
+		{ 0x01, AC_VERB_SET_GPIO_DATA, 0x02 },
+		{}
+	};
+
+	struct alc_spec *spec = codec->spec;
+
+	if (action == HDA_FIXUP_ACT_PRE_PROBE) {
+		spec->kb_dev = input_allocate_device();
+		if (!spec->kb_dev) {
+			codec_err(codec, "Out of memory (input_allocate_device)\n");
+			return;
+		}
+		spec->kb_dev->name = "Microphone Mute Button";
+		spec->kb_dev->evbit[0] = BIT_MASK(EV_KEY);
+		spec->kb_dev->keybit[BIT_WORD(KEY_MICMUTE)] = BIT_MASK(KEY_MICMUTE);
+		if (input_register_device(spec->kb_dev)) {
+			codec_err(codec, "input_register_device failed\n");
+			input_free_device(spec->kb_dev);
+			spec->kb_dev = NULL;
+			return;
+		}
+
+		snd_hda_add_verbs(codec, gpio_init);
+		snd_hda_codec_write_cache(codec, codec->afg, 0,
+					  AC_VERB_SET_GPIO_UNSOLICITED_RSP_MASK, 0x04);
+		snd_hda_jack_detect_enable_callback(codec, codec->afg,
+						    gpio2_mic_hotkey_event);
+
+		spec->gen.vmaster_mute.hook = alc_fixup_gpio_mute_hook;
+		spec->gen.cap_sync_hook = alc_fixup_gpio_mic_mute_hook;
+		spec->gpio_led = 0;
+		spec->mute_led_polarity = 0;
+		spec->gpio_mute_led_mask = 0x08;
+		spec->gpio_mic_led_mask = 0x10;
+		return;
+	}
+
+	if (!spec->kb_dev)
+		return;
+
+	switch (action) {
+	case HDA_FIXUP_ACT_PROBE:
+		spec->init_amp = ALC_INIT_DEFAULT;
+		break;
+	case HDA_FIXUP_ACT_FREE:
+		input_unregister_device(spec->kb_dev);
+		input_free_device(spec->kb_dev);
+		spec->kb_dev = NULL;
+	}
+#endif
+}
+
 static void alc269_fixup_hp_line1_mic1_led(struct hda_codec *codec,
 				const struct hda_fixup *fix, int action)
 {
@@ -4341,6 +4423,7 @@  enum {
 	ALC282_FIXUP_ASPIRE_V5_PINS,
 	ALC280_FIXUP_HP_GPIO4,
 	ALC286_FIXUP_HP_GPIO_LED,
+	ALC280_FIXUP_HP_GPIO2_MIC_HOTKEY,
 };
 
 static const struct hda_fixup alc269_fixups[] = {
@@ -4814,6 +4897,10 @@  static const struct hda_fixup alc269_fixups[] = {
 		.type = HDA_FIXUP_FUNC,
 		.v.func = alc286_fixup_hp_gpio_led,
 	},
+	[ALC280_FIXUP_HP_GPIO2_MIC_HOTKEY] = {
+		.type = HDA_FIXUP_FUNC,
+		.v.func = alc280_fixup_hp_gpio2_mic_hotkey,
+	},
 };
 
 static const struct snd_pci_quirk alc269_fixup_tbl[] = {
@@ -4843,6 +4930,7 @@  static const struct snd_pci_quirk alc269_fixup_tbl[] = {
 	SND_PCI_QUIRK(0x103c, 0x1586, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC2),
 	SND_PCI_QUIRK(0x103c, 0x18e6, "HP", ALC269_FIXUP_HP_GPIO_LED),
 	SND_PCI_QUIRK(0x103c, 0x218b, "HP", ALC269_FIXUP_LIMIT_INT_MIC_BOOST_MUTE_LED),
+	SND_PCI_QUIRK(0x103c, 0x225f, "HP", ALC280_FIXUP_HP_GPIO2_MIC_HOTKEY),
 	/* ALC282 */
 	SND_PCI_QUIRK(0x103c, 0x2210, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1),
 	SND_PCI_QUIRK(0x103c, 0x2214, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1),