Commit c7440acc authored by Takashi Iwai's avatar Takashi Iwai

Merge branch 'topic/hda-micmute-led' into for-next

This is a patch set inspired by the recent patch Kai-Heng posted about
the HD-audio mic-mute LED control.  Currently HD-audio driver deals
with the mute and mic-mute LED in several different ways: primarily
with the direct callback of vmaster hook and capture sync hook, while
another with the LED class device binding.  The latter has been used
for binding with the platform device LEDs like Thinkpad, Dell,
Huawei.  And, yet, recently we added our own LED classdev for the
mic-mute LED on some HP systems although they are controlled directly
with the callback; it's exposed, however, for the DMIC that is
governed by a different ASoC driver.

This patch set is an attempt to sort out and make them consistent:
namely,
* All LEDs are now controlled via LED class device
* The generic driver provides helper functions to easily build up the
  LED class dev and the relevant mixer controls
* Conversion of the existing framework and clean ups

The patches are lightly tested in my side with a couple of machines
and also through hda-emu tests.  Some devices receive new kcontrols
for the mute LED behavior (that have been missing so far), but
anything else look good though my tests.

Link: https://lore.kernel.org/r/20200618110842.27238-1-tiwai@suse.deSigned-off-by: default avatarTakashi Iwai <tiwai@suse.de>
parents f34a4c9d c9e272f9
......@@ -8,6 +8,9 @@ config SND_HDA
select SND_JACK
select SND_HDA_CORE
config SND_HDA_GENERIC_LEDS
bool
config SND_HDA_INTEL
tristate "HD Audio PCI"
depends on SND_PCI
......@@ -91,6 +94,7 @@ config SND_HDA_PATCH_LOADER
config SND_HDA_CODEC_REALTEK
tristate "Build Realtek HD-audio codec support"
select SND_HDA_GENERIC
select SND_HDA_GENERIC_LEDS
help
Say Y or M here to include Realtek HD-audio codec support in
snd-hda-intel driver, such as ALC880.
......@@ -111,6 +115,7 @@ comment "Set to Y if you want auto-loading the codec driver"
config SND_HDA_CODEC_SIGMATEL
tristate "Build IDT/Sigmatel HD-audio codec support"
select SND_HDA_GENERIC
select SND_HDA_GENERIC_LEDS
help
Say Y or M here to include IDT (Sigmatel) HD-audio codec support in
snd-hda-intel driver, such as STAC9200.
......@@ -155,6 +160,7 @@ comment "Set to Y if you want auto-loading the codec driver"
config SND_HDA_CODEC_CONEXANT
tristate "Build Conexant HD-audio codec support"
select SND_HDA_GENERIC
select SND_HDA_GENERIC_LEDS
help
Say Y or M here to include Conexant HD-audio codec support in
snd-hda-intel driver, such as CX20549.
......@@ -215,6 +221,9 @@ comment "Set to Y if you want auto-loading the codec driver"
config SND_HDA_GENERIC
tristate "Enable generic HD-audio codec parser"
select LEDS_CLASS if SND_HDA_GENERIC_LEDS
select LEDS_TRIGGERS if SND_HDA_GENERIC_LEDS
select LEDS_TRIGGER_AUDIO if SND_HDA_GENERIC_LEDS
help
Say Y or M here to enable the generic HD-audio codec parser
in snd-hda-intel driver.
......
......@@ -3887,6 +3887,66 @@ static int parse_mic_boost(struct hda_codec *codec)
return 0;
}
#ifdef CONFIG_SND_HDA_GENERIC_LEDS
/*
* vmaster mute LED hook helpers
*/
static int create_mute_led_cdev(struct hda_codec *codec,
int (*callback)(struct led_classdev *,
enum led_brightness),
bool micmute)
{
struct led_classdev *cdev;
cdev = devm_kzalloc(&codec->core.dev, sizeof(*cdev), GFP_KERNEL);
if (!cdev)
return -ENOMEM;
cdev->name = micmute ? "hda::micmute" : "hda::mute";
cdev->max_brightness = 1;
cdev->default_trigger = micmute ? "audio-micmute" : "audio-mute";
cdev->brightness_set_blocking = callback;
cdev->brightness = ledtrig_audio_get(micmute ? LED_AUDIO_MICMUTE : LED_AUDIO_MUTE);
cdev->flags = LED_CORE_SUSPENDRESUME;
return devm_led_classdev_register(&codec->core.dev, cdev);
}
static void vmaster_update_mute_led(void *private_data, int enabled)
{
ledtrig_audio_set(LED_AUDIO_MUTE, enabled ? LED_OFF : LED_ON);
}
/**
* snd_dha_gen_add_mute_led_cdev - Create a LED classdev and enable as vmaster mute LED
* @codec: the HDA codec
* @callback: the callback for LED classdev brightness_set_blocking
*/
int snd_hda_gen_add_mute_led_cdev(struct hda_codec *codec,
int (*callback)(struct led_classdev *,
enum led_brightness))
{
struct hda_gen_spec *spec = codec->spec;
int err;
if (callback) {
err = create_mute_led_cdev(codec, callback, false);
if (err) {
codec_warn(codec, "failed to create a mute LED cdev\n");
return err;
}
}
if (spec->vmaster_mute.hook)
codec_err(codec, "vmaster hook already present before cdev!\n");
spec->vmaster_mute.hook = vmaster_update_mute_led;
spec->vmaster_mute_enum = 1;
return 0;
}
EXPORT_SYMBOL_GPL(snd_hda_gen_add_mute_led_cdev);
/*
* mic mute LED hook helpers
*/
......@@ -3921,8 +3981,8 @@ static void call_micmute_led_update(struct hda_codec *codec)
if (val == spec->micmute_led.led_value)
return;
spec->micmute_led.led_value = val;
if (spec->micmute_led.update)
spec->micmute_led.update(codec);
ledtrig_audio_set(LED_AUDIO_MICMUTE,
spec->micmute_led.led_value ? LED_ON : LED_OFF);
}
static void update_micmute_led(struct hda_codec *codec,
......@@ -3994,20 +4054,8 @@ static const struct snd_kcontrol_new micmute_led_mode_ctl = {
.put = micmute_led_mode_put,
};
/**
* snd_hda_gen_add_micmute_led - helper for setting up mic mute LED hook
* @codec: the HDA codec
* @hook: the callback for updating LED
*
* Called from the codec drivers for offering the mic mute LED controls.
* When established, it sets up cap_sync_hook and triggers the callback at
* each time when the capture mixer switch changes. The callback is supposed
* to update the LED accordingly.
*
* Returns 0 if the hook is established or a negative error code.
*/
int snd_hda_gen_add_micmute_led(struct hda_codec *codec,
void (*hook)(struct hda_codec *))
/* Set up the capture sync hook for controlling the mic-mute LED */
static int add_micmute_led_hook(struct hda_codec *codec)
{
struct hda_gen_spec *spec = codec->spec;
......@@ -4015,48 +4063,44 @@ int snd_hda_gen_add_micmute_led(struct hda_codec *codec,
spec->micmute_led.capture = 0;
spec->micmute_led.led_value = 0;
spec->micmute_led.old_hook = spec->cap_sync_hook;
spec->micmute_led.update = hook;
spec->cap_sync_hook = update_micmute_led;
if (!snd_hda_gen_add_kctl(spec, NULL, &micmute_led_mode_ctl))
return -ENOMEM;
return 0;
}
EXPORT_SYMBOL_GPL(snd_hda_gen_add_micmute_led);
#if IS_REACHABLE(CONFIG_LEDS_TRIGGER_AUDIO)
static void call_ledtrig_micmute(struct hda_codec *codec)
{
struct hda_gen_spec *spec = codec->spec;
ledtrig_audio_set(LED_AUDIO_MICMUTE,
spec->micmute_led.led_value ? LED_ON : LED_OFF);
}
#endif
/**
* snd_hda_gen_fixup_micmute_led - A fixup for mic-mute LED trigger
*
* Pass this function to the quirk entry if another driver supports the
* audio mic-mute LED trigger. Then this will bind the mixer capture switch
* change with the LED.
* snd_dha_gen_add_micmute_led_cdev - Create a LED classdev and enable as mic-mute LED
* @codec: the HDA codec
* @callback: the callback for LED classdev brightness_set_blocking
*
* Note that this fixup has to be called after other fixup that sets
* cap_sync_hook. Otherwise the chaining wouldn't work.
* Called from the codec drivers for offering the mic mute LED controls.
* This creates a LED classdev and sets up the cap_sync_hook that is called at
* each time when the capture mixer switch changes.
*
* @codec: the HDA codec
* @fix: fixup pointer
* @action: only supports HDA_FIXUP_ACT_PROBE value
* When NULL is passed to @callback, no classdev is created but only the
* LED-trigger is set up.
*
* Returns 0 or a negative error.
*/
void snd_hda_gen_fixup_micmute_led(struct hda_codec *codec,
const struct hda_fixup *fix, int action)
int snd_hda_gen_add_micmute_led_cdev(struct hda_codec *codec,
int (*callback)(struct led_classdev *,
enum led_brightness))
{
#if IS_REACHABLE(CONFIG_LEDS_TRIGGER_AUDIO)
if (action == HDA_FIXUP_ACT_PROBE)
snd_hda_gen_add_micmute_led(codec, call_ledtrig_micmute);
#endif
int err;
if (callback) {
err = create_mute_led_cdev(codec, callback, true);
if (err) {
codec_warn(codec, "failed to create a mic-mute LED cdev\n");
return err;
}
}
return add_micmute_led_hook(codec);
}
EXPORT_SYMBOL_GPL(snd_hda_gen_fixup_micmute_led);
EXPORT_SYMBOL_GPL(snd_hda_gen_add_micmute_led_cdev);
#endif /* CONFIG_SND_HDA_GENERIC_LEDS */
/*
* parse digital I/Os and set up NIDs in BIOS auto-parse mode
......
......@@ -8,6 +8,8 @@
#ifndef __SOUND_HDA_GENERIC_H
#define __SOUND_HDA_GENERIC_H
#include <linux/leds.h>
/* table entry for multi-io paths */
struct hda_multi_io {
hda_nid_t pin; /* multi-io widget pin NID */
......@@ -86,7 +88,6 @@ struct hda_micmute_hook {
unsigned int led_mode;
unsigned int capture;
unsigned int led_value;
void (*update)(struct hda_codec *codec);
void (*old_hook)(struct hda_codec *codec,
struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol);
......@@ -353,9 +354,11 @@ unsigned int snd_hda_gen_path_power_filter(struct hda_codec *codec,
void snd_hda_gen_stream_pm(struct hda_codec *codec, hda_nid_t nid, bool on);
int snd_hda_gen_fix_pin_power(struct hda_codec *codec, hda_nid_t pin);
int snd_hda_gen_add_micmute_led(struct hda_codec *codec,
void (*hook)(struct hda_codec *));
void snd_hda_gen_fixup_micmute_led(struct hda_codec *codec,
const struct hda_fixup *fix, int action);
int snd_hda_gen_add_mute_led_cdev(struct hda_codec *codec,
int (*callback)(struct led_classdev *,
enum led_brightness));
int snd_hda_gen_add_micmute_led_cdev(struct hda_codec *codec,
int (*callback)(struct led_classdev *,
enum led_brightness));
#endif /* __SOUND_HDA_GENERIC_H */
......@@ -137,14 +137,16 @@ static void cx_auto_vmaster_hook(void *private_data, int enabled)
}
/* turn on/off EAPD according to Master switch (inversely!) for mute LED */
static void cx_auto_vmaster_hook_mute_led(void *private_data, int enabled)
static int cx_auto_vmaster_mute_led(struct led_classdev *led_cdev,
enum led_brightness brightness)
{
struct hda_codec *codec = private_data;
struct hda_codec *codec = dev_to_hda_codec(led_cdev->dev->parent);
struct conexant_spec *spec = codec->spec;
snd_hda_codec_write(codec, spec->mute_led_eapd, 0,
AC_VERB_SET_EAPD_BTLENABLE,
enabled ? 0x00 : 0x02);
brightness ? 0x02 : 0x00);
return 0;
}
static int cx_auto_init(struct hda_codec *codec)
......@@ -566,7 +568,7 @@ static void cxt_fixup_mute_led_eapd(struct hda_codec *codec,
if (action == HDA_FIXUP_ACT_PRE_PROBE) {
spec->mute_led_eapd = 0x1b;
spec->dynamic_eapd = 1;
spec->gen.vmaster_mute.hook = cx_auto_vmaster_hook_mute_led;
snd_hda_gen_add_mute_led_cdev(codec, cx_auto_vmaster_mute_led);
}
}
......@@ -631,21 +633,25 @@ static void cxt_update_gpio_led(struct hda_codec *codec, unsigned int mask,
}
/* turn on/off mute LED via GPIO per vmaster hook */
static void cxt_fixup_gpio_mute_hook(void *private_data, int enabled)
static int cxt_gpio_mute_update(struct led_classdev *led_cdev,
enum led_brightness brightness)
{
struct hda_codec *codec = private_data;
struct hda_codec *codec = dev_to_hda_codec(led_cdev->dev->parent);
struct conexant_spec *spec = codec->spec;
/* muted -> LED on */
cxt_update_gpio_led(codec, spec->gpio_mute_led_mask, !enabled);
cxt_update_gpio_led(codec, spec->gpio_mute_led_mask, brightness);
return 0;
}
/* turn on/off mic-mute LED via GPIO per capture hook */
static void cxt_gpio_micmute_update(struct hda_codec *codec)
static int cxt_gpio_micmute_update(struct led_classdev *led_cdev,
enum led_brightness brightness)
{
struct hda_codec *codec = dev_to_hda_codec(led_cdev->dev->parent);
struct conexant_spec *spec = codec->spec;
cxt_update_gpio_led(codec, spec->gpio_mic_led_mask,
spec->gen.micmute_led.led_value);
cxt_update_gpio_led(codec, spec->gpio_mic_led_mask, brightness);
return 0;
}
......@@ -660,12 +666,12 @@ static void cxt_fixup_mute_led_gpio(struct hda_codec *codec,
};
if (action == HDA_FIXUP_ACT_PRE_PROBE) {
spec->gen.vmaster_mute.hook = cxt_fixup_gpio_mute_hook;
snd_hda_gen_add_mute_led_cdev(codec, cxt_gpio_mute_update);
spec->gpio_led = 0;
spec->mute_led_polarity = 0;
spec->gpio_mute_led_mask = 0x01;
spec->gpio_mic_led_mask = 0x02;
snd_hda_gen_add_micmute_led(codec, cxt_gpio_micmute_update);
snd_hda_gen_add_micmute_led_cdev(codec, cxt_gpio_micmute_update);
}
snd_hda_add_verbs(codec, gpio_init);
if (spec->gpio_led)
......@@ -988,8 +994,6 @@ static int patch_conexant_auto(struct hda_codec *codec)
cx_auto_parse_eapd(codec);
spec->gen.own_eapd_ctl = 1;
if (spec->dynamic_eapd)
spec->gen.vmaster_mute.hook = cx_auto_vmaster_hook;
switch (codec->core.vendor_id) {
case 0x14f15045:
......@@ -1022,17 +1026,8 @@ static int patch_conexant_auto(struct hda_codec *codec)
break;
}
/* Show mute-led control only on HP laptops
* This is a sort of white-list: on HP laptops, EAPD corresponds
* only to the mute-LED without actualy amp function. Meanwhile,
* others may use EAPD really as an amp switch, so it might be
* not good to expose it blindly.
*/
switch (codec->core.subsystem_id >> 16) {
case 0x103c:
spec->gen.vmaster_mute_enum = 1;
break;
}
if (!spec->gen.vmaster_mute.hook && spec->dynamic_eapd)
spec->gen.vmaster_mute.hook = cx_auto_vmaster_hook;
snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PRE_PROBE);
......
This diff is collapsed.
......@@ -320,15 +320,18 @@ static void stac_gpio_set(struct hda_codec *codec, unsigned int mask,
}
/* hook for controlling mic-mute LED GPIO */
static void stac_capture_led_update(struct hda_codec *codec)
static int stac_capture_led_update(struct led_classdev *led_cdev,
enum led_brightness brightness)
{
struct hda_codec *codec = dev_to_hda_codec(led_cdev->dev->parent);
struct sigmatel_spec *spec = codec->spec;
if (spec->gen.micmute_led.led_value)
if (brightness)
spec->gpio_data |= spec->mic_mute_led_gpio;
else
spec->gpio_data &= ~spec->mic_mute_led_gpio;
stac_gpio_set(codec, spec->gpio_mask, spec->gpio_dir, spec->gpio_data);
return 0;
}
static int stac_vrefout_set(struct hda_codec *codec,
......@@ -366,10 +369,9 @@ static unsigned int stac_vref_led_power_filter(struct hda_codec *codec,
}
/* update mute-LED accoring to the master switch */
static void stac_update_led_status(struct hda_codec *codec, int enabled)
static void stac_update_led_status(struct hda_codec *codec, bool muted)
{
struct sigmatel_spec *spec = codec->spec;
int muted = !enabled;
if (!spec->gpio_led)
return;
......@@ -393,9 +395,13 @@ static void stac_update_led_status(struct hda_codec *codec, int enabled)
}
/* vmaster hook to update mute LED */
static void stac_vmaster_hook(void *private_data, int val)
static int stac_vmaster_hook(struct led_classdev *led_cdev,
enum led_brightness brightness)
{
stac_update_led_status(private_data, val);
struct hda_codec *codec = dev_to_hda_codec(led_cdev->dev->parent);
stac_update_led_status(codec, brightness);
return 0;
}
/* automute hook to handle GPIO mute and EAPD updates */
......@@ -4313,7 +4319,7 @@ static int stac_parse_auto_config(struct hda_codec *codec)
#endif
if (spec->gpio_led)
spec->gen.vmaster_mute.hook = stac_vmaster_hook;
snd_hda_gen_add_mute_led_cdev(codec, stac_vmaster_hook);
if (spec->aloopback_ctl &&
snd_hda_get_bool_hint(codec, "loopback") == 1) {
......@@ -4636,7 +4642,7 @@ static void stac_setup_gpio(struct hda_codec *codec)
spec->gpio_dir |= spec->mic_mute_led_gpio;
spec->mic_enabled = 0;
spec->gpio_data |= spec->mic_mute_led_gpio;
snd_hda_gen_add_micmute_led(codec, stac_capture_led_update);
snd_hda_gen_add_micmute_led_cdev(codec, stac_capture_led_update);
}
}
......
......@@ -3,13 +3,11 @@
* to be included from codec driver
*/
#if IS_ENABLED(CONFIG_THINKPAD_ACPI) && IS_REACHABLE(CONFIG_LEDS_TRIGGER_AUDIO)
#if IS_ENABLED(CONFIG_THINKPAD_ACPI)
#include <linux/acpi.h>
#include <linux/leds.h>
static void (*old_vmaster_hook)(void *, int);
static bool is_thinkpad(struct hda_codec *codec)
{
return (codec->core.subsystem_id >> 16 == 0x17aa) &&
......@@ -17,25 +15,14 @@ static bool is_thinkpad(struct hda_codec *codec)
acpi_dev_found("IBM0068"));
}
static void update_tpacpi_mute_led(void *private_data, int enabled)
{
if (old_vmaster_hook)
old_vmaster_hook(private_data, enabled);
ledtrig_audio_set(LED_AUDIO_MUTE, enabled ? LED_OFF : LED_ON);
}
static void hda_fixup_thinkpad_acpi(struct hda_codec *codec,
const struct hda_fixup *fix, int action)
{
struct hda_gen_spec *spec = codec->spec;
if (action == HDA_FIXUP_ACT_PROBE) {
if (!is_thinkpad(codec))
return;
old_vmaster_hook = spec->vmaster_mute.hook;
spec->vmaster_mute.hook = update_tpacpi_mute_led;
snd_hda_gen_fixup_micmute_led(codec, fix, action);
snd_hda_gen_add_mute_led_cdev(codec, NULL);
snd_hda_gen_add_micmute_led_cdev(codec, NULL);
}
}
......
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